前言:
上一篇已经把 RAG 的最小闭环跑通了:
PDF 能读,模型能答,链路是通的。
但在准备继续往下做(向量数据库、embedding)之前,我发现一个问题:
其实并不敢完全信这个结果。
没有继续加新能力,先停下来,给 RAG 补了一轮最基础的回归测试,看看效果。
一、最开始暴露出来的问题
在进入这一轮之前,准备了一组带明确预期的问题,而不是随便和模型对话。
1. 明明在文档里的内容,却经常检索不到
例如:
-
这个项目的最小验收标准是什么?
-
为什么不是直接用通用大模型对话?
-
RAG 的边界是怎么定义的?
这些问题在 PDF 中都有明确描述,但最初运行时,经常出现几种情况:
-
Top chunks 为空,直接 no hits
-
命中的页码明显不对
-
模型要么拒答,要么只能"猜着说"
通过这一步意识到:
问题不在生成,而在检索本身就不可靠。
2. 文档里没有的信息,模型存在"编造风险"
还问了一些文档中明确没有答案的问题:
-
这个项目用了什么向量数据库?
-
这个项目用了什么 embedding 模型?
其实不是关心"它能不能答",而是:
在没有依据的情况下,它会不会为了显得合理而乱答。
如果这一点兜不住,感觉谈向量数据库是没有多大意义的。
3. 同一个问题,多跑几次结果不稳定
即使是同一个问题,多跑几次也会发现:
-
命中的 chunk 会变
-
有时来自 p1,有时来自 p2
-
回答引用的依据也会变化
从工程角度来看,这意味着:
系统不可回归,也就谈不上可维护。
二、针对这些问题,做了哪些修复
这一轮没有引入新模型,也没有上向量数据库,只做了很小但必要的改动。
修复一:让检索过程"看得见"
对应的问题:
我不知道模型到底用的是哪段文档在回答。
做法:
-
新增一个专门用于问答的模式(mode 3)
-
每次问答时:
-
显式输出 Top-k 命中的
chunk_id / 页码 / 原文片段 -
要求模型在回答中必须引用对应的
chunk_id
-
结果:
-
可以清楚看到:
-
命中的是哪一页
-
回答是否真的基于这些片段
-
-
RAG 第一次从"黑盒生成"变成了可观测系统。

修复二:修复中文场景下检索几乎失效的问题
对应的问题:
很多中文问题明明在文档里,却经常 score=0。
原因分析:
-
原始 keyword 检索对中文支持极差
-
连续中文被当成一个整体 token
-
query 和 chunk 几乎没有交集
做法:
-
重写分词逻辑:
-
引入中文 2-gram(双字切分)
-
同时保留原始词块,避免短词漏检
-
结果:
-
"验收标准""为什么不直接用大模型""RAG 边界"
这类问题开始稳定命中正确页(p1 / p2 / p3)
-
检索从"经常不可用"变成了"可用且可测"。
修复三:验证系统是否具备"拒答能力"
对应的问题:
文档里没有的信息,模型会不会乱编?
做法:
-
在生成 prompt 中加入硬性规则:
- 如果检索片段不足以回答,必须明确拒答
-
使用反事实问题进行验证:
-
向量数据库?
-
embedding 模型?
-
结果:
-
模型能够稳定输出:
文档未提供相关信息
-
系统具备了不答错的能力。
修复四:验证系统是否具备可回归性
对应的问题:
同一问题多次运行结果不一致。
做法:
-
对同一个问题重复提问 2~3 次
-
对比每次命中的 Top chunks(页码与 chunk_id)
结果:
-
核心问题的 Top chunks 基本保持一致
-
在当前结构下,系统具备了最基本的稳定性。
三、这一轮回归测试后的结论
这一阶段并没有得出"效果很好"这样的结论,而是:
在当前结构下,已经可以清楚判断,RAG 是否在用对的资料回答问题。
所以说:
-
后续再引入向量数据库,是在一个
可观测、可回归、可解释的系统之上 -
而不是用更复杂的工具去掩盖现有问题。
四、当前阶段的边界说明
这次只记录这一阶段的工程判断与修复过程,不展开以下内容:
-
向量数据库选型
-
embedding 模型对比
-
检索指标(Recall / Precision)
这些会在后续阶段、在同一套回归问题基础上继续推进。
附:代码记录
本阶段代码已整理并上传 GitHub,用于阶段性存档与后续回放:
总结
RAG 跑通只是开始,
只有在能被回归测试之后,才敢继续往下做。