Skip to content

事件机制:

事件是 Agent Development Kit (ADK) 中信息流动的基本单元。它们记录了智能体交互生命周期中的所有重要节点——从初始用户输入到最终响应,以及其间所有操作步骤。理解事件机制至关重要,因为组件通信、状态管理和控制流转都是通过事件实现的。

事件本质与核心价值

ADK 中的 Event 是一个不可变记录,代表智能体执行过程中的特定时刻。它可能包含用户消息、智能体回复、工具调用请求(函数调用)、工具执行结果、状态变更、控制信号或错误信息。从技术实现看,每个事件都是基于基础结构 LlmResponse 扩展的 google.adk.events.Event 类实例,其中添加了 ADK 特有的元数据和 actions 有效载荷。

# Conceptual Structure of an Event
# from google.adk.events import Event, EventActions
# from google.genai import types

# class Event(LlmResponse): # Simplified view
#     # --- LlmResponse fields ---
#     content: Optional[types.Content]
#     partial: Optional[bool]
#     # ... other response fields ...

#     # --- ADK specific additions ---
#     author: str          # 'user' or agent name
#     invocation_id: str   # ID for the whole interaction run
#     id: str              # Unique ID for this specific event
#     timestamp: float     # Creation time
#     actions: EventActions # Important for side-effects & control
#     branch: Optional[str] # Hierarchy path
#     # ...

事件在 ADK 中具有核心地位,主要体现在:

  1. 通信枢纽:作为用户界面、Runner、智能体、大模型和工具之间的标准消息格式,所有交互都通过 Event 实现
  2. 状态变更信号:通过 event.actions.state_delta 传递状态修改指令,通过 event.actions.artifact_delta 跟踪产物更新,SessionService 服务依据这些信号确保持久化
  3. 流程控制event.actions.transfer_to_agentevent.actions.escalate 等特殊字段作为控制信号,决定下一个执行的智能体或循环终止条件
  4. 历史追溯:记录在 session.events 中的事件序列构成完整的交互时序历史,对调试、审计和逐步分析智能体行为极具价值

本质上,从用户查询到智能体最终响应的全过程,都是通过对 Event 对象的生成、解析和处理来编排的。

事件的理解与使用

开发者主要通过处理 Runner 产生的事件流进行交互。以下是解析事件信息的方法:

识别事件来源与类型

通过以下特征快速判断事件类型:

  • 发送方 (event.author)
    • 'user':表示来自终端用户的直接输入
    • 'AgentName':表示特定智能体(如 'WeatherAgent''SummarizerAgent')的输出或操作
  • 有效载荷类型 (event.contentevent.content.parts)
    • 文本消息:存在 event.content.parts[0].text 字段时通常为对话消息
    • 工具调用请求:检查 event.get_function_calls() 字段,若非空则表示大模型请求执行工具。列表中每个条目包含 .name.args
    • 工具执行结果:检查 event.get_function_responses() 字段,若非空则携带工具返回结果。每个条目包含 .name 和工具返回的字典 .response注意:出于历史记录结构考虑,content 内的 role 通常为 'user',但发起工具调用的事件 author 通常标记为请求代理
  • 流式输出标识 (event.partial)
    • True:表示大模型输出的文本片段,后续还有更多内容
    • FalseNone:表示当前内容块已完整(若 turn_complete 为 false 则整个交互轮次可能尚未结束)
# Pseudocode: Basic event identification
# async for event in runner.run_async(...):
#     print(f"Event from: {event.author}")
#
#     if event.content and event.content.parts:
#         if event.get_function_calls():
#             print("  Type: Tool Call Request")
#         elif event.get_function_responses():
#             print("  Type: Tool Result")
#         elif event.content.parts[0].text:
#             if event.partial:
#                 print("  Type: Streaming Text Chunk")
#             else:
#                 print("  Type: Complete Text Message")
#         else:
#             print("  Type: Other Content (e.g., code result)")
#     elif event.actions and (event.actions.state_delta or event.actions.artifact_delta):
#         print("  Type: State/Artifact Update")
#     else:
#         print("  Type: Control Signal or Other")

