如何进行 AI Agent 的上下文工程管理?
作者:🧑🚀 deadmau5v 发布于
构建生产级 AI Agent,最大的挑战不是模型不够强,而是上下文越跑越大,最后崩了。
虽然现在主流的大模型已经达到 1M 的上下文长度(Gemini、Grok 支持 2M),但是基于成本和性能考虑,我们仍然需要对上下文进行管理。
AI 会不断调用工具、产生中间结果,这些都会占用 Token。
举个例子,一个研究型 Agent 可能会:
- 搜 20 次 Google
- 读 10 个网页
- 分析 5 份文档
这很容易把 Context Window 撑爆,导致模型 上下文腐烂 :忘了指令、开始胡说八道。
什么会占用上下文
在优化之前,先搞清楚哪些东西会占用 Token。
思考过程
Claude、DeepSeek 等模型支持 Extended Thinking,模型会在回复前先进行内部推理。这部分思考内容对用户不可见,但会占用上下文窗口——Anthropic 文档明确说明 thinking tokens 计入 context window 上限。2026 年 Claude Opus 4 / Sonnet 4 依然如此。只有 API 层面可以选择不将 thinking 块传回后续请求,但默认行为是占用的。
计费上,Thinking Tokens 按输出价格收费。比如 Claude Sonnet 的 Thinking Tokens 是 $15/M,比普通输出贵一些。OpenAI 的 o1/o3 系列也有类似机制。
对于 Agent 来说,Thinking Tokens 既占用上下文又增加成本和延迟。长链推理场景下 thinking tokens 可能吃掉大量上下文预算,需要特别注意。如果任务不需要复杂推理,可以关掉 Extended Thinking。
图片 Token 计算
图片会被转换成 Token,不同模型的计算方式不同,且随版本迭代持续调整。
Claude 较早版本按图片尺寸计算(约每 750 像素对应 1 Token),Claude 4 系列的计算方式已有变化,具体公式应以Anthropic 官方文档为准。
OpenAI 较早版本按 tile 计算(512x512 小块,每 tile 170 Tokens + 85 基础消耗),GPT-5 系列的计算方式已有更新,具体以OpenAI 官方文档为准。
⚠️ 图片 Token 计算方式随模型迭代频繁变化,切勿依赖过时公式,务必以官方文档为准。
Agent 处理大量图片时,建议先做预处理:缩小尺寸、裁剪无关区域、转成低分辨率版本。
工具调用
工具调用的参数和返回值都会进入上下文。这一点很多人忽略。
# 这个调用会产生多少 Token?
response = client.messages.create(
tools=[{
"name": "search",
"description": "搜索网页", # 这段描述每次请求都会发送
"input_schema": {...} # Schema 定义也会占用 Token
}],
messages=[...]
)
工具定义(name、description、input_schema)在每次请求时都会发送,占用 Input Tokens。工具调用的参数、返回结果也会进入对话历史。
优化建议:工具描述写简洁点,别写小作文。返回结果做摘要压缩,别把原始数据全塞进去。
上下文占用大户:搜索结果
以搜索为例,搜出来的内容一大堆,怎么塞给模型?
方案一:全部保留(错误)
直接把搜索结果扔进去:
# ❌ 错误:直接存入
search_results = search_tool.search(query)
messages.append({
"role": "tool",
"content": search_results # 10000+ tokens,瞬间爆炸
})
方案二:激进压缩(错误)
只存标题,内容全丢了:
# ❌ 错误:过度压缩
compressed = [{"title": r.title, "url": r.url} for r in results]
messages.append({
"role": "tool",
"content": json.dumps(compressed) # 模型根本不知道网页里有啥
})
方案三:混合策略(推荐)
核心思路:结果落盘,Context里只放摘要。
错误做法
全部保留或激进压缩
# 错误示例:要么浪费Token,要么丢失信息
# 1. 全部保留,context爆炸
messages.append({"content": search_results})
# 2. 只保留标题,丢失内容
compressed = [{"title": r.title, "url": r.url} for r in results]
messages.append({
"role": "tool",
"content": json.dumps(compressed)
})
推荐做法
混合策略:完整结果写文件,摘要写进上下文
# ✅ 搜索结果写入文件,上下文只放摘要
file_path = f"search_{timestamp}.json"
save_to_file(search_results, file_path)
summary = summarize(search_results, max_tokens=500)
messages.append({
"role": "tool",
"content": f"搜索完成,结果已保存至 {file_path}\n摘要:{summary}"
})
这样,Agent 既能掌握大致内容,也能在需要时查阅详情(从文件读取),既节省Token,又保留了复杂信息。
智能体即工具
当工具越来越多,主 Agent 的上下文会被工具定义撑爆。换个思路:把一组相关工具封装成子 Agent,主 Agent 只需要调用这个子 Agent 就行。
class SearchAgent:
"""封装搜索相关的所有工具"""
def __init__(self):
self.tools = [google_search, bing_search, arxiv_search]
def execute(self, query):
# 内部调用多个工具,返回精炼结果
results = self.search_and_summarize(query)
return {"summary": results, "source_count": len(results)}
主 Agent 不需要知道搜索的实现细节,只需要拿到摘要结果。工具定义从十几个缩减到几个,上下文立刻清爽。
多 Agent 编排模式
“智能体即工具”不只是主 Agent 调子 Agent 这一种模式,常见的编排模式有:
- Orchestrator-Worker:编排器分配任务给多个专职 Worker,Worker 之间互不通信,结果汇总回编排器。适合任务明确可拆分的场景
- Map-Reduce:将大任务拆成多个独立子任务并行处理(Map),再合并结果(Reduce)。适合需要批量处理的场景,如同时分析多份文档
- Handoff:一个 Agent 完成自己的部分后,将控制权交给下一个 Agent。OpenAI Agents SDK 即采用此模式。适合流水线式的工作流
选择哪种模式取决于任务特性:任务独立用 Orchestrator-Worker,可并行用 Map-Reduce,有前后依赖用 Handoff。
智能体间通信
多 Agent 协作,最难的是信息同步。
错误做法:把父 Agent 的聊天记录一股脑传给子 Agent。后果是 Token 消耗翻倍,子 Agent 还会被无关信息干扰。
正确做法:只传子 Agent 需要的信息。
def delegate_task(task_description, required_files=[]):
# 最小上下文:任务描述 + 必要文件 + 输出格式
context = {
"task": task_description,
"files": required_files,
"output_schema": {...}
}
return sub_agent.execute(context)
三个原则:文件能解决的别塞 Context,接口定义要清晰,子任务要独立。
RAG 与长期记忆
前面讨论的都是单次会话内的上下文管理。但在 2025-2026 年的 Agent 架构中,检索增强生成(RAG)和长期记忆系统才是解决上下文瓶颈的核心手段,比手动”写文件再读取”更成熟。
为什么需要 RAG
上下文窗口再大也有上限。Agent 运行时间一长,不可能把所有历史信息都塞进 Context。RAG 的思路是:上下文按需加载,而不是全量保留。
# ❌ 错误:把所有相关文档塞进上下文
context = load_all_documents() # 500k+ tokens,直接爆炸
# ✅ 正确:按需检索,只加载相关片段
relevant_chunks = vector_db.search(query, top_k=5)
context = "\n".join(chunk.text for chunk in relevant_chunks) # ~2k tokens
长期记忆系统
RAG 解决了知识检索的问题,但 Agent 还需要跨会话的记忆。2025-2026 年主流做法是分层记忆:
- 短期工作记忆:当前对话上下文,随会话结束而消失
- 长期记忆:持久化存储,跨会话保留。包括用户偏好、历史决策、学到的规则等
- 情景记忆:特定事件的详细记录,按需检索
实现上,长期记忆通常用向量数据库 + 结构化存储(如 SQLite)组合,Agent 在关键节点自动提取和存储重要信息,后续会话按需检索加载。
上下文路由
更进一步:根据当前任务动态决定加载哪些上下文。不是所有任务都需要完整的 RAG 结果,一个调度层根据任务意图选择加载哪些知识源和工具集,避免无关信息污染上下文。
数据格式:Plain Text vs Structured
1. 按场景选择格式
简单日志用行格式,节省 Token;需要模型解析和操作的数据用 JSON,可靠性更佳。2025-2026 年主流模型对结构化输出(structured output / JSON mode)的支持已经非常成熟,JSON 的可靠性收益通常大于 Token 节省的收益。
# ✅ 简单日志:行格式节省 Token
logs = """
2024-01-15 10:00:00 | 开始搜索
2024-01-15 10:00:05 | 找到10个结果
"""
# ✅ 需要模型解析的数据:用 JSON 确保结构可靠
result = {"query": "AI Agent", "count": 10, "top_results": [...]}
2. 结构化输出要配合 Schema
必须输出 JSON 时,一定要给 Schema,不然模型容易乱写。
3. 大数据别进 Context
千万别把 DataFrame 转成字符串塞进去。给个 df.head() 或者统计信息就行了。
最好的方法是语义化返回,比如 return f"找到 {len(df)} 条数据,最大值为 {df['value'].max()}"
架构建议
- 关注工具描述的总量:瓶颈不是工具数量,而是工具描述的总 Token 数和工具之间的语义冲突。10 个描述冗长的工具可能比 50 个精简工具更撑上下文。Claude 支持 128+ tools,关键是每个工具的描述精简、语义明确、互不重叠。
- 缓存:System Prompt 和静态 Context 放前面,利用 KV Cache。
- 分层上下文管理:不要只靠”到了阈值就总结压缩”。简单的总结压缩容易丢失细节,效果不稳定。更成熟的做法是分层管理:
- 滑动窗口 + 重要信息提取:不是简单截断,而是保留最近 N 轮对话的同时提取关键信息(决策、状态、待办)存入长期记忆
- 分层上下文:system prompt(不变)→ 长期记忆(低频更新)→ 短期工作记忆(高频更新)→ 当前任务(实时变化)
- 上下文路由:根据任务动态决定加载哪些上下文,避免无关信息占用空间
小结
上下文工程说白了就是 LLM 的内存管理。
别整太复杂的架构,保持 Context 干净才是最重要的。简单粗暴的工程实现,往往比花哨的系统更有效。