[Go to site: main page, start]

Skip to content

为智能体工作流构建图路由

Supported in ADKPython v2.0.0Go v2.0.0

ADK 中的基于图的工作流将智能体逻辑定义为由执行节点和边组成的图,让你能够构建更可靠的流程,将人工智能(AI)推理与代码逻辑相结合。这些工作流允许你创建逻辑化的执行节点路由,封装代码函数、AI 驱动的智能体、工具和人工输入。通过显式映射路由逻辑,这种方法允许你在代码中定义具体的、逐步执行的流程工作流,相比纯粹基于提示词的智能体,提供了更高的精度和可靠性。

节点间条件路由的任务图

图 1. 任务图及其路由代码的可视化展示。

root_agent = Workflow(
  name="routing_workflow",
  edges=[
    ("START", process_message, router),
    (router,
      {
        "output-1": response_1,
        "output-2": response_2,
        "output-3": response_3,
      },
    ),
  ],
)

ADK Go v2.0.0 提供了以下基于图的工作流方式:

图引擎workflowagent + workflow.Edge):一个节点-边图 API, 直接对应 Python 的 Workflow(edges=[...])。 节点通过 workflow.NewFunctionNodeworkflow.NewAgentNodeworkflow.NewDynamicNode 定义,边声明为 []workflow.Edge, 整个图封装在一个 workflowagent.New 调用中:

edges := workflow.Concat(
    workflow.Chain(workflow.Start, classifyNode),
    []workflow.Edge{
        {From: classifyNode, To: responseA, Route: workflow.StringRoute("output-1")},
        {From: classifyNode, To: responseB, Route: workflow.StringRoute("output-2")},
        {From: classifyNode, To: responseC, Route: workflow.StringRoute("output-3")},
    },
)
rootAgent, _ := workflowagent.New(workflowagent.Config{
    Name:  "routing_workflow",
    Edges: edges,
})

使用基于图的智能体工作流的优势在于,相比基于提示词的智能体,在控制性、可预测性和可靠性方面有显著提升。通过在代码中定义整体流程工作流,你可以更好地控制任务的路由和执行方式。这种结构化的节点定义提高了智能体的可预测性,并增强了需要明确定义步骤和流程管理的复杂任务的可靠性。

通过查看基于图的智能体工作流,开始使用 ADK 中基于图的工作流。

节点

图由执行节点组成。这些节点可以是智能体、ADK 工具、人工输入任务或你编写的代码函数。节点可以从之前执行的节点获取输入,并通过事件对象发出数据。

以下是一个简单的函数节点示例,它处理文本输入并发送文本输出:

from google.adk import Event

def my_function_node(node_input: str):
    input_text_modified = node_input.upper()
    return Event(output=input_text_modified)

在 ADK Go v2.0.0 中,主要的节点类型是 workflow.NewFunctionNodeFunctionNode 封装了一个普通 Go 函数:函数返回一个带类型的值, 框架会自动将其包装为 session.Event,设置 event.Output。 后续节点接收该值作为其带类型的 input 参数——无需手动写入状态或构造事件:

// newFunctionNodePipeline demonstrates workflow.NewFunctionNode as the primary
// v2 node type. A FunctionNode wraps a plain Go function: the function returns
// a typed value, and the framework automatically wraps it in a session.Event,
// setting event.Output. The successor node receives this value as its typed
// input parameter.
//
// This is the direct Go equivalent of the Python FunctionNode:
//
//  def my_function_node(node_input: str):
//      return Event(output=node_input.upper())
func newFunctionNodePipeline() (agent.Agent, error) {
    upperFn := func(_ agent.Context, input string) (string, error) {
        return strings.ToUpper(input), nil
    }

    suffixFn := func(_ agent.Context, input string) (string, error) {
        return input + " IS AWESOME!", nil
    }

    // workflow.NewFunctionNode wraps each function as a graph node.
    // workflow.Chain wires them in order: START → upper → suffix.
    // The output of upperFn is delivered as the typed input of suffixFn
    // via event.Output — no session state writes are needed.
    nodeA := workflow.NewFunctionNode("upper", upperFn, workflow.NodeConfig{})
    nodeB := workflow.NewFunctionNode("suffix", suffixFn, workflow.NodeConfig{})

    return workflowagent.New(workflowagent.Config{
        Name:        "function_node_pipeline",
        Description: "Demonstrates workflow.NewFunctionNode data flow via Event.Output.",
        Edges:       workflow.Chain(workflow.Start, nodeA, nodeB),
    })
}

