74 lines
3.1 KiB
Python
74 lines
3.1 KiB
Python
|
|
import json
|
|||
|
|
import logging
|
|||
|
|
from backend.services.llm_service import llm_service
|
|||
|
|
|
|||
|
|
logger = logging.getLogger(__name__)
|
|||
|
|
|
|||
|
|
class RAGEvaluator:
|
|||
|
|
def __init__(self):
|
|||
|
|
self.llm = llm_service
|
|||
|
|
|
|||
|
|
def calculate_retrieval_metrics(self, retrieved_docs, dataset_item):
|
|||
|
|
"""
|
|||
|
|
计算检索阶段指标: Keyword Recall (关键词覆盖率)
|
|||
|
|
检查 dataset 中的 keywords 有多少出现在了 retrieved_docs 的 content 中
|
|||
|
|
"""
|
|||
|
|
required_keywords = dataset_item.get("keywords", [])
|
|||
|
|
if not required_keywords:
|
|||
|
|
return {"keyword_recall": 1.0, "hit": True} # 没有关键词要求,默认算对
|
|||
|
|
|
|||
|
|
# 将所有检索到的文本拼接并转小写
|
|||
|
|
full_context = " ".join([doc['content'] for doc in retrieved_docs]).lower()
|
|||
|
|
|
|||
|
|
found_count = 0
|
|||
|
|
for kw in required_keywords:
|
|||
|
|
if kw.lower() in full_context:
|
|||
|
|
found_count += 1
|
|||
|
|
|
|||
|
|
recall = found_count / len(required_keywords)
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"keyword_recall": recall,
|
|||
|
|
# 只要召回率大于 0 就认为 Hit 了一部分;
|
|||
|
|
# 严格一点可以要求 recall > 0.5,这里我们设定只要沾边就算 Hit
|
|||
|
|
"hit": recall > 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def evaluate_generation_quality(self, question, generated_answer, ground_truth_answer, q_type):
|
|||
|
|
"""
|
|||
|
|
使用 LLM 作为裁判,评估生成质量 (1-5分)
|
|||
|
|
"""
|
|||
|
|
prompt = f"""
|
|||
|
|
你是一名RAG系统的自动化测试裁判。请根据以下信息对“系统回答”进行评分(1-5分)。
|
|||
|
|
|
|||
|
|
【测试类型】: {q_type}
|
|||
|
|
【用户问题】: {question}
|
|||
|
|
【标准答案 (Ground Truth)】: {ground_truth_answer}
|
|||
|
|
【系统回答】: {generated_answer}
|
|||
|
|
|
|||
|
|
评分标准:
|
|||
|
|
- 5分: 含义与标准答案完全一致,逻辑正确,无幻觉。
|
|||
|
|
- 4分: 核心意思正确,但缺少部分细节或废话较多。
|
|||
|
|
- 3分: 回答了一部分正确信息,但有遗漏或轻微错误。
|
|||
|
|
- 2分: 包含大量错误信息或严重答非所问。
|
|||
|
|
- 1分: 完全错误,或产生了严重幻觉(例如在负向测试中编造了不存在的功能)。
|
|||
|
|
|
|||
|
|
注意:对于"negative_test"(负向测试),如果标准答案是“不支持/文档未提及”,而系统回答诚实地说“未找到相关信息”或“不支持”,应给满分。
|
|||
|
|
|
|||
|
|
请仅返回JSON格式: {{"score": 5, "reason": "理由..."}}
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
# 使用 system_prompt 强制约束格式
|
|||
|
|
result_str = self.llm.chat(prompt, system_prompt="你是一个只输出JSON的评测机器人。")
|
|||
|
|
|
|||
|
|
# 清洗 Markdown 格式 (```json ... ```)
|
|||
|
|
if "```" in result_str:
|
|||
|
|
result_str = result_str.split("```json")[-1].split("```")[0].strip()
|
|||
|
|
|
|||
|
|
eval_result = json.loads(result_str)
|
|||
|
|
return eval_result
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"Eval LLM failed: {e}")
|
|||
|
|
# 降级处理
|
|||
|
|
return {"score": 0, "reason": "Evaluation Script Error"}
|