切换主题

Agent 成本控制:模型路由、工具调用预算、缓存和失败重试怎么设计

Easton editorial illustration: agent rollout and rollback rail
7
预算层级
user、tenant、workflow、task、tool、retry、cache。
4
控制动作
route、degrade、pause、abort。
3
缓存类型
prompt prefix cache、business result cache、tool response cache。
数据来源: 本文工程清单基于阶段一官方文档调研整理;价格、折扣、模型可用性发布前需按官方页面复核。

"OpenAI API Pricing"

凌晨 3 点,后台 report Agent 返回空响应。HTTP 状态码是 200,body 却是空的。重试逻辑只检查状态码,于是继续尝试。每次请求发送 500 token 输入,重试 1500 次,烧掉 75 万 token。第二天早上看到账单,血压拉满。

根本原因不是“模型选贵了”,而是三个缺失:熔断器、预算检查、错误分类。Agent 成本失控有三种典型模式:

重试无上限。 失败模式分类缺失,把空响应当成可重试错误。熔断器缺失,连续失败 1500 次也不停。每次重试都重新发送完整上下文,成本放大 2-5 倍。

上下文膨胀。 长任务跑 6 小时后对话历史膨胀到 80K tokens。没有 checkpoint,失败后从头重跑,每一步都重新付费。

模型滥用。 所有任务都用 frontier 模型,没有路由策略。简单分类任务也用最贵模型,浪费 70% 的 token。

Agent 成本控制不是单点优化,而是多层设计:预算对象、路由策略、缓存命中、重试熔断、成本日志和告警阈值。把这六个工程对象拆成判断表或可执行清单。

预算对象设计:记什么、记在哪、怎么熔断

成本控制先要有预算对象,不能只记总数。没有分层,账单异常时你无法定位是哪个用户、哪个任务、哪个工具烧钱。

七层预算对象

预算对象从粗到细分成七层:

预算层级预算对象预算上限建议告警触发条件
Layer 1user每用户每日/每月上限剩余 < 20% 触发告警
Layer 2tenant每租户独立预算池剩余 < 30% 触发告警
Layer 3workflow每类 workflow 独立预算剩余 < 40% 触发告警
Layer 4task每任务类型独立预算剩余 < 50% 触发告警
Layer 5tool每工具调用独立预算超上限跳过工具
Layer 6retry重试次数上限 + 熔断连续失败 N 次停用工具
Layer 7cache缓存命中率监控命中率 < 预期值告警

每层预算上限依赖业务模型,属于易变配置;分层结构本身更稳定。预算剩余字段要写入成本日志,用于告警和熔断判断。

每层要记的字段

每层预算都要记录以下字段:

字段名用途类型必须记录原因
model定位模型string判断模型路由是否合理
inputTokens输入 token 数integer计算输入成本
outputTokens输出 token 数integer输出成本常常高于输入,需独立记录
cachedTokens缓存命中 token 数integer计算缓存节省
costEstimate本次成本估算float实时成本累加
budgetRemaining预算剩余float熔断判断依据

预算对象设计核心是“按维度记账”,不是“只记 total_cost”。多租户场景要按 tenantId 分摊成本,多工具场景要按 toolName 定位黑盒。

熔断逻辑

预算剩余 < 阈值时触发熔断:

def check_budget_before_retry(budget_remaining, retry_cost_estimate):
    if budget_remaining < retry_cost_estimate:
        return "skip_retry"  # 超预算跳过重试
    if budget_remaining < threshold:  # threshold 如 20%
        return "wait_approval"  # 剩余不足,等待审批
    return "continue"

熔断逻辑要检查预算剩余,不是重试后才发现没预算。每次重试前先估算成本,检查是否超预算,避免“空响应重试 1500 次”烧掉整日预算。

关于上下文工程中哪些内容进入稳定 prefix、哪些只放运行时变量,会在同系列的上下文工程文章中展开。

模型路由策略:不是所有任务都需要最贵模型