有关在节点之间传输数据的更多信息,请参阅智能体工作流的数据处理

工作流图语法

你通过组合工作流智能体来定义图。本节提供常见路由模式的概述。

注意:工作流智能体的限制

你可以将大语言模型智能体添加到基于图的工作流中。但是,它们必须配置为单轮或任务模式。有关智能体模式的更多信息,请参阅 构建协作智能体团队

路由序列

顺序路由按照列出的顺序依次运行每个节点。

edges 数组使用 START 关键字表示图执行的开始,每个列出的节点按顺序执行:

edges=[("START", task_A_node)]  # 单节点运行
edges=[("START",
        task_A_node,
        task_B_node,
        task_C_node)]           # 3 个节点按顺序运行

workflow.Chain(workflow.Start, nodeA, nodeB, nodeC) 将节点连接为顺序边切片。每个节点的带类型返回值通过 event.Output 转发给下一个节点——无需写入会话状态:

// newSequentialNodes builds a two-step sequential workflow using the v2 graph
// engine. workflow.Chain wires the nodes in order; each node's typed return
// value is forwarded to the next node via event.Output.
//
// This is the Go equivalent of:
//
//  edges=[("START", task_A_node, task_B_node)]
func newSequentialNodes() (agent.Agent, error) {
    // task_A_node: transforms the user's input.
    taskANode := workflow.NewFunctionNode("task_A_node",
        func(_ agent.Context, input string) (string, error) {
            return "Summary: " + strings.TrimSpace(input), nil
        },
        workflow.NodeConfig{},
    )

    // task_B_node: receives task A's output as its typed input and produces
    // the final result. No session state reads needed.
    taskBNode := workflow.NewFunctionNode("task_B_node",
        func(_ agent.Context, summary string) (string, error) {
            return strings.ToUpper(summary), nil
        },
        workflow.NodeConfig{},
    )

    return workflowagent.New(workflowagent.Config{
        Name:        "sequential_workflow",
        Description: "Runs task A then task B in order via workflow.Chain.",
        Edges:       workflow.Chain(workflow.Start, taskANode, taskBNode),
    })
}

路由分支与条件执行

在 Python 中,分支通过一个返回 Event(route=...) 值的 FunctionNode 处理,edges 字典将该值分发到不同的节点。

def router(node_input: str):
    """根据 node_input 路由到任务 B 或 C。"""
    if condition(node_input):
        return Event(route="RUN_TASK_C")
    return Event(route="RUN_TASK_B")

task_B_node = Agent(name="task_B_agent") # 执行节点 B 的智能体

def task_C_node(node_input: str):
    """执行节点 C 的函数节点。"""
    return Event(output="Task C completed")

root_agent = Workflow(
    name="routing_workflow",
    edges=[
        ("START", task_A_node, router),
        (router,
          {
            # "路由值": 要运行的节点
            "RUN_TASK_B": task_B_node,
            "RUN_TASK_C": task_C_node,
          },
        ),
    ],
)

在 ADK Go v2.0.0 中,条件分发使用 workflow 图引擎。 节点将 Event.Routes 设置为一个或多个字符串路由键,每个 workflow.Edge 使用 workflow.Route 匹配器选择其后继节点:

  • workflow.StringRoute("category") — 匹配单个字符串值
  • workflow.IntRoute(n)workflow.MultiRoute[int]{1, 2, 3} — 匹配 整数值
  • workflow.BoolRoute(true) — 匹配布尔值
  • workflow.Default — 当同一源节点上没有其他路由匹配时匹配

以下是 Go 等效的 Python 路由器模式:

// classifyNode 根据消息发出 Routes=[]string{"BUG"}、
// ["CUSTOMER_SUPPORT"] 或 ["LOGISTICS"] 的事件。
edges := workflow.Concat(
    workflow.Chain(workflow.Start, processMessage, classifyNode),
    []workflow.Edge{
        {From: classifyNode, To: bugHandler,       Route: workflow.StringRoute("BUG")},
        {From: classifyNode, To: supportHandler,   Route: workflow.StringRoute("CUSTOMER_SUPPORT")},
        {From: classifyNode, To: logisticsHandler, Route: workflow.StringRoute("LOGISTICS")},
    },
)
rootAgent, _ := workflowagent.New(workflowagent.Config{
    Name:  "routing_workflow",
    Edges: edges,
})

