如何进行 AI Agent 的上下文工程管理?
作者:🧑🚀 deadmau5v 发布于 2025/10/17
构建生产级 AI Agent,最大的挑战不是模型不够强,而是上下文越跑越大,最后崩了。
虽然现在主流的大模型已经达到 2M 的上下文长度,但是基于成本和性能考虑,我们仍然需要对上下文进行管理。
AI 会不断调用工具、产生中间结果,这些都会占用 Token。
举个例子,一个研究型 Agent 可能会:
- 搜 20 次 Google
- 读 10 个网页
- 分析 5 份文档
这很容易把 Context Window 撑爆,导致模型 上下文腐烂 :忘了指令、开始胡说八道。
什么会占用上下文
在优化之前,先搞清楚哪些东西会占用 Token。
思考过程
Claude、DeepSeek 等模型支持 Extended Thinking,模型会在回复前先进行内部推理。这部分思考内容会显示给用户,但不会进入后续对话的上下文。
计费上,Thinking Tokens 按输出价格收费。比如 Claude Sonnet 的 Thinking Tokens 是 $15/M,比普通输出贵一些。OpenAI 的 o1/o3 系列也有类似机制。
对于 Agent 来说,Thinking Tokens 不会撑爆上下文,但会增加成本和延迟。如果任务不需要复杂推理,可以关掉 Extended Thinking。
图片 Token 计算
图片会被转换成 Token,不同模型的计算方式不同。
Claude 按图片尺寸计算:大约每 750 像素对应 1 Token。一张 1000x1000 的图片大约消耗 1334 Tokens。缩小图片或降低分辨率能显著减少 Token 消耗。
OpenAI 按 tile 计算:先把图片缩放到合适尺寸,然后切成 512x512 的小块,每个 tile 消耗 170 Tokens,再加 85 Tokens 的基础消耗。
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。后果是 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,接口定义要清晰,子任务要独立。
数据格式:Plain Text vs Structured
1. 优先用“行”格式
JSON 费 Token,而且不好做正则匹配。推荐用基于行的日志格式:
# ✅ 推荐
logs = """
2024-01-15 10:00:00 | 开始搜索
2024-01-15 10:00:05 | 找到10个结果
"""
2. 结构化输出要配合 Schema
必须输出 JSON 时,一定要给 Schema,不然模型容易乱写。
3. 大数据别进 Context
千万别把 DataFrame 转成字符串塞进去。给个 df.head() 或者统计信息就行了。
最好的方法是语义化返回,比如 return f"找到 {len(df)} 条数据,最大值为 {df['value'].max()}"
架构建议
- 工具别太多:10-20 个是极限。多用 Code Interpreter 写代码解决问题,别全靠预定义工具。
- 缓存:System Prompt 和静态 Context 放前面,利用 KV Cache。
- 定期压缩:Token 到了阈值(比如 100k),就触发“总结-压缩”,只保留最近 N 轮对话。
小结
上下文工程说白了就是 LLM 的内存管理。
别整太复杂的架构,保持 Context 干净才是最重要的。简单粗暴的工程实现,往往比花哨的系统更有效。
评论