Effective context engineering for AI agents
[!术语速查 / 黑称]
- context: 从 LLM 采样时塞进窗口的那一整坨 token —— system prompt, tools, MCP, 外部数据, message history 全算在内;
- attention budget: 模型的 “注意力预算”, 每多塞一个 token 就从里面扣一点, 不是免费的;
- context rot: context 越长, 模型越记不住前面说过啥 —— “上下文腐烂”, 每个模型都有这毛病;
- just-in-time: 不在 inference 前把数据全嚼碎喂进去, 而是让 agent 攥着轻量标识 (文件路径, query, 链接), 运行时按需 拉回 数据;
- progressive disclosure: agent 靠探索一点点把相关 context 挖出来 —— 文件大小暗示复杂度, 命名暗示用途, 时间戳暗示新鲜度;
- compaction: context 快满时, 把对话 压 成摘要, 拿摘要开一个新窗口接着跑;
前置直觉: 为什么 context 越长反而越笨
先讲个最朴素的画面: 人塞太多东西进脑子也会断片; LLM 一模一样;
接上文逻辑, LLM 和人一样是有 attention budget 的, 每多塞一个 token 就从这个预算里扣掉一点; transformer 让每个 token 都要 attend 到其他所有 token, 于是 个 token 就有 对关系要算, context 一长这些关系就被摊薄, 模型自然就开始 context rot —— 越读越记不住前面说了啥;
这事的根子是架构性的, 不是调教不够:
- tokens → 对 pairwise 关系, context 越长这张关系网越被拉扯到极限;
- 模型训练时见的多半是 短序列, 给 context-wide 长程依赖准备的专用参数本来就少;
- 靠 position encoding 插值硬撑长序列是能跑, 但模型对 token 位置的理解会 退化;
所以结论不是 “超过某个长度就崩”, 而是一条 性能渐变的斜坡 —— context 是个有限资源, 边际收益递减; 由此引出整篇的 指导原则: 找到那一小撮高信号 token, 用最少的量把目标行为撬出来;
context engineering vs prompt engineering
prompt engineering 是写一段话; context engineering 是每一轮都重新决定 “这次该把哪些 token 端给模型”;
- prompt engineering 是一次性的写作任务; context engineering 是 agent 在 loop 里跑起来后, 每一轮都要做的 迭代式策展;
| 维度 | Prompt Engineering | Context Engineering |
|---|---|---|
| 管什么 | 写 / 组织 LLM 指令 (尤其 system prompt) | 策展并维护 inference 期间最优的那组 token |
| 范围 | 主要是 prompt 文本 | prompt 之外全包: 指令, tools, MCP, 外部数据, message history |
| 形态 | 离散的一次性写作 | 周期性 / 迭代, 每轮重新筛选 |
| 适配场景 | one-shot 分类 / 生成 | agent 在 loop 里不断滚雪球地产出新数据 |
agent 在 loop 里每跑一轮就会生出更多 “对下一轮有用” 的数据, 这堆信息必须 周期性地精炼; 所以 context engineering 是 prompt engineering 的自然延伸 —— 不是替代, 是把视野从 “那段话” 扩到 “整个窗口”;
高信号 context 的解剖
把指导原则落到三个料上: system prompt, tools, examples; 总纲是 既要信息量足, 又要紧;
system prompt 的 “right altitude”
system prompt 要用 极其清晰, 简单直接 的语言, 写在那个不高不低的 “Goldilocks 区”; 两种翻车姿势:
| 失败模式 | 长什么样 | 后果 |
|---|---|---|
| 写太低 (硬编码) | 把复杂脆弱的 if-else 逻辑钉死在 prompt 里 | 脆 + 维护成本爆炸 |
| 写太高 (太空泛) | 给一堆高层废话, 没有具体信号, 还假设模型跟你共享上下文 | 模型抓不到可落地的指引 |
最优 altitude = 具体到能引导行为, 又灵活到只给强启发而非死规则; 实操上:
- 用 XML tags 或 Markdown header 把 prompt 切成清楚的小节 (
<background_information>,## Tool guidance,## Output description); - 追求 “完整勾勒出预期行为的最小信息集” —— 注意 minimal 不等于 short;
- 起手用最 精简 的 prompt 配最强模型, 然后照着观察到的 failure mode 一条条加指令 / 加例子;
tools
tools 定义的是 agent 和它的 “动作 / 信息空间” 之间的契约; 好的 tool 要 token-efficient, 还要鼓励 agent 高效行动 —— 自包含, 抗错, 用途极清楚, 入参描述无歧义;
最常见的翻车: 臃肿的 tool set, 功能重叠, 决策点含糊; 一句狠话戳穿本质: 如果一个人类工程师都说不清该用哪个 tool, agent 当然也说不清; 所以要 策展出一套最小可用 tool set;
few-shot examples
强烈建议给, 但别把一长串 edge case 全堆上去; 正确做法是 策展一组多样, 典型 (canonical) 的例子, 把预期行为画给模型看;
对 LLM 来说, examples 就是那张 “一图胜千言” 的图;
运行时取 context: agentic search 与 just-in-time
agent 的最简定义: LLM 在 loop 里自主用 tools; 模型越强, 能给的自主权就越大 —— 取数据的方式也跟着从 “inference 前嚼碎” 往 “运行时按需拿” 迁移;
直觉先行: 人脑也不会把整个图书馆背进脑子, 而是靠文件系统, 收件箱, 书签这些 外部索引 临时检索; agent 同理, 攥着 file path / query / link 这些轻量标识, 要用时再用 tool 拉回;
| 策略 | 怎么取 context | 代价 / 适配 |
|---|---|---|
| Pre-retrieval (embedding) | inference 前把数据全过一遍, 预计算好塞进去 | 快, 但可能塞进无关数据 + 索引会过期 |
| Just-in-time | 攥住标识, 运行时动态 load 进 context | 灵活, 自管窗口; 但运行时探索 更慢, 工具/启发式不到位就会追着死胡同 吃 token |
| Hybrid (混合) | 一部分数据前置求速度 + 一部分留给 agent 自主探索 | 复杂研究里通常最优; 越是不太变的内容 (法律, 金融) 越偏前置 |
引用的 元数据本身就在精炼行为: tests/ 下的 test_utils.py 和 src/core_logic/ 下同名文件, 用途天差地别; 文件夹层级, 命名约定, 时间戳全是信号 —— 这就是 progressive disclosure: agent 靠探索一点点发现相关 context, 文件大小暗示复杂度, 命名暗示用途, 时间戳代理新鲜度;
Claude Code 就是个 hybrid 样板: CLAUDE.md 前置丢进去, 而 glob / grep 在运行时 just-in-time 导航, 绕开会过期的索引和复杂的语法树; 最优 agent 往往混着用 —— 一句箴言压舱: “do the simplest thing that works”;
长程任务的三板斧
长程任务指 token 总量会超出窗口的活: 代码库迁移, 全面调研, 动辄几十分钟到几小时; 注意 —— 更大的窗口救不了你, 任何尺寸都逃不过 context 污染和相关性问题; 三个杠杆:
| 技术 | 机制 | 适用场景 | 代价 |
|---|---|---|---|
| Compaction (压缩) | 对话快满时总结内容, 拿摘要重开一个新窗口 | 需要大量来回 / 对话流的活 | 压太狠会丢掉微妙但关键的 context |
| Structured note-taking (结构化记笔记) | agent 把笔记持久化到 窗口外 的 memory, 之后再拉回 | 有清晰里程碑的迭代式开发 | 几乎零开销, 但依赖笔记质量 |
| Sub-agent 架构 | 专职 sub-agent 拿干净窗口做聚焦子任务, 只回吐精炼摘要 | 复杂研究 / 分析, 并行探索能回本 | 协调成本, 主从职责要切清楚 |
compaction
长程一致性的 第一根杠杆; Claude Code 的做法: 把 message history 喂给模型 压 成摘要 —— 保留架构决策, 未解决的 bug, 实现细节, 丢掉冗余的 tool 输出和废话, 然后带着压缩后的 context + 最近访问的 5 个文件接着跑;
难点是 拿捏留什么 / 丢什么 (压过头会损失微妙的关键 context); 调 prompt 的顺序是 先把 recall 拉满, 再靠剔除冗余提升 precision; 最轻的一档是 tool result clearing (已是 Claude Developer Platform 的功能): 一个 tool 在历史深处早被调过了, 没必要再看它的原始结果;
先保证不漏, 再谈精简 —— compaction 的核心是手艺, 不是开关;
structured note-taking
让 agent 定期写笔记落到窗口外的 memory, 开销极小; 例子: Claude Code 的 to-do list, 一个 NOTES.md; 它能横跨几十次 tool call 追踪进度;
最生动的是 Claude 打 Pokémon: 它能跨上千步维护计数 (“过去 1234 步我一直在 Route 1 练级, 皮卡丘朝目标 10 级涨了 8 级”), 自己画地图, 记成就, 记战斗策略 —— 全程没人教它该怎么组织 memory; context 一 reset, 它读回自己的笔记就能接着跑几小时的连续操作; Sonnet 4.5 发布时随附了一个 memory tool (public beta) 做窗口外的文件式存储;
笔记是 agent 给未来的自己留的锚 —— context 清空也能续上;
sub-agent 架构
专职 sub-agent 用干净窗口做聚焦子任务; 主 agent 协调高层计划, sub-agent 钻深技术活 / 用 tool; 每个 sub-agent 可能烧掉几万 token, 但只回吐一份压缩蒸馏后的摘要 (常 1000-2000 token);
这是漂亮的关注点分离: 细碎的搜索 context 被隔离在 sub-agent 里, lead agent 只做综合; 在复杂研究上对单 agent 是 实质性提升;
怎么选: compaction → 要大量来回 / 对话流; note-taking → 有清晰里程碑的迭代开发; multi-agent → 并行探索能回本的复杂研究分析;
- context 是 有限的珍贵资源, attention budget 会被每个 token 消耗, 边际收益递减;
- 根因是架构性的: transformer 的 注意力 + 短序列训练偏置, 导致 context 越长越 context rot;
- context engineering 是 prompt engineering 的自然延伸 —— 从 “写好那段话” 升级到 “每轮策展整个窗口”;
- 高信号 context 三要件: system prompt 写在 right altitude, tools 做成最小可用集, examples 选典型而非堆 edge case;
- 取数据从 pre-retrieval 转向 just-in-time 与 hybrid, 靠元数据和 progressive disclosure 让 agent 自己挖;
- 长程任务靠 compaction / note-taking / sub-agent 三板斧, 大窗口救不了你; 一切回到那条原则: 找到最小的高信号 token 集, 撬出想要的行为 —— 模型越聪明, 越该少规训, 多放权;