workflow.EdgeBuilder 提供了一种流式替代方案,无需手动组装 []workflow.Edge 切片。该构建器的 AddAddFanOutAddFanIn 方法以更少的重复代码表达了相同的拓扑结构:

eb := workflow.NewEdgeBuilder()
eb.Add(workflow.Start, processMessage)
eb.Add(processMessage, classifyNode)
eb.AddRoute(classifyNode, bugHandler,       workflow.StringRoute("BUG"))
eb.AddRoute(classifyNode, supportHandler,   workflow.StringRoute("CUSTOMER_SUPPORT"))
eb.AddRoute(classifyNode, logisticsHandler, workflow.StringRoute("LOGISTICS"))

rootAgent, _ := workflowagent.New(workflowagent.Config{
    Name:  "routing_workflow",
    Edges: eb.Build(),
})

完整的可运行路由示例请参阅: 字符串路由整数/多值路由LLM 驱动的路由

预构建智能体:在状态中编码路由

当使用 sequentialagent / parallelagent / loopagent 而非图引擎时,没有 Event.Routes 分发。通过 OutputKey 将路由决策编码到会话状态中,并让下游智能体在其 Instruction 模板中检查它,或者使用带有基于 Escalate 退出的 loopagent——请参阅下面的循环和升级退出示例。

并行任务:扇出和合并路径

你可以创建将执行拆分到多个并行节点的图,通常你需要组装每个节点的输出以进行进一步处理。这种任务执行模式有两个阶段。工作流首先在启动多个并行任务时扇出,然后在这些任务完成后重新合并这些路径,再继续下一步。

连接到合并节点的任务

图 2. 并行任务节点的输出可以被组装和合并,然后再将结果传递给下一步。

你可以使用合并节点对象来完成合并步骤,它会等待每个并行任务完成,然后将这些节点的输出集合传递给下一个节点。

from google.adk.workflow import JoinNode

my_join_node = JoinNode(name="my_join_node")

edges=[
    ("START", parallel_task_A, my_join_node),
    ("START", parallel_task_B, my_join_node),
    ("START", parallel_task_C, my_join_node),
    (my_join_node, final_task_D),
]

注意:不完整节点导致合并节点卡住

合并节点对象只在其所有上游节点都提供了事件输出后才会继续执行。如果其中一个上游节点未能提供输出,合并节点将被卡住,工作流执行将停止。确保为向合并节点输出的任何节点包含容错输出。

ADK Go v2.0.0 为图引擎中的真正扇入提供了 workflow.NewJoinNode:从 workflow.Start(或任何共享源节点)扇出的边并行输入到合并节点,合并节点等待所有输入完成后,向前置节点名作为键的 map[string]any 发出输出到下一个节点。

workflow.EdgeBuilder 通过其专用的 AddFanOutAddFanIn 辅助方法使扇出/扇入连接变得简洁(如复杂工作流示例所示):

gatherNode := workflow.NewJoinNode("gather")

eb := workflow.NewEdgeBuilder()
eb.AddFanOut(workflow.Start, researchNodeA, researchNodeB, researchNodeC)
eb.AddFanIn(gatherNode, researchNodeA, researchNodeB, researchNodeC)
eb.Add(gatherNode, formatNode)
eb.Add(formatNode, synthesisNode)

rootAgent, _ := workflowagent.New(workflowagent.Config{
    Name:  "research_pipeline",
    Edges: eb.Build(),
})

以下代码片段展示了使用 workflow.NewJoinNodeEdgeBuilder.AddFanOut / AddFanIn 的完整扇出/合并模式:

