Редактировать soma_evaluate.py
This commit is contained in:
@@ -1,11 +1,18 @@
|
||||
# rag/functions/soma_evaluate.py
|
||||
"""
|
||||
SOMA-анализ (LLM-as-judge) для оценки качества ответа.
|
||||
Оценивает ответ по критериям: релевантность, полнота, отсутствие галлюцинаций,
|
||||
стиль, полезность. Возвращает оценки и вердикт.
|
||||
|
||||
Добавлено кэширование результатов (in-memory с TTL) для экономии токенов.
|
||||
Ключ кэша — хеш от (query, context, answer).
|
||||
TTL по умолчанию 300 секунд (5 минут), настраивается через config.soma_cache_ttl.
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import hashlib
|
||||
import time
|
||||
from typing import Dict, Any
|
||||
|
||||
from rag.services.giga_client import GigaClient
|
||||
@@ -13,6 +20,24 @@ from rag.config_models import AppConfig
|
||||
|
||||
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(
|
||||
query: str,
|
||||
@@ -55,12 +80,28 @@ async def soma_evaluate(
|
||||
"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")
|
||||
if not prompt_template:
|
||||
logger.error("Промпт для SOMA-оценки не найден")
|
||||
default_result["error"] = "Отсутствует промпт soma_evaluate"
|
||||
return default_result
|
||||
|
||||
# ---- Формирование промпта ----
|
||||
full_prompt = prompt_template.format(
|
||||
query=query,
|
||||
context=context[:3000] if context else "Нет контекста",
|
||||
@@ -93,7 +134,7 @@ async def soma_evaluate(
|
||||
threshold = getattr(config, 'soma_threshold', 3.5)
|
||||
verdict = "pass" if overall >= threshold else "fail"
|
||||
|
||||
return {
|
||||
result = {
|
||||
"scores": scores,
|
||||
"overall_score": overall,
|
||||
"verdict": verdict,
|
||||
@@ -101,6 +142,20 @@ async def soma_evaluate(
|
||||
"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:
|
||||
logger.error(f"Ошибка парсинга JSON от GigaChat: {e}, ответ: {response[:200]}")
|
||||
default_result["error"] = f"Ошибка парсинга JSON: {e}"
|
||||
|
||||
Reference in New Issue
Block a user