跳转到内容

事件:

事件是Agent Development Kit (ADK)中信息流动的基本单元。它们代表了智能体交互生命周期中的每一个重要时刻,从初始用户输入到最终响应,以及中间的所有步骤。理解事件至关重要,因为它们是组件间通信、状态管理和控制流导向的主要方式。

事件是什么及其重要性

在ADK中,Event是一个不可变记录,代表智能体执行过程中的特定节点。它捕获用户消息、智能体回复、工具使用请求(函数调用)、工具执行结果、状态变更、控制信号以及错误信息。从技术实现来看,它是google.adk.events.Event类的实例,该类在基础LlmResponse结构上扩展了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
#     # ...

事件是Agent Development Kit运作的核心,原因有以下几点:

  1. 通信: 它们作为用户界面、Runner、智能体、LLM和工具之间的标准消息格式。所有内容都以Event的形式流动。
  2. 状态信号与工件变更:事件通过event.actions.state_delta携带状态修改指令,并通过event.actions.artifact_delta追踪工件更新。SessionService利用这些信号确保持久性。
  3. 控制流:特定字段如event.actions.transfer_to_agentevent.actions.escalate作为框架的导向信号,决定下一个运行的智能体或是否终止循环。
  4. 历史与可观测性:记录在session.events中的事件序列提供了交互的完整时间线历史记录,对于调试、审计以及逐步理解智能体行为具有不可估量的价值。

本质上,从用户查询到智能体最终答案的整个过程,都是通过生成、解释和处理Event对象来协调完成的。

理解与使用事件

作为开发者,您主要需要与Runner产生的事件流进行交互。以下是理解和从中提取信息的方法:

识别事件来源与类型