// newParallelFanOut builds a fan-out / join workflow using the v2 graph engine.
// Three research nodes run in parallel from Start; workflow.NewJoinNode waits
// for all of them to complete and emits a map[nodeName]output to the format
// node, which assembles the results for a synthesis node.
//
// Graph topology:
//
//  START ─┬─> research_A ──┐
//         ├─> research_B ──┼─> gather (JoinNode) ─> format ─> synthesis
//         └─> research_C ──┘
//
// Python equivalent:
//
//  edges=[
//      ("START", research_A, my_join_node),
//      ("START", research_B, my_join_node),
//      ("START", research_C, my_join_node),
//      (my_join_node, format_node),
//      (format_node, synthesis_node),
//  ]
func newParallelFanOut() (agent.Agent, error) {
    researchA := workflow.NewFunctionNode("research_A",
        func(_ agent.Context, _ any) (string, error) {
            return "Fact about renewable energy.", nil
        },
        workflow.NodeConfig{},
    )
    researchB := workflow.NewFunctionNode("research_B",
        func(_ agent.Context, _ any) (string, error) {
            return "Fact about electric vehicles.", nil
        },
        workflow.NodeConfig{},
    )
    researchC := workflow.NewFunctionNode("research_C",
        func(_ agent.Context, _ any) (string, error) {
            return "Fact about carbon capture.", nil
        },
        workflow.NodeConfig{},
    )

    // workflow.NewJoinNode waits for all predecessors (research_A, research_B,
    // research_C) to complete and emits a map[nodeName]output to its successor.
    gatherNode := workflow.NewJoinNode("gather")

    // formatNode receives map[string]any from gatherNode and assembles a
    // combined prompt string.
    formatNode := workflow.NewFunctionNode("format",
        func(_ agent.Context, results map[string]any) (string, error) {
            return fmt.Sprintf("A: %v\nB: %v\nC: %v",
                results["research_A"],
                results["research_B"],
                results["research_C"],
            ), nil
        },
        workflow.NodeConfig{},
    )

    synthesisNode := workflow.NewFunctionNode("synthesis",
        func(_ agent.Context, prompt string) (string, error) {
            return "Combined report: " + prompt, nil
        },
        workflow.NodeConfig{},
    )

    // EdgeBuilder.AddFanOut fans workflow.Start out to all three research nodes.
    // EdgeBuilder.AddFanIn routes all three research nodes into gatherNode.
    eb := workflow.NewEdgeBuilder()
    eb.AddFanOut(workflow.Start, researchA, researchB, researchC)
    eb.AddFanIn(gatherNode, researchA, researchB, researchC)
    eb.Add(gatherNode, formatNode)
    eb.Add(formatNode, synthesisNode)

    return workflowagent.New(workflowagent.Config{
        Name:        "fan_out_workflow",
        Description: "Parallel research fan-out with JoinNode barrier and synthesis.",
        Edges:       eb.Build(),
    })
}

注意:不完整节点导致合并节点卡住

workflow.NewJoinNode 只在每个前置节点都发出 event.Output 后才会继续执行。如果前置节点在未发出输出的情况下失败,合并节点将被卡住,工作流执行将停止。为容易出错的前置节点附加 RetryConfig 以防止瞬时故障。

嵌套工作流

在构建更复杂的工作流时,你可能希望将特定任务的功能封装为可复用的工作流。一个或多个工作流智能体可以作为子智能体在另一个工作流智能体中使用,以实现此目标。

父工作流中的嵌套工作流

图 3. 嵌套工作流智能体作为父工作流中的子智能体。

from google.adk import Workflow

root_agent = Workflow(
    name="parent_workflow",
    edges=[
       ("START", task_A1, router),
       (router, {
            "RUN_WORKFLOW_B": workflow_B,
            "RUN_WORKFLOW_C": workflow_C,
            },
       ),
    ],
)

嵌套工作流的数据输出

嵌套 Workflow 对象的输出与单个节点的工作方式略有不同。当嵌套工作流完成其某个节点时,它会将数据传输到嵌套工作流图中的下一个节点,并且系统会将该节点的事件冒泡到父工作流,以实现流程可追溯性。当嵌套工作流完成其流程中的最后一个节点时,父节点从最终叶子节点提取数据,并将其作为嵌套工作流的输出发出。

ADK Go v2.0.0 通过两种互补方式支持嵌套工作流:

图引擎workflowagent + workflow.Edge):使用 workflowagent.New 创建的 workflowagent 本身就是一个 agent.Agent,因此可以用 workflow.NewAgentNode 封装,并作为节点用于另一个工作流的 edges 切片中。从外部图的角度来看,内部工作流作为单个节点运行完成,其终端输出作为外部图边上的节点输出发出:

