Context 管理:六种上下文工程手段,各自解决哪一层问题
Context 管理不是把 prompt 写短,而是在一个固定大小的窗口里做预算分配。窗口里同时住着系统提示、工具定义、对话历史、工具返回值和当前任务,每一类都在涨,而模型的注意力不会因为窗口变大而变好。
过去一年多,做 agent 的人基本收敛到六种手段:长对话摘要、工具结果卸载到文件、用 Skills 做渐进披露、用工具检索应对工具爆炸、用 subagent 做上下文隔离、把 Bash 当主要工具。它们不是六个可以随便挑的选项,而是作用在三个不同时间尺度上的机制——进入窗口前、窗口内、窗口快满时。单独用任何一个都会碰到天花板,组合起来才是今天主流 agent 的默认形态。
下面逐条拆机制、代价和失效边界,最后讲它们怎么拼在一起。
先算清账:一个 agent 的窗口里装了什么
以一个典型的编码 agent 为例,200k 的窗口大致被五样东西瓜分:
- 系统提示:身份、行为规则、输出格式,通常几千 token,稳定不变
- 工具定义:每个工具的 JSON Schema 加描述,单个 200–800 token
- 对话历史:每一轮用户消息 + 模型回复,只增不减
- 工具返回值:一次
grep可能几千行,一次网页抓取可能几万 token - 当前任务:正在处理的那一段代码或那个文件
问题在于后三项的增长速度和前两项完全不是一个量级。系统提示是常数,工具定义是常数,而工具返回值是脉冲式的——一次读大文件就能吃掉半个窗口。Claude Code 早期最典型的翻车场景就是这个:agent 信心满满地 cat 了一个 3000 行的日志,然后整轮对话开始胡言乱语。
MERMAID_BLOCK_0
这里有个容易被忽略的事实:窗口没满,性能也会下降。Chroma 在 2025 年做的 Context Rot 研究测了 18 个主流模型,在 needle-in-a-haystack、长文档问答、重复计数这类任务上,输入从几千 token 涨到几十万 token 的过程中,准确率普遍下滑,而且不是阶梯式下滑,是连续衰减。也就是说「还没到上限,所以没问题」是个错误假设。上下文管理的目标不是"别撑爆",而是"别让无关内容稀释注意力"。
于是成本有三个维度,不只是钱:
- 钱:每个 token 都进 prompt,按输入价计费;缓存命中能打下来一大截,但缓存的是前缀,任何前缀变化都会失效
- 注意力:窗口里 40% 是过期的工具输出,模型就得在这些噪声里找信号
- 延迟:首 token 时间随输入长度线性上涨,长上下文还会拖慢生成
记住这三个维度,后面每一种手段都是在拿其中一项换另外两项。
手段一:摘要,把历史换成一个状态包
最古老也最通用的做法:对话太长时,让模型把到目前为止的对话压缩成一段结构化摘要,然后丢掉原始消息,用「摘要 + 最近几轮」重建一个新窗口。Claude Code 里叫 compact,API 层叫 context editing,社区里有人叫 rolling summary。
机制上它有一个关键设计点:摘要不要写成叙事,要写成状态。叙事摘要("用户先让我改 A,然后我发现 B 有问题……")几乎没用,因为它保留的是过程。有用的是状态包:
Goal: 把 auth 模块从 session 迁移到 JWT
Constraints: 不能改 public API;必须保持 refresh token 语义
Files touched:
- src/auth/session.ts (deleted)
- src/auth/jwt.ts (new, 210 lines)
- src/middleware/auth.ts (modified, line 44-88)
Decisions: 用 jose 而不是 jsonwebtoken(因为 edge runtime)
Failed attempts: 直接替换 express-session 导致 3 个测试超时,已回滚
Next: 修 test/auth.refresh.spec.ts 的过期断言
这七行能替代两万 token 的对话,而且信息密度更高。Anthropic 在早期的研究型 agent 上验证过这一点:他们跑过一个需要约 300 万 token 的检索任务,靠的就是每轮结束后把结果摘要进下一轮,否则单窗口根本装不下。
在 API 层,这件事已经有原生支持。Anthropic 的 context editing 可以按 token 阈值自动清理旧的工具结果:
{
"context_management": {
"edits": [
{
"type": "clear_tool_uses_20250919",
"trigger": { "type": "input_tokens", "value": 100000 },
"keep": { "type": "tool_uses", "value": 3 },
"clear_at_least": { "type": "input_tokens", "value": 20000 }
}
]
}
}
这段配置的语义是:当输入超过 10 万 token,只保留最近 3 次工具调用,至少清掉 2 万 token。注意 clear_at_least 这个参数——它防的是「清了一次没清动,每轮都触发一次清理」的抖动。
为什么有效:它丢掉的是"怎么走到这里"的路径,保留的是"现在在哪、下一步去哪"的状态。对绝大多数 agent 任务来说,前者只在调试时有用。
代价和失效边界:
- 摘要本身是一次模型调用,长对话摘要要几秒到十几秒,还可能要几千 token 的输出
- 摘要会丢细节,而且是不可逆的。模型之后说"我记得之前试过 X 不行",但说不出为什么不行的具体报错,就容易再试一次
- 递归摘要会漂移。摘要的摘要的摘要,几轮之后约束条件会消失,这是长任务最常见的失控原因
- 任务本身依赖精确细节时不能用:逐行 diff、法律条款比对、需要引用原文的写作,摘要一次就废
所以摘要的定位应该是兜底机制,不是第一道防线。真要靠它跑长任务,就得把摘要 prompt 当核心代码来维护,明确列出"必须保留"的字段清单。
手段二:工具结果落盘,窗口里只留指针
比摘要更细粒度的一招:单个工具结果太大时,不要让它整块进上下文,而是写到文件里,窗口里只放「路径 + 行数 + 前 N 行 + 一句说明」。
MAX_INLINE = 2000 # tokens, roughly
PREVIEW_LINES = 20
def offload(result: str, name: str) -> str:
if count_tokens(result) <= MAX_INLINE:
return result
path = f"/tmp/tool-results/{name}-{uuid4().hex[:8]}.log"
write_file(path, result)
lines = result.count("\n")
preview = "\n".join(result.splitlines()[:PREVIEW_LINES])
return (
f"[offloaded] {lines} lines, {len(result)} bytes -> {path}\n"
f"--- first {PREVIEW_LINES} lines ---\n{preview}\n"
f"--- end preview ---\n"
f"Use grep/head on this file if you need more."
)
关键在于返回值的形状。很多人只返回一句 "output saved to /tmp/x.log",然后模型就忘了这个文件存在。带上行数、字节数、前 20 行预览,模型才有足够信息判断"要不要去读"。
为什么有效:工具返回值的角色是"中间产物",模型对它做的操作无非是三种——判断有没有目标字符串、提取几个字段、看看有没有报错。这三种操作都不需要全文进上下文。落盘之后,agent 可以用一次 grep 拿到精确答案,付出的成本是几十 token 而不是几万。
代价:
- 多一次往返。模型看到 preview 决定去读文件,这是额外的 turn,延迟增加
- 文件需要生命周期管理。长会话会攒几百个临时文件,得有清理策略,否则磁盘和检索都会变脏
- 模型可能"忘记"去看。这个失败模式很常见,解法是在系统提示里明确写"遇到
[offloaded]标记时必须用 grep 确认内容,不要凭预览下结论"
有个反直觉的细节:返回结构化预览比返回自然语言摘要更可靠。摘要由模型生成,会引入二次幻觉;而"前 20 行原文 + 总行数"是确定性的,模型自己能从原文里提取信息。所以落盘策略的核心是"给指针和样本",不是"给总结"。
手段三:Skills 渐进披露,把知识做成按需加载的模块
Skills 解决的是另一个方向的膨胀:不是对话太长,而是领域知识太多。一个通用 agent 可能要知道怎么写前端组件、怎么配 K8s、怎么用公司内部的发布系统,全塞进系统提示就是几万 token 的常数开销,而且大部分时候用不上。
渐进披露分成三层:
第一层,常驻的元数据。只有 name 和 description,加起来一百来个 token,永远在系统提示里:
---
name: cnblogs-publisher
description: Publish Markdown articles to CNBlogs using the MetaWeblog API.
Use when the user asks to publish, post, or push articles to CNBlogs.
---
第二层,触发时加载的正文。模型判断这个 skill 相关,才去读 SKILL.md 的正文——通常是几百到几千 token 的操作步骤、注意事项、判断标准。
第三层,执行中按需读取的附件。references/ 下的详细文档、scripts/ 下的可执行脚本、assets/ 下的模板,正文里只写"需要 X 的时候读 references/x.md",模型真的需要时才打开。
为什么有效:它把"知识"变成了一个检索系统,索引常驻、正文懒加载。系统提示的常数开销从几万 token 降到几百,而且窗口里的内容永远是当前任务相关的,注意力不被稀释。
这里面有一个设计上的必然结果:description 就是检索键。它写得好不好,直接决定 skill 会不会被触发。写成"帮助处理博客相关事务"基本等于不存在;写成"Publish Markdown articles to CNBlogs using MetaWeblog API. Use when the user asks to publish to 博客园"才有明确的使用场景和触发词。我见过最多的 skill 失败案例,不是正文写得差,是 description 写得太抽象,模型根本意识不到该用它。
代价和失效边界:
- 多一层文件读取,一次 skill 加载多几百毫秒
- 模型可能只读了正文就动手,跳过了 references 里的关键约束
- 有些知识不能懒加载。安全红线、输出格式、绝对不能做的事,必须放在系统提示里常驻——因为"没被触发"等于"不存在"
- 层级设计不好会变成俄罗斯套娃:正文说"详见 references",references 说"详见另一个文件",模型读到第三层就放弃了。实践上建议最多三层,且每层都要能独立完成任务
手段四:Tool search,工具多到装不下时的检索层
Skills 管知识,工具定义管能力,但两者会撞上同一个问题:数量。一个接了三四个 MCP server 的 agent,工具数轻松过百。按每个工具平均 300 token 算,光工具定义就是三万 token,还没开始干活。
Tool search 的思路和 skills 完全一致:默认只加载工具的名字,需要时再拉取完整定义。
{
"tools": [
{ "type": "tool_search_tool_regex_20251119", "name": "tool_search" },
{
"name": "create_issue",
"description": "Create a GitHub issue",
"input_schema": { "...": "..." },
"defer_loading": true
},
{
"name": "query_metrics",
"description": "Query production metrics by service",
"input_schema": { "...": "..." },
"defer_loading": true
}
]
}
带 defer_loading: true 的工具,完整 schema 不进首轮 prompt,只保留名字和一行描述。模型调用 tool_search,用正则或 BM25 检索工具名与描述,命中的工具才被展开成完整定义。Anthropic 的 API 提供了 regex 和 BM25 两个版本,前者适合命名规范统一的内部工具(jira_create_issue、jira_search_issues),后者适合名字不规律的第三方工具。
为什么有效:找工具本质上是检索问题。模型的推理能力再强,也没法在 100 个 schema 里"想"出哪个工具能查生产指标——但一次关键词匹配可以。
代价:
- 多一轮往返。先搜、再调,等于每个新任务多一次模型调用
- 检索失败等于工具不存在。工具命名混乱时 BM25 的命中率会掉得很厉害,比如把"查日志"的工具叫
fetch_events_v2 - 模型不知道有这个东西可用。得在系统提示里明确告诉它"工具很多,需要时先搜索",否则它可能直接用 bash 硬干
失效边界:工具总数在 20 个以下时,检索的往返成本大于省下的 token,不如全量加载。工具数超过几百个时,一次检索又会返回太多结果,需要分页或二次过滤。甜区大概在 30 到 200 之间。
手段五:subagent,用进程隔离换上下文隔离
前面四种手段都在"窗口内"做文章,subagent 换了个思路:让中间过程根本不进主窗口。
机制很简单:主 agent 派一个子 agent 去干一件封闭的活,子 agent 有自己的上下文窗口、自己的工具集、自己的循环,干完只回传一段结论。
主 agent 的上下文:
"找一个能处理 10 万 QPS 的 Rust 限流库,比较三个候选的许可证和维护活跃度"
子 agent 的上下文(独立 200k 窗口):
读了 14 个仓库的 README
跑了 6 次 GitHub API
读了 3 个 benchmark 文件
→ 回传 400 字结论
主 agent 实际看到的:
400 字 + 一句"需要细节我再展开"
为什么有效:探索型任务的输入输出比极度不对称。读 14 个仓库可能消耗 8 万 token,但结论只有 400 字。如果这些过程都进主窗口,主 agent 在第 15 个仓库时就已经被噪声淹没了。子 agent 相当于一个有损压缩器,而且压缩损失由子 agent 自己承担,主 agent 拿到的是已经提炼过的信号。
代价,而且不小:
- 子 agent 看不到主对话。你派它去"修一下刚才那个失败的测试",它不知道"刚才那个"是哪个。briefing 的质量直接决定成败,这是 subagent 最常见的失败点
- token 总量上升。并行 5 个子 agent,每个都要带一份系统提示和工具定义,总消耗可能是单线程的 3–5 倍。省的是主窗口的注意力,不是钱
- 不能中途纠偏。子 agent 跑偏了你只能等它跑完,或者杀掉重来
- 汇总会丢信息。子 agent 觉得不重要的细节,可能恰恰是主 agent 需要的
失效边界:任务之间需要强共享状态时别用(比如连续改同一个文件的不同部分);需要人工实时审批的操作也别用,因为子 agent 弹权限请求时你未必在看。它最适合的场景是只读的、可并行的、结论形态明确的调研。
手段六:Bash 作为主工具,让过滤发生在进入窗口之前
前五种手段都是在结果产生之后做处理,bash 的思路更彻底:在结果进入上下文之前就把它变小。
对比一下两种做法。给 agent 一个 read_log_file 工具,它返回 8000 行,全进窗口;给 agent 一个 shell,它写:
rg -c "ERROR|FATAL" /var/log/app/*.log | sort -t: -k2 -nr | head -5
# app-2026-11-03.log: 412
# app-2026-11-04.log: 87
# app-2026-11-02.log: 31
三行输出,信息量比 8000 行高。差别不在工具能力,在于过滤发生在哪一侧。专用工具的返回值是"全量",shell 的返回值是"模型自己定义的投影"。
这也是 Anthropic 在 MCP 的 code execution 方案里强调的点:与其把 MCP 工具一个个暴露成 tool,不如把它们暴露成代码里的函数,让 agent 写代码调用,中间结果留在执行环境里,只有 console.log 的内容进上下文。一个典型例子是"把会议转录稿加到日历"——传统做法要先把整份转录稿拉进上下文,再用模型逐条提取;代码执行方案里,转录稿在执行环境里被脚本处理,窗口里只有最终确认的几行。
为什么有效:上下文成本由"进入窗口的字节数"决定。shell 的重定向、管道、head、jq、awk 都是免费的过滤器,它们不消耗 token,只消耗 CPU。
代价:
- 安全边界变模糊。一个自由 shell 等于给了 agent 任意执行能力,必须配沙箱、白名单、路径限制
- 可观测性变差。结构化工具调用容易审计(谁在什么时候调了什么参数),自由文本命令难做策略
- 对模型能力有要求。弱模型写 shell 的错误率明显更高,一个引号错误就是一次失败的重试
- 跨平台问题。Windows 上没有
rg、jq是常态,得准备降级路径
失效边界:需要细粒度权限控制的场景不适合纯 bash——每个工具单独授权、单独计费、单独审计,这是结构化工具的优势,shell 给不了。
这套东西是怎么收敛到六条的
2023 年那批 agent 的上下文策略和现在完全不同,值得看一眼,因为淘汰过程解释了为什么最后剩下的是这六种。
最早的做法是全量塞:把整个代码库、整个文档、整段历史都拼进 prompt,赌窗口够大。窗口从 4k 涨到 200k 的过程中,这个策略看起来是对的——直到发现成本和延迟都线性上涨,而且模型在长输入上的准确率反而下降(也就是前面说的 context rot)。全量塞的隐含假设是"模型会自己忽略无关内容",这个假设被实验反复否掉。
第二代是检索式记忆:把历史对话向量化存进数据库,每轮检索 top-k 塞回去。听起来优雅,实际用起来问题很多——向量检索按语义相似度召回,但 agent 需要的往往是"三分钟前刚确认的那个约束",语义上和当前问题毫无相似性,检索不回来。而且检索进来的片段是碎片化的,模型拿不到完整因果链。这套方案今天的定位已经退到"跨会话的长期记忆",不再承担单次任务内的上下文管理。
第三代是滑动窗口:只保留最近 N 轮。简单,但会硬生生切掉任务开头定下的约束,长任务做到一半突然忘了最初的目标。
现在的六种手段本质上是同一个转向的结果:把记忆从"模型内部"外化成"外部环境"。文件系统负责存,shell 负责查,索引负责找,子进程负责隔离,摘要只负责保留不可重建的那部分状态。模型不再需要"记住"所有东西,只需要知道去哪里找。这个转向之所以成立,是因为编码 agent 本来就工作在一个有文件系统的环境里——外部记忆的存储和检索成本近乎为零,而窗口里的 token 是稀缺的。
组合:六种手段分在三个时间尺度上
单独看每一条都有明显短板,但它们的时间尺度不同,所以能叠起来用:
MERMAID_BLOCK_1
按一个请求的生命周期排,决策顺序大概是:
- 进窗口前:先在工具索引里搜一下有没有现成能力(tool search),再看有没有相关 skill 的正文需要读(渐进披露)
- 执行时:优先用 shell 管道把输出投影成小结果(bash),探索型子任务丢给 subagent
- 结果回来时:超过阈值的落盘,只留指针和预览(offloading)
- 窗口涨到 60–70%:清掉旧工具结果,保留最近几次(clear tool uses)
- 窗口快满:结构化摘要,重建窗口(compaction)
这五步构成一个闭环。少了任何一环,下一个环节的压力就会陡增——比如没有 bash 过滤,落盘的文件会多到没人管;没有落盘,清理就只能清掉整段有用信息;没有清理,摘要会来得越来越频繁,每次摘要都在丢细节。
失效边界:什么时候这套东西会帮倒忙
上面六种手段都有共同的假设:上下文里的大部分内容是噪声。这个假设在多数 agent 任务里成立,但有几个明确的反例。
任务本身就是长上下文。代码审查一个 5000 行 diff、逐条比对两份合同、按原文引用写报道——这些任务里每一行都可能是信号,任何压缩都是信息损失。这时候该做的是换更大的窗口或者拆任务,不是压缩。
压缩破坏了因果链。模型需要知道"为什么排除了方案 A"才能不重复尝试。摘要只保留"已排除 A",不保留理由,模型就会在下一轮重新提 A。这个失败模式在长任务里非常顽固,解法是在摘要模板里强制要求"每条决策保留一句理由"。
手段之间互相打架。subagent 隔离了上下文,但它的结论没法被主 agent 验证;工具结果落盘省了 token,但 agent 每次都要多一次读取;bash 过滤掉了噪声,也过滤掉了"看起来无关但其实相关"的线索。压缩比越高,agent 越像一个"只记得结论的实习生",执行快但容易在细节上翻车。
成本会转移,不会消失。subagent 省主窗口,但总 token 翻倍;tool search 省工具定义,但每次多一轮调用;落盘省上下文,但多一次读写和一套文件管理。真正省钱的做法是减少无效的探索——这靠的是更好的任务描述和更准的工具,不是更狠的压缩。
观测指标
调这套东西的时候,别只看"有没有报 context length exceeded"。有四个指标能提前暴露问题:
- 每轮输入 token 的斜率:正常任务应该是缓慢上升,如果出现垂直跳变,说明有工具在返回大块内容
- 缓存命中率:缓存的是前缀,任何系统提示或工具集的改动都会让命中率归零。命中率突然掉,通常是动态拼接了时间戳或随机 ID
- 摘要触发频率:一小时触发三次以上,说明前面的过滤环节失效了,该去看是哪个工具在灌水
- subagent token 占比:超过总量 60% 说明主 agent 已经退化成调度器,这时候要考虑是不是任务拆分本身有问题
这些数字比"感觉变快了"可靠得多,也更容易定位到具体是哪个环节漏了。
参考文档
- Effective context engineering for AI agents — 上下文工程的整体框架、压缩与注意力预算
- Context editing —
clear_tool_uses与clear_thinking的触发阈值、保留策略 - Tool search tool —
defer_loading、regex 与 BM25 两种检索实现 - Agent Skills — 三层渐进披露、SKILL.md 结构
- Code execution with MCP — 把工具暴露成代码 API、中间结果留在执行环境
- Building effective agents — 摘要式记忆、orchestrator-worker 模式
- Context Rot — 18 个模型的长上下文性能衰减实验
- Claude Code best practices — compact、子任务与工具使用习惯
你的 agent 现在卡在哪一环?是工具结果灌爆窗口,还是摘要之后模型开始"失忆"?评论区聊聊你踩过的坑。
作者: itech001
来源: 公众号:AI人工智能时代(the-ai-era)
网站: https://www.theaiera.top/
关注每日最新AI新闻和技术博客,主页有更多的文章的AI 技术参考:https://www.theaiera.top
本文首发于 AI 人工智能时代,转载请注明出处。

浙公网安备 33010602011771号