一个 ticket triage Agent 的实际分布:70% 任务是简单分类,20% 需要起草回复,只有 10% 要发客户邮件前才升级 frontier 模型。路由策略可节省 40-85% 成本。

模型层路由

模型层路由按任务复杂度分级:

任务层级典型任务推荐模型层级占比成本特征
70% - S 级分类、提取、过滤、简单问答nano/flash(最便宜)70%输出短、轮次少、工具调用少
20% - M 级起草、总结、代码生成、中等推理mid-tier(中等价格)20%输出中等长度、可能调用工具
10% - L 级审核、架构设计、复杂推理、多工具协调frontier(最贵)10%输出长、轮次多、工具调用频繁

路由策略三步骤:

第一步:任务分级。 给每个 workflow 定义 S/M/L 三档复杂度标准,包括输出长度、工具调用次数、推理深度和风险级别。

第二步:默认路由到 S 级。 只有命中复杂特征时才升级到 M 或 L 级。

第三步:级联路由。 S 级模型失败时升级到 M 级,M 级失败升级到 L 级,L 级失败进入人工介入。升级前检查预算剩余,超预算时跳过升级。

服务层路由

同一模型也可按 latency priority 分流。折扣比例和完成窗口属于易变事实,发布前复核官方定价。

服务层级成本折扣完成时间适用场景
实时 API无折扣即时响应交互式 Agent 对话、高优先级任务
Batch API50% cost discount(发布前复核)24-hour turnaround(发布前复核)批量 eval、分类、embedding、内容仓库处理
Flex Processing低成本(发布前复核)较慢响应,偶发不可用低优先级异步任务、model evaluations、data enrichment

离线任务路由决策清单:

  • 是否需要即时响应?是 -> 实时 API,走模型路由。
  • 是否可以接受 24 小时延迟?是 -> Batch API。
  • 是否低优先级且可容忍偶发失败?是 -> Flex Processing。
  • 是否批量处理 eval、分类、embedding?是 -> Batch API。

风险分级

任务按风险级别分级,对应不同路由路径:

风险级别典型操作路由策略预算分支
低风险分类、提取、内部总结S 级模型 + 自动路径无审批,预算上限宽松
中风险起草客户回复、代码变更建议M 级模型 + 可选审批超预算触发审批,可继续
高风险发客户邮件、扣款、架构变更L 级模型 + 必审批审批等待、拒绝、超时进入预算分支

关于审批等待、拒绝、超时如何影响预算分支,会在同系列的 Human-in-the-Loop 文章中展开。

关键约束

路由策略的关键约束:

  • 不硬编码价格数字:价格是易变事实,记录 model + pricingVersion,不写死成本公式。
  • 预算剩余检查:升级前检查 budgetRemaining,超预算时跳过升级或进入审批。
  • 错误分类:路由失败要区分可恢复的模型能力不足,以及不可恢复的参数错误、权限拒绝。

工具调用预算:per-tool budget、timeout 和重试上限

工具调用本身有 schema + API 双重成本。每次调用要发送 schema、参数、响应解析,加上外部 API 可能限流或超时,这些开销独立于模型调用。

工具预算控制

每个工具设置独立预算控制:

控制项建议配置监控字段触发动作
Per-tool budget单次调用上限tool_cost_estimate超上限跳过工具或降级处理
Tool timeout外部 API 被动超时tool_duration超时标记为可重试错误
Retry limit per tool单工具重试上限tool_retry_count超上限放弃工具,不进入重试循环

外部 API 用工具,包括搜索、数据库和第三方服务,都要单独记账,避免工具调用成为成本黑盒。

工具失败分类

工具失败分两类:可恢复和不可恢复。

失败类型典型错误处理策略成本影响
可恢复失败网络超时、503 Service Unavailable、429 Rate Limit自动重试,配合指数退避和 retry-after每次重试消耗完整上下文
不可恢复失败403 Permission Denied、400 Bad Request、工具不存在不重试,注入错误让模型判断不重试,避免无效消耗

失败分类的核心是“外部原因导致的临时失败才重试,内部配置错误不重试”。

熔断器

