跳到主要内容

AutoGPT — 计费、安全与嵌套(深水区)

本章讲什么: 前三章讲了块、图、引擎。这一章是把平台跑成「能托管、能收费、敢让陌生人用」的三块深水区:怎么算钱、怎么不真花钱地试跑、怎么在危险动作前拦一道人审、怎么让 agent 调 agent

1. 计费:预扣 + 事后对账

1.1 要解决的小问题

有的块成本跑之前就知道(固定 1 积分/次);有的只有跑完才知道(LLM 按 token、视频按时长)。要既能在跑前挡住「余额不足」,又能按真实用量收费,AutoGPT 用了两段式

1.2 成本类型

BlockCostType(blocks/_base.py:99)把成本分两类:

类型含义何时定价
RUN / BYTE每次运行 / 每字节,固定跑前就定
SECOND / ITEMS / COST_USD / TOKENS按时长/条数/美元/token跑后按 stats 结算

后四种叫动态成本(blocks/_base.py:123 _DYNAMIC_COST_TYPES)。它的 is_dynamic 属性(blocks/_base.py:113)有句关键注释:动态类型跑前返回 0、跑后用 charge_reconciled_usage 按 stats 结算

1.3 两段式流程

调度循环取到节点

① 预扣(pre-flight) billing.charge_usage() manager.py:1057
│ RUN 类:扣真实价 ; 动态类:扣 0(占位)
│ 记下 pre_flight_charge 作为对账基线 manager.py:1066

节点执行,产出 stats(真实 token 数 / 时长 / provider_cost)

② 对账(reconcile) billing.charge_reconciled_usage() manager.py:637
│ 按 stats 算真实价,与预扣的差额补扣或退款
│ delta 累加进 graph_stats.cost manager.py:642

整图 cost 反映真实花费

几个值得记住的细节:

  • 失败/中止也要对账。 注释明说:即使节点 FAILED/TERMINATED,也要 reconcile(manager.py:621)——因为它可能已经烧了真实的 provider token,预扣的钱应退到「实际用掉」的量,不能整笔吞掉用户的钱。
  • 对账基线被钉死。 pre_flight_charge 在预扣那一刻就记下(manager.py:1066),防止「预扣和对账之间有人热更新了估价 JSON」导致对账对错基线。
  • dry-run 完全跳过对账(manager.py:636):仿真不碰用户钱包。
  • 余额闸门在循环里:取节点前若 get_credits <= 0 直接 InsufficientBalanceError 优雅停图(manager.py:976),并发通知(manager.py:1090)。

2. dry-run:用 LLM 仿真整张图,不真花钱

2.1 直觉

用户搭了张复杂图,想先看看「大概会怎么流、输出长啥样」,但不想真去调 OpenAI、真发推特、真扣钱。dry-run 让整张图照常调度,但每个块的产出由 LLM 假装生成

2.2 怎么实现

开关是 ExecutionContext.dry_run(data/execution.py:99),这个 context 贯穿整个执行流。在 execute_node 里(manager.py:342):

# manager.py:342(精简)—— dry-run 分流
if execution_context.dry_run and _dry_run_input is None:
block_iter = simulate_block(node_block, input_data, user_id=user_id) # LLM 假装产出
else:
block_iter = node_block.execute(input_data, **extra_exec_kwargs) # 真跑

配套的容错铺在多处:

  • validate_exec 在 dry-run 下给缺失的凭据字段塞 None 占位,让节点能入队(executor/utils.py:344)——反正仿真不调真 API。
  • Block._execute 在 dry-run 下跳过凭据字段的 schema 必填校验(blocks/_base.py:841),只校非凭据输入。
  • 少数块(如编排块)在 dry-run 下仍真跑(用平台自己的模拟模型 + OpenRouter key,不需要用户凭据),由 prepare_dry_run 决定(manager.py:250)。

巧妙之处: dry-run 不是「另一套执行路径」,而是同一个引擎 + 同一个 context 字段 + 一个 simulate_block 分流点。整图的调度、路由、状态机全复用,只把「真正调块」这一步换成 LLM 仿真。

3. 人在回路(HITL):危险动作前拦一道

3.1 要解决的小问题

agent 自动跑,但某些动作(发邮件、删数据、花钱)用户想先看一眼再批

3.2 怎么实现

块在 __init__ 里声明 is_sensitive_action=True(例:PrintToConsoleBlock,blocks/basic.py:128)。执行管线 _execute 一进来就检查(blocks/_base.py:829):

# blocks/_base.py:775(精简)—— 敏感动作 + 安全模式 → 走审核
if not (self.is_sensitive_action and execution_context.sensitive_action_safe_mode):
return False, input_data # 不是敏感动作,或没开安全模式 → 放行
decision = await HITLReviewHelper.handle_review_decision(...)
if decision is None: return True, input_data # 还在等审 → 暂停
if not decision.should_proceed: raise BlockExecutionError(...) # 被拒 → 停
return False, decision.review_result.data # 批准(可能改过输入)→ 用新数据跑

