Day 05 / 共 20 天 · 第 1 周收官

一次执行的完整旅程

把前四天串成一条故事线:你在前端点"运行"一个 Agent(Graph),到它执行完,数据在整个平台里怎么流动。这是理解 AutoGPT Platform 全局的关键一天。

💡 今天的类比世界观:一次执行 = 一个"快递包裹"的旅程 从你点"运行"到执行完,就像寄一件快递:点运行 = 下单并预付运费(触发 + 扣费)入队 = 包裹进分拣中心排队(解耦,前端不用干等)起始节点开跑 = 从第一个中转站发出数据流向下游 = 一站站转运凭证注入 = 到需要通关的站点出示证件实时推送 = 你手机上的物流轨迹一条条更新。今天顺着"包裹旅程"走一遍全平台。

👶 小白:为什么要"先扣费、再入队",不直接开跑、跑完再按实际用量收钱?

👨‍🏫 老师:跟快递"先付运费才收件"一个道理。若先跑后收,用户余额不足时钱已经烧在半路(LLM 调用真花了钱),平台亏定了。所以先预扣(押运费)挡住余额不足的,再入队解耦——前端点完就能走、不用盯着,执行进程从队列慢慢取件处理。Day 16 会讲这套"预扣 + 对账"如何多退少补。

L01

旅程全景

假设你搭好了"网页摘要邮件"Agent(Day 01 的例子:读网页→LLM总结→发邮件),点了"运行"。整条链路:

前端 → REST:POST 执行请求,REST 查余额、扣费
REST → RabbitMQ:建执行记录、发到队列,立刻返回
executor 消费:从队列取任务,起始节点入执行队列
拓扑执行:Block 逐个跑,输出流向下游输入,齐了才入队
凭证注入:每个 Block 执行前注入所需凭证、预扣费
实时推送 + 对账:进度经 Redis 推给前端 WebSocket;跑完按真实用量对账
L02

① 触发与扣费

前端 POST /api/graphs/{id}/execute/{version}api/features/v1.py:1862)。REST 先做付费墙检查:

# v1.py:1848 挂了 Depends(enforce_payment_paywall)
# 先查余额 ≤0 → 返回 402(付费墙),否则 add_graph_execution(...)
读法:执行前先看你钱够不够——余额 ≤0 直接返回 402(Day 04 见过的付费墙状态码),根本不让跑。因为这是计量收费平台(Day 16),跑 Agent 要花 credit。
为什么执行前就检查余额? 避免"跑到一半没钱了"的尴尬——尤其调用真实付费 API(LLM、第三方服务)会实打实花平台的钱。先确认你有余额才开跑,是计量 SaaS 的基本防护。
L03

② 入队解耦

add_graph_executionexecutor/utils.py:1171)建库记录 + 发 MQ:

# utils.py 概念
create_graph_execution(...)                    # 在 DB 建执行记录(含起始节点 QUEUED)
publish_message(routing_key="graph_execution.run", ...)  # 发到 RabbitMQ
# REST 立刻返回执行 id,不等它跑完
读法:REST 不亲自跑——建好记录、把任务扔进 RabbitMQ 队列,立刻返回(Day 04 的"接单/干活分离")。这就是 API 层和执行引擎的解耦点。前端拿到执行 id 后,转而用 WebSocket 订阅这个执行的实时进度。
L04

③ 起始节点开跑

executor 进程(ExecutionManager)从 RabbitMQ 消费到任务,交给线程池的 ExecutionProcessor。核心调度在 _on_graph_executionexecutor/manager.py:942):

# 概念:建一个进程内的 ExecutionQueue,预填起始节点
# 起始节点(Day 03 的 starting_nodes)在 create_graph_execution 时已建为 QUEUED
# 主调度循环:从队列取节点 → 预扣费 → 异步执行 → 取输出 → 入队下游
读法:executor 先获取一把分布式锁(保证同一执行全集群只在一个进程上跑),然后进入调度循环——从"起始节点"开始,取一个节点执行,产出的输出决定下游哪些节点能入队。起始节点就是 Day 03 讲的"没有入边的节点 + INPUT 节点"。
📝 举个例子:一个节点的状态一路怎么变(ExecutionStatus) 状态机定义在 data/execution.py:137-164,一个正常跑通的"读网页"节点会经历:
QUEUED(已入队等 executor 取)→ RUNNING(executor 取到、正在跑 run())→ COMPLETED(成功,输出已产出)。
若中途出错(比如网页 404 触发 yield "error"):RUNNING → FAILED。整张图(graph execution)也有同一套状态,全部节点跑完才 COMPLETED。这套转移表限制了"合法的下一个状态",防止乱跳。
L05

④ 数据流向下游(拓扑执行的核心)

🤔 痛点:一个"汇总"块有两个上游,我怎么知道该等还是该跑? 如果只是"谁的输出到了就立刻跑下游",那"汇总"块在第一个上游到达时就会仓促执行——可另一个上游的数据还没来,结果全错。图里有分叉、汇聚、并行,执行顺序到底怎么保证正确?
💡 本质:攒输入 + "所有必需输入齐了才入队" = 拓扑执行 引擎不按"写好的顺序"跑,而是数据驱动:每个上游输出到达就写进下游节点的输入槽(攒着),每次都用 validate_exec 检查"这个下游的必填输入齐了没"——不齐就只落库等待,齐了才标 QUEUED 入队。这样无论图多复杂,节点永远在输入完整时才执行。

