Context Engineering:为 Agent 构建高信噪比上下文
Context Engineering 不是把更多资料塞进上下文,而是在每一步为 Agent 选择最少、最可靠、最能推动任务的信息,并让它在长任务中持续维护这份状态。
相关笔记:编程 Agent 的上下文工程【译】 · AI Agent 开发最佳实践 · 企业知识库建设实操指南 · AI 编程的上限与知识库原则 · SDD 和 活文档
目录
| 章节 | 说明 |
|---|---|
| 为什么需要上下文工程 | 有限注意力预算与 Prompt 的边界 |
| 一个编码任务的前后对照 | 从模糊指令到可执行任务包 |
| 观点谱系与控制权 | Anthropic、Martin Fowler、LangChain 的互补视角 |
| 上下文的五类资产 | 规则、任务、事实、状态与工具 |
| 上下文生命周期 | 启动、探索、执行、交接四阶段 |
| 分层加载与即时检索 | 什么时候读,读多少,何时不用 RAG |
| 长任务的状态管理 | 压缩、进度文件与子 Agent 隔离 |
| 工具与参考实现 | 让 Agent 获取可验证的上下文 |
| 评测与迭代 | 用任务结果而非感觉优化上下文 |
| 编程 Agent 实战模板 | 可直接落地的仓库约定与任务输入 |
| 常见反模式 | 信息过载、陈旧规则与伪记忆 |
| 落地清单 | 从单项目到团队平台的实施路径 |
为什么需要上下文工程
Prompt Engineering 关注“指令怎样写”;Context Engineering 则管理这一轮推理实际看到了什么:除 Prompt 外,还包括任务、代码与文档、历史消息、工具及其结果和持久化状态。两者互补;复杂 Agent 的可靠性取决于如何选择、维护和更新这些信息。
单轮、边界明确的任务通常只需优质 Prompt;但 Agent 在“思考 → 调工具 → 读取结果 → 再行动”中不断产生信息。没有选择与淘汰机制,旧日志、无关代码和冲突规则会挤占关键证据。
目标不是最大化 token 数量,而是最大化单位 token 对当前任务的有效信息量:最小的高信噪比上下文集。
一个编码任务的前后对照
以“为退款流程新增 PROCESSING 状态”为例。区别不在于把提示词写得更长,而在于让 Agent 在动手前拿到可执行的边界与证据入口。
| 只有提示词 | 经过上下文工程 |
|---|---|
| “新增退款状态并修改接口。” | 目标、兼容性边界、状态机文档、实现与测试入口、验收命令 |
| Agent 反复搜索,容易遗漏枚举、迁移或测试约定 | 先按需读取证据,再修改;失败原因可沉淀为规则或测试 |
任务:为退款流程增加 PROCESSING 状态。
边界:不改变既有接口字段;迁移可回滚;只修改 payment 模块。
证据入口:docs/payment-state.md;RefundService.java;RefundServiceTest.java。
验收:./gradlew :payment:test
有限窗口不是唯一约束
即使模型的上下文窗口很大,输入越长仍可能带来注意力稀释、目标漂移和错误引用。把整个仓库、完整聊天记录或所有检索结果一次性放入 Prompt,通常会造成以下问题:
| 失败模式 | 表现 | 根因 | 对策 |
|---|---|---|---|
| 信息淹没 | 忽略关键约束、重复提问 | 低信噪比材料占据注意力 | 只保留当前决策所需证据 |
| 指令冲突 | 同时遵从相互矛盾的规则 | 规则来源没有层级与生效范围 | 明确优先级、路径范围和失效条件 |
| 工具噪声 | 反复搜索、读取巨大输出 | 工具结果未经筛选直接回灌 | 限制输出、摘要并保存引用 |
| 状态遗失 | 新会话重复探索或误改 | 决策与未完成事项未持久化 | 使用结构化进度文件 |
| 陈旧知识 | 按已废弃接口或规范实现 | 文档没有来源、版本和所有者 | 将事实与推测分开,并记录更新时间 |
大上下文窗口有“容量承诺陷阱”:能传入 N 个 token,不等于模型能可靠利用其中的关键事实;评估重点是可检索、可利用的有效信息,而非窗口标称值。
观点谱系与控制权
各方表述不同但互补:分别解释了优化目标、配置对象、运行时控制点与组织改进机制。组合起来,才能从“写一份规则”走向可持续运行的 Agent 系统。
| 视角 | 核心问题 | 关键结论 | 对编程 Agent 的启发 |
|---|---|---|---|
| Anthropic | 上下文为何要精简? | token 是有限注意力预算;追求最小高信噪比集合 | 用即时检索、压缩和子 Agent 避免上下文污染 |
| Martin Fowler / Thoughtworks | 应配置哪些上下文,谁决定加载? | 区分指令、指导和上下文接口;加载者可以是人、LLM 或 Agent 软件 | 不把所有内容塞入规则文件;把确定性动作交给 hooks,把按需知识交给 Skills |
| LangChain | Agent 循环中哪些地方可控? | 分别管理模型上下文、工具上下文和生命周期上下文 | 不只优化 Prompt;还要约束工具输入输出,并在调用之间做摘要、护栏与日志 |
| Harness Engineering | 如何让改进累积? | 将指南作为前馈控制、测试/审查/观测作为反馈传感器,反复改进 harness | 每次重复失败都应沉淀为规则、测试、脚本或参考实现,而非只在下一轮聊天补一句话 |
Martin Fowler:上下文配置不是一份巨型规则文件
Thoughtworks 的 Birgitta Böckeler 将编程 Agent 的可复用文本区分为两种意图:
| 类型 | 作用 | 例子 | 最佳载体 |
|---|---|---|---|
| 指令(Instruction) | 告诉 Agent 当前要完成什么工作 | “按现有约定补一组 E2E 测试” | 任务契约、命令、Skill |
| 指导(Guidance) | 给出持续生效的规范与护栏 | “测试必须相互独立” | AGENTS.md、路径规则、CI |
Tools、MCP、Skills 和工作区文件是“上下文接口”:它们描述 Agent 如何按需取得证据。关键问题是:
- 谁决定加载? 人工触发可控但降低自动化;LLM 自主加载更灵活但存在遗漏风险;Agent 软件可在生命周期节点确定性加载。
- 何时加载? 高频且全局的不变量在启动时加载;与路径有关的规则在进入模块时加载;临时事实在决策前即时读取。
- 如何验证? 规则不能保证 LLM 必然正确执行;仍需测试、静态检查、审查等独立反馈。
目标不是“控制模型”,而是提高正确行为的概率,并为高风险步骤安排监督与确定性验证。
LangChain:把上下文放进 Agent 循环的三个控制面
LangChain 将 Agent 运行中可管理的对象分为模型上下文、工具上下文和生命周期上下文。这个划分能避免一个常见误区:把所有可靠性问题都归咎于系统 Prompt。
| 控制面 | 管什么 | 典型机制 | 常见失效 |
|---|---|---|---|
| 模型上下文 | 指令、消息历史、可用工具、输出格式 | 任务契约、Rules、少量示例、结构化输出 | 目标含糊、历史噪声、规则冲突 |
| 工具上下文 | 工具可读写的状态、运行时参数和外部事实 | 权限边界、参数约束、来源标识、结果截断 | 工具太泛、结果过大、陈旧或越权数据 |
| 生命周期上下文 | 模型调用与工具调用之间发生的处理 | 摘要、持久记忆、护栏、日志、重试 | 状态无法恢复、错误重复、没有审计线索 |
把三者连起来,可以得到一个更完整的工程闭环:任务契约定义模型行动 → 工具带回可追溯事实 → 生命周期机制筛选、保存或压缩结果 → 测试与审查把失败反馈到规则、工具和参考实现。
从 Context Engineering 到 Harness Engineering
Context Engineering 解决“让 Agent 在当前一步看到正确的信息”;Harness Engineering 进一步解决“如何让正确做法在多次任务后积累”。Martin Fowler 将前者视为后者的基础能力:指南、参考实现和工具为 Agent 提供前馈,测试、静态分析、日志与代码审查为其提供反馈。
实践上可以建立一张失败归因表,防止团队只靠增加 Prompt 字数解决问题:
| 重复失败 | 优先改进位置 | 例子 |
|---|---|---|
| 不知道业务规则 | 任务层 / 知识库 | 增加带来源的领域术语与状态机说明 |
| 总是读错模块 | 项目地图 / 路径规则 | 为模块入口和依赖边界补充导航 |
| 总是漏跑验证 | 生命周期 / CI | 将定向测试写入 Skill,并由 hook 或 CI 强制执行 |
| 总是生成不一致代码 | 参考实现 / 静态检查 | 提供可编译样例,或增加架构测试与 linter |
| 外部查询结果不可靠 | 工具接口 | 返回来源、时间戳、权限范围和有限结果集 |
上下文的五类资产
将上下文按职责拆开,比维护一个不断膨胀的 AGENTS.md 更可靠。
| 资产 | 回答的问题 | 典型载体 | 生命周期 | 应避免的内容 |
|---|---|---|---|---|
| 不变量与护栏 | 什么绝不能违反? | AGENTS.md、Rules、CI | 项目/目录级 | 临时任务细节 |
| 任务契约 | 这次要交付什么? | issue、Spec、验收清单 | 单任务 | 大段背景历史 |
| 事实与证据 | 真实系统当前是什么样? | 代码、测试、日志、文档、数据库查询 | 按需读取 | 未验证的经验结论 |
| 工作状态 | 已做什么、下一步是什么? | progress.md、计划、决策记录 | 跨会话 | 完整工具日志 |
| 能力接口 | 可以如何获取事实或产生变更? | Tools、MCP、Skills、脚本 | 按能力演进 | 模糊且重叠的工具描述 |
补充:以“事实与证据”“任务契约”两类资产为例,完成一个真实需求所需的上下文远不止代码,常见项包括:需求描述(含历史需求)、代码仓库、系统架构、调用链路、API 文档、编码规范、工程结构、监控打点、组织架构、业务流程、领域名词,以及安全策略、测试数据、降级处理等“隐形知识”。缺失这些,模型只能靠“猜”——上下文远不止代码。
规则要短、可定位、可验证
全局规则只保留每次任务都需要的不变量,例如语言版本、测试命令、安全边界和提交约定。模块规则放在靠近代码的位置,让 Agent 在进入该目录时才加载。
规则的好坏不取决于篇幅,而取决于能否转化为动作:
| 模糊规则 | 可执行规则 |
|---|---|
| 注意代码质量 | 修改业务逻辑后运行 ./gradlew test;新增分支需覆盖正常与异常路径 |
| 遵守架构 | api 层不得直接访问数据库;跨域调用必须经 application 层接口 |
| 少改代码 | 只改任务相关文件;需要跨模块改动时先说明依赖与风险 |
上下文生命周期
编程 Agent 的上下文不是一次性装配,而是随任务推进不断“获取、提炼、丢弃、交接”的循环。
1. 启动:建立任务边界
启动阶段只需要回答四个问题:目标是什么、哪些约束不可违背、如何验收、哪些信息尚不确定。不要预先加载全部代码;先读取仓库入口、相关 Rules、任务描述和已有测试。
一个足够好的任务契约示例:
目标:为订单取消接口增加幂等保护。
范围:order-service;不改支付服务和数据库表结构。
事实来源:现有 CancelOrderService、取消接口集成测试、订单状态机文档。
验收:相同 requestId 重试不重复退款;新增集成测试;执行 ./gradlew :order-service:test。
未知项:支付回调与取消请求并发时的状态优先级。
2. 探索:先找证据,再下结论
探索阶段通过搜索、代码导航、运行测试和读取监控数据建立事实。Agent 应输出“证据 → 推论”,而不是把猜测写成事实;例如“测试显示取消逻辑已在事务内”比“应该已经有事务”更可靠。
对于规模较大的代码库,先生成一个轻量索引:入口文件、领域对象、调用链、测试位置和配置来源。索引只保存路径与一句话用途,详细内容由工具按需读取。
3. 执行:让上下文服务于下一次决策
执行时保留三类信息即可:当前计划、已经验证的关键事实、尚未解决的风险。大段 rg 输出、编译日志和已处理的文件列表应被摘要或丢弃;否则它们会遮蔽下一步的选择。
4. 交接:把可恢复状态写出窗口
任务跨越上下文窗口或由另一位 Agent 接手时,必须将状态落到版本控制中的文件或任务系统,而不是依赖聊天记录。交接材料至少包含:已完成项、关键决策及理由、验证结果、未完成项、下一步命令和风险。
交接不只是写状态,还应把可复用产物沉淀为长期知识:任务产出的设计文档、Spec 或决策记录若只留在本次任务里,就是一次性产物;将其归档为项目级 Context 供后续需求复用,才能让一次产出持续反哺知识库,形成闭环。
分层加载与即时检索
三层上下文
| 层级 | 内容 | 加载方式 | 目标 |
|---|---|---|---|
| 入口层 | 项目地图、全局不变量、常用命令 | 会话开始时少量加载 | 知道从哪里找 |
| 任务层 | Spec、相关目录规则、调用链与测试 | 任务启动后加载 | 建立正确边界 |
| 证据层 | 精确代码片段、日志、文档段落、查询结果 | 需要决策时即时读取 | 支撑当前判断 |
“即时检索(Just-in-Time)”的关键不是有没有向量库,而是先保存轻量引用(路径、URL、查询条件、对象 ID),在真正需要时再读取原文。这样既避免提前塞入大量材料,也让 Agent 能返回来源重新核验。
本地索引 + 远程内容
知识的存放位置也需区分:长期稳定、内容量少、需要保证稳定触发的不变量放本地;容易变动、内容量大、需要时效的放远程,按需拉取。纯本地会导致多项目间难以同步更新,纯远程(如纯 MCP)又难以保证核心逻辑被稳定触发。混合策略兼顾稳定性与时效性——本地维护轻量索引与入口,远程承载大体积、易变的正文。
另一个维度:按复用范围分层
上面的“三层”按加载时机划分。换一个角度,可按复用范围/组织层级分层,目的是提升公共上下文的复用程度:
| 层级 | 内容 | 复用范围 |
|---|---|---|
| 公司级 | 技术基建、中间件、PaaS 平台、DevOps 工具链 | 全公司 AI Coding 场景 |
| 团队级 | 系统架构、应用架构、业务知识、领域名词 | 团队内多仓库 |
| 项目级 | 代码、工程结构、编码规范 | 单仓库/服务 |
| 需求级 | 需求文档、方案设计 | 单任务 |
越靠上复用度越高,越应沉淀为可共享的公共上下文;越靠下越具体、变化越快。把上下文按“加载时机”与“复用范围”两条维度交叉管理,是规模化 AI Coding 的关键。
分层还要定义优先级与冲突解决:层级越低越具体、优先级越高——项目级可细化团队级,但不能与之冲突;新增规则时需复核是否与上级规则矛盾,避免下层规则悄悄破坏上层约束。这与“指令冲突”反模式互为照应。
RAG 不是默认答案
知识量小、需要完整理解且稳定时,直接加载原文通常比切块检索更可靠;知识量大、更新频繁或任务只涉及局部时,再采用检索。企业知识库建设实操指南 中的混合检索、父子块和重排序适合作为“证据层”基础设施,而不是替代任务边界定义。
代码场景尤其要慎用切块检索:它可能破坏调用链与业务理由的完整性,索引也会随重构迅速陈旧;引入向量服务还需评估数据副本、权限隔离和保留策略。因此,许多编程工具优先沿调用链直接读取关联文件。
| 场景 | 优先策略 | 原因 |
|---|---|---|
| 一份短而完整的设计文档 | 全文加载 | 避免分块丢失因果关系 |
| 大型代码库排障 | 路径索引 + 代码搜索 | 先定位,再读取局部实现 |
| 企业制度/产品知识问答 | 混合检索 + 重排序 | 同时覆盖语义和精确术语 |
| 需要实时状态 | 工具查询源系统 | 文档可能已过期 |
| 有安全影响的操作 | 工具返回 + 人工确认 | 不能把模型记忆当事实 |
长任务的状态管理
长任务的本质不是“把对话压缩得更短”,而是将工作记忆外置成可恢复、可验证的工件。
长任务可用四类操作组织:写入(存到窗口外)、选择(按需取回)、压缩(摘要与修剪)、隔离(将大体积工作交给子 Agent 或沙箱)。下表重点展开后面三类。
三种策略的取舍
| 策略 | 适用情况 | 保留什么 | 代价 |
|---|---|---|---|
| 压缩(Compaction) | 连续对话、刚完成一段探索 | 目标、关键事实、决策、未解问题 | 摘要可能遗漏细节 |
| 结构化进度文件 | 多轮编码、跨会话接力 | 功能清单、状态、验证命令、下一步 | 需要主动维护 |
| 子 Agent 隔离 | 研究/检索子问题可并行 | 主 Agent 只接收结论与引用 | 协调成本、结论失真风险 |
压缩可由运行环境在接近窗口上限时自动生成历史摘要;阈值应随模型、任务和工具输出调整,而非固定为某个百分比。
推荐将 progress.md 设计为机器和人都能快速读取的固定结构:
## 目标
- 为订单取消增加 requestId 幂等。
## 已验证事实
- CancelOrderService 在事务中更新订单状态。
- 退款由 PaymentClient 异步发起;重复调用当前会重复发送。
## 已完成
- [x] 定位接口与集成测试。
- [x] 增加幂等记录的 repository 测试。
## 未完成与风险
- [ ] 明确支付回调和取消请求的并发优先级。
- [ ] 执行 order-service 集成测试。
## 下一步
1. 在 application 层写入并检查 requestId。
2. 运行 ./gradlew :order-service:test。
压缩只应保存“以后仍会影响决策”的信息;完整日志应留在可搜索的文件、CI 或观测系统中,而不是继续占用模型上下文。
工具与参考实现
工具既是能力接口,也是上下文接口。一个工具描述不清,Agent 就无法稳定决定何时调用它、输入什么、如何解释输出。
工具设计原则
| 原则 | 做法 |
|---|---|
| 单一意图 | search_code、run_targeted_test、get_order 分别解决一类问题 |
| 输入可约束 | 用路径、模块、时间范围、最大结果数限制搜索 |
| 输出可行动 | 返回摘要、来源、下一步可用的标识,而非整页原始数据 |
| 写操作可控 | 明确影响范围,执行前展示计划或要求确认 |
| 失败可诊断 | 区分权限、参数、空结果和系统异常 |
参考应用是高质量的 few-shot 上下文:相比在 Markdown 中维护代码片段,可编译、可测试的参考仓库能提供真实依赖、命名和错误处理方式。它也应有版本或 commit 标识,避免示例与生产代码长期漂移。
Skills、MCP、Rules 的职责边界
| 机制 | 最适合承载 | 不适合承载 |
|---|---|---|
Rules / AGENTS.md | 高频不变量、项目边界、命令入口 | 大段领域知识和一次性任务步骤 |
| Skill | 可复用且按需加载的工作流、检查表、领域操作 | 每个项目都不同的临时状态 |
| MCP / Tool | 实时查询、受控写入、外部系统能力 | 用自然语言描述即可的静态知识 |
| 文档 / 知识库 | 架构理由、业务约束、长期事实 | 没有所有者和更新时间的传闻 |
progress.md | 当前任务状态与下一步 | 长期项目规范 |
评测与迭代
上下文工程不能只凭“回答看起来更聪明”判断;应以固定真实任务比较不同配置下的成功率、返工、耗时和人工接管次数。
| 指标 | 观察点 | 例子 |
|---|---|---|
| 任务成功率 | 是否满足验收与测试 | 是否正确完成幂等改造 |
| 事实准确率 | 关键结论能否追溯证据 | 是否引用了正确模块和接口版本 |
| 检索质量 | 有用证据是否排在前面 | Top-5 是否包含目标领域规则 |
| 工具选择率 | 是否选对工具和参数 | 是否先运行定向测试而非全仓构建 |
| 交接恢复率 | 新会话能否继续推进 | 新 Agent 是否能从进度文件完成下一步 |
| 成本与时延 | token、调用次数、等待时间 | 压缩是否减少重复探索 |
迭代应先修复任务定义和证据来源,再优化工具接口与加载策略,最后才微调 Prompt;许多“模型不稳定”其实源于事实不完整、工具输出嘈杂或验收标准缺失。
编程 Agent 实战模板
仓库入口文件
# AGENTS.md
## 项目不变量
- Java 21;使用 Gradle;禁止直接修改 generated/。
- 所有 API 变更必须更新 OpenAPI 契约和集成测试。
## 导航
- 领域规则:docs/domain/
- 架构边界:docs/architecture/
- 运行与测试:docs/development.md
## 工作方式
1. 先读任务相关目录的 AGENTS.md 和测试。
2. 不确定事实时使用搜索或运行定向测试,不凭猜测修改。
3. 长任务维护 progress.md;交接前记录验证结果与未解风险。
进阶:入口文件可升级为意图路由,按任务意图触发对应工作流;仍应保持“入口索引 → 按需工作流 → 读取证据”的渐进式披露。
每次任务的上下文包
任务:<一句话目标>
范围:<允许修改的模块 / 不可触碰的边界>
验收:<测试、行为、性能或安全条件>
已知事实:<附来源路径或链接>
未知项:<必须先调查的问题>
上下文入口:<AGENTS.md、Spec、相关测试、搜索命令>
交付:<代码、文档、迁移、验证报告>
这个模板强制区分“事实”和“待验证假设”,也让 Agent 知道第一步该读什么,而不是在仓库中无目标地搜索。
常见反模式
| 反模式 | 为什么失败 | 替代做法 |
|---|---|---|
| 一个超长系统 Prompt 管理全部知识 | 难更新、难定位冲突、无任务边界 | 全局不变量 + 路径规则 + 按需证据 |
| 先检索再把 Top-50 全塞入模型 | 检索噪声变成注意力负担 | 重排序后读取少量原文,并保留来源 |
| 把完整工具输出追加到历史 | 日志挤占决策空间 | 限制输出、保存标识、总结结论 |
| 用聊天记录充当项目记忆 | 会话结束即丢失,且不可审计 | 任务状态写入版本控制或任务系统 |
| 复制别人的 Rules / Skills | 规则与本地工具、架构不匹配 | 从本项目真实失败模式提炼 |
| 只有生成,没有评测 | 无法判断上下文是否真的改善结果 | 用固定任务集与验收指标回归测试 |
落地清单
- 为每个仓库建立一页轻量入口:技术栈、命令、目录地图、不可违反的边界。
- 将模块规则下沉到目录;删除不可验证、长期不生效的规则。
- 为高频任务建立“任务契约 + 验收”的模板,而不是只存 Prompt。
- 将架构决策、业务约束和跨服务关系放入可检索、有人维护的知识库。
- 为长任务加入
progress.md或等价状态工件;规定交接格式。 - 收紧工具输入和输出,优先提供可追溯来源与小结果集。
- 选择 5~10 个真实任务,记录成功率、返工和交接恢复率,再迭代上下文设计。
参考资料
评论 (0)