连续失败 N 次后停用工具,避免“空响应重试 1500 次”:

def circuit_breaker_tool(tool_name, consecutive_failures, threshold=5):
    if consecutive_failures >= threshold:
        return "disable_tool"  # 停用工具
    return "continue"

熔断器状态要写入日志,用于排查“为什么工具停用”。熔断后等待人工介入或自动恢复检查,不要反复调用不稳定工具。

关于工具调用基础,已在工具调用文章中展开,本文扩展 per-tool budget、timeout 和重试上限。

Prompt Caching 设计:稳定 prefix、变量位置、1024 token 门槛

Prompt Caching 是稳定 prompt prefix 的输入 token 优化,不是业务结果缓存。它用相同 prompt prefix 路由到近期处理过同前缀的服务器,能降低延迟和输入 token 成本。

Prompt Caching ≠ 结果缓存

Prompt Caching 缓存的是稳定 prompt prefix,不是业务结果。关键区别:

  • Prompt Caching:缓存稳定 prompt prefix,如 system prompt、tool schema;命中时节省输入 token,但仍会重新执行推理。
  • 业务结果缓存:缓存完整输出,如工具调用结果、数据库查询;命中时直接返回,不调用模型。

两者目的不同:Prompt Caching 优化输入 token 成本,业务结果缓存优化完整调用成本。可以同时使用:稳定前缀走 Prompt Caching,高频工具调用结果走业务缓存。

结构要求

Prompt Caching 的关键是区分稳定前缀和运行时变量:

内容类型位置命中缓存可能性典型内容
稳定前缀(进 cache)Prompt 前部高System prompt、Tool schema、Policy 文档、Few-shot 示例
运行时变量(不进 cache)Prompt 后部低User input、File fragments、Runtime state(当前对话轮次、临时变量)

设计步骤:

  1. 把 System prompt、Tool schema、Policy 放在前部:这些内容在多次调用中稳定不变,容易命中缓存。
  2. 把 User input、File fragments、Runtime state 放在后部:这些内容每次调用都变化,不应该进稳定前缀。
  3. 监控 Cache hit rate:记录 cachedTokens 和总输入 token,计算命中率。Cache hit rate > 40% 为健康,< 20% 需调整前缀结构。

门槛与效果

Prompt Caching 自动启用门槛和效果都属于易变事实,发布前复核官方文档:

  • 门槛:1024 tokens 及以上自动启用(发布前复核)。
  • 效果:命中可降低成本和延迟(发布前复核具体比例)。
  • 检查命中:usage.prompt_tokens_details.cached_tokens 字段。

Prompt Caching 的支持模型、门槛、折扣比例都是易变事实,发布前按官方 pricing 页复核。关键设计原则是“静态内容在前,变量在后”,这个结构本身是稳定的。

失败重试熔断:幂等性、状态保存、预算剩余检查

重试是成本失控的最大来源。一个后台 report Agent 卡在不稳定工具上,每次失败都重新发送完整上下文,重试 1500 次后成本会迅速偏离正常路径。

重试前检查预算

重试前检查预算剩余,不是重试后才发现没预算:

def should_retry(error_type, budget_remaining, retry_cost_estimate):
    # 错误分类
    if error_type in ["403", "400", "tool_not_exist"]:
        return False  # 不可恢复错误,不重试

    # 预算检查
    if budget_remaining < retry_cost_estimate:
        return False  # 超预算,不重试

    return True  # 可重试

预算检查要写在重试逻辑之前,避免“空响应重试 1500 次”烧掉整日预算。

幂等性

重试不会重复执行副作用,比如发邮件、扣款:

  • 工具调用用幂等 ID,例如 requestId;外部 API 收到相同 ID 时返回缓存结果,不重复处理。
  • 幂等 ID 要写入成本日志,用于判断是否重复调用。

幂等设计的核心是“同一操作不重复消耗”,避免重试放大成本。

状态保存(Checkpoint)

