· 6 分钟 · 智能体

避免智能体循环耗尽你的 API 预算

避免智能体循环耗尽你的 API 预算

我会直接起草这篇文章,加入具体的工程策略、对比表和实用示例。内容会围绕真实的循环行为和成本控制展开,并确保以可执行的建议收尾。## 耗尽 API 预算的最快方式

我曾亲眼看到,一个“简单”的编程智能体在不到两分钟内,把一条 400 词元的用户请求变成了账单上一笔 28,000 词元的费用。

这种模式熟悉得令人痛心:智能体调用搜索工具,总结结果,重新阅读自己的总结,换个略有不同的说法再次调用同一个工具,然后不断扩充上下文,直到每一轮都带上整段对话、半个代码仓库,以及一连串失败的计划。模型本身没有问题,出问题的是循环设计。

这就是智能体系统的核心问题:成本并不随用户提示词增长,而是随 循环行为增长。一个糟糕的循环就能成倍增加:

  • 持续膨胀的上下文带来的输入词元,
  • 冗长“思考”带来的输出词元,
  • 重复调用带来的工具费用,
  • 以及串行重试带来的延迟。

如果你正在构建生产环境中的智能体,真正的任务并不是“让模型更聪明”,而是“让循环成本更低、流程更短、更难失控”。

为什么智能体循环会让成本暴涨

普通聊天的一轮交互只有一次输入和一次输出。智能体循环则包含如下序列:

  1. 用户提出问题,
  2. 模型制定计划,
  3. 模型调用工具,
  4. 工具返回数据,
  5. 模型读取数据,
  6. 模型决定下一步,
  7. 重复上述过程。

每次迭代都会为下一次迭代的提示词增加新的词元。如果你不断追加完整的工具输出和先前的思考内容,提示词就会大致呈阶梯式增长。

在实际运行中,这会产生三种成本放大因素:

  • 上下文累积:每一步都带上此前各步的内容。
  • 工具调用冗余:使用略有不同的参数反复调用同一个工具。
  • 失控的重试:模型已经做得足够多,却仍不断“再试一种办法”。

一个常见的坑是,词元浪费通常很难察觉,直到你查看每次运行的追踪记录。最终答案可能很简洁,但隐藏的执行链可能比用户可见的输出大 10x。

一个简单的成本模型

如果一个循环运行 N 步,每步包含:

  • C 词元的携带上下文,
  • I 词元的工具输出重新注入上下文,
  • O 词元的模型输出,

那么总量可粗略估算为:

total_tokens ≈ N * (C + I + O)

这个估算偏乐观,因为 C 通常会随着循环继续而增长。在真实的智能体中,后面的轮次往往成本最高。

真正重要的控制手段

你可以从几个地方着手施加控制。关键在于将它们结合起来。

控制手段减少的内容最佳适用场景权衡
步数上限失控循环任何使用工具的智能体处理困难任务时可能提前停止
上下文裁剪输入词元长程任务可能丢失有用的历史信息
成本更低的子智能体常规工作的费用搜索、提取、分类协调开销
工具结果缓存重复的工具费用稳定的数据源、重复查询数据过时与缓存失效
费用可观测性意外的高额账单生产系统需要埋点工作
循环停止条件无限或近乎无限的循环不可靠的智能体必须定义“足够好”的标准

最有效的生产环境配置会使用所有这些手段。

1) 严格限制步数

这是最简单、也最被低估的解决办法:如果你不设定硬性上限,就等于相信模型会自行停止。

我通常从以下设置开始:

  • 3–5 步,用于范围较小的任务,
  • 6–8 步,用于中等复杂度的任务,
  • 只有在理由充分时才增加步数。

合理的上限不仅是安全阀,还会促使任务得到更好的拆解。如果智能体只能行动四次,它往往会:

  • 更早请求使用正确的工具,
  • 避免探索性的绕路,
  • 并更快收敛到可用的答案。

循环控制器示例

MAX_STEPS = 5

for step in range(MAX_STEPS):
    action = model.next_action(state)

    if action.type == "final":
        return action.final_answer

    observation = run_tool(action.tool_name, action.arguments)
    state = update_state(state, action, observation)

raise RuntimeError("Agent stopped after step cap")

一个常见的坑:步数上限应适用于 整个计划,而不是分别对每个子程序独立生效。否则,你只是把失控增长从顶层智能体转移到了嵌套的辅助程序中。

2) 每轮都裁剪上下文

大多数智能体携带了过多的历史信息。它们不需要此前每一次工具响应,尤其是当响应很长、却只有一个细节有用时。

