1. 为什么“喂对信息”比“换个模型”更管用
这两年跟不少做AI应用的朋友聊,发现一个特别普遍的现象:模型一换再换,从这家换到那家,参数从7B一路加到几百B,结果业务效果该拉胯还是拉胯。钱花了不少,延迟还上去了。问题出在哪?十有八九不是模型不行,是喂给模型的东西不对。
“上下文工程”这个词最近被提得越来越多,说白了就是一门“让AI看到该看的东西”的手艺。它研究的不是怎么训练模型,而是怎么在推理阶段,把最相关、最精炼、最结构化的信息塞进那个有限的上下文窗口里。你可以把大模型想象成一个能力极强但记性有限的顾问——他聪明是真聪明,但你每次只能给他看几页纸,给错了,他再聪明也只能瞎猜。
这个内容适合谁看?如果你正在做RAG问答、智能客服、代码助手、文档分析这类应用,或者你只是单纯觉得“我明明把资料给它了,它怎么还是答非所问”,那这篇就是写给你的。我不打算讲一堆论文里的公式,而是把我在实际项目里踩过的坑、试出来的参数、以及那些文档里不会写的经验,一条条摊开来说。核心就一句话:上下文的质量,直接决定AI输出的上限,模型只是把这个上限兑现出来而已。
2. 上下文工程到底在解决什么问题
2.1 上下文窗口不是越大越好
很多人有个误区:既然模型支持128K甚至更长的上下文,那我就把整本手册、整个知识库全塞进去不就完了?实测下来,这么干通常有三个后果。第一,成本飙升,token是按量计费的,你塞十万token进去,每次调用都是真金白银。第二,延迟爆炸,长上下文的首token延迟能到好几秒,用户体验直接崩。第三,也是最反直觉的——信息越多,模型反而越容易抓不住重点。
这背后有个叫“迷失在中间”(Lost in the Middle)的现象:模型对上下文开头和结尾的信息记得比较牢,中间那一大段容易被忽略。你把关键答案埋在第五千字的位置,模型很可能视而不见。所以上下文工程的第一性原则不是“塞满”,而是“精选”。窗口大是给你留余地的,不是让你挥霍的。
2.2 上下文工程的四个核心动作
我把整个流程拆成四个动作,后面每个章节都会围绕它们展开。
- 检索:从海量资料里把可能相关的内容捞出来。这一步决定了召回率,捞漏了后面全白搭。
- 筛选与重排:捞出来的一堆里,哪些是真相关?按相关度排序,把最该看的放前面。
- 压缩与结构化:把长文档压成要点,把散乱信息整理成模型好理解的格式。
- 组装与预算分配:在有限的token预算里,决定系统提示、历史对话、检索内容、用户问题各占多少。
这四个动作环环相扣。我见过太多项目只做了第一步检索,后面三步全靠模型“自由发挥”,效果自然不稳定。下面逐个拆。
3. 检索环节:先把该捞的捞回来
3.1 分块策略决定了检索的天花板
检索的第一步是分块(Chunking)。文档不能整篇丢进去检索,得切成小块。切多大?这是第一个要命的参数。
切太小,比如按句切,每块只有一两句话,语义不完整,检索出来断章取义。切太大,比如按整章切,一块几千字,检索精度下降,还浪费token。我的经验值是300到800个token一块,块与块之间保留10%到20%的重叠。重叠是为了防止一个完整的语义被硬生生切断——比如一个答案跨在两块交界处,没有重叠就两边都捞不全。
但这不是死规矩。技术文档、法律条文这种结构清晰的,可以按标题层级切,一块就是一个完整小节。聊天记录、会议纪要这种口语化的,按对话轮次或时间窗口切更合适。我做过一个客服知识库的项目,最初按固定长度切,效果一般;后来改成按“问题-答案”对切,每块就是一个完整的FAQ,召回准确率直接上了一个台阶。分块的本质是让每一块都成为一个语义自洽的最小单元,这个原则比具体数字重要得多。
3.2 向量检索不是万能药
现在一提检索就是向量数据库,好像embedding一算就万事大吉。但纯向量检索有个硬伤:它擅长语义相似,不擅长精确匹配。用户问“订单号A12345的状态”,向量检索可能给你返回一堆讲“订单状态查询流程”的文档,就是找不到那个具体订单号。因为数字和专有名词在向量空间里区分度很低。
所以成熟的做法是混合检索:向量检索负责语义召回,关键词检索(比如BM25)负责精确匹配,两路结果合并后再重排。我一般会给关键词检索留30%左右的权重,具体看场景。如果用户query里经常出现编号、型号、人名这类实体,关键词那一路的权重还得往上调。
提示:混合检索的融合方式有两种,一种是加权求和,一种是倒数排名融合(RRF)。RRF对两路分数尺度不一致的情况更鲁棒,我一般优先用它,省得去调权重。
3.3 查询改写:别拿用户的原话直接去搜
用户的问题往往很口语、很模糊。“那个东西怎么弄来着”——你拿这句话去检索,神仙也捞不出对的。查询改写就是先把用户问题“翻译”成适合检索的形式。
常见的手法有三种。一是扩展:把“怎么退款”扩展成“退款流程、退款条件、退款到账时间”。二是分解:把“A和B有什么区别”拆成“A是什么”和“B是什么”两个子查询分别检索。三是假设文档生成:先让模型根据问题编一个“理想答案”,再拿这个答案去检索,因为答案和真实文档的语义更接近。这招在学术检索里效果很好,但会多一次模型调用,延迟敏感的场景要权衡。
我实测下来,查询改写对召回率的提升通常在10到25个百分点之间,是性价比极高的一步。但要注意别改过头,把用户的原始意图改没了。稳妥的做法是原始query和改写query都去检索,结果合并。
4. 筛选与重排:把最该看的排到最前面
4.1 重排模型是精度的守门员
检索捞回来比如20条,但上下文预算只够放5条。放哪5条?这就轮到重排(Rerank)出场了。向量检索用的是双塔结构,query和文档分别编码,速度快但精度有限。重排模型用的是交叉编码,把query和文档拼在一起过一遍模型,精度高得多,但慢,所以只适合对少量候选做精排。
我的标准流程是:向量+关键词混合检索召回50条,重排模型精排出前5到8条,再进上下文。重排这一步能把最相关的顶上来,实测对最终答案准确率的贡献非常明显。选重排模型时,别只看榜单分数,要看它在你的领域数据上的表现。通用榜单第一的模型,在你的垂直领域未必最好。
4.2 去重和多样性不能忘
重排之后还有个容易被忽略的步骤:去重。检索回来的内容经常高度重复,五条文档讲的是同一件事。全塞进去既浪费预算,又会让模型误以为这件事特别重要。我一般用语义相似度做去重,相似度超过0.9的只保留分数最高的那条。
另一个是多样性。有时候答案需要综合多个方面,如果检索结果全集中在某一个角度,模型就会答偏。可以用最大边际相关(MMR)这类算法,在相关性和多样性之间找平衡。简单说就是:选第一条要最相关,选第二条要既相关又和第一条不太一样,以此类推。
4.3 元数据过滤:用结构化信息砍掉噪音
很多文档自带元数据——时间、来源、类型、权限。这些信息在检索时是极好的过滤器。比如用户问“最新的政策”,你就该按时间倒序,把过期的直接过滤掉。用户问“技术方案”,你就该把会议纪要、闲聊记录排除在外。
我习惯在入库时就把元数据抽好、打好标签,检索时先做一轮硬过滤,再做语义检索。这样能大幅缩小检索范围,既提精度又降延迟。别小看这一步,很多项目效果差就是因为把一堆不相关的文档也扔进了检索池。
5. 压缩与结构化:让模型读得懂、读得快
5.1 上下文压缩的三种思路
检索回来的文档往往很长,直接塞进去占预算。压缩就是把它变短,但保留关键信息。三种思路,各有适用场景。
第一种是抽取式压缩:从原文里挑出最相关的句子,其余丢掉。优点是保真,不会引入错误;缺点是可能不连贯。适合事实性问答。
第二种是生成式压缩:让模型把长文档改写成简短摘要。优点是流畅连贯;缺点是有引入幻觉的风险,摘要可能偏离原意。适合需要综合理解的场景。
第三种是结构化压缩:把非结构化文本转成表格、列表、键值对。比如把一段产品介绍压成“型号/价格/参数”三行。模型对结构化信息的理解效率远高于大段散文。我特别推荐这一种,尤其在信息本身就有结构的时候。
注意:压缩是有损的,压得太狠会丢关键细节。我的做法是保留原文的引用锚点,模型如果觉得摘要不够,可以顺着锚点去取原文。这样兼顾了效率和完整性。
5.2 提示词的结构化组装
上下文不只是检索内容,还包括系统提示、对话历史、用户问题。这些部分怎么排布,直接影响模型表现。
我的默认排布顺序是:系统指令放最前,然后是检索到的参考资料,接着是对话历史,最后是用户当前问题。为什么这么排?因为模型对开头和结尾最敏感。系统指令是“宪法”,必须放开头让它时刻遵守;用户问题是“当前任务”,放结尾让它立刻响应;参考资料夹在中间,作为依据。
参考资料内部也要标清楚。我习惯给每条资料编号,并注明来源,让模型引用时能对上号。格式上,用清晰的分隔符把不同部分隔开,别让模型分不清哪是资料哪是指令。这一点在防提示注入时也特别重要——用户输入里如果混了“忽略以上指令”这种话,清晰的分隔能降低被带偏的风险。
5.3 token预算怎么分
这是最实操的一环。假设你的模型上下文窗口是8K token,怎么分?
| 部分 | 建议占比 | 说明 |
|---|---|---|
| 系统指令 | 10%到15% | 固定不变,可缓存 |
| 对话历史 | 20%到30% | 只保留最近几轮,老的做摘要 |
| 检索资料 | 40%到50% | 核心依据,按重排顺序放 |
| 用户问题 | 5%到10% | 通常很短 |
| 输出预留 | 10%到15% | 给模型生成答案留空间 |
这个比例不是死的。如果任务复杂、需要多轮推理,对话历史可以多留;如果是单轮问答,检索资料可以多占。关键是心里要有这本账,别让某一部分悄悄吃掉了预算。我见过系统提示写了三千字的项目,检索资料根本没地方放,效果能好才怪。
6. 实操过程:从零搭一条上下文流水线
6.1 整体架构与数据流
我把整条流水线画成一条数据流,方便你对照自己的项目。
用户提问进来,先做查询改写,得到原始query和改写query。两路query分别走向量检索和关键词检索,各召回一批候选。候选合并后做去重,再送进重排模型精排。精排后的top结果做压缩和结构化处理。同时,对话历史做滚动摘要,只保留最近几轮原文加更早的摘要。最后,系统指令、压缩后的资料、历史摘要、用户问题按预算组装成最终prompt,送进模型。
这条链路里,每一步都有可调参数。下面挑几个关键的说。
6.2 关键参数的计算与选择
分块大小:我一般从500 token起步,重叠100 token。然后拿一批真实问题去测召回率,如果发现答案经常跨块,就加大重叠;如果检索噪音多,就减小块大小。
召回数量:混合检索每路召回20到30条,合并后去重,通常剩30到50条进重排。召回太少会漏,太多重排压力大。这个数要结合你的重排模型吞吐来定。
重排保留数:进上下文的资料条数,我一般控制在5到8条。太少信息不足,太多稀释注意力。如果单条资料很短,可以适当多放几条。
历史轮数:保留最近3到5轮原文,更早的做摘要。摘要长度控制在200字以内。这样既保留了上下文连贯性,又不至于挤占资料空间。
这些数字都是起点,不是终点。每个项目的数据分布不同,必须用真实数据去调。我习惯建一个小评测集,几十条问题加标准答案,每次调参跑一遍,看准确率变化。没有评测集就调参,等于闭着眼睛开车。
6.3 一个可复现的组装示例
下面是一个组装后的prompt长什么样,我用伪代码示意结构,你可以直接套。
[系统指令] 你是一个严谨的问答助手。只依据下方【参考资料】回答, 资料中没有的信息,明确说“资料未提及”,不要编造。 回答时用[编号]标注依据来源。 [参考资料] [1] 来源:产品手册v2.3 退款需在签收后7天内发起,到账时间为3到5个工作日。 [2] 来源:客服FAQ 退款申请入口在“我的订单”页面,点击对应订单的“申请退款”。 [3] 来源:财务说明 退款原路返回,若原支付方式失效,需联系客服人工处理。 [对话历史摘要] 用户此前询问过订单A12345的物流状态,已告知预计明日送达。 [用户问题] 那这个订单怎么退款?这个结构里,系统指令明确了边界和引用要求,参考资料带编号和来源,历史做了摘要,用户问题在最后。模型拿到这样的输入,基本不会跑偏。你可以把这段直接改成你项目里的模板。
7. 常见问题与排查技巧实录
7.1 效果不稳定的排查顺序
效果时好时坏,先别急着换模型。按这个顺序查:先看检索,把每次实际进上下文的资料打出来看,是不是该有的没检索到,或者检索到一堆不相关的。再看重排,是不是把最相关的排到了后面。然后看压缩,是不是把关键信息压没了。最后才看模型和提示词。八成的问题出在前三步,模型背了很多不该背的锅。
7.2 常见问题速查表
| 现象 | 可能原因 | 排查动作 |
|---|---|---|
| 答非所问 | 检索没召回对的资料 | 检查分块和查询改写 |
| 答案不完整 | 资料被截断或压缩过度 | 检查分块重叠和压缩比例 |
| 引用错乱 | 资料编号不清或来源缺失 | 检查组装格式 |
| 忽略中间资料 | 关键信息埋在中间 | 把最相关的排到最前 |
| 重复啰嗦 | 检索结果高度重复 | 加去重步骤 |
| 编造信息 | 资料不足但没约束 | 强化系统指令的边界 |
| 响应慢 | 上下文太长或重排太多 | 压缩上下文,减少重排候选 |
| 成本高 | token浪费在无关内容 | 做元数据过滤和压缩 |
7.3 几个文档里不会写的坑
第一个坑:别用同一个embedding模型做检索和重排。有些团队图省事,检索和重排用同一个模型,效果打折。重排要用专门的交叉编码模型,这是两码事。
第二个坑:对话历史别无限累积。我见过一个项目,把用户所有历史对话都塞进去,跑到后面上下文全是历史,资料没地方放。历史一定要做滚动摘要,老的压掉。
第三个坑:系统指令别写太长。有人把系统指令写成一篇小作文,几千字,结果模型光读指令就累了,还挤占资料空间。系统指令要精炼,把最关键的约束说清楚就行,细节靠资料补充。
第四个坑:评测集要覆盖边界情况。只测常见问题,调出来的参数在边界情况上会翻车。要专门准备一批“资料里没有答案”“问题很模糊”“需要综合多条资料”的测试用例。
第五个坑:缓存能省大钱。系统指令和固定资料这部分,很多模型支持提示缓存,重复调用时这部分不计费或打折。把不变的内容放前面,能显著降成本。这个细节很多人不知道,白白多花钱。
8. 上下文工程的边界与取舍
做久了会发现,上下文工程不是越复杂越好。每加一个环节,就多一份延迟和一份出错的可能。小项目、简单问答,可能分块加向量检索就够了,硬上重排和压缩反而拖慢响应。大项目、复杂推理,才值得把整条流水线搭起来。
我的判断标准是:先跑通最简版本,用评测集量化效果,再针对短板加环节。短板在召回就加查询改写和混合检索,短板在精度就加重排,短板在成本就加压缩和缓存。每一步都要有数据支撑,别凭感觉堆技术。
还有一点,上下文工程和模型能力是互补的。模型本身推理能力越强,对上下文的容错就越高,你的工程压力就越小。所以选模型时,别只看价格,要看它在你的任务上“读懂上下文”的能力。有些模型便宜但需要你把上下文喂得特别干净,综合算下来未必划算。
最后分享一个我常用的自检习惯:每次线上出问题,第一件事就是把那次请求的完整上下文dump出来,从头到尾读一遍。十次里有八次,读完就找到原因了——要么资料没进去,要么进去的是错的,要么格式乱得模型没法读。上下文工程说到底就是个体力活加细心活,把每一次“喂进去的东西”都当回事,效果自然就稳了。