从“能回答”到“可评测”:OptiRAG 光学垂直 RAG 的数据集构建、错误分析与优化实践

很多 RAG 项目做到最后,只能展示几个看起来不错的问答截图。系统究竟召回了什么、
答案是否真的受文档支持、换一种检索方案是否更好,往往没有可靠证据。

OptiRAG 最初也更接近一个功能型原型:可以上传光学资料、检索文档、回答问题,还
包含实验方案设计能力。但如果没有固定数据集、统一指标和失败案例分析,就很难判断
项目是在进步,还是只是在增加功能。

因此,我们把工作重点从“再增加一个 Agent”转向“建立一个可评测的光学垂直
RAG”。最终完成了 100 条光学科研问答、四种检索基线、端到端生成评测、10 个以上
失败案例归因,以及一轮在独立 Dev 上选择、在冻结 Test 上验证的跨文档检索优化。

本文完整记录这条路径,包括有效的方法,也包括没有成功的实验。

一、先定义什么叫“做完”

项目开始前,我们先固定了交付标准:

  1. 建立 100 条光学领域测试题;
  2. 每题标注参考答案、引用文档和相关原文段落;
  3. 对比 BM25、纯向量、BM25 + 向量、图 + 混合检索;
  4. 记录 Recall@5、MRR@5、nDCG@5、答案正确率、引用正确率、幻觉率、P50/P95
    延迟和单次调用成本;
  5. 至少分析 10 个失败案例;
  6. 根据错误分析完成一次可验证优化。

整个流程遵循一个原则:先冻结评价标准,再比较方案;先在 Dev 上选择参数,再验证,
不能看到 Test 逐题结果后不断修改算法直到数字好看。

flowchart LR A["光学资料收集"] --> B["解析与分块"] B --> C["候选题编写"] C --> D["证据 span 标注"] D --> E["独立人工复核"] E --> F["冻结 100 题"] F --> G["四种检索基线"] G --> H["端到端回答评测"] H --> I["失败案例归因"] I --> J["CrossDoc Dev 调优"] J --> K["冻结 Test 验证"] K --> L["记录收益与代价"]

二、构建可审计的光学问答数据集

2.1 语料选择

语料来自 12 份 MIT OpenCourseWare 与 NIST 光学资料,覆盖几何光学、波动光学、
偏振、Gaussian 光束、光纤、激光、Q-switching、光谱响应度标定等主题。解析后形成
577 个 chunks。

我们没有只保存纯文本,还记录了:

  • chunk_iddocument_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 则是自评器假
阴性:答案与引用都受证据支持,裁判仍错误扣分。

这些案例把下一轮方向收敛到两个问题:

  1. 对包含“分别、比较、两份文档”的问题做子查询分解;
  2. 合并时增加文档多样性,但不能破坏原始向量的高排名结果。

六、建立独立 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:保守合并

第二版采用更保守的规则:

  1. 原始完整问题照常进行向量检索;
  2. 保留原始 Top 3,不允许子查询改写前三名;
  3. 将问题拆分为子查询;
  4. 子查询候选只能竞争第 4、5 两个扩展位;
  5. 优先补充尚未出现的新文档;
  6. 没有合适候选时回退到原始向量结果。

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=5preserve_k=3candidate_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_067optics_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

检索新增完整覆盖的 067073 同时获得答案和引用提升,说明检索收益传递到了
生成阶段。另一方面,延迟明显增加,幻觉率也没有改善。

069070 的新增不忠实风险经过人工复核确认:一个回答与自身引用证据矛盾,
另一个把“当前片段未检索到”扩大成“整份手册没有”。复核后的 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 项目只有进入这条链路,才真正从“能够演示”走向“能够评测”。

posted @ 2026-08-09 21:56  废物豆  阅读(0)  评论(0)    收藏  举报