长任务失败恢复不重跑完整流程:

  • Agent 执行到关键节点时保存 checkpoint,包括已完成步骤、当前状态和上下文摘要。
  • 失败恢复时从 checkpoint 继续,不从头重跑。
  • Checkpoint 要写入持久化存储,不只留在内存。

关于 checkpoint 和 thread state 的设计,已在LangGraph Agent 架构文章中展开。

重试策略

不同错误类型要不同重试策略:

错误类型典型错误重试策略成本影响
网络超时10 秒未响应指数退避 + retry-after,最多 3 次每次重试消耗完整上下文
503/429Service Unavailable、Rate Limit等待限流窗口 + retry-after,最多 3 次等待时间不消耗 token,但重试会消耗
403/400Permission Denied、Bad Request不重试,注入错误让模型判断不重试,避免无效消耗

重试策略核心是“可恢复错误才重试,不可恢复错误不重试”,避免把无效请求反复发给模型。

熔断器

连续失败 N 次后停止重试,等待人工介入:

def circuit_breaker(consecutive_failures, threshold=5):
    if consecutive_failures >= threshold:
        return "stop_retry"  # 停止重试
    return "continue"

熔断器要写入日志,用于排查“为什么停止重试”。熔断后等待人工介入或预算恢复,不要反复调用不稳定工具或模型。

成本日志与告警:记录哪些字段、设置什么阈值

成本可观测性是控制的前提。日志字段不完整就无法定位问题。

OpenTelemetry trace span attributes

成本日志按三层 span 设计:

Agent run span(顶层):

字段名用途类型必须记录原因
runId定位具体执行string区分同一 workflow 的多次执行
tenantId定位租户string多租户场景分摊成本
userId定位用户string按用户统计成本趋势
workflowName定位 workflowstring按 workflow 类型统计成本
totalCost总成本估算float实时成本累加
budgetRemaining预算剩余float熔断判断依据
totalRetries总重试次数integer重试放大成本

Model call span(子 span):

字段名用途类型必须记录原因
model定位模型string判断模型路由是否合理
pricingVersion定价版本string成本公式不写死,记录版本
inputTokens输入 token 数integer计算输入成本
outputTokens输出 token 数integer输出成本需独立记录
cachedTokens缓存命中 token 数integer计算缓存节省
costEstimate本次成本估算float实时成本累加
latencyMs调用延迟integer判断是否走 Batch/Flex

Tool call span(子 span):

字段名用途类型必须记录原因
toolName定位工具string定位工具调用 overhead
toolBudget工具预算上限float熔断判断依据
toolTimeout工具超时时间integer判断是否超时失败
retryCount重试次数integer判断重试放大
errorType错误类型string区分可恢复和不可恢复

不要只记 total_cost,要按维度拆分。没有这些字段,账单异常时你只能看到“超标”,却不知道是哪个用户、哪个工具、哪次重试烧钱。

关于日志、告警、失败恢复的完整设计,已在Agent 监控恢复文章中展开,本文补充成本字段和预算对象。

告警阈值

告警阈值按维度设置:

告警维度告警阈值告警方式触发动作
全局预算消耗70%、90%、100%Slack/邮件告警70% 提示,90% 降级,100% 熔断
单用户/tenant 消耗超过均值 3 倍Slack/邮件告警检查是否有异常调用
单模型调用失败率> 5%Dashboard 告警检查模型路由或服务状态
单工具重试次数> 阈值Dashboard 告警检查工具稳定性
缓存命中率< 预期值Dashboard 告警检查 prompt 结构是否错误

告警阈值要写在成本日志里,用于自动触发告警和熔断。

降级策略

告警触发后的降级路径:

降级路径降级方式适用场景成本影响
模型降级大模型 -> 小模型单模型失败率过高降成本,可能降质量
路径降级实时 API -> Batch API -> Flex Processing全局预算消耗过快延迟增加,成本下降
功能降级关闭非核心工具调用单工具重试次数过高减少工具调用 overhead
用户降级限流、排队、提示“稍后重试”单用户消耗异常避免单用户烧预算

降级策略要写在预算逻辑里,告警触发时自动降级,而不是人工介入后才降级。

