Agent 上下文工程:技术路径演进与落地方法
概述
Agent 的能力上限不只取决于模型,也取决于每次调用模型时,我们给了它什么上下文。
早期做大模型应用时,很多工作集中在提示词工程:怎么写 system prompt、怎么约束输出格式、怎么给 few-shot 示例。但 Agent 不是一次性问答,它会持续读取资料、调用工具、写代码、运行命令、处理错误、更新计划。随着任务变长,真正困难的问题变成:
在每一轮模型调用前,如何决定哪些信息进入上下文、哪些信息留在外部、哪些信息被压缩、哪些信息被隔离到子任务里。
这就是上下文工程(Context Engineering)。
截至 2026-05-11,Agent 上下文工程已经不再只是“长上下文不够用”的补丁,而是在向一套系统工程方法演进:状态管理、检索、压缩、记忆、工具协议、子 Agent 协作和可观测性共同组成 Agent 的上下文层。
技术路径演进
阶段一:Prompt Engineering
最早的大模型应用主要依赖提示词工程。
典型做法是:
- 写清楚角色和任务目标。
- 规定输出格式。
- 给几个示例。
- 在 prompt 里塞业务规则。
这种方式适合短任务,比如分类、摘要、信息抽取、单轮问答。因为模型只需要在一次调用里完成任务,上下文结构相对简单。
但它的问题也很明显:提示词只能控制“指令表达”,不能解决“运行过程中产生的信息如何管理”。一旦任务变成多轮,prompt 本身就不够用了。
阶段二:RAG,把外部知识拉进上下文
第二个阶段是 RAG。它解决的问题是:模型参数里没有最新知识或私有知识,所以需要从外部知识库检索相关内容,再放进上下文。
这一阶段的上下文工程重点是:
- 文档如何切块。
- 如何建立索引。
- query 如何改写。
- top-k 取多少。
- 如何 rerank。
- 如何把检索结果拼进 prompt。
RAG 让上下文从“手写 prompt”扩展到了“动态知识注入”。但早期 RAG 仍然偏向单轮问答:用户问一个问题,系统检索一批文档,模型生成答案。
Agent 场景会进一步复杂。Agent 不只是读文档,还要在过程中不断产生新状态:计划、工具结果、错误、代码改动、用户反馈、长期偏好。这些内容不是普通 RAG 能完整处理的。
阶段三:Tool Use,工具结果成为上下文
当模型开始调用工具,上下文结构又变复杂了一层。
工具调用带来了新的上下文类型:
- 工具定义:模型需要知道有哪些工具、什么时候用。
- 工具参数 schema:模型需要按结构生成参数。
- 工具返回值:模型需要理解外部系统的结果。
- 工具错误:模型需要根据失败信息恢复。
- 工具权限:模型需要知道哪些动作允许执行。
这个阶段的问题是:工具很容易制造上下文噪声。
例如搜索工具返回 50 条结果、浏览器工具返回整页 HTML、命令行工具返回几百行日志。模型看似获得了更多信息,实际上注意力被稀释了。
因此,工具时代的上下文工程开始强调两个原则:
- 工具描述要短而明确:不要把所有工具文档都塞给模型。
- 工具返回要结构化和裁剪:返回摘要、关键字段、错误类型、下一步线索,而不是无脑返回原始输出。
阶段四:Memory 与 Session,让上下文跨轮延续
多轮 Agent 必须记住过去发生了什么,于是出现了 session、memory、conversation state 等机制。
OpenAI Agents SDK 文档把 context 分成两类:
- Local context:代码本地可访问的状态,例如用户 ID、依赖对象、数据库连接、运行时状态。它不直接进入模型。
- LLM context:模型真正能看见的文本、消息、工具结果和检索内容。
这个区分很关键。不是所有状态都应该给模型看。
例如:
1 | 本地状态: |
Session 解决的是“历史如何延续”,但它也会带来新问题:如果每轮都把历史消息完整拼回去,Agent 会越来越慢,也越来越容易被旧信息干扰。
所以 session 之后,必然会走向 trimming、summarization、compaction。
阶段五:Compaction,压缩长任务历史
Anthropic 在 Agent 上下文工程文章里明确提到,长时间任务需要处理 context pollution 和信息相关性问题。它们在 Claude Code 中使用 compaction:当对话接近上下文限制时,把历史压缩成高保真摘要,再用摘要开启新的上下文窗口。
Compaction 和普通摘要不同。
普通摘要追求“读起来简洁”。Agent compaction 追求“压缩后还能继续干活”。它必须保留:
- 原始目标
- 当前进度
- 已修改文件
- 已确认事实
- 已做出的技术决策
- 已经排除的方案
- 最新错误和失败命令
- 下一步行动
一个可用的 compaction 输出可以长这样:
1 | ## Goal |
这类摘要不是写给人看的,而是写给下一轮 Agent 继续工作的。
阶段六:Structured Note-taking,把中间状态写到窗口外
只靠 compaction 仍然不够。因为压缩通常发生在上下文快满时,而长任务从一开始就应该主动管理状态。
因此更进一步的做法是 structured note-taking:让 Agent 把重要状态写到外部结构化存储。
可以是简单文件:
1 | TASK.md |
也可以是数据库表:
1 | tasks(id, goal, status, acceptance_criteria) |
关键点是:上下文窗口只保留当前需要推理的信息,完整历史放在外部状态里。
这种思路和传统软件工程很接近:模型上下文是工作内存,数据库和文件系统是持久存储。
阶段七:Sub-agent,把上下文隔离到子任务
当任务本身很复杂时,单个 Agent 的上下文管理会越来越困难。于是上下文工程进入多 Agent 或子 Agent 阶段。
Anthropic 提到 sub-agent architecture:主 Agent 维护高层计划,子 Agent 负责局部探索,最后只返回压缩结论。
例如做一次技术调研:
1 | 主 Agent: |
每个子 Agent 可以消耗大量 token,但主 Agent 只接收 1000 到 2000 token 的结果。这本质上是用任务边界来做上下文隔离。
阶段八:Context Policy,把上下文工程产品化
再往后,上下文工程会从经验规则变成可配置策略。
也就是不再手写:
1 | 把最近 10 条消息放进去,再加 5 条检索结果。 |
而是定义上下文策略:
1 | context_policy: |
这时 Agent 系统会有一个专门的 context builder,在每次模型调用前,根据策略组装上下文。
怎么做:一套可落地的上下文工程流水线
如果要实现一个较可靠的 Agent,不要从“写一个超长 prompt”开始,而应该从上下文流水线开始。
1. 先定义上下文分层
建议把上下文分成七层:
1 | L0 System Contract |
每一层都要回答三个问题:
- 这层信息从哪里来?
- 什么时候进入模型上下文?
- 超过 token 预算时如何裁剪?
2. 给每类上下文设置生命周期
不要让所有信息都永久留在历史里。
可以这样设计:
| 上下文类型 | 保留位置 | 生命周期 | 进入模型的方式 |
|---|---|---|---|
| 用户目标 | session/task 表 | 整个任务 | 每轮必带 |
| 验收标准 | session/task 表 | 整个任务 | 每轮必带 |
| 最近对话 | session | 短期 | 保留最近 N 轮 |
| 工具原始输出 | blob/log | 短期 | 默认不带,按需读取 |
| 工具摘要 | task state | 中期 | 相关时带入 |
| 错误记录 | errors 表 | 到解决为止 | 未解决错误必带 |
| 已确认事实 | facts 表 | 长期 | 检索命中时带入 |
| 用户偏好 | memory | 长期 | 高置信度才带入 |
| 敏感信息 | secure store | 按权限 | 默认不进入模型 |
这样做的好处是,Agent 不会把所有东西混在聊天记录里。
3. 实现 Context Builder
Context Builder 是上下文工程的核心模块。它的职责是:在每次模型调用前,把外部状态转换成模型输入。
伪代码如下:
1 | def build_context(task_id: str, user_message: str, token_budget: int) -> list[Message]: |
这里最重要的是 fit_to_budget。它不能简单截断,而要按优先级裁剪。
4. 工具返回必须结构化
工具返回是 Agent 上下文污染的高发区。
一个不好的工具返回:
1 | 返回整个网页 HTML |
一个更好的工具返回:
1 | { |
原则是:原始输出可以存起来,但进入模型上下文的应该是结构化摘要和必要引用。
5. 检索不只检索文档,也检索状态
很多人把 retrieval 理解成只检索知识库。Agent 场景下,应该检索四类东西:
- 文档知识:API 文档、设计文档、博客资料。
- 代码片段:相关函数、配置、测试。
- 任务历史:之前做过什么、失败过什么。
- 长期记忆:用户偏好、项目规范、历史决策。
检索 query 也不应该只用用户原话。更好的 query 应该结合当前任务状态:
1 | 用户原话: |
6. 压缩要分类型,不要统一摘要
不同内容需要不同压缩策略。
| 内容 | 压缩目标 | 推荐格式 |
|---|---|---|
| 对话历史 | 保留目标、决策、未解决问题 | task summary |
| 工具日志 | 保留结果、错误、关键行 | structured JSON |
| 资料调研 | 保留结论、来源、可信度 | source notes |
| 代码阅读 | 保留文件、函数、依赖关系 | code map |
| 子 Agent 输出 | 保留结论、证据、风险 | brief report |
例如资料调研的压缩结果可以是:
1 | ## Source |
7. 子 Agent 不是越多越好
子 Agent 的价值是隔离上下文,不是制造复杂性。
适合拆子 Agent 的情况:
- 需要并行查多个来源。
- 需要探索大量代码,但主任务只需要结论。
- 需要独立审查风险。
- 子任务失败不会破坏主任务状态。
不适合拆子 Agent 的情况:
- 下一步立即依赖它的结果。
- 子任务边界不清楚。
- 子 Agent 需要频繁修改同一批文件。
- 汇总成本高于隔离收益。
一个实用原则是:子 Agent 应该消耗大量上下文,但只返回小结果。
8. 加入上下文评估指标
上下文工程需要评估,否则只能凭感觉调 prompt。
可以记录这些指标:
1 | token_usage: |
其中最有价值的是两个指标:
- 重复工具调用次数:如果 Agent 反复查同一个信息,说明上下文没有保留好。
- 压缩后失败次数:如果 compaction 后 Agent 经常丢目标或走错方向,说明压缩模板不合格。
一个最小可行实现
如果现在要做一个可用的 Agent 上下文系统,可以先实现这个版本:
1 | 1. TaskState |
数据结构可以从简单的 JSON 开始:
1 | { |
这个版本不复杂,但已经比“把所有历史拼进 prompt”可靠很多。
常见设计取舍
长上下文 vs 精准上下文
长上下文能减少检索遗漏,但也会增加噪声、成本和延迟。对于高可靠 Agent,长上下文应该是兜底能力,不应该替代上下文选择。
摘要 vs 原文
摘要便宜,但可能丢细节。原文准确,但占 token。实践中应该保留原文引用,模型上下文里放摘要;当需要细节时,再用工具按引用读取原文。
单 Agent vs 多 Agent
单 Agent 简单,状态一致性好;多 Agent 能隔离探索成本,但会增加协调成本。多 Agent 更适合研究、代码库探索、审查等高并行任务。
自动记忆 vs 人工确认
自动记忆方便,但容易写入错误偏好。对于长期记忆,最好有置信度、来源、过期时间,重要偏好需要用户确认。
总结
Agent 上下文工程的技术路径大致是:
1 | Prompt Engineering |
它的本质不是“把 prompt 写得更长”,而是把 Agent 每一轮模型调用前的信息流管理起来。
真正落地时,可以抓住三句话:
- 上下文窗口是工作内存,不是数据库。
- 工具原始输出要存储,进入模型的是结构化摘要。
- 长任务要靠状态、压缩和隔离,而不是一直堆历史。
如果说提示词工程是在写“模型应该怎么做”,那么上下文工程就是在设计“模型每一步做事时能看见什么”。对于 Agent 来说,后者正在变成更核心的工程能力。
参考资料
- Anthropic: Effective context engineering for AI agents
- Anthropic: Building effective agents
- LangChain: Context Engineering
- LangChain Docs: Context engineering in agents
- HumanLayer: 12-Factor Agents
- OpenAI Agents SDK: Context management
- OpenAI Agents SDK: Sessions
- OpenAI Cookbook: Context Engineering - Short-Term Memory Management with Sessions