AgentLedger:AI Agent 花费控制层,如何在调用前拦截超支

发布时间:2026/10/27 16:07:58
AgentLedger:AI Agent 花费控制层,如何在调用前拦截超支

1. 从一次“跑飞的账单”说起:AgentLedger 到底想解决什么问题

做 AI Agent 应用的人,大概都经历过这种心跳时刻:凌晨两点,你写的一个自动化 Agent 正在后台勤勤恳恳地调用各种工具,你以为它只是查几条数据、生成一份报告,结果第二天早上打开账单,发现它在一个循环里把某个付费接口调用了四万七千次。钱已经花出去了,调用已经发生了,你唯一能做的就是对着账单发呆。

AgentLedger 这个项目标题里最扎眼的一句话是“a spend cap that refuses the call before it happens”——一个在调用发生之前就拒绝它的花费上限。注意这个措辞,它不是“事后告警”,不是“超额通知”,而是在调用真正发出去之前就把它拦下来。这个区别非常关键,也是我想在这篇博文里重点拆解的东西。

先把这个项目是什么讲清楚。AgentLedger 是一个面向 AI Agent 场景的花费控制层,它工作在 Agent 和外部工具/模型服务之间,充当一个“预算守门人”的角色。每当 Agent 要发起一次可能产生费用的调用(比如调用大模型 API、调用某个按次计费的第三方工具、触发一次 MCP 工具调用),AgentLedger 会先做一次预算检查:这次调用预计要花多少钱?当前会话/任务/时间窗口内已经花了多少?如果这次调用会让总额突破预设的上限,它就直接拒绝,调用根本不会发出去。

它解决的核心问题是:Agent 的自主性和成本可控性之间的矛盾。传统做法是给 API Key 设一个总额度,或者用云厂商的预算告警,但这些手段要么粒度太粗(整个账号一个上限),要么反应太慢(告警来了钱已经花了)。AgentLedger 把控制点前移到了“调用发生之前”这个瞬间,并且把预算粒度细化到了单个 Agent、单个任务、单个会话的级别。

这个内容适合谁来参考?三类人最应该看:一是正在做 Agent 产品、被成本问题折磨的开发者;二是负责 AI 基础设施、需要给多个团队做成本治理的平台工程师;三是对 MCP 生态感兴趣、想知道工具调用链路里还能插入哪些控制点的人。哪怕你现在还没被账单吓到过,提前把这套思路装进脑子里,早晚用得上。

2. 为什么“事前拒绝”比“事后告警”难得多

2.1 事后告警的天然缺陷

大部分团队的成本控制是从告警开始的。云厂商给你一个预算功能,你设一个月度上限,超过 80% 发邮件,超过 100% 再发一封。这套机制在传统业务里够用,因为传统业务的调用量是可预测的,流量曲线是平滑的。但 Agent 场景完全不是这样。

Agent 的调用模式有三个特点:突发性、递归性、不可预测性。突发性是指它可能在一个推理步骤里连续发起几十次工具调用;递归性是指它可能因为一个子任务失败而不断重试,重试又触发新的调用;不可预测性是指同样一个用户请求,Agent 走的路径可能完全不同,花费可能差几十倍。在这种模式下,事后告警基本等于“事后验尸”——你知道了死因,但人已经没了。

我见过一个真实的案例:某个团队做了一个自动化的竞品分析 Agent,用户输入一个公司名,Agent 会去搜索、抓取、总结。测试阶段一切正常,每次任务花费大概几毛钱。上线第一天,有个用户输入了一个特别冷门的公司名,Agent 搜不到结果,于是它开始换关键词重试,换了十几次之后开始尝试不同的搜索工具,每个工具都调一遍,最后陷入了一个“搜索-失败-换工具-再搜索”的循环。等团队发现的时候,这个用户的任务已经烧掉了几百块。事后告警在凌晨三点发出来,没人看。

2.2 事前拒绝需要解决的技术难题

“在调用发生之前拒绝”听起来简单,做起来要解决好几个硬问题。

第一个问题是成本预估。你要在调用发出之前知道它大概要花多少钱。对于大模型 API,这个预估相对可行,因为你可以根据输入 token 数估算输入成本,再根据 max_tokens 参数估算输出成本上限。但对于第三方工具,尤其是那些按次计费、按结果计费、或者计费规则不透明的工具,预估就难得多。AgentLedger 的应对策略是维护一个成本模型注册表,每个工具在接入时都要声明自己的计费方式:固定费用、按 token、按调用次数、按返回数据量等等。对于无法精确预估的,就按最坏情况估算,宁可保守也不放过。

