Редактировать soma_evaluate.py

This commit is contained in:
Markov Andrey
2026-06-30 22:40:49 +00:00
parent d2d4d800e2
commit 674efe8fc8

View File

@@ -1,11 +1,18 @@
# rag/functions/soma_evaluate.py
""" """
SOMA-анализ (LLM-as-judge) для оценки качества ответа. SOMA-анализ (LLM-as-judge) для оценки качества ответа.
Оценивает ответ по критериям: релевантность, полнота, отсутствие галлюцинаций, Оценивает ответ по критериям: релевантность, полнота, отсутствие галлюцинаций,
стиль, полезность. Возвращает оценки и вердикт. стиль, полезность. Возвращает оценки и вердикт.
Добавлено кэширование результатов (in-memory с TTL) для экономии токенов.
Ключ кэша — хеш от (query, context, answer).
TTL по умолчанию 300 секунд (5 минут), настраивается через config.soma_cache_ttl.
""" """
import json import json
import logging import logging
import hashlib
import time
from typing import Dict, Any from typing import Dict, Any
from rag.services.giga_client import GigaClient from rag.services.giga_client import GigaClient
@@ -13,6 +20,24 @@ from rag.config_models import AppConfig
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# In-memory кэш для SOMA-оценок
# Структура: {cache_key: (timestamp, result_dict)}
_soma_cache: Dict[str, tuple] = {}
# TTL по умолчанию (секунды)
_DEFAULT_TTL = 300
def _get_cache_key(query: str, context: str, answer: str) -> str:
"""
Формирует ключ кэша на основе хеша от (query, context, answer).
"""
# Ограничиваем длину контекста для хеширования, чтобы не перегружать память
# Берём первые 2000 символов контекста, чтобы избежать слишком длинных строк
context_hash = hashlib.md5(context[:2000].encode('utf-8')).hexdigest()
answer_hash = hashlib.md5(answer[:2000].encode('utf-8')).hexdigest()
query_hash = hashlib.md5(query.encode('utf-8')).hexdigest()
return f"soma:{query_hash}:{context_hash}:{answer_hash}"
async def soma_evaluate( async def soma_evaluate(
query: str, query: str,
@@ -55,12 +80,28 @@ async def soma_evaluate(
"error": None, "error": None,
} }
# ---- Проверка кэша ----
ttl = getattr(config, 'soma_cache_ttl', _DEFAULT_TTL)
cache_key = _get_cache_key(query, context, answer)
if cache_key in _soma_cache:
timestamp, cached_result = _soma_cache[cache_key]
if time.time() - timestamp < ttl:
logger.debug(f"SOMA-оценка взята из кэша (TTL {ttl} сек)")
return cached_result
else:
# Истекло — удаляем
del _soma_cache[cache_key]
logger.debug("SOMA-кэш истёк")
# ---- Проверка наличия промпта ----
prompt_template = prompts.get("soma_evaluate") prompt_template = prompts.get("soma_evaluate")
if not prompt_template: if not prompt_template:
logger.error("Промпт для SOMA-оценки не найден") logger.error("Промпт для SOMA-оценки не найден")
default_result["error"] = "Отсутствует промпт soma_evaluate" default_result["error"] = "Отсутствует промпт soma_evaluate"
return default_result return default_result
# ---- Формирование промпта ----
full_prompt = prompt_template.format( full_prompt = prompt_template.format(
query=query, query=query,
context=context[:3000] if context else "Нет контекста", context=context[:3000] if context else "Нет контекста",
@@ -93,7 +134,7 @@ async def soma_evaluate(
threshold = getattr(config, 'soma_threshold', 3.5) threshold = getattr(config, 'soma_threshold', 3.5)
verdict = "pass" if overall >= threshold else "fail" verdict = "pass" if overall >= threshold else "fail"
return { result = {
"scores": scores, "scores": scores,
"overall_score": overall, "overall_score": overall,
"verdict": verdict, "verdict": verdict,
@@ -101,6 +142,20 @@ async def soma_evaluate(
"error": None, "error": None,
} }
# ---- Сохранение в кэш ----
_soma_cache[cache_key] = (time.time(), result)
logger.debug(f"SOMA-оценка сохранена в кэш (всего {len(_soma_cache)} записей)")
# Ограничим размер кэша, чтобы не переполнить память
if len(_soma_cache) > 1000:
# Удаляем самые старые записи (по времени)
sorted_keys = sorted(_soma_cache.keys(), key=lambda k: _soma_cache[k][0])
for old_key in sorted_keys[:200]: # удаляем 200 самых старых
del _soma_cache[old_key]
logger.debug("Кэш SOMA очищен (старые записи удалены)")
return result
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
logger.error(f"Ошибка парсинга JSON от GigaChat: {e}, ответ: {response[:200]}") logger.error(f"Ошибка парсинга JSON от GigaChat: {e}, ответ: {response[:200]}")
default_result["error"] = f"Ошибка парсинга JSON: {e}" default_result["error"] = f"Ошибка парсинга JSON: {e}"