Skip to content

事件

Supported in ADKPython v0.1.0Go v0.1.0Java v0.1.0

事件是智能体开发工具包(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] # 层次结构路径
#     # ...

在 Go 中,这是一个 google.golang.org/adk/session.Event 类型的结构体。

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

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

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

在 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;        // 创建时间 (epoch 毫秒)
//     private EventActions actions;  // 对副作用和控制很重要
//     private Optional<String> branch; // 层次结构路径
//     // ... 其他字段,如 turnComplete, longRunningToolIds 等。
// }

由于几个关键原因,事件是 ADK 操作的核心:

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

理解和使用事件

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

注意

原语的特定参数或方法名称可能因 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.author}")
#
#     if event.content and event.content.parts:
#         if event.get_function_calls():
#             print("  类型: 工具调用请求")
#         elif event.get_function_responses():
#             print("  类型: 工具结果")
#         elif event.content.parts[0].text:
#             if event.partial:
#                 print("  类型: 流式文本块")
#             else:
#                 print("  类型: 完整文本消息")
#         else:
#             print("  类型: 其他内容 (例如,代码结果)")
#     elif event.actions and (event.actions.state_delta or event.actions.artifact_delta):
#         print("  类型: 状态/工件更新")
#     else:
#         print("  Type: Control Signal or Other")
  // 伪代码:基本事件识别 (Go)
import (
  "fmt"
  "google.golang.org/adk/session"
  "google.golang.org/genai"
)

func hasFunctionCalls(content *genai.Content) bool {
  if content == nil {
    return false
  }
  for _, part := range content.Parts {
    if part.FunctionCall != nil {
      return true
    }
  }
  return false
}

func hasFunctionResponses(content *genai.Content) bool {
  if content == nil {
    return false
  }
  for _, part := range content.Parts {
    if part.FunctionResponse != nil {
      return true
    }
  }
  return false
}

func processEvents(events <-chan *session.Event) {
  for event := range events {
    fmt.Printf("事件来源: %s\n", event.Author)

    if event.LLMResponse != nil && event.LLMResponse.Content != nil {
      if hasFunctionCalls(event.LLMResponse.Content) {
        fmt.Println("  类型: 工具调用请求")
      } else if hasFunctionResponses(event.LLMResponse.Content) {
        fmt.Println("  类型: 工具结果")
      } else if len(event.LLMResponse.Content.Parts) > 0 {
        if event.LLMResponse.Content.Parts[0].Text != "" {
          if event.LLMResponse.Partial {
            fmt.Println("  类型: 流式文本块")
          } else {
            fmt.Println("  类型: 完整文本消息")
          }
        } else {
          fmt.Println("  类型: 其他内容 (例如,代码结果)")
        }
      }
    } else if len(event.Actions.StateDelta) > 0 {
      fmt.Println("  类型: 状态更新")
    } else {
      fmt.Println("  类型: 控制信号或其他")
    }
  }
}
// Pseudocode: Basic event identification (Java)
// import com.google.genai.types.Content;
// import com.google.adk.events.Event;
// import com.google.adk.events.EventActions;

// runner.runAsync(...).forEach(event -> { // Assuming a synchronous stream or reactive stream
//     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");
//     }
// });

Extracting Key Information

```java
// 伪代码:基本事件识别 (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.author());
//
//     if (event.content().isPresent()) {
//         Content content = event.content().get();
//         if (!event.functionCalls().isEmpty()) {
//             System.out.println("  类型: 工具调用请求");
//         } else if (!event.functionResponses().isEmpty()) {
//             System.out.println("  类型: 工具结果");
//         } else if (content.parts().isPresent() && !content.parts().get().isEmpty() &&
//                    content.parts().get().get(0).text().isPresent()) {
//             if (event.partial().orElse(false)) {
//                 System.out.println("  类型: 流式文本块");
//             } else {
//                 System.out.println("  类型: 完整文本消息");
//             }
//         } else {
//             System.out.println("  类型: 其他内容 (例如,代码结果)");
//         }
//     } else if (event.actions() != null &&
//                ((event.actions().stateDelta() != null && !event.actions().stateDelta().isEmpty()) ||
//                 (event.actions().artifactDelta() != null && !event.actions().artifactDelta().isEmpty()))) {
//         System.out.println("  类型: 状态/工件更新");
//     } else {
//         System.out.println("  类型: 控制信号或其他");
//     }
// });
```