第二个问题是拦截点的位置。你必须在调用真正发出去的那个环节做拦截,而不是在更上层。如果拦截点在 Agent 的规划层,Agent 可能绕过它直接调用工具;如果拦截点在网络层,你可能拿不到足够的上下文来判断这次调用属于哪个任务、哪个预算。AgentLedger 选择把自己做成一个代理层,所有工具调用都必须经过它,它才有机会在最后一刻说“不”。

第三个问题是状态管理。预算是有状态的,你要知道当前已经花了多少。这个状态必须是强一致的,不能出现两个并发调用都以为自己还有预算、结果双双放行的情况。AgentLedger 用了一个中心化的账本(这也是它名字里 Ledger 的由来),每次调用前先做一次原子性的预算预留,调用完成后再根据实际花费做结算,多退少补。

2.3 和 MCP 生态的关系

最近 MCP 这个词热度很高,很多人问 MCP 是什么。简单说,MCP 是一套让 AI 应用和外部工具/数据源对接的协议标准,你可以把它理解成“AI 世界的 USB 接口”。Agent 通过 MCP 连接到各种工具服务器,然后调用工具。这个生态正在快速膨胀,从数据库查询到地图服务,从设计工具到调试工具,都在往 MCP 上靠。

这对 AgentLedger 来说既是机会也是挑战。机会在于,MCP 提供了一个统一的调用入口,AgentLedger 只需要在 MCP 客户端和 MCP 服务器之间插一层,就能覆盖所有走 MCP 的工具调用。挑战在于,MCP 工具的种类太多,计费方式千差万别,成本模型注册表要维护的内容会非常庞杂。而且 MCP 本身还在演进,协议层面的变化可能会影响拦截的实现方式。

标题里还出现了 402 和 x402 这两个词。402 是 HTTP 状态码里预留的“Payment Required”,一直没被正式使用。x402 则是围绕这个状态码衍生出来的一套思路,核心想法是让付费成为 HTTP 请求的一等公民——请求方在发起请求时就携带支付凭证,服务方验证后返回内容。这个思路和 AgentLedger 的“事前拒绝”在哲学上是一致的:把付费判断前移到请求发生之前。如果未来 x402 这类机制普及,AgentLedger 的拦截逻辑可能会和支付协议深度结合,预算检查直接变成支付能力检查。

3. AgentLedger 的核心架构与关键组件拆解

3.1 整体架构:三层结构

AgentLedger 的架构可以拆成三层,我用一个生活化的类比来解释:把它想象成公司里的报销制度。

最上面是策略层,相当于公司的报销政策。它定义了各种预算规则:每个 Agent 每天最多花多少、每个任务最多花多少、哪些工具需要审批、超额时是拒绝还是降级。这一层是配置驱动的,管理员通过配置文件或管理界面来设定。

中间是决策层,相当于财务审核员。每次调用请求过来,它负责查政策、算成本、看余额、做决定。这一层是 AgentLedger 的核心逻辑所在,也是性能瓶颈最可能出现的地方,因为每次调用都要过一遍。

最下面是账本层,相当于公司的账本。它记录每一笔花费,维护各个维度的余额,提供原子性的预留和结算操作。这一层对一致性的要求最高,通常需要一个支持事务的存储后端。

三层之间通过明确定义的接口通信,策略层的变化不需要改动账本层,决策层的逻辑可以独立测试。这种分层的好处是,当计费规则变化时,你只需要更新策略配置;当存储性能不够时,你只需要替换账本层的实现。

3.2 成本模型注册表:让每个工具“自报身价”

成本模型注册表是 AgentLedger 能做事前预估的基础。每个接入的工具都要在这里注册,声明自己的计费方式。我把它支持的计费类型整理成一张表:

计费类型说明预估方式典型场景
固定费用每次调用固定价格直接取固定值按次计费的 API
按输入 token根据输入长度计费统计输入 token 数乘以单价大模型输入
按输出 token根据输出长度计费用 max_tokens 上限估算大模型输出
按调用次数阶梯计价查当前阶梯乘以单价批量接口
按数据量根据返回数据大小计费用预估上限估算数据下载服务
复合计费多种方式叠加各项相加大多数大模型 API

