第 5 章:Context 的组成
本章导读
- 核心问题:一次 Agent 调用中的 Context 到底由哪些部分组成。
- 关键词:System Prompt、User Task、Tool Result、Project Knowledge、Memory、Scratchpad
- 学习产出:把 CI 日志、Git Diff、AGENTS.md、Memory 和代码片段拆成结构化 ContextChunk。
1. 本章要解决什么问题
上一章我们把 Context Engineering 定义为:
在模型做出下一次判断之前,设计它应该看到哪些信息。
但“上下文”这个词很容易被说得过于抽象。很多团队在实现 Agent 时,会把所有东西都叫 context:用户问题是 context,检索文档是 context,工具结果是 context,聊天历史也是 context。
这会带来一个问题:
如果所有东西都叫 Context,那么系统就无法管理 Context。
所以本章要做一件更工程化的事情:把 Context 拆开。
我们要回答:
- 一个 Agent 的上下文到底由哪些部分组成;
- 每类上下文解决什么问题;
- 每类上下文什么时候应该进入模型;
- 每类上下文有什么风险;
- 如何在 Java / Spring Boot 中把上下文建模为可治理对象。
本章的核心观点是:
Context 不是一段字符串,而是一组有类型、有来源、有生命周期、有权限和有优先级的信息资产。
2. 对应 Anthropic 文章
本章主要对应:
- Effective Context Engineering for AI Agents
Anthropic 在文章中强调,有效上下文的目标不是“更多 token”,而是:
smallest possible set of high-signal tokens
也就是:
用尽可能少的高信号 token,提高模型产生期望行为的概率。
为了做到这一点,我们必须先知道上下文有哪些组成部分。
3. Context 的主要组成
下面是一个企业级 Agent 常见的上下文结构:
System Prompt
Conversation
User Task
Task State
Project Knowledge
Retrieved Documents
Code Context
Git Context
Tool Definitions
Tool Results
Memory
Environment
Scratchpad
Examples
User Profile
Policy / Permission
这些上下文不是等价的。
它们的作用、生命周期、风险完全不同。
3.1 System Prompt:系统指令
System Prompt 定义 Agent 的基本行为边界。
它通常包含:
- Agent 的角色;
- 任务目标;
- 输出格式;
- 安全边界;
- 工具使用原则;
- 不允许做的事情;
- 与用户交互的方式。
例如研发 Agent 的系统指令可以包含:
你是企业研发助手。
你必须优先读取项目规范。
你不能直接修改生产配置。
你在执行写操作前必须确认权限。
你完成任务时必须提供验证证据。
风险
System Prompt 常见问题有两个极端。
第一种是太抽象:
请做一个专业、负责、聪明的助手。
这种指令几乎没有工程价值。
第二种是太复杂:
如果用户说 A 就执行 B,如果 B 失败就执行 C,否则执行 D...
这会把复杂业务逻辑硬编码进 Prompt,导致脆弱和难维护。
好的 System Prompt 应该处在中间:足够具体,但不替代程序逻辑。
3.2 Conversation:对话历史
Conversation 保存用户和 Agent 的交互历史。
它的价值是:
- 保留用户意图;
- 保留澄清过程;
- 保留已做出的决策;
- 避免重复询问;
- 支持多轮协作。
但对话历史也最容易污染上下文。
长对话中可能包含:
- 已经过期的假设;
- 被用户否定的方案;
- 早期探索中的错误结论;
- 与当前阶段无关的闲聊;
- 大量重复信息。
因此 Conversation 不应该无限增长。
长任务中应使用 Rolling Summary,把对话压缩为:
当前目标
已确认事实
关键决策
未解决问题
下一步计划
3.3 User Task:用户任务
User Task 是当前任务的核心目标。
例如:
分析这次 CI 失败原因,并给出修复建议。
一个好的 User Task 应包含:
- 目标;
- 范围;
- 约束;
- 期望输出;
- 是否允许修改;
- 验证标准。
例如更好的任务描述是:
请分析 build #4312 的 CI 失败原因。
只允许读取日志、代码和 Git 历史,不要修改文件。
输出失败原因、证据、可能责任提交和建议修复方向。
这比“帮我看看 CI 为什么挂了”更适合 Agent 执行。
3.4 Task State:任务状态
Task State 描述当前任务执行到哪里了。
它可能包括:
- 当前阶段;
- 已完成步骤;
- 当前计划;
- 已调用工具;
- 已发现事实;
- 未解决问题;
- 阻塞点;
- 停止条件。
Task State 与 Conversation 不同。
Conversation 是交互历史,Task State 是运行时状态。
在长任务中,Task State 不应该只存在于模型上下文里,而应该持久化到数据库或状态存储中。
3.5 Project Knowledge:项目知识
项目知识包括:
README.md
AGENTS.md
CLAUDE.md
架构文档
编码规范
构建说明
测试说明
部署说明
常见问题
它的作用是帮助 Agent 快速理解:
- 这个项目是什么;
- 如何构建;
- 如何测试;
- 代码风格是什么;
- 哪些目录重要;
- 哪些操作禁止;
- 团队有哪些约定。
项目知识是 Coding Agent 的入口上下文。
但项目知识不应该包含文件级百科全书。Agent 可以通过代码搜索自己发现的内容,不需要在 AGENTS.md 中维护每个类的说明。
3.6 Retrieved Documents:检索文档
Retrieved Documents 通常来自 RAG:
- 向量数据库;
- 全文搜索;
- 文档知识库;
- API 文档;
- Wiki;
- 历史工单。
它们的价值是提供外部知识。
但检索文档有几个风险:
- 相关性不一定高;
- 可能过期;
- 多个文档可能冲突;
- 文档很长;
- 来源可信度不同。
因此检索结果进入上下文前,应保留:
source
score
timestamp
snippet
引用链接
不要只给模型一段没有来源的文本。
3.7 Code Context:代码上下文
对研发 Agent 来说,代码是最重要的真实上下文之一。
代码上下文可以包括:
- 文件路径;
- 类名;
- 方法签名;
- 相关代码片段;
- 调用关系;
- 测试文件;
- 配置文件;
- 构建文件。
但代码上下文非常昂贵。
一个文件可能几百行,一个模块可能几万行,整个仓库可能百万行。
所以代码上下文应该按需加载:
先搜索符号
再读取关键文件
再截取相关片段
必要时读取完整文件
不要一开始把整个项目塞给模型。
3.8 Git Context:版本上下文
Git Context 包括:
- 当前分支;
- 最近提交;
- 当前 diff;
- blame;
- 历史变更;
- PR 描述;
- review 评论。
Git Context 对 Coding Agent 非常有价值,因为它回答:
最近发生了什么变化?
哪些文件被改过?
这个 API 为什么变成这样?
某个 bug 是否由最近提交引入?
在 CI 失败分析中,Git Diff 和最近提交往往比完整代码库更重要。
3.9 Tool Definitions:工具定义
工具定义也是上下文。
当模型需要调用工具时,它必须看到:
- 工具名称;
- 工具描述;
- 输入参数;
- 输出格式;
- 错误类型;
- 使用限制。
工具定义太少,模型不会用。
工具定义太多,会占用大量上下文窗口。
这也是为什么大型企业 Agent 平台需要 Tool Selection:不是把所有工具都暴露给每次调用,而是只暴露当前任务可能需要的工具。
3.10 Tool Results:工具结果
Tool Results 是 Agent 与环境交互后的观察。
例如:
read_file 返回文件内容
run_test 返回测试结果
search_code 返回匹配列表
git_diff 返回变更内容
query_database 返回查询结果
Tool Results 通常具有最高时效性,因为它们是当前任务的最新证据。
但它们也最容易过长。
例如一次测试失败可能产生上万行日志。
因此工具结果应先结构化和压缩,再进入上下文。
3.11 Memory:记忆
Memory 是跨会话保存的信息。
它可能包括:
- 用户偏好;
- 项目经验;
- 历史问题;
- 团队约定;
- 常见错误;
- 过去任务的总结。
Memory 与 Project Knowledge 不同。
Project Knowledge 是显式文档。
Memory 通常来自 Agent 运行过程中的积累。
Memory 的风险更高,因为它可能:
- 过期;
- 错误;
- 与当前任务无关;
- 泄露其他用户或项目的信息。
所以 Memory 必须有作用域、来源、置信度和过期策略。
3.12 Environment:环境上下文
Environment 包括:
- 当前目录;
- 操作系统;
- 语言版本;
- 依赖版本;
- 环境变量;
- 容器信息;
- CI 环境;
- 当前权限;
- 可用工具。
环境上下文经常被忽略。
但很多问题其实来自环境差异:
本地 Java 17,CI Java 21
本地依赖缓存存在,CI 不存在
本地测试通过,容器缺少环境变量
因此研发 Agent 应该能读取并理解关键环境信息。
3.13 Scratchpad:临时工作区
Scratchpad 是 Agent 的临时工作区。
它可以保存:
- 临时假设;
- 中间推理;
- 搜索线索;
- 待验证想法;
- 草稿计划。
Scratchpad 不一定应该长期保存,也不一定应该暴露给其他 Agent。
它的价值是帮助当前 Agent 组织思路。
它的风险是:如果把未验证假设当成事实写入长期 Memory,会造成长期污染。
3.14 Examples:示例
示例可以帮助模型理解输出格式和行为模式。
例如:
输入一个 CI 失败日志,输出失败原因报告。
示例特别适合:
- 固定输出格式;
- 复杂业务规则;
- 领域特定写法;
- Tool 使用模式。
但示例也会占用上下文。
因此示例应该少而精,并且与当前任务高度相关。
3.15 User Profile:用户画像
User Profile 包括:
- 用户角色;
- 技术水平;
- 偏好语言;
- 常用项目;
- 输出偏好;
- 权限范围。
它能帮助 Agent 调整回答方式。
例如面对架构师,可以输出架构权衡;面对新人,可以输出更详细解释。
但用户画像必须严格受隐私和权限控制。
3.16 Policy / Permission:策略与权限
在企业 Agent 中,权限上下文非常重要。
它告诉 Agent:
- 当前用户能访问哪些项目;
- 当前任务能调用哪些工具;
- 是否允许写文件;
- 是否允许访问生产数据;
- 是否需要人工审批;
- 哪些内容必须脱敏。
Policy 不只是后端校验,也应该进入上下文,让 Agent 在规划时知道边界。
但最终权限判断不能只靠模型遵守,必须由程序强制执行。
4. Context 组成的工程分类
可以按三个维度对上下文分类。
4.1 按来源分类
| 来源 | 示例 |
|---|---|
| 用户输入 | 任务描述、补充说明 |
| 系统配置 | System Prompt、Policy |
| 项目资产 | README、代码、Git |
| 工具观察 | 命令输出、API 返回 |
| 外部知识 | RAG 文档、Web、Wiki |
| 历史经验 | Memory、历史任务 |
4.2 按生命周期分类
| 生命周期 | 示例 |
|---|---|
| 单次调用 | 当前工具结果片段 |
| 当前步骤 | 当前错误分析 |
| 当前任务 | User Task、Task State |
| 当前会话 | 对话摘要 |
| 项目长期 | AGENTS.md、项目规范 |
| 用户长期 | 用户偏好 |
| 组织长期 | 企业规范、通用 Skill |
4.3 按敏感度分类
| 敏感度 | 示例 | 处理方式 |
|---|---|---|
| 公开 | 开源文档 | 可直接使用 |
| 内部 | 项目代码、内部 Wiki | 需要项目权限 |
| 机密 | 生产日志、客户数据 | 脱敏、审批 |
| 高危 | 凭证、密钥 | 不应进入模型 |
这个分类对企业落地非常重要。
因为 Context Engineering 不只是性能问题,也是安全问题。
5. 架构图 / 流程图
5.1 Context 组成全景
5.2 Context 生命周期
5.3 企业研发 Agent 的上下文来源
6. Java / Spring Boot 落地方案
6.1 ContextMetadata
上下文必须带元数据:
public record ContextMetadata(
String source,
String sourceUri,
Instant createdAt,
Instant expiresAt,
double confidence,
Sensitivity sensitivity,
Set<String> permissions,
boolean allowMemoryWrite
) {}
敏感级别:
public enum Sensitivity {
PUBLIC,
INTERNAL,
CONFIDENTIAL,
SECRET
}
6.2 ContextChunk
public record ContextChunk(
String id,
ContextType type,
String title,
String content,
int priority,
int tokenEstimate,
ContextMetadata metadata
) {}
这比简单字符串更适合工程治理。
因为系统可以根据 type、priority、sensitivity、expiresAt 做选择、压缩和权限过滤。
6.3 ContextProvider
public interface ContextProvider {
String name();
boolean supports(ContextRequest request);
List<ContextChunk> provide(ContextRequest request);
}
示例:
@Component
public class ProjectKnowledgeProvider implements ContextProvider {
@Override
public String name() {
return "project-knowledge";
}
@Override
public boolean supports(ContextRequest request) {
return request.repositoryId() != null;
}
@Override
public List<ContextChunk> provide(ContextRequest request) {
// 读取 README.md、AGENTS.md、构建文件摘要
return List.of();
}
}
6.4 ContextType
public enum ContextType {
SYSTEM_PROMPT,
USER_TASK,
CONVERSATION_SUMMARY,
TASK_STATE,
PROJECT_KNOWLEDGE,
RETRIEVED_DOCUMENT,
CODE_SNIPPET,
GIT_DIFF,
TOOL_DEFINITION,
TOOL_RESULT,
MEMORY,
ENVIRONMENT,
SCRATCHPAD,
EXAMPLE,
USER_PROFILE,
POLICY
}
6.5 ContextRenderer
最终上下文需要渲染成模型输入:
public interface ContextRenderer {
List<LlmMessage> render(ContextWindow window);
}
渲染时要注意顺序。
推荐结构:
1. System / Policy
2. User Task
3. Task State
4. Project Knowledge
5. Relevant Code / Git
6. Tool Observations
7. Memory
8. Output Requirements
顺序本身就是一种引导。
7. 常见误区
7.1 把所有上下文当成字符串
字符串无法表达来源、权限、生命周期和优先级。
工程系统应使用结构化 ContextChunk。
7.2 忽略工具定义也是上下文
工具定义会占用 token。
工具越多,模型越难选择。
因此工具也需要选择和裁剪。
7.3 把临时假设写入长期 Memory
Agent 在探索过程中会产生很多假设。
只有被验证的事实才适合进入长期记忆。
7.4 不区分项目知识和记忆
项目知识应来自显式文档和代码仓库。
记忆来自历史任务和用户偏好。
二者治理方式不同。
7.5 忽略权限上下文
企业 Agent 不能只考虑“相关性”。
还必须考虑:
用户是否有权看到这些内容?
当前任务是否允许调用这些工具?
是否需要脱敏?
8. 贯穿案例:CI 失败分析 Agent
在 CI 失败分析案例中,各类 Context 可以明确拆分。
| Context 组成 | 案例内容 | 作用 |
|---|---|---|
| User Task | 分析 build #4312,且只读 | 定义目标和边界 |
| Policy | 不允许修改文件 | 限制 Agent 行为 |
| Project Knowledge | AGENTS.md 中的测试命令和项目规则 | 提供项目约定 |
| Tool Result | CI 失败摘要 | 提供最新证据 |
| Git Context | PR #882 的 Diff | 定位最近变更 |
| Code Context | OrderStateMachine、OrderStatus、OrderServiceTest |
支持根因判断 |
| Memory | 历史类似状态机失败 | 提供经验 |
| Environment | Maven、Java、CI job 信息 | 判断环境问题 |
这个拆分能避免把所有信息都当成普通字符串。
例如,CI 失败摘要是当前任务的高优先级证据;AGENTS.md 是项目长期知识;历史类似失败是 Project Memory;完整日志只是外部资源引用,不应默认全部进入上下文。
因此,CI 失败分析 Agent 的 ContextWindow 应由多个结构化 ContextChunk 组成,而不是一段拼接后的长 Prompt。
9. 本章小结
本章把 Context 拆成了多个组成部分。
核心结论:
- Context 不是一段字符串,而是一组结构化信息资产。
- 不同上下文有不同来源、生命周期、风险和价值。
- Tool Definitions 和 Tool Results 也属于 Context。
- Memory、Project Knowledge、Conversation、Task State 必须区分。
- 企业 Agent 必须为上下文增加权限、敏感度、来源和过期信息。
一句话总结:
只有先把 Context 拆清楚,后续才谈得上选择、压缩、隔离和治理。
10. 实践任务
任务 1:设计上下文清单
为“自动生成单元测试 Agent”列出需要的上下文:
- 用户任务;
- 项目测试规范;
- 被测类;
- 相似测试;
- 构建命令;
- 已有测试失败结果;
- 禁止事项。
并标注每类上下文的生命周期和敏感度。
任务 2:实现 ContextChunk
在 examples/java-agent-runtime 中设计:
ContextChunk
ContextType
ContextMetadata
Sensitivity
要求支持权限和过期时间。
任务 3:写一个 AGENTS.md ContextProvider
实现一个 Provider,读取项目根目录下的 AGENTS.md,并生成 PROJECT_KNOWLEDGE 类型的 ContextChunk。
任务 4:上下文风险评审
选择一个企业场景,回答:
哪些上下文不能进入模型?
哪些上下文必须脱敏?
哪些上下文不能写入长期记忆?