Files
Agent-Docs/ADKLearning/google-adk-tutorial/07-callbacks-and-events.md
Nelson 514723dbe7 init
2026-04-06 12:36:04 +08:00

20 KiB
Raw Blame History

第07章回调机制与事件系统

📌 本章目标

  • 理解 ADK 的事件系统和工作原理
  • 掌握四种回调函数的使用方法
  • 学习如何通过回调实现自定义逻辑
  • 了解 Human-in-the-Loop人机协作模式
  • 掌握事件流的处理和过滤

7.1 事件系统概述

7.1.1 什么是事件Event

在 ADK 中Agent 的每一次操作都会产生事件Event。事件是 Agent 执行过程中的基本单元,记录了从接收用户输入到返回最终响应的完整过程。

用户输入 → [事件1] → [事件2] → ... → [事件N] → 最终响应
           │          │                │
           │          │                └── 最终响应事件
           │          └── 工具调用事件
           └── 模型调用事件

7.1.2 事件类型

事件类型 说明 触发时机
Model Event LLM 模型调用事件 Agent 调用 LLM 时
Tool Event 工具调用事件 Agent 调用工具时
Transfer Event Agent 转移事件 LLM 决定委派给其他 Agent 时
Final Response 最终响应事件 Agent 生成最终回复时
Error Event 错误事件 执行过程中发生错误时

7.2 回调函数详解

ADK 提供了四种回调函数,允许你在 Agent 执行的关键节点插入自定义逻辑:

用户输入
   │
   ▼
┌─────────────────────────────────────┐
│  before_model_callback              │ ← 模型调用前
│  (可以修改输入、记录日志等)         │
├─────────────────────────────────────┤
│  LLM 模型调用                        │
├─────────────────────────────────────┤
│  after_model_callback               │ ← 模型调用后
│  (可以修改输出、记录响应等)         │
├─────────────────────────────────────┤
│  before_tool_callback               │ ← 工具调用前
│  (可以验证参数、阻止调用等)         │
├─────────────────────────────────────┤
│  工具执行                            │
├─────────────────────────────────────┤
│  after_tool_callback                │ ← 工具调用后
│  (可以处理结果、记录日志等)         │
└─────────────────────────────────────┘

7.2.1 before_model_callback模型调用前

"""
before_model_callback 示例
在 LLM 模型调用之前执行
可以修改输入、记录日志、添加上下文等
"""

from google.adk.agents import Agent          # 导入 Agent 类
from google.adk.agents.invocation_context import InvocationContext  # 导入调用上下文


# ========================================
# 定义 before_model_callback
# ========================================

async def my_before_model_callback(
    callback_context,                        # 回调上下文
    invocation_context: InvocationContext,   # 调用上下文
):
    """
    模型调用前的回调函数

    Args:
        callback_context: 回调上下文,包含工具调用信息
        invocation_context: 调用上下文,包含会话和状态信息
    """
    # 记录日志:模型即将被调用
    print(f"[日志] Agent '{invocation_context.agent.name}' 即将调用模型")  # 打印日志

    # 访问会话状态
    call_count = invocation_context.session.state.get(  # 读取调用计数
        "model_call_count",                     # 状态键名
        0,                                      # 默认值
    )

    # 更新调用计数
    invocation_context.session.state["model_call_count"] = call_count + 1  # 递增计数

    print(f"[日志] 这是第 {call_count + 1} 次模型调用")  # 打印调用次数

    # 可以在这里添加额外的上下文信息
    # 例如:将用户偏好注入到状态中
    invocation_context.session.state["temp:call_timestamp"] = "2025-04-05T10:00:00Z"  # 记录时间戳


# ========================================
# 创建使用回调的 Agent
# ========================================

agent = Agent(
    name="callback_agent",                   # Agent 名称
    model="gemini-2.0-flash",               # 模型
    instruction="你是一个助手。",             # 指令
    before_model_callback=my_before_model_callback,  # 🔑 注册模型前回调
)

7.2.2 after_model_callback模型调用后

"""
after_model_callback 示例
在 LLM 模型调用之后执行
可以修改输出、记录响应、触发后续操作等
"""

from google.adk.agents import Agent          # 导入 Agent 类
from google.adk.agents.invocation_context import InvocationContext  # 导入调用上下文