对于大模型 API,预估逻辑是这样的:输入成本 = 输入 token 数 × 输入单价,输出成本 = max_tokens × 输出单价,总预估 = 输入成本 + 输出成本。这里的关键是用 max_tokens 而不是期望输出长度,因为你要防的是最坏情况。如果 Agent 没有设置 max_tokens,AgentLedger 会强制要求设置一个默认上限,否则拒绝调用。这个设计有点霸道,但非常必要——没有上限的调用就是没有上限的风险。

对于无法精确预估的工具,注册表允许声明一个“预估上限”,实际结算时如果花费低于预估,差额会退回账本。这种“先按最坏情况预留,再按实际情况结算”的模式,和信用卡的预授权机制是一样的。

3.3 账本层:原子性预留与结算

账本层要解决的核心问题是并发安全。假设一个任务有 10 块钱预算,当前已经花了 9 块,同时来了两个调用,每个预估 1 块。如果没有原子性保证,两个调用可能都看到“还剩 1 块”,然后双双放行,结果花了 11 块,超了。

AgentLedger 的做法是预留-结算两阶段。调用发起前,先做一次原子性的预留操作:检查余额是否足够,如果足够就扣减预留金额,返回一个预留凭证。调用完成后,用实际花费做结算:如果实际花费小于预留,退回差额;如果大于预留(比如输出 token 超了预估),从账本里补扣,如果补扣时余额不足,这笔超支会被记录为“欠款”,影响后续调用的放行。

这个机制的关键在于预留操作的原子性。在关系型数据库里,可以用SELECT ... FOR UPDATE加事务来实现;在 Redis 里,可以用 Lua 脚本保证原子性。选哪种取决于你的并发量和一致性要求。如果并发量不高,关系型数据库更简单可靠;如果并发量很大,Redis 的性能更好,但要处理好持久化问题。

注意:预留金额的粒度要合理。如果预留太粗(比如每次都预留一个很大的固定值),会导致预算利用率低,很多调用明明花不了那么多却被拦下。如果预留太细,又会导致预留操作过于频繁,增加账本层压力。我的经验是,按工具类型设置不同的预留策略,大模型调用按 max_tokens 预估,固定费用工具直接按固定值预留。

3.4 拦截器:最后一刻的守门人

拦截器是 AgentLedger 和外部世界交互的边界。它需要做到两件事:识别出哪些调用需要拦截,以及在不破坏原有调用语义的前提下拦截。

识别哪些调用需要拦截,靠的是调用元数据。每次调用都会携带一些上下文信息:是哪个 Agent 发起的、属于哪个任务、调用的是哪个工具、传了什么参数。拦截器根据这些信息去策略层查询适用的规则。这里有个设计难点:上下文信息怎么传递?如果 Agent 框架本身不支持传递这些信息,拦截器就拿不到足够的上下文。AgentLedger 的解决方案是提供一个轻量级的 SDK,Agent 在发起调用时通过 SDK 注入上下文,SDK 负责把上下文编码到调用请求的元数据里。

不破坏原有调用语义,意味着拦截器对上层 Agent 和下层工具都应该是透明的。Agent 不需要知道 AgentLedger 的存在,它只是正常发起调用;工具也不需要知道,它只是正常接收请求。拦截器在中间做检查,通过了就转发,没通过就返回一个标准的拒绝响应。这个拒绝响应要设计得合理,让 Agent 能够理解“这次调用被拒绝了,原因是预算不足”,而不是把它当成一个普通的调用失败去重试。如果 Agent 把预算拒绝当成网络错误去重试,那就白拦了。

4. 实操落地:从零搭建一个 AgentLedger 式的预算控制层

4.1 环境准备与技术选型

要复现一套类似 AgentLedger 的机制,你需要准备这些东西。存储层我推荐 PostgreSQL,因为它的事务支持完善,做原子性预留很自然,而且最近社区里关于 PostgreSQL 的 skill 和 MCP 集成讨论很多,生态在变好。如果你追求极致性能,可以用 Redis 做预留层、PostgreSQL 做持久化账本,两层配合。

