有一年我帮团队面 RAG 岗。候选人把七步链路背得很顺:清洗、切分、向量化、入库、召回、重排、生成,每一步都能报出对应的工具和框架。我问了最后一个问题,他上一次遇到的 bad case,落在哪一步。他停了一会儿,说记不清了。
那次之后我把题库砍了一半。链路可以背,bad case 背不出来。
背得出链路,不代表跑过链路
标准链路本身没有错,它是排查问题的地图。区别在于,做过的人会指出地图上哪一段最容易塌。
我会连着问三个问题。第一个是失败案例的具体形态:检索根本没召回到,召回对了但模型没用,还是数据和预期本来就不一致。第二个是当时试过什么、放弃了什么。第三个是现在这套系统里,哪个环节最脆。
这三个问题没有标准答案,但答得出来的人,通常对系统的边界有感觉。比如他知道知识库更新是异步的,用户刚上传的文件要过几分钟才能被检索到;知道权限过滤必须在检索前做,不能等生成完再遮;知道同一份制度存了三个版本时,系统只能挑一个,而挑错的责任不在模型。
数据侧的问题最难伪装。问一句 PDF 里的跨页表格怎么处理,答案立刻就分出来了:没做过的人会说按固定字数切分,做过的人会先讲版面分析、表格转结构化、标题挂到元数据上。后者不一定都对,但至少知道坑在哪。再问一句扫描件和截图怎么办,如果对方只提 OCR,不提识别错误率和后续清洗,基本可以判断这块他没真正跑过。
多租户也是我常问的点。检索前过滤和检索后过滤,看起来只是顺序差别,实际决定了会不会越权。向量库的元数据过滤能力、权限变更后的索引同步、删除用户数据时向量怎么清,这些细节比模型选型更能说明系统上过线。很多 demo 里没有“删除”这个动作,生产环境里它是必答题。
答不上来的人,往往把一切错误归给模型不够强,或者 prompt 没调好。这两个归因都太省事,而且都无法验证。
能力差异也不在会不会用框架。初级能搭出链路,高级知道链路的每一段会在什么条件下失效:并发上来之后向量库的连接池会打满,重排模型在长文本上会超时,增量写入会让索引短暂不可见。这些都不是文档里写的,是踩过才知道的。
所以我面人的时候会让对方画一遍链路,然后在图上标三个东西:延迟最大的那一段、最容易出错的那一段、成本最高的那一段。三处往往不是同一个地方,能分开标出来的人,说明他真的看过监控。只标得出“模型”的人,通常还没进过生产。
一个说不清失败案例的人,通常也没有真正做过评测。
因为评测的本质就是系统性地收集失败。没有评测集,bad case 只能靠用户投诉送上门,那种反馈既稀疏又滞后。
混合检索和重排,都是用复杂度换准确率
纯向量检索有一个稳定的短板:精确匹配。产品型号、报错码、生僻人名、合同编号,这些东西在语义空间里被压缩成“某种错误”或“某类文件”,返回的结果看着相关,实际没用。
所以生产环境基本都会加一路关键词检索。BM25 负责命中字符串,向量负责命中意思,两路结果用 RRF 融合。RRF 只看排名不看分数,绕开了 BM25 分数无上限、余弦相似度在零到一之间这种尺度不一致的问题,实现也简单,不需要训练一个融合模型。
代价是真实存在的。两套索引要维护,两套参数要调,故障面翻倍,写入链路也变长。知识库只有几千个块的时候,这一层复杂度换不回等价的收益,我一般先不加。
重排是另一笔交易。双塔模型可以离线把文档向量算好,在线只算 query;交叉编码器必须把 query 和文档拼在一起过一遍模型,精度高得多,但每一对都要实时算。所以它只能用在漏斗的第二层,对粗排的前几十条打分,不可能扫全库。
重排买的不是准确率,是“在这批候选里挑对的”的能力。候选本身错了,重排救不回来。
值不值得加,取决于用户能等多久。内部知识库多几百毫秒无所谓,实时语音对话里这几百毫秒就是事故。重排模型还要常驻显存,对小团队来说这是固定成本,不是按量付费。
模型大小的影响比想象中大。同样是重排,小模型快、显存友好,但对“意思相近但结论相反”的文档区分不出来;大模型判断更准,延迟和成本都往上走。我一般先用小模型跑评测集,看它在前几十条里的排序对不对,不够再换大的。直接上最大的,往往只是把延迟问题推迟到上线那天。
也有人主张干脆用长上下文替代检索,把所有材料一起塞进去。短文档、少量资料时确实可行,省掉了索引和维护。但材料一多,成本和延迟都会上去,而且中间部分容易被忽略。检索的价值不只是省 Token,它还在做筛选和排序,这件事长窗口替代不了。
向量库的选型我也按维护成本来。Faiss 严格说是个库,快,但持久化、分片、元数据过滤都要自己补。Milvus 能力全,代价是一套 K8s 集群和专职运维。Chroma、LanceDB 适合原型,规模上去以后扩展性有限。我近两年更愿意用 pgvector,因为业务数据本来就在 Postgres 里,向量和权限、时间戳做联合过滤很自然,少一个系统就少一类故障。代价是规模上限和索引可调的空间都不如专用库,数据到亿级就得重新考虑。
索引算法也是同一类取舍。HNSW 查询快、召回高,但图结构要常驻内存,构建慢,实时删除也不友好;IVF 配合量化省内存,适合海量数据,召回低一些,还得先训练聚类中心。数据量和内存预算定下来,答案基本就定了。
增量更新是我经常追问的一点。向量索引对单点插入不友好,常见做法是内存里放一个小索引接增量,磁盘上放一个定期合并的大索引,查询时两边都查再合并。缺点是数据可见性有延迟,可能是秒级也可能是分钟级。RAG 场景大多能接受,但如果产品承诺“上传即可问”,这个延迟就得写进交互里。
再往前走一步是查询改写。用户的问法常常不适合直接检索,一句话里塞了两个意图,或者用了文档里不存在的说法。让模型先把它拆成几个子查询,分别召回再合并,多意图问题的命中率会好一些。代价是多一次推理,以及拆错之后连原来的召回都保不住。
HyDE 是另一种思路:先生成一段假设性的答案,再用这段答案的向量去检索。它把“问题找文档”变成了“文档找文档”,在问题很短、表述和文档差很远的时候确实有效。但它依赖假设答案的方向大致正确,如果模型一开始就想偏了,检索会被带得更偏。我在短查询为主的场景用过,长查询场景收益不明显,反而多花一次生成。
混合检索的权重不要凭感觉设。留一个验证集,对两路的权重做网格搜索,看召回而不是看总分。这个过程很土,但比拍脑袋可靠,也方便在换 embedding 模型之后重新校准。
索引重建的成本也要提前算。全量重建在千万级数据上可能要跑很久,期间写入还得有个地方接住。我的做法是把重建做成可重复的离线任务,跑完再切,避免在主链路上做原地更新。
微调改的是形式,RAG 改的是事实
这两件事经常被放在一起比较,其实解决的问题不同。RAG 提供外部上下文,更新数据源就生效;微调改变模型的输出习惯,格式、语气、术语、指令遵循。
需要精准引用的事实查询,走 RAG。需要模型稳定输出某种结构,或者学会一套行业黑话,走微调。两者叠加也常见:先用 SFT 把输出格式固定下来,再用 RAG 注入当天变更的数据。
SFT 阶段我最在意的是数据质量而不是数量。这件事在 LIMA 那篇工作之后基本成了共识:少量精选样本能激活预训练里已有的能力,SFT 更像是在做表面对齐,而不是灌新知识。所以拿几万条嘈杂数据去堆,通常不如几百条人工过一遍的样本。样本的多样性比单条的精美更重要,全是一个模板的问答,训出来只会更死板。
对齐环节,RLHF 和 DPO 的差别主要在工程成本。PPO 要同时维护策略、价值、奖励、参考几个模型,超参敏感,训练容易发散;DPO 直接在偏好对上优化,显存和实现都轻得多。但 DPO 也不是免费的,它很容易在偏好集上过拟合,输出分布会塌,得靠 beta 参数、混入 SFT loss、早停这些手段压住。
我的判断是,大多数团队根本走不到对齐这一步。业务上的“幻觉”八成是检索没做好或者没有拒答机制,不是价值观问题。把 RLHF 当成幻觉的解法,方向就错了:它优化的是偏好,不是事实。
真正有效的顺序是:先确认检索能召回,再压 prompt 让它只用材料回答,再给一个体面的拒答出口,最后才考虑用训练去改变行为。这个顺序每往上一层的成本都高一个量级,收益却不一定递增。
微调还有一笔账容易被忽略:维护。数据一变就要重训,重训要重新评测,评测要重新标注。RAG 更新一份文档只需要一次写入,模型更新一次知识要动整条训练链。所以我倾向于把变化快的知识全部放在检索侧,只把稳定的格式、语气、输出结构放进权重。判断标准很简单:这份知识半年内会不会变。会变,就不要训进去。
还有一层边界值得说清:SFT 解决“会不会按格式说话”,对齐解决“该不该说、说得得不得体”,两者都不解决“说得对不对”。把安全问题和事实问题混在一起,往往两边都做不好。我见过用偏好训练去修事实错误的方案,最后模型变得更爱拒答,错误率却没降。
幻觉首先是信息问题,其次才是模型问题。
这句话我用来判断一个人是不是真的调过系统。把所有失败都往模型能力上推,通常意味着他没有认真看过中间结果。
评测是最容易被跳过的一步
通用榜单测的是参数化知识,物理、历史、常识推理。它和公司内部的合同、工单、产品手册没有关系。用 C-Eval 的排名来证明选型正确,在面试里是减分项。
我要求的是一个小规模的黄金集:几十到一百条真实问题,每条标注标准答案和必须出现的关键条件。真实问题很重要,自己编的问题太规整,覆盖不到用户那种省略主语、夹杂错别字的问法。
指标要分层看。检索层看正确答案所在的那一块有没有进 top-k,生成层看答案是否只依据了召回的内容。这两层混在一起看,最后只会得到一个说不清来源的总分。检索不行的时候去调 prompt,是最常见的无效劳动。
用现成框架时,我通常只借它的三个概念:召回内容是否相关、答案是否被内容支撑、答案是否回应了问题。前一个归检索,后两个归生成。任何一个单独拿出来都会误导:忠实度高但相关性低,说明模型在一本正经地回答另一个问题;相关性强但忠实度低,说明它在用材料的语气讲自己的记忆。
分层还能省时间。如果召回里根本没有答案,就不用再看生成;如果召回里有答案而模型没用,才轮到检查指令、上下文顺序、温度这些参数。排查顺序错了,会在一堆无关变量里打转。
用大模型当裁判可以,但要接受它的偏好:偏爱长答案、偏爱结构整齐的答案、对数字不敏感。同一份输出换个顺序打分,结果可能不一样。所以关键结论我还是人工抽查,尤其是那些看起来特别完整的回答。完整的回答最容易掩盖缺失。
评测集还要能回归。每次改 prompt、换模型、调 top-k,都跑一遍,指标不降才放量。这件事很枯燥,维护成本也不低,问题会过期,答案会变,标注的人会换。我暂时没有找到让它自动更新的办法,只能定期人工过一遍。
上线前我还会单独看一批“最容易错”的样本。不是随机抽,是把历史上出过问题的、边界模糊的、涉及多个文档的挑出来,逐条读完整回答。自动化指标告诉我有没有变差,人工阅读告诉我差在哪里。这两件事不能互相替代。
抽样的成本也要控制。全量让人看不可能,全量让模型看又太贵,我的做法是固定比例抽样加分层抽样:正常流量里随机取一小部分,再按业务分类各取几条。比例很低,主要看趋势,不看单次分数。
技术指标和业务指标要分开记。召回率、忠实度、延迟这些是手段,用户有没有解决问题才是目的。中间会出现一段背离:指标都在涨,满意度不动,因为涨的那部分都在用户不关心的场景里。遇到这种情况,我会先回去看问题分布,而不是继续调模型。
我暂时还没有好办法的是多跳问题。答案分散在三份文档里,需要先关联再推理,单次检索拿不到。GraphRAG 这类方案能解,但构图、抽取实体、维护更新,成本比整套检索链路还高。多数业务里我会先劝退,把问题拆成两个单跳查询,让用户多点一次。
这个取舍不好看,但它可维护。而可维护,是我现在评估任何方案时权重最高的一项。
评论