从“能回答”到“可评测”:OptiRAG 光学垂直 RAG 的数据集构建、错误分析与优化实践
很多 RAG 项目做到最后,只能展示几个看起来不错的问答截图。系统究竟召回了什么、
答案是否真的受文档支持、换一种检索方案是否更好,往往没有可靠证据。
OptiRAG 最初也更接近一个功能型原型:可以上传光学资料、检索文档、回答问题,还
包含实验方案设计能力。但如果没有固定数据集、统一指标和失败案例分析,就很难判断
项目是在进步,还是只是在增加功能。
因此,我们把工作重点从“再增加一个 Agent”转向“建立一个可评测的光学垂直
RAG”。最终完成了 100 条光学科研问答、四种检索基线、端到端生成评测、10 个以上
失败案例归因,以及一轮在独立 Dev 上选择、在冻结 Test 上验证的跨文档检索优化。
本文完整记录这条路径,包括有效的方法,也包括没有成功的实验。
一、先定义什么叫“做完”
项目开始前,我们先固定了交付标准:
- 建立 100 条光学领域测试题;
- 每题标注参考答案、引用文档和相关原文段落;
- 对比 BM25、纯向量、BM25 + 向量、图 + 混合检索;
- 记录 Recall@5、MRR@5、nDCG@5、答案正确率、引用正确率、幻觉率、P50/P95
延迟和单次调用成本; - 至少分析 10 个失败案例;
- 根据错误分析完成一次可验证优化。
整个流程遵循一个原则:先冻结评价标准,再比较方案;先在 Dev 上选择参数,再验证,
不能看到 Test 逐题结果后不断修改算法直到数字好看。
二、构建可审计的光学问答数据集
2.1 语料选择
语料来自 12 份 MIT OpenCourseWare 与 NIST 光学资料,覆盖几何光学、波动光学、
偏振、Gaussian 光束、光纤、激光、Q-switching、光谱响应度标定等主题。解析后形成
577 个 chunks。
我们没有只保存纯文本,还记录了:
chunk_id、document_id、页码和标题;- 原始文本与来源文件哈希;
- 内容类型和来源清单;
- 文档许可与内部使用边界。
这些元数据使评测结果能够回到具体文档和页码,而不是停留在“模型说它来自某篇
论文”的不可验证状态。
2.2 题型设计
100 道题由 20 道 Dev 和 80 道 Test 组成,类别分布如下:
| 类别 | 数量 | 主要考察能力 |
|---|---|---|
| concept | 15 | 光学概念理解 |
| device_parameter | 15 | 仪器与器件参数定位 |
| experiment_method | 15 | 实验步骤和方法 |
| formula | 15 | 公式、变量和条件 |
| table_figure | 15 | 表格与图中信息 |
| multi_chunk | 10 | 同文档多段证据组合 |
| multi_document | 10 | 跨文档证据组合与比较 |
| unanswerable | 5 | 证据不足时拒答 |
题型分层很重要。如果全部是单段抽取题,系统即使无法处理表格、跨段落或跨文档
问题,也可能得到很高的平均分。
2.3 标注结构
每道题至少包含:
{
"question_id": "optics_test_067",
"question": "偏振讲义与双偏振片实验分别如何描述……?",
"reference_answer": "……",
"answerable": true,
"citation_document_ids": ["doc_a", "doc_b"],
"relevant_chunk_ids": ["chunk_a", "chunk_b"],
"relevant_spans": [
{"chunk_id": "chunk_a", "quote": "原文证据", "start": 10, "end": 42}
],
"category": "multi_document",
"difficulty": "hard"
}
只标参考答案是不够的。检索指标需要 relevant_chunk_ids,引用评测需要文档与段落
标注,人工复核则需要能够直接阅读原文 span。
2.4 候选题编写与人工复核
候选题按批次编写,并通过标注工作台展示问题、参考答案、原始证据和相关元数据。
复核人需要确认:
- 问题是否能够由语料回答;
- 参考答案是否完整且没有超出证据;
- 引用文档和相关 chunk 是否正确;
- span 是否能在 chunk 中精确定位;
- 跨文档题是否真的包含至少两份文档;
- 不可回答题是否确实没有金标证据。
Test 80 题完成独立人工复核后冻结,并记录文件哈希:
- Test SHA-256:
2a88d939be77da4efeb38cd583e1d7e817a44d640879a84db6d1ebcfd12e0e5d; - Corpus SHA-256:
40e63c7f3ef2123ad7c5aa09722f45aad4221d33245c93354c51394818e5d3d4。
哈希的意义不是形式完整,而是防止评测后悄悄修改题目或语料。
三、建立四种检索基线
统一使用 Top 5,并在 75 道可回答 Test 题上统计检索质量。5 道不可回答题没有
相关 chunk,不应纳入 Recall、MRR 和 nDCG 均值。
3.1 指标定义
- Recall@5:Top 5 覆盖了多少比例的金标 chunks;
- MRR@5:第一个相关结果出现得有多靠前;
- nDCG@5:多个相关结果的整体排序质量;
- P50/P95:中位和尾部检索延迟;
- 在线成本:BM25 为 0;Embedding 与 LLM 未配置单价时保持未知,不用猜测值
填补。
3.2 正式 Test 结果
| 方案 | Recall@5 | MRR@5 | nDCG@5 | P95 |
|---|---|---|---|---|
| BM25 | 0.487 | 0.376 | 0.386 | 0.7 ms |
| 纯向量 | 0.927 | 0.803 | 0.817 | 297.4 ms |
| BM25 + 向量 RRF | 0.893 | 0.775 | 0.785 | 271.2 ms |
| 文档邻接图 + 混合检索 | 0.720 | 0.499 | 0.539 | 297.7 ms |
结果与一个常见直觉相反:更复杂的方案没有自然胜出。
BM25 很快,但中文问题与英文资料之间存在明显词汇鸿沟。等权 RRF 把 BM25 的噪声
带入向量结果,使质量低于纯向量。所谓“图检索”使用的是同文档相邻页边,高权重
扩展会把语义直接命中的结果挤出 Top 5。
门控图策略把 Recall 从 0.720 恢复到 0.893,但只恢复到普通混合检索水平,没有
超过纯向量。因此项目不能写成“知识图谱提升了 Recall”。更准确的说法是:限制
低质量图扩展能够消除伤害,而当前文档邻接图尚未产生额外收益。
四、从检索走到端到端回答
检索命中不等于回答正确。我们固定纯向量 Top 5,使用 deepseek-chat 生成答案,
要求模型返回答案和引用 chunk ID,再使用严格自评器分别判断正确性与证据忠实度。
正式 Test 结果为:
| 指标 | 结果 |
|---|---|
| 答案正确率 | 0.880 |
| 引用 Precision | 0.877 |
| 引用 Recall | 0.947 |
| 引用 F1 | 0.898 |
| 自动幻觉率 | 0.066 |
| P50 / P95 | 2466 / 4337 ms |
引用指标只统计 75 道可回答题。不可回答题不存在应引用的金标证据,如果把它们按
引用 F1=0 混入均值,会错误惩罚正确拒答。
分类型结果揭示了平均值掩盖的问题:概念题正确率达到 0.990,而跨文档题只有
0.630;跨文档引用 F1 也只有 0.583,自动幻觉率达到 0.200。于是下一步不再是
增加更多框架,而是解释跨文档失败。
五、用失败案例决定优化方向
我们逐题审计了至少 10 个失败案例,而不是只看总分。
5.1 跨文档结果坍缩
optics_test_074 要求比较两份 NIST 文档,但 Top 5 全部集中在其中一份。
optics_test_070 指定两份 MIT 实验手册,结果却被另一份同系列激光实验资料占据。
问题不是系统完全没找到主题,而是相似内容集中在同一文档,另一份证据没有获得
Top 5 配额。
5.2 多证据覆盖不完整
optics_test_067 只召回双偏振片实验,没有召回偏振理论;optics_test_073 只
召回 Gaussian 光束资料,没有召回 Fourier 平面相关讲义。单个问题向量往往被
其中一个子问题主导。
5.3 表格与编号歧义
optics_test_049 只包含通用的表号和参数描述,系统被其他 NIST 表格吸引。对于
“Table 2.6”一类问题,表号本身没有跨文档唯一性。
5.4 生成与评测问题
optics_test_035 暴露了符号定义与公式没有一起约束的问题;optics_test_018
给出了等价条件,却没有使用题目指定的符号形式。optics_test_059 则是自评器假
阴性:答案与引用都受证据支持,裁判仍错误扣分。
这些案例把下一轮方向收敛到两个问题:
- 对包含“分别、比较、两份文档”的问题做子查询分解;
- 合并时增加文档多样性,但不能破坏原始向量的高排名结果。
六、建立独立 CrossDoc Dev,而不是直接在 Test 上反复修改
正式 Test 已经暴露了跨文档弱点。如果继续根据它逐题调参,就会把测试集变成开发
集。因此我们另外建立了 12 道 CrossDoc Dev 题,每题需要至少两份文档,并完成
独立复核。
原始向量基线为:
- Recall@5:0.444;
- MRR@5:0.628;
- 完整证据覆盖率:0.083(1/12);
- P95:353.0 ms。
除普通 Recall 外,我们增加了两个更适合跨文档问题的指标:
- 完整证据覆盖率:Top 5 是否包含该题全部金标 chunks;
- 目标文档覆盖率:Top 5 覆盖了多少比例的金标文档。
如果一道题需要两份文档,而系统只找到其中一份,MRR 仍可能很高;完整证据覆盖率
能够揭示这种问题。
七、两次合并实验:一次失败,一次通过
7.1 v1:查询拆分 + 强制文档均衡
第一版把比较问题拆成两个子查询,分别向量检索,再为每个子查询保留不同文档的
结果。它取得:
- Recall@5:0.444 → 0.514;
- MRR@5:0.628 → 0.549;
- 完整证据覆盖率:0.083 → 0.167。
虽然 Recall 提高,但 MRR 下降 0.079。原因是强制多样性改变了前几个结果的位置,
为了补充另一份文档,牺牲了原始向量检索已经排好的高质量结果。v1 没有通过验收。
7.2 v2:保守合并
第二版采用更保守的规则:
- 原始完整问题照常进行向量检索;
- 保留原始 Top 3,不允许子查询改写前三名;
- 将问题拆分为子查询;
- 子查询候选只能竞争第 4、5 两个扩展位;
- 优先补充尚未出现的新文档;
- 没有合适候选时回退到原始向量结果。
CrossDoc Dev 上的结果为:
- Recall@5:0.444 → 0.556;
- MRR@5:保持 0.628;
- nDCG@5:0.443 → 0.501;
- 完整证据覆盖率:0.083 → 0.250;
- 目标文档覆盖率:0.750 → 0.875;
- P95:353.0 → 380.5 ms。
这次优化同时满足了“增加证据覆盖”和“不损害首个相关结果排名”两个目标,因此
参数在 Dev 上冻结为 top_k=5、preserve_k=3、candidate_k=30。
八、冻结参数后的 Test 验证
固定参数后,只在 Test 的 10 道 multi_document 题上运行一次 v2:
| 指标 | 原始向量 | v2 | 变化 |
|---|---|---|---|
| Recall@5 | 0.600 | 0.700 | +0.100 |
| MRR@5 | 0.600 | 0.600 | 0.000 |
| nDCG@5 | 0.554 | 0.607 | +0.053 |
| 完整证据覆盖率 | 0.500 | 0.700 | +0.200 |
| 目标文档覆盖率 | 0.750 | 0.900 | +0.150 |
| P95 | 361.4 ms | 519.3 ms | +157.9 ms |
逐题配对显示,optics_test_067 和 optics_test_073 从半覆盖变成完整覆盖,没有
题目出现 Recall 或 MRR 退化。这比只比较两个平均值更有说服力:收益不是以牺牲
其他题为代价换来的。
需要准确描述数据边界:该 Test 曾用于发现“跨文档问题较弱”,所以它不是完全
未见的外部测试集;但 v2 的具体规则与参数只在独立 CrossDoc Dev 上确定,验证
期间没有继续调参。
九、检索提升是否真正改善了答案?
我们使用两组固定检索结果重新评测相同的 10 道跨文档题:
| 指标 | 原始向量 | v2 | 变化 |
|---|---|---|---|
| 答案正确率 | 0.630 | 0.710 | +0.080 |
| 引用 F1 | 0.540 | 0.660 | +0.120 |
| 混合幻觉率 | 0.200 | 0.220 | +0.020 |
| P95 | 4419.0 ms | 5341.6 ms | +922.6 ms |
检索新增完整覆盖的 067、073 同时获得答案和引用提升,说明检索收益传递到了
生成阶段。另一方面,延迟明显增加,幻觉率也没有改善。
069 和 070 的新增不忠实风险经过人工复核确认:一个回答与自身引用证据矛盾,
另一个把“当前片段未检索到”扩大成“整份手册没有”。复核后的 0.220 是 2 个风险
案例人工确认、其余 8 题沿用严格自评器的混合指标,不应写成“10 题全部人工评估”。
十、一个失败的安全提示词实验
针对上述风险,我们尝试只增加一条回答约束:当前证据未包含某项信息时,必须说
“提供的证据不足以回答”,不得断言整份文档不存在该信息。
在相同 CrossDoc Dev 检索结果上的配对结果却变差:
| 提示词 | 答案正确率 | 引用 F1 | 幻觉率 | P95 |
|---|---|---|---|---|
| 原提示词 v1 | 0.425 | 0.506 | 0.167 | 4333.9 ms |
| 安全提示词 v2 | 0.342 | 0.456 | 0.286 | 5567.3 ms |
安全提示词引发过度拒答、错误证据归属,甚至在拒答句中继续作全局判断。它没有通过
任何主要验收指标,因此最终系统仍使用原提示词。
这个失败说明:判断证据是否充分和组织最终答案是两个不同任务。继续向同一个提示词
添加限制,不一定会更安全。更合理的后续设计是增加结构化的证据充分性状态,再由
生成器根据该状态回答;同时评测器必须区分“答案是否正确”和“陈述是否受证据支持”。
十一、工程实现中的几个关键点
11.1 评测产物不可覆盖
正式 Test 和关键验证结果写入独立目录。如果结果已经存在,接口返回冲突错误,避免
重复运行后选择更好的一次数字。
每次运行保存:
config.json:模型、数据路径、运行时间、Top K 等配置;per_question.jsonl:逐题召回、答案、引用、评分与延迟;summary.json:汇总指标。
11.2 API Key 不写入服务端
开发环境允许用户分别在浏览器中输入 LLM Key 和 Embedding Key,Key 只保存在
当前浏览器的 localStorage,并通过请求头传给后端。生成、检索使用各自的 Key,
避免把 DeepSeek Key 错用于 DashScope Embedding。
11.3 指标口径必须写清楚
本项目曾遇到两个典型口径问题:
- 不可回答题不应按引用 F1=0 计入可回答题均值;
- 自动幻觉率、风险案例人工复核后的混合指标、全量人工幻觉率不是同一个概念。
指标数字本身不难计算,难的是保证分母、适用范围和人工参与程度可追溯。
十二、最终得到的经验
经验一:先做评测闭环,再增加框架
没有固定数据和逐题结果时,多一个 Agent、多一种融合策略很难形成可信结论。100 条
高质量垂直数据带来的价值,高于继续叠加多个编排框架。
经验二:复杂方案不一定优于简单基线
纯向量是整体最佳检索方案。BM25 融合和邻接图都可能引入噪声。任何“高级”模块都
必须与简单基线配对比较。
经验三:跨文档问题需要覆盖指标
MRR 只关心第一个相关结果。对需要两份文档的问题,还必须看完整证据覆盖率和目标
文档覆盖率。
经验四:保守优化比强制重排更稳定
v1 强制文档均衡提高 Recall,却破坏 MRR;v2 保护原始 Top 3,只使用尾部槽位补证据,
最终取得了可泛化的收益。
经验五:失败实验应该保留
加权图、强制均衡和安全提示词都失败了。保留这些结果能解释最终方案为什么这样设计,
也能避免后来重复走同样的弯路。
经验六:不要让自评器同时定义事实
LLM 裁判会产生假阴性,也会混淆正确性与忠实度。高风险案例仍需要回到引用原文进行
人工审计。
十三、如何准确描述这个项目
可以使用下面这段项目描述:
构建并冻结 100 条光学科研问答评测集,标注参考答案、引用文档及相关段落;在
80 条 Test 上对比 BM25、向量、混合与图检索,纯向量 Recall@5 达 0.927、
MRR@5 达 0.803。针对跨文档证据坍缩设计查询分解与保守多文档合并,在固定
10 条跨文档 Test 切片上将 Recall@5 从 0.600 提升至 0.700、完整证据覆盖率
从 0.500 提升至 0.700,并将答案正确率从 0.630 提升至 0.710、引用 F1 从
0.540 提升至 0.660;完成 10+ 失败案例归因,并记录延迟与幻觉率权衡。
不应写成“知识图谱将整体 Recall 从 X 提升到 Y”,因为当前图方案没有超过纯向量;
也不应省略“10 道跨文档切片”的范围限制。
结语
OptiRAG 这次最重要的变化,不是多了一个检索器或提示词,而是形成了一条可以被
重复、质疑和验证的工程链路:
数据集构建 → 人工复核 → 冻结版本 → 多方案基线 → 逐题错误分析 → Dev 调优 →
固定参数验证 → 下游影响评测 → 记录收益、失败与边界。
一个垂直 RAG 项目只有进入这条链路,才真正从“能够演示”走向“能够评测”。

浙公网安备 33010602011771号