检索流程:切分、召回、重排与引用
一个文档问答系统首先要找到能回答问题的原文,然后才能要求模型据此作答。如果原文没进入上下文,换更大的生成模型常常只会让猜测写得更流畅。本文讨论从文档到证据的链条;跨任务保存什么信息,由记忆机制处理。
从问题倒推检索单元
假设有一份虚构的设备手册:一节介绍更换电池,下一节说明防水等级只在外壳完整时成立。用户问“换完电池还能直接浸水吗?”检索必须同时找到操作和限制。只返回含有“电池”的段落,答案就可能缺少决定性条件。
切分的目的,是让一个候选片段既能表达完整意思,又不会因为太长而把证据淹没。可以先按标题、段落、函数或表格边界切,再对超长部分设置长度上限。重叠能保留跨边界的句子,也会增加重复候选。不存在适合所有语料的固定片段长度:API 参数说明、聊天记录和论文章节需要不同边界。
每个片段至少保留原文标识、位置、标题路径和版本。若答案引用的是表格的一行,必须能恢复表头和单位。只存一段脱离来源的文本,会让后续引用、去重和更新都变得困难。文档删除后,旧向量也应失效,否则搜索结果仍会指向过时内容。
召回与重排各自做什么
召回先从整个语料中筛出一批可能有用的片段;重排再细看问题与候选的对应关系。Sentence Transformers 的检索示例展示了先用词法搜索或双编码器召回,再用交叉编码器重新排序的两阶段结构。
混合搜索可以合并词法和向量候选,但两种分数的量纲未必相同。一个简单的基线是按排名融合,而不是直接把余弦相似度与词法分数相加。例如倒数排名融合给每个候选累加 1 / (c + rank);常数 c 控制前几名的优势。它只是一种排序策略,不能赋予结果“正确概率”。
一次可定位的失败
继续看电池问题。假设召回取前 20 项,重排取前 4 项,最终上下文只容纳 2 个片段。这些数字只是演示预算,不是推荐配置。
如果防水条件排在第 30 位,是召回问题;在候选中却被重排到第 12 位,是排序问题;排在前 4 位却被重复段落挤掉,是组装问题;已经进入上下文却被回答忽略,则是生成问题。保存每层的候选 ID 和位置,就能区分这些情况。只保存最后答案,无法判断应该换 embedding、调整切分还是修改回答指令。
对涉及多节的提问,可以先找命中的小片段,再取相邻段落或父章节补足语境。扩大上下文也会引入无关内容,因此应给扩展设预算,并按问题保留必要条件。权限、时间范围和文档版本最好进入候选过滤过程;不能在生成完答案后才发现引用了不适用的资料。
引用必须落到具体主张
“参考手册”不是充分引用。回答中“更换后需要重新检查密封圈”应指向支持这句话的原文位置,而不是只链接整份手册。对于两份互相矛盾的版本,先辨明适用日期和设备型号;如果仍冲突,回答应保留冲突,不让模型自行拼出不存在的统一结论。
检索文本是证据,不是执行指令。原文中要求忽略规则或调用外部工具的句子,不会因为排名靠前就获得权限。读取证据与执行动作的边界见工具契约。
将工具放回这条链
QMD适合查找 Markdown 笔记,zvec-grep面向工作区代码与文档的语义搜索,Basic Memory让持久笔记及其关系可供读取。它们的具体索引与查询方式不同,不必强行共用一个数据库。应统一的是返回契约:来源、位置、版本、相关片段,以及必要的访问范围。
先用少量带原文依据的问题建立基线,分别观察有没有召回证据、是否排到前面、答案是否正确使用。具体指标和反例设计接着看检索与生成评估;进入模型上下文后如何分配空间,接着看上下文工程。