下一步推荐

Agent 成本控制不是孤立的,需要监控、工具调用、上下文工程配合:

  • 已发布:Agent 监控恢复:日志字段、告警设置、失败恢复,本文补充成本字段和预算对象。
  • 已发布:LangGraph Agent 架构:checkpoint、thread state、失败恢复,用于说明长任务状态保存。
  • 已发布:工具调用:工具调用基础,本文扩展 per-tool budget、timeout、重试上限。
  • 同系列:上下文工程:稳定 prefix 与缓存命中,哪些上下文进入稳定 prefix,哪些只放运行时变量。
  • 同系列:Human-in-the-Loop:审批等待、拒绝、超时的预算分支,审批如何影响成本和重试路径。

从 Session 级防线开始:设置 per-session cost limit,单次会话超预算自动终止。这是最快见效的第一步,避免一个跑飞的任务烧掉整日预算。后续逐步扩展到预算对象分层、模型路由、工具调用预算、Prompt Caching、重试熔断和成本日志。

设计 Agent 成本预算和熔断机制

用预算对象、模型路由、服务层路由、缓存、工具预算和重试熔断,把 Agent 成本控制前移到每次 run 执行前。

⏱️ 预计耗时: 45 分钟

  1. 1

    步骤 1: 列出所有成本路径

    列出 Agent 的模型调用、工具调用、文件读取、外部 API、批处理、缓存和重试路径。
  2. 2

    步骤 2: 定义预算对象

    按 tenant、user、run、workflow、model、tool、retry、cache 和 time window 记录预算对象。
  3. 3

    步骤 3: 设置路由策略

    为不同任务类型设置模型层路由和服务层路由,区分 online、batch、flex 和 queue。
  4. 4

    步骤 4: 设计缓存命中

    把稳定上下文放入 prompt prefix,变量内容放后面,同时区分 prompt caching、业务结果缓存和工具响应缓存。
  5. 5

    步骤 5: 限制工具和重试

    为每个工具设置 timeout、max retries、idempotency key、per-tool budget 和 fallback。
  6. 6

    步骤 6: 记录成本 span

    在 run/span 上记录 token、cached token、tool、retry、latency、estimated cost、budget remaining 和 traceId。
  7. 7

    步骤 7: 配置降级和熔断

    设置降级、暂停、熔断和告警阈值,并用真实失败案例回归测试。

常见问题

Agent 成本应该按用户、会话、任务还是工具统计?
按多层记账:用户、tenant、workflow、task、tool、retry、cache 七层,每层独立预算和熔断。不能只记 total_cost,要按维度拆分,否则账单异常时无法定位问题。
模型路由是不是只要把简单任务换成小模型?
不只是模型层路由,还要考虑服务层路由(Batch/Flex/实时)和风险分级。同一模型也可按 latency priority 分流,离线任务走 Batch API,低优先级任务走 Flex Processing。
Prompt Caching 和普通业务缓存有什么区别?
Prompt Caching 缓存的是稳定 prompt prefix,如 system prompt、tool schema、policy,不是业务结果。业务结果缓存保存完整输出或工具响应;两者目的不同,可以同时使用。
工具调用失败时重试几次才算合理?
不是固定次数,而是看失败分类、预算剩余和熔断器。可恢复错误最多少量重试;403、400、工具不存在等不可恢复错误不重试,避免无效消耗。
Agent 长任务跑到一半没预算了,应该降级、暂停还是直接失败?
优先暂停并保存 checkpoint,等待预算恢复或人工介入。直接失败会丢失进度,盲目降级可能影响质量;暂停通常是更稳的工程选择。
成本日志应该记录哪些字段?
至少记录 runId、tenantId、workflow、model、input/output/cached tokens、toolName、retryCount、latency、costEstimate、budgetRemaining、decision 和 traceId。

16 分钟阅读 · 发布于: 2026年9月17日 · 修改于: 2026年9月17日

评论

使用 GitHub 账号登录后即可评论

Easton BlogEaston Blog