第五章:上下文工程(Context Engineering)
欢迎来到第五章!在第四章的基础上,本章介绍 Agent 开发中最重要的概念之一:上下文工程(Context Engineering)。
随着 Agent 交互轮次增加,上下文长度会持续增长,最终触及模型上下文窗口限制,导致性能下降或请求失败。本章实现一套完整的上下文管理机制,让 Agent 能在有限窗口内稳定运行。
🎯 你将学到什么
- 上下文管理机制:理解为什么需要上下文工程,以及常见压缩手段。
- 截断策略(Truncate):如何安全删除旧消息并保留对话连续性。
- 卸载策略(Offload):将长内容写入外部存储,在上下文中保留预览与恢复入口。
- 摘要策略(Summarize):使用 LLM 对历史消息进行增量压缩。
- Policy 模式设计:如何设计可扩展的策略接口,支持多策略组合执行。
- 策略协同与调参:如何根据阈值、批大小、保留条数进行工程化调优。
🛠 准备工作
本章启动时会读取新的配置文件(见 ch05/main/main.go):
config.json:应用模型配置(前台模型 + 后台摘要模型)mcp-server.json:MCP 服务配置(可选,读取失败仅打印日志)
可按下面方式准备:
cp config.example.json config.jsonconfig.json 示例:
{
"llm_providers": {
"front_model": {
"base_url": "https://api.openai.com/v1",
"model": "gpt-5.2",
"api_key": "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"context_window": 200000
},
"back_model": {
"base_url": "https://api.openai.com/v1",
"model": "gpt-4o-mini",
"api_key": "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"context_window": 128000
}
}
}mcp-server.json 示例:
{
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"${workspaceFolder}"
]
}
}📖 核心原理解析
1. 为什么需要上下文工程?
大语言模型的上下文窗口(Context Window)是有限的,例如:
- GPT-4o-mini: 128K tokens
- Claude 3.5 Sonnet: 200K tokens
Agent 每轮对话都会消耗 tokens,包括:
- 用户输入
- 模型输出
- 工具调用与工具结果
如果不做管理,多轮对话后会出现:
- 超出窗口限制:请求被拒绝
- 成本增加:长上下文导致更高 API 费用
- 性能下降:模型在长上下文中注意力分散,响应质量下降
2. 三种上下文管理策略
2.1 截断策略(Truncate)
原理:删除较早历史消息,保留最近对话。
实现要点:
- 触发条件:
contextUsage > UsageThreshold - 保留最近
KeepRecentMessages条消息作为“不可裁剪区” - 在可裁剪区中寻找最后一条 user 消息作为截断边界,减少语义断裂
- 避免极端情况下把历史全部删空
适用场景:对远期历史依赖较弱、优先保障实时交互的任务。
相关代码:ch05/context/policy_truncate.go
2.2 卸载策略(Offload)
原理:将长消息正文存到外部存储,在上下文中保留短预览与恢复提示。
实现要点:
- 触发条件:
contextUsage > UsageThreshold - 只处理“非最近 N 条消息”中的
tool消息 - 只处理长度超过
PreviewCharLimit的长内容 - 通过
Storage.Store存储原文 - 用“预览 +
load_storage(key="...")提示”替换正文
示例效果:
原始消息(2000 tokens):
"这是一个非常长的工具输出..."
卸载后消息(约200 tokens):
"这是一个非常长的工具输出...(更多内容已卸载,如需查看全文请使用 load_storage(key=\"/offload/...\") 工具)"适用场景:有大量长工具输出,但允许按需回读全文的任务。
相关代码:ch05/context/policy_offload.go、ch05/tool/load_storage.go
2.3 摘要策略(Summarize)
原理:把多条历史消息压缩成一条摘要消息,保留关键信息。
实现要点:
- 触发条件:
contextUsage > UsageThreshold - 仅处理“非最近 N 条消息”
- 分批摘要:每批最多
SummaryBatchSize条,且受Summarizer.GetSummaryInputTokenLimit()限制 - 增量摘要:
runningSummary + batchMessages -> newSummary - 最终用一条摘要消息替换被压缩历史
适用场景:对话较长但只需保留核心事实、决策与结果。
相关代码:ch05/context/policy_summary.go、ch05/context/summary.go
3. Policy 模式设计
本章通过 Policy 接口实现可扩展上下文管理:
type Policy interface {
Name() string
ShouldApply(ctx context.Context, engine *Engine) bool
Apply(ctx context.Context, engine *Engine) (PolicyResult, error)
}优势:
- 可扩展:新增策略无需改
Engine核心流程 - 可组合:多个策略按注册顺序依次执行
- 可控制:每个策略通过
ShouldApply决定是否触发
执行流程:
- 每轮新增消息后,
Engine.CommitTurn调用内部applyPolicies - 按顺序检查每个
Policy.ShouldApply - 返回
true则执行Policy.Apply Apply返回新的消息列表和 token 计数(PolicyResult)
相关代码:ch05/context/policy.go、ch05/context/engine.go
4. Engine 与 Summarizer 协作
当前实现中,摘要能力从策略中解耦为 Summarizer 抽象:
type Summarizer interface {
GetSummaryInputTokenLimit() int
Summarize(ctx context.Context, runningSummary string, messages []shared.OpenAIMessage) (string, error)
}默认实现是 LLMSummarizer,使用 shared.ModelConfig 创建摘要模型客户端。
好处:
- 摘要策略只关心“何时摘要、摘要哪些消息”
- 摘要实现可替换(不同模型、本地模型、规则摘要器)
- 便于单测与演进
相关代码:ch05/context/summary.go、ch05/context/policy_summary.go
5. Token 计数
为了准确判断是否触发策略,需要实时统计上下文 token。
实现方式:
- 使用
tiktoken-go(cl100k_base) - 对消息正文做 token 计数
- 每轮提交和策略执行后重算上下文 token
注意:属于近似估算,和实际 API 计费可能有细微差异。
相关代码:ch05/context/share.go
💻 代码结构速览
Context 包
ch05/context/engine.go:上下文引擎核心(Engine)ch05/context/policy.go:Policy接口与PolicyResultch05/context/policy_truncate.go:截断策略ch05/context/policy_offload.go:卸载策略ch05/context/policy_summary.go:摘要策略ch05/context/summary.go:Summarizer接口与LLMSummarizerch05/context/share.go:token 计数与角色识别工具
其他核心模块
ch05/storage/storage.go:存储接口(卸载策略依赖)ch05/tool/load_storage.go:读取卸载内容的工具ch05/agent.go:集成上下文引擎的 Agentch05/main/main.go:策略装配与启动入口
🚀 动手运行
进入项目根目录,执行:
go run ./ch05/main在 TUI 中可尝试:
1. 测试截断策略
请连续输出数字 1 到 100,每个数字单独输出2. 测试卸载策略
请读取 README.md 并总结每一章节的内容3. 测试摘要策略
我们来进行多轮对话:第一轮,请介绍你自己
第二轮,请列出当前目录的文件
第三轮,请读取 agent.go 文件
...⚠️ 注意事项
- 策略顺序有影响:当前默认顺序是
offload -> summarize -> truncate - 摘要有额外成本:摘要策略会引入额外 LLM 调用
- 存储建议持久化:生产环境建议用 Redis/数据库替代内存存储
- 阈值需按模型调参:不同模型上下文窗口不同,触发阈值需要按实际容量设置
- 策略必须先评测再上线:任何策略都要先通过可复现实验(离线评测或线上对照)证明有效,不能仅凭人工判断把它当作“优化”直接上线
📚 扩展阅读与参考资料
- OpenAI Tokenizer
- 在线工具,可以直观看到文本的 token 切分结果
- tiktoken GitHub
- OpenAI 官方的 Python tokenizer 库
- tiktoken-go GitHub
- 本章使用的 Go 语言 tokenizer 移植版本
- Anthropic Context Window Management
- Anthropic 官方关于上下文管理的最佳实践