服务层用 Python 或 Node.js 都可以,取决于你的 Agent 技术栈。Python 的话用 FastAPI 写拦截服务,Node.js 的话用 Express 或 Fastify。我个人的偏好是 Python,因为 Agent 生态里 Python 的库更全,调试起来也方便。

Agent 框架这边,不管你用的是哪种框架,关键是要能在工具调用链路上插入一个中间层。如果你的框架支持自定义工具包装器,那就把 AgentLedger 的检查逻辑包在工具外面;如果不支持,那就得在更底层做,比如包装 HTTP 客户端。

4.2 数据库表结构设计

账本层的表结构是整个系统的地基,设计不好后面会很痛苦。我建议至少设计这几张表:

-- 预算账户表:每个 Agent、每个任务、每个时间窗口一个账户 CREATE TABLE budget_accounts ( id BIGSERIAL PRIMARY KEY, scope_type VARCHAR(32) NOT NULL, -- agent / task / session / global scope_id VARCHAR(128) NOT NULL, period VARCHAR(32) NOT NULL, -- daily / monthly / total limit_amount NUMERIC(18, 6) NOT NULL, reserved_amount NUMERIC(18, 6) NOT NULL DEFAULT 0, spent_amount NUMERIC(18, 6) NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE (scope_type, scope_id, period) ); -- 预留记录表:每次预留一条,用于结算和审计 CREATE TABLE reservations ( id BIGSERIAL PRIMARY KEY, account_id BIGINT NOT NULL REFERENCES budget_accounts(id), reservation_token VARCHAR(64) NOT NULL UNIQUE, estimated_amount NUMERIC(18, 6) NOT NULL, actual_amount NUMERIC(18, 6), status VARCHAR(16) NOT NULL DEFAULT 'reserved', -- reserved / settled / expired tool_name VARCHAR(128) NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), settled_at TIMESTAMPTZ ); -- 成本模型表:每个工具一条 CREATE TABLE cost_models ( id BIGSERIAL PRIMARY KEY, tool_name VARCHAR(128) NOT NULL UNIQUE, billing_type VARCHAR(32) NOT NULL, unit_price NUMERIC(18, 8) NOT NULL, config JSONB NOT NULL DEFAULT '{}', created_at TIMESTAMPTZ NOT NULL DEFAULT now() );

budget_accounts表里的reserved_amount和spent_amount是分开的,这样你能清楚地看到“已经花掉的”和“预留中还没结算的”。可用余额 = limit_amount - reserved_amount - spent_amount。预留操作就是检查可用余额是否大于等于预估金额,如果是就增加 reserved_amount。

4.3 预留操作的原子性实现

预留操作是整个系统里最需要保证原子性的地方。用 PostgreSQL 实现的话,核心 SQL 是这样的:

BEGIN; -- 锁定账户行,防止并发修改 SELECT id, limit_amount, reserved_amount, spent_amount FROM budget_accounts WHERE scope_type = $1 AND scope_id = $2 AND period = $3 FOR UPDATE; -- 在应用层计算可用余额,判断是否足够 -- 如果足够,执行更新 UPDATE budget_accounts SET reserved_amount = reserved_amount + $4, updated_at = now() WHERE id = $5; -- 插入预留记录 INSERT INTO reservations (account_id, reservation_token, estimated_amount, tool_name) VALUES ($5, $6, $4, $7); COMMIT;

FOR UPDATE是关键,它会给这一行加排他锁,其他并发事务必须等待。这样就能保证同一时刻只有一个事务在检查和修改余额。这个方案的性能瓶颈在于锁竞争,如果同一个账户的并发调用很多,会排队。缓解办法是给账户分片,比如按任务 ID 哈希到不同的账户行,但这样会让预算的全局视图变复杂。我的建议是先用简单方案,等真的遇到性能问题再优化。

4.4 拦截器的实现与集成

拦截器的核心逻辑是一个函数:接收调用请求,返回放行或拒绝。伪代码大概长这样:

def intercept_call(agent_id, task_id, tool_name, params): # 1. 查成本模型 cost_model = get_cost_model(tool_name) if cost_model is None: # 未注册的工具,按策略决定是拒绝还是用默认模型 if policy.reject_unregistered: return Reject("tool not registered") cost_model = policy.default_cost_model # 2. 预估成本 estimated = estimate_cost(cost_model, params) # 3. 查适用的预算账户 accounts = find_applicable_accounts(agent_id, task_id) if not accounts: return Reject("no budget account") # 4. 逐个账户做预留 reservations = [] for account in accounts: result = try_reserve(account, estimated, tool_name) if not result.success: # 回滚已成功的预留 for r in reservations: rollback_reservation(r) return Reject(f"budget exceeded on {account.scope_type}") reservations.append(result) # 5. 放行,返回预留凭证 return Allow(reservations)

