拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python后端AI专题32:建立第一套 RAG 评测集:问题、证据与不可回答样本

Python后端AI专题32:建立第一套 RAG 评测集:问题、证据与不可回答样本

Python后端AI专题32:建立第一套 RAG 评测集:问题、证据与不可回答样本

没有固定评测集,“换模型后感觉更聪明”无法复核;只收集能回答的问题,又会把胡编但流畅的模型选上去。评测数据至少要同时表达:用户问题、哪些 chunk 相关、答案必须包含哪些事实、这个问题是否本应拒答。

供应商升级测试门答案

层用例失败说明
Provider普通响应字段/usage 映射v2 HTTP 合同变化
Provider空 delta + token + DONE流解析不兼容
Gateway429 可重试、400 不重试、超时取消稳定性策略回归
离线 E2E上传到引用回答应用接线回归,与真实模型质量无关
真实评测v1/v2 同数据集对比质量、成本或延迟变化

完整流合同测试使用 MockTransport 返回三帧,只得到['七天']:

content=('data: {"choices":[{"delta":{}}]}\n\n''data: {"choices":[{"delta":{"content":"七天"}}]}\n\n''data: [DONE]\n\n')

真实结果:3 passed in 0.25s。

样本字段为什么这样设计

@dataclass(frozen=True,slots=True)classEvaluationCase:id:strquestion:strrelevant_ids:list[str]expected_facts:list[str]unanswerable:bool
  • id 稳定引用失败样本;
  • question 保留真实用户表达,不只写文档标题;
  • relevant_ids 评估检索,与生成质量分开;
  • expected_facts 做确定性答案基线;
  • unanswerable 检验拒答,不让空 relevant 被误当标注漏失。

相关性最好由至少两位标注者复核;若多个 chunk 都能支持答案,应全部标记,否则 Recall 会错误惩罚合法召回。

当前数据集不是三个占位问题

现有 8 条覆盖:退款期限、产品端口、健康路径、向量错误、验证码、索引状态,以及董事长电话/办公室地址两个不可回答问题。示例:

{"id":"vector-error","question":"出现 E_VECTOR_02 应该怎么办?","relevant_ids":["product-manual"],"expected_facts":["重建","索引版本"],"unanswerable":false}{"id":"office-address","question":"公司北京办公室地址在哪里?","relevant_ids":[],"expected_facts":[],"unanswerable":true}

生产评测还应加入:口语改写、错别字、长条件、表格、跨文档冲突、权限和 Injection。数据集要从真实失败日志脱敏沉淀,而不是由开发者只写系统擅长的问题。

完整加载与校验模块

from__future__importannotationsimportjsonfromdataclassesimportdataclassfrompathlibimportPath@dataclass(frozen=True,slots=True)classEvaluationCase:id:strquestion:strrelevant_ids:list[str]expected_facts:list[str]unanswerable:booldefload_cases(path:Path)->list[EvaluationCase]:cases:list[EvaluationCase]=[]seen:set[str]=set()forline_number,lineinenumerate(path.read_text(encoding="utf-8").splitlines(),start=1):ifnotline.strip():continueraw=json.loads(line)case=EvaluationCase(id=str(raw["id"]),question=str(raw["question"]),relevant_ids=list(raw.get("relevant_ids",[])),expected_facts=list(raw.get("expected_facts",[])),unanswerable=bool(raw.get("unanswerable",False)),)ifcase.idinseen:raiseValueError(f"duplicate case id{case.id}at line{line_number}")ifnotcase.unanswerableandnotcase.relevant_ids:raiseValueError(f"case{case.id}requires relevant_ids")ifcase.unanswerableandcase.relevant_ids:raiseValueError(f"unanswerable case{case.id}cannot have relevant_ids")seen.add(case.id)cases.append(case)ifnotcases:raiseValueError("evaluation dataset is empty")returncases

JSONL 允许逐条追加和 diff;加载器拒绝重复 id、可回答却无相关证据、不可回答却标相关证据、空文件。Schema 校验是数据质量第一关,不会验证标注事实本身正确。

如何避免数据泄漏

调参时反复看同一评测集会过拟合。应分 dev/test:dev 可用于选 chunk size/Prompt,test 只在候选版本确定后运行;最终还有线上灰度。真实客户数据需脱敏、获得授权并控制访问,不能为了评测把私人问题提交到代码仓库。

Fake 评测的 1.0 代表什么

当前脚本的 Fake evaluator 根据 expected_facts 直接构造答案,因此 1.0 只证明 Runner、数据读取和报表计算接线正确:

Recall@5=1.0, MRR=1.0, nDCG@5=1.0, citation_precision=1.0, answer_accuracy=1.0

它绝不是 KnowFlow 真实语义质量。真实 Provider 必须用同一 EvaluationFunction 接口跑并单独记录模型、Prompt、索引哈希。

本篇最终完整模块:dataset.py

前面的代码片段用于解释本次改动;下面是本篇结束时可直接核对和替换的磁盘完整版本。

from__future__importannotationsimportjsonfromdataclassesimportdataclassfrompathlibimportPath@dataclass(frozen=True,slots=True)classEvaluationCase:id:strquestion:strrelevant_ids:list[str]expected_facts:list[str]unanswerable:booldefload_cases(path:Path)->list[EvaluationCase]:cases:list[EvaluationCase]=[]seen:set[str]=set()forline_number,lineinenumerate(path.read_text(encoding="utf-8").splitlines(),start=1):ifnotline.strip():continueraw=json.loads(line)case=EvaluationCase(id=str(raw["id"]),question=str(raw["question"]),relevant_ids=list(raw.get("relevant_ids",[])),expected_facts=list(raw.get("expected_facts",[])),unanswerable=bool(raw.get("unanswerable",False)),)ifcase.idinseen:raiseValueError(f"duplicate case id{case.id}at line{line_number}")ifnotcase.unanswerableandnotcase.relevant_ids:raiseValueError(f"case{case.id}requires relevant_ids")ifcase.unanswerableandcase.relevant_ids:raiseValueError(f"unanswerable case{case.id}cannot have relevant_ids")seen.add(case.id)cases.append(case)ifnotcases:raiseValueError("evaluation dataset is empty")returncases

本篇练习:检查一组坏标注

为 loader 写四个测试:重复 id;answerable 无 relevant;unanswerable 却有 relevant;空文件。再判断以下样本应怎样改:问题“退款多久到账”,expected_facts 写“七天”,相关文档其实只说“七天内申请”。说明为什么这是答案标注错误而不是模型错误。

下一篇给出测试答案,并手算 Recall@K、MRR、nDCG 与引用精度,解释为什么不能只看一个总分。

返回列表