一个节点跑完,它的输出怎么触发下游?_enqueue_next_nodesmanager.py:389):

# 对当前节点每条 output_link:
# 1. 输出值匹配连线的 source_name 才继续
# 2. upsert_execution_input:把值写给下游节点执行(攒输入)
# 3. validate_exec 检查下游输入齐了吗:
#    - 不齐 → 只落库,不入队(等其它输入到)
#    - 齐了 → 标 QUEUED,入执行队列
读法:关键规则:一个节点只有当它所有必需输入口都到齐时,才会被入队执行。因为一个节点可能有多个上游(比如"汇总"块等"研究"和"分析"都完成)。框架用"攒输入 + 齐了才入队"优雅处理了汇聚(fan-in)和分叉(fan-out)。
汇聚(fan-in):汇总块等两个上游都到齐才入队 研究块 ✔ 已完成 分析块 ⏳ 还在跑 汇总块 输入槽:1/2 到齐 → 等待 值已写入 还没来 汇总块 2/2 齐 → QUEUED 入队 等分析也完成后
汇总块有两个上游:研究块先完成、值写进它的输入槽,但分析块还没跑完——此时汇总块只落库不入队(1/2 不齐);等分析块也完成、2/2 到齐,才标 QUEUED 入执行队列。这就是 validate_exec "齐了才跑"的规则。
这就是"拓扑执行" 数据像水一样从起始节点往下流。每个节点等自己所有输入口都有水了才启动,启动后把结果注入下游的输入口。有多个上游的节点,会耐心"攒齐"所有输入才跑。这保证了执行顺序正确——不会有节点在输入没齐时就仓促执行。静态连线(Day 03)的值还会回填给等待中的节点。这套机制让任意复杂的图(分支、汇聚、并行)都能正确执行。
L06

⑤ 凭证注入执行

单个节点执行 execute_nodemanager.py:142)执行前,注入凭证(manager.py:263):

# 遍历 Block input schema 里的凭证字段
credentials, lock = await creds_manager.acquire(user_id, credentials_meta.id)
extra_exec_kwargs[field_name] = credentials    # 以 kwarg 注入
# 然后:block.execute(input_data, **extra_exec_kwargs)
读法:Block 需要 API Key/OAuth 时,执行引擎从凭证库取出已解密、已刷新、且加锁的凭证,以参数形式注入 run()(Day 08/17 细讲)。执行前还 charge_usage 预扣 credit(Day 16)。凭证加锁保证"同一套凭证同一时刻只被一个 Block 用"。
回忆 Day 02:Block 的 run(self, input_data, *, credentials, **kwargs)——这个 credentials 就是在这里被框架注入的真实凭证实体。Block 代码里从不硬编码密钥,密钥只在执行瞬间注入——安全(Day 17)。
L07

⑥ 实时推送与对账

两件收尾事:

  • 实时推送:每次节点状态变化,executor publish 事件到 Redis 事件总线;WebSocket 进程订阅后转发给前端(conn_manager.py)——你在画布上看到节点一个个亮起、数据流动(Day 15)。
  • 对账:Block 跑完拿到真实用量,charge_reconciled_usagebilling.py:200)算 delta = 真实成本 - 预扣——多退少补(Day 16)。
"预扣 + 对账"为什么这么设计? 很多 Block 的真实成本执行前算不准(比如 LLM 按 token 计费,不跑完不知道用了多少 token)。所以:执行前按历史估算预扣一笔(保证有余额),执行后按真实用量对账——多扣了退你、少扣了补上。这和你加油/充电时"先预授权冻结一笔、结束后按实际扣款"一模一样。Day 16 会深入这套精妙的计费机制。
L08

🎓 第 1 周收官 + 动手

第 1 周(Day 01-05)你已建立完整心智

  • Day 01 全景:从自主智能体到平台、Block+Graph 心智
  • Day 02 Block:有输入输出口和 run() 的积木
  • Day 03 Graph:Node+Link 连成的智能体
  • Day 04 运行平台:多进程 + 队列 + 中间件
  • Day 05 执行旅程:触发→扣费→入队→拓扑执行→注入→推送→对账

你现在理解了"一个 Agent 如何被执行"的全局。下周(Day 06-10)深入 Block 系统——基类、真实块、凭证、成本、SDK。

✋ 动手:验证旅程

P=autogpt_platform/backend/backend
grep -n 'def add_graph_execution\|publish_message' $P/executor/utils.py | head
grep -n 'def _on_graph_execution\|_enqueue_next_nodes\|upsert_execution_input' $P/executor/manager.py | head
grep -n 'creds_manager.acquire\|charge_usage\|charge_reconciled' $P/executor/manager.py $P/executor/billing.py | head
下周预告 · Day 06:深入 Block 基类与 Schema——BlockSchema、_execute() 执行生命周期(校验→人审→run→输出校验)、BlockType/BlockCategory 分类、注册发现的严格校验。
← Day 04 运行 Day 06 · Block 基类 →