这个逻辑里有个细节值得说:多个账户的预留要么全成功要么全失败。因为一个调用可能同时受 Agent 级预算和任务级预算约束,两个都要检查。如果 Agent 级通过了但任务级没通过,Agent 级的预留必须回滚,否则会白白占用预算。这就是为什么代码里有个回滚循环。

集成到 Agent 框架时,最干净的方式是包装工具调用函数。以 Python 为例,如果你用的是函数调用模式,可以写一个装饰器:

def with_budget_check(func): @functools.wraps(func) def wrapper(*args, **kwargs): context = get_current_context() decision = intercept_call( agent_id=context.agent_id, task_id=context.task_id, tool_name=func.__name__, params=kwargs ) if decision.is_rejected: raise BudgetExceededError(decision.reason) try: result = func(*args, **kwargs) settle_reservation(decision.reservations, actual_cost=estimate_actual(result)) return result except Exception as e: # 调用失败,释放预留 release_reservation(decision.reservations) raise return wrapper

这个装饰器把预算检查、调用执行、结算/释放三个环节串起来了。注意异常处理分支:如果工具调用本身失败了,预留要释放,因为这次调用没有产生费用(或者只产生了部分费用,按实际情况处理)。

4.5 结算与对账

结算环节容易被忽视,但它决定了账本的准确性。结算时要处理三种情况:实际花费等于预估、小于预估、大于预估。

等于预估最简单,把 reserved_amount 减掉,spent_amount 加上,预留记录标记为 settled。小于预估时,除了上面的操作,还要把差额退回——实际上就是 reserved_amount 减去预估、spent_amount 加上实际,差额自然就释放了。大于预估时,spent_amount 加上实际值,如果此时可用余额不够,就产生欠款,需要记录并在后续调用中优先扣减。

对账是定期跑的任务,用来发现账本和实际账单的差异。理想情况下,AgentLedger 记录的 spent_amount 应该和云厂商账单一致。但现实中会有差异,原因可能是计费延迟、汇率波动、或者某些费用没被 AgentLedger 捕获。对账任务的作用就是发现这些差异,人工介入排查。我建议对账频率不要太高,每天一次就够了,因为账单本身也有延迟。

5. 常见问题与排查技巧实录

5.1 预算被“幽灵调用”吃掉

这是最常见的问题之一。你发现某个任务的预算莫名其妙少了很多,但查调用记录又找不到对应的调用。原因通常是预留了但没结算。比如 Agent 发起调用后进程崩溃了,预留没有释放;或者结算逻辑有 bug,预留一直挂着。

排查思路:查 reservations 表里 status 为 reserved 且创建时间超过一定阈值的记录。这些就是“悬挂的预留”。解决办法是加一个过期清理任务,定期把超过阈值(比如 10 分钟)还没结算的预留释放掉。阈值怎么定?要大于你系统里最长的一次调用耗时。如果你的工具调用可能跑几分钟,阈值就设 15 分钟。

实操心得:我在预留记录里加了一个expires_at字段,创建时就设好过期时间。清理任务只扫这个字段,不用做时间计算,简单可靠。过期时间按工具类型设置,大模型调用设 5 分钟,批处理工具设 30 分钟。

5.2 预估偏差导致频繁拒绝

如果预估总是偏高,会导致预算利用率低,明明还有钱却调不了。如果预估偏低,又会导致频繁超支补扣。这个平衡很难一次调好。

我的经验是分工具类型调。大模型调用的预估偏差主要来自 max_tokens 设置,如果 Agent 总是把 max_tokens 设得很大但实际输出很短,预估就会偏高。解决办法是统计历史调用的实际输出 token 分布,用 P95 分位数作为预估依据,而不是直接用 max_tokens。对于固定费用工具,预估就是精确值,不存在偏差。对于按数据量计费的工具,预估偏差最大,只能靠保守估计加频繁结算来缓解。

5.3 并发下的账本不一致