提取关键信息

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

  • 文本内容: 在访问文本之前,请务必检查内容和部分是否存在。在 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_name}, 参数:{arguments}")
            # 应用程序可能会根据此信息分派执行
    
    import (
        "fmt"
        "google.golang.org/adk/session"
        "google.golang.org/genai"
    )
    
    func handleFunctionCalls(event *session.Event) {
        if event.LLMResponse == nil || event.LLMResponse.Content == nil {
            return
        }
        calls := event.Content.FunctionCalls()
        if len(calls) > 0 {
            for _, call := range calls {
                toolName := call.Name
                arguments := call.Args
                fmt.Printf("  Tool: %s, Args: %v\n", toolName, arguments)
                // Application might dispatch execution based on this
            }
        }
    }
    
    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("  工具:" + toolName + ", 参数:" + 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}")
    
    import (
        "fmt"
        "google.golang.org/adk/session"
        "google.golang.org/genai"
    )
    
    func handleFunctionResponses(event *session.Event) {
        if event.LLMResponse == nil || event.LLMResponse.Content == nil {
            return
        }
        responses := event.Content.FunctionResponses()
        if len(responses) > 0 {
            for _, response := range responses {
                toolName := response.Name
                result := response.Response
                fmt.Printf("  工具结果:%s -> %v\n", toolName, result)
            }
        }
    }
    
    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("  工具结果:" + toolName + " -> " + result);
        }
    }
    
  • 标识符:

    • event.id: 此特定事件实例的唯一 ID。
    • event.invocation_id: 此事件所属的整个用户请求到最终响应周期的 ID。用于日志记录和跟踪。

检测操作和副作用

