如何进行 AI Agent 的上下文工程管理?

作者:🧑‍🚀 deadmau5v 发布于 2025/10/17

构建生产级 AI Agent,最大的挑战不是模型不够强,而是上下文越跑越大,最后崩了

虽然现在主流的大模型已经达到 2M 的上下文长度,但是基于成本和性能考虑,我们仍然需要对上下文进行管理。

AI 会不断调用工具、产生中间结果,这些都会占用 Token。

举个例子,一个研究型 Agent 可能会:

  • 搜 20 次 Google
  • 读 10 个网页
  • 分析 5 份文档

这很容易把 Context Window 撑爆,导致模型 上下文腐烂 ( ContextRot ) :忘了指令、开始胡说八道。

什么会占用上下文

在优化之前,先搞清楚哪些东西会占用 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()}"

架构建议

  1. 工具别太多:10-20 个是极限。多用 Code Interpreter 写代码解决问题,别全靠预定义工具。
  2. 缓存:System Prompt 和静态 Context 放前面,利用 KV Cache。
  3. 定期压缩:Token 到了阈值(比如 100k),就触发“总结-压缩”,只保留最近 N 轮对话。

小结

上下文工程说白了就是 LLM 的内存管理

别整太复杂的架构,保持 Context 干净才是最重要的。简单粗暴的工程实现,往往比花哨的系统更有效。

标签:AIAgent上下文工程LLM架构设计

评论

发表评论

加载评论中...