Skip to content

事件

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

什么是事件以及为什么它们很重要

ADK 中的Event是一个不可变记录,代表智能体执行中的特定时点。它捕获用户消息、智能体回复、使用工具的请求(函数调用)、工具结果、状态变更、控制信号和错误。

从技术上讲,它是google.adk.events.Event类的实例,该类在基本的LlmResponse结构基础上添加了重要的 ADK 特定元数据和actions负载。

# 事件的概念结构(Python)
# from google.adk.events import Event, EventActions
# from google.genai import types

# class Event(LlmResponse): # 简化视图
#     # --- LlmResponse 字段 ---
#     content: Optional[types.Content]
#     partial: Optional[bool]
#     # ... 其他响应字段 ...

#     # --- ADK 特定添加 ---
#     author: str          # 'user' 或智能体名称
#     invocation_id: str   # 整个交互运行的 ID
#     id: str              # 此特定事件的唯一 ID
#     timestamp: float     # 创建时间
#     actions: EventActions # 对副作用和控制很重要
#     branch: Optional[str] # 层次路径
#     # ...

在 Java 中,这是com.google.adk.events.Event类的实例。它也在基本响应结构基础上添加了重要的 ADK 特定元数据和actions负载。

// 事件的概念结构(Java - 参见 com.google.adk.events.Event.java)
// 基于提供的 com.google.adk.events.Event.java 的简化视图
// public class Event extends JsonBaseModel {
//     // --- 类似于 LlmResponse 的字段 ---
//     private Optional<Content> content;
//     private Optional<Boolean> partial;
//     // ... 其他响应字段,如 errorCode、errorMessage ...

//     // --- ADK 特定添加 ---
//     private String author;         // 'user' 或智能体名称
//     private String invocationId;   // 整个交互运行的 ID
//     private String id;             // 此特定事件的唯一 ID
//     private long timestamp;        // 创建时间(纪元毫秒)
//     private EventActions actions;  // 对副作用和控制很重要
//     private Optional<String> branch; // 层次路径
//     // ... 其他字段,如 turnComplete、longRunningToolIds 等
// }

事件对 ADK 操作至关重要,主要有几个原因:

  1. 通信: 它们作为用户界面、Runner、智能体、LLM 和工具之间的标准消息格式。一切都以Event的形式流动。

  2. 状态和工件变更信号: 事件携带状态修改指令并跟踪工件更新。SessionService使用这些信号来确保持久性。在 Python 中,变更通过event.actions.state_deltaevent.actions.artifact_delta发出信号。

  3. 控制流: 特定字段如event.actions.transfer_to_agentevent.actions.escalate充当指导框架的信号,确定下一个运行的智能体或循环是否应该终止。

  4. 历史和可观察性: 记录在session.events中的事件序列提供了交互的完整时间顺序历史,对于调试、审计和逐步理解智能体行为非常宝贵。

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

理解和使用事件

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

Note

原语的具体参数或方法名称可能因 SDK 语言而略有不同(例如,Python 中的event.content(),Java 中的event.content().get().parts())。有关详细信息,请参阅特定语言的 API 文档。

识别事件来源和类型