async def my_after_model_callback(
    callback_context,                        # 回调上下文
    invocation_context: InvocationContext,   # 调用上下文
):
    """
    模型调用后的回调函数

    Args:
        callback_context: 回调上下文,包含模型响应信息
        invocation_context: 调用上下文
    """
    # 获取模型响应
    response = callback_context.response     # 模型响应对象

    if response:                             # 如果有响应
        # 记录响应信息
        print(f"[日志] 模型返回了响应")       # 打印日志

        # 检查响应中是否有工具调用
        if response.function_calls:          # 如果有函数调用
            for fc in response.function_calls:  # 遍历函数调用
                print(f"[日志] 模型决定调用工具: {fc.name}")  # 打印工具名
        else:                                # 如果没有函数调用
            print(f"[日志] 模型直接返回了文本响应")  # 打印日志

    # 可以在这里进行响应后处理
    # 例如:记录 token 使用量、响应延迟等
    invocation_context.session.state["temp:last_model_call"] = "completed"  # 标记完成


agent = Agent(
    name="callback_agent",                   # Agent 名称
    model="gemini-2.0-flash",               # 模型
    instruction="你是一个助手。",             # 指令
    after_model_callback=my_after_model_callback,  # 🔑 注册模型后回调
)

7.2.3 before_tool_callback工具调用前

"""
before_tool_callback 示例
在工具调用之前执行
可以验证参数、阻止调用、修改参数等
"""

from google.adk.agents import Agent          # 导入 Agent 类
from google.adk.agents.invocation_context import InvocationContext  # 导入调用上下文


async def my_before_tool_callback(
    callback_context,                        # 回调上下文
    invocation_context: InvocationContext,   # 调用上下文
):
    """
    工具调用前的回调函数

    Args:
        callback_context: 回调上下文,包含工具调用信息
        invocation_context: 调用上下文
    """
    # 获取工具调用信息
    tool_call = callback_context.function_call  # 函数调用对象
    tool_name = tool_call.name               # 工具名称
    tool_args = tool_call.args               # 工具参数

    print(f"[日志] 即将调用工具: {tool_name}")  # 打印工具名
    print(f"[日志] 工具参数: {tool_args}")      # 打印参数

    # ========================================
    # 示例敏感操作确认Human-in-the-Loop
    # ========================================
    sensitive_tools = ["delete_data", "send_email", "make_payment"]  # 敏感操作列表

    if tool_name in sensitive_tools:         # 如果是敏感操作
        print(f"[警告] 敏感操作: {tool_name},需要人工确认!")  # 打印警告
        # 在实际应用中,这里可以:
        # 1. 发送确认请求给用户
        # 2. 阻止工具执行
        # 3. 记录审计日志

    # ========================================
    # 示例:参数验证
    # ========================================
    if tool_name == "search":                # 如果是搜索工具
        query = tool_args.get("query", "")   # 获取查询参数
        if len(query) < 2:                   # 如果查询太短
            print("[警告] 搜索查询过短")      # 打印警告


agent = Agent(
    name="callback_agent",                   # Agent 名称
    model="gemini-2.0-flash",               # 模型
    instruction="你是一个助手。",             # 指令
    before_tool_callback=my_before_tool_callback,  # 🔑 注册工具前回调
)

7.2.4 after_tool_callback工具调用后

"""
after_tool_callback 示例
在工具调用之后执行
可以处理结果、记录日志、触发后续操作等
"""

from google.adk.agents import Agent          # 导入 Agent 类
from google.adk.agents.invocation_context import InvocationContext  # 导入调用上下文


async def my_after_tool_callback(
    callback_context,                        # 回调上下文
    invocation_context: InvocationContext,   # 调用上下文
):
    """
    工具调用后的回调函数

    Args:
        callback_context: 回调上下文,包含工具调用结果
        invocation_context: 调用上下文
    """
    # 获取工具调用信息
    tool_call = callback_context.function_call  # 函数调用对象
    tool_result = callback_context.tool_result  # 工具执行结果

    print(f"[日志] 工具 {tool_call.name} 执行完成")  # 打印完成日志
    print(f"[日志] 执行结果: {tool_result}")       # 打印结果

    # ========================================
    # 示例:结果后处理
    # ========================================
    if tool_result:                          # 如果有结果
        # 将工具结果保存到状态中
        invocation_context.session.state[  # 保存到状态
            f"temp:last_{tool_call.name}_result"  # 动态键名
        ] = str(tool_result)                 # 保存结果字符串

    # ========================================
    # 示例:错误处理
    # ========================================
    if tool_result and tool_result.get("status") == "error":  # 如果工具返回错误
        print(f"[错误] 工具执行失败: {tool_result.get('error_message')}")  # 打印错误
        # 可以在这里进行错误恢复或通知