event.actions 对象表示已发生或应发生的更改。在访问 event.actions 及其字段/方法之前,请务必检查它们是否存在。

  • State Changes: Gives you a collection of key-value pairs that were modified in the session state during the step that produced this event.

    delta = event.actions.state_delta (a dictionary of {key: value} pairs).

    if event.actions and event.actions.state_delta:
        print(f"  State changes: {event.actions.state_delta}")
        # Update local UI or application state if necessary
    

    delta := event.Actions.StateDelta (一个 map[string]any)

    import (
        "fmt"
        "google.golang.org/adk/session"
    )
    
    func handleStateChanges(event *session.Event) {
        if len(event.Actions.StateDelta) > 0 {
            fmt.Printf("  状态变更:%v\n", event.Actions.StateDelta)
            // 如有必要,更新本地 UI 或应用程序状态
        }
    }
    

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

    import java.util.concurrent.ConcurrentMap;
    import com.google.adk.events.EventActions;
    
    EventActions actions = event.actions(); // Assuming event.actions() is not null
    if (actions != null && actions.stateDelta() != null && !actions.stateDelta().isEmpty()) {
        ConcurrentMap<String, Object> stateChanges = actions.stateDelta();
        System.out.println("  State changes: " + stateChanges);
        // Update local UI or application state if necessary
    }
    
  • 工件保存: 为你提供一个集合,指示保存了哪些工件及其新版本号(或相关的 Part 信息)。

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

    artifactChanges := event.Actions.ArtifactDelta (一个 map[string]artifact.Artifact)

    import (
        "fmt"
        "google.golang.org/adk/artifact"
        "google.golang.org/adk/session"
    )
    
    func handleArtifactChanges(event *session.Event) {
        if len(event.Actions.ArtifactDelta) > 0 {
            fmt.Printf("  工件已保存:%v\n", event.Actions.ArtifactDelta)
            // UI 可能会刷新工件列表
            // 迭代 event.Actions.ArtifactDelta 以获取文件名和 artifact.Artifact 详细信息
            for filename, art := range event.Actions.ArtifactDelta {
                fmt.Printf("    文件名:%s, 版本:%d, MIME 类型:%s\n", filename, art.Version, art.MIMEType)
            }
        }
    }
    

    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(); // Assuming event.actions() is not null
    if (actions != null && actions.artifactDelta() != null && !actions.artifactDelta().isEmpty()) {
        ConcurrentMap<String, Part> artifactChanges = actions.artifactDelta();
        System.out.println("  Artifacts saved: " + artifactChanges);
        // UI might refresh an artifact list
        // Iterate through artifactChanges.entrySet() to get filename and Part details
    }
    
  • 控制流信号: 检查布尔标志或字符串值:

    • 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.Actions.TransferToAgent (string): 控制应传递给指定的智能体。
    • event.Actions.Escalate (bool): 循环应终止。
    • event.Actions.SkipSummarization (bool): 工具结果不应由 LLM 总结。
      import (
          "fmt"
          "google.golang.org/adk/session"
      )
      
      func handleControlFlow(event *session.Event) {
          if event.Actions.TransferToAgent != "" {
              fmt.Printf("  信号:转移到 %s\n", event.Actions.TransferToAgent)
          }
          if event.Actions.Escalate {
              fmt.Println("  信号:升级 (终止循环)")
          }
          if event.Actions.SkipSummarization {
              fmt.Println("  信号:跳过工具结果的总结")
          }
      }
      
    • event.actions().transferToAgent() (returns Optional<String>): Control should pass to the named agent.
    • event.actions().escalate() (returns Optional<Boolean>): A loop should terminate.
    • event.actions().skipSummarization() (returns Optional<Boolean>): A tool result should not be summarized by the LLM.
    import com.google.adk.events.EventActions;
    import java.util.Optional;
    
    EventActions actions = event.actions(); // Assuming event.actions() is not 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)) { // or escalate.isPresent() && escalate.get()
            System.out.println("  Signal: Escalate (terminate loop)");
        }
    
        Optional<Boolean> skipSummarization = actions.skipSummarization();
        if (skipSummarization.orElse(false)) { // or skipSummarization.isPresent() && skipSummarization.get()
            System.out.println("  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("  信号:转移到 " + transferAgent.get());
        }
    
        Optional<Boolean> escalate = actions.escalate();
        if (escalate.orElse(false)) { // 或 escalate.isPresent() && escalate.get()
            System.out.println("  信号:升级 (终止循环)");
        }
    
        Optional<Boolean> skipSummarization = actions.skipSummarization();
        if (skipSummarization.orElse(false)) { // 或 skipSummarization.isPresent() && skipSummarization.get()
            System.out.println("  信号:跳过工具结果的总结");
        }
    }
    

判断事件是否为“最终”响应