通过检查以下内容快速确定事件代表什么:

  • 谁发送的?(event.author
    • 'user':表示直接来自最终用户的输入。
    • 'AgentName':表示来自特定智能体的输出或动作(例如,'WeatherAgent''SummarizerAgent')。
  • 主要负载是什么?(event.contentevent.content.parts

    • 文本: 表示对话消息。对于 Python,检查event.content.parts[0].text是否存在。对于 Java,检查event.content()是否存在,其parts()是否存在且不为空,以及第一部分的text()是否存在。
    • 工具调用请求: 检查event.get_function_calls()。如果不为空,LLM 正在请求执行一个或多个工具。列表中的每个项目都有.name.args
    • 工具结果: 检查event.get_function_responses()。如果不为空,此事件携带工具执行的结果。每个项目都有.name.response(工具返回的字典)。注意: 对于历史结构,content内的role通常是'user',但事件author通常是请求工具调用的智能体。
  • 是流式输出吗?(event.partial 表示这是否是来自 LLM 的不完整文本块。

    • True:将有更多文本跟随。
    • FalseNone/Optional.empty():内容的这部分是完整的(尽管如果turn_complete也为 false,整个轮次可能尚未完成)。
# 伪代码:基本事件识别(Python)
# 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")
// 伪代码:基本事件识别(Java)
// import com.google.genai.types.Content;
// import com.google.adk.events.Event;
// import com.google.adk.events.EventActions;

// runner.runAsync(...).forEach(event -> { // 假设同步流或响应式流
//     System.out.println("Event from: " + event.author());
//
//     if (event.content().isPresent()) {
//         Content content = event.content().get();
//         if (!event.functionCalls().isEmpty()) {
//             System.out.println("  Type: Tool Call Request");
//         } else if (!event.functionResponses().isEmpty()) {
//             System.out.println("  Type: Tool Result");
//         } else if (content.parts().isPresent() && !content.parts().get().isEmpty() &&
//                    content.parts().get().get(0).text().isPresent()) {
//             if (event.partial().orElse(false)) {
//                 System.out.println("  Type: Streaming Text Chunk");
//             } else {
//                 System.out.println("  Type: Complete Text Message");
//             }
//         } else {
//             System.out.println("  Type: Other Content (e.g., code result)");
//         }
//     } else if (event.actions() != null &&
//                ((event.actions().stateDelta() != null && !event.actions().stateDelta().isEmpty()) ||
//                 (event.actions().artifactDelta() != null && !event.actions().artifactDelta().isEmpty()))) {
//         System.out.println("  Type: State/Artifact Update");
//     } else {
//         System.out.println("  Type: Control Signal or Other");
//     }
// });

提取关键信息

一旦知道了事件类型,就可以访问相关数据:

  • 文本内容: 在访问文本之前,始终检查内容和部分的存在。在 Python 中是text = event.content.parts[0].text

  • 函数调用详情:

    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}")
            # 应用程序可能基于此分派执行
    
    import com.google.genai.types.FunctionCall;
    import com.google.common.collect.ImmutableList;
    import java.util.Map;
    
    ImmutableList<FunctionCall> calls = event.functionCalls(); // 来自 Event.java
    if (!calls.isEmpty()) {
      for (FunctionCall call : calls) {
        String toolName = call.name().get();
        // args 是 Optional<Map<String, Object>>
        Map<String, Object> arguments = call.args().get();
               System.out.println("  Tool: " + toolName + ", Args: " + arguments);
        // 应用程序可能基于此分派执行
      }
    }
    
  • 函数响应详情:

    responses = event.get_function_responses()
    if responses:
        for response in responses:
            tool_name = response.name
            result_dict = response.response # 工具返回的字典
            print(f"  Tool Result: {tool_name} -> {result_dict}")
    
    import com.google.genai.types.FunctionResponse;
    import com.google.common.collect.ImmutableList;
    import java.util.Map; 
    
    ImmutableList<FunctionResponse> responses = event.functionResponses(); // 来自 Event.java
    if (!responses.isEmpty()) {
        for (FunctionResponse response : responses) {
            String toolName = response.name().get();
            Map<String, String> result= response.response().get(); // 获取响应前检查
            System.out.println("  Tool Result: " + toolName + " -> " + result);
        }
    }
    
  • 标识符:

    • 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 或应用程序状态
    

    ConcurrentMap<String, Object> delta = event.actions().stateDelta();

    import java.util.concurrent.ConcurrentMap;
    import com.google.adk.events.EventActions;
    
    EventActions actions = event.actions(); // 假设 event.actions() 不为 null
    if (actions != null && actions.stateDelta() != null && !actions.stateDelta().isEmpty()) {
        ConcurrentMap<String, Object> stateChanges = actions.stateDelta();
        System.out.println("  State changes: " + stateChanges);
        // 如有必要,更新本地 UI 或应用程序状态
    }
    
  • 工件保存: 为你提供一个集合,指示保存了哪些工件及其新版本号(或相关的Part信息)。

    artifact_changes = event.actions.artifact_delta{filename: version}的字典)。

    if event.actions and event.actions.artifact_delta:
        print(f"  Artifacts saved: {event.actions.artifact_delta}")
        # UI 可能刷新工件列表
    

    ConcurrentMap<String, Part> artifactChanges = event.actions().artifactDelta();

    import java.util.concurrent.ConcurrentMap;
    import com.google.genai.types.Part;
    import com.google.adk.events.EventActions;
    
    EventActions actions = event.actions(); // 假设 event.actions() 不为 null
    if (actions != null && actions.artifactDelta() != null && !actions.artifactDelta().isEmpty()) {
        ConcurrentMap<String, Part> artifactChanges = actions.artifactDelta();
        System.out.println("  Artifacts saved: " + artifactChanges);
        // UI 可能刷新工件列表
        // 遍历 artifactChanges.entrySet() 以获取文件名和 Part 详情
    }
    
  • 控制流信号: 检查布尔标志或字符串值:

    • event.actions.transfer_to_agent(字符串):控制应传递给指定的智能体。
    • event.actions.escalate(布尔):循环应该终止。
    • event.actions.skip_summarization(布尔):工具结果不应由 LLM 总结。
      if event.actions:
          if event.actions.transfer_to_agent:
              print(f"  Signal: Transfer to {event.actions.transfer_to_agent}")
          if event.actions.escalate:
              print("  Signal: Escalate (terminate loop)")
          if event.actions.skip_summarization:
              print("  Signal: Skip summarization for tool result")
      
    • event.actions().transferToAgent()(返回Optional<String>):控制应传递给指定的智能体。
    • event.actions().escalate()(返回Optional<Boolean>):循环应该终止。
    • event.actions().skipSummarization()(返回Optional<Boolean>):工具结果不应由 LLM 总结。
    import com.google.adk.events.EventActions;
    import java.util.Optional;
    
    EventActions actions = event.actions(); // 假设 event.actions() 不为 null
    if (actions != null) {
        Optional<String> transferAgent = actions.transferToAgent();
        if (transferAgent.isPresent()) {
            System.out.println("  Signal: Transfer to " + transferAgent.get());
        }
    
        Optional<Boolean> escalate = actions.escalate();
        if (escalate.orElse(false)) { // 或 escalate.isPresent() && escalate.get()
            System.out.println("  Signal: Escalate (terminate loop)");
        }
    
        Optional<Boolean> skipSummarization = actions.skipSummarization();
        if (skipSummarization.orElse(false)) { // 或 skipSummarization.isPresent() && skipSummarization.get()
            System.out.println("  Signal: Skip summarization for tool result");
        }
    }
    

确定事件是否为"最终"响应

使用内置辅助方法event.is_final_response()来识别适合作为智能体一轮完整输出显示的事件。

  • 目的: 从最终面向用户的消息中过滤掉中间步骤(如工具调用、部分流式文本、内部状态更新)。
  • 何时为True
    1. 事件包含工具结果(function_response)且skip_summarizationTrue
    2. 事件包含标记为is_long_running=True的工具的工具调用(function_call)。在 Java 中,检查longRunningToolIds列表是否为空:
      • event.longRunningToolIds().isPresent() && !event.longRunningToolIds().get().isEmpty()true
    3. 或者,满足所有以下条件:
      • 没有函数调用(get_function_calls()为空)。
      • 没有函数响应(get_function_responses()为空)。
      • 不是部分流块(partial不是True)。
      • 不以可能需要进一步处理/显示的代码执行结果结尾。
  • 用法: 在应用程序逻辑中过滤事件流。

    # 伪代码:在应用程序中处理最终响应(Python)
    # 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--- Final Output Detected ---")
    #         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"Display to user: {final_text.strip()}")
    #              full_response_text = "" # 重置累加器
    #         elif event.actions and event.actions.skip_summarization and event.get_function_responses():
    #              # 如需要,处理显示原始工具结果
    #              response_data = event.get_function_responses()[0].response
    #              print(f"Display raw tool result: {response_data}")
    #         elif hasattr(event, 'long_running_tool_ids') and event.long_running_tool_ids:
    #              print("Display message: Tool is running in background...")
    #         else:
    #              # 如适用,处理其他类型的最终响应
    #              print("Display: Final non-textual response or signal.")
    
    // 伪代码:在应用程序中处理最终响应(Java)
    import com.google.adk.events.Event;
    import com.google.genai.types.Content;
    import com.google.genai.types.FunctionResponse;
    import java.util.Map;
    
    StringBuilder fullResponseText = new StringBuilder();
    runner.run(...).forEach(event -> { // 假设事件流
         // 如需要,累积流式文本...
         if (event.partial().orElse(false) && event.content().isPresent()) {
             event.content().flatMap(Content::parts).ifPresent(parts -> {
                 if (!parts.isEmpty() && parts.get(0).text().isPresent()) {
                     fullResponseText.append(parts.get(0).text().get());
                }
             });
         }
    
         // 检查是否为最终可显示事件
         if (event.finalResponse()) { // 使用 Event.java 中的方法
             System.out.println("\n--- Final Output Detected ---");
             if (event.content().isPresent() &&
                 event.content().flatMap(Content::parts).map(parts -> !parts.isEmpty() && parts.get(0).text().isPresent()).orElse(false)) {
                 // 如果是流的最终部分,使用累积文本
                 String eventText = event.content().get().parts().get().get(0).text().get();
                 String finalText = fullResponseText.toString() + (event.partial().orElse(false) ? "" : eventText);
                 System.out.println("Display to user: " + finalText.trim());
                 fullResponseText.setLength(0); // 重置累加器
             } else if (event.actions() != null && event.actions().skipSummarization().orElse(false)
                        && !event.functionResponses().isEmpty()) {
                 // 如需要,处理显示原始工具结果,
                 // 特别是如果 finalResponse() 由于其他条件为 true
                 // 或者如果你想显示跳过总结的结果,无论 finalResponse() 如何
                 Map<String, Object> responseData = event.functionResponses().get(0).response().get();
                 System.out.println("Display raw tool result: " + responseData);
             } else if (event.longRunningToolIds().isPresent() && !event.longRunningToolIds().get().isEmpty()) {
                 // 此情况由 event.finalResponse() 覆盖
                 System.out.println("Display message: Tool is running in background...");
             } else {
                 // 如适用,处理其他类型的最终响应
                 System.out.println("Display: Final non-textual response or signal.");
             }
         }
     });
    

通过仔细检查事件的这些方面,你可以构建强大的应用程序,适当地响应 ADK 系统中流动的丰富信息。

事件流:生成和处理

事件在不同点创建,并由框架系统地处理。理解这个流程有助于阐明行动和历史是如何管理的。

  • 生成源:

    • 用户输入: Runner通常将初始用户消息或对话中的输入包装成author='user'Event
    • 智能体逻辑: 智能体(BaseAgentLlmAgent)显式yield Event(...)对象(设置author=self.name)来传达响应或发出动作信号。
    • LLM 响应: ADK 模型集成层将原始 LLM 输出(文本、函数调用、错误)转换为Event对象,由调用智能体创作。
    • 工具结果: 工具执行后,框架生成包含function_responseEventauthor通常是请求工具的智能体,而content内的role设置为'user'用于 LLM 历史。
  • 处理流程:

    1. 产出/返回: 事件由其源生成并产出(Python)或返回/发出(Java)。
    2. Runner 接收: 执行智能体的主Runner接收事件。
    3. SessionService 处理: Runner将事件发送到配置的SessionService。这是关键步骤:
      • 应用增量: 服务将event.actions.state_delta合并到session.state中,并基于event.actions.artifact_delta更新内部记录。(注意:实际的工件保存通常在调用context.save_artifact时较早发生)。
      • 完成元数据: 如果不存在,分配唯一的event.id,可能更新event.timestamp
      • 持久化到历史: 将处理后的事件追加到session.events列表。
    4. 外部产出: Runner向外产出(Python)或返回/发出(Java)处理后的事件到调用应用程序(例如,调用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 的事件 ...
    
  • 工具调用请求(由 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"]}}}]
      }
      // 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(链接工具动作):

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

    • 当你使用CallbackContextToolContext修改状态或保存工件时,这些变更不会立即写入持久存储。
    • 相反,它们填充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可以表示错误。检查event.error_codeevent.error_message字段(从LlmResponse继承)。
    • 错误可能源自 LLM(例如,安全过滤器、资源限制)或如果工具严重失败,可能由框架打包。检查工具的FunctionResponse内容以获取典型的工具特定错误。
      // 错误事件示例(概念性)
      {
        "author": "LLMAgent",
        "invocation_id": "e-err...",
        "content": null,
        "error_code": "SAFETY_FILTER_TRIGGERED",
        "error_message": "由于安全设置,响应被阻止。",
        "actions": {}
      }
      

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

使用事件的最佳实践

要在 ADK 应用程序中有效使用事件:

  • 明确作者身份: 构建自定义智能体时,确保历史中智能体动作的正确归属。框架通常正确处理 LLM/工具事件的作者身份。

    BaseAgent子类中使用yield Event(author=self.name, ...)

    在自定义智能体逻辑中构造Event时,设置作者,例如:Event.builder().author(this.getAgentName()) // ... .build();

  • 语义内容和动作: 使用event.content表示核心消息/数据(文本、函数调用/响应)。专门使用event.actions发出副作用信号(状态/工件增量)或控制流(transferescalateskip_summarization)。

  • 幂等性意识: 理解SessionService负责应用event.actions中发出信号的状态/工件变更。虽然 ADK 服务旨在保持一致性,但如果你的应用程序逻辑重新处理事件,请考虑潜在的下游影响。
  • 使用is_final_response() 在应用程序/UI 层依赖此辅助方法来识别完整的面向用户的文本响应。避免手动复制其逻辑。
  • 利用历史: 会话的事件列表是你的主要调试工具。检查作者、内容和动作的序列来跟踪执行和诊断问题。
  • 使用元数据: 使用invocation_id关联单个用户交互中的所有事件。使用event.id引用特定的唯一事件。

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