上个月我们做医疗知识库问答,RAG的召回率硬生生调到了78%,但内测用户满意度不到三成。所有人都说继续微调模型,我翻了一晚上badcase,发现一大半错误根本不是模型听不懂问题,而是检索回来的正确文档被放在了错误的位置,甚至被其他相似但无关的片段给挤掉了。有个问题明明是问“高血压患者能不能吃柚子”,系统召回的文章全是关于“柚子与药物代谢”,从语义上特别相关,但并没有正面回答禁忌。这种“检索到了但没用”的case多了以后,我开始怀疑整个RAG的评估体系是不是本身就建在流沙上。
后来我花了三周时间做了个对比测试,把同样的知识库分别交给微调模型和RAG系统去跑。微调用的是Qwen-7B,花了大概800块买了卡时,LoRA跑了20个epoch,结果训练集损失降得漂亮,验证集一到新问法就现原形。而RAG这边,我用bge-large做embedding和重排序,MiniLM先生成128维的稀疏向量做召回,召回top30再重排选top5。单个问题的精确率从64%提升到79%,看着很爽吧?结果回答的正确率只从40%升到43%,这3%的差异在统计上根本不显著。那问题出在哪?我后来把检索回来的文档一段段拆开,发现只要把正确答案从原来的第2段挪到输出上下文的第7段以后,模型的正确率直接跌了接近25%。
这件事让我意识到,RAG工程里大家天天聊embedding模型、reranker、chunk大小,但几乎没人愿意花时间研究检索结果在prompt里的排布方式。这不就是注意力机制的老毛病吗?Transformer对位置编码本来就很敏感,超长上下文里头部和尾部注意力最集中,中间部分基本是“记忆黑洞”。我们却总是把检索回来的片段一股脑塞进上下文中间,然后指望模型自带海马体功能,这不是瞎扯吗?我把这个问题叫做“上下文工程”缺失——它不同于提示工程那种技巧性套话,而是要把文档当成一种物理空间来设计:哪段信息该出现在什么位置,用什么样的符号标识让模型知道“这是用户原话”、“这是检索事实”、“这是可能相关的参考”,每一层都得明确。
后来我做了个挺糙的实验:同一套RAG管道,只改变检索片段的排列顺序和分隔符。把最相关的3篇排在最前面,然后用[引用]和[事实]这样的显式标签,而不是靠自然语言去说“以下是参考资料”。就这么个改动,回答正确率从43%直接拉到61%。接着我调整了每篇片段的最大长度,把所有内容截断到512个token而不是贪心保留全文,反而再涨了4%。因为长文本里真正的关键信息往往就一两句,全塞进去不仅稀释了注意力,还让模型更容易被中间段的噪声带偏。那个月我们最后没有微调任何模型,就靠这种“上下文排布手术”把系统救活了。
现在如果有人问我AI开发里最被低估的模块是什么,我会说是上下文工程——它比RAG的召回率更敏感,比微调的性价比高得多。微调适合固定输出格式、语气和领域方言,但你要是想靠它往模型脑子里塞新知识,几百条数据根本不够,过拟合会让你哭都来不及。RAG负责找知识,可找到之后怎么摆、以什么姿态告诉模型,这才是真正拉开效果差距的地方。我甚至觉得未来会有人专门做“上下文布局工程师”,就像前端工程师把信息设计成用户容易消费的页面一样,我们该把大模型当成一个注意力有限的用户,而不是一本什么都能记住的百科全书。
这个项目结束后我留下一个检查清单,你也可以试试:先看badcase里检索到的正确文档是不是排在第三个之后?如果是,先调排序和截断规则,别动模型。再看看你的prompt里是不是写了一堆“以下是你的参考”这种废话?删掉,换成结构化的块和边界符号。最后,如果你还没给每条上下文标注“来源”、“置信度”或“与问题的相关性”,就别谈什么复杂优化了。我们总以为AI开发的门槛在模型参数量上,实际上大多数坑都埋在字节和位置这些最无聊的参数里。