RAG 混合检索怎么做:关键词、向量与引用溯源
回答写得流畅,却找不到依据;资料里明明有一个版本号,检索结果却全是相似主题。这两类问题要沿着检索链路分别排查。我的做法是先保留能解释的关键词基线,再增加语义召回,并让每个回答片段能回到原文。
下面以 Knowledge 项目 的本地检索思路说明实现顺序。示例文档与问题均为虚构,代码只演示排名融合,不包含真实资料、向量计算或回答生成。线上只读演示与本机完整能力分开:示例资料未批准或索引未就绪时,公网检索保持关闭。
1. 先明确问题和资料范围
假设有三份内部说明:install-v2 记录安装步骤,limits-v2 记录配额,revoke-v2 记录撤销旧版本。用户问“资料改过以后,为什么之前的引用不能点了?”
关键词可能命中“引用”“撤销”,向量检索可能找到“原件变更”和“旧版本失效”的同义描述。两路结果互补的前提,是它们只在当前用户有权限、已经批准且仍有效的版本中检索。
建议先保留文档 ID、版本 ID、片段 ID、原文位置和来源标识。权限不能交给提示词处理:召回时过滤,返回原文及生成回答前再检查当前有效性。
2. 做出可复查的关键词基线
先让“明确术语、版本号、错误码”能找到对应段落。SQLite FTS5 可以做关键词检索;其中 bm25() 的实现约定是分数越小越靠前,排序时不要照搬“相似度越大越好”的方向。SQLite FTS5 官方说明
中文切词、标点、路径和版本号也要单独检查。检索不到 v2.1,可能是分词或索引问题,不能直接归因于大模型。
保留一组固定查询及预期证据,至少覆盖:精确术语、同义表达、跨文档问题、没有答案的问题、无权限资料和已失效版本。先记录关键词结果,后续才有可比较的基线。
3. 接上真实向量召回,再融合排名
语义召回需要实际可用的 embedding 模型与索引。模型、分词器、切分参数或语料版本改变后,要有对应的索引版本;依赖未就绪时明确报错或提供标明模式的关键词检索,不能返回一个假“语义分数”。
关键词分数和向量相似度通常不是同一量纲。RRF(Reciprocal Rank Fusion)按各路名次累计 1 / (k + rank),可以先不直接相加原始分数。k 和每路候选数量仍需用自己的题集验证;融合方式没有对所有数据都有效的保证。混合检索融合方法研究
这段独立可运行的 Python 示例,输入是已经过权限与版本过滤的片段 ID 排名。它只说明融合,不代表一个完整的 RAG 服务:
from collections.abc import Sequence
def rrf(rankings: Sequence[Sequence[str]], k: int = 60) -> list[str]:
if k < 1:
raise ValueError("k must be positive")
scores: dict[str, float] = {}
for ranking in rankings:
seen: set[str] = set()
for rank, chunk_id in enumerate(ranking, 1):
if chunk_id in seen:
continue
seen.add(chunk_id)
scores[chunk_id] = scores.get(chunk_id, 0.0) + 1 / (k + rank)
return sorted(scores, key=lambda item: (-scores[item], item))
lexical = ["limits-v2#1", "revoke-v2#2", "install-v2#1"]
semantic = ["revoke-v2#2", "limits-v2#1"]
assert rrf([lexical, semantic])[:2] == ["limits-v2#1", "revoke-v2#2"]
重复片段在同一路只计一次,跨路命中可以累计;并列时用 ID 稳定排序。两个片段在例子里的融合分数相同,排在第一位不表示内容一定更相关。真实服务还需要同文档数量限制、低相关结果处理与召回过程记录。
4. 排名之后,再决定回答能看到什么
候选进入上下文前,按证据标识去重,限制单篇文档占用,并按模型实际分词器计算预算。优先放完整、能独立说明问题的证据;过长的片段要缩成可定位的窗口,不要截掉版本、条件或否定词。
如果证据为空,直接说明在当前资料范围没有找到依据。只有主题相似、无法支持问题时,应提示证据不足或请用户澄清;不要让模型靠常识补成“资料中的结论”。不同模型与数据的相关性阈值也不能直接共用。
5. 引用可定位,不等于事实已核实
回答中的 [1] 应关联本次实际放入上下文的证据,包含文档、版本、片段和原文位置。核对编号是否存在、证据是否真的给过模型,以及原文目前是否仍可访问。
这些检查只能发现一部分问题。编号合法或句子与证据存在词汇重合,不能证明整句话受证据支持;重要结论仍要读原文,区分资料观点、来源中的事实与模型推断。来源变更后,旧引用要明确标为失效。
6. 用失败现象决定下一步
| 现象 | 先检查什么 | 可验证的改进 |
|---|---|---|
| 原文有版本号,但关键词没命中 | 分词、索引覆盖、版本过滤 | 固定版本号查询能定位原段 |
| 同义问题召回很差 | 真实模型就绪、切分、候选数量 | 在独立题集与关键词基线比较 |
| 前几条都来自同一篇资料 | 重复窗口、单文档占比 | 保留不同来源的必要证据 |
| 检索有证据,回答仍遗漏条件 | 上下文截断、提示词、引用映射 | 回答保留适用条件且可定位 |
| 引用打开失败 | 当前权限与来源有效性 | 明确失效,禁止回退到旧证据 |
检索层看证据覆盖与排序,生成层看答案是否受证据支持、引用是否完整,还要记录延迟。Hit@5 的含义是前五条结果的命中口径;它不会自动变成答案准确率。调参题和验收题应分开,避免只把记住的样本做得更好。
想看项目边界,可以读 Knowledge 案例;想看我如何把测试经验用到 AI 项目,继续读测试工程师转 AI 应用开发。项目讨论与求职联系见经历与联系。