使用内置的辅助方法 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--- 检测到最终输出 ---")
    #         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 and event.actions.skip_summarization and event.get_function_responses():
    #              # 如果需要,处理显示原始工具结果
    #              response_data = event.get_function_responses()[0].response
    #              print(f"显示原始工具结果:{response_data}")
    #         elif hasattr(event, 'long_running_tool_ids') and event.long_running_tool_ids:
    #              print("显示消息:工具正在后台运行...")
    #         else:
    #              # Handle other types of final responses if applicable
    #              print("Display: Final non-textual response or signal.")
    
    // 伪代码:在应用程序中处理最终响应 (Go)
    import (
        "fmt"
        "strings"
        "google.golang.org/adk/session"
        "google.golang.org/genai"
    )
    
    // isFinalResponse 检查事件是否是适合显示的最终响应。
    func isFinalResponse(event *session.Event) bool {
        if event.LLMResponse != nil {
            // 条件 1: 带有跳过总结的工具结果。
            if event.LLMResponse.Content != nil && len(event.LLMResponse.Content.FunctionResponses()) > 0 && event.Actions.SkipSummarization {
                return true
            }
            // 条件 2: 长时间运行的工具调用。
            if len(event.LongRunningToolIDs) > 0 {
                return true
            }
            // 条件 3: 没有工具调用或响应的完整消息。
            if (event.LLMResponse.Content == nil ||
                (len(event.LLMResponse.Content.FunctionCalls()) == 0 && len(event.LLMResponse.Content.FunctionResponses()) == 0)) &&
                !event.LLMResponse.Partial {
                return true
            }
        }
        return false
    }
    
    func handleFinalResponses() {
        var fullResponseText strings.Builder
        // for event := range runner.Run(...) { // 示例循环
        //  // 如果需要,累积流式文本...
        //  if event.LLMResponse != nil && event.LLMResponse.Partial && event.LLMResponse.Content != nil {
        //      if len(event.LLMResponse.Content.Parts) > 0 && event.LLMResponse.Content.Parts[0].Text != "" {
        //          fullResponseText.WriteString(event.LLMResponse.Content.Parts[0].Text)
        //      }
        //  }
        //
        //  // 检查它是否是最终的可显示事件
        //  if isFinalResponse(event) {
        //      fmt.Println("\n--- 检测到最终输出 ---")
        //      if event.LLMResponse != nil && event.LLMResponse.Content != nil {
        //          if len(event.LLMResponse.Content.Parts) > 0 && event.LLMResponse.Content.Parts[0].Text != "" {
        //              // 如果它是流的最后一部分,则使用累积的文本
        //              finalText := fullResponseText.String()
        //              if !event.LLMResponse.Partial {
        //                  finalText += event.LLMResponse.Content.Parts[0].Text
        //              }
        //              fmt.Printf("向用户显示:%s\n", strings.TrimSpace(finalText))
        //              fullResponseText.Reset() // 重置累加器
        //          }
        //      } else if event.Actions.SkipSummarization && event.LLMResponse.Content != nil && len(event.LLMResponse.Content.FunctionResponses()) > 0 {
        //          // 如果需要,处理显示原始工具结果
        //          responseData := event.LLMResponse.Content.FunctionResponses()[0].Response
        //          fmt.Printf("显示原始工具结果:%v\n", responseData)
        //      } else if len(event.LongRunningToolIDs) > 0 {
        //          fmt.Println("显示消息:工具正在后台运行...")
        //      } else {
        //          // 如果适用,处理其他类型的最终响应
        //          fmt.Println("显示:最终的非文本响应或信号。")
        //      }
        //  }
        // }
    }
    
    // Pseudocode: Handling final responses in application (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 -> { // Assuming a stream of events
         // Accumulate streaming text if needed...
         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());
                }
             });
         }
    
         // Check if it's a final, displayable event
         if (event.finalResponse()) { // Using the method from 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)) {
                 // If it's the final part of a stream, use accumulated text
                 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); // Reset accumulator
             } else if (event.actions() != null && event.actions().skipSummarization().orElse(false)
                        && !event.functionResponses().isEmpty()) {
                 // Handle displaying the raw tool result if needed,
                 // especially if finalResponse() was true due to other conditions
                 // or if you want to display skipped summarization results regardless of 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()) {
                 // This case is covered by event.finalResponse()
                 System.out.println("Display message: Tool is running in background...");
             } else {
                 // Handle other types of final responses if applicable
                 System.out.println("Display: Final non-textual response or signal.");
             }
         }
     });
    

By carefully examining these aspects of an event, you can build robust applications that react appropriately to the rich information flowing through the ADK system.

    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--- 检测到最终输出 ---");
             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("向用户显示:" + 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("显示原始工具结果:" + responseData);
             } else if (event.longRunningToolIds().isPresent() && !event.longRunningToolIds().get().isEmpty()) {
                 // 这种情况由 event.finalResponse() 覆盖
                 System.out.println("显示消息:工具正在后台运行...");
             } else {
                 // 如果适用,处理其他类型的最终响应
                 System.out.println("显示:最终的非文本响应或信号。");
             }
         }
     });
    ```

通过仔细检查事件的这些方面,你可以构建出能够对流经 ADK 系统的丰富信息做出适当反应的健壮应用程序。

事件如何流动:生成和处理

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

  • 生成来源:

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

    1. Yield/Return: 事件由其源生成并 yield (Python) 或 return/emit (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. 外部 Yield: Runner 将处理后的事件向外 yield (Python) 或 return/emit (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 可能有状态增量等。
    }
    
  • 智能体流式文本响应: (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, ...)

    在自定义智能体 Run 方法中,框架通常会处理作者身份。如果手动创建事件,请设置作者:yield(&session.Event{Author: a.name, ...}, nil)

    在你的自定义智能体逻辑中构造 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 中复杂智能体行为的关键。