Google Cloud 于 2026 年 9 月 30 日公开 ADK Workflow 图编排教程:JoinNode 按节点名聚合并行分支、parallel_worker 保持输入顺序、ctx.run_node 动态调度,缺 rerun_on_resume 直接抛 ValueError,前置部署 Forward Deployed Engineer(FDE)可照抄六种模式选型表
Google Cloud 于 2026 年 9 月 30 日公开 ADK Workflow 图编排教程,用退款流程演示六种编排模式与三道硬报错边界。
这条教程落在「前置部署」Forward Deployed Engineer(FDE)每天要交的那一段:把客户现场的流程拆成节点,再决定每一步由代码、模型还是人接手。「前置部署」Forward Deployed Engineer(FDE)在 Agent 编排上的选择,通常决定这套系统上线之后客户能不能自己改;Google 的 Agent Development Kit(ADK)把「谁决定下一步」变成一份显式声明,「图编排」的第一步也就从选框架变成了分清依赖,「前置部署」Forward Deployed Engineer(FDE)因此可以把编排决策写进交付文档,而不是留在某个人的记忆里。作者是 Google Cloud 的 Annie Wang 与 Shangjie Chen,文章用一条退款流程把「并行分支」「人工介入」「批量处理」与动态调度四种形态摆在一起比较,「前置部署」Forward Deployed Engineer(FDE)读完能得到的不是概念,而是一张按需求查模式的对照表,加上四个会直接抛异常的边界。FDE 在企业级 AI 落地时最容易失控的正是流程分支,而这篇教程的写法恰好是「先给形状,再给报错」。
教程从一个 agent 起步:一个 Agent 挂三个查询工具(fetch_order、fetch_payment、fetch_history),指令里写清先查订单、再查支付、再查退款历史,然后按规则判拒付或超期就拒、金额低于 50 美元且过去 12 个月退款少于 3 次就自动通过,最后写一封回信。作者指出,这条 prompt 里其实已经藏着明确的工作切分:三次查询、一次决策、一封回信。把它们拆成节点,才有机会分别决定每一段怎么跑,这正是「前置部署」Forward Deployed Engineer(FDE)在客户现场反复要做的那件判断:哪些步骤彼此独立、哪些必须等前一步的结果。FDE 如果把这条判断留在提示词里,得到的就是一个每次运行顺序都可能不一样的流程。
三次查询都只需要订单号,彼此之间没有依赖,所以可以并行。在 ADK 里,把函数名放进一个嵌套元组就表示同时启动,edges 写成 (START, (fetch_order, fetch_payment, fetch_history), join_case),意思是三条分支一起开始,全部结束后再走 join_case。JoinNode 做三件事:等所有前驱完成、把各分支输出聚合成一个以节点名为键的字典、把并行分支合并回同一条分支上下文,所以下游节点直接读 node_input["fetch_payment"] 就行,不需要再花一次模型调用去汇总。有个容易被忽略的细节:直接连到 START 的分支会以 __START__ 为键进字典。作者的判据是「依赖决定边」,而不是提示词里列出的顺序;如果支付查询需要订单查询返回的交易号,这两步就必须改成顺序执行。「前置部署」Forward Deployed Engineer(FDE)把这条判据写进评审清单,可以一次性砍掉一批本不该串行的等待。
JoinNode 有两个会安静失效的限制,值得单独记下来。第一,它的输出永远是字典,需要别的形状就得在它下游的节点里转换。第二,如果 join 声明了一个落在条件分支上的前驱,而那条分支没有被走到,join 就永远不会触发;工作流会在实际执行到的分支跑完后正常结束,join 以及它下游的一切被静默跳过,任何地方都不会报错。第三个细节在校验上:JoinNode 的 input_schema 是针对每个前驱的输出分别校验的,不是针对聚合后的字典;任意一个值校验失败,整个工作流失败。把 input_schema 写成原始 JSON schema 字典或 types.Schema 时校验不会生效,要真正拦住错误就得用 pydantic 模型。「前置部署」Forward Deployed Engineer(FDE)在客户现场最怕的就是这种静默跳过,FDE 应当在交付前用一个条件分支不命中的用例把这条路径跑一遍,确认业务流程能接受「少了半截」的结果。
Workflow(...) 在构造时就会建图并检查,重复的节点名与无条件环在这一步报错,而不是跑到一半才炸;这是把结构性错误前移到启动阶段的设计。工作流的输出取自没有出边的终端节点,规则按终端节点数量分三种:只有一个终端节点执行并产出非空结果时,它就是工作流输出;多个分支都产出结果时,工作流会在结束时以 ValueError 失败,因为边的声明里没有任何信息说明该选哪一个,需要并行结果就必须先送进 JoinNode 聚合。另有一个导入层的坑:START 从 google.adk.workflow 导出,写成 from google.adk import START 会在运行前直接 ImportError,因为顶层包只导出 Agent、Context、Event、Runner 与 Workflow。「前置部署」Forward Deployed Engineer(FDE)可以把这条当成新成员上手的第一个检查项:报错发生在导入行,而不是在编排逻辑里。FDE 带教时先讲这三个导入与终端节点的规则,比讲编排理念省时间。
路由是「选择下一个分支」的节点,教程把「确定性路由器」与「智能体路由器」拆开讲。意图识别用智能体路由器:模型把客户消息分类成 REFUND、EXCHANGE 或 CLARIFY,再由一个小函数发出 Event(route=...) 把请求送进对应流程,模型负责理解语义,图负责定义可用去向。退款策略用确定性路由器:refund_policy 是一个纯函数,输入金额、天数、是否有未结拒付、过去 12 个月退款次数,返回 AUTO_APPROVE、DENY 或 MANUAL_REVIEW;有未结拒付或超过 30 天判拒,金额低于 50 美元且次数少于 3 次自动通过,其余转人工。作者的对照结论是:规则写清楚的策略用代码判,模型调用次数为零且可以脱离模型测试;语义难以写成规则的场景才交给模型判,但那一次模型调用省不掉。「前置部署」Forward Deployed Engineer(FDE)在客户现场常被问「这个判断为什么不能交给大模型」,这篇给的是一个可以直接引用的分界线。
需要人拍板的分支靠 RequestInput 实现:节点 yield 一个 RequestInput,带上 interrupt_id、给审核人看的 message、payload 与 response_schema,这次运行就停在原地;审核人可能几分钟也可能几天后才回。恢复时节点靠 rerun_on_resume=True 重新执行,答案放在 ctx.resume_inputs 里,再按答案路由到通过或拒绝。ADK 自带两种写法:上面这种单节点模式在恢复时自己读 ctx.resume_inputs;另一种两节点模式里,一个节点发 RequestInput,审核结果作为下一个节点的 node_input 到达,那份样本文件里根本没有 ctx.resume_inputs,照抄时找它只会白费时间。「前置部署」Forward Deployed Engineer(FDE)在客户现场做审批流时,FDE 可以直接复述这条分界:单节点省一个节点但逻辑集中,两节点把「问」与「答」分成两段,代价是要显式把答案传回去。
批量场景用 parallel_worker=True:一个节点对输入列表的每一项各跑一次,结果按输入原顺序汇总成一个列表,因此不需要再挂 JoinNode。这个开关对函数与 agent 都生效;max_parallel_workers 可以给并发数封顶。错误传播规则很硬:任意一项失败,剩余任务被取消并立刻抛出错误。作者也说明了适用条件——策略计算本身很小时并发没什么收益,只有当每一项都在等 API 或模型调用时才划得来。「前置部署」Forward Deployed Engineer(FDE)在客户现场做批量审批、批量分类时,FDE 可以把「列表交给 worker、不要自己在节点里写 for 循环」当成默认动作,这样批量规模变化时图不用改。
教程里有三个报错值得抄进排查手册。第一,worker 函数的参数必须正好叫 node_input,别的名字会被当成会话状态绑定,找不到就抛 ValueError: Missing value for parameter。第二,要把字典的键绑定到多个参数上,得在装饰器里加 parameter_binding='node_input'。第三,把 agent 当 worker 用时,指令里不要写 {node_input},花括号会去会话状态里替换,那里没有这个键,会报 KeyError: Context variable not found。同一个 node_input 命名规则也适用于动态调度,它是 ADK 里唯一一个直接传递、不做状态查找的参数名。「前置部署」Forward Deployed Engineer(FDE)在客户现场交付时,FDE 把这三条写在交接文档第一页,能省掉一串「为什么它去翻 state」的解释。
当流程形状要等运行中才知道时,用 ctx.run_node 做动态编排:父节点用普通 Python 控制流(循环、条件)启动子节点并 await 结果,子节点可以是函数、agent 或另一个 Workflow。两个参数值得记:use_sub_branch=True 让并发子节点的事件留在各自分支上,use_as_output=True 让子节点的产出直接成为父节点的输出,不必再发一次事件。教程给的例子是:先查争议退款是否牵出第二笔交易,再由代码决定接下来查什么,同时用「每个发现最多三次后续、最多两层调查」把探索封顶。「前置部署」Forward Deployed Engineer(FDE)在客户现场遇到开放式排障时,FDE 最需要的正是这种「模型建议方向、代码限制工作量」的写法,而不是让模型自由循环到预算烧完。
动态调度有四条硬规则,违反任何一条都会当场失败或静默丢结果。第一,任何调用 ctx.run_node 的节点必须配 rerun_on_resume=True,缺了它,运行时第一次调用就抛 ValueError,原因是恢复要能把父节点重跑一遍。第二,嵌套的动态节点自己也是父节点,同样要带这个标记。第三,生成器节点要用 yield Event(output=...) 产出结果,写 return value 在 async 生成器里是 SyntaxError,在普通生成器里更糟——不报错,值被直接丢掉。第四,恢复时父节点从头重跑,已成功的 run_node 调用从会话历史取回缓存输出,查询不会重复执行,前提是副作用留在子节点内,否则父节点复活时会真的再做一遍。「前置部署」Forward Deployed Engineer(FDE)在客户现场做长流程时,「副作用必须放在子节点」这一条几乎决定了一次中断之后能不能安全恢复;FDE 把它写进代码评审 checklist,比事后补幂等更省事。
教程给出的选型方法只有一句话:先问「输入到达之前,能不能把可能的流程画出来」,能画就写静态 edges,画不出来或形状随结果变化才用动态节点。六种模式与需求的对应关系是:独立任务再汇总用 fan-out 加 fan-in;固定规则选分支用确定性路由器;需要理解语义选分支用智能体路由器;需要人先拍板用 RequestInput;同一段逻辑套在一份列表上用 parallel worker;要让结果决定下一步用动态节点。作者也补充了一条边界:静态图一样可以表达循环。「回边」加一个限制修订次数的路由器就够了,静态说的是「可能的连接」,不是必然的路径。「前置部署」Forward Deployed Engineer(FDE)在方案评审时用这张表逐项对照,可以把「要不要上动态编排」从偏好之争变成需求分类。
退出与回退的路径作者也写清楚了。一个小的开放式任务只需要一个 agent 加它的工具,不必先上 Workflow;动态编排失控时的退路,是把动态节点收回到静态 edges,保留一个能做条件回边的图;反过来,静态图里也可以只把需要动态的那一段换成动态节点,其余部分继续用边声明。并行分支的回退是把 JoinNode 之前的分支改回串行,代价是失去并行收益,换来的是分支顺序可预测。「前置部署」Forward Deployed Engineer(FDE)在客户现场做演进式交付时,FDE 建议先上静态图跑通,再把唯一需要动态的阶段替换掉,这样每一次改动都只动一个变量。
文章与 ADK 文档都写明了局限,这部分比新模式更值得信。第一,这是一篇以退款为例的教程,不是生产部署复盘,没有给出任何吞吐、延迟或成本的前后对比数字,读者无法据此推算收益。第二,并行 worker 只在每项都在等 I/O 时划算,纯计算任务上并发不会变快。第三,路由器只能在已经定义好的路径里选,agent 路由器的判断可以变化,但静态图里可能的连接不会因为它而变化。第四,JoinNode 遇到未命中的条件分支会静默跳过下游,没有报错也没有告警,只能靠测试覆盖来发现。第五,教程没有覆盖多租户权限与生产级幂等,中断恢复的语义依赖会话历史,跨会话的长时间待审需要另行设计。「前置部署」Forward Deployed Engineer(FDE)把这几条写进方案的风险栏,可以避免把教程里的示例当成投产承诺。
- 「前置部署」Forward Deployed Engineer(FDE)上手 ADK 编排的第一个动作是画边:把三次独立查询写成 (START, (fetch_order, fetch_payment, fetch_history), join_case),凡是彼此不依赖的节点都放同一个嵌套元组里,让依赖来决定边。
- FDE 区分两类判断:规则能写清楚的用确定性路由器(纯函数、零模型调用、可脱网测试),语义难以写成规则的才用智能体路由器(一次模型调用省不掉),两者可以同时出现在同一张静态图里。
- 「前置部署」Forward Deployed Engineer(FDE)抄参数名前先改函数签名:worker 与动态子节点的参数必须叫 node_input,多值传递用 parameter_binding='node_input' 绑定字典键,agent 当 worker 时指令里不要出现 {node_input}。
- FDE 给人工审批流加暂停时记住两个变体:单节点模式恢复后读 ctx.resume_inputs,两节点模式下审核结果作为下一节点的 node_input 到达、那份样本里没有 resume_inputs;调用 ctx.run_node 的节点必须带 rerun_on_resume=True。
- 「前置部署」Forward Deployed Engineer(FDE)做长流程的幂等设计时,把副作用全部放进子节点:父节点恢复会从头重跑,已完成的 run_node 调用从会话历史取缓存输出,绕过这一点就会重复执行。
- FDE 给动态编排设上限:让模型建议调查方向,用代码限制后续数量与层数(教程示例是每个发现最多三次、最多两层),避免开放式循环把预算耗在无界探索上。
- 「前置部署」Forward Deployed Engineer(FDE)的退出与回退方式:小任务直接退回单 agent 加工具;动态编排失控就把动态节点收回静态 edges;并行分支出问题就把 JoinNode 之前的分支改回串行。
原始来源:
- https://cloud.google.com/blog/topics/developers-practitioners/graph-workflows-in-adk-everything-you-need-to-know
- https://github.com/google/adk-python/blob/main/docs/guides/workflow/workflow/index.md
- https://github.com/google/adk-python/blob/main/docs/guides/workflow/join_node/index.md
- https://github.com/google/adk-python/blob/main/docs/guides/workflow/dynamic_nodes/index.md
- https://github.com/google/adk-python/blob/main/docs/guides/workflow/parallel_worker/index.md
评论(0)
- 暂无评论。