在实践中,分层记忆策略给我带来了最好的效果:

  • 原样保留:用户请求、当前目标、最终决策、尚未解决的约束。
  • 总结:先前的推理、分支尝试、冗长的工具输出。
  • 丢弃:走入死胡同的假设、重复的工具结果、旧的中间草稿。

应保留哪些内容

紧凑的状态对象比直接回放原始对话记录更有效:

{
  "goal": "Find the failing endpoint and propose a fix",
  "constraints": [
    "No schema changes",
    "Must preserve backward compatibility"
  ],
  "known_facts": [
    "500s started after deploy 1842",
    "Only /v1/search is affected"
  ],
  "open_questions": [
    "Is the new cache layer returning stale payloads?"
  ],
  "step": 3
}

这比向模型输入 20 轮“边想边说”的内容便宜得多。

实用的裁剪模式

使用三个缓冲区:

  • working_set:当前轮次、工具输出、当前执行中的计划。
  • summary_memory:压缩后的历史记录。
  • archive:审计时可能需要、但不必放入提示词上下文的内容。

智能体只能看到 working_set + summary_memory。当 working_set 变得过大时,将其总结到 summary_memory.

这里的陷阱是摘要漂移。如果摘要生成过程会丢失信息,就可能抹去约束,导致智能体重蹈覆辙。我倾向于将结构化事实与自由形式的摘要分开保留,这样模型就不必从文字描述中推断这些事实。

3) 用更便宜的子智能体处理简单工作

并非每一步都值得动用你最好的模型。

如果任务是:

  • 从 JSON 中提取字段,
  • 对工单进行分类,
  • 检查文件是否发生变化,
  • 或生成简短的搜索查询,

那么更小、更便宜的模型通常就够用了。将昂贵的模型留给规划、综合分析和处理歧义。

常见分工

  • 主智能体:对任务进行推理,决定何时停止。
  • 子智能体:执行搜索、提取或规范化等范围明确的工作。
  • 验证器:检查输出的格式、完整性或明显错误。

一种实用架构:

User
  -> Orchestrator (strong model)
      -> Search agent (cheap model)
      -> Extract agent (cheap model)
      -> Final synthesis (strong model)

为什么这样有效

主模型可以专注于控制流程,而不是亲自处理所有事情。子智能体则可以通过提示词限定任务范围,并严格限制 token 用量。

搜索子智能体的提示词示例:

Return only:
1. the best search query,
2. 3 likely source categories,
3. no explanation.

这种范围明确的约定能让输出保持简短且可预测。

如果你的平台提供价格实惠的多模型访问能力,这正是它发挥价值的地方:用一个强大的模型负责协调,用一个更便宜的模型处理重复性子任务,没有理由把高价 token 消耗在常规解析上。

4) 认真做好工具结果缓存

智能体循环经常为同一份信息重复付费:

  • 重复读取文件,
  • 重复查询数据库,
  • 重复进行网页搜索,
  • 重复调用嵌入或检索功能。

缓存工具输出是你能做出的投资回报率最高的改进之一。

使用语义键缓存,而不是原始文本

只要措辞稍有变化,原始提示词缓存就无法命中。更好的缓存键应基于规范化后的意图:

def cache_key(tool_name, args):
    normalized = normalize_args(args)
    return f"{tool_name}:{hash(normalized)}"

缓存条目示例:

{
  "tool": "repo_search",
  "query": "auth middleware rate limit bug",
  "result": {
    "files": ["src/middleware/auth.py", "src/api/routes.py"],
    "ts": 1710001123
  }
}

缓存在哪些场景下最有帮助

  • 代码仓库检查智能体,
  • 文档搜索,
  • 内容稳定的知识库,
  • 确定性转换,
  • 多轮工作流中的重复检索。

缓存可能带来问题的场景

  • 快速变化的数据,
  • 因用户而异的结果,
  • 任何对数据时效性有严格要求的内容。

常见的陷阱是缓存了工具输出,却没有向智能体说明数据的新鲜程度。如果模型拿到的是缓存数据,却以为是实时数据,就可能做出错误决策。应在工具元数据中包含时间戳或有效期。

5) 在仪表板上展示支出

如果你只跟踪延迟和成功率,就会漏掉真正的失败模式:代价高昂的成功。

你需要在每次运行中观测以下指标:

  • 输入 token 数,
  • 输出 token 数,
  • 工具调用次数,
  • 工具延迟,
  • 总支出,
  • 步骤数,
  • 停止原因。

