256 lines
8.8 KiB
Python
256 lines
8.8 KiB
Python
"""
|
||
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()) # 执行主函数
|