innerNode, _ := workflow.NewAgentNode(innerWorkflowAgent, workflow.NodeConfig{})

outerEdges := workflow.Chain(workflow.Start, outerStepNode, innerNode, finalNode)
rootAgent, _ := workflowagent.New(workflowagent.Config{
    Name:  "parent_workflow",
    Edges: outerEdges,
})

以下代码片段展示了内部和外部图的构建过程。 workflow.NewAgentNode 封装了内部 workflowagent,使其可以放入外部图的 workflow.Chain 中:

// newNestedWorkflows shows how to nest one workflowagent inside another using
// the v2 graph engine. The inner workflowagent is wrapped with
// workflow.NewAgentNode and placed as a node in the outer graph's edge slice.
// From the outer graph's perspective the inner workflow is a single node that
// runs to completion before the edge to finalNode is followed.
//
// Python equivalent:
//
//  root_agent = Workflow(
//      name="parent_workflow",
//      edges=[("START", task_A1, workflow_B, final_node)],
//  )
func newNestedWorkflows() (agent.Agent, error) {
    // --- Inner workflow B ---
    innerStep1 := workflow.NewFunctionNode("inner_step_1",
        func(_ agent.Context, input string) (string, error) {
            return "[ES] " + input, nil // simulate translation to Spanish
        },
        workflow.NodeConfig{},
    )
    innerStep2 := workflow.NewFunctionNode("inner_step_2",
        func(_ agent.Context, spanish string) (string, error) {
            return "[EN] " + spanish, nil // simulate translation back to English
        },
        workflow.NodeConfig{},
    )

    // workflowB is a self-contained inner graph.
    workflowB, err := workflowagent.New(workflowagent.Config{
        Name:        "workflow_B",
        Description: "Translates input to Spanish then back to English.",
        Edges:       workflow.Chain(workflow.Start, innerStep1, innerStep2),
    })
    if err != nil {
        return nil, fmt.Errorf("workflowB: %w", err)
    }

    // --- Outer graph ---
    taskA1 := workflow.NewFunctionNode("task_A1",
        func(_ agent.Context, input string) (string, error) {
            return "Summary: " + strings.TrimSpace(input), nil
        },
        workflow.NodeConfig{},
    )

    finalNode := workflow.NewFunctionNode("final_node",
        func(_ agent.Context, result string) (string, error) {
            return "Final: " + result, nil
        },
        workflow.NodeConfig{},
    )

    // workflow.NewAgentNode wraps workflowB so it can be placed as a node
    // in the outer graph's edges slice.
    innerNode, err := workflow.NewAgentNode(workflowB, workflow.NodeConfig{})
    if err != nil {
        return nil, fmt.Errorf("NewAgentNode(workflowB): %w", err)
    }

    return workflowagent.New(workflowagent.Config{
        Name:        "parent_workflow",
        Description: "Runs task_A1 then the nested workflow_B then final_node.",
        Edges:       workflow.Chain(workflow.Start, taskA1, innerNode, finalNode),
        SubAgents:   []agent.Agent{workflowB},
    })
}

循环和升级退出

循环会重复一组步骤,直到满足终止条件。在 Python 中,这通过 edges 图中路由回较早节点的回边来表达。在 ADK Go v2.0.0 中,图引擎直接支持相同的模式:添加一条从下游节点回较早节点的边并附带路由条件,引擎将在每次迭代中以全新的生命周期重新激活目标节点。

def router(node_input: str):
    """根据 node_input 路由到任务 B 或 C。"""
    if condition(node_input):
        return Event(route="RUN_TASK_C")
    return Event(route="RUN_TASK_B")

root_agent = Workflow(
    name="routing_workflow",
    edges=[
        ("START", task_A_node, router),
        (router,
          {
            "RUN_TASK_B": task_B_node,
            "RUN_TASK_C": task_C_node,
          },
        ),
    ],
)

以下示例使用带有 workflow.EdgeBuilder 的图引擎。 评审节点返回判定结果,路由节点设置 Event.Routes, 从优化器到评审节点的回边创建循环。当评审节点满意时,它会路由到终端 done 节点:

// draft carries the working document through the refinement loop.
type draft struct {
    Text string `json:"text"`
}