通过检查快速确定事件代表的含义:

  • Who sent it? (event.author)
    • 'user': 表示直接来自终端用户的输入。
    • 'AgentName': 表示来自特定智能体的输出或动作(例如'WeatherAgent', 'SummarizerAgent')。
  • What's the main payload? (event.content and event.content.parts)
    • 文本: 如果 event.content.parts[0].text 存在,这很可能是一条对话消息。
    • 工具调用请求: 检查 event.get_function_calls()。如果不为空,表示LLM正在请求执行一个或多个工具。列表中的每个项都有 .name.args
    • 工具执行结果: 检查 event.get_function_responses()。如果不为空,该事件携带了工具执行的结果。每个结果项包含 .name.response(工具返回的字典)。注意: 对于历史记录结构化处理,content 中的 role 通常是 'user',但事件的 author 通常是发起工具调用的智能体。
  • Is it streaming output? (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: {tool_name}, Args: {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}")
    
  • Identifiers:
    • 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"  State changes: {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可能需要刷新工件列表
    
  • Control Flow Signals: Check boolean flags or string values:
    • event.actions.transfer_to_agent (string): 控制权应转移至指定的智能体。
    • event.actions.escalate (bool): 循环应该终止。
    • event.actions.skip_summarization (bool): 工具结果不应由LLM进行总结。
      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() 来识别适合作为智能体单轮完整输出展示的事件。

  • 目的:从面向用户的最终消息中过滤掉中间步骤(如工具调用、部分流式文本、内部状态更新)。
  • When True?
    1. 该事件包含一个工具结果(function_response)且skip_summarizationTrue
    2. 该事件包含一个工具调用(function_call),该工具被标记为is_long_running=True
    3. OR, all of the following are met:
      • 没有函数调用(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'
    • 智能体逻辑: 智能体(BaseAgent, LlmAgent)通过显式yield Event(...)对象(设置author=self.name)来传递响应或发出动作信号。
    • LLM响应: ADK模型集成层(例如google_llm.py)将原始LLM输出(文本、函数调用、错误)转换为由调用智能体创建的Event对象。
    • 工具执行结果: 当工具执行完成后,框架会生成一个包含function_responseEvent事件。author通常是请求该工具的智能体,而content中的role会被设置为'user'以便记录LLM的历史交互。
  • 处理流程:

    1. Yield(产出): 事件由其源生成并产出。
    2. Runner接收: 执行智能体的主Runner接收该事件。
    3. SessionService Processing (append_event): The Runner sends the event to the configured SessionService. This is a critical step:
      • 应用增量变更: 该服务会将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": "Book a flight to London for next Tuesday"}]}
      // actions通常为空
    }
    
  • 智能体最终文本响应: (is_final_response() == True)
    {
      "author": "TravelAgent",
      "invocation_id": "e-xyz...",
      "content": {"parts": [{"text": "好的,我可以帮忙处理这个问题。您能确认一下出发城市吗?"}]},
      "partial": false,
      "turn_complete": true
      // 动作可能包含状态增量等
    }
    
  • 智能体流式文本响应: (is_final_response() == False)
    {
      "author": "SummaryAgent",
      "invocation_id": "e-abc...",
      "content": {"parts": [{"text": "该文档讨论了三个要点:"}]},
      "partial": true,
      "turn_complete": false
    }
    // ... 更多partial=True事件紧随其后 ...
    
  • 工具调用请求(由LLM发起): (is_final_response() == False)
    {
      "author": "TravelAgent",
      "invocation_id": "e-xyz...",
      "content": {"parts": [{"function_call": {"name": "find_airports", "args": {"city": "London"}}}]}
      // actions通常为空
    }
    
  • 工具结果提供(给LLM): (is_final_response() 取决于 skip_summarization)
    {
      "author": "TravelAgent", // 作者是发起调用的智能体
      "invocation_id": "e-xyz...",
      "content": {
        "role": "user", // LLM历史记录中的角色
        "parts": [{"function_response": {"name": "find_airports", "response": {"result": ["LHR", "LGW", "STN"]}}}]
      }
      // 动作可能设置了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": "Maximum retries reached."}]}, // 可选内容
      "actions": {"escalate": true}
    }
    

附加上下文和事件详情

除了核心概念之外,以下是关于上下文和事件的一些具体细节,这些细节对某些用例非常重要:

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

    • 当LLM请求一个工具(FunctionCall)时,该请求会有一个ID。提供给工具函数的ToolContext中包含这个function_call_id
    • 重要性: 此ID对于将认证等操作(request_credentialget_auth_response)链接回发起它们的特定工具请求至关重要,特别是在一个回合中调用多个工具时。框架在内部使用此ID。
  2. 状态/工件变更的记录方式:

    • 当你使用CallbackContextToolContext修改状态(context.state['key'] = value)或保存工件(context.save_artifact(...))时,这些更改不会立即写入持久化存储。
    • 相反,它们会填充EventActions对象中的state_deltaartifact_delta字段。
    • 这个EventActions对象会附加在变更后生成的下一个事件上(例如智能体的响应或工具结果事件)。
    • SessionService.append_event 方法会从传入事件中读取这些增量变更,并将其应用到会话的持久化状态和工件记录中。这确保了变更按时间顺序与事件流相关联。
  3. 状态作用域前缀 (app:, user:, temp:):

    • When managing state via context.state, you can optionally use prefixes:
      • app:my_setting: 建议与整个应用程序相关的状态(需要一个持久的SessionService)。
      • user:user_preference: 建议跨会话保留与特定用户相关的状态(需要持久化的SessionService)。
      • temp:intermediate_result 或无前缀:通常是当前调用会话特定的临时状态。
    • 底层的SessionService决定了这些前缀如何处理以实现持久化。
  4. 错误事件:

    • 一个Event可以表示错误。检查event.error_codeevent.error_message字段(继承自LlmResponse)。
    • 错误可能源自LLM(例如安全过滤器、资源限制),或者在工具严重失败时可能由框架封装。检查工具FunctionResponse内容以获取典型的工具特定错误。
      // 示例错误事件(概念性)
      {
        "author": "LLMAgent",
        "invocation_id": "e-err...",
        "content": null,
        "error_code": "SAFETY_FILTER_TRIGGERED",
        "error_message": "Response blocked due to safety settings.",
        "actions": {}
      }
      

这些细节为涉及工具认证、状态持久化范围和事件流内错误处理的高级用例提供了更完整的视图。

事件处理最佳实践

为了在您的ADK应用中有效使用事件:

  • 明确作者身份: 构建自定义智能体(BaseAgent)时,确保使用yield Event(author=self.name, ...)来正确记录历史中智能体的操作归属。框架通常能正确处理LLM/工具事件的作者身份。
  • 语义内容与操作: 使用 event.content 传递核心消息/数据(文本、函数调用/响应)。专门使用 event.actions 来标记副作用(状态/工件变更)或控制流程(transfer, escalate, skip_summarization)。
  • 幂等性意识: 理解SessionService负责应用event.actions中指示的状态/工件变更。虽然Agent Development Kit服务追求一致性,但如果您的应用逻辑重新处理事件,请考虑潜在的下游影响。
  • 使用 is_final_response(): 在您的应用程序/用户界面层中依赖此辅助方法来识别面向用户的完整文本响应。避免手动复制其逻辑。
  • 利用历史记录: session.events 列表是您的主要调试工具。检查作者、内容和操作的序列,以追踪执行过程并诊断问题。
  • 使用元数据: 使用 invocation_id 来关联单个用户交互中的所有事件。使用 event.id 来引用特定的、唯一的事件发生。

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