几个点:

  • 三态返回:暂停(等审)/ 拒绝(停)/ 批准(可带审核人修改过的输入继续)。
  • 暂停时整图状态变成 REVIEW(manager.py:1204 has_pending_reviews_for_graph_exec),引擎把图挂起;用户批了之后,图从 REVIEW 状态续跑(manager.py:849)。这复用了第 3 章的「历史状态续跑」机制。
  • 是否启用由图设置 sensitive_action_safe_mode(data/graph.py:60)和 human_in_the_loop_safe_mode(data/graph.py:57)控制,默认后者开、前者关。

4. 嵌套:agent 调 agent

4.1 直觉

你做了个「翻译 agent」,现在想在「新闻摘要 agent」里把它当一个步骤用。AutoGPT 让一张图作为一个块嵌进另一张图——这就是 AgentExecutorBlock(blocks/agent.py:24)。

4.2 怎么实现:复用同一个引擎,递归调用

AgentExecutorBlock.run(blocks/agent.py:83)做的事极简洁——它就是再调一次 add_graph_execution,启动子图:

# blocks/agent.py:93(精简)—— 子 agent = 再跑一张图
graph_exec = await execution_utils.add_graph_execution(
graph_id=input_data.graph_id,
user_id=input_data.user_id,
inputs=input_data.inputs,
execution_context=execution_context.model_copy(
update={"parent_execution_id": graph_exec_id}, # 记住父执行
),
dry_run=execution_context.dry_run, # dry-run 沿袭
)

然后它监听子图的执行事件总线(blocks/agent.py:154 event_bus.listen),把子图各节点的产出当成自己的输出 yield 出去,让父图的下游能接。

4.3 两个关键卷回

  • 开销卷回父图。 子图自己已经逐节点扣过费了;子图 COMPLETED 时,AgentExecutorBlock 把子图总 cost 作为 reconciled_cost_delta 合并进自己的 stats(blocks/agent.py:177),这样父图的 graph_stats.cost 反映完整的子图花费。extra_steps 同理把子图节点数卷进父图的步数统计(manager.py:647)。
  • 超时豁免。 前面说过,AgentExecutorBlockexecution_timeout_seconds = None(blocks/agent.py:28),因为子图各节点自己有时限,外层不该再卡一道。

巧妙之处: 嵌套没有引入任何新机制。子 agent 就是「再调一次入口函数 + 监听事件总线 + 把子图成本卷回」。整个第 3 章的引擎被原样复用,ExecutionContextparent_execution_id/dry_run 字段沿调用链传下去——这是一个把「递归」做得几乎免费的设计。

5. 把四件事串起来:ExecutionContext

上面四个能力(对账、dry-run、人审、嵌套)的开关全挂在一个对象上:ExecutionContext(data/execution.py:80)。它从入口一路传到每个块,携带:身份(user/graph/node id)、安全设置(dry_runsensitive_action_safe_mode)、层级(parent_execution_idroot_execution_id)。

add_graph_execution(execution_context) ──┐
_on_graph_execution │ 同一个 context
execute_node │ 逐层 model_copy 透传
block.execute(execution_context=...)
└─ dry-run? 敏感动作? 子 agent? 全看 context

一个对象统一承载「这次执行的所有横切关注点」,是这套设计能保持简洁的关键。

6. 边界与局限

  • 动态计费依赖块如实上报 stats(token/时长);块少报就少收。对账只在节点终态触发一次。
  • dry-run 的仿真质量取决于 simulate_block 背后的 LLM,不保证和真跑的输出形状一致——只是「像样的假数据」。
  • 嵌套的成本卷回发生在子图 COMPLETED 事件;子图若以非完成态结束,卷回逻辑走的是不同分支(blocks/agent.py:159 的状态过滤)。

7. 横向对比

  • 预扣+对账和云厂商的计量计费一脉相承(预留额度 → 按实结算),在 agent 平台里少见——多数开源 agent 框架不计费,这是 AutoGPT 作为托管商业平台的必备件。
  • dry-run 仿真比「mock 每个工具」更省事:不用为每个块写 mock,直接让 LLM 顶上。代价是不精确。
  • 嵌套复用入口函数和 Temporal 的 child workflow 思路一致,但实现轻得多(一个块 + 事件总线监听)。

8. 代码地图

主题文件符号
成本类型(含动态判定)autogpt_platform/backend/backend/blocks/_base.pyBlockCostTypeBlockCost
预扣executor/manager.pyexecutor/billing.pybilling.charge_usage
事后对账executor/manager.pyexecutor/billing.pycharge_reconciled_usage
取块成本data/credit.pyget_block_costBLOCK_COSTS
dry-run 分流executor/manager.pyexecute_node(simulate_block 分支)
dry-run 校验放行executor/utils.pyblocks/_base.pyvalidate_execBlock._execute
人在回路blocks/_base.pyBlock.is_block_exec_need_review
子 agent 块blocks/agent.pyAgentExecutorBlock
贯穿全程的上下文data/execution.pyExecutionContext