// criticResult is emitted by the critic node with the review verdict and
// optional suggestions. The router reads Verdict to set Event.Routes.
type criticResult struct {
    Verdict     string `json:"verdict"`     // "REFINE" or "DONE"
    Suggestions string `json:"suggestions"` // non-empty when Verdict == "REFINE"
}

// writeDraft is the initial writer node: produces the first draft from the
// user's topic. Its typed return value becomes the input to the critic node
// via Event.Output — no session state writes needed.
func writeDraft(_ agent.Context, topic string) (draft, error) {
    // In a real workflow this would call an LLM; here we return a stub.
    return draft{Text: "Draft about " + topic + ": placeholder content."}, nil
}

// reviewDraft is the critic node: inspects the draft and returns a verdict.
// "DONE" exits the loop; "REFINE" triggers a back-edge to the refiner.
func reviewDraft(_ agent.Context, d draft) (criticResult, error) {
    // Simulate a critic: approve once the draft contains "improved".
    if strings.Contains(d.Text, "improved") {
        return criticResult{Verdict: "DONE"}, nil
    }
    return criticResult{
        Verdict:     "REFINE",
        Suggestions: "Add more detail and mark the text as improved.",
    }, nil
}

// routeVerdict reads the critic's verdict and sets Event.Routes so the
// graph engine dispatches to either the refiner or the done node.
// Returning nil suppresses the automatic terminal event.
func routeVerdict(ctx agent.Context, r criticResult, emit func(*session.Event) error) (any, error) {
    ev := session.NewEvent(ctx, ctx.InvocationID())
    ev.Routes = []string{r.Verdict}
    ev.Output = r // forward the full result to the chosen successor
    if err := emit(ev); err != nil {
        return nil, err
    }
    return nil, nil
}

// refineDraft applies the critic's suggestions and returns the improved draft.
// Its output feeds back to the critic node via the back-edge.
func refineDraft(_ agent.Context, r criticResult) (draft, error) {
    return draft{Text: "improved draft incorporating: " + r.Suggestions}, nil
}

// reportDone is the terminal node, reached only when the critic is satisfied.
func reportDone(_ agent.Context, r criticResult) (string, error) {
    return "Refinement complete. Final verdict: " + r.Verdict, nil
}

// newLoopEscalate builds an iterative document-refinement workflow using the
// graph engine. The critic node emits a route ("REFINE" or "DONE") and the
// engine dispatches to either the refiner (which loops back to the critic via
// a back-edge) or the terminal done node.
//
// Graph topology:
//
//  START → writer → critic → router ─┬─ "REFINE" → refiner ──┐
//                                     └─ "DONE"   → done       │
//                   ▲_______________________________┘ (back-edge)
//
// Python equivalent:
//
//  edges=[
//      ("START", writer_node, critic_node, router),
//      (router, {"REFINE": refiner_node, "DONE": done_node}),
//      (refiner_node, critic_node),  # back-edge creates the loop
//  ]
func newLoopEscalate() (agent.Agent, error) {
    writerNode := workflow.NewFunctionNode("writer", writeDraft, workflow.NodeConfig{})
    criticNode := workflow.NewFunctionNode("critic", reviewDraft, workflow.NodeConfig{})
    routerNode := workflow.NewEmittingFunctionNode("router", routeVerdict, workflow.NodeConfig{})
    refinerNode := workflow.NewFunctionNode("refiner", refineDraft, workflow.NodeConfig{})
    doneNode := workflow.NewFunctionNode("done", reportDone, workflow.NodeConfig{})

    // Build the edges. The back-edge from refinerNode to criticNode creates
    // the loop; the graph engine re-activates criticNode with a fresh
    // lifecycle on each iteration.
    eb := workflow.NewEdgeBuilder()
    eb.Add(workflow.Start, writerNode)
    eb.Add(writerNode, criticNode)
    eb.Add(criticNode, routerNode)
    eb.AddRoute(routerNode, refinerNode, workflow.StringRoute("REFINE"))
    eb.AddRoute(routerNode, doneNode, workflow.StringRoute("DONE"))
    eb.AddRoute(refinerNode, criticNode, workflow.Default) // back-edge: loop back for another review

    return workflowagent.New(workflowagent.Config{
        Name:        "iterative_writer",
        Description: "Writes then iteratively refines a document using a critic/refiner loop.",
        Edges:       eb.Build(),
    })
}