Фото (тест-режим): детальный отчёт ИИ — время, токены (приём/отдача), скорость ток/сек, детали ошибок
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -6,6 +6,7 @@ import base64
|
|||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import time
|
||||||
import urllib.request
|
import urllib.request
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
@@ -55,8 +56,12 @@ def validate_photo_with_ai(file_bytes: bytes) -> dict:
|
|||||||
"""
|
"""
|
||||||
Прямая проверка фото ИИ (тестовый мод).
|
Прямая проверка фото ИИ (тестовый мод).
|
||||||
|
|
||||||
Возвращает {'ok': bool, 'verdict': str, 'reason': str, 'error': str}
|
Возвращает {'ok': bool, 'verdict': str, 'reason': str, 'error': str,
|
||||||
|
'metrics': {'total_time', 'prompt_tokens', 'completion_tokens',
|
||||||
|
'prompt_per_second', 'predicted_per_second'},
|
||||||
|
'error_time': float}
|
||||||
"""
|
"""
|
||||||
|
t0 = time.perf_counter()
|
||||||
try:
|
try:
|
||||||
# Сжатие (PIL): разрешение настраивается в config (PHOTO_TEST_AI_MAX_SIDE)
|
# Сжатие (PIL): разрешение настраивается в config (PHOTO_TEST_AI_MAX_SIDE)
|
||||||
side = int(config.PHOTO_TEST_AI_MAX_SIDE)
|
side = int(config.PHOTO_TEST_AI_MAX_SIDE)
|
||||||
@@ -88,6 +93,18 @@ def validate_photo_with_ai(file_bytes: bytes) -> dict:
|
|||||||
|
|
||||||
with urllib.request.urlopen(req, timeout=config.PHOTO_TEST_AI_TIMEOUT) as resp:
|
with urllib.request.urlopen(req, timeout=config.PHOTO_TEST_AI_TIMEOUT) as resp:
|
||||||
res = json.loads(resp.read().decode('utf-8'))
|
res = json.loads(resp.read().decode('utf-8'))
|
||||||
|
total_time = time.perf_counter() - t0
|
||||||
|
|
||||||
|
# llama.cpp в ответ кладёт usage (токены) и timings (скорости)
|
||||||
|
usage = res.get('usage') or {}
|
||||||
|
timings = res.get('timings') or {}
|
||||||
|
metrics = {
|
||||||
|
'total_time': total_time,
|
||||||
|
'prompt_tokens': usage.get('prompt_tokens'),
|
||||||
|
'completion_tokens': usage.get('completion_tokens'),
|
||||||
|
'prompt_per_second': timings.get('prompt_per_second'),
|
||||||
|
'predicted_per_second': timings.get('predicted_per_second'),
|
||||||
|
}
|
||||||
|
|
||||||
full_content = (res['choices'][0]['message'].get('content') or '').strip()
|
full_content = (res['choices'][0]['message'].get('content') or '').strip()
|
||||||
clean_ans = full_content.split(THINK_CLOSE_TAG)[-1].strip() if THINK_CLOSE_TAG in full_content else full_content.strip()
|
clean_ans = full_content.split(THINK_CLOSE_TAG)[-1].strip() if THINK_CLOSE_TAG in full_content else full_content.strip()
|
||||||
@@ -114,10 +131,18 @@ def validate_photo_with_ai(file_bytes: bytes) -> dict:
|
|||||||
if not reason:
|
if not reason:
|
||||||
reason = clean_ans.replace('\n', ' ').strip()
|
reason = clean_ans.replace('\n', ' ').strip()
|
||||||
|
|
||||||
logger.info(f"[TEST MODE] ИИ-вердикт: {verdict} | мимика={mimi} | графика={grafika} | {reason}")
|
logger.info(
|
||||||
|
f"[TEST MODE] ИИ-вердикт: {verdict} | мимика={mimi} | графика={grafika} | {reason}"
|
||||||
|
f" | total={metrics['total_time']:.2f}s "
|
||||||
|
f"prompt={metrics['prompt_tokens']}tok/({metrics['prompt_per_second']:.1f} tps) "
|
||||||
|
f"gen={metrics['completion_tokens']}tok/({metrics['predicted_per_second']:.1f} tps)"
|
||||||
|
)
|
||||||
return {'ok': True, 'verdict': verdict, 'reason': reason, 'error': '',
|
return {'ok': True, 'verdict': verdict, 'reason': reason, 'error': '',
|
||||||
'mimi': mimi, 'grafika': grafika}
|
'mimi': mimi, 'grafika': grafika, 'metrics': metrics}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[TEST MODE] Ошибка обращения к ИИ: {e}")
|
err = f"{type(e).__name__}: {e}"
|
||||||
return {'ok': False, 'verdict': '', 'reason': '', 'error': str(e)}
|
elapsed = time.perf_counter() - t0
|
||||||
|
logger.error(f"[TEST MODE] Ошибка обращения к ИИ через {elapsed:.1f} сек: {err}")
|
||||||
|
return {'ok': False, 'verdict': '', 'reason': '', 'error': err,
|
||||||
|
'error_time': elapsed}
|
||||||
|
|||||||
@@ -323,7 +323,10 @@ async def photo_module_handler(msg: Message):
|
|||||||
if not ai_res['ok']:
|
if not ai_res['ok']:
|
||||||
# Фото остаётся в pending — можно повторить проверку (кнопка 1)
|
# Фото остаётся в pending — можно повторить проверку (кнопка 1)
|
||||||
log_menu_stats(user_id, "Photo Bot (TEST)", "Ошибка обращения к ИИ")
|
log_menu_stats(user_id, "Photo Bot (TEST)", "Ошибка обращения к ИИ")
|
||||||
await msg.answer(photo_test_ai_error(), parse_mode="html")
|
await msg.answer(
|
||||||
|
photo_test_ai_error(ai_res.get('error', ''), ai_res.get('error_time')),
|
||||||
|
parse_mode="html"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
verdict = ai_res['verdict']
|
verdict = ai_res['verdict']
|
||||||
@@ -333,7 +336,10 @@ async def photo_module_handler(msg: Message):
|
|||||||
log_menu_stats(user_id, "Photo Bot (TEST)", f"ИИ-вердикт: {verdict}")
|
log_menu_stats(user_id, "Photo Bot (TEST)", f"ИИ-вердикт: {verdict}")
|
||||||
|
|
||||||
# Фото остаётся в pending — можно повторить проверку другим фото (кнопка 2)
|
# Фото остаётся в pending — можно повторить проверку другим фото (кнопка 2)
|
||||||
await msg.answer(photo_test_result(verdict, reason, mimi, grafika), parse_mode="html")
|
await msg.answer(
|
||||||
|
photo_test_result(verdict, reason, mimi, grafika, ai_res.get('metrics')),
|
||||||
|
parse_mode="html"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
elif cmd == "2":
|
elif cmd == "2":
|
||||||
|
|||||||
Binary file not shown.
+40
-6
@@ -309,7 +309,8 @@ def _photo_test_ai_checking(emojis: dict) -> str:
|
|||||||
def _photo_test_receiving(emojis: dict) -> str:
|
def _photo_test_receiving(emojis: dict) -> str:
|
||||||
return "🧪 <i>Ищу лицо на фото (тестовый режим), пожалуйста, подождите...</i>"
|
return "🧪 <i>Ищу лицо на фото (тестовый режим), пожалуйста, подождите...</i>"
|
||||||
|
|
||||||
def _photo_test_result(verdict: str, reason: str, mimi: str = '', grafika: str = '', emojis: dict = None) -> str:
|
def _photo_test_result(verdict: str, reason: str, mimi: str = '', grafika: str = '',
|
||||||
|
ai_metrics: dict = None, emojis: dict = None) -> str:
|
||||||
emojis = emojis or {}
|
emojis = emojis or {}
|
||||||
icons = {"Прошел": "✅", "Не прошел": "❌", "На модерацию": "⏳"}
|
icons = {"Прошел": "✅", "Не прошел": "❌", "На модерацию": "⏳"}
|
||||||
icon = icons.get(verdict, "ℹ️")
|
icon = icons.get(verdict, "ℹ️")
|
||||||
@@ -322,19 +323,51 @@ def _photo_test_result(verdict: str, reason: str, mimi: str = '', grafika: str =
|
|||||||
detail = "\n".join(lines) + "\n\n"
|
detail = "\n".join(lines) + "\n\n"
|
||||||
else:
|
else:
|
||||||
detail = ""
|
detail = ""
|
||||||
|
metrics_block = ''
|
||||||
|
if ai_metrics:
|
||||||
|
ml = ["📊 <b>Технические показатели</b>"]
|
||||||
|
tt = ai_metrics.get('total_time')
|
||||||
|
if tt is not None:
|
||||||
|
ml.append(f"⏱ Время ответа: <b>{tt:.2f} сек</b>")
|
||||||
|
pt = ai_metrics.get('prompt_tokens')
|
||||||
|
gps = ai_metrics.get('prompt_per_second')
|
||||||
|
if pt is not None:
|
||||||
|
s = f"⬇️ Приём (промпт): <b>{pt} токенов</b>"
|
||||||
|
if gps is not None:
|
||||||
|
s += f" — {gps:.1f} ток/сек"
|
||||||
|
ml.append(s)
|
||||||
|
ct = ai_metrics.get('completion_tokens')
|
||||||
|
ggs = ai_metrics.get('predicted_per_second')
|
||||||
|
if ct is not None:
|
||||||
|
s = f"⬆️ Отдача (генерация): <b>{ct} токенов</b>"
|
||||||
|
if ggs is not None:
|
||||||
|
s += f" — {ggs:.1f} ток/сек"
|
||||||
|
ml.append(s)
|
||||||
|
if len(ml) > 1:
|
||||||
|
metrics_block = "\n".join(ml) + "\n\n"
|
||||||
return (
|
return (
|
||||||
f"🧪 <b>Тестовый режим: вердикт ИИ</b>\n\n"
|
f"🧪 <b>Тестовый режим: вердикт ИИ</b>\n\n"
|
||||||
f"{icon} <b>Вердикт:</b> {verdict}\n"
|
f"{icon} <b>Вердикт:</b> {verdict}\n"
|
||||||
f"{detail}"
|
f"{detail}"
|
||||||
f"📝 <b>Причина:</b> {reason}\n\n"
|
f"📝 <b>Причина:</b> {reason}\n\n"
|
||||||
|
f"{metrics_block}"
|
||||||
f"⚠️ <i>Тестовый режим: фото не публиковалось и никуда не отправлялось.</i>\n\n"
|
f"⚠️ <i>Тестовый режим: фото не публиковалось и никуда не отправлялось.</i>\n\n"
|
||||||
f"{emojis.get('2', '2')} — Загрузить другое фото\n"
|
f"{emojis.get('2', '2')} — Загрузить другое фото\n"
|
||||||
f"{emojis.get('0', '0')} — В главное меню"
|
f"{emojis.get('0', '0')} — В главное меню"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _photo_test_ai_error(emojis: dict) -> str:
|
def _photo_test_ai_error(emojis: dict, error: str = '', elapsed: float = None) -> str:
|
||||||
|
detail = ''
|
||||||
|
err = (error or '').strip()
|
||||||
|
if len(err) > 400:
|
||||||
|
err = err[:400] + '…'
|
||||||
|
if err:
|
||||||
|
detail += f"📋 <b>Детали:</b> <i>{err}</i>\n"
|
||||||
|
if elapsed is not None:
|
||||||
|
detail += f"⏱ Время до ошибки: <b>{elapsed:.1f} сек</b>\n"
|
||||||
return (
|
return (
|
||||||
f"❌ <b>Ошибка обращения к ИИ.</b>\n\n"
|
f"❌ <b>Ошибка обращения к ИИ.</b>\n\n"
|
||||||
|
f"{detail}\n"
|
||||||
f"Проверьте доступность ИИ-сервиса. Повторите попытку позже.\n\n"
|
f"Проверьте доступность ИИ-сервиса. Повторите попытку позже.\n\n"
|
||||||
f"{emojis['1']} — Повторить проверку\n"
|
f"{emojis['1']} — Повторить проверку\n"
|
||||||
f"{emojis['2']} — Загрузить другое фото\n"
|
f"{emojis['2']} — Загрузить другое фото\n"
|
||||||
@@ -428,11 +461,12 @@ PHOTO_TEST_PREVIEW_TEXT = _photo_test_preview(EMOJI_DIGITS)
|
|||||||
PHOTO_TEST_AI_CHECKING_TEXT = _photo_test_ai_checking(EMOJI_DIGITS)
|
PHOTO_TEST_AI_CHECKING_TEXT = _photo_test_ai_checking(EMOJI_DIGITS)
|
||||||
PHOTO_TEST_RECEIVING_TEXT = _photo_test_receiving(EMOJI_DIGITS)
|
PHOTO_TEST_RECEIVING_TEXT = _photo_test_receiving(EMOJI_DIGITS)
|
||||||
|
|
||||||
def photo_test_result(verdict: str, reason: str, mimi: str = '', grafika: str = '') -> str:
|
def photo_test_result(verdict: str, reason: str, mimi: str = '', grafika: str = '',
|
||||||
return _photo_test_result(verdict, reason, mimi, grafika, EMOJI_DIGITS)
|
ai_metrics: dict = None) -> str:
|
||||||
|
return _photo_test_result(verdict, reason, mimi, grafika, ai_metrics, EMOJI_DIGITS)
|
||||||
|
|
||||||
def photo_test_ai_error() -> str:
|
def photo_test_ai_error(error: str = '', elapsed: float = None) -> str:
|
||||||
return _photo_test_ai_error(EMOJI_DIGITS)
|
return _photo_test_ai_error(EMOJI_DIGITS, error, elapsed)
|
||||||
|
|
||||||
|
|
||||||
# =========================================================
|
# =========================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user