agent = Agent(
    name="callback_agent",                   # Agent 名称
    model="gemini-2.0-flash",               # 模型
    instruction="你是一个助手。",             # 指令
    after_tool_callback=my_after_tool_callback,  # 🔑 注册工具后回调
)

7.2.5 组合使用所有回调

"""
组合使用所有回调函数
创建一个完整的带日志和监控的 Agent
"""

from google.adk.agents import Agent          # 导入 Agent 类
from google.adk.agents.invocation_context import InvocationContext  # 导入调用上下文
import time                                  # 导入时间模块


# ========================================
# 日志回调:记录所有操作
# ========================================

async def log_before_model(cb_ctx, inv_ctx):
    """模型调用前:记录开始时间"""
    inv_ctx.session.state["temp:model_start_time"] = time.time()  # 记录开始时间
    print(f"🔍 [模型] 开始调用 LLM...")       # 打印日志


async def log_after_model(cb_ctx, inv_ctx):
    """模型调用后:计算耗时"""
    start = inv_ctx.session.state.get("temp:model_start_time", time.time())  # 获取开始时间
    elapsed = time.time() - start            # 计算耗时
    print(f"✅ [模型] LLM 调用完成,耗时: {elapsed:.2f}s")  # 打印耗时


async def log_before_tool(cb_ctx, inv_ctx):
    """工具调用前:记录工具信息"""
    fc = cb_ctx.function_call               # 获取函数调用
    print(f"🔧 [工具] 调用工具: {fc.name}({fc.args})")  # 打印工具信息


async def log_after_tool(cb_ctx, inv_ctx):
    """工具调用后:记录结果"""
    fc = cb_ctx.function_call               # 获取函数调用
    result = cb_ctx.tool_result             # 获取工具结果
    status = "成功" if result and result.get("status") == "success" else "失败"  # 判断状态
    print(f"📊 [工具] {fc.name} 执行{status}")  # 打印结果


# ========================================
# 创建带完整回调的 Agent
# ========================================

def get_weather(city: str) -> dict:
    """获取天气"""
    return {"status": "success", "weather": "晴天"}  # 模拟天气数据


monitored_agent = Agent(
    name="monitored_agent",                  # Agent 名称
    model="gemini-2.0-flash",               # 模型
    instruction="你是一个天气助手。",         # 指令
    tools=[get_weather],                     # 工具
    before_model_callback=log_before_model,  # 模型前回调
    after_model_callback=log_after_model,    # 模型后回调
    before_tool_callback=log_before_tool,    # 工具前回调
    after_tool_callback=log_after_tool,      # 工具后回调
)

7.3 Human-in-the-Loop人机协作

7.3.1 实现人工确认

"""
Human-in-the-Loop 模式
在关键操作前暂停,等待人工确认
"""

from google.adk.agents import Agent          # 导入 Agent 类
from google.adk.agents.invocation_context import InvocationContext  # 导入调用上下文


# ========================================
# 定义需要人工确认的工具
# ========================================

def delete_file(filename: str) -> dict:
    """
    删除文件(危险操作)

    Args:
        filename (str): 要删除的文件名

    Returns:
        dict: 操作结果
    """
    # 在实际应用中,这里会执行真正的删除操作
    print(f"🗑️ 已删除文件: {filename}")       # 打印删除信息

    return {                                 # 返回结果
        "status": "success",                 # 状态
        "message": f"文件 '{filename}' 已删除",  # 消息
    }


def send_email(to: str, subject: str, body: str) -> dict:
    """
    发送邮件(敏感操作)

    Args:
        to (str): 收件人邮箱
        subject (str): 邮件主题
        body (str): 邮件正文

    Returns:
        dict: 操作结果
    """
    print(f"📧 已发送邮件到: {to}")          # 打印发送信息

    return {                                 # 返回结果
        "status": "success",
        "message": f"邮件已发送到 {to}",
    }


# ========================================
# 工具调用前回调:人工确认
# ========================================

