跳到主要内容

被你的 RAG 当成工程规范引用的那张营销页

· 阅读需 10 分钟
Tian Pan
Software Engineer

一位支持工程师把客户工单粘进你内部的 AI 助手。问题很尖锐:"我们的 API 在免费层支持多区域写入吗?"助手秒回,引用了一个余弦相似度 0.91 的片段。答案是肯定的。这个片段来自 2023 年市场部为打赢竞品对比写的落地页。十八个月前,工程团队就把免费层的多区域写入功能下掉了,并发了一份没人在客户页面上链接过的、措辞简短的内部 RFC。这份 RFC 也在向量库里,只拿到了 0.74。

助手并没有幻觉。它检索到了得分最高的文档,然后忠实地把答案锚定在那段文本上。检索器尽到了职责。只是,那份职责本身就是错的。

这是没有任何 RAG 基准能抓到、也没有任何 faithfulness 指标会报警的失败模式。你的助手在引用来源。来源是真实存在的。流水线在做它被设计去做的事。Bug 在于:检索相关性与来源权威性是两个不同的维度,而标准 RAG 栈把它们压成了同一个分数。

打磨过的文字在向量赛跑里赢了

营销文案密集地堆满了高信号词汇。写它的人,职业就是让句子令人难忘。每个名词都是产品术语,每个动词都是行动动词,每句话都把卖点说成收益。当你把这些文本嵌入向量,得到的向量会对任何用户可能问出的查询都指得很准——因为这些文本本来就是为了贴近用户描述自身需求的方式而优化的。

工程文档恰恰相反。它保守,用词精确,对每个声明都加限定,然后把真正的能力陈述埋在三段铺垫之后。工程文档的嵌入是分散的:它把信号散在"弃用时间表""迁移路径""服务级别约束""已知限制"之间。一个"X 是否支持 Y"的查询会像灯塔一样把营销片段照亮,而对工程片段擦肩而过,像穿过雾。

这不是调参问题,而是内容形态错配。你的检索器最有信心的那一份,恰恰是你业务上最没把握的那一份。研究检索器–生成器对齐的论文发现,在 47%~67% 的查询里,生成器会忽略检索器的 top-1 文档;在 48%~66% 的查询里,它会依赖排名更靠后的文档。原因不是生成器坏了,而是排在最上面的那份文档错得让生成器都能感觉到,而打分器感觉不到。

相关性是个排序决策,不是真相保证

相关性分数是关于嵌入空间中几何邻近度的一句陈述。它告诉你,被检索的片段和查询是同一个主题。它不告诉你这个片段是否是最新的、写它的人是否有资格作此声明、那个声明是否在下个迭代中存活下来。

标准 RAG 流水线把权威性当成语料库的属性而非每篇文档的属性。"我们整理过知识库"成了每季度一次的检查点,与此同时,文档却从每个有 Confluence 写权限的团队那里源源不断地渗入。向量库变成了一层层的地质记录:2022 年那一层寒武纪大爆发似的营销页面,一层泥盆纪化石床似的弃用 wiki,上面再覆一层上周新写的 RFC 的活土。余弦相似度对地层视若无睹。

更糟的是,助手一旦引用了来源,faithfulness 指标就亮起绿灯。输出有根据,证据链清晰,评估套件被满足,而客户被误导了。对生产 RAG 系统的研究记录了这种模式:faithfulness 分数因为答案有引用而升高,而不论那些来源是否相关、是否正确。你建了一套系统,自信地用引用的外衣给错误信息洗白。

检索流水线里,权威性到底长什么样

有一类修复方法把权威性当作打分器的一等输入,而不是套在语料库上的"氛围"。它们都有一个共同的结构性承诺:权威性必须和文档绑定旅行,而不是由建索引的那个人随身携带。

第一招是 按来源类型加权。知识库中的每篇文档在入库时打上标签:engineering-rfcmarketing-pagesupport-macrodeprecated-wikicustomer-ticket。检索打分器根据标签和推断出的查询意图,在余弦相似度上乘一个倍率。看起来技术性的查询——出现 API 名、版本号、配置键这类实体——惩罚 marketing 标签的片段;看起来像定位类的问题,惩罚内部 RFC。倍率不大;你不是在否决文档,而是在权威性与相关性不一致时轻推排名。IEEE 关于元数据增强检索的工作显示,这种预过滤大约能带来 12 个百分点的精度提升——不是因为嵌入变好了,而是因为嵌入不再是唯一的信号。