前面讲过用FOR UPDATE保证原子性,但实际部署时还是可能出问题。最常见的原因是用了连接池但事务没正确提交或回滚。比如代码里抛异常了,事务没回滚,锁一直占着,其他调用全部阻塞。

排查这类问题,先看数据库的锁等待情况。PostgreSQL 里可以查pg_locks和pg_stat_activity,看有没有长时间运行的事务。如果有,基本就是代码里事务边界没处理好。解决办法是严格用上下文管理器管理事务,确保异常时自动回滚。

另一个坑是读写分离。如果你把账本读操作路由到从库,可能会读到旧数据,导致余额判断错误。账本层的读写必须走主库,不能分离。这个原则要写进架构文档里,不然新人很容易踩坑。

5.4 拒绝响应被 Agent 当成错误重试

这个问题的表现是:预算明明已经超了,但调用量还在涨。原因是 Agent 收到拒绝响应后,把它当成了临时错误,触发重试逻辑,重试又被拒绝,又重试,形成循环。

解决办法是让拒绝响应携带明确的语义。不要返回一个普通的 500 错误,而是返回一个特定的错误码和错误信息,比如BUDGET_EXCEEDED,并且在信息里说明“这是预算限制,重试无效”。同时,在 Agent 侧的重试逻辑里,要把这个错误码加入“不可重试”列表。如果你的 Agent 框架支持自定义错误处理,就在这里做文章。

问题现象可能原因排查方法解决措施
预算莫名减少预留未结算查 reserved 状态记录加过期清理任务
频繁拒绝但余额充足预估偏高对比预估与实际用历史 P95 调整预估
并发调用阻塞事务未提交查 pg_locks修复事务边界
拒绝后调用量仍涨Agent 重试看调用日志明确错误码 + 禁用重试
账本与账单不符计费延迟/遗漏定期对账人工排查差异项

5.5 工具未注册时的策略选择

新工具接入时,如果忘了在成本模型注册表里注册,拦截器会面临一个选择:拒绝还是放行?拒绝的话,业务会中断,但安全;放行的话,业务不中断,但可能产生未受控的费用。

我的建议是默认拒绝,但提供白名单机制。对于开发环境,可以配置成放行并记录警告;对于生产环境,必须拒绝。同时,注册流程要尽量简单,最好能自动化——比如从工具的 OpenAPI 描述里自动提取计费信息,减少人工遗漏。

6. 从 AgentLedger 延伸出去:还能怎么玩

AgentLedger 的核心思路是“在花钱之前做判断”,这个思路可以延伸到很多地方。

第一个延伸方向是性能预算。不只是钱有预算,时间也有预算。你可以给每个任务设一个超时上限,Agent 在规划下一步时先检查剩余时间,如果不够就换更快的方案或者直接返回部分结果。这和花费预算是同一个逻辑,只是计量单位从钱变成了秒。

第二个延伸方向是调用配额。有些工具不是按钱计费,而是按次数限制,比如免费额度每天 1000 次。你可以把配额也纳入 AgentLedger 的管理,和预算共用一套预留-结算机制,只是单位不同。

第三个延伸方向是降级策略。当预算不足时,不一定非要拒绝,也可以降级。比如从 GPT-4 降级到更便宜的模型,从精确搜索降级到模糊搜索。这需要在策略层定义降级规则,在决策层实现降级逻辑。降级比拒绝复杂,但用户体验更好。

第四个延伸方向是和 x402 结合。如果未来 x402 这类支付协议普及,AgentLedger 可以直接和支付层对接,预算检查变成支付能力检查,拒绝变成“支付失败”。这样整个链路会更简洁,因为支付协议本身就包含了验证和结算的逻辑。

我在实际搭建这套机制的过程中最大的体会是:预算控制不是一个技术问题,而是一个产品问题。你需要和业务方一起决定,什么情况下宁可拒绝也不能超支,什么情况下可以超支但要告警,什么情况下可以降级。这些决策没有标准答案,取决于你的业务特性和风险偏好。技术只是把这些决策落地的手段。

最后分享一个小技巧:在 AgentLedger 的拒绝响应里,除了错误码,还可以带上“当前余额”和“本次预估花费”。这样 Agent 在收到拒绝后,可以据此调整策略——比如换一个更便宜的工具,或者把任务拆小。让 Agent 知道“为什么被拒绝”,比单纯告诉它“不行”要有用得多。