最简事件结构

{
  "run_id": "r_8f2c",
  "step": 4,
  "model": "gpt-5.5",
  "input_tokens": 1842,
  "output_tokens": 221,
  "tool_name": "repo_search",
  "tool_latency_ms": 312,
  "estimated_cost_usd": 0.087,
  "stop_reason": "step_cap"
}

实际运行中应关注什么

  • 每个成功任务的 token 用量:最直接的成本指标。
  • 每一步的 token 用量:用于发现提示词膨胀。
  • 重复的工具调用:通常意味着规划存在缺陷。
  • 停止原因分布:如果大量运行因达到上限而结束,说明智能体的规划不够充分。

按任务类型统计成本是最有用的图表之一。有些类别自然会比较昂贵,这没有问题。你要找出的是那些本应便宜、实际却不便宜的任务。

6) 尽早终止失控循环

循环的停止理由不应只有“我已达到步骤上限”。

有用的停止条件包括:

  • 模型已生成最终答案,
  • 同一工具调用不断重复,却没有带来新信息,
  • 置信度或效用的增量低于阈值,
  • 验证器判定任务已解决,
  • 计划已停滞。

循环中断器示例

seen_calls = set()

for step in range(MAX_STEPS):
    action = model.next_action(state)
    signature = (action.tool_name, canonicalize(action.arguments))

    if signature in seen_calls:
        return fail("repeated tool call with no new information")
    seen_calls.add(signature)

    observation = run_tool(action.tool_name, action.arguments)
    state = update_state(state, action, observation)

    if verifier_says_done(state):
        return finalize(state)

这可以发现一种非常常见的失败模式:模型“想要帮上忙”,却只是在改写同一个查询。

另一项有用的防护措施:效用门控

如果某一步没有让状态发生足够大的变化,就不要继续。判断依据可以很简单,例如:

  • 没有找到新文件,
  • 没有提取出新实体,
  • 答案的置信度没有提升,
  • 没有加入新的证据。

具体指标取决于任务,但必须以某种方式衡量边际收益。

调优到位后,实际会发生什么

收紧循环之后,智能体通常会在一些起初容易被忽略的方面变得更好:

  • 它会少说废话,决策更果断。
  • 它会更早请求合适的工具。
  • 它会减少“我还在调查”的循环。
  • 它的成本会更低, 和 速度也会更快,因为轮次减少意味着上下文更少、延迟更低。

需要注意的权衡是,硬性上限和激进裁剪可能让系统在处理真正困难的任务时变得脆弱。因此,不要把智能体实际上因超时而终止的问题说成已经“解决”。应在日志和面向用户的消息中如实说明。

我的经验法则是:优化目标应是 有边界的胜任能力 而非无休止的探索。

实用的生产环境配置

如果明天就要上线一个智能体,我会从以下配置开始:

  • MAX_STEPS = 5
  • 使用结构化状态,而非重放完整对话记录
  • 每轮结束后生成摘要记忆
  • 用低成本子智能体执行搜索和提取
  • 为工具结果建立缓存,并附带数据新鲜度元数据
  • 记录每次运行的 token 用量和成本
  • 重复调用检测器
  • 明确的停止原因

这套配置并不亮眼,但它能防止账单增长速度超过产品增长速度。

实践要点

  • 首先限制步骤数;这是防止循环失控的最低成本防护措施。
  • 大幅精简上下文,并将结构化状态与摘要分开保存。
  • 让更便宜的子智能体处理范围明确的任务,避免处处消耗高价 token。
  • 缓存可重复使用的工具输出,但要明确呈现数据新鲜度,避免模型盲目信任过时数据。
  • 按每次运行、每个步骤和每次工具调用记录支出,让成本恶化立即显现。
  • 在出现重复、进展停滞或确认完成时停止循环,而不是只在资源耗尽时停止。
  • 围绕有边界的胜任能力进行设计:足够好、快速且可预测,始终胜过聪明但昂贵。
A

本博客由 AI Prime Tech 发布。文章借助 AI 辅助起草;在据此采取行动前,请对照官方文档核实模型 ID、价格和限额等特定版本的细节。

以更低价格接入 Claude API

一个 API 密钥即可使用 Claude Opus 5.5、Sonnet 5、Haiku 4.5 和 Fable 5.1,以及 GPT-6 个模型。按量付费,无需订阅。

获取 API 密钥 →
AI Prime Tech 是独立的第三方 API 网关。Claude™ 和 Anthropic® 是 Anthropic, PBC 的商标。此处不暗示任何关联或背书。