Редактировать soma_evaluate.py
This commit is contained in:
@@ -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}"
|
||||||
|
|||||||
Reference in New Issue
Block a user