117 lines
4.4 KiB
Python
117 lines
4.4 KiB
Python
# rag/functions/soma_evaluate.py
|
||
"""
|
||
SOMA-анализ (LLM-as-judge) для оценки качества ответа.
|
||
Оценивает ответ по критериям: релевантность, полнота, отсутствие галлюцинаций,
|
||
стиль, полезность. Возвращает оценки и вердикт.
|
||
"""
|
||
|
||
import json
|
||
import logging
|
||
from typing import Dict, Any, Optional
|
||
|
||
from rag.services.giga_client import GigaClient
|
||
from rag.config_models import AppConfig
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
async def soma_evaluate(
|
||
query: str,
|
||
context: str,
|
||
answer: str,
|
||
config: AppConfig,
|
||
giga: GigaClient,
|
||
prompts: Dict[str, str],
|
||
) -> Dict[str, Any]:
|
||
"""
|
||
Оценивает ответ LLM с помощью другого вызова GigaChat.
|
||
|
||
Аргументы:
|
||
query: исходный запрос пользователя.
|
||
context: контекст из базы знаний (может быть пустым).
|
||
answer: сгенерированный ответ.
|
||
config: объект конфигурации.
|
||
giga: клиент GigaChat для вызова.
|
||
prompts: словарь с содержимым промптов (ключ 'soma_evaluate').
|
||
|
||
Возвращает:
|
||
dict с полями:
|
||
scores: dict с оценками по критериям (числа от 1 до 5).
|
||
overall_score: средняя оценка (float).
|
||
verdict: 'pass' или 'fail' (в зависимости от порога).
|
||
feedback: текстовое обоснование.
|
||
error: строка ошибки (если есть).
|
||
"""
|
||
default_result = {
|
||
"scores": {
|
||
"relevance": 3.0,
|
||
"completeness": 3.0,
|
||
"no_hallucination": 3.0,
|
||
"style": 3.0,
|
||
"usefulness": 3.0,
|
||
},
|
||
"overall_score": 3.0,
|
||
"verdict": "fail",
|
||
"feedback": "Оценка не удалась, возвращены значения по умолчанию.",
|
||
"error": None,
|
||
}
|
||
|
||
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 "Нет контекста",
|
||
answer=answer,
|
||
)
|
||
|
||
# Параметры из конфига (можно сделать отдельную секцию)
|
||
temperature = getattr(config, 'soma_temperature', 0.1) # низкая для детерминизма
|
||
timeout = getattr(config, 'soma_timeout', 30)
|
||
|
||
try:
|
||
response = await giga.chat(
|
||
history=[],
|
||
query=full_prompt,
|
||
system_prompt=None,
|
||
file_id=None,
|
||
temperature=temperature,
|
||
timeout=timeout, # нужно добавить timeout в GigaClient.chat (если нет, игнорировать)
|
||
)
|
||
# Ожидаем JSON
|
||
data = json.loads(response.strip())
|
||
scores = data.get("scores", {})
|
||
feedback = data.get("feedback", "")
|
||
|
||
# Приводим оценки к числам
|
||
for key in scores:
|
||
try:
|
||
scores[key] = float(scores[key])
|
||
except (ValueError, TypeError):
|
||
scores[key] = 3.0
|
||
overall = sum(scores.values()) / len(scores) if scores else 3.0
|
||
|
||
# Порог можно задать в конфиге
|
||
threshold = getattr(config, 'soma_threshold', 3.5)
|
||
verdict = "pass" if overall >= threshold else "fail"
|
||
|
||
return {
|
||
"scores": scores,
|
||
"overall_score": overall,
|
||
"verdict": verdict,
|
||
"feedback": feedback,
|
||
"error": None,
|
||
}
|
||
|
||
except json.JSONDecodeError as e:
|
||
logger.error(f"Ошибка парсинга JSON от GigaChat: {e}, ответ: {response[:200]}")
|
||
default_result["error"] = f"Ошибка парсинга JSON: {e}"
|
||
return default_result
|
||
except Exception as e:
|
||
logger.exception(f"Ошибка при вызове SOMA-оценки: {e}")
|
||
default_result["error"] = str(e)
|
||
return default_result |