Context 工程
一句话定位:Context 工程管 Agent 循环里「喂什么、留什么、扔什么」——上下文是稀缺且会衰减的资源,把它当预算来花,长任务才不跑偏。前置:Agent:循环、工具调用与 harness——知道了循环怎么转,这篇管循环每圈吃进去的东西。
Agent 干得越久,越不像它自己
Agent 跑长任务的头几步,又快又准:目标清晰、材料新鲜、每一步有的放矢。
几十圈之后画风突变:开始忘事(最初的约束早被挤出了视野)、开始重复劳动(刚查过的东西再查一遍)、开始拿不相干的旧材料当依据。模型没换、指令没改,表现却一路下滑。
问题出在哪?不在模型,在喂给它的东西——上下文窗口是有限的,每转一圈都往里塞新内容,早期的关键信息被稀释、被挤出,注意力跟着失焦。业界给这种现象起了名字:上下文腐烂(context rot)。
怎么让长任务的第 50 步和第 1 步一样清醒?答案是把上下文当预算来花——这就是 Context 工程。
图:上下文流水线——素材池里大部分东西进不了窗口;①②③决定谁能进、怎么摆、何时重装
渐进拆解:一只容量固定的行李箱
类比:收拾一只容量固定的行李箱
Context 工程就是收拾一只容量固定的行李箱——箱子就那么大(上下文窗口),旅程却在变长(循环圈数)。
- 身份证件放外袋(系统提示):常驻、固定位置、每次过安检都要第一时间拿出来。
- 衣服按天卷着放(工具返回与检索材料):今天的必带,三天后的大概率不用带。
- 纪念品最占地方(消息历史):每一件都有意义,但全塞进去箱子就关不上——得挑、得压、得扔。
- 装箱单(Token 预算):每类东西配额多少,出发前定好,超了就淘汰——外袋(常驻)、主仓(本任务)、侧袋(机动)各划各的线。
不会收拾箱子的旅行者有两种:什么都带的(超重,翻东西翻半天——对应注意力稀释)和什么都不带的(到目的地两眼一抹黑——对应信息不足瞎编)。收拾箱子的手艺,就是在「带够」和「装得下」之间找平衡。
例子:30 圈长任务的上下文账
把前一篇的运维助手放进一个 30 圈的长任务里,算一笔上下文账:系统提示 800 token 纹丝不动;用户最初那句「服务响应变慢了」早就被挤到视野边缘;30 条工具返回里,只有最近 3 条还有用,其余 27 条是几千 token 的日志原文,静静躺在上下文里稀释注意力。第 31 圈模型开始重复查询第 5 圈查过的东西——不是它忘了怎么干活,是干活需要的线索被淹了。
大白话
模型的注意力像手电筒的光,上下文越长,同一束光照到的地方越多、每处越暗。Context 工程的全部工作,就是让这束光永远只照现在重要的东西。
技术定义
Context 工程是提示词工程的演进——后者关注「一条指令怎么写」,前者关注「推理时的整个上下文状态怎么策划」,包括系统提示、工具、消息历史、外部检索材料的持续取舍。业界权威表述把上下文看作注意力预算:token 是稀缺资源,且模型召回信息的准确度会随上下文总量增加而衰减,目标是「找到能最大程度实现预期结果的最小高信号 token 集合」。这套策划落地为三条流水线,见下节。
三条流水线各干各的
过滤:进门先问「跟这步有关吗」
工具返回原样回填是腐烂素材的大头——一次 grep 几千行日志,有用就几十行。工具相关性过滤让「和当前任务无关的工具」压根不进候选:查数据库的活,别让模型看见发邮件的工具;消息相关性过滤淘汰与当前目标无关的旧对话;检索材料按相似度掐尖。
过滤的标准只有一条:这一段如果删掉,下一步的判断会变差吗? 不会就删。
组装:位置也是信息
一般经验是窗口开头与结尾的内容更受关注,中段容易被「扫过」。所以常驻约束(角色、边界、输出格式)放系统区;关键材料靠近当前任务放;历史长尾压缩成摘要垫底。Token 预算按「常驻区 / 本任务区 / 机动区」切分配额,超区的素材先过滤再进窗。
迭代:每圈重新装一次箱
上下文不是一次性打包——每圈循环结束,都要重新过一遍流水线:新工具返回进来了、旧材料过期了、目标推进到新阶段了。节奏上不必每圈都大动干戈:常规圈只做增量检查(新素材够不够格进窗),每隔几圈做一次全量重组(历史压缩、预算复核)。这也是它与一次性提示词写作的本质区别:写 prompt 是一次动作,管 context 是持续职责。
失败重试与降级也要配合这套节奏:
- 重试时,回填的错误信息要精炼:错误类型 + 关键参数,而不是整个报错堆栈——「上游超时 30s,重试第 2 次」比 200 行堆栈有用且便宜。
- 降级到备用模型时,上下文要按新模型的窗口重新适配:预算表跟着换,别拿大模型的行李塞小模型的箱子。
动手:一张上下文预算表
伪代码:每圈循环跑一次预算检查,超区先压缩再进窗:
BUDGET = {"reserved": 2000, "task": 24000, "flex": 8000} # 三区配额(token)
def pack(system, history, tool_results, query):
ctx = system[:BUDGET["reserved"]] # 常驻区:角色与约束
task_zone = history + tool_results # 本任务区:历史与返回
while size(task_zone) > BUDGET["task"]: # 超预算 → 压最旧的
task_zone = summarize_oldest(task_zone) # 旧记录压成摘要
flex = retrieve(query, top_k=3)[:BUDGET["flex"]] # 机动区:检索掐尖
return ctx + task_zone + flex
# 每圈循环调用一次 pack——上下文是每圈重新装的行李,不是一次装箱
配合一张 Debug 记录(见下节),每一次打包都可以复查:谁进了、谁被压了、各区实际占用多少。
三区的配额给个可感的量级:一个 2 万 token 的窗口,常驻区给 2000(角色与约束,反复被读不嫌多)、本任务区给 14000(历史与工具返回的主战场)、机动区留 4000(检索材料按需进出)。数字随模型与任务浮动,比例感比绝对值重要:常驻要小而硬,机动要留余量。
边界与取舍:污染与检测
上下文污染:长任务最常见的翻车原因
一段被注入的错误信息、一次失败工具的噪音返回、一段过时的旧结论——只要留在窗口里,就会持续影响后续判断,而且模型往往言之凿凿地引用它。污染的麻烦在于不可见:窗口里几万 token,人眼扫不出那一段是毒。
所以检测要靠报表而不是靠眼睛。每次打包时顺手记录一份上下文调试报告:本圈进了哪些素材(来源、token 数)、压掉了哪些、各区占用与配额的偏差、与上圈相比的增量。长这样:
图:调试报告样例——逐圈记录谁进窗、谁被压、token 多少;出问题翻表即可定位污染源
别把过滤做成误杀
过滤的标准是「删了不影响下一步」——但「下一步」会变:此刻没用的材料,三圈后可能就是关键证据。最典型的懊悔场面:第 3 圈丢掉的报错码,第 30 圈才发现正是破案钥匙。保守做法是把被过滤的素材降级留档(移出窗口、留在仓库),需要时可以捞回,而不是物理删除。
方法与实现的分工
本篇讲的是「喂什么、留什么」的方法论;工程上还有一整套源码级的压缩实现——按什么时机触发、对哪些内容折叠、怎么保留骨架——那是上下文压缩体系专篇的主题。两篇合起来才是完整拼图:这里定策略,那里看机器。
自测
合上文章先自己答,再展开对照:
「上下文污染」是什么、怎么检测?
污染指错误信息混进上下文并持续影响后续判断:被注入的错误指令、失败工具的噪音返回、过时的旧结论,都会被模型当作依据反复引用,且引用得言之凿凿。麻烦在于不可见——几万 token 里人眼扫不出那一段是毒。检测靠报表:每次打包记录一份上下文调试报告(本圈进了哪些素材及来源与 token 数、压掉了哪些、各区占用与配额偏差、逐圈增量),出问题时翻到异常素材进入的那一圈,即可定位污染源。预防手段是把被过滤素材降级留档而非物理删除,随时可回捞。
这篇的方法论与上下文压缩体系的源码级实现,分工在哪?
分工是「策略层」与「实现层」。本篇(Context 工程)回答策略问题:喂什么、留什么、扔什么——过滤标准、三区预算怎么切、位置怎么摆、每圈怎么迭代,这些决策先于任何实现。上下文压缩体系专篇回答实现问题:压缩在什么时机触发(阈值、定时)、对哪些内容怎么折叠(工具结果占位、历史摘要)、多级压缩体系怎么用源码组织。先有策略再有实现——不知道该扔什么,再精巧的压缩算法也只是在错误的取舍上跑得飞快。
参考与延伸
- 视频版:动画拆解(约 4 分半)——本篇的交互式演示版,逐节拍推进、可点击回看,支持竖屏模式。
- Anthropic《Effective Context Engineering for AI Agents》(2025-09-29):「注意力预算」表述与压缩、结构化笔记、子代理三大技术的出处(context rot 概念转引自下条) — anthropic.com/engineering/effective-context-engineering-for-ai-agents
- Chroma《Context Rot: How Increasing Input Tokens Impacts LLM Performance》(2025-07):context rot 术语的原始出处,长上下文性能随输入增长而衰减的实测 — trychroma.com/research/context-rot
下一站:Embedding 与向量化——机器怎么把「语义」变成可计算的距离:检索靠它排序,Agent 记忆靠它去重,是两大机制共同的地基。