ADK Go 2.0:图基工作流引擎与多 Agent 编排
Agent Development Kit Go 2.0 发布,新增图基工作流、内置人工介入机制、动态编排。显著提升多 Agent 应用的可靠性和灵活性。
Agent Development Kit Go 2.0 发布,新增图基工作流、内置人工介入机制、动态编排。显著提升多 Agent 应用的可靠性和灵活性。
现实世界中的 agent 应用很少像发送单个 prompt 那么简单。生产级 agent 必须进行分类、条件分支、扇出执行、请求人类批准、失败重试,以及循环直到完成。将这种复杂的编排表达为临时性的控制流很容易变得脆弱不堪。
自 1.0 版本发布以来,Agent Development Kit(ADK)for Go 一直在帮助 Go 开发者用简洁、符合 Go 习惯的 API 来构建生产级 agent——强类型、iter.Seq2 事件流,以及能自然融入现有 Go 服务的运行时。这个基础取得了真实的成功,正是它使下一步成为可能。
今天我们很高兴分享 ADK for Go 2.0。其核心是一个全新的、一级公民的方式来组合多 agent 应用:基于图的工作流引擎。与之同时推出的还有内置人类在环(HITL)作为基本原语、用纯 Go 编写的动态编排、LLM agent 模式,以及一个统一的节点运行时将所有这些整合在一起——单个 agent 和完整的图现在运行在相同的执行模型上。
如果你关注过 Python ADK 2.0,这会感到熟悉:它是同样的图优先方向,从头开始被设计得像 Go。
真实的 agent 应用很少是单个 prompt。它们需要分类、分支、扇出给专家、聚合结果、请求人类批准、失败时重试,以及循环直到完成。将其表达为临时性的控制流很容易变得脆弱。
ADK 2.0 让你将应用结构描述为一个由边连接的节点图,然后把执行交给一个调度器。这个调度器知道如何并发运行、持久化状态、暂停等待人类,以及之后恢复——甚至可以跨进程重启恢复。以下是链接节点有多简单的例子:
import "google.golang.org/adk/v2/workflow"
upper := workflow.NewFunctionNode("upper", upperFn, cfg)
suffix := workflow.NewFunctionNode("suffix", suffixFn, cfg)
edges := workflow.Chain(workflow.Start, upper, suffix)
wf, _ := workflowagent.New(workflowagent.Config{
Name: "simple_sequence_workflow",
Edges: edges,
})
这个 wf 就是一个 agent.Agent。它运行在你已经使用的同一个 runner、launcher 和 console 里——没有特殊的工具,也没有新的服务器。一个图就是一个 agent。
一个节点是任何实现 Node 接口的工作单元。你很少需要手写那个接口——ADK 为常见场景提供了类型化的节点构造函数:
函数节点包装一个普通的、类型化的 Go 函数。泛型为你推断输入/输出的 schema:
workflow.NewFunctionNode("classify",
func(ctx agent.Context, in string) (Category, error) { ... }, cfg)
发出函数节点是带有 emit 回调的函数节点,让单个函数可以流化事件或暂停等待人类,而无需降低到动态节点:
workflow.NewEmittingFunctionNode("progress",
func(ctx agent.Context, in Job, emit func(*session.Event) error) (Result, error) { ... }, cfg)
Agent 节点将任何 agent.Agent(比如 LlmAgent)放入图中。
Tool 节点将一个 tool.Tool 转换为图中的一个步骤。
Join 节点是扇入屏障:它等待所有前驱节点完成,然后把它们的输出作为一个 map 交给你。
Dynamic 节点让你在代码中编排逻辑(下面会详细讲)。
Workflow 节点将整个子工作流嵌入为单个节点——图是可组合的。
Parallel workers 在列表中的每个项目上并发运行一个节点,并聚合结果。
State-bound 节点(NewFunctionNodeFromState)通过 state:"<key>" 标记,直接把选定的会话状态值拉入一个类型化的 Params 结构——不需要手动的状态管道。
边连接节点,它们可以携带路由条件。一个节点发出一个路由值;匹配的边就会触发。这个简单的想法给了你所有需要的控制流形状:
b := workflow.NewEdgeBuilder()
b.AddRoutes(router, map[string]workflow.Node{
"question": answerNode,
"statement": commentNode,
"exclamation": reactNode,
})
b.AddFanOut(planner, researchA, researchB, researchC) // parallel branches
b.AddFanIn(join, researchA, researchB, researchC) // gather results
顺序链、条件路由器、扇出/扇入、嵌套子图,甚至循环(一个完成的节点可以被重新触发,所以循环是一级的)——都可以通过边和路由来实现。标准路由包括 StringRoute、IntRoute、BoolRoute、MultiRoute,以及一个在没有其他匹配时触发的 Default。对于更高级的配置,可以使用 Route 接口。
最有用的模式之一是使用一个模型作为路由器的大脑。LlmAgent 对用户的消息进行分类;一个简单的函数发出匹配的路由;图分派到正确的处理器:
User -> What time is it? Agent -> question answering question...
User -> Hello world! Agent -> exclamation reacting to exclamation...
User -> The sky is blue. Agent -> statement commenting on statement...
模型做出决定;图让它变得可靠、可观察、可恢复。(见 examples/workflow/routing/llm/。)
有时执行顺序直到运行时才知道:它取决于数据、循环计数、或者模型刚说的内容。为此,ADK 2.0 提供了动态节点,其中编排逻辑就是普通的 Go 代码,为每个子节点调用 RunNode(...):
greeter := workflow.NewDynamicNode("greeter_workflow",
func(nc agent.Context, in string, emit func(*session.Event) error) (string, error) {
return workflow.RunNode[string](nc, greeterNode, in)
},
workflow.NodeConfig{},
)
循环、条件、累积、跨动态列表的扇出——都用你已经熟悉的 Go 来表达。WithRunID、WithUseSubBranch、WithUseAsOutput 和 WithIsolationScope 这样的选项给了你对子节点身份、历史隔离和输出委托的精确控制。这就是 Python ADK 动态图在 Go 中的对应实现。
生产级 agent 经常需要一个人类在运行中途批准、更正或提供某些东西。在 ADK 2.0 中,任何节点都可以暂停图并向人类提问——工作流会耐久地等待答案:
event := workflow.NewRequestInputEvent(ctx, session.RequestInput{
InterruptID: "approve_refund",
Message: "Approve a $200 refund? (yes/no)",
ResponseSchema: schema,
})
// yield the event; the node moves to "waiting"
当人类在稍后的某个回合回复时,工作流就会恢复。你可以选择如何恢复:
Handoff——答案直接流向下一个节点。
Re-entry——暂停的节点用通过 ctx.ResumedInput(...) 获取的人类响应重新运行。
而且恢复是耐久的。运行状态存储在会话中,ADK 甚至可以通过扫描会话历史来重构暂停的工作流——所以一个工作流可以在进程重启后恢复,甚至可以跨不同的运行时恢复,因为中断格式与 Python ADK 共享。响应根据 schema 验证,恢复是幂等的,当出现不一致时你会得到清晰的错误信息(ErrInvalidResumeResponse、ErrNothingToResume)。
console launcher 和 Web UI 开箱即用地支持 HITL,都能呈现工具确认提示和工作流输入请求。
每个节点都可以带有一个指数退避和抖动的重试策略——不需要任何外部依赖:
cfg := workflow.NodeConfig{ RetryConfig: workflow.DefaultRetryConfig() }
// 5 attempts, 1s initial delay, 60s cap, 2x backoff, full jitter
添加每个节点的 Timeout,用 WithMaxConcurrency(n) 限制全图并发,并隔离并行分支,这样一个分支的输出永远不会泄漏到另一个分支的 LLM 提示历史中。调度器为你处理 goroutines、channels、背压和取消。
ADK 2.0 为 LLM agent 引入了模式——Chat、Task 和 SingleTurn——这样协调器可以和用户聊天,同时子 agent 安静地完成任务或进行单次运行。正确的辅助工具(finish_task、single_turn、task)会根据每个 agent 的角色自动安装。
在幕后,runner 现在通过驱动普通 LlmAgent 使用和驱动工作流相同的节点运行时。好处是:单 agent 应用和完整图共享一个执行模型,人类在环现在对普通 LLM agent 也有效——不仅仅在工作流内部。
我们还简化了编程模型:ToolContext 和 CallbackContext 现在统一为一个 agent.Context——无论你是在写工具、回调还是图节点,都只需要学一个类型——而节点/agent 执行会显示在一个一致的遥测 span 树中,所以你可以清楚地看到你的图究竟做了什么。
ADK 2.0 是高度后向兼容的——整个工作流引擎是你可以选择加入的新包。伴随着统一运行时有几处新增和破坏性的改变;每处都有简单的、机械式的修复:
节点和节点函数签名现在采用 agent.Context。如果你写节点或节点函数,改变第一个参数从 agent.InvocationContext 到 agent.Context(它嵌入 InvocationContext,所以你使用过的所有方法仍然有效):
// before: func(ctx agent.InvocationContext, in string) (string, error)
// after: func(ctx agent.Context, in string) (string, error)
统一的上下文。ToolContext 和 CallbackContext 已移除——工具、回调和工作流节点都直接接收 agent.Context。如果你在测试中模拟了上下文,agent/context_mock.go 仍然保留;使用该文件中的 StrictContextMock 作为你的测试替身。
自定义 InvocationContext 实现需要两个方法:IsolationScope() 和 ResumedInput(id string)。大多数代码嵌入提供的实现,这两个方法会自动获得。
事件流更丰富了。事件现在携带节点字段(IsolationScope、Output、Routes、RequestedInput)和一个元数据字段(NodeInfo)。如果你在测试中对精确的 session.Event 相等性进行了断言,要预期新字段的出现;自定义会话存储应该持久化这些字段。
llmagent.New 可能会安装特定于模式的工具。如果你设置了子 agent 模式,有效的工具集会反映这一点;task-mode agent 不能用作静态图节点。
session.NewEvent 现在采用一个上下文。签名现在是 NewEvent(ctx context.Context, invocationID string)。通过把已在作用域内的 context.Context 作为第一个参数传递来迁移调用点。
这就是全部改变。runner.Run/RunLive、agenttool 和 llmagent 回调的公开签名没有改变。如需逐步的迁移前/后说明,见 ADK Go 2.0 迁移指南。
最快感受 ADK 2.0 的方式是运行新的工作流示例:
go run ./examples/workflow/basic/
go run ./examples/workflow/routing/llm/ # LLM-as-router
go run ./examples/workflow/dynamic/hitl/ # dynamic + human-in-the-loop
go run ./examples/workflow/hitl_rerun/ # HITL with re-entry resume
go run ./examples/workflow/complex/ # a larger, multi-shape graph
ADK 1.0 证明了在 Go 中构建严肃的 agent 可以是清晰和富有成效的。ADK 2.0 更进一步:将这些 agent 组合成可靠、可观察、可恢复的工作流——作为一个图、用地道的 Go、在需要时引入人类。
我们迫不及待地想看到你们将构建什么。
——ADK for Go 团队