async def human_confirmation_callback(
    callback_context,                        # 回调上下文
    invocation_context: InvocationContext,   # 调用上下文
):
    """
    人工确认回调
    在执行敏感操作前暂停,等待人工确认
    """
    tool_name = callback_context.function_call.name  # 获取工具名
    tool_args = callback_context.function_call.args  # 获取工具参数

    # 定义需要人工确认的操作
    dangerous_operations = {                 # 危险操作映射
        "delete_file": "删除文件",            # 删除文件
        "send_email": "发送邮件",             # 发送邮件
    }

    if tool_name in dangerous_operations:    # 如果是危险操作
        operation = dangerous_operations[tool_name]  # 获取操作描述
        print(f"\n⚠️  需要人工确认!")          # 打印警告
        print(f"操作: {operation}")           # 打印操作
        print(f"参数: {tool_args}")           # 打印参数
        print(f"请确认是否继续...")            # 提示确认

        # 在实际应用中,这里应该:
        # 1. 通过 API 发送确认请求给前端
        # 2. 等待用户确认
        # 3. 根据确认结果决定是否继续

        # 模拟人工确认(实际应用中应替换为真正的确认机制)
        # 如果用户拒绝,可以抛出异常来阻止工具执行
        # raise ValueError("用户拒绝了此操作")


# ========================================
# 创建带人工确认的 Agent
# ========================================

safe_agent = Agent(
    name="safe_agent",                      # Agent 名称
    model="gemini-2.0-flash",               # 模型
    instruction=(                           # 指令
        "你是一个文件管理助手。\n"
        "可以查看和删除文件,也可以发送邮件。\n"
        "执行敏感操作前需要人工确认。"
    ),
    tools=[                                 # 工具列表
        delete_file,                        # 删除文件
        send_email,                         # 发送邮件
    ],
    before_tool_callback=human_confirmation_callback,  # 🔑 人工确认回调
)

7.4 事件流处理

7.4.1 处理不同类型的事件

"""
事件流处理
从 Runner 的事件流中提取和处理不同类型的事件
"""

from google.adk.agents import Agent          # 导入 Agent 类
from google.adk.runners import Runner        # 导入运行器
from google.adk.sessions import InMemorySessionService  # 导入会话服务
from google.genai import types               # 导入类型定义
import asyncio                               # 导入异步库


agent = Agent(
    name="event_agent",                     # Agent 名称
    model="gemini-2.0-flash",               # 模型
    instruction="你是一个助手。",             # 指令
)


async def process_events():
    """处理事件流的完整示例"""

    # 初始化
    session_service = InMemorySessionService()  # 会话服务
    await session_service.create_session(    # 创建会话
        app_name="event_app",                # 应用名称
        user_id="user_001",                  # 用户 ID
        session_id="event_001",              # 会话 ID
    )

    runner = Runner(
        agent=agent,                         # Agent
        app_name="event_app",                # 应用名称
        session_service=session_service,     # 会话服务
    )

    # 构造消息
    content = types.Content(
        role='user',                         # 角色
        parts=[types.Part(text='你好')],     # 内容
    )

    # 运行并处理事件
    events = runner.run_async(
        user_id="user_001",                  # 用户 ID
        session_id="event_001",              # 会话 ID
        new_message=content,                 # 消息
    )

    async for event in events:               # 遍历事件流
        # 检查事件类型并分别处理
        event_type = type(event).__name__    # 获取事件类型名
        print(f"[事件] 类型: {event_type}")  # 打印事件类型

        # 处理最终响应
        if event.is_final_response():        # 如果是最终响应
            text = event.content.parts[0].text  # 提取文本
            print(f"[最终响应] {text}")      # 打印响应

        # 处理工具调用
        if hasattr(event, 'function_call') and event.function_call:  # 如果有工具调用
            fc = event.function_call          # 获取函数调用
            print(f"[工具调用] {fc.name}({fc.args})")  # 打印调用信息

        # 处理工具响应
        if hasattr(event, 'function_response') and event.function_response:  # 如果有工具响应
            fr = event.function_response      # 获取函数响应
            print(f"[工具响应] {fr.name}: {fr.response}")  # 打印响应


asyncio.run(process_events())                # 执行事件处理

📌 本章小结

  • ADK 通过事件系统记录 Agent 的完整执行过程
  • 四种回调函数before_model、after_model、before_tool、after_tool
  • 回调函数可用于日志记录、参数验证、人工确认等
  • Human-in-the-Loop 模式通过 before_tool_callback 实现
  • Runner 返回的事件流可以逐个处理不同类型的事件

下一章第08章 - 智能体评估 → 学习如何系统地评估 Agent 的性能。