提取关键信息

确定事件类型后,可获取以下数据:

  • 文本内容text = event.content.parts[0].text(需先检查 event.contentevent.content.parts
  • 函数调用详情
    calls = event.get_function_calls()
    if calls:
        for call in calls:
            tool_name = call.name
            arguments = call.args # 通常为字典结构
            print(f"  工具: {tool_name}, 参数: {arguments}")
            # 应用程序可根据此信息调度执行
    
  • 函数响应详情
    responses = event.get_function_responses()
    if responses:
        for response in responses:
            tool_name = response.name
            result_dict = response.response # 工具返回的字典
            print(f"  工具结果: {tool_name} -> {result_dict}")
    
  • 标识符
    • event.id:该事件实例的唯一ID
    • event.invocation_id:该事件所属的完整用户请求-响应周期的ID,适用于日志追踪

检测操作与副作用

event.actions 对象标记已发生或应发生的变更。访问其字段前务必检查 event.actions 是否存在。

  • 状态变更delta = event.actions.state_delta 提供字典形式的 {key: value} 键值对,表示会话状态的变更
    if event.actions and event.actions.state_delta:
        print(f"  状态变更: {event.actions.state_delta}")
        # 必要时更新本地UI或应用状态
    
  • 产物保存artifact_changes = event.actions.artifact_delta 提供 {filename: version} 字典,记录被保存产物及其新版本号
    if event.actions and event.actions.artifact_delta:
        print(f"  产物保存: {event.actions.artifact_delta}")
        # UI可据此刷新产物列表
    
  • 控制流信号:检查布尔标记或字符串值:
    • event.actions.transfer_to_agent (字符串):控制权应转移至指定智能体
    • event.actions.escalate (布尔值):应终止当前循环
    • event.actions.skip_summarization (布尔值):工具结果不应由大模型总结
      if event.actions:
          if event.actions.transfer_to_agent:
              print(f"  信号: 转移至 {event.actions.transfer_to_agent}")
          if event.actions.escalate:
              print("  信号: 升级处理(终止循环)")
          if event.actions.skip_summarization:
              print("  信号: 跳过工具结果总结")
      

判断"最终"响应事件

使用内置方法 event.is_final_response() 识别适合作为智能体完整输出的最终事件。

  • 作用:过滤中间步骤(如工具调用、流式文本片段、内部状态更新),提取面向用户的最终消息
  • 判定条件 True
    1. 事件包含工具结果 (function_response) 且 skip_summarizationTrue
    2. 事件包含标记为 is_long_running=True 的工具调用 (function_call)
    3. 或满足所有以下条件:
      • 无函数调用 (get_function_calls() 为空)
      • 无函数响应 (get_function_responses() 为空)
      • 非流式片段 (partial 不为 True)
      • 不以需要进一步处理的代码执行结果结尾
  • 应用示例:在业务逻辑中过滤事件流

    # 伪代码:处理最终响应的应用逻辑
    # full_response_text = ""
    # async for event in runner.run_async(...):
    #     # 累积流式文本...
    #     if event.partial and event.content and event.content.parts and event.content.parts[0].text:
    #         full_response_text += event.content.parts[0].text
    #
    #     # 检查是否为最终可展示事件
    #     if event.is_final_response():
    #         print("\n--- 检测到最终输出 ---")
    #         if event.content and event.content.parts and event.content.parts[0].text:
    #              # 如果是流式最终片段,使用累积文本
    #              final_text = full_response_text + (event.content.parts[0].text if not event.partial else "")
    #              print(f"用户展示内容: {final_text.strip()}")
    #              full_response_text = "" # 重置累积器
    #         elif event.actions.skip_summarization:
    #              # 处理需要展示原始工具结果的情况
    #              response_data = event.get_function_responses()[0].response
    #              print(f"展示原始工具结果: {response_data}")
    #         elif event.long_running_tool_ids:
    #              print("展示消息: 工具正在后台运行...")
    #         else:
    #              # 处理其他类型的最终响应
    #              print("展示: 非文本最终响应或信号")
    

通过仔细分析事件的这些特征,可以构建健壮的应用系统来恰当响应 ADK 中的信息流。

事件流转:生成与处理

事件在不同节点生成后,由框架系统化处理。理解此流程有助于掌握操作与历史记录的管理机制。

  • 生成来源

    • 用户输入Runner 通常将初始用户消息或会话中输入包装为 Event 事件,并设置 author='user'
    • 智能体逻辑:智能体 (BaseAgentLlmAgent) 显式 yield Event(...) 对象(设置 author=self.name)来传递响应或发送信号
    • 大模型响应:ADK 模型集成层(如 google_llm.py)将大模型原始输出(文本、函数调用、错误)转换为 Event 对象,标记为调用智能体生成
    • 工具结果:工具执行后,框架生成包含 function_responseEvent 事件。author 通常是发起调用的智能体,而 content 内的 role 会设为 'user' 以适配大模型历史记录
  • 处理流程

    1. 生成:事件由源节点生成并抛出
    2. 接收:执行智能体的主 Runner 接收事件
    3. 会话服务处理 (append_event)Runner 将事件发送至配置的 SessionService,此为核心步骤:
      • 应用变更:服务将 event.actions.state_delta 合并到 session.state,并根据 event.actions.artifact_delta 更新内部记录(注:实际产物保存操作通常在调用 context.save_artifact 时已完成)
      • 元数据定型:分配唯一 event.id(若不存在),可能更新 event.timestamp
      • 历史持久化:将处理后的事件追加到 session.events 列表
    4. 外部抛出Runner 将处理完成的事件抛给调用方应用(如执行 runner.run_async 的代码)

此流程确保状态变更与历史记录随事件内容同步更新。

常见事件示例(典型模式)

以下是事件流中可能出现的典型示例:

  • 用户输入
    {
      "author": "user",
      "invocation_id": "e-xyz...",
      "content": {"parts": [{"text": "下周二预订去伦敦的航班"}]}
      // actions 通常为空
    }
    
  • 智能体最终文本响应:(is_final_response() == True)
    {
      "author": "TravelAgent",
      "invocation_id": "e-xyz...",
      "content": {"parts": [{"text": "好的,我可以协助处理。请确认出发城市?"}]},
      "partial": false,
      "turn_complete": true
      // actions 可能包含 state delta 等
    }
    
  • 智能体流式文本响应:(is_final_response() == False)
    {
      "author": "SummaryAgent",
      "invocation_id": "e-abc...",
      "content": {"parts": [{"text": "该文档讨论了三个要点:"}]},
      "partial": true,
      "turn_complete": false
    }
    // ...后续还有 partial=True 的事件...
    
  • 工具调用请求(大模型发起):(is_final_response() == False)
    {
      "author": "TravelAgent",
      "invocation_id": "e-xyz...",
      "content": {"parts": [{"function_call": {"name": "find_airports", "args": {"city": "London"}}}]}
      // actions 通常为空
    }
    
  • 工具执行结果(提供给大模型):(is_final_response() 取决于 skip_summarization)
    {
      "author": "TravelAgent", // 发起调用的智能体
      "invocation_id": "e-xyz...",
      "content": {
        "role": "user", // 大模型历史记录中的角色
        "parts": [{"function_response": {"name": "find_airports", "response": {"result": ["LHR", "LGW", "STN"]}}}]
      }
      // actions 可能包含 skip_summarization=True
    }
    
  • 仅状态/产物更新:(is_final_response() == False)
    {
      "author": "InternalUpdater",
      "invocation_id": "e-def...",
      "content": null,
      "actions": {
        "state_delta": {"user_status": "verified"},
        "artifact_delta": {"verification_doc.pdf": 2}
      }
    }
    
  • 智能体转移信号:(is_final_response() == False)
    {
      "author": "OrchestratorAgent",
      "invocation_id": "e-789...",
      "content": {"parts": [{"function_call": {"name": "transfer_to_agent", "args": {"agent_name": "BillingAgent"}}}]},
      "actions": {"transfer_to_agent": "BillingAgent"} // 由框架添加
    }
    
  • 循环升级信号:(is_final_response() == False)
    {
      "author": "CheckerAgent",
      "invocation_id": "e-loop...",
      "content": {"parts": [{"text": "已达到最大重试次数。"}]}, // 可选内容
      "actions": {"escalate": true}
    }
    

扩展上下文与事件细节

除核心概念外,以下特定场景的细节也值得关注:

  1. ToolContext.function_call_id(工具操作关联)

    • 当大模型请求工具调用 (FunctionCall) 时,该请求包含ID。工具函数接收的 ToolContext 会携带此 function_call_id
    • 重要性:此ID对于将认证操作 (request_credentialget_auth_response) 关联回原始工具请求至关重要,特别是单轮次内调用多个工具时。框架内部会使用此ID
  2. 状态/产物变更记录机制

    • 通过 context.state['key'] = value 修改状态或 context.save_artifact(...) 保存产物时,变更不会立即持久化
    • 这些变更会填充 EventActions 对象中的 state_deltaartifact_delta 字段
    • EventActions 对象会附加在下一个生成的事件上(如智能体响应或工具结果事件)
    • SessionService.append_event 方法从入站事件读取这些增量变更,应用到会话的持久化状态和产物记录。这确保变更与事件流保持时序一致
  3. 状态作用域前缀 (app:user:temp:)

    • 使用 context.state 管理状态时可选前缀:
      • app:my_setting:表示应用全局状态(需持久化 SessionService
      • user:user_preference:表示用户跨会话状态(需持久化 SessionService
      • temp:intermediate_result 或无前缀:通常为当前调用的会话级临时状态
    • 底层 SessionService 决定这些前缀的持久化处理方式
  4. 错误事件

    • Event 可表示错误,需检查继承自 LlmResponseevent.error_codeevent.error_message 字段
    • 错误可能源自大模型(如安全过滤、资源限制)或工具严重失败。检查工具 FunctionResponse 内容获取工具特定错误
      // 错误事件示例(概念模型)
      {
        "author": "LLMAgent",
        "invocation_id": "e-err...",
        "content": null,
        "error_code": "SAFETY_FILTER_TRIGGERED",
        "error_message": "响应因安全设置被拦截",
        "actions": {}
      }
      

这些细节为涉及工具认证、状态作用域和错误处理的高级场景提供了完整视角。

事件处理最佳实践

在ADK应用中高效使用事件的建议:

  • 明确 authorship:开发自定义智能体 (BaseAgent) 时确保正确设置 yield Event(author=self.name, ...),框架通常能正确处理大模型/工具事件的归属
  • 语义化内容与操作:使用 event.content 承载核心消息/数据(文本、函数调用/响应),event.actions 专用于标记副作用(状态/产物变更)或控制流(transferescalateskip_summarization
  • 幂等性认知:理解 SessionService 负责应用 event.actions 中的变更。虽然ADK服务保证一致性,但应考虑事件重复处理对下游的影响
  • 善用 is_final_response():在应用/UI层依赖此方法识别完整的用户端响应,避免手动实现相同逻辑
  • 利用历史记录session.events 列表是主要调试工具,通过分析事件序列的作者、内容和操作来追踪执行过程
  • 使用元数据:通过 invocation_id 关联单个用户交互的所有事件,使用 event.id 引用特定唯一事件

将事件视为具有明确目的的结构化消息,是构建、调试和管理复杂ADK智能体行为的关键。