第 7 / 15 章 约 8 分钟
Context Engineering
组织有效上下文,控制长度并保留任务状态。
内容同步:2026年10月25日 · 文档版本 6
这个模块在 Agent 里的位置
Context 是模型的短期工作记忆。它决定:
- 模型看到什么。
- 哪些信息被保留为引用。
- 上下文什么时候增长过快。
- 压缩后保留什么。
- 任务如何继续,而不是从头重做。
工程上,Context 的目标不是“塞得越多越好”,而是让模型在有限注意力里看到正确的信息。
先记住几个概念
| 概念 | 含义 | 为什么重要 |
|---|---|---|
| Working context | 当前 turn 可见消息 | 模型实际看到的内容 |
| Context budget | token、图片、工具输出的上限 | 控制成本和质量 |
| Refs | 稳定 ID 引用 | 让证据可校验、可精查 |
| Compaction | 把旧历史压缩成摘要 | 防止上下文无限增长 |
| Resume prompt | 压缩后的继续指令 | 避免重做已完成步骤 |
| Checkpoint | 可恢复的上下文快照 | 支持断点续跑和审计 |
从一个真实问题开始:上下文越长,结果反而越差
一个 Agent 要完成:
分析 30 天、50 万实体,输出 Top 10 风险实体和证据引用。
前 5 轮执行正常:
- 查询 30 天聚合。
- 锁定 Top 10。
- 补查 3 个证据窗口。
第 6 轮开始,模型突然问:
我需要重新查询 30 天聚合。
排查发现:
- 工具返回了 20 个原始事件窗口。
- 上下文超过 128k。
- 系统触发 compaction。
- 压缩摘要只保留了“已分析风险”,没有保留“已完成聚合查询”。
- 模型看不到已完成步骤,于是重新查。
这不是模型“忘性大”,而是 compaction summary 的契约不完整。
上下文膨胀的连锁反应
工具返回过大
-> 上下文增长
-> token 成本上升
-> 关键约束被稀释
-> 触发 compaction
-> 摘要丢任务状态
-> 模型重复查询
-> 成本继续上升
上下文过长还会带来一个更隐蔽的问题:注意力被稀释。
例如任务要求“每个结论必须引用 evidenceWindowId”,但如果上下文里混入了:
- 50 万行原始数据
- 20 个无关工具输出
- 3 张无关截图
- 旧错误结论
模型很容易把关键约束淹没在噪声里。
工具结果应该先瘦身,再进入上下文
错误做法:
{
"events": [
{ "id": 1, "payload": { "...": "raw" } },
{ "id": 2, "payload": { "...": "raw" } }
]
}
正确做法:
{
"summary": {
"topRiskEntityCount": 10,
"timeRange": "30d"
},
"refs": [
{
"entityId": "entity-9182",
"evidenceWindowIds": ["win-1001", "win-1002"]
}
],
"coverage": {
"complete": true
}
}
给模型的应该是:
- 结论。
- 稳定引用。
- 数据覆盖状态。
- 未解决问题。
不是原始世界。
Context budget 不只管 token
一个常见的错误是只看 token。实际上还应该管:
| 维度 | 控制方式 | 原因 |
|---|---|---|
| Token | 真实 usage + 上下文窗口策略 | 成本和窗口限制 |
| Tool calls | 最大调用次数 | 控制多轮增长 |
| Output bytes | 单次工具输出上限 | 控制单次膨胀 |
| Images | image block 数量和字节数 | 多模态请求容易失败 |
| History blocks | 保留最新关键块 | 避免旧噪声持续影响 |
示例预算:
{
"contextBudget": {
"maxTokens": 128000,
"maxToolCalls": 20,
"maxOutputBytes": 131072,
"maxImageBlocks": 8
}
}
真实 token 用量来自 LLM Gateway,不由工具自行估算。
Compaction summary 必须保留什么
Compaction 不是简单“删历史”。它至少要保留:
- 当前任务目标。
- 已完成步骤。
- 重要引用。
- 未完成问题。
- 用户最新约束。
- 失败和重试状态。
一个可用的 summary 结构:
{
"taskGoal": "输出 Top 10 风险实体",
"completed": ["已查询 30 天聚合", "已锁定 Top 10 实体"],
"keyRefs": ["win-1001", "win-1002", "win-1003"],
"constraints": ["每个结论必须引用证据窗口", "缺数据写入 unresolvedQuestions"],
"pending": ["补查 3 个实体的证据窗口"]
}
对应恢复提示:
从中断处继续当前任务,不要重做已经完成的查询。
这句话的价值在于:
- 明确任务是继续,不是重开。
- 保留已完成步骤。
- 保留关键引用。
- 保留用户约束。
图片和多模态要单独预算
如果任务包含截图、图表或 OCR 结果,不能简单按文本 token 处理。
常见问题:
- 图片数量超过 provider 限制。
- 单张图片过大。
- 历史图片累计过多。
- 旧图片没有被裁剪。
工程做法:
- 工具先返回 imageId。
- 模型需要看图时再读取。
- 每轮限制 image block 数量。
- 上下文里只保留最新关键图。
- 旧图替换成引用文本。
示例:
{
"images": [
{
"imageId": "image-1001",
"reason": "风险峰值窗口"
}
],
"budget": {
"loadedImageCount": 4,
"maxImageBlocks": 8
}
}
Checkpoint 设计
大上下文不应直接存数据库 JSONB 字段。更稳的做法是:
- 数据库存元数据和指针。
- 对象存储存 checkpoint 文件。
- checkpoint immutable。
- 恢复时校验 generation。
create table agent_context_checkpoints (
id bigint primary key,
run_id bigint not null,
generation bigint not null,
checkpoint_type text not null,
object_key text not null,
token_count int not null,
created_at timestamptz not null
);
create index agent_context_checkpoint_idx
on agent_context_checkpoints (run_id, generation, created_at desc);
设计要点:
- checkpoint 不可覆盖。
- 旧 worker 只能追加,不能覆盖新结果。
- token count 用于监控,不用于权威计费。
- 恢复时按 generation 校验。
上下文包示例
{
"runId": "run-9f27",
"contextBudget": {
"maxTokens": 128000,
"usedTokens": 63400,
"imageBlocks": 4
},
"taskState": {
"goal": "输出 Top 10 风险实体",
"completedSteps": ["已查询 30 天聚合", "已锁定 Top 10 实体"],
"pendingSteps": ["补查 3 个实体的证据窗口"]
},
"evidenceRefs": [
{
"windowId": "win-1001",
"reason": "风险峰值"
}
]
}
这份数据能明确回答:
- 任务目标是什么。
- 已经做到哪里。
- 还剩什么。
- 关键引用在哪里。
- 预算用了多少。
常见工程错误
| 错误 | 实际后果 | 正确做法 |
|---|---|---|
| 原始数据进上下文 | 注意力稀释、成本暴涨 | summary + refs |
| 压缩只保留结论 | 任务状态丢失 | 保留 goal / completed / pending |
| 图片无预算 | 请求失败或成本暴涨 | image block budget |
| 观测日志进上下文 | 无关噪声增加 | 观测和上下文分离 |
| checkpoint 可覆盖 | 旧 worker 覆盖新状态 | immutable + generation |
| 只看 token 不看工具输出 | 单次返回仍可失控 | bytes / rows / images |
上线前检查
- 上下文有 token 和多模态预算。
- 工具输出先瘦身。
- 引用 ID 稳定。
- compaction summary 保留任务状态。
- 恢复提示明确“继续,不重做”。
- checkpoint 有版本和 generation。