photo_bot: тестовый мод — фото напрямую в ИИ, без публикации
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
# /opt/trueconf_bot/photo_bot/ai_validator.py
|
||||
# Тестовый мод фото-бота: прямая проверка фото ИИ (llama.cpp).
|
||||
# Endpoint, модель и промпт — 1:1 из SibCemProfile/PhotoApp/photo_validator.py.
|
||||
# Фото никуда не отправляется: только в ИИ, ответ возвращается пользователю.
|
||||
import base64
|
||||
import io
|
||||
import json
|
||||
import logging
|
||||
import urllib.request
|
||||
from PIL import Image
|
||||
|
||||
import config.config as config
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Тег рассуждений thinking-моделей (конкатенация без литерала)
|
||||
THINK_CLOSE_TAG = '</' + 'think>'
|
||||
|
||||
|
||||
def _build_prompt() -> str:
|
||||
"""Промпт проверки фото (без подсказки MediaPipe — в тест-моде геометрия не считается)."""
|
||||
cv_hint = 'ℹ️ ПОДСКАЗКА ОТ ALGORITHM: Геометрическая проверка в тестовом режиме не выполняется, проверяй визуально.'
|
||||
|
||||
return ('''Ты — автоматический эксперт пропускной системы. Проверь фотографию сотрудника на соответствие регламенту.
|
||||
|
||||
%s
|
||||
|
||||
ЗАПРЕЩЕНО выводить внутренние рассуждения и теги размышлений. Сразу пиши финальный ответ.
|
||||
|
||||
БЛОК 0. КРИТИЧЕСКИЕ СТОП-ФАКТОРЫ:
|
||||
1. Не реальное фото: ИИ-генерация, 3D-арт, цифровой рисунок, мультфильм, стилизованный портрет, сильная обработка фильтрами.
|
||||
2. Графика и текст: Любые надписи, водяные знаки, рамки.
|
||||
3. Костюмы и атрибуты: Исторические костюмы, короны, маски, каски (кроме строительных).
|
||||
4. Посторонние предметы: Оружие, предметы перед лицом.
|
||||
5. Неправильный ракурс и взгляд: Строгий боковой профиль, затылок, человек смотрит совсем в сторону.
|
||||
|
||||
БЛОК 1. МАСШТАБ И КАДРИРОВАНИЕ:
|
||||
- подбородок НИЖЕ середины кадра → тест пройден.
|
||||
- «Не прошел», если видны талия, живот, подмышки, одежда ниже линии груди.
|
||||
|
||||
ИТОГОВЫЙ ВЕРДИКТ:
|
||||
- «Прошел» — нормальные портретные фотографии.
|
||||
- «Не прошел» — только явные нарушения.
|
||||
- «На модерацию» — если фото качественное, но масштаб на грани.
|
||||
|
||||
ФОРМАТ ОТВЕТА — СТРОГО 2 строки:
|
||||
Вердикт: [Прошел / Не прошел / На модерацию]
|
||||
Причина: [1–2 предложения с указанием конкретной причины]
|
||||
''') % cv_hint
|
||||
|
||||
|
||||
def validate_photo_with_ai(file_bytes: bytes) -> dict:
|
||||
"""
|
||||
Прямая проверка фото ИИ (тестовый мод).
|
||||
|
||||
Возвращает {'ok': bool, 'verdict': str, 'reason': str, 'error': str}
|
||||
"""
|
||||
try:
|
||||
# Сжатие как в photo_validator.py (PIL, без сторонних DLL)
|
||||
with Image.open(io.BytesIO(file_bytes)) as img:
|
||||
img = img.convert('RGB')
|
||||
img.thumbnail((768, 768))
|
||||
buffer = io.BytesIO()
|
||||
img.save(buffer, format='JPEG', quality=85)
|
||||
b64_img = base64.b64encode(buffer.getvalue()).decode('utf-8')
|
||||
|
||||
payload = {
|
||||
'model': config.PHOTO_TEST_AI_MODEL,
|
||||
'messages': [{
|
||||
'role': 'user',
|
||||
'content': [
|
||||
{'type': 'text', 'text': _build_prompt()},
|
||||
{'type': 'image_url', 'image_url': {'url': 'data:image/jpeg;base64,' + b64_img}}
|
||||
]
|
||||
}],
|
||||
'temperature': 0.1,
|
||||
'max_tokens': 1000
|
||||
}
|
||||
|
||||
req = urllib.request.Request(
|
||||
config.PHOTO_TEST_AI_URL,
|
||||
data=json.dumps(payload).encode('utf-8'),
|
||||
headers={'Content-Type': 'application/json'}
|
||||
)
|
||||
|
||||
with urllib.request.urlopen(req, timeout=config.PHOTO_TEST_AI_TIMEOUT) as resp:
|
||||
res = json.loads(resp.read().decode('utf-8'))
|
||||
|
||||
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()
|
||||
|
||||
text_lower = clean_ans.lower()
|
||||
if 'вердикт: прошел' in text_lower or ('прошел' in text_lower and 'не прошел' not in text_lower and 'модерац' not in text_lower):
|
||||
verdict = 'Прошел'
|
||||
elif 'на модерацию' in text_lower or 'модерац' in text_lower:
|
||||
verdict = 'На модерацию'
|
||||
else:
|
||||
verdict = 'Не прошел'
|
||||
|
||||
reason = ''
|
||||
for line in clean_ans.splitlines():
|
||||
if line.strip().lower().startswith('причина:'):
|
||||
reason = line.split(':', 1)[1].strip()
|
||||
break
|
||||
if not reason:
|
||||
reason = clean_ans.replace('\n', ' ').strip()
|
||||
|
||||
logger.info(f"[TEST MODE] ИИ-вердикт: {verdict} | {reason}")
|
||||
return {'ok': True, 'verdict': verdict, 'reason': reason, 'error': ''}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[TEST MODE] Ошибка обращения к ИИ: {e}")
|
||||
return {'ok': False, 'verdict': '', 'reason': '', 'error': str(e)}
|
||||
Reference in New Issue
Block a user