RAG 项目有一个很奇怪的倾向:系统一旦答错,大家首先怀疑的总是模型。
这当然可以理解。模型是整套系统里最显眼的部分,也是最容易替换的部分。换一个模型,输出立刻就会变化;调整一下提示词,当天就能找到几个更好的答案。相比之下,整理文档、检查版本、设计测试,看起来既慢又不够聪明。它们很少在演示里制造惊喜,更多时候只是让一些错误不再发生。
但是呢,生产系统的质量,恰恰是在这些“稳”中体现的。要我说,一个 RAG 项目的工作大概是 50% 评测,40% 整理数据,8% 接入业务,最后 2% 才是模型训练。
这说的可能有点夸张了啊,但是实际上你想想是不是这样。我们喜欢优化模型,理论上说是“模型最重要”,实际上是因为它最容易给人进展感。
但是一评测,就会发现,这种令人舒服的模糊其实挺虚幻的:什么叫回答得不错?是找到了相关文档,就算不错?引用了真实页面,就算不错?还是答案必须使用当前有效的制度,保留原文中的限制条件,在材料不足时承认自己不知道?
如果这些问题没有提前说清楚,每一次优化都只能依赖感觉。团队会拿几个熟悉的问题反复测试。新模型碰巧答得更顺,就认为它更好。至于它是否在其他问题上退步,是否更容易把旧资料说成新结论,或者是否只是把错误表达得更有说服力,没有人知道。
所以,第一点,先建立一个有用的评测:不仅出结果,还要告诉我们为什么,这样我们才能知道怎么修。而不是就说一句“回答不准”,回答不准的情况可太多了,修法却完全不同。你还是得先搞清楚错误发生在哪里,而不是急着换模型。
接下来第二点是数据工作。举个例子:
假设一名员工问:“我下周去上海出差,住宿最高能报销多少?”
知识库里有三份材料:一份 2024 年的旧制度,一份 2026 年的新制度,还有一份只适用于销售团队的补充通知。旧制度的标题最接近员工的问法,于是被系统放在最前面。模型读完以后回答“每晚 800 元”,还附上了一个完全真实的出处。
这类答案最危险的地方,是它不像错误。文字通顺,数字准确,引用也能打开。只要没有人继续追问这份制度是否仍然有效、员工属于哪个部门、去上海适用的是哪一档标准,它看起来已经完成了任务。
但这,其实是数据清洗出了问题。大家不要望文生义觉得“数据清洗”就是删除重复文件、修复乱码、统一格式就完了,其实它还包含了一个“建立”的过程:把错的剔除掉,把对的排排好。用现在的话说就是:
- 建立可靠的记忆层。
在这个例子里面,系统把“2024 年制度”和“2026 年制度”当成了两段相似的文字,却没有把它们理解成前后相继的两个状态。它知道两个文件都在讨论报销,却不知道后者替代了前者。它也知道销售补充通知里有一个数字,却不知道这个数字只对特定人群成立。
从文字的角度看,这三份材料都相关。从业务的角度看,它们不能被平等对待。
- 知识,也是有状态的。
一份制度不是一段文字。它是某个部门在某个时间发布、对某类人适用、可能被另一份制度替代的一项规则。一张表格也不是一串数字。它可能是某段结论的依据,而表头、脚注和所在章节共同限定了数字应该怎样理解。
所以,chunking 的时候,就不能把文档切成互不相干的小段,你要保留它的身份和状态,这样模型看见数字“报销上限 1200 元”的时候,才能知道它的“适用业务”、“对应职级”和“实行期限”。
说到底,数据工作就是对知识状态的整理。光知道数据对不对是不够的,你还要知道它的“关系”,比方说谁发布的,何时生效,何时失效,适用于谁,和其他材料是什么关系等等,这个过程也被叫作 ontology 。
第三是评测和数据整理的配合。评测会暴露系统缺少哪些区别,反过来,每当知识结构增加一个新的区别,测试也要增加相应的问题,确认系统真的会使用它。
50% 评测和 40% 数据整理,这不是两个排在模型之前的准备阶段,是要一直轮动的。有些 RAG 项目在演示时很好,上线后却越来越差,就是因为演示面对的是一批静止的文件,生产环境面对的却是一个不断变化的组织。制度会更新,部门会调整,产品会改名,原来正确的标签会慢慢失效。数据不是准备一次就结束的燃料,而是系统必须持续维护的现实。
第四,要接入业务,而不是光在那里演。线上出现错误,我们要能回到当时使用的材料,知道缺的是哪一条信息,然后修改文档关系或回答规则,再让以前的所有问题重新跑一遍,确认这次修复没有破坏其他地方。系统不是靠一次大升级变得可靠,而是靠每一次错误都留下可以学习的东西。
最后,如果正确材料已经稳定地被找到,版本和适用范围没有丢失,问题也提供了足够条件,而模型仍然反复误解复杂条款、读错表格或无法组织多份证据,这时我们才真正知道瓶颈在模型。此时训练也有了明确目标:不是笼统地“让它更懂公司知识”,而是修正一种稳定、重复、可以被测试的行为。
经常变化的事实,反而不应该被训练进模型。报销金额、产品价格、当前制度都可能更新。一旦把这些内容藏进模型内部,我们不仅很难及时修改,也更难解释答案来自哪里。更稳妥的方式,是让事实留在模型之外,始终可以更新和复查;模型负责阅读、比较和表达,而不是偷偷成为公司制度的唯一副本。
这也是我们做排查工作时逐渐形成的判断。我们一开始面对的像是一个文档解析问题,后来发现真正需要保存的不是文本,而是文档原本所在的世界:章节在哪里,表格属于哪段说明,图片和正文如何相连,一份材料来自哪里,答案沿着什么路径找到它。
现在我是自己开发了一个小工具来解决这一系列的事情,从文档识别、解析,一路到 chunking 、embedding ,到最后形成 memory 提供给模型检索,都能一个工具解决。因为采用了树形解析的方式,所以每个 chunk 上的结构、层级信息都被完整地保留了下来,方便模型查看它的归属、它的时间、它的状态,以及跟其他同类信息进行对比。尤其是如果解析有问题,还能让模型自己回溯到相应的地方进行检索,自查自纠,100%溯源,避免幻觉。
有需要的可以自取,开源小项目: https://github.com/Ontos-AI/knowhere
用 API 或者 Claw 版也行: https://knowhereto.ai/?utm_source=v2ex
最后,欢迎大家交流经验,多多分享,谢谢。