智能体工作流的数据处理¶
在智能体和基于图的节点之间构建和管理数据,对于使用 ADK 构建可靠的流程至关重要。本指南介绍了基于图的工作流和协作智能体中的数据处理,包括信息如何在图节点之间传输和接收。它涵盖了传递数据、内容和状态的核心参数,并解释了如何使用数据格式 Schema 和特定指令语法为函数节点和智能体节点实现结构化数据传输。
工作流数据流¶
在基于图的工作流中,节点通过事件向下游步骤传递数据。一个步骤将其输出写入命名的事件字段,下一个步骤将其作为类型化输入接收。
在 Python 中,数据通过 Event 在图节点之间交换。节点数据处理的关键参数包括:
output:在节点之间传递信息的参数。message:作为用户回复的数据。state:通过 Event 在整个 ADK 会话中跨节点自动持久化的数据。
在 ADK Go v2.0.0 中,数据传递机制取决于你使用的智能体风格:
workflow 包(FunctionNode、AgentNode、DynamicNode):节点通过 session.Event 字段进行通信,与 Python 非常相似:
Event.Output:节点的返回值,当FunctionNode返回非*genai.Content值时由框架自动设置。后继节点将其作为类型化input参数接收。Event.Routes:由发出节点显式设置的路由键,用于选择要遵循的条件边——相当于 Python 的Event(route=...)。Event.NodeInfo:调度器元数据(path、MessageAsOutput、OutputFor)。由工作流引擎设置;节点不直接设置此项。
预构建工作流智能体(sequentialagent、parallelagent、loopagent):这些智能体通过会话状态进行通信:
llmagent.Config上的OutputKey:框架在每轮结束后将智能体的最终文本响应写入state[OutputKey]。ctx.Session().State().Set/.Get:在自定义代码中对状态进行读写任意值。Instruction中的{key}:框架在调用模型之前将state["key"]替换到提示词中。
状态键可以携带前缀来控制其生命周期和作用域:
| 前缀常量 | 前缀字符串 | 作用域 |
|---|---|---|
session.KeyPrefixApp |
"app:" |
应用中所有用户和会话共享 |
session.KeyPrefixUser |
"user:" |
绑定到用户,在其会话间共享 |
session.KeyPrefixTemp |
"temp:" |
当前调用结束后丢弃 |
| (无) | — | 在会话生命周期内持久化 |
节点输出¶
工作流中的每个步骤都会为其后继步骤产生输出。
使用 return 或 yield 语法将数据传递给下一个节点:
from google.adk import Event
def my_function_node(node_input: str):
output_value = node_input.upper()
return Event(output=output_value) # "THE RESULT"
当输出不需要额外处理的 Event 数据时,使用 return 语法。当需要发出需要额外处理的数据,或者你正在生成多个数据项时,可以使用多个 yield 命令。每个 yield 调用都会添加到 Event 上的数据对象列表中,该列表会传递给图的下一个节点。不带参数的 return 或 yield 命令会将 None 值传递给下一个节点。
workflow 包:FunctionNode 只需返回一个类型化的 Go 值。框架会自动将返回值包装在 session.Event 中并设置 Event.Output。后继节点将其作为类型化 input 参数接收——无需手动构建事件:
// newEventOutputPipeline demonstrates the primary data-passing mechanism for
// workflow package nodes: a FunctionNode returns a typed Go value, and the
// framework automatically sets event.Output to that value. The successor node
// receives it as its typed `input` parameter.
//
// This mirrors the Python pattern exactly:
//
// def my_function_node(node_input: str):
// return Event(output=node_input.upper())
//
// In Go, the function simply returns the value — no Event construction needed.
func newEventOutputPipeline() (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
}
nodeA := workflow.NewFunctionNode("upper", upperFn, workflow.NodeConfig{})
nodeB := workflow.NewFunctionNode("suffix", suffixFn, workflow.NodeConfig{})
// workflow.Chain wires START → nodeA → nodeB. The output of nodeA is
// delivered as the typed input of nodeB via event.Output.
return workflowagent.New(workflowagent.Config{
Name: "event_output_pipeline",
Description: "Demonstrates Event.Output data flow between FunctionNodes.",
Edges: workflow.Chain(workflow.Start, nodeA, nodeB),
})
}
预构建工作流智能体:使用 llmagent.Config 上的 OutputKey 将智能体的文本响应保存到会话状态中,然后在下游智能体的 Instruction 模板中通过 {key} 引用它:
// newOutputKeyPipeline demonstrates the OutputKey mechanism for the prebuilt
// sequentialagent. When OutputKey is set on an llmagent.Config, the framework
// automatically writes the agent's final text response to session state under
// that key. Downstream agents read it by referencing {key} in their Instruction.
//
// This pattern applies to sequentialagent / parallelagent / loopagent.
// For the workflow package (FunctionNode / AgentNode), use Event.Output instead.
func newOutputKeyPipeline(ctx context.Context, geminiModel model.LLM) (agent.Agent, error) {
step1, err := llmagent.New(llmagent.Config{
Name: "step_1",
Model: geminiModel,
Description: "Transforms the user's text.",
Instruction: "Convert the user's message to uppercase. Output only the transformed text.",
OutputKey: "upper_result",
})
if err != nil {
return nil, fmt.Errorf("step1: %w", err)
}
step2, err := llmagent.New(llmagent.Config{
Name: "step_2",
Model: geminiModel,
Description: "Reports the transformed text.",
Instruction: "The transformed text is: {upper_result}. Report it to the user.",
})
if err != nil {
return nil, fmt.Errorf("step2: %w", err)
}
return sequentialagent.New(sequentialagent.Config{
AgentConfig: agent.Config{
Name: "output_key_pipeline",
SubAgents: []agent.Agent{step1, step2},
},
})
}
节点输出:传递结构化数据¶
你可以以可序列化的格式传递更长的结构化数据:
注意:Event.output 限制
每次执行只允许节点发出单个 Event.output 数据负载。此限制意味着虽然你可以在一个节点中使用多个 yield,但有两个或更多带有 Event.output 的 yield 命令会导致运行时错误。
workflow 包:FunctionNode 可以返回任何可 JSON 序列化的 Go 结构体。框架将其序列化为 Event.Output,并反序列化为后继节点的类型化 input 参数。没有单个负载限制——每个节点恰好有一个类型化返回值:
// newStructuredOutputPipeline shows how to pass a struct from one FunctionNode
// to another. The framework serialises the return value into event.Output and
// deserialises it back into the successor's typed input parameter.
//
// This is the Go equivalent of:
//
// class CityTime(BaseModel):
// time_info: str
// city: str
//
// def lookup_time_function(city: str):
// return Event(output=CityTime(time_info="10:10 AM", city=city))
//
// def city_report(node_input: CityTime):
// return Event(output=f"It is {node_input.time_info} in {node_input.city}.")
type CityTime struct {
TimeInfo string `json:"time_info"`
City string `json:"city"`
}
func newStructuredOutputPipeline(ctx context.Context, geminiModel model.LLM) (agent.Agent, error) {
lookupTimeFn := func(_ agent.Context, city string) (CityTime, error) {
// Simulate looking up the current time in the city.
return CityTime{TimeInfo: "10:10 AM", City: city}, nil
}
cityReportAgent, err := llmagent.New(llmagent.Config{
Name: "city_report_agent",
Model: geminiModel,
Description: "Reports the city and current time from the previous node's output.",
// When wrapped as an AgentNode, the predecessor's event.Output
// is delivered as the agent's user content. The {key} template
// syntax is not required — the struct fields are provided inline.
Instruction: "Report the city time information you received in a friendly sentence.",
})
if err != nil {
return nil, fmt.Errorf("cityReportAgent: %w", err)
}
lookupTimeNode := workflow.NewFunctionNode("lookup_time", lookupTimeFn, workflow.NodeConfig{})
cityReportNode, err := workflow.NewAgentNode(cityReportAgent, workflow.NodeConfig{})
if err != nil {
return nil, fmt.Errorf("NewAgentNode: %w", err)
}
return workflowagent.New(workflowagent.Config{
Name: "city_time_pipeline",
Edges: workflow.Chain(workflow.Start, lookupTimeNode, cityReportNode),
SubAgents: []agent.Agent{cityReportAgent},
})
}
预构建工作流智能体:使用多个 OutputKey 值,每个智能体一个,将各个字段存储在会话状态中。下游智能体通过 Instruction 中的 {key} 独立读取每个字段。
路由输出¶
workflow 包:发出事件的 FunctionNode 直接构造 session.Event,将 Event.Routes 设置为所需的路由键,并将 Event.Output 设置为将负载转发给后继节点。工作流引擎在分发时读取 Event.Routes 以选择匹配的边:
// classifyAndRoute shows how to set event.Routes alongside event.Output from
// an emitting FunctionNode. The function constructs a session.Event directly,
// sets Routes to select the conditional edge, and sets Output to forward the
// payload to the successor node.
//
// This mirrors the Python pattern:
//
// def router(node_input: str):
// return Event(route="BUG")
func classifyAndRoute(ctx agent.Context, msg string, emit func(*session.Event) error) (any, error) {
category := classifyMessage(msg)
ev := session.NewEvent(ctx, ctx.InvocationID())
ev.Routes = []string{category} // drives edge dispatch
ev.Output = msg // forwarded as typed input to the successor
if err := emit(ev); err != nil {
return nil, err
}
return nil, nil // nil suppresses the automatic terminal event
}
func classifyMessage(msg string) string {
switch {
case strings.Contains(strings.ToLower(msg), "bug"):
return "BUG"
case strings.Contains(strings.ToLower(msg), "help"):
return "CUSTOMER_SUPPORT"
default:
return "LOGISTICS"
}
}
func newRoutingPipeline() (agent.Agent, error) {
classifyNode := workflow.NewEmittingFunctionNode("classify", classifyAndRoute, workflow.NodeConfig{})
bugHandler := workflow.NewFunctionNode("bug_handler",
func(_ agent.Context, msg string) (string, error) {
return "Handling bug: " + msg, nil
}, workflow.NodeConfig{})
supportHandler := workflow.NewFunctionNode("support_handler",
func(_ agent.Context, msg string) (string, error) {
return "Handling support: " + msg, nil
}, workflow.NodeConfig{})
logisticsHandler := workflow.NewFunctionNode("logistics_handler",
func(_ agent.Context, msg string) (string, error) {
return "Handling logistics: " + msg, nil
}, workflow.NodeConfig{})
edges := workflow.Concat(
workflow.Chain(workflow.Start, 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")},
},
)
return workflowagent.New(workflowagent.Config{
Name: "routing_pipeline",
Description: "Classifies and routes a message using Event.Routes.",
Edges: edges,
})
}
面向用户的消息¶
使用 Event 的 message 参数向用户发送响应,而不是向下一个节点传递数据:
workflow 包:要在不推进节点类型化输出的情况下发出用户可见的消息,请在通过 EmittingFunctionNode 中的 emit 回调发出的中间事件上设置 Event.Content。最终返回值(或 nil)控制 Event.Output。
预构建工作流智能体:任何 llmagent 步骤都会自动将其模型响应作为面向用户的事件发出。对于非 LLM 步骤,在 agent.Agent 上编写自定义 Run 函数,使其生成 LLMResponse.Content 包含文本的事件。
会话状态和状态作用域¶
会话状态在会话内的各轮之间持久化数据。它是预构建工作流智能体的主要数据共享机制,无论你使用哪种智能体风格,都可以在工具和回调中使用。
使用 Event 的 state 参数来维护跨节点的值。节点可以修改状态值,修改后的状态值可供下游节点使用:
async def init_state_node(attempts: int = 0):
yield Event(
state={
"attempts": attempts,
},
)
async def task_attempt_node(node_input: Content, attempts: int):
yield Event(
state={
"attempts": attempts + 1,
},
)
async def read_state_node(ctx: Context):
print(f"attempts state: {ctx.state}") # attempts state: attempts: 1
root_agent = Workflow(
name="root_agent",
edges=[("START", init_state_node, task_attempt_node, read_state_node)],
)
注意:state 属性数据限制
state 参数不应被用于在节点之间持久化大量数据。请使用制品或其他数据持久化机制(如数据库工具)在工作流的生命周期中持久化大型数据资源。
状态通过 ctx.Session().State().Set(key, value) 写入,通过 .Get(key) 读取。session 包定义的前缀常量映射到与 Python 的 state 参数相同的生命期作用域。此模式适用于预构建工作流智能体,也适用于任何智能体风格中的工具和回调:
// stateScopes shows how session-state key prefixes control the lifetime and
// visibility of stored values. This pattern applies to the prebuilt workflow
// agents (sequentialagent / parallelagent / loopagent) and to tools and
// callbacks. For the workflow package (FunctionNode / AgentNode), prefer
// returning values directly via Event.Output.
//
// Available prefixes:
//
// session.KeyPrefixApp ("app:") – shared across all users and sessions
// session.KeyPrefixUser ("user:") – tied to the user, shared across sessions
// session.KeyPrefixTemp ("temp:") – discarded after the current invocation
//
// Keys with no prefix persist for the lifetime of the session.
func stateScopes(ctx agent.Context) error {
st := ctx.Session().State()
// Session-scoped (no prefix) — persists for the life of this session.
if err := st.Set("attempts", 0); err != nil {
return fmt.Errorf("state.Set attempts: %w", err)
}
// App-scoped — shared across all users and sessions for this app.
if err := st.Set(session.KeyPrefixApp+"global_counter", 42); err != nil {
return fmt.Errorf("state.Set app:global_counter: %w", err)
}
// User-scoped — shared across all sessions belonging to this user.
if err := st.Set(session.KeyPrefixUser+"login_count", 1); err != nil {
return fmt.Errorf("state.Set user:login_count: %w", err)
}
// Temp-scoped — discarded after this invocation ends.
if err := st.Set(session.KeyPrefixTemp+"scratch", "ephemeral"); err != nil {
return fmt.Errorf("state.Set temp:scratch: %w", err)
}
return nil
}
注意:状态数据限制
会话状态是一个轻量级的键值存储。不要使用它来持久化大型负载,如文件内容或二进制数据。请改用 ADK 制品或外部存储工具。
workflow 包:优先使用 Event.Output 而非 state
对于 workflow 包(FunctionNode、AgentNode、DynamicNode),通过返回类型化值在节点之间传递数据——框架会自动设置 Event.Output。只有当你需要与工具、回调或智能体 Instruction 模板共享值时才使用 State().Set。
使用 Schema 约束节点数据¶
你可以设置输入和输出数据 Schema 来约束任何智能体节点接受和产生的数据格式。
使用扩展自 BaseModel 的类配合 input_schema 和 output_schema 来约束任何智能体的输入和输出:
from google.adk import Agent
from pydantic import BaseModel
class FlightSearchInput(BaseModel):
origin: str # 机场代码 "SFO"
destination: str # 机场代码 "CDG"
departure_date: date # date(2026, 3, 15)
passengers: int = 1 # 乘客数量
class FlightSearchOutput(BaseModel):
flights: list[Flight]
cheapest_price: float
flight_searcher = Agent(
name="flight_searcher",
instruction="Search for available flights.",
input_schema=FlightSearchInput,
output_schema=FlightSearchOutput,
tools=[search_flights_api],
mode="single_turn",
...
)
assistant = Agent(
name="assistant",
instruction="You help users plan trips.",
sub_agents=[flight_searcher],
...
)
workflow 包:使用 workflow.NewAgentNodeTyped[Input, Output] 为智能体节点附加 Schema。泛型类型参数会自动反射为 *jsonschema.Schema——无需手动构建 Schema。节点的 Event.Output 将结构化结果传递给后继节点——不需要 OutputKey 或状态写入:
// FlightSearchInput is the typed input schema for the flight-search agent node.
// workflow.NewAgentNodeTyped[FlightSearchInput, FlightSearchOutput] reflects
// these structs into *jsonschema.Schema automatically — no hand-built schema
// construction needed.
type FlightSearchInput struct {
Origin string `json:"origin" jsonschema:"Departure airport code e.g. SFO"`
Destination string `json:"destination" jsonschema:"Arrival airport code e.g. CDG"`
DepartureDate string `json:"departure_date" jsonschema:"Travel date in YYYY-MM-DD format"`
}
// FlightSearchOutput is the typed output schema for the flight-search agent node.
type FlightSearchOutput struct {
CheapestPrice string `json:"cheapest_price" jsonschema:"Cheapest available fare e.g. $450"`
FlightCount string `json:"flight_count" jsonschema:"Number of matching flights found"`
}
// newSchemaAgentPipeline demonstrates workflow.NewAgentNodeTyped, which infers
// *jsonschema.Schema from the generic type parameters. This is the Go equivalent
// of Python's:
//
// flight_searcher = Agent(
// input_schema=FlightSearchInput,
// output_schema=FlightSearchOutput,
// ...
// )
//
// The node's event.Output carries the structured result to the successor —
// no OutputKey or state write is needed.
func newSchemaAgentPipeline(ctx context.Context, geminiModel model.LLM) (agent.Agent, error) {
flightSearchAgent, err := llmagent.New(llmagent.Config{
Name: "flight_searcher",
Model: geminiModel,
Description: "Searches for available flights and returns structured results.",
Instruction: `You are a flight-search assistant. Respond ONLY with a JSON object.`,
})
if err != nil {
return nil, fmt.Errorf("flightSearchAgent: %w", err)
}
synthAgent, err := llmagent.New(llmagent.Config{
Name: "trip_assistant",
Model: geminiModel,
Description: "Summarises flight search results for the user.",
Instruction: `You help users plan trips. Summarise the flight result you received.`,
})
if err != nil {
return nil, fmt.Errorf("synthAgent: %w", err)
}
// NewAgentNodeTyped[In, Out] reflects FlightSearchInput and FlightSearchOutput
// into *jsonschema.Schema automatically. The node enforces the input schema
// and constrains the model reply to the output schema's shape.
flightNode, err := workflow.NewAgentNodeTyped[FlightSearchInput, FlightSearchOutput](flightSearchAgent, workflow.NodeConfig{})
if err != nil {
return nil, fmt.Errorf("flightNode: %w", err)
}
synthNode, err := workflow.NewAgentNode(synthAgent, workflow.NodeConfig{})
if err != nil {
return nil, fmt.Errorf("synthNode: %w", err)
}
return workflowagent.New(workflowagent.Config{
Name: "flight_booking_pipeline",
Edges: workflow.Chain(workflow.Start, flightNode, synthNode),
SubAgents: []agent.Agent{flightSearchAgent, synthAgent},
})
}
预构建工作流智能体:在 llmagent.Config 上设置 InputSchema 和 OutputSchema。OutputSchema 强制模型回复符合 Schema 的 JSON 对象(当设置了 OutputSchema 时智能体无法使用工具)。使用 OutputKey 将 JSON 字符串保存到状态中,供下游智能体通过 Instruction 中的 {key} 引用。
在智能体中访问结构化数据¶
使用花括号 { } 语法从输入 Schema 中选择属性,或使用 < > 选择属性并通过源节点名称进行限定:
class CityTime(BaseModel):
time_info: str # 时间信息
city: str # 城市名称
def lookup_time_function(city: str):
"""模拟返回指定城市的当前时间。"""
return Event(output=CityTime(time_info='10:10 AM', city=city))
city_report_agent = Agent(
name="city_report_agent",
model="gemini-flash-latest",
input_schema=CityTime,
# 基于类和参数的数据选择
# instruction="""
# Return a sentence in the following format:
# It is {CityTime.time_info} in {CityTime.city} right now.
# """,
# 基于源节点名称的更严格数据选择
instruction="""
Return a sentence in the following format:
It is <CityTime.time_info from lookup_time_function> in
<CityTime.city from lookup_time_function> right now.
""",
)
root_agent = Workflow(
name="root_agent",
edges=[
(START, city_generator_agent, lookup_time_function, city_report_agent)
],
)
在 ADK Go v2.0.0 中,FunctionNode 返回一个类型化结构体,框架将其序列化为 Event.Output。后继的 AgentNode 将该结构体作为用户内容接收——字段可直接用于智能体的 Instruction,无需任何 {key} 模板语法。这相当于 Python 的 input_schema=CityTime 配合 {CityTime.time_info} 模板占位符:结构化字段作为类型化输入传递,而非从状态中按名称查找。
// newStructuredOutputPipeline shows how to pass a struct from one FunctionNode
// to another. The framework serialises the return value into event.Output and
// deserialises it back into the successor's typed input parameter.
//
// This is the Go equivalent of:
//
// class CityTime(BaseModel):
// time_info: str
// city: str
//
// def lookup_time_function(city: str):
// return Event(output=CityTime(time_info="10:10 AM", city=city))
//
// def city_report(node_input: CityTime):
// return Event(output=f"It is {node_input.time_info} in {node_input.city}.")
type CityTime struct {
TimeInfo string `json:"time_info"`
City string `json:"city"`
}
func newStructuredOutputPipeline(ctx context.Context, geminiModel model.LLM) (agent.Agent, error) {
lookupTimeFn := func(_ agent.Context, city string) (CityTime, error) {
// Simulate looking up the current time in the city.
return CityTime{TimeInfo: "10:10 AM", City: city}, nil
}
cityReportAgent, err := llmagent.New(llmagent.Config{
Name: "city_report_agent",
Model: geminiModel,
Description: "Reports the city and current time from the previous node's output.",
// When wrapped as an AgentNode, the predecessor's event.Output
// is delivered as the agent's user content. The {key} template
// syntax is not required — the struct fields are provided inline.
Instruction: "Report the city time information you received in a friendly sentence.",
})
if err != nil {
return nil, fmt.Errorf("cityReportAgent: %w", err)
}
lookupTimeNode := workflow.NewFunctionNode("lookup_time", lookupTimeFn, workflow.NodeConfig{})
cityReportNode, err := workflow.NewAgentNode(cityReportAgent, workflow.NodeConfig{})
if err != nil {
return nil, fmt.Errorf("NewAgentNode: %w", err)
}
return workflowagent.New(workflowagent.Config{
Name: "city_time_pipeline",
Edges: workflow.Chain(workflow.Start, lookupTimeNode, cityReportNode),
SubAgents: []agent.Agent{cityReportAgent},
})
}
有关此工作流的完整示例,请参阅基于图的智能体工作流。