做 RAG 时,最容易被关注的是最后那次模型回答。但真正动手以后,我发现很多“模型答不好”的问题,往前追几步就会变成:文档没读全、切块切断了、元数据丢了,或者正确证据根本没有被召回。
第 4 章把加载器、文档转换、Embedding、向量存储和 Retriever 放在“数据增强模块”中。与其把它们记成五个类,我更愿意把它们看成一条知识加工流水线。
先把 RAG 写成一条数据流
原始文件
-> 加载为 Document
-> 清洗与切分
-> 生成 Embedding
-> 写入向量库
-> 根据问题检索
-> 把证据交给模型回答
这条流程里,生成答案只是最后一步。前面任何一个环节出错,模型都可能在缺少证据的情况下给出一段很流畅的回答。
Loader 不能只留下纯文本
假设知识库里有一份 80 页的产品手册。如果加载完成后只剩下一长串文字,后面即使检索正确,也很难告诉用户答案来自第几页;文档更新时,也不知道应该替换哪些向量。
因此 Document 除了正文,还应尽量保留:
- 文件名、路径或业务主键;
- 页码、章节标题和段落层级;
- 文档版本、更新时间和权限标签;
- 可回到原文的 URL 或定位信息。
这些元数据不会直接让模型变聪明,却决定了引用、过滤、更新和审计能不能做好。
切块不是“随便定个 500”
块太大,一段里混进多个主题,Embedding 很难表达重点;块太小,定义和上下文被拆开,检索回来只剩半句话。
例如下面这段产品说明:
退款条件:订单支付后 24 小时内可自助退款。
例外情况:已使用优惠券的订单需要人工审核。
如果刚好从两句中间切开,用户询问“用了优惠券能否退款”时,系统可能只召回第一句,进而给出过度确定的答案。
所以切分要尽量尊重标题、段落、列表、代码块和表格结构。重叠能缓解边界断裂,但也会增加存储量和重复召回。块大小、重叠量和分隔规则,最好用一组真实问题做实验,而不是凭感觉拍板。
Embedding 找的是“相近”,不是“正确”
Embedding 把文本映射成向量,适合发现同义表达。例如用户问“账号进不去”,系统仍可能召回标题为“登录失败排查”的文档。
但向量相似度不会自动判断哪份资料更新、哪份更权威,也不擅长所有精确匹配。订单号、错误码、版本号和专有名词,往往仍需要关键词检索。
实际系统常采用组合方式:
元数据过滤 -> 关键词与向量召回 -> 合并去重 -> 重排序 -> 返回前 k 条证据
更换 Embedding 模型时,也不能把新查询向量直接拿去搜索旧索引。模型和维度变化后,通常需要重新生成向量,并把模型版本记录下来。
Vector Store 和 Retriever 分工不同
向量库负责保存向量并执行相似度搜索;Retriever 面向应用,决定查询怎样改写、按什么条件过滤、取多少条,以及是否融合多个数据源。
把两层分开有一个实际好处:底层从本地向量库换成云服务时,上层问答流程不必全部改写。反过来,要加入时间过滤、混合检索或重排序,也不一定要替换存储。
Retriever 最终返回的内容,至少应包含正文、来源和必要元数据。相似度分数可以辅助分析,但不要把不同模型、不同索引产生的分数直接当成统一置信度。
先评估检索,再评估答案
如果只看最终回答,很容易被模型的语言能力误导。它可能没有检索到资料,却依靠参数知识碰巧答对。
我会先准备一组“问题 + 标准证据”,单独检查:
- 正确证据是否出现在前 k 条;
- 召回结果里有多少无关内容;
- 权限、时间和文档类型过滤是否生效;
- 引用能否准确回到原文;
- 没有资料或资料冲突时,系统是否会承认不确定。
等检索链路稳定以后,再评估生成答案是否忠于证据、是否遗漏关键信息。
这章给我的启发
RAG 不是“给模型接一个向量数据库”,而是在建设一条可更新、可追踪、可评估的知识通道。
加载决定资料有没有丢,切分决定知识以什么颗粒度存在,Embedding 和检索策略决定证据能不能被找到,最后的 Prompt 才决定模型怎样使用这些证据。把问题沿这条链路拆开,调试就不再只是反复修改一句提示词。
文章摘自:https://www.cnblogs.com/hazy-star/p/22067463
