上下文工程:Agent 时代最被低估的核心手艺
- 提示工程(prompt engineering)管「怎么说」,上下文工程(context engineering)决定「模型此刻能看见什么」——后者才是多轮 Agent 的真约束。
- 账本四类:system 与工具定义固定、吃缓存红利;工具结果与对话历史随循环膨胀,才是预算管理的对象。
- context rot 有实证:18 个模型在长输入下一致退化;口诀——标称窗口打对折起步,推理型任务再打折。
- 急救三招:压缩(摘要重启)、裁剪(清旧工具结果)、外部化(落盘留引用);超大工具结果入口截断、出口蒸馏。
上篇回顾:上一篇给循环立了三条规矩——每步接地、失败有回音、圈圈有边界。但循环每转一圈,都在往同一个容器里倒东西:system 指令、工具定义、工具结果、对话历史。本篇讲这个容器——往里装什么、装多少、什么时候该扔。
一、账本:上下文是最贵的资源
这门手艺最近有了正式的名字。Anthropic 的定义:提示工程是「书写和组织 LLM 指令以获得最优结果」,而上下文工程是「筛选并维护最优 token 集合」的策略集合,是前者的自然演进 [1]。我的转译更直白:提示工程管「怎么说」,上下文工程决定「模型此刻能看见什么」。单轮问答里前者为主;到了多轮循环的 Agent 里,后者才是真约束。
为什么说是「最贵」?Anthropic 的原话:上下文是「关键但有限的资源」,LLM 有一笔「注意力预算」——transformer 对 n 个 token 要建立 n² 个两两关系,越长注意力越摊薄,所以上下文「必须被当作边际收益递减的有限资源」,目标是「找到能最大化期望结果的最小高信号 token 集合」[1]。翻译成工程师的话:上下文不是仓库,是工资单——每个月的额度有限,乱花就要在其他地方挨饿。
管好工资单,先记账。Agent 的上下文由四类构成,而且它们的增长曲线完全不同:
| 构成 | 增长曲线 | 管理策略 |
|---|---|---|
| system 指令 + 工具定义 | 固定,但每轮都重复发送 | 写精简;保持前缀稳定,吃缓存红利 |
| 工具结果 | 每次调用可能爆炸式增长 | 入口截断/分页,过程定期裁剪 |
| 对话历史 | 随循环线性增长 | 压缩、摘要 |
| 用户输入 | 一次性 | 保持聚焦,别一次倒进全部资料 |
还有一笔隐藏的经济账值得单列:Anthropic 与业界的实践都指出,循环的各步共享巨大的公共前缀,缓存命中的输入 token 价格约是未命中的十分之一;所以要让前缀保持稳定——不要塞随机时间戳、不要中途改写历史——并且只追加、不修改(Manus 团队的经验总结,此处为转述)[2]。这解释了上表第一行的策略:system 与工具定义是全循环共享的前缀,把它们写稳,是整条流水线里性价比最高的一次优化。
预算具体怎么分?给一个可抄的起点。假设有效预算 30K token:system 加工具定义控制在 4K 以内(它们每轮都发,是最贵的「固定成本」);工具结果给 8K,靠截断与分页守住;对话历史 10K,超了就裁就压;剩下留作余量与输出。这只是个量级示意,重点是养成先分配、再消费的习惯——像做成本表一样做上下文预算,而不是等报错了再回头翻 token 账单。另外别忘了第 2 篇的分法:上下文窗口不是记忆,它只是模型「现在」能看见的东西;窗口外的一切,对模型来说从未存在过。
二、context rot:窗口够大 ≠ 用得上
「窗口都 1M token 了,还省什么?」——这是本篇要粉碎的第一个幻觉,学界给了它一个名字:上下文腐化(context rot)。Chroma 的实证研究测了 18 个模型(含 GPT-4.1、Claude 4、Gemini 2.5 一类的一线模型),共 194,480 次调用,结论:即便是 SOTA 模型,性能也随输入长度显著波动,连简单任务也不例外;在 LongMemEval 基准上,全量提示(约 113K token)对比聚焦提示(约 300 token),所有模型一致退化 [3]。
更扎心的是,大家熟悉的「大海捞针」测试(Needle in a Haystack, NIAH)高估了长上下文能力:它本质是字面匹配的简单检索。Chroma 发现,只要在 haystack 里放 1 个干扰项就会造成伤害,干扰项叠加还会放大退化;甚至「结构越连贯的干扰材料,损害越稳定」[3]。把镜头对准 Agent,这个结论几乎是量身定做的警告:Agent 恰恰是长上下文加多干扰的重灾区——每一轮的工具结果都是新塞进来的「干草」,彼此相似、结构连贯、越积越多。你以为模型在「参考全部历史」,它可能正被历史淹没。
两个更严苛的旁证:RULER(NVIDIA)提出「有效上下文长度」概念,宣称 32K 以上窗口的模型中,只有约一半能在 32K 长度保持满意表现;广为引用的案例之一是宣称 128K 的旗舰模型,实测有效约 64K [4];NoLiMa(Adobe)把字面重叠也去掉、只考联想匹配,1K 以内各模型普遍 90% 以上,到 32K 时多数跌破自身短基线的一半 [5]。
三、System Prompt 写作清单
账本里唯一「写完就不动」的部分,值得单独讲怎么写。Anthropic 的建议归纳成一份清单 [1]:
- 简单、直接、处于正确的高度(right altitude):既不碎到硬编码 if-else(「如果用户提到 X 就说 Y」),也不空到只有大方向;停在「原则 + 少量关键规则」的高度;
- 追求最小信息集,但 minimal ≠ short:目标是「完整刻画预期行为的最小信息集」——该长的长,该短的自然短;
- 每条指令过一道闸:删掉它会不会出错?不会就删。system 是每轮都要发的固定开销,每一行都该挣自己的饭钱;
- 方法上:先测最小提示,按真实失败模式逐条增补——而不是凭空写一篇散文再试图删;
- 写清输出契约与停止规则:何时算完成、何时求助、何时拒绝——这正是第 3 篇「停止条件」在上下文侧的落点;
- 顺手做工具集卫生:臃肿或重叠的工具集会造成选择歧义,Anthropic 的试金石是「如果人类工程师都无法断言该用哪个工具,也不能指望 Agent 做得更好」[1]。
这份清单最反直觉的是第二条:我们总被教导「提示要短」,但对 Agent 来说,一条必要的边界规则胜过十句客套。该省的是废话,不是信息。举个例子,「请认真仔细地完成用户的任务」是典型的空话,删掉零损失;而「任务无法完成时,说明卡在哪、已尝试什么、需要什么信息,不要编造结果」是真规则——它恰好覆盖了一类真实失败模式(编造收场),删掉它,你会在评测里看见这个失败出现。写完 system 之后,把每一条都做一遍这个「删除测试」,清单就自动瘦身了。
四、急救三招:压缩、裁剪、外部化
循环跑久了,上下文总会逼近上限。Anthropic 给了几种急救手段,按实现成本可排成三级 [1]:
- 裁剪(clearing):把历史深处的旧工具结果替换成占位符。理由很硬:「工具调用发生在历史深处之后,Agent 为什么还要再看一遍原始结果?」这是最安全、最轻量的压缩形式——结构不动,只删冗余;
- 压缩(compaction):把接近上限的对话摘要化,以「摘要 + 最近几轮」重启窗口。Claude Code 的实践是保留架构决策、bug 与实现细节,丢弃冗余工具输出;「compaction 的艺术在于取舍」,先保召回、再提精度;
- 外部化(externalizing):把状态持久化到窗口之外,需要时再拉回——经典案例是打宝可梦的 Claude 把步数与策略写成笔记 [1];Manus 的表述更彻底,「把文件系统当作最终的、无限大的上下文」(转述)[2]。工程侧的现成轮子就是第 2 篇提过的 checkpointer [6]。
三招怎么选?Anthropic 的建议按任务形态分:有清晰里程碑的迭代任务选外部化,需要并行探索的选子 Agent 隔离,对话式的来回选压缩 [1]。另外再抄三条 Manus 的实战经验(转述)[2]:用掩码隐藏工具而不是动态删工具(删历史会打爆缓存);让模型定期把目标复述到上下文尾部,对抗「迷失在中间」(lost in the middle);把报错留在上下文里让模型自适应——最后这条,第 3 篇的错误回填已经替你做了一半。
五、动手:给循环装上预算与裁剪
超大工具结果是预算的头号杀手,先看一个真实机制:Claude Code 默认把工具返回截断到 25,000 token[7],超限即报错并要求用 limit 分页缩减——社区里不乏单次返回三万多 token 被拦下的案例。这招是「入口截断」。另一路是「出口蒸馏」:用子 Agent 隔离,让它烧几万 token 去翻,只把 1,000-2,000 token 的蒸馏摘要带回主循环 [1]。原则就一句:别让一次搜索吃掉一整天的注意力预算。
接下来把「裁剪」和「压缩」写成代码。第一段是零成本方案:token 粗估 + 旧工具结果裁剪,任何循环都能直接加:
import json
def est_tokens(text: str) -> int:
"""粗估 token:中文约 1 字 1 token,英文约 4 字符 1 token。够预算判断用。"""
cn = sum(1 for ch in text if "\u4e00" <= ch <= "\u9fff")
return cn + (len(text) - cn) // 4
def prune(messages: list, keep_recent: int = 6) -> list:
"""裁剪:旧工具结果替换为占位符;system、开头目标与最近几轮原样保留。"""
cut = max(2, len(messages) - keep_recent) # 守卫:keep_recent 过大时不重叠
head, tail = messages[:2], messages[cut:]
mid = messages[2:cut]
trimmed = [{**m, "content": "(旧工具结果已裁剪;需要时请重新调用工具)"}
if m.get("role") == "tool" else m
for m in mid]
return head + trimmed + tail
# 在循环里每轮检查一次(沿用第 3 篇的 react 循环):
if est_tokens(json.dumps(messages, ensure_ascii=False)) > 30_000:
messages = prune(messages)
print("[context] 已裁剪旧工具结果") # 轨迹里留痕,排障时看得见
注意两点设计:开头两条消息(system + 首个 user)永远保留——那是目标的锚,裁掉它 Agent 就会漂(第 3 篇说的「计划漂移」就是这么来的);裁剪只动 tool 消息的内容、不动消息结构——否则 API 直接报错。触发时机也有讲究:别等到 90% 满才动手,建议在有效窗口的 50-60% 就开始裁——留出余量给「正在进行的这一轮」,否则最容易出现在最后一步的翻车,恰恰发生在你最需要的时刻。当裁剪也救不了时,上第二段:把旧历史压缩成摘要重启窗口:
def compact(messages: list, client, keep_recent: int = 4) -> list:
"""压缩:旧历史折叠成摘要,system 与最近几轮原样保留(有损,慎用阈值)。"""
cut = max(1, len(messages) - keep_recent)
while cut < len(messages) and messages[cut].get("role") == "tool":
cut += 1 # 守卫:切点不能落在孤儿 tool 消息上
head, tail = messages[:1], messages[cut:]
old = messages[1:cut]
summary = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "把以下对话与工具结果压缩成要点清单:"
"保留目标、关键决定、已完成步骤、遗留问题;丢弃过程性输出。"},
{"role": "user", "content": json.dumps(old, ensure_ascii=False)[:20000]}, # 超 2 万字符先粗截:摘要输入有损
]).choices[0].message.content
return head + [{"role": "user", "content": f"[此前进展摘要]\n{summary}"}] + tail
# 用法:messages = compact(messages, client)
# 预期输出:历史以「[此前进展摘要]」开头的新 user 消息重启,总 token 显著下降
记住压缩的代价:它是有损的。关键决策和重要约束,别指望摘要一定留住——同步写到一个外部文件(外部化),比事后懊悔可靠。两段代码合起来,就是一个 Agent 上下文管理的最小闭环:入口截断(工具层)、过程裁剪(prune)、极限压缩(compact)、关键信息外部化(落盘)。
小结
- 上下文是边际收益递减的有限资源;管「模型此刻能看见什么」,比管「怎么说」更能决定 Agent 的成败;
- 账本四类里,system 与工具定义求稳(吃缓存),工具结果与历史求省(截断/裁剪/压缩);前缀稳定 + 只追加,是成本的第一杠杆;
- context rot 是实测现象不是理论担忧:标称窗口打对折起步,推理任务再打折;
- System Prompt 每条指令要「删掉会出错」;急救三招按成本排:裁剪 → 压缩 → 外部化,关键信息永远同步落盘。
下一篇预告:四大件齐了、心脏会跳了、血液也管住了——下一篇《工具调用实战》把它们拧成一台真机器:JSON Schema 的细节、并行工具调用、人工确认门控,从零写一个真能干活的 Agent(Python)。
参考来源
- Effective Context Engineering for AI Agents, Anthropic, 2025,anthropic.com
- Context Engineering for AI Agents: Lessons from Building Manus, Manus, 2025,manus.im(要点为转述)
- Context Rot: How Increasing Input Tokens Impacts LLM Performance, Chroma Research, 2025,trychroma.com
- RULER: What's the Real Context Size of Your Long-Context Language Models?, NVIDIA, 2024,arxiv.org
- NoLiMa: Long-Context Evaluation Beyond Literal Matching, Adobe Research, 2025,arxiv.org
- LangGraph Persistence 官方文档, LangChain, 2025,docs.langchain.com
- Writing Tools for Agents, Anthropic Engineering, 2025,anthropic.com