1. 从“提示词焦虑”到“上下文设计”:一个被忽视的转折点
如果你最近半年一直在折腾大模型应用,大概率经历过这样一个阶段:每天泡在各种提示词技巧里,研究怎么加“你是世界级专家”、怎么用“让我们一步步思考”、怎么塞few-shot示例。折腾一圈下来,效果时好时坏,同一个提示词今天跑得通,明天换个模型就翻车。我身边不少做AI应用的朋友都卡在这个瓶颈上,直到大家慢慢意识到一个事实——提示词本身能解决的问题,其实比想象中少得多。
真正决定模型输出质量的,往往不是那句指令写得多漂亮,而是模型在生成之前,它的“视野”里到底有什么。这就是“上下文工程”要解决的核心问题。说白了,提示词工程关注的是“你怎么问”,而上下文工程关注的是“模型看到了什么”。前者是话术,后者是信息架构。一个模型再聪明,如果你给它的上下文里塞了一堆无关噪音,或者缺了关键事实,它照样会一本正经地胡说八道。
这篇内容我想聊的,就是怎么系统性地设计模型看到的上下文——包括哪些信息该进、哪些该挡在外面、以什么顺序进、用什么格式进。适合已经跑通过基础对话Demo、但发现效果不稳定想进一步优化的开发者,也适合做RAG、Agent、知识问答这类应用的产品和技术同学。我不会给你一套万能模板,因为上下文工程本质上是个“因场景而异”的活儿,但我会把背后的判断逻辑和实操方法讲透,让你能自己推导出适合自己项目的方案。
先给一个我自己的定义:上下文工程是在模型推理之前,对输入信息进行筛选、组织、压缩和排序的一整套工程实践。它比提示词工程更靠前,也更底层。提示词是最后那层“包装”,上下文才是“货”。货不对,包装再精美也没用。
2. 上下文窗口不是越大越好:先搞清楚模型到底“看”到了什么
2.1 上下文窗口的物理边界与注意力衰减
很多人有个直觉:既然现在模型支持几十万甚至上百万token的上下文,那我干脆把所有相关资料全塞进去不就行了?实测下来,这个思路在简单任务上勉强能用,一旦任务稍微复杂,效果就会明显下滑。原因有两个层面。
第一个是物理层面。上下文窗口确实变大了,但模型对窗口内信息的“注意力”并不是均匀分布的。业界有个被反复验证的现象叫“中间遗忘”——模型对上下文开头和结尾的信息记得比较牢,中间部分容易被忽略。你塞进去十万字,真正被有效利用的可能只有头尾那几万字。这不是模型厂商没优化好,而是注意力机制本身的特性决定的。
第二个是信息干扰层面。假设你的上下文里有一段关键事实,同时又混进了五段语义相近但结论不同的内容,模型在生成时就会犹豫,甚至把不同来源的信息缝合在一起,产生看似合理实则错误的输出。我做过一个对比测试:同样一个问题,上下文里只放一段准确资料,回答正确率大概在九成;如果把这段资料和四段相似但过时的资料混在一起,正确率直接掉到六成左右。信息不是越多越好,冗余信息会稀释关键信息的权重。
2.2 一个生活化类比:上下文像给专家做简报
你可以把模型想象成一位能力很强但完全不了解你项目的顾问。你跟他开一次会,会议时间有限,他只能听你讲这么多。上下文工程就是你准备这份简报的过程。
如果你把公司三年的财报、所有会议纪要、竞品分析全堆给他,他听完大概率抓不住重点。但如果你提前筛选出“这次要解决的具体问题”“相关的三个关键数据”“一条明确的约束条件”,他就能给出高质量建议。简报的质量,直接决定顾问的输出质量。模型也一样,它不会抱怨你给的信息太多,但它会用糟糕的输出“抗议”。
2.3 判断“该给什么”的三个提问
每次设计上下文之前,我会问自己三个问题,这三个问题基本能框定筛选方向:
- 模型要完成这个任务,最少需要知道哪几件事?把必要信息列出来,这是底线。
- 哪些信息是“看起来相关但其实会误导”的?比如过时的版本、不同场景下的相似案例,这些要主动排除。
- 信息的呈现顺序会不会影响理解?先给背景还是先给问题,先给结论还是先给证据,效果差别很大。
这三个问题不需要复杂工具,拿张纸就能列。但就是这三步,能帮你砍掉一大半无效上下文。
3. 上下文筛选的实操框架:从“全量投喂”到“精准投喂”
3.1 信息分层:把上下文拆成四层来管理
我在实际项目里习惯把上下文分成四层,每层有不同的处理策略。这个分层方法不是标准答案,但用下来比较顺手。
第一层是系统指令层,也就是角色设定、输出格式要求、硬性约束。这层内容相对固定,通常放在上下文最前面。它的作用是给模型定调,告诉它“你是谁、要遵守什么规则”。
第二层是任务上下文层,包括当前要解决的具体问题、用户的历史交互、相关的业务背景。这层是动态的,每次请求都可能不同。它决定了模型这次要干什么。
第三层是知识证据层,也就是从知识库、文档、数据库里检索出来的事实性内容。这层是RAG应用的核心,也是最容易出问题的地方——检索回来的东西质量参差不齐,需要二次筛选。
第四层是示例层,给模型看的输入输出范例。这层可选,但在格式要求严格的任务里非常有用。
分层的好处是,你可以针对每一层单独优化。比如知识证据层效果不好,你就去调检索策略;系统指令层不稳定,你就去固化模板。混在一起调,根本不知道问题出在哪。
3.2 检索结果的二次过滤:别让向量库“骗”了你
做RAG的同学应该都有体会,向量检索返回的Top-K结果,经常有那么一两条是“语义相似但实际无关”的。比如你问“退款流程”,检索回来一条“退货政策”,字面很像,但根本不是一回事。如果直接把Top-K全塞进上下文,模型就可能把退货政策当成退款流程来回答。
我的做法是在检索和组装上下文之间加一道过滤。具体操作上,可以用一个轻量模型或者规则来判断每条检索结果和问题的实际相关性,把明显跑偏的剔除掉。更简单的办法是给检索结果加上来源标签和时间戳,让模型自己判断哪条更可信。实测下来,哪怕只是把Top-K从10条砍到4条,只要砍得准,回答质量反而会提升。
这里有个经验值可以参考:对于事实型问答,上下文里放3到5条高质量证据通常就够了;对于需要综合多个来源的分析型任务,可以放宽到8到10条,但每条都要有明确的来源标注。
3.3 上下文压缩:用“摘要”换“空间”
当单条资料本身就很长时,直接塞进去会占用大量窗口。这时候需要做压缩。压缩不是简单截断,而是提取和当前任务相关的部分。
我常用的方法是“问题导向摘要”:让一个便宜的小模型先读一遍长文档,只提取和当前问题相关的段落和结论,生成一段精简摘要,再把摘要放进主上下文。这样既保留了关键信息,又大幅节省了窗口空间。代价是多了一次模型调用,但对于长文档场景,这个代价很值。
注意:压缩会引入信息损失,所以压缩后的摘要最好保留原文的引用位置,方便在最终输出里做溯源。否则一旦模型基于摘要产生错误,你连排查都无从下手。
4. 顺序与格式的隐形力量:同样的信息,换个排法效果差一倍
4.1 位置效应:关键信息放头还是放尾
前面提到模型对上下文中间部分注意力较弱,这个特性直接影响信息排布。我的经验是:最关键的约束和问题放在开头,最重要的证据放在结尾附近,中间放次要的补充材料。
举个例子,如果你要模型基于一份合同回答“违约金怎么算”,那么开头应该明确“请严格依据以下合同条款回答,不要自行推断”,结尾附近放违约金相关的那几条条款原文,中间可以放合同的基本信息。这样模型在生成时,开头记住了约束,结尾附近看到了关键条款,中间部分即使被忽略也不影响核心任务。
4.2 格式选择:结构化标记比纯文本更“抗遗忘”
同样一段信息,用纯文本段落写和用带标签的结构化格式写,模型的处理效果不一样。结构化格式相当于给信息加了“路标”,模型更容易定位和引用。
我常用的格式是给每段证据加上来源和类型标签,比如:
[来源:产品手册v2.3 | 类型:官方定义] 退款指用户在购买后7天内发起的全额返还请求。 [来源:客服FAQ | 类型:操作说明] 退款需在订单页面点击“申请退款”按钮,填写原因后提交。这种写法比直接堆两段文字清晰得多。模型在生成时能明确知道哪条是定义、哪条是操作步骤,不容易混淆。而且一旦输出有问题,你也能快速定位是哪条来源的锅。
4.3 指令与证据的“距离”控制
还有一个容易被忽略的点:指令和它要约束的证据之间,距离不能太远。如果你在开头说“只依据以下三条规则判断”,然后隔了五千字才给出这三条规则,模型很可能已经忘了开头的约束。
我的做法是把强约束和它直接相关的证据放在相邻位置。比如判断类任务,我会写成“依据以下规则判断:[规则1][规则2][规则3],待判断内容:[内容]”。规则和内容紧挨着,模型不容易跑偏。
5. 动态上下文:让每一轮对话都“看到该看的”
5.1 多轮对话里的上下文膨胀问题
单轮任务的上下文好控制,多轮对话就麻烦了。每多聊一轮,历史消息就多一截,几轮下来窗口就被占满了。更糟的是,早期轮次里可能有一些已经过时的信息,比如用户一开始说“我想买红色的”,后面改口说“算了还是蓝色吧”,如果两句话都在上下文里,模型可能会纠结。
处理多轮上下文,核心思路是保留结论、压缩过程。用户明确表达过的偏好、确认过的事实、达成的共识,这些要保留;中间的试探、重复、无关闲聊,可以压缩掉。具体实现上,可以每几轮做一次历史摘要,把之前的对话浓缩成一段“当前状态描述”,替换掉原始消息。
5.2 状态追踪:把“对话历史”变成“结构化状态”
比摘要更彻底的做法是维护一个结构化状态对象。比如一个订票助手,它的状态可能是:
{ "出发地": "已确认", "目的地": "已确认", "日期": "待确认", "舱位偏好": "经济舱" }每一轮对话后更新这个状态,然后把状态而不是完整历史放进上下文。这样无论聊多少轮,上下文长度都是可控的,而且信息高度聚焦。缺点是前期需要设计好状态字段,对开放域对话不太适用。但对于任务型对话,这个方法非常有效。
5.3 触发式上下文注入:该出现时才出现
还有一种动态策略是“按需注入”。不是把所有可能用到的信息一开始就放进去,而是等对话进行到某个节点,判断需要某类信息了,再动态检索并注入。
比如一个技术支持助手,用户描述问题时,先只给基础排查步骤;如果用户反馈“还是不行”,再注入进阶排查方案;如果用户提到具体错误码,再注入该错误码的详细说明。这样上下文始终只包含当前最相关的信息,噪音最小。
实现上,这需要一个简单的意图判断环节,可以用规则也可以用模型。规则适合场景固定的应用,模型适合开放一些的场景。我一般先用规则跑通主流程,遇到规则覆盖不了的情况再补模型判断。
6. 踩过的坑与验证方法:上下文工程怎么调才不玄学
6.1 三个我真实踩过的坑
坑一:把“相关”当成“有用”。早期做知识问答,检索回来只要语义相似度高于阈值就全塞进去。结果模型经常把不同文档里的相似概念混在一起,输出自相矛盾。后来加了来源区分和冲突检测,才稳定下来。教训是:语义相关不等于对当前问题有用,必须结合任务判断。
坑二:上下文顺序随意排。有段时间我组装上下文时按检索分数从高到低排,觉得高分在前更合理。结果发现模型经常忽略后面的低分但关键的证据。后来改成“关键约束在前、核心证据在后、补充材料居中”,效果明显改善。顺序不是小事。
坑三:忽略格式的一致性。有一次上下文里混用了三种不同的标注格式,模型在引用时张冠李戴。统一格式后问题消失。格式一致性对模型理解的影响,比我想象的大。
6.2 怎么验证上下文改动的效果
上下文工程最怕“感觉变好了”这种主观判断。我的做法是建一个小型评测集,固定20到50个典型问题,每次改动上下文策略后跑一遍,记录正确率、引用准确率、格式合规率这几个指标。
正确率看最终答案对不对;引用准确率看模型引用的证据是否真的支持它的结论;格式合规率看输出是否符合要求的格式。三个指标一起看,基本能判断改动是正向还是负向。
如果条件允许,最好做A/B对比:同一批问题,一半用旧上下文策略,一半用新策略,人工或自动评分对比。这样得出的结论比单看几个case靠谱得多。
6.3 一个实用的调试技巧:把上下文“打印”出来看
模型看到的东西和你以为它看到的东西,经常不是一回事。我养成了一个习惯:每次调试时,把最终组装好的完整上下文打印出来,自己从头读一遍。读的时候问自己:如果我是模型,只看这段文字,能不能得出正确结论?有没有歧义?有没有缺失?
这个笨办法帮我发现了无数问题。很多时候不是模型不行,是上下文里藏着一句有歧义的话,或者少了一个关键限定条件。自己读一遍,比看一百个指标都直观。
7. 不同场景下的上下文策略差异
7.1 知识问答场景:证据为王,克制推断
知识问答的核心是“有据可依”。上下文里应该以检索到的事实证据为主体,系统指令里明确要求“只依据给定资料回答,资料中没有的信息不要编造”。证据要标注来源,方便溯源。对于证据之间可能存在的冲突,要么提前解决,要么在指令里说明“如遇冲突,以最新来源为准”。
这个场景下,我倾向于把上下文做得“窄而深”——证据数量不求多,但每条都要经过筛选和验证。宁可告诉用户“资料中没有相关信息”,也不要让模型基于模糊证据硬答。
7.2 创意生成场景:给方向,留空间
创意类任务(写文案、起名字、构思方案)的上下文策略完全相反。这时候不需要塞大量事实,而是要给足“调性参考”和“约束边界”。比如给几个风格示例、说明目标受众、列出必须包含的元素和必须避免的雷区。
创意场景下,上下文要“宽而浅”——提供氛围和方向,但不限制具体表达。塞太多硬性事实反而会束缚模型的发挥。我通常会给2到3个风格示例,加一段简短的调性描述,剩下的交给模型。
7.3 代码生成场景:接口定义和约束优先
代码生成对上下文的精确性要求极高。模型需要知道:用什么语言、什么框架、输入输出是什么、有哪些必须遵守的接口约定。这些信息要放在最前面,格式要严格。
我习惯把接口定义、数据结构、关键约束用代码块的形式放进上下文,而不是用自然语言描述。代码块对模型来说更“无歧义”。同时会明确说“不要引入未声明的依赖”“保持函数签名不变”这类硬约束。
7.4 Agent任务场景:状态和工具说明是核心
Agent类应用的上下文里,最重要的是当前状态和可用工具说明。模型需要知道“我现在在哪一步”“我有哪些工具可以用”“每个工具接受什么参数”。这些信息要结构化呈现,并且随着任务推进动态更新。
工具说明我一般写成简短的函数签名加一句用途描述,不写长篇文档。状态则用键值对或简短列表。Agent的上下文要尽量精简,因为每一步都要重新组装,太长会拖慢响应也增加出错概率。
8. 我个人的几条实操心得
第一,上下文工程没有一劳永逸的配置。模型在更新,你的数据在变化,用户的问题分布也在变。我一般每个月会重新跑一遍评测集,看看之前的策略是否还成立。发现效果下滑就及时调整。
第二,先做减法再做加法。新手容易犯的错是不断往上下文里加东西,觉得加得越多越保险。实际上应该反过来,先确定最小必要信息集,跑通之后再考虑要不要加。加的时候也要有明确理由,不能凭感觉。
第三,把上下文当成产品来设计,而不是当成字符串来拼接。它有自己的信息架构、有优先级、有格式规范。用做产品的思路去对待它,效果会好很多。
第四,保留可追溯性。无论怎么压缩和筛选,关键信息的来源要能追溯。这样出问题时你能快速定位,而不是面对一个黑盒干瞪眼。
最后分享一个我最近在用的检查清单,每次上线新的上下文策略前过一遍:必要信息是否齐全、冗余信息是否剔除、关键约束是否靠前、核心证据是否在有效注意力区、格式是否统一、来源是否可追溯、多轮场景下是否会膨胀。这七条过完,基本不会出大问题。上下文工程这件事,说到底就是让模型“看到该看的东西”,剩下的,交给模型自己的能力就好。