1. 上下文工程到底在解决什么问题
第一次听到“上下文工程”这个词,很多人会下意识觉得它不过是“提示词工程”换了个马甲。我一开始也这么想,直到在实际项目里连续踩了几次坑,才意识到这两者根本不在一个层面上。提示词工程关心的是“这句话怎么写”,而上下文工程关心的是“模型在做决策的那一刻,眼前到底能看到什么”。这个区别听起来很微妙,但它直接决定了一个系统是只能做演示,还是能真正上线跑业务。
举个我亲身经历的例子。早些年我做过一个智能客服的原型,提示词打磨得相当漂亮,单轮问答准确率能到九成以上。可一旦用户连续追问三四轮,模型就开始答非所问,甚至把前面已经确认过的订单号记错。当时我以为是模型能力不够,换了好几个更大的模型,问题依旧。后来才想明白,症结不在模型,而在于我从来没有认真设计过“每一轮对话里,究竟有哪些信息应该被送进模型的上下文窗口”。历史对话无脑全塞进去,噪声越积越多,关键信息反而被淹没。这就是典型的上下文工程缺失。
所以,上下文工程(Context Engineering)指的是:围绕大模型在完成一次具体任务时所能感知到的全部信息,进行系统性的选择、组织、压缩、排序和动态管理的一整套方法论与工程实践。它管的不是一句话的措辞,而是模型“工作台”上摆放的所有材料——包括系统指令、历史对话、检索到的知识片段、工具返回结果、用户画像、当前任务状态等等。用个生活化的类比:提示词工程像是教一个人怎么措辞提问,而上下文工程像是给这个人准备一间办公室,决定桌上放哪些文件、哪些该收进抽屉、哪些该扔掉、哪些要贴在墙上随时能看见。
它解决的问题非常具体。第一是信息过载:模型的上下文窗口再大也是有限的,把所有东西都塞进去,不仅成本高,还会稀释关键信息的权重。第二是信息缺失:该给的信息没给,模型只能靠猜,结果就是幻觉。第三是信息错位:信息给了,但顺序不对、格式不对、时机不对,模型照样用不好。第四是状态漂移:多轮交互中,早期的重要约束被后续内容冲淡,导致行为不一致。
这套东西适合谁来学?我的判断是:只要你在做基于大模型的应用,无论你是写提示词的、做后端的、搞产品的,都绕不开它。尤其是那些已经过了“调提示词”阶段、开始处理真实业务复杂度的团队,上下文工程往往是决定成败的那一环。新手可以从理解概念入手,有经验的开发者则应该把它当成和数据库设计、接口设计同等重要的架构能力来对待。
2. 上下文工程和提示词工程的区别与联系
2.1 一个管“说什么”,一个管“能看到什么”
我习惯用这样一个对比来跟团队里新人解释:提示词工程是“遣词造句”,上下文工程是“布置考场”。你题目出得再好,如果考生桌上堆满了无关的草稿纸,或者关键的参考资料没放上去,成绩照样一塌糊涂。
提示词工程的核心动作是撰写和优化指令文本,关注的是语气、格式、示例(few-shot)、思维链引导这些。它的产出通常是一段相对静态的文本模板。而上下文工程的核心动作是动态组装:在每一次模型调用前,根据当前任务、用户、历史、外部数据,实时决定这次要放哪些内容进去、放多少、按什么顺序放。它的产出是一套运行时逻辑,而不是一段固定文本。
这两者是包含关系还是并列关系?我的理解是:提示词工程是上下文工程的一个子集。系统指令本身就是上下文的一部分,只不过它相对稳定。上下文工程的视野更大,它把提示词、检索内容、工具输出、记忆、状态全部纳入统一管理。
2.2 为什么现在必须把上下文单独拎出来讲
有几个现实变化逼着我们必须重视它。第一,模型能力越来越强,但上下文窗口的边际效益在递减。你把窗口从几万扩到几十万,不代表模型就能用好里面的每一条信息,中间信息丢失、首尾偏好这些问题依然存在。第二,检索增强生成(RAG)普及之后,检索回来的内容质量参差不齐,怎么筛选、怎么排序、怎么压缩,成了新的技术难点。第三,多轮、多工具、多智能体的复杂流程越来越多,状态管理变得极其棘手。
我踩过的一个典型坑是:在一个文档问答系统里,检索模块一次返回了二十个片段,我图省事全塞进上下文。结果模型回答时经常引用一些边缘的、甚至互相矛盾的片段,答案质量反而不如只给三个精准片段。后来我加了一层重排序和去重,把片段压到三到五个,准确率立刻回升。这件事让我彻底明白,上下文的质量远比数量重要,而质量是需要工程手段去保证的。
2.3 两者的协作关系
实际项目中,提示词工程和上下文工程是配合使用的。系统指令、任务模板这些属于提示词工程的范畴,它们构成了上下文的“骨架”;而检索内容、历史摘要、工具结果这些动态部分,属于上下文工程的“血肉”。骨架要稳,血肉要精。我通常会把稳定的指令固化下来,把动态的部分做成可配置、可观测的流水线,这样调试的时候能快速定位到底是“指令没写清”还是“上下文没给对”。
3. 上下文的组成要素拆解
3.1 系统指令与角色设定
这是上下文里最稳定、也最容易被忽视的部分。系统指令定义了模型的身份、行为边界、输出格式要求。很多人写系统指令喜欢堆砌形容词,比如“你是一个专业、严谨、友好的助手”,但这类描述对模型行为的实际约束力很弱。我更推荐用具体规则代替抽象形容,比如“回答必须基于提供的资料,资料中没有的内容要明确说明无法回答”“输出必须是JSON格式,字段包括answer和confidence”。
系统指令在上下文中的位置通常是开头,而模型对开头和结尾的信息敏感度更高,所以关键约束放在这里是对的。但要注意,系统指令太长也会挤占空间,我一般控制在几百字以内,把最硬的规则放前面。
3.2 历史对话与记忆管理
多轮对话是上下文工程的主战场。无脑保留全部历史是最常见的错误。我的做法是分层处理:最近几轮保留原文,稍早的轮次做摘要,更早的只保留关键实体和结论。摘要不是随便让模型总结,而是要有针对性地提取“对后续任务有用的信息”,比如用户确认过的参数、已经排除的选项、达成的共识。
这里有个实操心得:摘要的触发时机很重要。不要等上下文快满了才摘要,那样容易丢信息。我通常设定一个阈值,比如历史轮次超过八轮,或者token数超过窗口的某个比例,就触发一次滚动摘要。摘要本身也要存起来,形成“长期记忆”,下次需要时可以再检索回来。
3.3 检索增强内容
RAG场景下,检索回来的片段是上下文的大头。这里的关键动作有三个:筛选、排序、压缩。筛选是去掉明显不相关的;排序是把最相关的放前面或放结尾(利用首尾偏好);压缩是把长片段提炼成要点,减少token占用同时保留信息。
我常用的一个技巧是给每个片段打上来源标记和相关性分数,然后在上下文里按分数排序,并在片段前加一句简短的说明,比如“以下内容来自产品手册第三章,与问题高度相关”。这样模型能更好地判断该信任哪段内容。
3.4 工具调用结果
当模型调用外部工具(比如查数据库、调API)后,返回结果会进入上下文。这里最容易出问题的是结果格式不统一和结果过大。我的经验是,工具返回后先做一层清洗和裁剪,只把模型真正需要的字段留下,并且统一成结构化格式。比如查询订单返回一大堆字段,其实模型只需要订单号、状态、金额,那就只给这三个。
3.5 任务状态与中间变量
在复杂流程里,当前进行到哪一步、已经收集了哪些参数、还缺什么,这些状态信息必须显式地放进上下文,否则模型会“忘记”自己在干什么。我习惯用一个结构化的状态对象,每次调用前序列化后注入,让模型始终清楚当前的任务进度。
4. 上下文工程的核心操作手法
4.1 选择:什么该进,什么该出
选择是上下文工程的第一道关。判断标准只有一个:这条信息对当前这次决策有没有直接帮助。有帮助的进,没帮助的坚决不进。听起来简单,做起来难,因为“有没有帮助”往往需要预判。
我的做法是建立一个打分机制。对每条候选信息,从相关性、时效性、权威性三个维度打分,综合分数低于阈值的直接丢弃。这个机制可以先用规则实现,后面再考虑用模型来辅助判断。关键是养成“先筛选再组装”的习惯,而不是“先全塞再祈祷”。
4.2 压缩:在有限空间里保留最大信息量
压缩的手段有很多。最粗暴的是截断,但截断容易丢关键信息,我一般只在末尾内容明显次要时才用。更优雅的是摘要,让模型把长文本提炼成要点。还有一种是结构化提取,把自然语言里的关键实体和关系抽成表格或JSON,信息密度极高。
我实测下来,对于技术文档类内容,结构化提取的效果最好,token能压到原文的十分之一,关键信息还基本不丢。对于叙事类内容,摘要更合适。压缩的时候一定要保留原文的出处,方便需要时回溯。
4.3 排序:位置决定权重
模型对上下文不同位置的敏感度是不一样的。普遍观察是开头和结尾的信息更容易被记住和利用,中间部分容易被忽略。所以组装上下文时,最重要的信息要么放最前面,要么放最后面。
我的常规排布是:系统指令放最前,当前任务和最关键的资料紧随其后,然后是次要资料,最后再重复一次核心约束或问题。这种“首尾呼应”的结构,实测能明显提升模型对关键信息的利用率。
4.4 动态管理:让上下文活起来
上下文不是一次性组装好就完事,它需要在对话或任务推进过程中动态调整。比如用户突然切换话题,那之前话题的历史就该被压缩或移出;比如检索到新资料,要决定是追加还是替换旧资料。
我通常会把上下文管理做成一个独立模块,对外暴露“添加”“移除”“压缩”“重排”几个操作,主流程只负责调用。这样逻辑清晰,也方便观测每次操作对最终输出的影响。
5. 一个可复现的上下文组装流程
5.1 场景设定与目标
假设我们要做一个技术文档问答助手,用户会连续提问,系统需要基于一批内部文档回答,并且支持多轮追问。目标是回答准确、不编造、多轮不跑偏。这个场景足够典型,能覆盖上下文工程的大部分要点。
5.2 上下文组装的具体步骤
第一步,接收用户问题后,先做意图识别和实体抽取,明确用户到底在问什么、涉及哪些关键词。第二步,用这些关键词去检索文档库,拿到候选片段。第三步,对候选片段做重排序和去重,选出最相关的三到五段。第四步,检查历史对话,如果轮次较多,触发摘要,把早期历史压缩成要点。第五步,组装上下文:系统指令在最前,然后是历史摘要,接着是检索片段(按相关性排序),最后是当前问题和输出格式要求。第六步,调用模型,拿到结果。第七步,把本轮问答加入历史,更新状态。
这个流程里,每一步都有可以调优的参数,比如检索返回多少条、重排序用什么模型、摘要保留多少字、片段怎么排序。我建议先把流程跑通,再逐个参数调优,不要一上来就追求完美。
5.3 关键参数的选择与计算
检索返回条数我一般设成十到二十条作为候选,重排序后取三到五条进上下文。为什么是这个数?因为实测发现,超过五条之后,新增片段的边际贡献急剧下降,反而增加噪声和token成本。摘要长度我控制在原文的百分之十到二十,既能保留要点又不至于太占空间。
Token预算的分配也很关键。假设模型窗口是一万六千token,我会大致这样分配:系统指令五百,历史摘要一千,检索片段四千,当前问题和格式要求五百,留出余量应对波动。这个分配不是死的,要根据任务特点调整。问答类任务检索片段占比高,创作类任务可能历史占比高。
5.4 实操中的观测与迭代
上线后一定要有观测手段。我会记录每次调用的上下文组成、token占用、模型输出质量评分。通过分析这些数据,能发现很多问题,比如某类问题检索总是召回不准,或者某类对话历史摘要总是丢关键信息。有了数据,优化才有方向。
我踩过的一个坑是:早期没有做上下文观测,出了问题只能靠猜,改来改去效率极低。后来加了日志,把每次组装的上下文完整记录下来,排查问题的速度提升了好几倍。这个投入绝对值得。
6. 常见问题与排查技巧
6.1 模型答非所问或忽略关键信息
这是最常见的问题。排查思路是:先看关键信息到底有没有进上下文,再看它进的位置对不对,最后看它有没有被其他内容淹没。我遇到过好几次,关键信息其实进了上下文,但被放在了一大段无关内容的中间,模型根本没注意到。把它挪到开头或结尾,问题就解决了。
还有一种情况是信息进了,但格式不清晰,模型没识别出来。比如把关键参数混在自然语言段落里,不如单独列出来加个标签。格式即信号,这句话在上下文工程里特别成立。
6.2 多轮对话中行为不一致
这通常是历史管理没做好。早期的重要约束被后续内容冲淡了。解决办法是在每轮组装时,把核心约束重新强调一遍,或者把约束提取成结构化状态,每轮都注入。不要指望模型自己记住,要主动帮它记住。
6.3 上下文超长导致报错或质量下降
超长有两个层面:一是硬性超出窗口限制导致报错,二是没超但质量下降。前者靠压缩和截断解决,后者靠筛选和排序解决。我的经验是,永远不要贴着窗口上限用,留出至少百分之二十的余量,给模型留出“思考空间”,质量会更稳。
6.4 检索内容互相矛盾
文档库大了,不同来源的内容可能冲突。这时候不能全塞给模型让它自己判断,而应该在组装前做冲突检测,把矛盾的内容标记出来,或者只保留权威性最高的那份。如果确实需要模型权衡,那就在上下文里明确说明冲突点,引导模型给出有条件的回答。
| 常见问题 | 可能原因 | 排查动作 | 解决方向 |
|---|---|---|---|
| 答非所问 | 关键信息缺失或位置不佳 | 检查上下文组成和顺序 | 调整筛选和排序 |
| 多轮跑偏 | 历史约束被冲淡 | 检查历史摘要和状态注入 | 强化核心约束 |
| 超长报错 | 上下文超出窗口 | 统计token占用 | 压缩、截断、分层 |
| 内容矛盾 | 检索源冲突 | 检查片段来源 | 冲突检测与去重 |
| 格式错乱 | 输出要求不明确 | 检查系统指令 | 明确格式约束 |
6.5 几个独家避坑技巧
第一个技巧:给上下文加“目录”。在长上下文开头用一两句话概括里面有什么,比如“以下包含系统规则、历史摘要、三段参考资料和当前问题”。这样模型能快速建立全局认知,利用率会提升。
第二个技巧:重要信息重复但不冗余。核心约束可以在开头和结尾各出现一次,用不同措辞表达,强化印象。但不要简单复制粘贴,那样显得冗余。
第三个技巧:用分隔符明确边界。不同来源的内容之间用清晰的分隔符隔开,比如三个短横线或者带标签的标题。模型对边界的感知比我们想象的弱,明确分隔能减少串味。
第四个技巧:定期做上下文“体检”。抽一批真实请求,把组装的上下文打印出来人工看一遍,往往能发现自动化指标发现不了的问题。我每隔一段时间就会做一次,每次都有收获。
7. 上下文工程的能力边界与演进方向
7.1 它不能解决什么
上下文工程不是万能的。如果模型本身能力不足,再好的上下文也救不回来。如果检索源本身质量差,筛选再精也巧妇难为无米之炊。如果任务本身定义不清,上下文组装就失去了依据。所以它是在模型能力、数据质量、任务定义这三个前提都基本满足的情况下,用来把效果从“能用”推到“好用”的关键一环。
7.2 值得关注的方向
一个是自动化上下文优化,用模型来辅助决定放什么、怎么放,减少人工规则。另一个是上下文的可观测性与可解释性,让开发者能清楚看到每条信息对最终输出的贡献。还有一个是跨会话的长期记忆管理,让系统在多次交互之间保持连贯。
我个人最看好的方向是上下文组装的标准化和模块化。现在很多团队还在各写各的,如果能形成一套通用的抽象和工具,会大大降低门槛。不过这需要时间,也需要实践沉淀。
7.3 给不同阶段读者的建议
如果你是刚接触,建议先从一个简单场景入手,把“筛选、排序、压缩”这三个动作手动做一遍,感受一下上下文变化对输出的影响。如果你已经在做应用,建议把上下文组装逻辑从业务代码里抽出来,做成独立模块,加上观测。如果你在带团队,建议把上下文工程当成一项基础能力来建设,而不是当成临时调参的活儿。
我在实际项目里最深的体会是:上下文工程做得好不好,短期看效果,长期看维护成本。前期多花点心思设计,后期能省下大量救火的时间。那些看起来“差不多就行”的上下文组装,往往就是线上问题的根源。把每一轮调用都当成一次精心准备的沟通,而不是一次随意的投喂,效果自然不一样。