模型下一次采样时,窗口里装的不只是你写的那句提示。系统约定、检索到的文档、工具返回、到这一步为止的记录,都在同一串 token 里。2025 年夏天,这个活有了比较固定的名字:上下文工程(Context Engineering)。
核心是给任务备齐上下文,让模型有可能做对。工业级应用里,是把窗口刚好填对:太少做不了,太多或形态不对,费用上去,表现也会掉。
它在说什么
模型一次推理能看到的,就是当时送进去的那串 token。系统提示、用户这句话、聊天记录、检索到的文档、工具定义、工具返回、结构化输出约束,都算。再拆细一点,还包括任务说明、少样本(few-shot)、检索增强生成(RAG)、多模态数据、状态和历史、压缩后的摘要。
上下文工程是一套动态系统:在对的时间,把对的信息和工具,用对的格式,交给模型去完成当前这一步。窗口不是越大越好用。每多一个 token 都在分同一笔注意力预算(attention budget)。
它管的是这一次推理能看到的全部 token,而且每轮调用前都要重新挑。它不是 LLM 应用的全部:问题怎么拆、调用哪类模型、校验和界面、安全、评测,都不在这个词里面。
和提示词工程(Prompt Engineering)差在哪
提示词工程管怎么写、怎么组织指令,尤其是系统提示。上下文工程管推理过程中整份状态:指令之外,还有工具、模型上下文协议(MCP,会把一批工具说明一次性塞进窗口)、外部数据、消息历史,以及每一轮要留下什么、丢掉什么。
早期很多任务是一次性分类或生成,写好系统提示就够了。Agent 会连着跑很多轮,每轮都在往窗口里堆新东西,就必须每一步重新挑。提示还在,只是变成窗口里的一块。窗口变脏了,先看是指令不清楚,还是这一轮不该进窗口的东西进来了。
窗口变大解决不了什么
2023 年有过一个很干净的实验:多文档问答里,答案一定在某一篇里,只改这篇的位置。模型在开头和结尾表现最好,放在中间明显变差,曲线像 U。有的设置下,答案放中间,还不如不给文档(闭卷)准。更简单的键值检索里,一部分模型连「从中间取出配对的值」都会掉。这篇就是《迷失在中间》(Lost in the Middle)。
这件事和 预测下一个词 对得上。Transformer 里每个 token 都要和其它 token 两两算相关度,长度是 时关系是 。序列变长,这些关系会变稀;训练数据里短序列也更常见。所以不是到了某个长度突然不能用,而是越长越不准。
2025 年 7 月的上下文腐烂(Context Rot)报告把这件事又测了一遍,评了 18 个当时的模型。任务故意保持简单,只改输入长度。结论是:模型并不均匀地使用上下文,输入越长,越不可靠。大海捞针(Needle in a Haystack)那种「在长文里找回一句原话」很多模型接近满分,所以会给人「长上下文已经解决了」的错觉。把针改成语义相关、再加干扰项,掉点就出来了。
等更大窗口,解决不了污染和相关性。长任务还是得管窗口里留下什么。
一次推理里会装进什么
一次调用里,窗口通常由这些拼起来:
- 指令:系统提示、规则、少样本、工具说明。
- 用户当前这句话。
- 短时状态:到这一步为止的对话和工具轨迹。
- 长时记忆:跨会话存下来的偏好、项目摘要、事实。
- 检索到的外部材料。
- 工具定义,以及上一轮工具返回的结果。
- 输出格式:JSON Schema、枚举、必填字段。
可以把模型比成 CPU,窗口比成内存。磁盘不会整盘装进内存,这一步该进窗口的才进。
原则:少而有用
尽量用一小撮高信号 token,让目标结果更可能出现。少,不等于短。该交代的行为、约束、例子还是要给够。可以先用最小提示加最强模型试任务,再按失败模式往上加,而不是一上来写满。
系统提示的高度要合适。一端是把 if-else 写进提示,又脆又难维护;另一端是太空,假装模型和你有共同背景。可用的区间是:具体到能指导行为,又留出启发式,而不是写死每条路径。
工具是模型和外部世界的契约,返回值要省 token,功能不要互相重叠。人说不清这一步该用哪个工具,模型更说不清。
少样本仍然有用,但不要把边角案例列成清单。挑一组多样、能代表预期行为的例子。例子太多、太像,会反过来把模型带进重复动作。
写:把东西存到窗外
写,是先存到窗口外面,需要时再读回来。
一轮任务里可以用草稿:计划写进 Memory 或 NOTES.md,窗口超限被截断时计划还在。Claude Code 的待办、不断改写的 todo.md,都是同一类。把总目标反复写到上下文末尾,等于把计划推到最近被看到的位置,减轻迷失在中间的问题。
跨会话的记忆是另一层。产品会从交互里抽出长期记忆。选错会很烦:画一张不该带位置的图,却把记忆里的住址塞进去。记忆一旦乱选,用户会觉得窗口不再属于自己。
选:用的时候再装进来
选,是决定这一步读什么进来。
一种是推理前检索:嵌入搜索、规则文件、总是装进窗口的 CLAUDE.md。代码 Agent 里这仍然常见。建索引不等于检索。代码库一大,单靠嵌入不可靠,得叠 grep、知识图谱、重排。
另一种是即时取(just-in-time):只拿轻量标识(路径、查询、链接),用工具在运行时再装。对大数据库可以写查询,用 head / tail 看,不必把整表塞进窗口。代价是比预取慢,工具和启发式没设计好时,Agent 会空转、走死路。
常见的混合是:CLAUDE.md 一开始就进窗口,具体文件靠 glob、grep 现查。
智能体技能(Agent Skills)把「先露目录、再翻章节」做成了产品机制。启动时只把每个 skill 的 name 和 description 放进系统提示;相关了再读整个 SKILL.md;更细的说明和脚本,用到再打开。脚本可以在窗外跑,只把输出送回来。这样 skill 里能捆多少材料,几乎不受窗口限制。
工具太多也是选择问题。说明重叠,模型会选错。运行中动态装卸工具通常不划算:工具定义在窗口前部,一改就作废后面的键值缓存(KV-cache);历史里还会提着已经卸掉的工具。更稳的是定义不动,只在解码时屏蔽某些工具名。
压:压缩但不丢到找不回来
压,是窗口快满或已经太吵时,留下还能继续干活的部分。
压缩(compaction)就是对话接近上限时做摘要,用摘要开一个新窗口。留下架构决定、未修的 bug、实现细节,丢掉重复的工具输出,再带上最近打开的几个文件。先保证关键信息不丢,再去掉多余。最轻的一档是清掉很早以前的工具结果。
再严一点:压缩必须可还原。网页正文可以从窗口里拿掉,但 URL 要留着;文档可以不在窗口里,但沙箱路径要在。文件系统几乎是无限的上下文。不可逆压缩有风险,因为你不知道十步之后哪条观察会变成关键。
和压缩绑在一起的是键值缓存。Agent 的输入往往远长于输出,有的产品平均大约 100
。前缀不变时,缓存能把延迟和费用打下来,缓存输入可以到未缓存的十分之一。要做到高命中:- 前缀稳定。系统提示开头放精确到秒的时间戳,从那个 token 起缓存全废。
- 只追加,不改已经发生的动作和观察。JSON 序列化键序不稳,也会悄悄打穿缓存。
- 需要时显式打缓存断点(cache breakpoint),至少覆盖系统提示末尾。
隔:不要所有事挤在同一扇窗
隔,是把上下文拆开,避免一件事把整扇窗填满。
子 Agent 是最常见的拆法。子 Agent 自己跑很长的检索,只把 1000 到 2000 token 的摘要交回主 Agent。复杂研究任务上,这样往往比单线程把所有搜索轨迹都留在主窗口里更干净。
反过来,并行写很容易出事。不是「多个模型」本身不行,是上下文传不干净。要共享的是完整轨迹,不是只转发一句任务。动作还带着没写明的决定,决定冲突,结果就坏。
把「做个 Flappy Bird 克隆」拆成两个子任务:一个去做背景,做成了马里奥管道;一个去做鸟,做成了不像游戏资源的东西。把原任务再复制一份给子 Agent 也不够,因为真实系统是多轮的,中间还做过工具调用。就算都看到原任务,两边仍可能做成两套画风,因为彼此看不见对方已经做了哪些隐含选择。
比较稳的折中是:能并行读,不要并行写互相看不见的决定。调查可以丢给子任务,改代码留在能看见既有决定的那条线程。Claude Code 早期即使生子任务,也多半只让它回答问题,不并行写。
重对象还可以留在环境里。沙箱里跑代码,图片、音频先赋给变量,需要时再把返回值送回模型。运行时状态对象也可以:schema 里只有 messages 每轮给模型看,别的字段按需暴露。
动作会带上没写明的决定
不是只有「任务描述」算上下文。一次改文件、一次选库、一次定接口,都在做没有写进提示的决定。后面的步骤如果看不见这些决定,就会在另一套假设上继续干。
2024 年不少编码 Agent 用「大模型写修改说明、小模型按说明重写整个文件」。说明稍有含糊,小模型就会改错。决定和执行拆开、上下文又传不完整,就会这样。后来编辑决定和落盘更常由同一个模型一次做完。
设计拆分时可以先问:这一步做完,有哪些决定被写进了世界,下一步看不看得到。
错误和少样本
出错当时最好让模型看见失败动作和对应的观察。抹掉失败等于抹掉证据,它没法改先验。错误恢复是 Agent 能不能接着干的标志,公开基准很少测这个。
修好以后,原始堆栈不一定还要占着位置,但「试过什么、为什么不行」最好以更密的形式留下。
少样本也有两面。例子要多样、典型。审 20 份简历这种重复任务,窗口里如果已经是同一套动作-观察,模型会跟着重复,甚至幻觉。序列化和措辞上加一点变化,可以打断模式。别把自己用少样本套进沟里。
两个例子
回一封约会议的邮件
邮件只有一句:「明天方便快速同步一下吗?」
窗口里只有这一句时,模型只能回「明天可以,你想几点」,听起来像客服。
调用前先把这些装进去:日历(明天已经排满)、和对方的历史邮件(语气)、联系人(这是重要伙伴)、以及发邀请 / 回邮件的工具。模型才写得出:「明天一天会,周四上午可以的话我发了邀请。」
差别不在模型突然变聪明,在这一步该看见的东西有没有进窗口。
一次部署
窗口格式可以自己定,不必死守标准的 role: tool 消息数组。按事件拼起来,密度更高,也更好控制留下什么:
下一步仍然是同一句:到目前为止发生了这些,下一步做什么。工具失败留在窗口里,模型才能改去查工作流状态,而不是再部署一次。
编码 Agent 也可以按同一套清单想:系统约定(CLAUDE.md / 规则)先在;相关文件用搜索现取,不要把整个仓库嵌入进去;工具输出太大就写到文件,窗口里留路径;窗口到阈值就摘要,但保留未完成的决定和最近打开的文件;调查类工作可以丢给子 Agent,改代码尽量留在能看见既有决定的那条线程。
常见错误
- 把长窗口当免费午餐。大海捞针满分不等于中间的内容用得好。先假设越长越吵,再决定装什么。
- 系统提示里放会变的前缀,尤其是精确时间。缓存从第一个不同的 token 作废。
- 运行中增删工具定义。前缀变了,历史里还会提到已经不存在的工具。
- 把所有边角写成规则清单,而不是少数典型例子。
- 多 Agent 并行写,又不共享完整轨迹和已经做过的决定。
- 不可逆压缩。十步之后要用的观察,只留「大概摘要」会找不回来。能还原则留指针。
- 工具又多又重叠。人分不清该用哪个,就先砍到能分清。
- 重复任务里上下文太整齐,模型开始复读自己。
- 失败一出现就从轨迹里抹掉,模型没有改行为的证据。
- 长期记忆无差别注入。选错一次,用户会觉得窗口被抢走了。
参考资料
- Tobi Lütke,关于上下文工程的帖子,2025-06-19。
- Andrej Karpathy,关于上下文工程的帖子,2025-06-25。
- Simon Willison,《上下文工程》(Context engineering),2025-06-27。
- Phil Schmid,《AI 里的新技能不是提示,是上下文工程》,2025-06-30。
- Lance Martin,《面向智能体的上下文工程》(Context Engineering for Agents),2025-06-23。
- Walden Yan / Cognition,《不要做多 Agent》(Don’t Build Multi-Agents),2025-06-12。
- Peak Ji / Manus,《从做 Manus 学到的》(Context Engineering for AI Agents: Lessons from Building Manus),2025-07-18。
- Liu et al.,《迷失在中间》(Lost in the Middle),2023。
- Hong, Troynikov, Huber / Chroma,《上下文腐烂》(Context Rot),2025-07。
- Anthropic,《面向 AI 智能体的有效上下文工程》(Effective context engineering for AI agents),2025-09-29。
- Anthropic,《智能体技能》(Equipping agents for the real world with Agent Skills),2025-10-16。
- Anthropic,《我们如何搭建多 Agent 研究系统》(How we built our multi-agent research system),2025-06-13。
- Dex Horthy / 12-Factor Agents,原则 3:自己掌控上下文窗口(Own your context window)。