第二招是 把冲突检测当作检索原语。不要直接返回 top-3 片段就扔给模型,而是返回 top-3 之后跑一遍结构性检查:这些片段彼此的声明相容吗?如果两个说"已支持",一个说"自 v3.1 起已弃用",检索器不该把不一致悄悄平均掉。它应该把冲突暴露给编排层,由编排层决定是升级处理、按权威性重排,还是要求模型显式推理该信任哪个来源。近期关于"冲突驱动总结"的工作把这一点形式化了:把矛盾证据当作语料库存在结构性分歧的信号,而不是交给生成器去平均掉的噪声。

第三招是最不光鲜的那个:内容流水线纪律。营销文案不属于工程知识库。销售 deck 不属于支持知识库。弃用的 wiki 页面不属于任何知识库。修复方法不是更聪明的检索器,而是一道入库门禁:对每篇进入索引的文档问一句,这份文档所作的声明归谁所有,声明改变时这份文档会发生什么。如果没人拥有这个声明,这份文档就是负债。如果声明改变时什么都不会发生,这份文档已经是一颗只是还没引爆的负债。

时效性问题,其实是权威性问题的伪装

生产团队常常用一个"时效性分数"来打补丁:把更近的文档加权,把旧的衰减。这有一点用,但更多地是误导。一份 2024 年描述了一项已弃用功能的营销页,仍然"足够新",可以压过 2022 年那份正确描述了约束的工程 RFC。当权威来源比误导来源更老时,时效性作为权威性的代理就崩了。

更深的结构是:权威性是一张图,而不是一个数。工程 RFC 之所以权威,是因为 RFC 的拥有者在为它所描述的系统值班、因为对系统的变更要求更新 RFC、因为 RFC 链接到强制执行其声明的部署流水线。剥掉这些边,你拥有的就只是一份文档,而不是真相之源。一个把每个片段都当作同等权威的向量库,等于把每份文档都当作同等"无根"。文档级溯源跟踪和锚定在知识图谱上的 RAG 这类工具,是把这些边重新装回去的早期尝试——但它们只有在你的入库流水线一开始就保留了这些边时才奏效。

这也是为什么"直接换个交叉编码器重排"这一招通常令人失望。交叉编码器只是更贵的相关性打分器,不是权威性打分器。把更强的模型扔到错的维度上,只会让你得到一个更自信的错误排序。

一个可落地的下一次做法

如果你现在就在跑一套生产 RAG,前路是具体的:

  • 在入库时给每篇文档打上来源类型、负责团队、最近一次审阅日期、以及它所作的声明类型(能力、政策、定价、状态)。
  • 在入站查询上跑一个小的意图分类器,把它们路由到被允许作答的来源类型。技术问题优先从 engineering 标签的来源检索;定价问题优先从财务团队拥有的来源检索。
  • 把冲突暴露出来。如果 top-N 片段在一个事实声明上互相打架,不要替它挑一个。把它们都给模型,让模型显式推理该信任哪一个,并在答案中说明冲突。
  • 按受众隔离语料库。给客户用的助手所查询的知识库,不该是给工程团队用的助手所查询的同一个。把它们合二为一的诱惑,本质上是为了节省存储成本而牺牲信任。
  • 跑一个专门测试"来源冲突"场景的评估切片。挑十个工程与营销对同一项功能持相反说法的样本,衡量助手在冲突存在时是否还能答对。

这些做法不会让问题消失。它们会把问题从看不见挪到能被观察到——而这是唯一重要的那一步。

你没有的那套知识策略

"我们把所有东西都丢进了向量库"不是一套知识策略,而是缺乏策略的婉转说法。它把"你的组织把什么当作权威"这个问题,推给了一个从未被问过这个问题的相似度函数。

那些在生产中悄悄更准确的助手,并不是有最花哨的检索器或最长的上下文窗口的那些。它们是那些团队把知识库当作一份有所有者、有过期日期、有冲突解决规则的精心维护的工件来对待的那些。检索流水线是这些决定的下游。当这些决定缺席,检索器就用余弦相似度最高的那一份去填空白——而三年前的一张营销页,每一次都会赢一份默默正确的 RFC。

下一次你看到一个面向客户的答案"自信地错了"时,不要从调嵌入模型开始。从问"模型引用的是哪份来源、它的声明类型是什么"开始。Bug 几乎从不在检索器里。它在被交给检索器的那份语料库里,以及那个隐秘的假设里:把一份文档放进向量库,就等于为它背书。

References:Let's stay in touch and Follow me for more thoughts and updates