Files
Nelson 514723dbe7 init
2026-04-06 12:36:04 +08:00

256 lines
8.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Google ADK 回调机制完整示例
展示四种回调函数的使用方法
对应教程第07章 - 回调机制与事件系统
"""
# 导入 ADK 核心模块
from google.adk.agents import Agent # Agent 类
from google.adk.agents.invocation_context import InvocationContext # 调用上下文
from google.adk.runners import Runner # 运行器
from google.adk.sessions import InMemorySessionService # 会话服务
from google.genai import types # 类型定义
# 导入异步和时间模块
import asyncio # 异步编程库
import time # 时间模块
# ========================================
# 示例一:日志回调(记录所有操作)
# ========================================
async def log_before_model(cb_ctx, inv_ctx):
"""
模型调用前回调
Args:
cb_ctx: 回调上下文
inv_ctx: 调用上下文
"""
# 记录开始时间
inv_ctx.session.state["temp:model_start_time"] = time.time() # 保存时间戳
# 获取调用计数
count = inv_ctx.session.state.get("model_call_count", 0) # 读取计数
inv_ctx.session.state["model_call_count"] = count + 1 # 递增
print(f"🔍 [模型前] Agent '{inv_ctx.agent.name}' 即将调用 LLM (第{count+1}次)") # 打印日志
async def log_after_model(cb_ctx, inv_ctx):
"""
模型调用后回调
Args:
cb_ctx: 回调上下文
inv_ctx: 调用上下文
"""
# 计算耗时
start = inv_ctx.session.state.get("temp:model_start_time", time.time()) # 开始时间
elapsed = time.time() - start # 计算耗时
response = cb_ctx.response # 获取模型响应
if response and response.function_calls: # 如果有工具调用
tools = [fc.name for fc in response.function_calls] # 工具名列表
print(f"✅ [模型后] LLM 调用完成 ({elapsed:.2f}s),决定调用工具: {tools}") # 打印
else: # 如果直接响应
print(f"✅ [模型后] LLM 调用完成 ({elapsed:.2f}s),直接响应") # 打印
async def log_before_tool(cb_ctx, inv_ctx):
"""
工具调用前回调
Args:
cb_ctx: 回调上下文
inv_ctx: 调用上下文
"""
fc = cb_ctx.function_call # 获取函数调用
print(f"🔧 [工具前] 调用工具: {fc.name}({fc.args})") # 打印工具信息
async def log_after_tool(cb_ctx, inv_ctx):
"""
工具调用后回调
Args:
cb_ctx: 回调上下文
inv_ctx: 调用上下文
"""
fc = cb_ctx.function_call # 获取函数调用
result = cb_ctx.tool_result # 获取工具结果
status = "成功" # 默认成功
if result and result.get("status") == "error": # 如果错误
status = "失败" # 标记失败
print(f"📊 [工具后] {fc.name} 执行{status}") # 打印结果
# ========================================
# 示例二:人工确认回调
# ========================================
async def human_confirmation(cb_ctx, inv_ctx):
"""
人工确认回调
在敏感操作前暂停
Args:
cb_ctx: 回调上下文
inv_ctx: 调用上下文
"""
tool_name = cb_ctx.function_call.name # 获取工具名
tool_args = cb_ctx.function_call.args # 获取工具参数
# 定义敏感操作
sensitive_ops = { # 敏感操作映射
"delete_file": "删除文件", # 删除文件
"send_email": "发送邮件", # 发送邮件
"make_payment": "发起支付", # 发起支付
}
if tool_name in sensitive_ops: # 如果是敏感操作
op = sensitive_ops[tool_name] # 获取操作描述
print(f"\n⚠️ [人工确认] 需要人工确认!") # 打印警告
print(f" 操作: {op}") # 打印操作
print(f" 参数: {tool_args}") # 打印参数
print(f" 状态: 已记录(模拟自动通过)") # 模拟通过
# ========================================
# 示例三:参数验证回调
# ========================================
async def validate_params(cb_ctx, inv_ctx):
"""
参数验证回调
在工具调用前验证参数
Args:
cb_ctx: 回调上下文
inv_ctx: 调用上下文
"""
tool_name = cb_ctx.function_call.name # 工具名
tool_args = cb_ctx.function_call.args # 工具参数
# 验证搜索查询长度
if tool_name == "search": # 如果是搜索工具
query = tool_args.get("query", "") # 获取查询
if len(query) < 2: # 如果太短
print(f"⚠️ [验证] 搜索查询过短: '{query}'") # 打印警告
# 验证数值范围
if tool_name == "calculate": # 如果是计算工具
value = tool_args.get("value", 0) # 获取数值
if value < 0: # 如果为负数
print(f"⚠️ [验证] 数值不能为负: {value}") # 打印警告
# ========================================
# 定义工具函数
# ========================================
def get_weather(city: str) -> dict:
"""获取天气信息"""
weather_data = { # 天气数据
"北京": {"temp": "25°C", "condition": "晴天"},
"上海": {"temp": "28°C", "condition": "多云"},
}
data = weather_data.get(city) # 查找数据
if not data: # 如果找不到
return {"status": "error", "error_message": f"未找到'{city}'的天气信息"}
return {"status": "success", "city": city, **data} # 返回天气
def delete_file(filename: str) -> dict:
"""删除文件(模拟)"""
print(f"🗑️ 执行删除: {filename}") # 模拟删除
return {"status": "success", "message": f"文件 '{filename}' 已删除"}
# ========================================
# 创建带回调的 Agent
# ========================================
monitored_agent = Agent(
name="monitored_agent", # Agent 名称
model="gemini-2.0-flash", # 模型
instruction=( # 系统指令
"你是一个天气助手。\n"
"使用 get_weather 工具查询天气。\n"
"使用 delete_file 工具删除文件(需要确认)。"
),
tools=[get_weather, delete_file], # 注册工具
before_model_callback=log_before_model, # 模型前回调
after_model_callback=log_after_model, # 模型后回调
before_tool_callback=log_before_tool, # 工具前回调
after_tool_callback=log_after_tool, # 工具后回调
)
# ========================================
# 运行演示
# ========================================
APP_NAME = "callback_demo" # 应用名称
USER_ID = "user_001" # 用户 ID
SESSION_ID = "session_001" # 会话 ID
async def main():
"""主函数"""
print("=" * 60) # 分隔线
print("Google ADK 回调机制演示") # 标题
print("=" * 60) # 分隔线
# 初始化
session_service = InMemorySessionService() # 会话服务
await session_service.create_session( # 创建会话
app_name=APP_NAME, # 应用名称
user_id=USER_ID, # 用户 ID
session_id=SESSION_ID, # 会话 ID
)
# 创建 Runner
runner = Runner(
agent=monitored_agent, # Agent
app_name=APP_NAME, # 应用名称
session_service=session_service, # 会话服务
)
# 测试问题
queries = [ # 测试列表
"北京天气怎么样?", # 天气查询
]
for query in queries: # 遍历测试
print(f"\n{'='*60}") # 分隔线
print(f"[用户]: {query}") # 打印用户输入
# 构造消息
content = types.Content(
role='user', # 角色
parts=[types.Part(text=query)], # 内容
)
# 运行 Agent
events = runner.run_async(
user_id=USER_ID, # 用户 ID
session_id=SESSION_ID, # 会话 ID
new_message=content, # 消息
)
# 处理事件
async for event in events: # 遍历事件
if event.is_final_response(): # 最终响应
print(f"[Agent]: {event.content.parts[0].text}") # 打印回复
if __name__ == "__main__": # 直接运行
asyncio.run(main()) # 执行主函数