From 514723dbe7e298be01cb7c775926b797acdb46ce Mon Sep 17 00:00:00 2001 From: Nelson Date: Mon, 6 Apr 2026 12:36:04 +0800 Subject: [PATCH] init --- ADKLearning/.DS_Store | Bin 0 -> 6148 bytes ADKLearning/google-adk-tutorial/.DS_Store | Bin 0 -> 8196 bytes .../01-introduction-and-setup.md | 356 +++++++++ .../02-quick-start-hello-world.md | 388 ++++++++++ .../03-llm-agent-in-depth.md | 434 +++++++++++ .../google-adk-tutorial/04-custom-tools.md | 564 +++++++++++++++ .../05-multi-agent-systems.md | 631 ++++++++++++++++ .../06-session-and-state.md | 595 ++++++++++++++++ .../07-callbacks-and-events.md | 565 +++++++++++++++ .../google-adk-tutorial/08-evaluation.md | 444 ++++++++++++ .../google-adk-tutorial/09-deployment.md | 420 +++++++++++ .../google-adk-tutorial/10-advanced-topics.md | 674 ++++++++++++++++++ ADKLearning/google-adk-tutorial/README.md | 90 +++ .../google-adk-tutorial/code/advanced_demo.py | 293 ++++++++ .../google-adk-tutorial/code/callback_demo.py | 255 +++++++ .../code/custom_tools_demo.py | 338 +++++++++ .../google-adk-tutorial/code/deploy_demo.py | 201 ++++++ .../google-adk-tutorial/code/eval_demo.py | 272 +++++++ .../google-adk-tutorial/code/hello_world.py | 137 ++++ .../code/llm_agent_demo.py | 210 ++++++ .../code/multi_agent_demo.py | 275 +++++++ .../code/session_state_demo.py | 318 +++++++++ .../google-adk-tutorial/code/setup_demo.py | 69 ++ 23 files changed, 7529 insertions(+) create mode 100644 ADKLearning/.DS_Store create mode 100644 ADKLearning/google-adk-tutorial/.DS_Store create mode 100644 ADKLearning/google-adk-tutorial/01-introduction-and-setup.md create mode 100644 ADKLearning/google-adk-tutorial/02-quick-start-hello-world.md create mode 100644 ADKLearning/google-adk-tutorial/03-llm-agent-in-depth.md create mode 100644 ADKLearning/google-adk-tutorial/04-custom-tools.md create mode 100644 ADKLearning/google-adk-tutorial/05-multi-agent-systems.md create mode 100644 ADKLearning/google-adk-tutorial/06-session-and-state.md create mode 100644 ADKLearning/google-adk-tutorial/07-callbacks-and-events.md create mode 100644 ADKLearning/google-adk-tutorial/08-evaluation.md create mode 100644 ADKLearning/google-adk-tutorial/09-deployment.md create mode 100644 ADKLearning/google-adk-tutorial/10-advanced-topics.md create mode 100644 ADKLearning/google-adk-tutorial/README.md create mode 100644 ADKLearning/google-adk-tutorial/code/advanced_demo.py create mode 100644 ADKLearning/google-adk-tutorial/code/callback_demo.py create mode 100644 ADKLearning/google-adk-tutorial/code/custom_tools_demo.py create mode 100644 ADKLearning/google-adk-tutorial/code/deploy_demo.py create mode 100644 ADKLearning/google-adk-tutorial/code/eval_demo.py create mode 100644 ADKLearning/google-adk-tutorial/code/hello_world.py create mode 100644 ADKLearning/google-adk-tutorial/code/llm_agent_demo.py create mode 100644 ADKLearning/google-adk-tutorial/code/multi_agent_demo.py create mode 100644 ADKLearning/google-adk-tutorial/code/session_state_demo.py create mode 100644 ADKLearning/google-adk-tutorial/code/setup_demo.py diff --git a/ADKLearning/.DS_Store b/ADKLearning/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..6734c82c37e7c78604e1af8232c6814c4db1ef38 GIT binary patch literal 6148 zcmeHKPfrs;6n_I%wji=Vt3Wi_*oz4eD-sMbgjz7#7z3dMi-2{vJCv1Wr`g?7fspj9 zCqIB6z;9sU#iJLm-i#kXub%YHpAFP%!bynP*Sz_?nR)L`-*5lS4gipR-M$H+1AvCg zG0~09AB4|yZIcc)l0YOPhYJ+O!2}z&+q~m}n1Psqv&H~_b_byf41ZNP{``(Z2z68j zJ~;SHy9Jk3ESFgr!Cx0MsG~oHpmpLbRwJmXEbpwE>jYso`)BZKJrw z8pY5I>p|Z1*LZK4G1rQ0(^_$8qpYXqm=~IkM|~0C*c4sXA3L7S8hKXt>_Eg8<%E{h zl4X79;9z2UYRs6--aZ^N4yGrvXy2MTJWOio%#Axshn70n9KaU zTc>MP-?57|v*LKwQb=EfMQ5Lu^?~19m#F>pXlQw_hCZpI0g>*$PpK#JEo^QEG%Ovn zSIc@|Q1O}TF0#N09pAo>n+#Xn#VfE-<5R&3^#jDVn9{20`=(c=`MnL- zw5Yq_~XL1iCz>KFi<(> z6gDM_R|huY2!O~xBZQ#MKMr_|p)jYgDG_H-hzv!Pp+p@qhz!SZL;2+tHYLh%AnNcz z)XGGiP>5I^^Bck)NKRth#tg&^v@*~syE@NNlW literal 0 HcmV?d00001 diff --git a/ADKLearning/google-adk-tutorial/.DS_Store b/ADKLearning/google-adk-tutorial/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..520586922972055a8d2afe1ea53e55a5935ef8f0 GIT binary patch literal 8196 zcmeHML2nyH6n^6dVmFj*(ztb^RtZ6aVI@KQ!VJ|Bv>q^u;er=@yypi)Nw$T2jl0v+c7g()VolE|Kx3JOkr zdSD5ZB}WXV;pi_)bI3{}ds=EZF%2h{jLeb~3R9y4S5)T2Dq5P_M8HH~Gy?MMevQg> zm*&YkbAESlmiKU$571+yb|E9;Xzfs&Zc~fgHD5RlUo^lWzjjqzxBpfJ8qsy|E&%lz z5FR;npcrDXgilv#OyZd8p~~>L0~xl! z`4;dJCs{knAMIy~PbBj=j9&&`XG^YxBSc4MG6k>lHi*w--IkUAuJRzjjJR5QZOIWcCJ`#(CW4m^#i{Zh%Ycl6qan6bV=Wz)?Y$9 zoww&4Ci7vx^yl-tW%Xy2yEyUMrPn8|sp-p8GgJ9|{>s(D?A+Y7x^=l>I|t2BdiqfK zBCPry{-_yxZmn%QUeMf$`R8%Xd&cY5^pNQ$cRzXFv~|>m&CK%#MfYCFd7${#_x2(l z?+oa>b!#$mLgD)xBJyHS1iOzT#Ca{|t+ilJV8lbjc)cyVqHP{VkRzC#T{{fzpvkL8 zPkg(<{q>+K;#jn#rDStNun}^}wBtdSb16OeQ-`rs`Jk`7p%sftTEWR`rmrQEm40$H za4Qf2&*e&$jif6}H|bsakhbxad`4f}2=l|97-~YeRb2QGI2$%@`dj#0nN^M2{c<=8a!{+jbX%W{`T(U^Jo|X!NN}9-a jbsUF0{lgG?G3l!$vZp0_Q2z550cQU<`@dRwFL(71Ep*ok literal 0 HcmV?d00001 diff --git a/ADKLearning/google-adk-tutorial/01-introduction-and-setup.md b/ADKLearning/google-adk-tutorial/01-introduction-and-setup.md new file mode 100644 index 0000000..93e75f7 --- /dev/null +++ b/ADKLearning/google-adk-tutorial/01-introduction-and-setup.md @@ -0,0 +1,356 @@ +# 第01章:ADK 简介与环境搭建 + +## 📌 本章目标 + +- 理解 Google ADK 是什么、能做什么 +- 了解 ADK 的核心架构和设计理念 +- 完成开发环境的安装和配置 +- 验证安装是否成功 + +--- + +## 1.1 什么是 Google ADK? + +**Agent Development Kit(ADK)** 是 Google 开源的一个灵活、模块化的 AI 智能体(Agent)开发框架。它让开发者能够像开发软件一样来构建、评估和部署 AI 智能体。 + +### 核心定位 + +``` +┌─────────────────────────────────────────────────────┐ +│ Google ADK │ +│ │ +│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ +│ │ 模型无关 │ │ 部署无关 │ │ 框架兼容 │ │ +│ │Model │ │Deploy │ │ Framework │ │ +│ │Agnostic │ │Agnostic │ │ Compatible │ │ +│ └──────────┘ └──────────┘ └──────────────────┘ │ +│ │ +│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ +│ │ 代码优先 │ │ 模块化 │ │ 多智能体支持 │ │ +│ │Code First│ │Modular │ │ Multi-Agent │ │ +│ └──────────┘ └──────────┘ └──────────────────┘ │ +└─────────────────────────────────────────────────────┘ +``` + +### ADK vs 其他框架对比 + +| 特性 | Google ADK | LangGraph | OpenAI Agents SDK | +|------|-----------|-----------|-------------------| +| **开发语言** | Python/Go/Java | Python | Python/TypeScript | +| **模型支持** | 模型无关(优化 Gemini) | 模型无关 | 优化 OpenAI | +| **多智能体** | ✅ 原生支持层级架构 | ✅ 图结构编排 | ✅ Handoff 机制 | +| **工具生态** | 丰富(Google Search、MCP 等) | 丰富(LangChain 生态) | 丰富(OpenAI 生态) | +| **部署** | Cloud Run / Vertex AI | 多种选择 | OpenAI 平台 | +| **评估** | ✅ 内置评估系统 | ❌ 需第三方 | ❌ 需第三方 | + +--- + +## 1.2 核心架构 + +### 1.2.1 Agent 类型体系 + +ADK 的所有智能体都继承自 `BaseAgent`,主要分为三大类: + +``` + BaseAgent(基础智能体) + │ + ┌─────────────┼─────────────┐ + │ │ │ + LlmAgent WorkflowAgent CustomAgent + (LLM 智能体) (工作流智能体) (自定义智能体) + │ │ + │ ┌──────┼──────┐ + │ │ │ │ + │ Sequential Parallel Loop + │ (顺序) (并行) (循环) + │ + Agent(别名) +``` + +| 类型 | 说明 | 适用场景 | +|------|------|----------| +| **LlmAgent** | 使用 LLM 作为核心引擎 | 自然语言理解、推理、工具调用 | +| **SequentialAgent** | 顺序执行子智能体 | 数据处理流水线 | +| **ParallelAgent** | 并行执行子智能体 | 多任务同时处理 | +| **LoopAgent** | 循环执行子智能体 | 轮询、迭代优化 | +| **CustomAgent** | 继承 BaseAgent 自定义 | 特殊逻辑、非标准流程 | + +### 1.2.2 运行时架构 + +``` +用户输入 + │ + ▼ +┌─────────┐ ┌──────────────┐ ┌──────────────┐ +│ Runner │───▶│ Session │───▶│ Agent │ +│ (运行器)│ │ Service │ │ (智能体) │ +└─────────┘ │ (会话服务) │ └──────┬───────┘ + └──────────────┘ │ + ┌──────────────┐ ▼ + │ Memory │ ┌──────────────┐ + │ Service │ │ Tools │ + │ (记忆服务) │ │ (工具集) │ + └──────────────┘ └──────────────┘ +``` + +--- + +## 1.3 核心特性详解 + +### 1.3.1 丰富的工具生态 + +ADK 提供了三大类工具: + +**Gemini 原生工具:** +- **Google Search** — 网络搜索 +- **Code Execution** — 代码执行与调试 +- **Computer Use** — 操作计算机界面 + +**Google Cloud 工具:** +- BigQuery、Spanner、Bigtable — 数据库 +- Vertex AI RAG Engine — 私有数据检索 +- Vertex AI Search — 企业搜索 +- API Registry — MCP 工具集成 + +**第三方工具:** +- GitHub、GitLab — 代码管理 +- Notion、Linear — 项目管理 +- Stripe、PayPal — 支付 +- ElevenLabs、Cartesia — 语音生成 +- Qdrant — 向量搜索 + +### 1.3.2 多智能体协作模式 + +ADK 支持多种多智能体协作模式: + +| 模式 | 说明 | 实现方式 | +|------|------|----------| +| **协调器/调度器** | 父 Agent 分配任务给子 Agent | LlmAgent + sub_agents | +| **顺序流水线** | Agent 按顺序执行 | SequentialAgent | +| **并行扇出** | Agent 同时执行 | ParallelAgent | +| **层级任务分解** | 多层 Agent 分解任务 | 嵌套 sub_agents | +| **人机协作** | 人工审批关键步骤 | Human-in-the-Loop | +| **评审/批评** | 生成器 + 评审器协作 | LoopAgent + 条件退出 | + +### 1.3.3 上下文管理 + +ADK 在架构层面分离了三种上下文: + +``` +┌────────────────────────────────────────────┐ +│ 上下文管理架构 │ +│ │ +│ ┌──────────────────────────────────┐ │ +│ │ Session(会话) │ │ +│ │ - 当前对话线程 │ │ +│ │ - 消息历史 │ │ +│ │ - 临时状态 │ │ +│ └──────────────────────────────────┘ │ +│ │ +│ ┌──────────────────────────────────┐ │ +│ │ State(状态) │ │ +│ │ - 会话内数据 │ │ +│ │ - 购物车、用户偏好等 │ │ +│ │ - temp: 临时状态 │ │ +│ └──────────────────────────────────┘ │ +│ │ +│ ┌──────────────────────────────────┐ │ +│ │ Memory(记忆) │ │ +│ │ - 跨会话持久化知识 │ │ +│ │ - 可搜索的知识库 │ │ +│ │ - 外部数据源 │ │ +│ └──────────────────────────────────┘ │ +└────────────────────────────────────────────┘ +``` + +--- + +## 1.4 环境搭建 + +### 1.4.1 系统要求 + +| 要求 | 最低版本 | 推荐版本 | +|------|----------|----------| +| Python | 3.10 | 3.11+ | +| pip | 最新版 | 最新版 | +| 操作系统 | Windows/macOS/Linux | 任意 | + +### 1.4.2 安装步骤 + +#### 步骤一:创建虚拟环境(推荐) + +```bash +# 创建虚拟环境 +python -m venv adk_env + +# 激活虚拟环境 +# macOS / Linux: +source adk_env/bin/activate + +# Windows CMD: +adk_env\Scripts\activate.bat + +# Windows PowerShell: +adk_env\Scripts\Activate.ps1 +``` + +#### 步骤二:安装 ADK + +```bash +# 安装稳定版本(推荐) +pip install google-adk + +# 如果需要使用非 Gemini 模型(如 DeepSeek、OpenAI 等),还需安装 litellm +pip install google-adk litellm + +# 如果需要安装开发版本 +pip install git+https://github.com/google/adk-python.git@main +``` + +#### 步骤三:验证安装 + +```bash +# 检查 ADK 版本 +adk --help + +# 应该看到以下输出: +# Usage: adk [OPTIONS] COMMAND [ARGS]... +# +# Agent Development Kit CLI tools. +# +# Options: +# --version Show the version and exit. +# --help Show this message and exit. +# +# Commands: +# api_server Starts a FastAPI server for agents. +# create Creates a new app in the current folder with prepopulated agent template. +# deploy Deploys agent to hosted environments. +# eval Evaluates an agent given the eval sets. +# run Runs an interactive CLI for a certain agent. +# web Starts a FastAPI server with Web UI for agents. +``` + +### 1.4.3 配置 API Key + +#### 使用 Gemini 模型(默认) + +```bash +# 在项目目录下创建 .env 文件 +echo 'GOOGLE_API_KEY="你的_GEMINI_API_KEY"' > .env +``` + +> 💡 获取 Gemini API Key:访问 [Google AI Studio](https://aistudio.google.com/app/apikey) 创建 API Key。 + +#### 使用其他模型 + +```bash +# 在 .env 文件中添加对应的 API Key +echo 'OPENAI_API_KEY="你的_OPENAI_API_KEY"' >> .env +echo 'DEEPSEEK_API_KEY="你的_DEEPSEEK_API_KEY"' >> .env +``` + +--- + +## 1.5 ADK CLI 工具详解 + +安装 ADK 后,会获得一个 `adk` 命令行工具: + +| 命令 | 说明 | 示例 | +|------|------|------| +| `adk create` | 创建新的 Agent 项目 | `adk create my_agent` | +| `adk run` | 在命令行中运行 Agent | `adk run my_agent` | +| `adk web` | 启动 Web UI 界面 | `adk web --port 8000` | +| `adk api_server` | 启动 FastAPI 服务器 | `adk api_server` | +| `adk eval` | 评估 Agent 性能 | `adk eval my_agent eval_set.json` | +| `adk deploy` | 部署到托管环境 | `adk deploy my_agent` | + +--- + +## 1.6 代码验证:环境搭建 + +以下代码用于验证 ADK 是否安装成功,详见 `code/setup_demo.py`: + +```python +""" +Google ADK 环境搭建验证脚本 +验证 ADK 是否正确安装,并打印版本信息 +""" + +# 导入 ADK 核心模块,验证安装是否成功 +from google.adk.agents import Agent # 智能体模块 +from google.adk.runners import Runner # 运行器模块 +from google.adk.sessions import InMemorySessionService # 内存会话服务 + +# 打印安装验证信息 +print("=" * 50) # 打印分隔线 +print("Google ADK 环境验证") # 打印标题 +print("=" * 50) # 打印分隔线 +print("✅ ADK 核心模块导入成功!") # 确认导入成功 + +# 验证 Agent 类是否可用 +agent = Agent( # 创建一个简单的测试 Agent + name="test_agent", # 设置 Agent 名称 + model="gemini-2.0-flash", # 使用 Gemini 模型 + instruction="你是一个测试助手。", # 设置系统指令 +) +print(f"✅ Agent 创建成功:{agent.name}") # 打印 Agent 名称 + +# 验证 Runner 类是否可用 +print("✅ Runner 模块可用") # 确认 Runner 可用 + +# 验证 SessionService 是否可用 +session_service = InMemorySessionService() # 创建内存会话服务 +print("✅ SessionService 可用") # 确认会话服务可用 + +print("=" * 50) # 打印分隔线 +print("🎉 所有模块验证通过!") # 打印成功信息 +print("可以开始使用 Google ADK 了!") # 提示用户 +``` + +--- + +## 1.7 常见问题 + +### Q1: 安装失败怎么办? + +```bash +# 尝试升级 pip +pip install --upgrade pip + +# 使用国内镜像源 +pip install google-adk -i https://pypi.tuna.tsinghua.edu.cn/simple +``` + +### Q2: Python 版本不满足要求? + +```bash +# 检查 Python 版本 +python --version + +# 如果低于 3.10,需要升级 Python +# 推荐使用 pyenv 或 conda 管理 Python 版本 +``` + +### Q3: adk 命令找不到? + +```bash +# 确保虚拟环境已激活 +# 确认 google-adk 已安装 +pip show google-adk + +# 如果安装了但找不到命令,尝试重新安装 +pip install --force-reinstall google-adk +``` + +--- + +## 📌 本章小结 + +- ADK 是 Google 开源的模块化 AI 智能体开发框架 +- 支持多种 Agent 类型:LlmAgent、WorkflowAgent、CustomAgent +- 提供丰富的工具生态和完善的上下文管理 +- 安装简单:`pip install google-adk` +- 提供 CLI 工具 `adk` 用于项目创建、运行和部署 + +**下一章**:[第02章 - 快速开始:Hello World](./02-quick-start-hello-world.md) → 创建并运行你的第一个 Agent! diff --git a/ADKLearning/google-adk-tutorial/02-quick-start-hello-world.md b/ADKLearning/google-adk-tutorial/02-quick-start-hello-world.md new file mode 100644 index 0000000..5003476 --- /dev/null +++ b/ADKLearning/google-adk-tutorial/02-quick-start-hello-world.md @@ -0,0 +1,388 @@ +# 第02章:快速开始 — Hello World + +## 📌 本章目标 + +- 使用 `adk create` 创建第一个 Agent 项目 +- 理解 Agent 项目结构 +- 编写一个带有自定义工具的 Agent +- 通过 CLI 和 Web UI 两种方式运行 Agent + +--- + +## 2.1 创建 Agent 项目 + +### 2.1.1 使用 CLI 创建项目 + +```bash +# 在工作目录下创建一个新的 Agent 项目 +adk create hello_world + +# 创建完成后,会生成以下目录结构: +# hello_world/ +# ├── agent.py # Agent 主代码文件(核心) +# ├── .env # 环境变量配置文件(API Key 等) +# └── __init__.py # Python 包初始化文件 +``` + +### 2.1.2 项目结构说明 + +``` +hello_world/ +│ +├── agent.py # 🔑 核心文件:定义 Agent 的行为和工具 +│ # - root_agent 变量是 ADK 的入口点 +│ # - 可以定义自定义工具函数 +│ +├── .env # 🔑 配置文件:存储 API Key 等敏感信息 +│ # - GOOGLE_API_KEY="your_key_here" +│ # - 不会被提交到版本控制 +│ +└── __init__.py # Python 包标识文件 + # - 使目录成为 Python 包 + # - 通常为空文件 +``` + +> ⚠️ **重要**:`agent.py` 中的 `root_agent` 是 ADK 识别 Agent 的入口点,这个变量名**不能修改**。 + +--- + +## 2.2 编写第一个 Agent + +### 2.2.1 最简单的 Agent + +打开 `agent.py`,编写最基础的 Agent: + +```python +""" +第一个 Google ADK Agent 示例 +这是一个最简单的 Agent,不包含任何自定义工具 +""" + +# 从 ADK 的 agents 模块导入 Agent 类 +# Agent 是 LlmAgent 的别名,是最常用的智能体类型 +from google.adk.agents import Agent + +# 定义 root_agent —— 这是 ADK 的入口点 +# ADK 会自动查找名为 root_agent 的变量 +root_agent = Agent( + name="hello_agent", # Agent 的名称,用于标识和日志 + model="gemini-2.0-flash", # 使用的 LLM 模型 + instruction="你是一个友好的助手。", # 系统指令,定义 Agent 的角色和行为 +) +``` + +### 2.2.2 带工具的 Agent + +让我们给 Agent 添加一个获取当前时间的工具: + +```python +""" +带自定义工具的 Hello World Agent +演示如何为 Agent 添加自定义函数工具 +""" + +# 导入 ADK 的 Agent 类 +from google.adk.agents import Agent + +# 导入标准库:日期时间处理 +import datetime # 用于获取当前时间 +from zoneinfo import ZoneInfo # 用于处理时区信息 + + +# ======================================== +# 定义自定义工具函数 +# ======================================== + +def get_current_time(city: str) -> dict: + """ + 获取指定城市的当前时间 + + 这个函数会被 ADK 自动转换为工具(FunctionTool), + Agent 可以在对话中调用这个工具来获取时间信息。 + + Args: + city (str): 需要查询时间的城市名称,例如"北京"、"东京"、"纽约" + + Returns: + dict: 包含状态和结果的字典 + - status: "success" 或 "error" + - report: 时间信息或错误消息 + """ + # 定义城市到时区的映射字典 + city_timezone_map = { # 常见城市对应的时区 + "北京": "Asia/Shanghai", # 北京使用上海时区 + "上海": "Asia/Shanghai", # 上海使用上海时区 + "东京": "Asia/Tokyo", # 东京时区 + "纽约": "America/New_York", # 纽约时区 + "伦敦": "Europe/London", # 伦敦时区 + "巴黎": "Europe/Paris", # 巴黎时区 + } + + # 查找城市对应的时区标识符 + tz_identifier = city_timezone_map.get(city) # 从字典中获取时区 + + # 如果找不到该城市的时区信息 + if not tz_identifier: + return { # 返回错误信息 + "status": "error", # 状态标记为错误 + "error_message": f"未找到'{city}'的时区信息,请尝试其他城市。" # 错误描述 + } + + # 根据时区标识符创建时区对象 + tz = ZoneInfo(tz_identifier) # 创建 ZoneInfo 时区对象 + + # 获取该时区的当前时间 + now = datetime.datetime.now(tz) # 获取指定时区的当前时间 + + # 格式化时间字符串 + time_str = now.strftime( # 将时间格式化为字符串 + "%Y-%m-%d %H:%M:%S %Z%z" # 格式:年-月-日 时:分:秒 时区 + ) + + # 返回成功结果 + return { # 返回包含时间信息的字典 + "status": "success", # 状态标记为成功 + "city": city, # 城市名称 + "time": time_str # 格式化后的时间字符串 + } + + +# ======================================== +# 定义 Agent +# ======================================== + +root_agent = Agent( + name="hello_agent", # Agent 的唯一标识名称 + model="gemini-2.0-flash", # 使用 Gemini 2.0 Flash 模型 + description="一个能查询城市时间的友好助手。", # Agent 的描述,用于多 Agent 场景 + instruction=( # 系统指令(多行字符串) + "你是一个友好的助手,可以帮助用户查询世界各城市的当前时间。\n" + "当用户询问某个城市的时间时,使用 get_current_time 工具来获取。\n" + "如果工具返回错误,请友好地告知用户并建议尝试其他城市。\n" + "回复时请使用中文。" + ), + tools=[get_current_time], # 将自定义函数注册为 Agent 的工具 +) +``` + +--- + +## 2.3 配置 API Key + +在运行 Agent 之前,需要配置 API Key: + +```bash +# 方法一:在 .env 文件中配置(推荐) +echo 'GOOGLE_API_KEY="你的_GEMINI_API_KEY"' > hello_world/.env + +# 方法二:通过环境变量设置 +export GOOGLE_API_KEY="你的_GEMINI_API_KEY" + +# 方法三:直接在代码中设置(不推荐,有安全风险) +``` + +> 💡 **获取 API Key**:访问 [Google AI Studio](https://aistudio.google.com/app/apikey),点击 "Create API Key" 即可免费获取。 + +--- + +## 2.4 运行 Agent + +### 2.4.1 命令行交互模式(CLI) + +```bash +# 在项目父目录下运行(不是在 hello_world 目录内) +adk run hello_world + +# 运行后进入交互式对话界面: +# Running agent hello_agent, type exit to exit. +# [user]: 北京现在几点? +# [hello_agent]: 北京当前时间是 2025-04-05 14:30:00 CST+0800。 +# [user]: exit +``` + +### 2.4.2 Web UI 模式 + +```bash +# 启动 Web 服务器(默认端口 8000) +adk web --port 8000 + +# 浏览器打开 http://localhost:8000 +# 在左上角选择 Agent,然后输入问题进行对话 +``` + +Web UI 提供以下功能: +- 💬 **对话界面**:与 Agent 进行交互式对话 +- 📊 **Event 面板**:查看工具调用和事件流 +- 📋 **Request/Response**:查看原始请求和响应数据 +- 🔍 **调试信息**:帮助排查问题 + +### 2.4.3 代码方式运行 + +除了 CLI 和 Web UI,还可以通过 Python 代码直接运行 Agent: + +```python +""" +通过 Python 代码直接运行 Agent +适用于集成到自己的应用中 +""" + +# 导入必要的模块 +from google.adk.agents import Agent # Agent 类 +from google.adk.runners import Runner # 运行器,负责执行 Agent +from google.adk.sessions import InMemorySessionService # 内存会话服务 +from google.genai import types # Google GenAI 类型定义 + +# 导入异步运行支持 +import asyncio # 异步编程库 + + +# ======================================== +# 定义工具函数 +# ======================================== + +def get_current_time(city: str) -> dict: + """获取指定城市的当前时间""" + import datetime # 导入日期时间模块 + from zoneinfo import ZoneInfo # 导入时区模块 + + city_timezone_map = { # 城市时区映射 + "北京": "Asia/Shanghai", + "上海": "Asia/Shanghai", + "东京": "Asia/Tokyo", + "纽约": "America/New_York", + "伦敦": "Europe/London", + } + + tz_identifier = city_timezone_map.get(city) # 查找时区 + if not tz_identifier: # 如果找不到时区 + return { # 返回错误 + "status": "error", + "error_message": f"未找到'{city}'的时区信息" + } + + tz = ZoneInfo(tz_identifier) # 创建时区对象 + now = datetime.datetime.now(tz) # 获取当前时间 + time_str = now.strftime("%Y-%m-%d %H:%M:%S %Z%z") # 格式化时间 + + return { # 返回结果 + "status": "success", + "city": city, + "time": time_str + } + + +# ======================================== +# 定义 Agent +# ======================================== + +root_agent = Agent( + name="hello_agent", # Agent 名称 + model="gemini-2.0-flash", # 使用 Gemini 模型 + instruction="你是一个友好的时间查询助手。使用工具获取城市时间。", # 系统指令 + tools=[get_current_time], # 注册工具 +) + + +# ======================================== +# 定义运行逻辑 +# ======================================== + +# 定义应用名称、用户ID和会话ID +APP_NAME = "hello_world_app" # 应用名称,用于标识 +USER_ID = "user_001" # 用户唯一标识 +SESSION_ID = "session_001" # 会话唯一标识 + + +async def main(): + """主函数:异步运行 Agent""" + + # 第一步:创建会话服务 + # InMemorySessionService 将会话数据存储在内存中 + # 适用于开发和测试,数据在程序重启后会丢失 + session_service = InMemorySessionService() + + # 第二步:创建会话 + # 会话(Session)代表一次对话交互 + # 包含消息历史、状态数据等 + session = await session_service.create_session( + app_name=APP_NAME, # 关联的应用名称 + user_id=USER_ID, # 用户标识 + session_id=SESSION_ID, # 会话标识 + ) + + # 第三步:创建运行器 + # Runner 是 ADK 的核心执行引擎 + # 负责协调 Agent、会话服务和工具调用 + runner = Runner( + agent=root_agent, # 要运行的 Agent + app_name=APP_NAME, # 应用名称 + session_service=session_service, # 会话服务 + ) + + # 第四步:构造用户消息 + # Content 对象表示一条消息 + # role='user' 表示这是用户发送的消息 + content = types.Content( + role='user', # 消息角色:用户 + parts=[types.Part(text='北京现在几点?')] # 消息内容 + ) + + # 第五步:运行 Agent 并获取响应 + # run_async 返回一个异步事件流 + # 我们需要遍历所有事件来获取最终响应 + events = runner.run_async( + user_id=USER_ID, # 用户标识 + session_id=SESSION_ID, # 会话标识 + new_message=content, # 用户消息 + ) + + # 第六步:处理事件流 + async for event in events: # 遍历所有事件 + if event.is_final_response(): # 如果是最终响应事件 + # 从事件中提取文本内容 + final_response = event.content.parts[0].text + print(f"Agent 回复: {final_response}") # 打印 Agent 的回复 + + +# 运行主函数 +if __name__ == "__main__": # 当脚本被直接运行时 + asyncio.run(main()) # 使用 asyncio 运行异步主函数 +``` + +--- + +## 2.5 理解 Agent 的核心参数 + +`Agent` 构造函数接受以下关键参数: + +| 参数 | 类型 | 必需 | 说明 | +|------|------|------|------| +| `name` | str | ✅ | Agent 的唯一标识名称 | +| `model` | str | ✅ | 使用的 LLM 模型名称 | +| `instruction` | str | ❌ | 系统指令,定义 Agent 的角色和行为 | +| `description` | str | ❌ | Agent 描述,用于多 Agent 场景中的路由 | +| `tools` | list | ❌ | Agent 可用的工具列表 | +| `sub_agents` | list | ❌ | 子 Agent 列表,用于多 Agent 系统 | +| `output_key` | str | ❌ | 输出保存到 State 的键名 | +| `before_model_callback` | func | ❌ | 模型调用前的回调函数 | +| `after_model_callback` | func | ❌ | 模型调用后的回调函数 | +| `before_tool_callback` | func | ❌ | 工具调用前的回调函数 | +| `after_tool_callback` | func | ❌ | 工具调用后的回调函数 | + +--- + +## 2.6 完整代码示例 + +详见 `code/hello_world.py` — 包含完整的带工具的 Hello World Agent 示例。 + +--- + +## 📌 本章小结 + +- 使用 `adk create` 快速创建 Agent 项目 +- `agent.py` 中的 `root_agent` 是 ADK 的入口点 +- 自定义工具就是普通的 Python 函数,ADK 会自动转换为 FunctionTool +- 三种运行方式:CLI(`adk run`)、Web UI(`adk web`)、代码调用 +- Runner 是 ADK 的核心执行引擎,负责协调 Agent 和会话 + +**下一章**:[第03章 - LLM 智能体详解](./03-llm-agent-in-depth.md) → 深入了解 LlmAgent 的配置和高级用法。 diff --git a/ADKLearning/google-adk-tutorial/03-llm-agent-in-depth.md b/ADKLearning/google-adk-tutorial/03-llm-agent-in-depth.md new file mode 100644 index 0000000..bc64c6e --- /dev/null +++ b/ADKLearning/google-adk-tutorial/03-llm-agent-in-depth.md @@ -0,0 +1,434 @@ +# 第03章:LLM 智能体详解 + +## 📌 本章目标 + +- 深入理解 LlmAgent 的工作原理 +- 掌握 Agent 的指令设计(Instruction Design) +- 学习如何选择和配置不同的 LLM 模型 +- 了解 output_key、before/after 回调等高级特性 +- 使用 Google 内置工具(搜索、代码执行等) + +--- + +## 3.1 LlmAgent 工作原理 + +### 3.1.1 执行流程 + +``` +用户输入 + │ + ▼ +┌─────────────────────────────────────────────────┐ +│ LlmAgent │ +│ │ +│ 1. 接收用户消息 │ +│ ↓ │ +│ 2. before_model_callback(模型调用前回调) │ +│ ↓ │ +│ 3. 构建上下文(系统指令 + 历史消息 + 工具定义) │ +│ ↓ │ +│ 4. 调用 LLM 获取响应 │ +│ ↓ │ +│ 5. after_model_callback(模型调用后回调) │ +│ ↓ │ +│ 6. 判断是否需要调用工具 │ +│ ├── 是 → before_tool_callback → 执行工具 │ +│ │ → after_tool_callback → 回到步骤3 │ +│ └── 否 → 输出最终响应 │ +│ ↓ │ +│ 7. 保存输出到 State(如果设置了 output_key) │ +│ ↓ │ +│ 8. 返回响应给用户 │ +└─────────────────────────────────────────────────┘ +``` + +### 3.1.2 Agent 与 LlmAgent 的关系 + +```python +# Agent 是 LlmAgent 的别名,两者完全等价 +from google.adk.agents import Agent # Agent = LlmAgent 的别名 +from google.adk.agents import LlmAgent # LlmAgent 是完整类名 + +# 以下两种写法效果完全相同: +agent1 = Agent(name="test", model="gemini-2.0-flash") +agent2 = LlmAgent(name="test", model="gemini-2.0-flash") +``` + +--- + +## 3.2 指令设计(Instruction Design) + +### 3.2.1 好的指令设计原则 + +```python +""" +Agent 指令设计示例 +展示如何编写高质量的 Agent 系统指令 +""" + +from google.adk.agents import Agent + +# ❌ 不好的指令设计:过于简单、缺乏约束 +bad_agent = Agent( + name="bad_agent", + model="gemini-2.0-flash", + instruction="你是一个助手。", # 太模糊,Agent 不知道该做什么 +) + +# ✅ 好的指令设计:清晰、具体、有约束 +good_agent = Agent( + name="good_agent", + model="gemini-2.0-flash", + instruction="""你是一个专业的旅行规划助手。 + +## 你的职责 +- 帮助用户规划旅行行程 +- 提供目的地推荐和旅行建议 +- 使用工具查询天气、航班等信息 + +## 回答规范 +1. 始终使用中文回答 +2. 回答要简洁明了,使用列表格式 +3. 如果信息不足,主动询问用户 +4. 不要编造不存在的信息 + +## 工具使用规则 +- 查询天气时,使用 get_weather 工具 +- 查询航班时,使用 search_flights 工具 +- 只有在需要实时数据时才使用工具 +""", +) +``` + +### 3.2.2 指令模板 + +```python +""" +常用指令模板 +可以根据自己的需求修改使用 +""" + +# 模板一:数据分析助手 +data_analyst_instruction = """你是一个数据分析专家。 + +## 核心能力 +- 分析数据并生成洞察 +- 创建数据可视化建议 +- 解释统计指标的含义 + +## 工作流程 +1. 理解用户的数据分析需求 +2. 使用工具获取和处理数据 +3. 生成分析报告,包含关键发现 +4. 提供可操作的建议 + +## 输出格式 +- 使用 Markdown 格式 +- 包含数据表格和关键指标 +- 提供结论和建议 +""" + +# 模板二:代码助手 +code_assistant_instruction = """你是一个编程助手。 + +## 核心能力 +- 编写和审查代码 +- 调试和修复错误 +- 代码重构和优化建议 + +## 规则 +1. 代码注释使用中文 +2. 遵循最佳实践和设计模式 +3. 解释代码的工作原理 +4. 提供测试建议 +""" + +# 模板三:客服助手 +customer_service_instruction = """你是一个专业的客服代表。 + +## 核心职责 +- 解答用户问题 +- 处理投诉和反馈 +- 引导用户完成操作 + +## 规则 +1. 语气友好、专业 +2. 先理解用户问题,再提供解决方案 +3. 无法解决时,转交人工客服 +4. 记录用户反馈 +""" +``` + +--- + +## 3.3 模型选择与配置 + +### 3.3.1 Gemini 模型(默认) + +```python +""" +使用 Gemini 模型配置 Agent +Gemini 是 ADK 默认优化的模型 +""" + +from google.adk.agents import Agent + +# 使用 Gemini 2.0 Flash(推荐,性价比高) +agent_flash = Agent( + name="flash_agent", + model="gemini-2.0-flash", # 快速、经济 + instruction="你是一个高效的助手。", +) + +# 使用 Gemini 2.5 Pro(更强推理能力) +agent_pro = Agent( + name="pro_agent", + model="gemini-2.5-pro-preview-05-06", # 更强的推理能力 + instruction="你是一个需要深度推理的助手。", +) + +# 使用 Gemini 3 Pro(最新版本) +agent_latest = Agent( + name="latest_agent", + model="gemini-3-pro-preview", # 最新版本 + instruction="你是一个使用最新模型的助手。", +) +``` + +### 3.3.2 使用其他模型(LiteLLM) + +```python +""" +使用 LiteLLM 适配其他模型 +LiteLLM 支持 100+ 种模型 +""" + +from google.adk.agents import Agent # 导入 Agent 类 +from google.adk.models.lite_llm import LiteLlm # 导入 LiteLLM 适配器 + +# ======================================== +# 使用 DeepSeek 模型 +# ======================================== +deepseek_agent = Agent( + name="deepseek_agent", # Agent 名称 + model=LiteLlm( # 使用 LiteLLM 适配器 + model="deepseek/deepseek-chat", # 格式:provider/model_name + api_key="你的_DEEPSEEK_API_KEY", # DeepSeek API Key + # api_base="https://api.deepseek.com", # 可选:自定义 API 地址 + ), + instruction="你是一个使用 DeepSeek 模型的助手。", +) + +# ======================================== +# 使用 OpenAI 模型 +# ======================================== +openai_agent = Agent( + name="openai_agent", # Agent 名称 + model=LiteLlm( # 使用 LiteLLM 适配器 + model="openai/gpt-4o", # OpenAI GPT-4o 模型 + api_key="你的_OPENAI_API_KEY", # OpenAI API Key + ), + instruction="你是一个使用 GPT-4o 模型的助手。", +) + +# ======================================== +# 使用 Claude 模型 +# ======================================== +claude_agent = Agent( + name="claude_agent", # Agent 名称 + model=LiteLlm( # 使用 LiteLLM 适配器 + model="anthropic/claude-3-5-sonnet-20241022", # Claude 模型 + api_key="你的_ANTHROPIC_API_KEY", # Anthropic API Key + ), + instruction="你是一个使用 Claude 模型的助手。", +) +``` + +### 3.3.3 模型选择指南 + +| 场景 | 推荐模型 | 原因 | +|------|----------|------| +| 简单对话 | gemini-2.0-flash | 速度快、成本低 | +| 复杂推理 | gemini-2.5-pro | 推理能力强 | +| 代码生成 | gemini-2.5-pro / gpt-4o | 代码质量高 | +| 中文场景 | deepseek-chat | 中文能力强 | +| 长文本处理 | gemini-2.5-pro | 支持长上下文 | + +--- + +## 3.4 使用 Google 内置工具 + +### 3.4.1 Google Search(网络搜索) + +```python +""" +使用 Google Search 工具 +让 Agent 能够搜索互联网获取最新信息 +""" + +from google.adk.agents import Agent # 导入 Agent 类 +from google.adk.tools import google_search # 导入 Google 搜索工具 + +# 创建带有搜索能力的 Agent +search_agent = Agent( + name="search_agent", # Agent 名称 + model="gemini-2.0-flash", # 使用 Gemini 模型 + instruction=( # 系统指令 + "你是一个信息查询助手。\n" + "当用户提问时,使用 Google Search 工具搜索最新信息。\n" + "根据搜索结果提供准确、全面的回答。\n" + "如果搜索结果不够充分,可以多次搜索不同关键词。" + ), + tools=[google_search], # 注册 Google 搜索工具 +) +``` + +### 3.4.2 Code Execution(代码执行) + +```python +""" +使用 Code Execution 工具 +让 Agent 能够执行 Python 代码进行计算和分析 +""" + +from google.adk.agents import Agent # 导入 Agent 类 +from google.adk.tools import code_execution # 导入代码执行工具 + +# 创建带有代码执行能力的 Agent +code_agent = Agent( + name="code_agent", # Agent 名称 + model="gemini-2.0-flash", # 使用 Gemini 模型 + instruction=( # 系统指令 + "你是一个数据分析助手。\n" + "当需要进行数学计算、数据分析或生成图表时,\n" + "使用 Code Execution 工具执行 Python 代码。\n" + "确保代码正确且高效。" + ), + tools=[code_execution], # 注册代码执行工具 +) +``` + +### 3.4.3 组合多个内置工具 + +```python +""" +组合使用多个 Google 内置工具 +创建功能更强大的 Agent +""" + +from google.adk.agents import Agent # 导入 Agent 类 +from google.adk.tools import google_search # 导入 Google 搜索工具 +from google.adk.tools import code_execution # 导入代码执行工具 + +# 创建多功能 Agent +super_agent = Agent( + name="super_agent", # Agent 名称 + model="gemini-2.0-flash", # 使用 Gemini 模型 + instruction=( # 系统指令 + "你是一个全能助手。\n" + "你可以搜索互联网获取最新信息。\n" + "你也可以执行代码来进行计算和分析。\n" + "根据用户的需求选择合适的工具。" + ), + tools=[ # 注册多个工具 + google_search, # 网络搜索 + code_execution, # 代码执行 + ], +) +``` + +--- + +## 3.5 output_key — 输出到 State + +```python +""" +output_key 参数详解 +将 Agent 的输出保存到会话状态(State)中 +用于在多 Agent 系统中传递数据 +""" + +from google.adk.agents import Agent # 导入 Agent 类 + +# 创建一个将输出保存到 State 的 Agent +capital_agent = Agent( + name="capital_agent", # Agent 名称 + model="gemini-2.0-flash", # 使用 Gemini 模型 + instruction="回答用户关于国家首都的问题。只回答首都名称,不要多余内容。", # 指令 + output_key="capital_city", # 🔑 关键参数:将输出保存到 state['capital_city'] +) + +# 当这个 Agent 运行后,它的最终响应文本会自动保存到 session.state['capital_city'] +# 其他 Agent 可以通过 {capital_city} 在指令中引用这个值 +``` + +--- + +## 3.6 Agent Transfer — 动态任务委派 + +```python +""" +Agent Transfer 机制 +LLM 可以根据用户需求动态地将任务委派给其他 Agent +""" + +from google.adk.agents import LlmAgent # 导入 LlmAgent 类 + +# 定义专门的子 Agent +booking_agent = LlmAgent( + name="BookingAgent", # 预订 Agent + model="gemini-2.0-flash", # 使用 Gemini 模型 + description="负责处理航班和酒店的预订请求。", # 描述:用于 LLM 路由决策 + instruction="你是一个预订助手,帮助用户预订航班和酒店。", # 系统指令 +) + +info_agent = LlmAgent( + name="InfoAgent", # 信息查询 Agent + model="gemini-2.0-flash", # 使用 Gemini 模型 + description="负责回答一般性问题和提供信息。", # 描述:用于 LLM 路由决策 + instruction="你是一个信息助手,回答用户的各种问题。", # 系统指令 +) + +# 定义协调器 Agent(父 Agent) +coordinator = LlmAgent( + name="Coordinator", # 协调器 Agent + model="gemini-2.0-flash", # 使用 Gemini 模型 + description="主协调器,负责将用户请求分配给合适的子 Agent。", # 描述 + instruction=( # 系统指令 + "你是一个协调器助手。\n" + "当用户需要预订航班或酒店时,将任务委派给 BookingAgent。\n" + "当用户询问一般性问题时,将任务委派给 InfoAgent。\n" + "使用 transfer_to_agent 工具来委派任务。" + ), + sub_agents=[ # 🔑 注册子 Agent + booking_agent, # 预订 Agent + info_agent, # 信息 Agent + ], +) + +# 工作原理: +# 1. 用户发送消息给 Coordinator +# 2. Coordinator 的 LLM 分析消息,决定委派给哪个子 Agent +# 3. LLM 生成 transfer_to_agent(agent_name='BookingAgent') 调用 +# 4. ADK 框架自动将执行切换到目标 Agent +# 5. 目标 Agent 处理请求并返回结果 +``` + +--- + +## 3.7 完整代码示例 + +详见 `code/llm_agent_demo.py` — 包含完整的 LlmAgent 配置和运行示例。 + +--- + +## 📌 本章小结 + +- LlmAgent 是 ADK 最常用的智能体类型,以 LLM 为核心引擎 +- 好的指令设计是 Agent 效果的关键 +- ADK 默认优化 Gemini 模型,通过 LiteLLM 支持其他模型 +- Google 内置工具(搜索、代码执行)可直接使用 +- `output_key` 用于在多 Agent 系统中传递数据 +- Agent Transfer 实现动态任务委派 + +**下一章**:[第04章 - 自定义工具开发](./04-custom-tools.md) → 学习如何为 Agent 创建自定义工具。 diff --git a/ADKLearning/google-adk-tutorial/04-custom-tools.md b/ADKLearning/google-adk-tutorial/04-custom-tools.md new file mode 100644 index 0000000..6e72a5c --- /dev/null +++ b/ADKLearning/google-adk-tutorial/04-custom-tools.md @@ -0,0 +1,564 @@ +# 第04章:自定义工具开发 + +## 📌 本章目标 + +- 理解 ADK 工具系统的工作原理 +- 掌握函数工具(Function Tool)的开发方法 +- 学习 MCP 工具的集成方式 +- 了解 OpenAPI 工具的生成方法 +- 掌握工具间数据传递的技巧 + +--- + +## 4.1 工具系统概述 + +### 4.1.1 工具是什么? + +在 ADK 中,**工具(Tool)** 是 Agent 与外部世界交互的桥梁。Agent 通过调用工具来执行特定任务,如查询数据库、调用 API、执行计算等。 + +``` +┌─────────────────────────────────────────────┐ +│ Agent │ +│ │ +│ 用户: "帮我查一下北京的天气" │ +│ ↓ │ +│ LLM 分析: 需要调用天气查询工具 │ +│ ↓ │ +│ 调用工具: get_weather(city="北京") │ +│ ↓ │ +│ 工具返回: {"status": "success", │ +│ "report": "晴天,25°C"} │ +│ ↓ │ +│ LLM 生成回复: "北京今天天气晴朗,气温25°C" │ +└─────────────────────────────────────────────┘ +``` + +### 4.1.2 工具类型 + +| 类型 | 说明 | 适用场景 | +|------|------|----------| +| **Function Tool** | Python 函数自动转换 | 自定义逻辑、API 调用 | +| **MCP Tool** | 连接 MCP 服务器 | 标准化工具集成 | +| **OpenAPI Tool** | 从 OpenAPI 规范生成 | REST API 集成 | +| **Agent Tool** | 将 Agent 作为工具使用 | Agent 复用 | +| **内置工具** | Google 提供的预置工具 | 搜索、代码执行等 | + +--- + +## 4.2 函数工具(Function Tool) + +### 4.2.1 基本原理 + +ADK 会自动检查 Python 函数的以下信息来生成工具 Schema: + +- **函数名**:工具的名称 +- **docstring**:工具的描述(发送给 LLM) +- **参数类型**:参数的类型提示 +- **默认值**:区分必选和可选参数 +- **返回值**:工具的输出 + +### 4.2.2 基础示例 + +```python +""" +函数工具基础示例 +演示如何创建简单的自定义工具 +""" + +from google.adk.agents import Agent # 导入 Agent 类 + + +# ======================================== +# 示例一:简单的天气查询工具 +# ======================================== + +def get_weather(city: str) -> dict: + """ + 获取指定城市的天气信息 + + Args: + city (str): 城市名称,例如"北京"、"上海" + + Returns: + dict: 包含天气状态的字典 + """ + # 模拟天气数据(实际应用中应调用真实天气 API) + weather_data = { # 模拟天气数据库 + "北京": {"temp": "25°C", "condition": "晴天"}, # 北京天气 + "上海": {"temp": "28°C", "condition": "多云"}, # 上海天气 + "广州": {"temp": "32°C", "condition": "雷阵雨"}, # 广州天气 + } + + # 查找城市天气数据 + data = weather_data.get(city) # 从字典中获取天气数据 + + # 如果找不到该城市的天气 + if not data: # 检查数据是否存在 + return { # 返回错误信息 + "status": "error", # 状态:错误 + "error_message": f"未找到'{city}'的天气信息" # 错误描述 + } + + # 返回成功结果 + return { # 返回天气信息 + "status": "success", # 状态:成功 + "city": city, # 城市名称 + "temperature": data["temp"], # 温度 + "condition": data["condition"], # 天气状况 + } + + +# ======================================== +# 示例二:带可选参数的工具 +# ======================================== + +def search_restaurant( + city: str, # 必选参数:城市 + cuisine: str = "中餐", # 可选参数:菜系,默认中餐 + price_range: str = "中等", # 可选参数:价格范围,默认中等 +) -> dict: + """ + 搜索指定城市的餐厅 + + Args: + city (str): 城市名称 + cuisine (str, optional): 菜系类型,默认为"中餐" + price_range (str, optional): 价格范围,默认为"中等" + + Returns: + dict: 包含搜索结果的字典 + """ + # 模拟餐厅搜索结果 + return { # 返回搜索结果 + "status": "success", # 状态:成功 + "city": city, # 城市 + "cuisine": cuisine, # 菜系 + "price_range": price_range, # 价格范围 + "results": [ # 餐厅列表 + {"name": "美味餐厅", "rating": 4.5}, # 餐厅1 + {"name": "佳肴轩", "rating": 4.2}, # 餐厅2 + ], + } + + +# ======================================== +# 创建使用工具的 Agent +# ======================================== + +agent = Agent( + name="tool_agent", # Agent 名称 + model="gemini-2.0-flash", # 使用 Gemini 模型 + instruction=( # 系统指令 + "你是一个生活助手。\n" + "你可以查询天气和搜索餐厅。\n" + "根据用户需求使用合适的工具。" + ), + tools=[ # 注册工具列表 + get_weather, # 天气查询工具 + search_restaurant, # 餐厅搜索工具 + ], +) +``` + +### 4.2.3 参数类型详解 + +```python +""" +工具参数类型详解 +展示不同类型的参数定义方式 +""" + +from typing import Optional, List # 导入类型提示 +from google.adk.agents import Agent # 导入 Agent 类 + + +# ======================================== +# 必选参数 vs 可选参数 +# ======================================== + +def calculate_loan( + principal: float, # 必选:贷款本金 + annual_rate: float, # 必选:年利率 + years: int, # 必选:贷款年限 + extra_payment: float = 0.0, # 可选:额外月供,默认0 +) -> dict: + """ + 计算贷款月供和总利息 + + Args: + principal (float): 贷款本金(元) + annual_rate (float): 年利率(百分比,如 4.5 表示 4.5%) + years (int): 贷款年限 + extra_payment (float, optional): 每月额外还款金额,默认为 0 + + Returns: + dict: 包含月供、总利息等信息的字典 + """ + monthly_rate = annual_rate / 100 / 12 # 计算月利率 + total_months = years * 12 # 计算总月数 + + # 计算等额本息月供 + monthly_payment = principal * monthly_rate * (1 + monthly_rate) ** total_months / ((1 + monthly_rate) ** total_months - 1) + + total_amount = monthly_payment * total_months + extra_payment * total_months # 总还款额 + total_interest = total_amount - principal # 总利息 + + return { # 返回计算结果 + "status": "success", # 状态 + "monthly_payment": round(monthly_payment, 2), # 月供 + "total_interest": round(total_interest, 2), # 总利息 + "total_amount": round(total_amount, 2), # 总还款额 + } + + +# ======================================== +# 使用 Optional 标记可选参数 +# ======================================== + +def create_user_profile( + username: str, # 必选:用户名 + bio: Optional[str] = None, # 可选:个人简介 + avatar_url: Optional[str] = None, # 可选:头像 URL +) -> dict: + """ + 创建用户档案 + + Args: + username (str): 用户名 + bio (str, optional): 个人简介 + avatar_url (str, optional): 头像图片 URL + + Returns: + dict: 创建结果 + """ + profile = { # 创建用户档案字典 + "username": username, # 用户名 + "bio": bio or "这个人很懒,什么都没写。", # 简介(默认值) + "avatar_url": avatar_url or "", # 头像 URL + } + + return { # 返回结果 + "status": "success", # 状态 + "profile": profile, # 用户档案 + } + + +# ======================================== +# 使用 List 类型参数 +# ======================================== + +def compare_cities(cities: List[str]) -> dict: + """ + 比较多个城市的信息 + + Args: + cities (List[str]): 城市名称列表 + + Returns: + dict: 城市比较结果 + """ + city_info = { # 城市信息数据库 + "北京": {"population": "2189万", "area": "16410km²"}, # 北京 + "上海": {"population": "2487万", "area": "6341km²"}, # 上海 + "广州": {"population": "1881万", "area": "7434km²"}, # 广州 + } + + results = [] # 初始化结果列表 + for city in cities: # 遍历城市列表 + info = city_info.get(city) # 获取城市信息 + if info: # 如果信息存在 + results.append({ # 添加到结果列表 + "city": city, # 城市名 + **info, # 展开城市信息 + }) + + return { # 返回比较结果 + "status": "success" if results else "error", # 状态 + "results": results, # 比较结果 + } + + +# 创建 Agent +agent = Agent( + name="param_agent", # Agent 名称 + model="gemini-2.0-flash", # 模型 + instruction="你是一个多功能助手。", # 指令 + tools=[ # 工具列表 + calculate_loan, # 贷款计算器 + create_user_profile, # 用户档案创建 + compare_cities, # 城市比较 + ], +) +``` + +### 4.2.4 工具返回值最佳实践 + +```python +""" +工具返回值最佳实践 +返回值会被 LLM 读取,因此需要清晰、结构化 +""" + +# ❌ 不好的返回值:信息不清晰 +def bad_tool(query: str) -> str: + """不好的返回值示例""" + return "0" # LLM 不知道 0 代表什么 + + +# ✅ 好的返回值:结构化、有状态码 +def good_tool(query: str) -> dict: + """ + 好的返回值示例 + 包含状态码和详细描述 + """ + return { + "status": "success", # 状态码:success / error / pending + "query": query, # 原始查询 + "result": "查询结果详情", # 具体结果 + "metadata": { # 可选的元数据 + "source": "database", # 数据来源 + "timestamp": "2025-04-05T10:00:00Z", # 时间戳 + }, + } + + +# ✅ 错误处理:返回有意义的错误信息 +def robust_tool(query: str) -> dict: + """ + 健壮的工具:包含完善的错误处理 + """ + if not query or not query.strip(): # 检查空输入 + return { # 返回参数错误 + "status": "error", # 状态:错误 + "error_code": "INVALID_INPUT", # 错误码 + "error_message": "查询内容不能为空,请提供有效的查询参数。" # 人类可读的错误信息 + } + + try: # 尝试执行操作 + result = do_something(query) # 执行实际操作 + return { # 返回成功结果 + "status": "success", # 状态:成功 + "result": result, # 操作结果 + } + except Exception as e: # 捕获异常 + return { # 返回异常信息 + "status": "error", # 状态:错误 + "error_code": "INTERNAL_ERROR", # 错误码 + "error_message": f"处理请求时发生错误:{str(e)}" # 错误描述 + } + + +def do_something(query: str) -> str: + """模拟操作函数""" + return f"处理结果: {query}" # 返回处理结果 +``` + +--- + +## 4.3 工具间数据传递 + +```python +""" +工具间数据传递 +使用 session.state 的 temp: 前缀在工具间传递数据 +""" + +from google.adk.agents import Agent # 导入 Agent 类 +from google.adk.tools import FunctionTool # 导入 FunctionTool +from google.adk.tools import ToolContext # 导入工具上下文 + + +# ======================================== +# 使用 ToolContext 访问会话状态 +# ======================================== + +def step1_fetch_data( + query: str, # 搜索关键词 + ctx: ToolContext, # 工具上下文(自动注入) +) -> dict: + """ + 第一步:获取原始数据 + + Args: + query (str): 搜索关键词 + ctx (ToolContext): 工具上下文,可用于访问会话状态 + + Returns: + dict: 原始数据 + """ + # 模拟获取数据 + raw_data = f"关于'{query}'的原始数据..." # 模拟数据 + + # 将数据保存到临时状态(temp: 前缀) + # temp: 状态只在当前调用中有效,调用结束后自动清除 + ctx.state["temp:raw_data"] = raw_data # 保存到临时状态 + + return { # 返回结果 + "status": "success", # 状态 + "message": f"已获取关于'{query}'的数据", # 消息 + "data_length": len(raw_data), # 数据长度 + } + + +def step2_analyze_data(ctx: ToolContext) -> dict: + """ + 第二步:分析第一步获取的数据 + + Args: + ctx (ToolContext): 工具上下文 + + Returns: + dict: 分析结果 + """ + # 从临时状态中读取第一步保存的数据 + raw_data = ctx.state.get("temp:raw_data") # 读取临时状态 + + if not raw_data: # 如果没有数据 + return { # 返回错误 + "status": "error", + "error_message": "没有找到原始数据,请先执行数据获取步骤。" + } + + # 模拟数据分析 + analysis = f"对数据进行了深度分析:{raw_data[:50]}..." # 分析结果 + + return { # 返回分析结果 + "status": "success", # 状态 + "analysis": analysis, # 分析结果 + } + + +# 创建 Agent +agent = Agent( + name="pipeline_agent", # Agent 名称 + model="gemini-2.0-flash", # 模型 + instruction=( # 指令 + "你是一个数据分析助手。\n" + "当用户需要分析数据时,先使用 step1_fetch_data 获取数据,\n" + "然后使用 step2_analyze_data 进行分析。" + ), + tools=[ # 工具列表 + step1_fetch_data, # 数据获取工具 + step2_analyze_data, # 数据分析工具 + ], +) +``` + +--- + +## 4.4 Agent-as-a-Tool(智能体作为工具) + +```python +""" +Agent-as-a-Tool +将一个 Agent 包装为工具,供其他 Agent 调用 +""" + +from google.adk.agents import Agent # 导入 Agent 类 +from google.adk.tools import AgentTool # 导入 AgentTool 类 + + +# ======================================== +# 定义一个专门的翻译 Agent +# ======================================== + +translator_agent = Agent( + name="translator", # 翻译 Agent 名称 + model="gemini-2.0-flash", # 使用 Gemini 模型 + instruction="你是一个专业翻译,将用户输入的文本翻译成目标语言。只输出翻译结果。", # 指令 +) + +# ======================================== +# 将翻译 Agent 包装为工具 +# ======================================== + +translation_tool = AgentTool( # 创建 AgentTool + agent=translator_agent, # 传入要包装的 Agent +) + +# ======================================== +# 在主 Agent 中使用翻译工具 +# ======================================== + +main_agent = Agent( + name="main_agent", # 主 Agent 名称 + model="gemini-2.0-flash", # 使用 Gemini 模型 + instruction=( # 系统指令 + "你是一个多语言助手。\n" + "当用户需要翻译时,使用 translation_tool 工具。\n" + "其他情况直接回答用户问题。" + ), + tools=[translation_tool], # 注册翻译工具 +) + +# AgentTool 与 sub_agents 的区别: +# - sub_agents:通过 LLM Transfer 机制委派任务,Agent 切换执行 +# - AgentTool:作为工具被调用,在当前 Agent 上下文中执行 +``` + +--- + +## 4.5 MCP 工具集成 + +```python +""" +MCP(Model Context Protocol)工具集成 +连接外部 MCP 服务器作为 Agent 的工具 +""" + +from google.adk.agents import Agent # 导入 Agent 类 +from google.adk.tools.mcp_tool.mcp_toolset import McpToolset # 导入 MCP 工具集 + + +# ======================================== +# 连接本地 MCP 服务器 +# ======================================== + +async def create_mcp_agent(): + """创建使用 MCP 工具的 Agent""" + + # 创建 MCP 工具集 + # 需要先启动 MCP 服务器(如 filesystem、database 等) + mcp_tools = McpToolset( + connection_params={ # MCP 服务器连接参数 + "url": "http://localhost:3000", # MCP 服务器地址 + }, + tool_names=[ # 要导入的工具名称列表 + "read_file", # 读取文件工具 + "write_file", # 写入文件工具 + "list_directory", # 列出目录工具 + ], + ) + + # 获取 MCP 工具列表 + tools = await mcp_tools.get_tools() # 异步获取工具 + + # 创建使用 MCP 工具的 Agent + agent = Agent( + name="mcp_agent", # Agent 名称 + model="gemini-2.0-flash", # 使用 Gemini 模型 + instruction="你是一个文件管理助手,可以读写文件和浏览目录。", # 指令 + tools=tools, # 注册 MCP 工具 + ) + + return agent # 返回 Agent +``` + +--- + +## 4.6 完整代码示例 + +详见 `code/custom_tools_demo.py` — 包含完整的自定义工具开发示例。 + +--- + +## 📌 本章小结 + +- ADK 自动将 Python 函数转换为工具,通过函数签名生成 Schema +- 好的 docstring 是 LLM 理解工具的关键 +- 返回值推荐使用字典,包含 `status` 字段 +- `ToolContext` 允许工具访问会话状态,实现工具间数据传递 +- `AgentTool` 可以将 Agent 包装为工具供其他 Agent 使用 +- MCP 工具集支持连接外部标准化工具服务器 + +**下一章**:[第05章 - 多智能体系统](./05-multi-agent-systems.md) → 学习如何构建多 Agent 协作系统。 diff --git a/ADKLearning/google-adk-tutorial/05-multi-agent-systems.md b/ADKLearning/google-adk-tutorial/05-multi-agent-systems.md new file mode 100644 index 0000000..d8aef44 --- /dev/null +++ b/ADKLearning/google-adk-tutorial/05-multi-agent-systems.md @@ -0,0 +1,631 @@ +# 第05章:多智能体系统 + +## 📌 本章目标 + +- 理解多智能体系统的设计理念 +- 掌握 Agent 层级结构的构建方法 +- 学习三种工作流 Agent:Sequential、Parallel、Loop +- 掌握常见多 Agent 协作模式 +- 了解 Agent 间的通信机制 + +--- + +## 5.1 多智能体系统概述 + +### 5.1.1 为什么需要多智能体? + +当应用变得复杂时,使用单个"巨型"Agent 会面临以下问题: + +| 问题 | 说明 | +|------|------| +| **指令膨胀** | 系统指令过长,LLM 难以同时遵循所有规则 | +| **工具过多** | 工具列表过长,LLM 选择工具的准确率下降 | +| **难以维护** | 修改一个功能可能影响其他功能 | +| **难以调试** | 出错时难以定位问题来源 | + +**多智能体系统**通过将复杂任务分解为多个专门的 Agent 来解决这些问题。 + +### 5.1.2 ADK 多智能体架构 + +``` + root_agent(根智能体) + │ + ┌─────────────┼─────────────┐ + │ │ │ + LlmAgent SequentialAgent ParallelAgent + (协调器) (顺序流水线) (并行执行器) + │ │ │ + ┌────┼────┐ ┌────┼────┐ ┌───┴───┐ + │ │ │ │ │ │ │ │ + Agent Agent Agent Agent Agent Agent Agent + (子1) (子2) (子3) (步骤1)(步骤2)(任务A) (任务B) +``` + +--- + +## 5.2 Agent 层级结构 + +### 5.2.1 父子关系 + +```python +""" +Agent 层级结构基础 +通过 sub_agents 参数建立父子关系 +""" + +from google.adk.agents import LlmAgent # 导入 LlmAgent 类 + +# ======================================== +# 第一步:定义叶子节点 Agent(最底层的 Agent) +# ======================================== + +# 定义一个专门处理问候的 Agent +greeter_agent = LlmAgent( + name="Greeter", # Agent 名称 + model="gemini-2.0-flash", # 使用 Gemini 模型 + description="负责向用户打招呼和寒暄。", # 描述:用于路由决策 + instruction=( # 系统指令 + "你是一个友好的问候助手。\n" + "热情地向用户打招呼,并询问有什么可以帮助的。" + ), +) + +# 定义一个专门处理任务执行的 Agent +task_agent = LlmAgent( + name="TaskExecutor", # Agent 名称 + model="gemini-2.0-flash", # 使用 Gemini 模型 + description="负责执行用户的具体任务。", # 描述:用于路由决策 + instruction=( # 系统指令 + "你是一个任务执行助手。\n" + "认真完成用户交给你的任务。" + ), +) + +# ======================================== +# 第二步:定义父 Agent,通过 sub_agents 建立层级 +# ======================================== + +coordinator = LlmAgent( + name="Coordinator", # 父 Agent 名称 + model="gemini-2.0-flash", # 使用 Gemini 模型 + description="主协调器,负责分配任务。", # 描述 + instruction=( # 系统指令 + "你是一个协调器。\n" + "根据用户的请求,将任务分配给合适的子 Agent。\n" + "问候和寒暄类请求交给 Greeter。\n" + "具体任务交给 TaskExecutor。\n" + "使用 transfer_to_agent 工具来委派任务。" + ), + sub_agents=[ # 🔑 注册子 Agent 列表 + greeter_agent, # 问候 Agent + task_agent, # 任务执行 Agent + ], +) + +# ======================================== +# 层级关系说明 +# ======================================== +# coordinator(父) +# ├── greeter_agent(子) +# └── task_agent(子) +# +# ADK 会自动设置: +# greeter_agent.parent_agent == coordinator +# task_agent.parent_agent == coordinator +# +# 注意:一个 Agent 只能有一个父 Agent +# 尝试将同一个 Agent 添加到多个父 Agent 会抛出 ValueError +``` + +### 5.2.2 多层级嵌套 + +```python +""" +多层级嵌套的 Agent 结构 +构建更复杂的多 Agent 系统 +""" + +from google.adk.agents import LlmAgent, SequentialAgent # 导入 Agent 类型 + +# ======================================== +# 第三层:具体执行 Agent +# ======================================== + +code_reviewer = LlmAgent( + name="CodeReviewer", # 代码审查 Agent + model="gemini-2.0-flash", # 模型 + description="审查代码质量,提出改进建议。", # 描述 + instruction="你是代码审查专家,审查代码并给出改进建议。", # 指令 +) + +test_writer = LlmAgent( + name="TestWriter", # 测试编写 Agent + model="gemini-2.0-flash", # 模型 + description="为代码编写单元测试。", # 描述 + instruction="你是测试工程师,为给定的代码编写单元测试。", # 指令 +) + +# ======================================== +# 第二层:开发流水线(顺序执行) +# ======================================== + +dev_pipeline = SequentialAgent( + name="DevPipeline", # 流水线名称 + sub_agents=[ # 按顺序执行子 Agent + code_reviewer, # 先审查代码 + test_writer, # 再编写测试 + ], +) + +# ======================================== +# 第二层:其他专门 Agent +# ======================================== + +doc_writer = LlmAgent( + name="DocWriter", # 文档编写 Agent + model="gemini-2.0-flash", # 模型 + description="编写技术文档。", # 描述 + instruction="你是技术文档工程师,编写清晰的技术文档。", # 指令 +) + +# ======================================== +# 第一层:根 Agent(协调器) +# ======================================== + +root_agent = LlmAgent( + name="RootCoordinator", # 根协调器 + model="gemini-2.0-flash", # 模型 + description="开发团队协调器。", # 描述 + instruction=( # 指令 + "你是一个开发团队协调器。\n" + "代码审查和测试任务交给 DevPipeline。\n" + "文档编写任务交给 DocWriter。" + ), + sub_agents=[ # 子 Agent 列表 + dev_pipeline, # 开发流水线 + doc_writer, # 文档编写 + ], +) + +# 完整层级结构: +# root_agent(根协调器) +# ├── dev_pipeline(开发流水线 - SequentialAgent) +# │ ├── code_reviewer(代码审查) +# │ └── test_writer(测试编写) +# └── doc_writer(文档编写) +``` + +--- + +## 5.3 工作流 Agent + +### 5.3.1 SequentialAgent(顺序执行) + +```python +""" +SequentialAgent — 顺序执行工作流 +子 Agent 按照列表顺序依次执行 +适用于数据处理流水线等场景 +""" + +from google.adk.agents import LlmAgent, SequentialAgent # 导入 Agent 类型 + +# ======================================== +# 定义流水线的各个步骤 +# ======================================== + +# 步骤一:信息提取 Agent +extractor = LlmAgent( + name="Extractor", # 信息提取 Agent + model="gemini-2.0-flash", # 模型 + instruction="从用户输入中提取关键信息。只输出提取的关键信息。", # 指令 + output_key="extracted_info", # 🔑 将输出保存到 state['extracted_info'] +) + +# 步骤二:信息分析 Agent +analyzer = LlmAgent( + name="Analyzer", # 信息分析 Agent + model="gemini-2.0-flash", # 模型 + instruction=( # 指令:引用上一步的输出 + "分析以下提取的信息,给出深度分析报告。\n" + "提取的信息:{extracted_info}" # 通过 {key} 引用 state 中的值 + ), + output_key="analysis_result", # 将分析结果保存到 state +) + +# 步骤三:报告生成 Agent +reporter = LlmAgent( + name="Reporter", # 报告生成 Agent + model="gemini-2.0-flash", # 模型 + instruction=( # 指令:引用前两步的输出 + "根据分析结果生成一份简洁的报告。\n" + "原始信息:{extracted_info}\n" # 引用提取的信息 + "分析结果:{analysis_result}" # 引用分析结果 + ), +) + +# ======================================== +# 创建顺序流水线 +# ======================================== + +pipeline = SequentialAgent( + name="InfoPipeline", # 流水线名称 + sub_agents=[ # 🔑 按顺序执行 + extractor, # 第一步:提取信息 + analyzer, # 第二步:分析信息 + reporter, # 第三步:生成报告 + ], +) + +# 执行流程: +# 1. Extractor 运行 → 输出保存到 state['extracted_info'] +# 2. Analyzer 运行 → 读取 state['extracted_info'] → 输出保存到 state['analysis_result'] +# 3. Reporter 运行 → 读取两个 state 值 → 生成最终报告 +``` + +### 5.3.2 ParallelAgent(并行执行) + +```python +""" +ParallelAgent — 并行执行工作流 +子 Agent 同时执行,互不阻塞 +适用于多任务同时处理的场景 +""" + +from google.adk.agents import LlmAgent, ParallelAgent # 导入 Agent 类型 + +# ======================================== +# 定义并行执行的任务 +# ======================================== + +# 任务一:天气查询 Agent +weather_agent = LlmAgent( + name="WeatherFetcher", # 天气查询 Agent + model="gemini-2.0-flash", # 模型 + instruction="查询并返回用户指定城市的天气信息。只返回天气数据。", # 指令 + output_key="weather_data", # 输出保存到 state['weather_data'] +) + +# 任务二:新闻查询 Agent +news_agent = LlmAgent( + name="NewsFetcher", # 新闻查询 Agent + model="gemini-2.0-flash", # 模型 + instruction="查询并返回用户指定城市的最新新闻。只返回新闻摘要。", # 指令 + output_key="news_data", # 输出保存到 state['news_data'] +) + +# 任务三:交通查询 Agent +traffic_agent = LlmAgent( + name="TrafficFetcher", # 交通查询 Agent + model="gemini-2.0-flash", # 模型 + instruction="查询并返回用户指定城市的交通状况。只返回交通信息。", # 指令 + output_key="traffic_data", # 输出保存到 state['traffic_data'] +) + +# ======================================== +# 创建并行执行器 +# ======================================== + +gatherer = ParallelAgent( + name="InfoGatherer", # 并行执行器名称 + sub_agents=[ # 🔑 这些 Agent 将同时执行 + weather_agent, # 天气查询(并行) + news_agent, # 新闻查询(并行) + traffic_agent, # 交通查询(并行) + ], +) + +# 执行流程: +# 1. WeatherFetcher、NewsFetcher、TrafficFetcher 同时开始执行 +# 2. 各自独立运行,互不影响 +# 3. 所有 Agent 完成后,后续 Agent 可以读取三个 state 值 +# +# 注意事项: +# - 并行 Agent 共享同一个 session.state +# - 使用不同的 output_key 避免数据覆盖 +# - 事件(Event)可能交错输出 +``` + +### 5.3.3 LoopAgent(循环执行) + +```python +""" +LoopAgent — 循环执行工作流 +子 Agent 按顺序循环执行,直到满足退出条件 +适用于轮询、迭代优化等场景 +""" + +from google.adk.agents import ( # 导入所需类型 + LlmAgent, # LLM 智能体 + LoopAgent, # 循环工作流 + BaseAgent, # 基础智能体(用于自定义) +) +from google.adk.events import Event, EventActions # 导入事件和事件动作 +from google.adk.agents.invocation_context import InvocationContext # 导入调用上下文 +from typing import AsyncGenerator # 导入异步生成器类型 + + +# ======================================== +# 自定义条件检查 Agent +# ======================================== + +class QualityChecker(BaseAgent): + """ + 质量检查 Agent + 检查内容质量是否达标,决定是否继续循环 + """ + + async def _run_async_impl( + self, + ctx: InvocationContext, # 调用上下文 + ) -> AsyncGenerator[Event, None]: # 返回异步事件生成器 + """异步执行方法""" + + # 从会话状态中获取当前质量分数 + quality_score = ctx.session.state.get( # 读取状态 + "quality_score", # 状态键名 + 0, # 默认值 + ) + + # 判断质量是否达标(分数 >= 80 则通过) + is_passed = quality_score >= 80 # 质量阈值 + + # 生成事件,设置 escalate 标志 + # escalate=True 表示退出循环 + # escalate=False 表示继续循环 + yield Event( + author=self.name, # 事件作者 + actions=EventActions( + escalate=is_passed, # 🔑 达标则退出循环 + ), + content=f"质量分数: {quality_score}, {'通过' if is_passed else '未通过'}", + ) + + +# ======================================== +# 内容优化 Agent +# ======================================== + +content_improver = LlmAgent( + name="ContentImprover", # 内容优化 Agent + model="gemini-2.0-flash", # 模型 + instruction=( # 指令 + "你是一个内容优化专家。\n" + "根据当前内容的质量分数进行优化。\n" + "优化后评估质量分数(0-100),并更新到 state['quality_score']。" + ), +) + +# ======================================== +# 创建循环执行器 +# ======================================== + +optimizer_loop = LoopAgent( + name="ContentOptimizer", # 循环执行器名称 + max_iterations=5, # 🔑 最大循环次数(防止无限循环) + sub_agents=[ # 每次循环依次执行 + content_improver, # 第一步:优化内容 + QualityChecker(name="Checker"), # 第二步:检查质量 + ], +) + +# 执行流程: +# 迭代1: ContentImprover → Checker(分数<80, 继续) +# 迭代2: ContentImprover → Checker(分数<80, 继续) +# 迭代3: ContentImprover → Checker(分数>=80, 退出) +# +# 退出条件(满足任一即退出): +# 1. QualityChecker 设置 escalate=True +# 2. 达到 max_iterations 上限 +``` + +--- + +## 5.4 常见多 Agent 协作模式 + +### 5.4.1 协调器/调度器模式 + +```python +""" +协调器/调度器模式(Coordinator/Dispatcher Pattern) +最常用的多 Agent 模式 +父 Agent 根据用户请求动态分配任务给子 Agent +""" + +from google.adk.agents import LlmAgent # 导入 LlmAgent + +# 定义专门的子 Agent +math_agent = LlmAgent( + name="MathExpert", # 数学专家 + model="gemini-2.0-flash", # 模型 + description="擅长数学计算和统计分析。", # 描述 + instruction="你是数学专家,解决数学问题。", # 指令 +) + +writing_agent = LlmAgent( + name="WritingExpert", # 写作专家 + model="gemini-2.0-flash", # 模型 + description="擅长文案撰写和内容创作。", # 描述 + instruction="你是写作专家,帮助用户撰写内容。", # 指令 +) + +coding_agent = LlmAgent( + name="CodingExpert", # 编程专家 + model="gemini-2.0-flash", # 模型 + description="擅长编程和代码调试。", # 描述 + instruction="你是编程专家,帮助用户解决编程问题。", # 指令 +) + +# 定义协调器 +coordinator = LlmAgent( + name="Coordinator", # 协调器 + model="gemini-2.0-flash", # 模型 + description="根据问题类型分配给合适的专家。", # 描述 + instruction=( # 指令 + "你是一个任务协调器。\n" + "根据用户的问题类型,将任务分配给合适的专家:\n" + "- 数学相关 → MathExpert\n" + "- 写作相关 → WritingExpert\n" + "- 编程相关 → CodingExpert\n" + "使用 transfer_to_agent 工具进行任务分配。" + ), + sub_agents=[ # 注册所有专家 Agent + math_agent, # 数学专家 + writing_agent, # 写作专家 + coding_agent, # 编程专家 + ], +) +``` + +### 5.4.2 评审/批评模式(Generator-Critic) + +```python +""" +评审/批评模式(Generator-Critic Pattern) +生成器创建内容,评审器评估并给出改进建议 +通过 LoopAgent 实现迭代优化 +""" + +from google.adk.agents import LlmAgent, LoopAgent, BaseAgent # 导入类型 +from google.adk.events import Event, EventActions # 导入事件类型 +from google.adk.agents.invocation_context import InvocationContext # 导入上下文 +from typing import AsyncGenerator # 导入异步生成器 + + +# 生成器 Agent:创建初稿 +generator = LlmAgent( + name="Generator", # 生成器 + model="gemini-2.0-flash", # 模型 + instruction=( # 指令 + "你是一个创意写手。\n" + "根据用户需求创作内容。\n" + "将创作的内容保存到 state['draft']。" + ), + output_key="draft", # 输出保存到 state['draft'] +) + +# 评审器 Agent:评估内容质量 +critic = LlmAgent( + name="Critic", # 评审器 + model="gemini-2.0-flash", # 模型 + instruction=( # 指令 + "你是一个严格的内容评审。\n" + "评审以下内容,给出评分(1-10)和改进建议。\n" + "当前草稿:{draft}\n" + "如果评分 >= 8,将 state['approved'] 设为 True。\n" + "否则,将改进建议保存到 state['feedback']。" + ), +) + +# 条件检查 Agent:决定是否继续循环 +class ApprovalChecker(BaseAgent): + """检查内容是否通过审批""" + + async def _run_async_impl( + self, + ctx: InvocationContext, # 调用上下文 + ) -> AsyncGenerator[Event, None]: # 返回事件生成器 + """执行检查""" + + approved = ctx.session.state.get("approved", False) # 获取审批状态 + + yield Event( # 生成事件 + author=self.name, # 事件作者 + actions=EventActions( + escalate=approved, # 通过则退出循环 + ), + ) + +# 创建迭代优化循环 +review_loop = LoopAgent( + name="ReviewLoop", # 循环名称 + max_iterations=3, # 最多迭代3次 + sub_agents=[ # 循环体 + critic, # 第一步:评审 + ApprovalChecker(name="Checker"), # 第二步:检查是否通过 + generator, # 第三步:如果未通过,重新生成 + ], +) + +# 执行流程: +# 1. Generator 创建初稿 → state['draft'] +# 2. Critic 评审 → 评分 < 8 → state['feedback'] +# 3. Checker → approved=False → 继续循环 +# 4. Generator 根据反馈修改 → state['draft'] +# 5. Critic 再次评审 → 评分 >= 8 → state['approved']=True +# 6. Checker → approved=True → 退出循环 +``` + +--- + +## 5.5 Agent 间通信机制 + +### 5.5.1 共享状态(Shared State) + +```python +""" +Agent 间通过共享状态通信 +最基础、最常用的通信方式 +""" + +from google.adk.agents import LlmAgent, SequentialAgent # 导入类型 + +# Agent A:收集信息 +collector = LlmAgent( + name="Collector", # 信息收集 Agent + model="gemini-2.0-flash", # 模型 + instruction="收集用户需求信息。", # 指令 + output_key="user_requirements", # 输出保存到 state +) + +# Agent B:基于 Agent A 的输出进行设计 +designer = LlmAgent( + name="Designer", # 设计 Agent + model="gemini-2.0-flash", # 模型 + instruction=( # 指令:引用 Agent A 的输出 + "根据用户需求进行系统设计。\n" + "用户需求:{user_requirements}" # 通过 {key} 引用 + ), + output_key="design_doc", # 输出保存到 state +) + +# Agent C:基于 Agent B 的输出进行评审 +reviewer = LlmAgent( + name="Reviewer", # 评审 Agent + model="gemini-2.0-flash", # 模型 + instruction=( # 指令:引用 Agent A 和 B 的输出 + "评审系统设计文档。\n" + "用户需求:{user_requirements}\n" # 引用需求 + "设计文档:{design_doc}" # 引用设计 + ), +) + +# 创建流水线 +pipeline = SequentialAgent( + name="DesignPipeline", # 流水线名称 + sub_agents=[collector, designer, reviewer], # 顺序执行 +) +``` + +### 5.5.2 通信机制对比 + +| 机制 | 说明 | 适用场景 | +|------|------|----------| +| **共享 State** | 通过 `session.state` 读写数据 | 顺序流水线、数据传递 | +| **output_key** | 自动将输出保存到 State | 简单的数据传递 | +| **LLM Transfer** | LLM 动态决定委派给哪个 Agent | 灵活的任务路由 | +| **AgentTool** | 将 Agent 作为工具调用 | Agent 复用 | +| **temp: State** | 临时状态,当前调用有效 | 工具间数据传递 | + +--- + +## 📌 本章小结 + +- 多智能体系统通过 `sub_agents` 参数建立层级关系 +- **SequentialAgent**:子 Agent 按顺序执行,适合流水线 +- **ParallelAgent**:子 Agent 并行执行,适合多任务同时处理 +- **LoopAgent**:子 Agent 循环执行,适合迭代优化 +- Agent 间通信主要通过共享 State 和 LLM Transfer +- `output_key` 是最简单的数据传递方式 + +**下一章**:[第06章 - 会话与状态管理](./06-session-and-state.md) → 深入了解会话和状态管理机制。 diff --git a/ADKLearning/google-adk-tutorial/06-session-and-state.md b/ADKLearning/google-adk-tutorial/06-session-and-state.md new file mode 100644 index 0000000..959147e --- /dev/null +++ b/ADKLearning/google-adk-tutorial/06-session-and-state.md @@ -0,0 +1,595 @@ +# 第06章:会话与状态管理 + +## 📌 本章目标 + +- 理解 Session、State、Memory 三层上下文架构 +- 掌握 SessionService 的使用方法 +- 学习 State 的读写和管理 +- 了解 MemoryService 和跨会话记忆 +- 掌握 Runner 的使用方法 + +--- + +## 6.1 上下文管理架构 + +### 6.1.1 三层上下文模型 + +``` +┌──────────────────────────────────────────────────────┐ +│ 上下文管理架构 │ +│ │ +│ ┌────────────────────────────────────────────┐ │ +│ │ Memory(记忆层) │ │ +│ │ - 跨会话持久化知识 │ │ +│ │ - 可搜索的知识库 │ │ +│ │ - 管理者:MemoryService │ │ +│ │ - 实现:InMemory / Vector DB / Cloud │ │ +│ └────────────────────────────────────────────┘ │ +│ │ +│ ┌────────────────────────────────────────────┐ │ +│ │ Session(会话层) │ │ +│ │ - 单次对话线程 │ │ +│ │ - 消息历史(Events) │ │ +│ │ - 管理者:SessionService │ │ +│ │ - 实现:InMemory / Firestore / DB │ │ +│ └────────────────────────────────────────────┘ │ +│ │ +│ ┌────────────────────────────────────────────┐ │ +│ │ State(状态层) │ │ +│ │ - 会话内的临时数据 │ │ +│ │ - temp: 临时状态(当前调用有效) │ │ +│ │ - 持久状态(会话期间有效) │ │ +│ └────────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────┘ +``` + +### 6.1.2 各层对比 + +| 层级 | 生命周期 | 用途 | 管理服务 | +|------|----------|------|----------| +| **Memory** | 跨会话持久化 | 长期知识、用户偏好 | MemoryService | +| **Session** | 单次对话 | 消息历史、对话上下文 | SessionService | +| **State** | 会话期间 | 临时数据、中间结果 | session.state | +| **temp: State** | 单次调用 | 工具间数据传递 | session.state | + +--- + +## 6.2 Session(会话) + +### 6.2.1 Session 是什么? + +Session 代表用户与 Agent 系统之间的一次对话交互。它包含: + +- **消息历史**:按时间顺序记录的所有消息和事件 +- **状态数据**:对话过程中产生的临时数据 +- **元信息**:应用名称、用户 ID、会话 ID 等 + +### 6.2.2 SessionService(会话服务) + +```python +""" +SessionService — 会话管理服务 +负责创建、读取、更新和删除会话 +""" + +from google.adk.sessions import InMemorySessionService # 导入内存会话服务 +import asyncio # 导入异步库 + + +async def session_demo(): + """演示 SessionService 的使用""" + + # ======================================== + # 创建会话服务 + # ======================================== + + # InMemorySessionService:将数据存储在内存中 + # 适用于开发和测试,数据在程序重启后丢失 + session_service = InMemorySessionService() + + # ======================================== + # 创建会话 + # ======================================== + + session = await session_service.create_session( + app_name="my_app", # 应用名称(用于标识应用) + user_id="user_001", # 用户唯一标识 + session_id="session_001", # 会话唯一标识 + # state={"key": "value"}, # 可选:初始化状态 + ) + + print(f"会话创建成功: {session.id}") # 打印会话 ID + print(f"应用名称: {session.app_name}") # 打印应用名称 + print(f"用户 ID: {session.user_id}") # 打印用户 ID + + # ======================================== + # 获取会话 + # ======================================== + + existing_session = await session_service.get_session( + app_name="my_app", # 应用名称 + user_id="user_001", # 用户 ID + session_id="session_001", # 会话 ID + ) + + print(f"获取会话: {existing_session.id}") # 打印会话信息 + + # ======================================== + # 删除会话 + # ======================================== + + await session_service.delete_session( + app_name="my_app", # 应用名称 + user_id="user_001", # 用户 ID + session_id="session_001", # 会话 ID + ) + + print("会话已删除") # 确认删除 + + +# 运行演示 +asyncio.run(session_demo()) # 执行异步函数 +``` + +### 6.2.3 SessionService 实现对比 + +| 实现 | 存储方式 | 持久化 | 适用场景 | +|------|----------|--------|----------| +| **InMemorySessionService** | 内存 | ❌ | 开发、测试 | +| **FirestoreSessionService** | Google Firestore | ✅ | 生产环境 | +| **SqliteSessionService** | SQLite 文件 | ✅ | 本地生产 | +| **自定义** | 自定义后端 | ✅ | 特殊需求 | + +--- + +## 6.3 State(状态管理) + +### 6.3.1 State 基础操作 + +```python +""" +State 状态管理 +在会话中读写和管理数据 +""" + +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 # 导入异步库 + + +# ======================================== +# 定义使用 State 的工具 +# ======================================== + +def add_to_cart(item: str, price: float, ctx) -> dict: + """ + 将商品添加到购物车 + + Args: + item (str): 商品名称 + price (float): 商品价格 + ctx: 工具上下文(自动注入),用于访问会话状态 + + Returns: + dict: 操作结果 + """ + # 获取当前购物车(如果不存在则初始化为空列表) + cart = ctx.state.get("cart", []) # 从 state 中读取购物车 + + # 添加新商品到购物车 + cart.append({ # 追加商品信息 + "item": item, # 商品名称 + "price": price, # 商品价格 + }) + + # 更新 state 中的购物车 + ctx.state["cart"] = cart # 写回 state + + # 计算购物车总价 + total = sum(item["price"] for item in cart) # 求和 + + return { # 返回结果 + "status": "success", # 状态 + "message": f"已将 {item} 添加到购物车", # 消息 + "cart_items": len(cart), # 购物车商品数量 + "total": total, # 总价 + } + + +def get_cart(ctx) -> dict: + """ + 获取购物车内容 + + Args: + ctx: 工具上下文 + + Returns: + dict: 购物车内容 + """ + cart = ctx.state.get("cart", []) # 读取购物车 + + if not cart: # 如果购物车为空 + return { # 返回空购物车信息 + "status": "success", + "message": "购物车是空的", + "items": [], + "total": 0, + } + + total = sum(item["price"] for item in cart) # 计算总价 + + return { # 返回购物车内容 + "status": "success", + "items": cart, # 商品列表 + "total": total, # 总价 + } + + +def clear_cart(ctx) -> dict: + """ + 清空购物车 + + Args: + ctx: 工具上下文 + + Returns: + dict: 操作结果 + """ + ctx.state["cart"] = [] # 清空购物车 + + return { # 返回结果 + "status": "success", + "message": "购物车已清空", + } + + +# ======================================== +# 创建 Agent +# ======================================== + +shopping_agent = Agent( + name="shopping_assistant", # Agent 名称 + model="gemini-2.0-flash", # 模型 + instruction=( # 指令 + "你是一个购物助手。\n" + "帮助用户管理购物车:添加商品、查看购物车、清空购物车。" + ), + tools=[ # 注册工具 + add_to_cart, # 添加商品 + get_cart, # 查看购物车 + clear_cart, # 清空购物车 + ], +) +``` + +### 6.3.2 temp: 临时状态 + +```python +""" +temp: 临时状态 +只在当前调用(invocation)中有效,调用结束后自动清除 +适用于工具间传递中间数据 +""" + +def step1_process(query: str, ctx) -> dict: + """ + 处理步骤一:预处理数据 + + Args: + query (str): 用户查询 + ctx: 工具上下文 + + Returns: + dict: 预处理结果 + """ + # 对查询进行预处理 + processed = query.strip().lower() # 去除空格并转小写 + + # 保存到临时状态(temp: 前缀) + # 这个数据只在当前调用中有效 + ctx.state["temp:processed_query"] = processed # 临时存储 + + return { # 返回结果 + "status": "success", + "processed_query": processed, + } + + +def step2_enhance(ctx) -> dict: + """ + 处理步骤二:增强数据 + + Args: + ctx: 工具上下文 + + Returns: + dict: 增强结果 + """ + # 从临时状态读取步骤一的数据 + processed = ctx.state.get("temp:processed_query") # 读取临时状态 + + if not processed: # 如果没有数据 + return { # 返回错误 + "status": "error", + "error_message": "请先执行预处理步骤。" + } + + # 增强处理 + enhanced = f"[增强] {processed}" # 模拟增强处理 + + return { # 返回结果 + "status": "success", + "enhanced_query": enhanced, + } + +# temp: 状态 vs 普通状态: +# - temp:xxx:当前调用结束后自动清除 +# - xxx:会话期间持久保存 +``` + +--- + +## 6.4 Runner(运行器) + +### 6.4.1 Runner 基础用法 + +```python +""" +Runner — ADK 的核心执行引擎 +负责协调 Agent、会话服务和工具调用 +""" + +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 +# ======================================== + +def get_greeting(name: str) -> dict: + """生成问候语""" + return { # 返回问候语 + "status": "success", + "greeting": f"你好,{name}!欢迎来到 ADK 教程。", + } + + +agent = Agent( + name="greeting_agent", # Agent 名称 + model="gemini-2.0-flash", # 模型 + instruction="你是一个问候助手。", # 指令 + tools=[get_greeting], # 注册工具 +) + + +# ======================================== +# 使用 Runner 运行 Agent +# ======================================== + +APP_NAME = "demo_app" # 应用名称 +USER_ID = "user_001" # 用户 ID +SESSION_ID = "session_001" # 会话 ID + + +async def run_agent(): + """运行 Agent 的完整流程""" + + # 第一步:创建会话服务 + session_service = InMemorySessionService() # 内存会话服务 + + # 第二步:创建会话 + session = await session_service.create_session( + app_name=APP_NAME, # 应用名称 + user_id=USER_ID, # 用户 ID + session_id=SESSION_ID, # 会话 ID + ) + + # 第三步:创建 Runner + runner = Runner( + agent=agent, # 要运行的 Agent + app_name=APP_NAME, # 应用名称 + session_service=session_service, # 会话服务 + # max_turns=10, # 可选:最大对话轮数 + ) + + # 第四步:构造用户消息 + user_message = types.Content( + role='user', # 角色:用户 + parts=[types.Part(text='你好,请向我打招呼')], # 消息内容 + ) + + # 第五步:运行 Agent 并处理事件流 + events = runner.run_async( + user_id=USER_ID, # 用户 ID + session_id=SESSION_ID, # 会话 ID + new_message=user_message, # 用户消息 + ) + + # 第六步:遍历事件流,提取响应 + async for event in events: # 遍历所有事件 + print(f"事件类型: {type(event).__name__}") # 打印事件类型 + + # 检查是否为最终响应 + if event.is_final_response(): # 如果是最终响应 + response_text = event.content.parts[0].text # 提取文本 + print(f"Agent 回复: {response_text}") # 打印回复 + + # 检查是否为工具调用事件 + if hasattr(event, 'function_call'): # 如果有函数调用 + print(f"工具调用: {event.function_call}") # 打印调用信息 + + +# 运行 +asyncio.run(run_agent()) # 执行异步函数 +``` + +### 6.4.2 多轮对话 + +```python +""" +多轮对话示例 +在同一个会话中进行多轮交互 +""" + +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="chat_agent", # Agent 名称 + model="gemini-2.0-flash", # 模型 + instruction="你是一个友好的聊天助手,记住用户告诉你的信息。", # 指令 +) + + +async def multi_turn_chat(): + """多轮对话演示""" + + # 初始化服务 + session_service = InMemorySessionService() # 会话服务 + await session_service.create_session( # 创建会话 + app_name="chat_app", # 应用名称 + user_id="user_001", # 用户 ID + session_id="chat_001", # 会话 ID + ) + + # 创建 Runner + runner = Runner( + agent=agent, # Agent + app_name="chat_app", # 应用名称 + session_service=session_service, # 会话服务 + ) + + # 模拟多轮对话 + questions = [ # 对话列表 + "我叫小明,今年25岁。", # 第一轮:自我介绍 + "我叫什么名字?", # 第二轮:测试记忆 + "我今年多大?", # 第三轮:测试记忆 + ] + + for question in questions: # 遍历每轮对话 + print(f"\n[用户]: {question}") # 打印用户消息 + + # 构造消息 + content = types.Content( + role='user', # 角色 + parts=[types.Part(text=question)], # 内容 + ) + + # 运行 Agent + events = runner.run_async( + user_id="user_001", # 用户 ID + session_id="chat_001", # 会话 ID(同一个会话) + new_message=content, # 新消息 + ) + + # 获取响应 + async for event in events: # 遍历事件 + if event.is_final_response(): # 最终响应 + print(f"[Agent]: {event.content.parts[0].text}") # 打印回复 + + +asyncio.run(multi_turn_chat()) # 执行多轮对话 +``` + +--- + +## 6.5 Memory(记忆服务) + +### 6.5.1 Memory 概述 + +Memory 是跨会话的持久化知识存储,Agent 可以搜索和检索历史信息。 + +```python +""" +MemoryService — 记忆管理服务 +管理跨会话的持久化知识 +""" + +from google.adk.memory import InMemoryMemoryService # 导入内存记忆服务 + + +async def memory_demo(): + """演示 MemoryService 的使用""" + + # ======================================== + # 创建记忆服务 + # ======================================== + + # InMemoryMemoryService:将记忆存储在内存中 + # 适用于开发测试,数据在重启后丢失 + memory_service = InMemoryMemoryService() + + # ======================================== + # 添加记忆 + # ======================================== + + await memory_service.add_session_events_to_memory( + app_name="my_app", # 应用名称 + user_id="user_001", # 用户 ID + session_id="session_001", # 会话 ID + ) + + # ======================================== + # 搜索记忆 + # ======================================== + + results = await memory_service.search( + query="用户偏好", # 搜索查询 + app_name="my_app", # 应用名称 + user_id="user_001", # 用户 ID + ) + + # 处理搜索结果 + for result in results: # 遍历结果 + print(f"记忆内容: {result}") # 打印记忆 + + +# 运行演示 +import asyncio +asyncio.run(memory_demo()) +``` + +### 6.5.2 Memory 与 Session 的区别 + +``` +┌────────────────────────────────────────────────────┐ +│ Session(会话) │ +│ ┌──────────────────────────────────────────────┐ │ +│ │ 消息1 → 消息2 → 消息3 → ... → 消息N │ │ +│ │ state: {cart: [...], preferences: {...}} │ │ +│ └──────────────────────────────────────────────┘ │ +│ 生命周期:会话开始 → 会话结束 │ +└────────────────────────────────────────────────────┘ + +┌────────────────────────────────────────────────────┐ +│ Memory(记忆) │ +│ ┌──────────────────────────────────────────────┐ │ +│ │ 会话1摘要 → 会话2摘要 → 会话3摘要 → ... │ │ +│ │ 用户偏好 → 历史交互 → 长期知识 │ │ +│ └──────────────────────────────────────────────┘ │ +│ 生命周期:跨会话持久化 │ +└────────────────────────────────────────────────────┘ +``` + +--- + +## 📌 本章小结 + +- ADK 采用三层上下文架构:Memory > Session > State +- **Session**:单次对话线程,由 SessionService 管理 +- **State**:会话内数据,通过 `ctx.state` 读写 +- **temp: State**:临时状态,当前调用有效 +- **Memory**:跨会话持久化知识,由 MemoryService 管理 +- **Runner** 是核心执行引擎,协调 Agent 和会话 + +**下一章**:[第07章 - 回调机制与事件系统](./07-callbacks-and-events.md) → 学习如何使用回调函数控制 Agent 行为。 diff --git a/ADKLearning/google-adk-tutorial/07-callbacks-and-events.md b/ADKLearning/google-adk-tutorial/07-callbacks-and-events.md new file mode 100644 index 0000000..1217788 --- /dev/null +++ b/ADKLearning/google-adk-tutorial/07-callbacks-and-events.md @@ -0,0 +1,565 @@ +# 第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(模型调用前) + +```python +""" +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(模型调用后) + +```python +""" +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(工具调用前) + +```python +""" +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(工具调用后) + +```python +""" +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 组合使用所有回调 + +```python +""" +组合使用所有回调函数 +创建一个完整的带日志和监控的 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 实现人工确认 + +```python +""" +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 处理不同类型的事件 + +```python +""" +事件流处理 +从 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章 - 智能体评估](./08-evaluation.md) → 学习如何系统地评估 Agent 的性能。 diff --git a/ADKLearning/google-adk-tutorial/08-evaluation.md b/ADKLearning/google-adk-tutorial/08-evaluation.md new file mode 100644 index 0000000..9d9e452 --- /dev/null +++ b/ADKLearning/google-adk-tutorial/08-evaluation.md @@ -0,0 +1,444 @@ +# 第08章:智能体评估 + +## 📌 本章目标 + +- 理解 Agent 评估的重要性和方法 +- 掌握评估集(Eval Set)的创建方法 +- 学习使用 `adk eval` 命令进行自动化评估 +- 了解评估指标和结果分析 +- 掌握持续评估的最佳实践 + +--- + +## 8.1 评估概述 + +### 8.1.1 为什么需要评估? + +Agent 的行为是非确定性的(由 LLM 驱动),同样的输入可能产生不同的输出。因此,需要系统化的评估来确保: + +| 目标 | 说明 | +|------|------| +| **正确性** | Agent 是否给出了正确的回答 | +| **工具使用** | Agent 是否正确地使用了工具 | +| **指令遵循** | Agent 是否遵循了系统指令 | +| **安全性** | Agent 是否避免了不当输出 | +| **一致性** | Agent 在不同场景下表现是否一致 | + +### 8.1.2 ADK 评估方法 + +ADK 提供了两种评估维度: + +``` +┌────────────────────────────────────────────┐ +│ ADK 评估体系 │ +│ │ +│ ┌──────────────────────────────────────┐ │ +│ │ 最终响应评估(Response Evaluation) │ │ +│ │ - 评估 Agent 的最终输出质量 │ │ +│ │ - 准确性、完整性、格式等 │ │ +│ └──────────────────────────────────────┘ │ +│ │ +│ ┌──────────────────────────────────────┐ │ +│ │ 执行轨迹评估(Trajectory Evaluation)│ │ +│ │ - 评估 Agent 的执行过程 │ │ +│ │ - 工具调用顺序、决策逻辑等 │ │ +│ └──────────────────────────────────────┘ │ +└────────────────────────────────────────────┘ +``` + +--- + +## 8.2 创建评估集 + +### 8.2.1 评估集格式 + +评估集是一个 JSON 文件,包含多个测试用例: + +```json +{ + "eval_cases": [ + { + "case_id": "test_001", + "description": "测试天气查询功能", + "user_input": "北京今天天气怎么样?", + "expected_function_calls": [ + { + "name": "get_weather", + "args": { + "city": "北京" + } + } + ], + "expected_keywords": ["北京", "天气"] + }, + { + "case_id": "test_002", + "description": "测试时间查询功能", + "user_input": "现在几点了?", + "expected_function_calls": [ + { + "name": "get_current_time", + "args": { + "city": "北京" + } + } + ] + } + ] +} +``` + +### 8.2.2 创建评估集文件 + +```python +""" +创建评估集文件 +使用 Python 代码生成 .evalset.json 文件 +""" + +import json # 导入 JSON 模块 + + +def create_eval_set(): + """创建评估集""" + + # 定义评估用例列表 + eval_cases = [ # 评估用例列表 + { + # 用例标识 + "case_id": "weather_beijing", # 用例唯一 ID + + # 用例描述 + "description": "测试北京天气查询", # 用例说明 + + # 用户输入 + "user_input": "北京今天天气怎么样?", # 模拟用户提问 + + # 期望的工具调用(可选) + "expected_function_calls": [ # 期望 Agent 调用的工具 + { + "name": "get_weather", # 工具名称 + "args": { # 期望的参数 + "city": "北京", + }, + }, + ], + + # 期望的关键词(可选) + "expected_keywords": ["北京", "天气"], # 回复中应包含的关键词 + }, + { + "case_id": "weather_shanghai", # 用例 ID + "description": "测试上海天气查询", # 描述 + "user_input": "帮我查一下上海的天气", # 用户输入 + "expected_function_calls": [ # 期望工具调用 + { + "name": "get_weather", + "args": {"city": "上海"}, + }, + ], + }, + { + "case_id": "greeting", # 用例 ID + "description": "测试问候功能", # 描述 + "user_input": "你好!", # 用户输入 + "expected_keywords": ["你好"], # 期望关键词 + "not_expected_keywords": ["工具", "错误"], # 不应出现的关键词 + }, + { + "case_id": "unknown_city", # 用例 ID + "description": "测试未知城市处理", # 描述 + "user_input": "查询一下月球基地的天气", # 用户输入 + "expected_keywords": ["找不到", "不支持", "无法"], # 应包含的错误提示 + }, + ] + + # 构建评估集对象 + eval_set = { # 评估集 + "eval_cases": eval_cases, # 用例列表 + } + + # 写入 JSON 文件 + with open("my_agent_eval_set.evalset.json", "w", encoding="utf-8") as f: # 打开文件 + json.dump( # 写入 JSON + eval_set, # 评估集数据 + f, # 文件对象 + ensure_ascii=False, # 允许中文 + indent=2, # 缩进格式化 + ) + + print("评估集已创建: my_agent_eval_set.evalset.json") # 打印成功信息 + + +# 执行创建 +create_eval_set() # 调用函数 +``` + +--- + +## 8.3 运行评估 + +### 8.3.1 使用 CLI 运行评估 + +```bash +# 基本评估命令 +adk eval my_agent my_agent_eval_set.evalset.json + +# 指定输出格式 +adk eval my_agent my_agent_eval_set.evalset.json --output-format json + +# 指定输出文件 +adk eval my_agent my_agent_eval_set.evalset.json --output-file results.json +``` + +### 8.3.2 评估输出示例 + +```json +{ + "summary": { + "total_cases": 4, + "passed": 3, + "failed": 1, + "pass_rate": 0.75 + }, + "results": [ + { + "case_id": "weather_beijing", + "status": "passed", + "response": "北京今天天气晴朗,气温25°C。", + "function_calls_matched": true + }, + { + "case_id": "weather_shanghai", + "status": "passed", + "response": "上海今天多云,气温28°C。", + "function_calls_matched": true + }, + { + "case_id": "greeting", + "status": "passed", + "response": "你好!有什么可以帮助你的吗?" + }, + { + "case_id": "unknown_city", + "status": "failed", + "response": "月球基地的天气信息暂时无法获取。", + "reason": "Missing expected keywords: ['找不到', '不支持', '无法']" + } + ] +} +``` + +--- + +## 8.4 代码方式评估 + +```python +""" +通过代码方式运行评估 +适用于集成到 CI/CD 流水线 +""" + +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 # 导入异步库 +import json # 导入 JSON 模块 + + +# ======================================== +# 定义 Agent 和工具 +# ======================================== + +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} # 返回天气 + + +agent = Agent( + name="weather_agent", # Agent 名称 + model="gemini-2.0-flash", # 模型 + instruction="你是天气助手,使用 get_weather 工具查询天气。", # 指令 + tools=[get_weather], # 工具 +) + + +# ======================================== +# 定义评估逻辑 +# ======================================== + +async def evaluate_agent(test_cases: list) -> dict: + """ + 评估 Agent + + Args: + test_cases: 测试用例列表 + + Returns: + dict: 评估结果 + """ + session_service = InMemorySessionService() # 会话服务 + runner = Runner( # 运行器 + agent=agent, # Agent + app_name="eval_app", # 应用名称 + session_service=session_service, # 会话服务 + ) + + results = [] # 结果列表 + passed = 0 # 通过计数 + + for i, case in enumerate(test_cases): # 遍历测试用例 + # 创建独立会话 + session_id = f"eval_session_{i}" # 会话 ID + await session_service.create_session( # 创建会话 + app_name="eval_app", # 应用名称 + user_id="eval_user", # 用户 ID + session_id=session_id, # 会话 ID + ) + + # 构造用户消息 + content = types.Content( + role='user', # 角色 + parts=[types.Part(text=case["user_input"])], # 内容 + ) + + # 运行 Agent + events = runner.run_async( + user_id="eval_user", # 用户 ID + session_id=session_id, # 会话 ID + new_message=content, # 消息 + ) + + # 收集响应 + response_text = "" # 初始化响应文本 + async for event in events: # 遍历事件 + if event.is_final_response(): # 最终响应 + response_text = event.content.parts[0].text # 提取文本 + + # 检查是否通过 + case_passed = True # 默认通过 + failure_reason = "" # 失败原因 + + # 检查期望关键词 + if "expected_keywords" in case: # 如果有关键词要求 + for keyword in case["expected_keywords"]: # 遍历关键词 + if keyword not in response_text: # 如果关键词不存在 + case_passed = False # 标记为失败 + failure_reason = f"缺少关键词: '{keyword}'" # 记录原因 + break # 跳出循环 + + if case_passed: # 如果通过 + passed += 1 # 递增通过计数 + + # 记录结果 + results.append({ # 添加结果 + "case_id": case.get("case_id", f"case_{i}"), # 用例 ID + "status": "passed" if case_passed else "failed", # 状态 + "response": response_text[:200], # 响应(截断) + "reason": failure_reason, # 失败原因 + }) + + # 返回汇总结果 + return { # 返回评估结果 + "total": len(test_cases), # 总用例数 + "passed": passed, # 通过数 + "failed": len(test_cases) - passed, # 失败数 + "pass_rate": passed / len(test_cases), # 通过率 + "results": results, # 详细结果 + } + + +# ======================================== +# 运行评估 +# ======================================== + +async def main(): + """主函数""" + + # 定义测试用例 + test_cases = [ # 测试用例列表 + { + "case_id": "test_001", # 用例 ID + "user_input": "北京天气怎么样?", # 用户输入 + "expected_keywords": ["北京", "天气"], # 期望关键词 + }, + { + "case_id": "test_002", + "user_input": "上海天气如何?", + "expected_keywords": ["上海"], + }, + { + "case_id": "test_003", + "user_input": "你好", + "expected_keywords": ["你好"], + }, + ] + + # 执行评估 + result = await evaluate_agent(test_cases) # 运行评估 + + # 打印结果 + print("=" * 50) # 分隔线 + print("评估结果") # 标题 + print("=" * 50) # 分隔线 + print(f"总用例: {result['total']}") # 总数 + print(f"通过: {result['passed']}") # 通过数 + print(f"失败: {result['failed']}") # 失败数 + print(f"通过率: {result['pass_rate']:.1%}") # 通过率 + print("-" * 50) # 分隔线 + + for r in result["results"]: # 遍历详细结果 + status_icon = "✅" if r["status"] == "passed" else "❌" # 状态图标 + print(f"{status_icon} {r['case_id']}: {r['status']}") # 打印结果 + if r["reason"]: # 如果有失败原因 + print(f" 原因: {r['reason']}") # 打印原因 + + +asyncio.run(main()) # 执行主函数 +``` + +--- + +## 8.5 评估最佳实践 + +### 8.5.1 评估用例设计原则 + +| 原则 | 说明 | 示例 | +|------|------|------| +| **覆盖核心功能** | 每个工具至少一个正向用例 | 天气查询 → 查询有效城市 | +| **边界情况** | 测试异常输入的处理 | 查询不存在的城市 | +| **多轮对话** | 测试上下文记忆能力 | 先说名字,再问名字 | +| **安全测试** | 测试不当请求的处理 | 注入攻击、敏感信息 | +| **回归测试** | 修复 bug 后添加用例 | 之前出错的场景 | + +### 8.5.2 持续评估流程 + +``` +┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ +│ 开发 Agent │───▶│ 编写评估集 │───▶│ 运行评估 │───▶│ 分析结果 │ +└──────────┘ └──────────┘ └──────────┘ └──────────┘ + ▲ │ + │ │ + └───────────────────────────────────────────────┘ + 根据结果优化 Agent +``` + +--- + +## 📌 本章小结 + +- Agent 评估是确保质量的关键环节 +- 评估集是 JSON 格式的测试用例集合 +- 使用 `adk eval` 命令或代码方式运行评估 +- 评估维度包括最终响应和执行轨迹 +- 好的评估集应覆盖核心功能、边界情况和安全场景 + +**下一章**:[第09章 - 部署指南](./09-deployment.md) → 学习如何将 Agent 部署到生产环境。 diff --git a/ADKLearning/google-adk-tutorial/09-deployment.md b/ADKLearning/google-adk-tutorial/09-deployment.md new file mode 100644 index 0000000..db832b1 --- /dev/null +++ b/ADKLearning/google-adk-tutorial/09-deployment.md @@ -0,0 +1,420 @@ +# 第09章:部署指南 + +## 📌 本章目标 + +- 了解 ADK Agent 的多种部署方式 +- 掌握使用 Docker 容器化部署 +- 学习部署到 Google Cloud Run +- 了解部署到 Vertex AI Agent Engine +- 掌握 API Server 的使用方法 + +--- + +## 9.1 部署方式概览 + +``` +┌──────────────────────────────────────────────────────┐ +│ ADK 部署选项 │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌────────────┐ │ +│ │ 本地运行 │ │ Cloud Run │ │ Vertex AI │ │ +│ │ (开发测试) │ │ (容器部署) │ │ Agent Eng. │ │ +│ └──────────────┘ └──────────────┘ └────────────┘ │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ │ +│ │ API Server │ │ 自定义部署 │ │ +│ │ (FastAPI) │ │ (Docker) │ │ +│ └──────────────┘ └──────────────┘ │ +└──────────────────────────────────────────────────────┘ +``` + +| 方式 | 适用场景 | 复杂度 | 成本 | +|------|----------|--------|------| +| **本地运行** | 开发、测试 | 低 | 免费 | +| **API Server** | 集成到现有应用 | 低 | 取决于服务器 | +| **Cloud Run** | 生产环境、自动扩缩 | 中 | 按使用付费 | +| **Vertex AI** | 企业级、大规模 | 高 | 按使用付费 | +| **Docker** | 自托管、私有化 | 中 | 取决于服务器 | + +--- + +## 9.2 API Server 部署 + +### 9.2.1 启动 API Server + +```bash +# 启动 FastAPI 服务器 +adk api_server --port 8080 + +# 指定 Agent 目录 +adk api_server --agent-dir ./my_agent --port 8080 + +# 启动后,API Server 提供以下端点: +# POST /apps/{app_name}/users/{user_id}/sessions/{session_id}:run +# GET /apps/{app_name}/users/{user_id}/sessions +# POST /apps/{app_name}/users/{user_id}/sessions +``` + +### 9.2.2 调用 API Server + +```python +""" +调用 ADK API Server +通过 HTTP 请求与 Agent 交互 +""" + +import requests # 导入 HTTP 请求库 +import json # 导入 JSON 模块 + + +# ======================================== +# 配置 API Server 地址 +# ======================================== + +API_BASE_URL = "http://localhost:8080" # API Server 地址 +APP_NAME = "my_agent" # 应用名称 +USER_ID = "user_001" # 用户 ID +SESSION_ID = "session_001" # 会话 ID + + +# ======================================== +# 创建会话 +# ======================================== + +def create_session(): + """创建新的会话""" + + url = f"{API_BASE_URL}/apps/{APP_NAME}/users/{USER_ID}/sessions" # API URL + + response = requests.post( # 发送 POST 请求 + url, # 请求 URL + json={ # 请求体 + "session_id": SESSION_ID, # 会话 ID + }, + ) + + session = response.json() # 解析 JSON 响应 + print(f"会话创建成功: {session}") # 打印结果 + return session # 返回会话信息 + + +# ======================================== +# 发送消息给 Agent +# ======================================== + +def send_message(message: str): + """发送消息并获取 Agent 响应""" + + url = ( # 构建 URL + f"{API_BASE_URL}/apps/{APP_NAME}/" + f"users/{USER_ID}/sessions/{SESSION_ID}:run" + ) + + payload = { # 请求体 + "user_id": USER_ID, # 用户 ID + "session_id": SESSION_ID, # 会话 ID + "new_message": { # 新消息 + "role": "user", # 角色 + "parts": [{"text": message}], # 消息内容 + }, + } + + response = requests.post( # 发送 POST 请求 + url, # URL + json=payload, # 请求体 + stream=True, # 流式响应 + ) + + # 处理流式响应 + for line in response.iter_lines(): # 逐行读取 + if line: # 如果有内容 + data = json.loads(line) # 解析 JSON + print(f"事件: {data}") # 打印事件 + + +# ======================================== +# 使用示例 +# ======================================== + +if __name__ == "__main__": + create_session() # 创建会话 + send_message("你好!") # 发送消息 +``` + +--- + +## 9.3 Docker 容器化部署 + +### 9.3.1 创建 Dockerfile + +```dockerfile +# ======================================== +# ADK Agent Docker 部署文件 +# ======================================== + +# 使用 Python 3.11 作为基础镜像 +FROM python:3.11-slim + +# 设置工作目录 +WORKDIR /app + +# 设置环境变量 +# PYTHONUNBUFFERED: 确保 Python 输出直接显示到控制台 +ENV PYTHONUNBUFFERED=1 + +# 复制依赖文件 +COPY requirements.txt . + +# 安装 Python 依赖 +RUN pip install --no-cache-dir -r requirements.txt + +# 复制 Agent 代码 +COPY my_agent/ ./my_agent/ + +# 复制启动脚本 +COPY start.sh . + +# 赋予启动脚本执行权限 +RUN chmod +x start.sh + +# 暴露端口 +EXPOSE 8080 + +# 启动 API Server +CMD ["./start.sh"] +``` + +### 9.3.2 创建 requirements.txt + +```text +# ADK Agent 依赖文件 +google-adk>=1.0.0 +# 如果需要使用其他模型 +litellm>=1.0.0 +``` + +### 9.3.3 创建启动脚本 + +```bash +#!/bin/bash +# ADK Agent 启动脚本 + +# 设置 API Key(从环境变量读取) +export GOOGLE_API_KEY="${GOOGLE_API_KEY}" + +# 启动 API Server +adk api_server \ + --agent-dir ./my_agent \ + --port 8080 \ + --host 0.0.0.0 +``` + +### 9.3.4 构建和运行 Docker 镜像 + +```bash +# 构建 Docker 镜像 +docker build -t my-adk-agent . + +# 运行 Docker 容器 +docker run -d \ + --name my-agent \ + -p 8080:8080 \ + -e GOOGLE_API_KEY="your_api_key" \ + my-adk-agent + +# 查看日志 +docker logs -f my-agent + +# 测试 API +curl -X POST http://localhost:8080/apps/my_agent/users/user_001/sessions \ + -H "Content-Type: application/json" \ + -d '{"session_id": "test_session"}' +``` + +--- + +## 9.4 部署到 Google Cloud Run + +### 9.4.1 使用 ADK CLI 部署 + +```bash +# 使用 adk deploy 命令部署到 Cloud Run +adk deploy my_agent --platform cloud-run + +# 部署过程: +# 1. 自动构建 Docker 镜像 +# 2. 推送到 Google Container Registry +# 3. 部署到 Cloud Run +# 4. 配置环境变量和密钥 +``` + +### 9.4.2 手动部署到 Cloud Run + +```bash +# 第一步:配置 gcloud CLI +gcloud auth login # 登录 Google Cloud +gcloud config set project YOUR_PROJECT_ID # 设置项目 ID + +# 第二步:构建并推送镜像 +gcloud builds submit --tag gcr.io/YOUR_PROJECT_ID/my-agent + +# 第三步:部署到 Cloud Run +gcloud run deploy my-agent \ + --image gcr.io/YOUR_PROJECT_ID/my-agent \ + --platform managed \ + --region us-central1 \ + --allow-unauthenticated \ + --set-env-vars "GOOGLE_API_KEY=your_api_key" + +# 第四步:获取服务 URL +gcloud run services describe my-agent \ + --region us-central1 \ + --format 'value(status.url)' +``` + +--- + +## 9.5 部署到 Vertex AI Agent Engine + +### 9.5.1 使用 ADK CLI 部署 + +```bash +# 部署到 Vertex AI Agent Engine +adk deploy my_agent --platform vertex-ai + +# Vertex AI Agent Engine 提供: +# - 自动扩缩容 +# - 内置监控和日志 +# - 企业级安全 +# - 与 Google Cloud 生态集成 +``` + +### 9.5.2 部署配置 + +```python +""" +Vertex AI Agent Engine 部署配置 +""" + +# 在 agent.py 中添加部署元数据 +from google.adk.agents import Agent # 导入 Agent 类 + +root_agent = Agent( + name="my_agent", # Agent 名称 + model="gemini-2.0-flash", # 模型 + instruction="你是一个助手。", # 指令 + # Vertex AI 部署相关配置 + # output_key="response", # 输出键 + # before_model_callback=..., # 回调函数 +) +``` + +--- + +## 9.6 生产环境最佳实践 + +### 9.6.1 安全配置 + +```python +""" +生产环境安全配置 +""" + +# ======================================== +# 1. API Key 管理 +# ======================================== + +# ❌ 不要硬编码 API Key +# BAD: api_key = "AIzaSy..." + +# ✅ 使用环境变量 +import os # 导入 os 模块 +api_key = os.environ.get("GOOGLE_API_KEY") # 从环境变量读取 + +# ✅ 使用 Secret Manager(Google Cloud) +# from google.cloud import secretmanager +# client = secretmanager.SecretManagerServiceClient() +# secret = client.access_secret_version(name="projects/.../secrets/api-key/versions/latest") + + +# ======================================== +# 2. 输入验证 +# ======================================== + +async def safe_before_model_callback(cb_ctx, inv_ctx): + """安全回调:验证输入""" + # 获取用户输入 + user_input = inv_ctx.session.state.get("temp:user_input", "") # 读取输入 + + # 检查输入长度 + if len(user_input) > 10000: # 如果输入过长 + raise ValueError("输入过长,请缩短您的消息。") # 抛出异常 + + # 检查敏感内容(示例) + sensitive_patterns = [ # 敏感内容模式 + "密码", "credit card", "ssn", # 敏感词列表 + ] + for pattern in sensitive_patterns: # 遍历模式 + if pattern.lower() in user_input.lower(): # 如果匹配 + print(f"[安全] 检测到敏感内容: {pattern}") # 记录日志 + + +# ======================================== +# 3. 速率限制 +# ======================================== + +# 在 API Server 前面配置速率限制 +# 可以使用 Nginx、Cloud Armor 等工具 +``` + +### 9.6.2 监控和日志 + +```python +""" +生产环境监控配置 +""" + +import logging # 导入日志模块 +import time # 导入时间模块 + +# 配置日志 +logging.basicConfig( # 配置日志格式 + level=logging.INFO, # 日志级别 + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', # 格式 +) + +logger = logging.getLogger("adk_agent") # 创建日志记录器 + + +async def monitoring_callback(cb_ctx, inv_ctx): + """监控回调:记录性能指标""" + + start_time = time.time() # 记录开始时间 + + # 记录请求信息 + logger.info( # 记录信息日志 + f"Agent: {inv_ctx.agent.name}, " # Agent 名称 + f"User: {inv_ctx.session.user_id}" # 用户 ID + ) + + # 在 after 回调中计算耗时 + elapsed = time.time() - start_time # 计算耗时 + logger.info(f"处理耗时: {elapsed:.3f}s") # 记录耗时 + + # 如果耗时过长,记录警告 + if elapsed > 5.0: # 如果超过5秒 + logger.warning(f"请求处理耗时过长: {elapsed:.3f}s") # 记录警告 +``` + +--- + +## 📌 本章小结 + +- ADK 支持多种部署方式:本地、API Server、Cloud Run、Vertex AI +- API Server 基于 FastAPI,提供 REST API 接口 +- Docker 容器化是推荐的生产部署方式 +- Cloud Run 提供自动扩缩容和按使用付费 +- Vertex AI Agent Engine 提供企业级功能 +- 生产环境需要注意安全、监控和日志 + +**下一章**:[第10章 - 高级主题](./10-advanced-topics.md) → 学习安全最佳实践、A2A 协议等高级功能。 diff --git a/ADKLearning/google-adk-tutorial/10-advanced-topics.md b/ADKLearning/google-adk-tutorial/10-advanced-topics.md new file mode 100644 index 0000000..1824aa2 --- /dev/null +++ b/ADKLearning/google-adk-tutorial/10-advanced-topics.md @@ -0,0 +1,674 @@ +# 第10章:高级主题 + +## 📌 本章目标 + +- 掌握 ADK 的安全最佳实践 +- 了解 A2A(Agent-to-Agent)协议 +- 学习多模型混合使用策略 +- 掌握自定义 Agent 的开发方法 +- 了解性能优化技巧 + +--- + +## 10.1 安全最佳实践 + +### 10.1.1 输入安全 + +```python +""" +输入安全:防止注入攻击和不当输入 +""" + +from google.adk.agents import Agent # 导入 Agent 类 +from google.adk.agents.invocation_context import InvocationContext # 导入上下文 +import re # 导入正则表达式模块 + + +# ======================================== +# 输入过滤器 +# ======================================== + +async def input_filter_callback( + callback_context, # 回调上下文 + invocation_context: InvocationContext, # 调用上下文 +): + """ + 输入安全过滤回调 + 在模型调用前检查用户输入的安全性 + """ + + # 获取用户消息 + # 从调用上下文中获取最近的用户消息 + events = invocation_context.session.events # 获取会话事件 + user_messages = [ # 筛选用户消息 + e for e in events + if hasattr(e, 'content') and e.content.role == 'user' + ] + + if not user_messages: # 如果没有用户消息 + return # 直接返回 + + last_message = user_messages[-1] # 获取最后一条用户消息 + user_text = last_message.content.parts[0].text # 提取文本 + + # ======================================== + # 检查1:提示注入检测 + # ======================================== + injection_patterns = [ # 注入攻击模式 + r"忽略.*指令", # "忽略之前的指令" + r"ignore.*instruction", # 英文注入 + r"你现在是", # 角色切换 + r"pretend.*you are", # 英文角色切换 + r"system\s*:", # 系统提示伪造 + ] + + for pattern in injection_patterns: # 遍历模式 + if re.search(pattern, user_text, re.IGNORECASE): # 如果匹配 + print(f"[安全] 检测到可能的注入攻击: {pattern}") # 记录警告 + # 在实际应用中,可以拒绝处理或返回安全提示 + + # ======================================== + # 检查2:输入长度限制 + # ======================================== + MAX_INPUT_LENGTH = 5000 # 最大输入长度 + if len(user_text) > MAX_INPUT_LENGTH: # 如果超过限制 + print(f"[安全] 输入过长: {len(user_text)} > {MAX_INPUT_LENGTH}") # 记录警告 + + # ======================================== + # 检查3:敏感信息检测 + # ======================================== + sensitive_patterns = [ # 敏感信息模式 + r"\b\d{3}-\d{2}-\d{4}\b", # SSN 格式 + r"\b\d{16}\b", # 信用卡号格式 + r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", # 邮箱 + ] + + for pattern in sensitive_patterns: # 遍历模式 + if re.search(pattern, user_text): # 如果匹配 + print(f"[安全] 检测到可能的敏感信息") # 记录警告 + # 不要在日志中记录实际的敏感信息 + + +# ======================================== +# 创建安全 Agent +# ======================================== + +safe_agent = Agent( + name="safe_agent", # Agent 名称 + model="gemini-2.0-flash", # 模型 + instruction="你是一个安全的助手。遵循安全准则。", # 指令 + before_model_callback=input_filter_callback, # 🔑 安全过滤回调 +) +``` + +### 10.1.2 输出安全 + +```python +""" +输出安全:过滤不当输出 +""" + +async def output_filter_callback( + callback_context, # 回调上下文 + invocation_context: InvocationContext, # 调用上下文 +): + """ + 输出安全过滤回调 + 在模型返回响应后检查输出内容 + """ + + response = callback_context.response # 获取模型响应 + + if not response: # 如果没有响应 + return # 直接返回 + + # 检查响应中是否有文本内容 + if response.candidates: # 如果有候选响应 + for candidate in response.candidates: # 遍历候选 + if candidate.content: # 如果有内容 + text = candidate.content.parts[0].text # 提取文本 + + # 检查不当内容 + forbidden_words = [ # 禁止词列表 + "暴力", "违法", # 中文禁止词 + ] + + for word in forbidden_words: # 遍历禁止词 + if word in text: # 如果包含禁止词 + print(f"[安全] 输出包含不当内容: {word}") # 记录警告 + + +agent = Agent( + name="output_safe_agent", # Agent 名称 + model="gemini-2.0-flash", # 模型 + instruction="你是一个安全的助手。", # 指令 + after_model_callback=output_filter_callback, # 🔑 输出过滤回调 +) +``` + +--- + +## 10.2 A2A 协议(Agent-to-Agent) + +### 10.2.1 A2A 概述 + +A2A(Agent-to-Agent)协议是 Google 提出的开放标准,用于实现不同 Agent 系统之间的远程通信。 + +``` +┌──────────────┐ ┌──────────────┐ +│ Agent A │ │ Agent B │ +│ (ADK) │◄────────►│ (其他框架) │ +│ │ A2A 协议 │ │ +│ - 发送任务 │ │ - 接收任务 │ +│ - 接收结果 │ │ - 返回结果 │ +└──────────────┘ └──────────────┘ +``` + +### 10.2.2 A2A 集成示例 + +```python +""" +A2A 协议集成示例 +让 ADK Agent 与远程 Agent 通信 +""" + +from google.adk.agents import Agent # 导入 Agent 类 +from google.adk.tools import AgentTool # 导入 AgentTool + +# ======================================== +# 创建远程 Agent 客户端 +# ======================================== + +# 假设有一个远程 Agent 服务运行在 http://remote-agent:3000 +# 可以通过 A2A 协议与之通信 + +# 在实际使用中,需要安装 A2A 客户端库 +# pip install a2a-sdk + +# 创建远程 Agent 的代理 +# remote_agent_proxy = create_remote_agent_proxy( +# url="http://remote-agent:3000", # 远程 Agent 地址 +# agent_name="RemoteExpert", # 远程 Agent 名称 +# ) + +# 将远程 Agent 包装为工具 +# remote_tool = AgentTool(agent=remote_agent_proxy) + +# ======================================== +# 在本地 Agent 中使用远程 Agent +# ======================================== + +local_agent = Agent( + name="local_agent", # 本地 Agent 名称 + model="gemini-2.0-flash", # 模型 + instruction=( # 指令 + "你是一个协调助手。\n" + "对于本地可以处理的问题,直接回答。\n" + "对于需要专业知识的问题,使用远程 Agent 工具。" + ), + # tools=[remote_tool], # 注册远程 Agent 工具 +) +``` + +--- + +## 10.3 自定义 Agent 开发 + +### 10.3.1 继承 BaseAgent + +```python +""" +自定义 Agent 开发 +通过继承 BaseAgent 实现独特的 Agent 逻辑 +""" + +from google.adk.agents import BaseAgent # 导入基础 Agent 类 +from google.adk.agents.invocation_context import InvocationContext # 导入调用上下文 +from google.adk.events import Event, EventActions # 导入事件类型 +from typing import AsyncGenerator # 导入异步生成器 +import json # 导入 JSON 模块 +import random # 导入随机数模块 + + +# ======================================== +# 示例一:随机决策 Agent +# ======================================== + +class RandomDecisionAgent(BaseAgent): + """ + 随机决策 Agent + 随机选择一个子 Agent 来处理请求 + """ + + async def _run_async_impl( + self, + ctx: InvocationContext, # 调用上下文 + ) -> AsyncGenerator[Event, None]: # 返回事件生成器 + """异步执行方法""" + + # 获取子 Agent 列表 + sub_agents = self.sub_agents # 获取子 Agent + + if not sub_agents: # 如果没有子 Agent + yield Event( # 生成错误事件 + author=self.name, # 作者 + content="没有可用的子 Agent。", # 错误消息 + ) + return # 退出 + + # 随机选择一个子 Agent + chosen = random.choice(sub_agents) # 随机选择 + print(f"[随机] 选择了 {chosen.name}") # 打印选择结果 + + # 将选择结果保存到状态 + ctx.session.state["temp:chosen_agent"] = chosen.name # 保存选择 + + # 生成事件,指示委派给选中的 Agent + yield Event( # 生成委派事件 + author=self.name, # 作者 + actions=EventActions( # 事件动作 + transfer_to_agent=chosen.name, # 委派给选中的 Agent + ), + ) + + +# ======================================== +# 示例二:规则引擎 Agent +# ======================================== + +class RuleEngineAgent(BaseAgent): + """ + 规则引擎 Agent + 基于预定义规则处理请求,不使用 LLM + """ + + def __init__(self, rules: dict, **kwargs): + """ + 初始化规则引擎 + + Args: + rules: 规则字典,key 为匹配模式,value 为响应 + """ + super().__init__(**kwargs) # 调用父类初始化 + self.rules = rules # 保存规则 + + async def _run_async_impl( + self, + ctx: InvocationContext, # 调用上下文 + ) -> AsyncGenerator[Event, None]: # 返回事件生成器 + """异步执行方法""" + + # 获取用户消息 + events = ctx.session.events # 获取会话事件 + user_events = [ # 筛选用户消息 + e for e in events + if hasattr(e, 'content') and e.content.role == 'user' + ] + + if not user_events: # 如果没有用户消息 + yield Event( # 生成提示事件 + author=self.name, + content="请输入您的问题。", + ) + return # 退出 + + user_text = user_events[-1].content.parts[0].text # 提取文本 + + # 匹配规则 + matched_response = None # 匹配的响应 + for pattern, response in self.rules.items(): # 遍历规则 + if pattern.lower() in user_text.lower(): # 如果匹配 + matched_response = response # 保存响应 + break # 跳出循环 + + if matched_response: # 如果匹配到规则 + yield Event( # 生成响应事件 + author=self.name, # 作者 + content=matched_response, # 响应内容 + ) + else: # 如果没有匹配 + yield Event( # 生成默认响应 + author=self.name, + content="抱歉,我无法处理您的请求。请尝试其他问题。", + ) + + +# ======================================== +# 使用自定义 Agent +# ======================================== + +# 创建规则引擎 Agent +faq_agent = RuleEngineAgent( + name="FAQBot", # Agent 名称 + rules={ # 规则字典 + "价格": "我们的产品价格请参考官网定价页面。", # 价格相关 + "地址": "我们的地址是北京市海淀区xxx路xxx号。", # 地址相关 + "电话": "客服电话:400-xxx-xxxx。", # 电话相关 + "营业时间": "营业时间:周一至周五 9:00-18:00。", # 营业时间 + }, +) + +# 创建随机决策 Agent +random_agent = RandomDecisionAgent( + name="RandomRouter", # Agent 名称 + sub_agents=[ # 子 Agent 列表 + faq_agent, # FAQ Agent + ], +) +``` + +--- + +## 10.4 多模型混合策略 + +```python +""" +多模型混合使用 +根据任务类型选择不同的模型 +""" + +from google.adk.agents import LlmAgent # 导入 LlmAgent +from google.adk.models.lite_llm import LiteLlm # 导入 LiteLLM + + +# ======================================== +# 策略一:不同 Agent 使用不同模型 +# ======================================== + +# 简单任务使用快速模型 +simple_agent = LlmAgent( + name="SimpleAgent", # 简单任务 Agent + model="gemini-2.0-flash", # 快速模型 + description="处理简单的问答和翻译任务。", # 描述 + instruction="快速准确地回答简单问题。", # 指令 +) + +# 复杂推理使用强力模型 +reasoning_agent = LlmAgent( + name="ReasoningAgent", # 推理 Agent + model="gemini-2.5-pro-preview-05-06", # 强力模型 + description="处理需要深度推理的复杂任务。", # 描述 + instruction="仔细分析问题,给出深入的推理过程。", # 指令 +) + +# 代码生成使用专用模型 +code_agent = LlmAgent( + name="CodeAgent", # 代码 Agent + model=LiteLlm( # 使用 LiteLLM + model="deepseek/deepseek-coder", # DeepSeek Coder + api_key="your_api_key", # API Key + ), + description="处理代码生成和调试任务。", # 描述 + instruction="编写高质量的代码。", # 指令 +) + +# 协调器使用快速模型 +coordinator = LlmAgent( + name="Coordinator", # 协调器 + model="gemini-2.0-flash", # 快速模型 + instruction=( # 指令 + "根据任务复杂度分配给合适的 Agent:\n" + "- 简单问答 → SimpleAgent\n" + "- 复杂推理 → ReasoningAgent\n" + "- 代码相关 → CodeAgent" + ), + sub_agents=[simple_agent, reasoning_agent, code_agent], # 子 Agent +) +``` + +--- + +## 10.5 性能优化技巧 + +### 10.5.1 指令优化 + +```python +""" +指令优化技巧 +减少 token 使用,提高响应速度 +""" + +# ❌ 冗长的指令(浪费 token) +verbose_instruction = """ +你是一个非常专业的、经验丰富的、知识渊博的助手。 +你擅长回答各种各样的问题,包括但不限于技术问题、 +生活问题、工作问题、学习问题等等。 +当用户问你问题时,你应该: +1. 首先理解用户的问题 +2. 然后分析问题 +3. 最后给出答案 +请始终使用中文回答。 +""" + +# ✅ 精简的指令(节省 token) +concise_instruction = """ +你是专业助手。用中文简洁回答。 +""" + +# ✅ 使用结构化指令 +structured_instruction = """ +## 角色 +技术助手 + +## 规则 +1. 使用中文 +2. 简洁回答(<200字) +3. 不确定时说"不确定" +""" +``` + +### 10.5.2 工具优化 + +```python +""" +工具优化技巧 +减少不必要的工具调用 +""" + +# ❌ 工具描述过于详细(浪费 token) +def bad_tool(query: str) -> dict: + """ + 这是一个非常详细的工具描述,包含了大量的信息, + 但是大部分信息对 LLM 来说是不必要的。 + LLM 需要读取所有这些文本来理解工具的功能, + 这会消耗大量的 token 并降低响应速度。 + """ + return {"result": "done"} + +# ✅ 精简的工具描述 +def good_tool(query: str) -> dict: + """搜索知识库并返回相关文档片段。""" + return {"result": "done"} + +# ✅ 合并相似工具 +# 如果有多个功能相似的工具,考虑合并 +def unified_search( + query: str, # 搜索关键词 + search_type: str = "all", # 搜索类型:all/doc/faq +) -> dict: + """ + 统一搜索工具 + + Args: + query: 搜索关键词 + search_type: 搜索类型,可选 "all"(全部)、"doc"(文档)、"faq"(FAQ) + """ + return {"status": "success", "results": []} +``` + +--- + +## 10.6 完整实战项目:智能客服系统 + +```python +""" +完整实战项目:多智能体客服系统 +整合本教程所有知识点 +""" + +from google.adk.agents import ( # 导入所有 Agent 类型 + Agent, # LLM Agent + LlmAgent, # LLM Agent(完整名) + SequentialAgent, # 顺序工作流 + ParallelAgent, # 并行工作流 + LoopAgent, # 循环工作流 + BaseAgent, # 基础 Agent +) +from google.adk.events import Event, EventActions # 导入事件类型 +from google.adk.agents.invocation_context import InvocationContext # 导入上下文 +from google.adk.tools import google_search # 导入 Google 搜索 +from typing import AsyncGenerator # 导入异步生成器 +import asyncio # 导入异步库 + + +# ======================================== +# 第一步:定义工具 +# ======================================== + +def search_faq(query: str) -> dict: + """搜索常见问题解答""" + faq_db = { # FAQ 数据库 + "退款": "退款将在3-5个工作日内处理完成。", # 退款 + "配送": "标准配送3-5天,加急配送1-2天。", # 配送 + "退换货": "7天无理由退换货,请保持商品完好。", # 退换货 + } + for key, value in faq_db.items(): # 遍历 FAQ + if key in query: # 如果匹配 + return {"status": "success", "answer": value} # 返回答案 + return {"status": "not_found", "answer": "未找到相关FAQ。"} # 未找到 + + +def create_ticket( + category: str, # 工单类别 + description: str, # 问题描述 + priority: str = "normal", # 优先级 +) -> dict: + """创建客户工单""" + ticket_id = f"TK{len(description)}" # 生成工单号 + return { # 返回工单信息 + "status": "success", + "ticket_id": ticket_id, + "message": f"工单 {ticket_id} 已创建,我们会尽快处理。", + } + + +# ======================================== +# 第二步:定义子 Agent +# ======================================== + +# FAQ Agent:处理常见问题 +faq_agent = LlmAgent( + name="FAQBot", # FAQ 机器人 + model="gemini-2.0-flash", # 快速模型 + description="处理常见问题,如退款、配送、退换货等。", # 描述 + instruction="你是FAQ助手,使用 search_faq 工具查找答案。", # 指令 + tools=[search_faq], # FAQ 搜索工具 +) + +# 搜索 Agent:处理需要联网查询的问题 +search_agent = LlmAgent( + name="SearchBot", # 搜索机器人 + model="gemini-2.0-flash", # 快速模型 + description="处理需要搜索互联网的问题。", # 描述 + instruction="使用 Google Search 搜索最新信息。", # 指令 + tools=[google_search], # Google 搜索工具 +) + +# 工单 Agent:处理需要人工介入的问题 +ticket_agent = LlmAgent( + name="TicketBot", # 工单机器人 + model="gemini-2.0-flash", # 快速模型 + description="创建客户工单,转交人工处理。", # 描述 + instruction="收集用户问题描述,使用 create_ticket 创建工单。", # 指令 + tools=[create_ticket], # 创建工单工具 +) + +# ======================================== +# 第三步:定义协调器 +# ======================================== + +coordinator = LlmAgent( + name="CustomerServiceCoordinator", # 客服协调器 + model="gemini-2.0-flash", # 快速模型 + description="智能客服主协调器,负责路由用户请求。", # 描述 + instruction=( # 系统指令 + "你是一个智能客服协调器。\n" + "根据用户问题类型,分配给合适的子 Agent:\n" + "- 退款、配送、退换货等常见问题 → FAQBot\n" + "- 需要最新信息的问题 → SearchBot\n" + "- 无法自动解决的问题 → TicketBot\n" + "使用 transfer_to_agent 进行任务分配。" + ), + sub_agents=[ # 注册子 Agent + faq_agent, # FAQ 机器人 + search_agent, # 搜索机器人 + ticket_agent, # 工单机器人 + ], +) + +# ======================================== +# 第四步:添加监控回调 +# ======================================== + +async def monitoring_callback(cb_ctx, inv_ctx): + """监控回调:记录所有操作""" + agent_name = inv_ctx.agent.name # 获取 Agent 名称 + print(f"[监控] Agent '{agent_name}' 正在处理请求") # 打印日志 + + +coordinator.before_model_callback = monitoring_callback # 添加监控 + +# ======================================== +# 第五步:设置 root_agent +# ======================================== + +root_agent = coordinator # 设置根 Agent + +print("✅ 智能客服系统构建完成!") # 打印成功信息 +print(f"根 Agent: {root_agent.name}") # 打印根 Agent 名称 +print(f"子 Agent 数量: {len(root_agent.sub_agents)}") # 打印子 Agent 数量 +``` + +--- + +## 📌 本章小结 + +- 输入安全:检测注入攻击、限制输入长度、过滤敏感信息 +- 输出安全:过滤不当内容、记录审计日志 +- A2A 协议:实现不同 Agent 系统间的远程通信 +- 自定义 Agent:继承 BaseAgent 实现独特逻辑 +- 多模型策略:根据任务类型选择合适的模型 +- 性能优化:精简指令、合并工具、合理选择模型 + +--- + +## 🎉 教程完结 + +恭喜你完成了 Google ADK 完整教程的学习! + +### 学习路径回顾 + +``` +第01章 环境搭建 → 第02章 Hello World + ↓ +第03章 LLM Agent → 第04章 自定义工具 + ↓ +第05章 多智能体 → 第06章 会话状态 + ↓ +第07章 回调机制 → 第08章 评估 + ↓ +第09章 部署 → 第10章 高级主题 +``` + +### 后续学习建议 + +1. **实践项目**:基于教程知识,构建自己的 Agent 应用 +2. **阅读源码**:深入研究 [adk-python](https://github.com/google/adk-python) 源码 +3. **社区参与**:关注 ADK 的更新和社区讨论 +4. **探索生态**:了解 ADK 的 Java、Go 版本和 Web 版本 + +### 官方资源 + +- 📖 [官方文档](https://google.github.io/adk-docs/) +- 💻 [GitHub 仓库](https://github.com/google/adk-python) +- 📦 [示例代码](https://github.com/google/adk-samples) diff --git a/ADKLearning/google-adk-tutorial/README.md b/ADKLearning/google-adk-tutorial/README.md new file mode 100644 index 0000000..93521ca --- /dev/null +++ b/ADKLearning/google-adk-tutorial/README.md @@ -0,0 +1,90 @@ +# Google ADK 完整使用教程 + +> **Agent Development Kit (ADK)** — Google 开源的 AI 智能体开发框架 +> +> 版本:v1.8.0+ | 语言:Python | 许可证:Apache 2.0 + +--- + +## 📖 教程目录 + +### 第一部分:入门基础 + +| 章节 | 标题 | 说明 | +|------|------|------| +| [第01章](./01-introduction-and-setup.md) | ADK 简介与环境搭建 | 框架概述、核心特性、安装配置 | +| [第02章](./02-quick-start-hello-world.md) | 快速开始:Hello World | 创建第一个 Agent、运行与测试 | + +### 第二部分:核心开发 + +| 章节 | 标题 | 说明 | +|------|------|------| +| [第03章](./03-llm-agent-in-depth.md) | LLM 智能体详解 | Agent 配置、指令设计、模型选择 | +| [第04章](./04-custom-tools.md) | 自定义工具开发 | 函数工具、MCP 工具、OpenAPI 集成 | +| [第05章](./05-multi-agent-systems.md) | 多智能体系统 | 协调器模式、工作流编排、Agent 间通信 | +| [第06章](./06-session-and-state.md) | 会话与状态管理 | Session、State、Memory 机制详解 | + +### 第三部分:进阶与部署 + +| 章节 | 标题 | 说明 | +|------|------|------| +| [第07章](./07-callbacks-and-events.md) | 回调机制与事件系统 | Before/After 回调、事件处理、自定义逻辑 | +| [第08章](./08-evaluation.md) | 智能体评估 | 评估集设计、自动化测试、性能分析 | +| [第09章](./09-deployment.md) | 部署指南 | Docker 容器化、Cloud Run、Vertex AI Agent Engine | +| [第10章](./10-advanced-topics.md) | 高级主题 | 安全最佳实践、A2A 协议、多模型适配 | + +--- + +## 📂 代码文件说明 + +| 文件 | 对应章节 | 说明 | +|------|----------|------| +| `code/setup_demo.py` | 第01章 | 环境搭建与安装验证 | +| `code/hello_world.py` | 第02章 | Hello World 示例 | +| `code/llm_agent_demo.py` | 第03章 | LLM 智能体完整示例 | +| `code/custom_tools_demo.py` | 第04章 | 自定义工具开发示例 | +| `code/multi_agent_demo.py` | 第05章 | 多智能体系统示例 | +| `code/session_state_demo.py` | 第06章 | 会话与状态管理示例 | +| `code/callback_demo.py` | 第07章 | 回调机制示例 | +| `code/eval_demo.py` | 第08章 | 评估示例 | +| `code/deploy_demo.py` | 第09章 | 部署配置示例 | +| `code/advanced_demo.py` | 第10章 | 高级功能示例 | + +--- + +## 🚀 快速导航 + +### 我是新手,从哪里开始? +👉 从 [第01章](./01-introduction-and-setup.md) 开始,按顺序学习。 + +### 我只想快速体验? +👉 直接跳到 [第02章](./02-quick-start-hello-world.md) 的 Hello World。 + +### 我想构建多智能体系统? +👉 重点学习 [第05章](./05-multi-agent-systems.md)。 + +### 我想部署到生产环境? +👉 查看 [第09章](./09-deployment.md)。 + +--- + +## 🔗 官方资源 + +- **官方文档**:https://google.github.io/adk-docs/ +- **GitHub 仓库**:https://github.com/google/adk-python +- **示例代码**:https://github.com/google/adk-samples +- **Java 版本**:https://github.com/google/adk-java +- **Web 版本**:https://github.com/google/adk-web + +--- + +## 📋 前置要求 + +- **Python**:3.10 或更高版本(v1.19.0+ 要求) +- **pip**:Python 包管理器 +- **Google AI Studio API Key**:用于 Gemini 模型调用 +- **(可选)** 其他 LLM 的 API Key(如 OpenAI、DeepSeek 等) + +--- + +> 💡 **提示**:本教程所有代码示例均提供详细中文注释,建议配合代码文件一起阅读。 diff --git a/ADKLearning/google-adk-tutorial/code/advanced_demo.py b/ADKLearning/google-adk-tutorial/code/advanced_demo.py new file mode 100644 index 0000000..2a11b6a --- /dev/null +++ b/ADKLearning/google-adk-tutorial/code/advanced_demo.py @@ -0,0 +1,293 @@ +""" +Google ADK 高级主题完整示例 +展示安全、自定义 Agent、多模型等高级功能 + +对应教程:第10章 - 高级主题 +""" + +# 导入 ADK 核心模块 +from google.adk.agents import ( # 导入所有 Agent 类型 + Agent, # LLM Agent + LlmAgent, # LLM Agent(完整名) + BaseAgent, # 基础 Agent +) +from google.adk.events import Event, EventActions # 导入事件类型 +from google.adk.agents.invocation_context import InvocationContext # 导入上下文 +from google.adk.models.lite_llm import LiteLlm # LiteLLM 适配器 + +# 导入辅助模块 +from typing import AsyncGenerator # 异步生成器 +import re # 正则表达式 +import random # 随机数 +import asyncio # 异步编程 + + +# ======================================== +# 示例一:输入安全过滤 +# ======================================== + +async def input_security_filter(cb_ctx, inv_ctx): + """ + 输入安全过滤回调 + 检测注入攻击和敏感信息 + + Args: + cb_ctx: 回调上下文 + inv_ctx: 调用上下文 + """ + # 获取用户消息 + events = inv_ctx.session.events # 获取会话事件 + user_msgs = [ # 筛选用户消息 + e for e in events + if hasattr(e, 'content') and e.content.role == 'user' + ] + + if not user_msgs: # 如果没有用户消息 + return # 直接返回 + + text = user_msgs[-1].content.parts[0].text # 提取文本 + + # 检测注入攻击 + injection_patterns = [ # 注入模式 + r"忽略.*指令", # 中文注入 + r"ignore.*instruction", # 英文注入 + r"你现在是", # 角色切换 + r"system\s*:", # 系统提示伪造 + ] + + for pattern in injection_patterns: # 遍历模式 + if re.search(pattern, text, re.IGNORECASE): # 如果匹配 + print(f"[安全] ⚠️ 检测到可能的注入攻击") # 记录警告 + break # 跳出循环 + + # 检测敏感信息 + sensitive_patterns = [ # 敏感信息模式 + r"\b\d{3}-\d{2}-\d{4}\b", # SSN + r"\b\d{16}\b", # 信用卡号 + ] + + for pattern in sensitive_patterns: # 遍历模式 + if re.search(pattern, text): # 如果匹配 + print(f"[安全] ⚠️ 检测到可能的敏感信息") # 记录警告 + break # 跳出循环 + + # 检查输入长度 + if len(text) > 5000: # 如果过长 + print(f"[安全] ⚠️ 输入过长: {len(text)} 字符") # 记录警告 + + +# ======================================== +# 示例二:自定义 Agent — 规则引擎 +# ======================================== + +class RuleEngineAgent(BaseAgent): + """ + 规则引擎 Agent + 基于预定义规则处理请求,不使用 LLM + """ + + def __init__(self, rules: dict, **kwargs): + """ + 初始化规则引擎 + + Args: + rules: 规则字典 {匹配模式: 响应} + """ + super().__init__(**kwargs) # 调用父类初始化 + self.rules = rules # 保存规则 + + async def _run_async_impl( + self, + ctx: InvocationContext, # 调用上下文 + ) -> AsyncGenerator[Event, None]: # 返回事件生成器 + """执行规则匹配""" + + # 获取用户消息 + events = ctx.session.events # 获取事件 + user_events = [ # 筛选用户消息 + e for e in events + if hasattr(e, 'content') and e.content.role == 'user' + ] + + if not user_events: # 如果没有消息 + yield Event( # 生成提示 + author=self.name, + content="请输入您的问题。", + ) + return # 退出 + + text = user_events[-1].content.parts[0].text # 提取文本 + + # 匹配规则 + for pattern, response in self.rules.items(): # 遍历规则 + if pattern.lower() in text.lower(): # 如果匹配 + yield Event( # 生成响应 + author=self.name, + content=response, + ) + return # 退出 + + # 没有匹配的规则 + yield Event( # 生成默认响应 + author=self.name, + content="抱歉,我无法处理您的请求。请尝试其他问题。", + ) + + +# ======================================== +# 示例三:自定义 Agent — 随机路由 +# ======================================== + +class RandomRouter(BaseAgent): + """ + 随机路由 Agent + 随机选择一个子 Agent 处理请求 + """ + + async def _run_async_impl( + self, + ctx: InvocationContext, # 调用上下文 + ) -> AsyncGenerator[Event, None]: # 返回事件生成器 + """随机选择子 Agent""" + + sub_agents = self.sub_agents # 获取子 Agent + + if not sub_agents: # 如果没有子 Agent + yield Event( # 生成错误 + author=self.name, + content="没有可用的子 Agent。", + ) + return # 退出 + + chosen = random.choice(sub_agents) # 随机选择 + print(f"[随机路由] 选择了 {chosen.name}") # 打印选择 + + yield Event( # 生成委派事件 + author=self.name, + actions=EventActions( + transfer_to_agent=chosen.name, # 委派 + ), + ) + + +# ======================================== +# 示例四:多模型混合策略 +# ======================================== + +# 简单任务:使用快速模型 +simple_agent = LlmAgent( + name="SimpleAgent", # 简单任务 Agent + model="gemini-2.0-flash", # 快速模型 + description="处理简单的问答和翻译。", # 描述 + instruction="快速准确地回答简单问题。", # 指令 +) + +# 复杂推理:使用强力模型 +reasoning_agent = LlmAgent( + name="ReasoningAgent", # 推理 Agent + model="gemini-2.5-pro-preview-05-06", # 强力模型 + description="处理需要深度推理的复杂任务。", # 描述 + instruction="仔细分析,给出深入的推理过程。", # 指令 +) + +# 代码生成:使用 DeepSeek +code_agent = LlmAgent( + name="CodeAgent", # 代码 Agent + model=LiteLlm( # 使用 LiteLLM + model="deepseek/deepseek-coder", # DeepSeek Coder + api_key="your_api_key", # API Key + ), + description="处理代码生成和调试。", # 描述 + instruction="编写高质量的代码。", # 指令 +) + +# 协调器:使用快速模型 +multi_model_coordinator = LlmAgent( + name="MultiModelCoordinator", # 多模型协调器 + model="gemini-2.0-flash", # 快速模型 + instruction=( # 指令 + "根据任务类型分配给合适的 Agent:\n" + "- 简单问答 → SimpleAgent\n" + "- 复杂推理 → ReasoningAgent\n" + "- 代码相关 → CodeAgent" + ), + sub_agents=[simple_agent, reasoning_agent, code_agent], # 子 Agent +) + + +# ======================================== +# 示例五:完整实战 — 智能客服系统 +# ======================================== + +def search_faq(query: str) -> dict: + """搜索 FAQ""" + faq_db = { # FAQ 数据库 + "退款": "退款将在3-5个工作日内处理完成。", + "配送": "标准配送3-5天,加急配送1-2天。", + "退换货": "7天无理由退换货,请保持商品完好。", + } + for key, value in faq_db.items(): # 遍历 FAQ + if key in query: # 如果匹配 + return {"status": "success", "answer": value} + return {"status": "not_found"} + + +def create_ticket(category: str, description: str) -> dict: + """创建工单""" + ticket_id = f"TK{hash(description) % 10000:04d}" # 生成工单号 + return { # 返回结果 + "status": "success", + "ticket_id": ticket_id, + "message": f"工单 {ticket_id} 已创建。", + } + + +# FAQ Agent +faq_agent = LlmAgent( + name="FAQBot", # FAQ 机器人 + model="gemini-2.0-flash", # 模型 + description="处理常见问题。", # 描述 + instruction="使用 search_faq 工具查找答案。", # 指令 + tools=[search_faq], # FAQ 工具 +) + +# 工单 Agent +ticket_agent = LlmAgent( + name="TicketBot", # 工单机器人 + model="gemini-2.0-flash", # 模型 + description="创建客户工单。", # 描述 + instruction="使用 create_ticket 创建工单。", # 指令 + tools=[create_ticket], # 工单工具 +) + +# 客服协调器 +customer_service = LlmAgent( + name="CustomerService", # 客服系统 + model="gemini-2.0-flash", # 模型 + description="智能客服主协调器。", # 描述 + instruction=( # 指令 + "你是智能客服协调器。\n" + "常见问题 → FAQBot\n" + "无法解决 → TicketBot\n" + "使用 transfer_to_agent 委派任务。" + ), + sub_agents=[faq_agent, ticket_agent], # 子 Agent + before_model_callback=input_security_filter, # 安全过滤 +) + + +# ======================================== +# 打印信息 +# ======================================== + +if __name__ == "__main__": + print("=" * 60) # 分隔线 + print("Google ADK 高级主题示例") # 标题 + print("=" * 60) # 分隔线 + + print("\n🔒 安全过滤回调: input_security_filter") # 安全 + print("🤖 自定义 Agent: RuleEngineAgent, RandomRouter") # 自定义 + print("🔀 多模型策略: SimpleAgent + ReasoningAgent + CodeAgent") # 多模型 + print("🏢 实战项目: 智能客服系统 (CustomerService)") # 实战 + + print("\n✅ 所有高级功能定义完成!") # 成功信息 diff --git a/ADKLearning/google-adk-tutorial/code/callback_demo.py b/ADKLearning/google-adk-tutorial/code/callback_demo.py new file mode 100644 index 0000000..ba746f8 --- /dev/null +++ b/ADKLearning/google-adk-tutorial/code/callback_demo.py @@ -0,0 +1,255 @@ +""" +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()) # 执行主函数 diff --git a/ADKLearning/google-adk-tutorial/code/custom_tools_demo.py b/ADKLearning/google-adk-tutorial/code/custom_tools_demo.py new file mode 100644 index 0000000..66d1d7b --- /dev/null +++ b/ADKLearning/google-adk-tutorial/code/custom_tools_demo.py @@ -0,0 +1,338 @@ +""" +Google ADK 自定义工具开发完整示例 +展示各种工具开发技巧和最佳实践 + +对应教程:第04章 - 自定义工具开发 +""" + +# 导入 ADK 核心模块 +from google.adk.agents import Agent # Agent 类 +from google.adk.tools import AgentTool # AgentTool(Agent 作为工具) + +# 导入类型提示 +from typing import Optional, List # 可选类型和列表类型 + +# 导入运行相关模块 +from google.adk.runners import Runner # 运行器 +from google.adk.sessions import InMemorySessionService # 会话服务 +from google.genai import types # 类型定义 + +# 导入异步库 +import asyncio # 异步编程库 + + +# ======================================== +# 示例一:基础函数工具 +# ======================================== + +def get_weather(city: str) -> dict: + """ + 获取指定城市的天气信息 + + Args: + city (str): 城市名称,例如"北京"、"上海" + + Returns: + dict: 包含天气状态的字典 + - status: "success" 或 "error" + - city: 城市名称 + - temperature: 温度 + - condition: 天气状况 + """ + # 模拟天气数据 + weather_data = { # 天气数据库 + "北京": {"temp": "25°C", "condition": "晴天"}, # 北京 + "上海": {"temp": "28°C", "condition": "多云"}, # 上海 + "广州": {"temp": "32°C", "condition": "雷阵雨"}, # 广州 + } + + # 查找城市天气 + data = weather_data.get(city) # 获取天气数据 + + if not data: # 如果找不到 + return { # 返回错误 + "status": "error", + "error_message": f"未找到'{city}'的天气信息" + } + + return { # 返回成功结果 + "status": "success", + "city": city, + "temperature": data["temp"], + "condition": data["condition"], + } + + +# ======================================== +# 示例二:带可选参数的工具 +# ======================================== + +def search_restaurant( + city: str, # 必选参数 + cuisine: str = "中餐", # 可选参数,默认中餐 + price_range: str = "中等", # 可选参数,默认中等 +) -> dict: + """ + 搜索指定城市的餐厅 + + Args: + city (str): 城市名称 + cuisine (str, optional): 菜系类型,默认"中餐" + price_range (str, optional): 价格范围,默认"中等" + + Returns: + dict: 搜索结果 + """ + return { # 返回搜索结果 + "status": "success", + "city": city, + "cuisine": cuisine, + "price_range": price_range, + "results": [ # 餐厅列表 + {"name": "美味餐厅", "rating": 4.5, "price": "人均100元"}, + {"name": "佳肴轩", "rating": 4.2, "price": "人均80元"}, + ], + } + + +# ======================================== +# 示例三:使用 Optional 类型 +# ======================================== + +def create_user_profile( + username: str, # 必选参数 + bio: Optional[str] = None, # 可选参数 + avatar_url: Optional[str] = None, # 可选参数 +) -> dict: + """ + 创建用户档案 + + Args: + username (str): 用户名 + bio (str, optional): 个人简介 + avatar_url (str, optional): 头像 URL + + Returns: + dict: 创建结果 + """ + profile = { # 创建档案 + "username": username, # 用户名 + "bio": bio or "这个人很懒,什么都没写。", # 简介 + "avatar_url": avatar_url or "", # 头像 + } + + return { # 返回结果 + "status": "success", + "profile": profile, + } + + +# ======================================== +# 示例四:List 类型参数 +# ======================================== + +def compare_cities(cities: List[str]) -> dict: + """ + 比较多个城市的信息 + + Args: + cities (List[str]): 城市名称列表 + + Returns: + dict: 比较结果 + """ + city_info = { # 城市信息 + "北京": {"population": "2189万", "area": "16410km²"}, + "上海": {"population": "2487万", "area": "6341km²"}, + "广州": {"population": "1881万", "area": "7434km²"}, + } + + results = [] # 结果列表 + for city in cities: # 遍历城市 + info = city_info.get(city) # 获取信息 + if info: # 如果存在 + results.append({"city": city, **info}) # 添加到结果 + + return { # 返回结果 + "status": "success" if results else "error", + "results": results, + } + + +# ======================================== +# 示例五:健壮的工具(完善的错误处理) +# ======================================== + +def calculate_loan( + principal: float, # 贷款本金 + annual_rate: float, # 年利率 + years: int, # 贷款年限 +) -> dict: + """ + 计算贷款月供和总利息 + + Args: + principal (float): 贷款本金(元) + annual_rate (float): 年利率(百分比,如 4.5) + years (int): 贷款年限 + + Returns: + dict: 计算结果 + """ + # 参数验证 + if principal <= 0: # 本金必须大于0 + return { # 返回错误 + "status": "error", + "error_code": "INVALID_PRINCIPAL", + "error_message": "贷款本金必须大于0。" + } + + if annual_rate <= 0 or annual_rate > 30: # 利率范围检查 + return { # 返回错误 + "status": "error", + "error_code": "INVALID_RATE", + "error_message": "年利率必须在 0-30% 之间。" + } + + if years <= 0 or years > 30: # 年限范围检查 + return { # 返回错误 + "status": "error", + "error_code": "INVALID_YEARS", + "error_message": "贷款年限必须在 1-30 年之间。" + } + + try: # 尝试计算 + monthly_rate = annual_rate / 100 / 12 # 月利率 + total_months = years * 12 # 总月数 + + # 等额本息月供公式 + if monthly_rate == 0: # 如果利率为0 + monthly_payment = principal / total_months # 直接除 + else: # 正常计算 + monthly_payment = ( # 月供计算 + principal * monthly_rate * (1 + monthly_rate) ** total_months + / ((1 + monthly_rate) ** total_months - 1) + ) + + total_amount = monthly_payment * total_months # 总还款额 + total_interest = total_amount - principal # 总利息 + + return { # 返回计算结果 + "status": "success", + "monthly_payment": round(monthly_payment, 2), # 月供 + "total_interest": round(total_interest, 2), # 总利息 + "total_amount": round(total_amount, 2), # 总还款额 + } + + except Exception as e: # 捕获计算异常 + return { # 返回错误 + "status": "error", + "error_code": "CALCULATION_ERROR", + "error_message": f"计算出错:{str(e)}" + } + + +# ======================================== +# 示例六:Agent-as-a-Tool +# ======================================== + +# 定义一个翻译 Agent +translator_agent = Agent( + name="translator", # 翻译 Agent + model="gemini-2.0-flash", # 模型 + instruction="你是翻译专家,将文本翻译成目标语言。只输出翻译结果。", # 指令 +) + +# 将翻译 Agent 包装为工具 +translation_tool = AgentTool( + agent=translator_agent, # 传入翻译 Agent +) + + +# ======================================== +# 创建综合 Agent +# ======================================== + +comprehensive_agent = Agent( + name="comprehensive_agent", # Agent 名称 + model="gemini-2.0-flash", # 模型 + instruction=( # 系统指令 + "你是一个多功能助手。\n" + "你可以:\n" + "1. 查询天气(get_weather)\n" + "2. 搜索餐厅(search_restaurant)\n" + "3. 创建用户档案(create_user_profile)\n" + "4. 比较城市(compare_cities)\n" + "5. 计算贷款(calculate_loan)\n" + "6. 翻译文本(translation_tool)\n" + "根据用户需求选择合适的工具。" + ), + tools=[ # 注册所有工具 + get_weather, # 天气查询 + search_restaurant, # 餐厅搜索 + create_user_profile, # 用户档案 + compare_cities, # 城市比较 + calculate_loan, # 贷款计算 + translation_tool, # 翻译工具 + ], +) + + +# ======================================== +# 运行示例 +# ======================================== + +APP_NAME = "tools_demo" # 应用名称 +USER_ID = "user_001" # 用户 ID +SESSION_ID = "session_001" # 会话 ID + + +async def main(): + """运行工具演示""" + + # 创建会话服务 + session_service = InMemorySessionService() # 会话服务 + await session_service.create_session( # 创建会话 + app_name=APP_NAME, # 应用名称 + user_id=USER_ID, # 用户 ID + session_id=SESSION_ID, # 会话 ID + ) + + # 创建运行器 + runner = Runner( + agent=comprehensive_agent, # Agent + app_name=APP_NAME, # 应用名称 + session_service=session_service, # 会话服务 + ) + + # 测试查询 + queries = [ # 测试问题列表 + "北京天气怎么样?", # 天气查询 + "帮我搜索上海的意大利餐厅", # 餐厅搜索 + "贷款100万,利率4.5%,30年,月供多少?", # 贷款计算 + ] + + for query in queries: # 遍历测试问题 + print(f"\n[用户]: {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()) # 执行主函数 diff --git a/ADKLearning/google-adk-tutorial/code/deploy_demo.py b/ADKLearning/google-adk-tutorial/code/deploy_demo.py new file mode 100644 index 0000000..43157d8 --- /dev/null +++ b/ADKLearning/google-adk-tutorial/code/deploy_demo.py @@ -0,0 +1,201 @@ +""" +Google ADK 部署配置完整示例 +展示各种部署方式的配置 + +对应教程:第09章 - 部署指南 +""" + +# 导入 ADK 核心模块 +from google.adk.agents import Agent # Agent 类 +from google.adk.tools import google_search # Google 搜索工具 + + +# ======================================== +# 定义 Agent(部署入口) +# ======================================== + +def get_weather(city: str) -> dict: + """ + 获取天气信息 + + Args: + city (str): 城市名称 + + Returns: + 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} # 返回天气 + + +# root_agent 是 ADK 的入口点 +# 部署时 ADK 会自动查找这个变量 +root_agent = Agent( + name="deploy_agent", # Agent 名称 + model="gemini-2.0-flash", # 模型 + description="部署演示 Agent。", # 描述 + instruction=( # 系统指令 + "你是一个多功能助手。\n" + "可以查询天气和搜索信息。\n" + "使用中文回答。" + ), + tools=[get_weather, google_search], # 注册工具 +) + + +# ======================================== +# 以下为部署配置参考 +# ======================================== + +# ---------------------------------------- +# Dockerfile 内容(保存为 Dockerfile) +# ---------------------------------------- +""" +# 使用 Python 3.11 作为基础镜像 +FROM python:3.11-slim + +# 设置工作目录 +WORKDIR /app + +# 设置环境变量 +ENV PYTHONUNBUFFERED=1 + +# 复制依赖文件 +COPY requirements.txt . + +# 安装依赖 +RUN pip install --no-cache-dir -r requirements.txt + +# 复制 Agent 代码 +COPY deploy_demo.py . + +# 暴露端口 +EXPOSE 8080 + +# 启动 API Server +CMD ["adk", "api_server", "--port", "8080", "--host", "0.0.0.0"] +""" + +# ---------------------------------------- +# requirements.txt 内容 +# ---------------------------------------- +""" +google-adk>=1.0.0 +""" + +# ---------------------------------------- +# start.sh 启动脚本 +# ---------------------------------------- +""" +#!/bin/bash +# ADK Agent 启动脚本 + +# 从环境变量读取 API Key +export GOOGLE_API_KEY="${GOOGLE_API_KEY}" + +# 启动 API Server +adk api_server \\ + --port 8080 \\ + --host 0.0.0.0 +""" + +# ---------------------------------------- +# docker-compose.yml 内容 +# ---------------------------------------- +""" +version: '3.8' + +services: + adk-agent: + build: . + ports: + - "8080:8080" + environment: + - GOOGLE_API_KEY=${GOOGLE_API_KEY} + restart: unless-stopped +""" + +# ---------------------------------------- +# 部署命令参考 +# ---------------------------------------- +""" +# 构建镜像 +docker build -t my-adk-agent . + +# 运行容器 +docker run -d \\ + --name my-agent \\ + -p 8080:8080 \\ + -e GOOGLE_API_KEY="your_api_key" \\ + my-adk-agent + +# 使用 adk deploy 部署到 Cloud Run +adk deploy . --platform cloud-run + +# 使用 adk deploy 部署到 Vertex AI +adk deploy . --platform vertex-ai +""" + + +# ======================================== +# API 调用示例 +# ======================================== + +def api_call_example(): + """ + 调用 ADK API Server 的示例代码 + 需要先启动 API Server: adk api_server --port 8080 + """ + + import requests # HTTP 请求库 + import json # JSON 处理 + + API_BASE = "http://localhost:8080" # API 地址 + APP_NAME = "deploy_agent" # 应用名称 + USER_ID = "user_001" # 用户 ID + SESSION_ID = "session_001" # 会话 ID + + # 创建会话 + session_url = f"{API_BASE}/apps/{APP_NAME}/users/{USER_ID}/sessions" + response = requests.post( # 发送请求 + session_url, # URL + json={"session_id": SESSION_ID}, # 请求体 + ) + print(f"会话创建: {response.json()}") # 打印结果 + + # 发送消息 + run_url = f"{session_url}/{SESSION_ID}:run" + payload = { # 请求体 + "user_id": USER_ID, # 用户 ID + "session_id": SESSION_ID, # 会话 ID + "new_message": { # 新消息 + "role": "user", # 角色 + "parts": [{"text": "北京天气怎么样?"}], # 内容 + }, + } + + response = requests.post( # 发送请求 + run_url, # URL + json=payload, # 请求体 + stream=True, # 流式响应 + ) + + for line in response.iter_lines(): # 逐行读取 + if line: # 如果有内容 + data = json.loads(line) # 解析 JSON + print(f"事件: {data}") # 打印事件 + + +if __name__ == "__main__": + print("✅ 部署配置示例") # 打印信息 + print("请参考文件中的注释配置部署。") # 提示 + print("\n快速启动命令:") # 打印命令 + print(" adk api_server --port 8080 # 启动 API Server") + print(" adk web --port 8000 # 启动 Web UI") + print(" adk deploy . --platform cloud-run # 部署到 Cloud Run") diff --git a/ADKLearning/google-adk-tutorial/code/eval_demo.py b/ADKLearning/google-adk-tutorial/code/eval_demo.py new file mode 100644 index 0000000..4f559d8 --- /dev/null +++ b/ADKLearning/google-adk-tutorial/code/eval_demo.py @@ -0,0 +1,272 @@ +""" +Google ADK 智能体评估完整示例 +展示评估集创建和评估运行方法 + +对应教程:第08章 - 智能体评估 +""" + +# 导入 ADK 核心模块 +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 # 异步编程库 +import json # JSON 处理 + + +# ======================================== +# 定义被评估的 Agent +# ======================================== + +def get_weather(city: str) -> dict: + """ + 获取天气信息 + + Args: + city (str): 城市名称 + + Returns: + dict: 天气信息 + """ + weather_data = { # 天气数据 + "北京": {"temp": "25°C", "condition": "晴天"}, + "上海": {"temp": "28°C", "condition": "多云"}, + "广州": {"temp": "32°C", "condition": "雷阵雨"}, + } + data = weather_data.get(city) # 查找数据 + if not data: # 如果找不到 + return {"status": "error", "error_message": f"未找到'{city}'的天气信息"} + return {"status": "success", "city": city, **data} # 返回天气 + + +agent = Agent( + name="weather_agent", # Agent 名称 + model="gemini-2.0-flash", # 模型 + instruction="你是天气助手,使用 get_weather 工具查询天气。用中文回答。", # 指令 + tools=[get_weather], # 工具 +) + + +# ======================================== +# 示例一:创建评估集 +# ======================================== + +def create_eval_set(): + """ + 创建评估集文件 + 生成 .evalset.json 文件供 adk eval 使用 + """ + + # 定义评估用例 + eval_cases = [ # 用例列表 + { + "case_id": "weather_beijing", # 用例 ID + "description": "测试北京天气查询", # 描述 + "user_input": "北京今天天气怎么样?", # 用户输入 + "expected_keywords": ["北京", "天气"], # 期望关键词 + }, + { + "case_id": "weather_shanghai", # 用例 ID + "description": "测试上海天气查询", # 描述 + "user_input": "帮我查一下上海的天气", # 用户输入 + "expected_keywords": ["上海"], # 期望关键词 + }, + { + "case_id": "greeting", # 用例 ID + "description": "测试问候功能", # 描述 + "user_input": "你好!", # 用户输入 + "expected_keywords": ["你好"], # 期望关键词 + }, + { + "case_id": "unknown_city", # 用例 ID + "description": "测试未知城市处理", # 描述 + "user_input": "查询月球基地的天气", # 用户输入 + "expected_keywords": ["找不到", "无法", "不支持"], # 期望关键词 + }, + { + "case_id": "multiple_cities", # 用例 ID + "description": "测试多城市查询", # 描述 + "user_input": "北京和上海的天气对比", # 用户输入 + "expected_keywords": ["北京", "上海"], # 期望关键词 + }, + ] + + # 构建评估集 + eval_set = { # 评估集对象 + "eval_cases": eval_cases, # 用例列表 + } + + # 写入文件 + filepath = "weather_agent_eval_set.evalset.json" # 文件路径 + with open(filepath, "w", encoding="utf-8") as f: # 打开文件 + json.dump( # 写入 JSON + eval_set, # 数据 + f, # 文件对象 + ensure_ascii=False, # 允许中文 + indent=2, # 格式化 + ) + + print(f"✅ 评估集已创建: {filepath}") # 打印成功信息 + print(f" 用例数量: {len(eval_cases)}") # 打印用例数 + return filepath # 返回文件路径 + + +# ======================================== +# 示例二:代码方式运行评估 +# ======================================== + +async def evaluate_agent(test_cases: list) -> dict: + """ + 评估 Agent + + Args: + test_cases: 测试用例列表 + + Returns: + dict: 评估结果汇总 + """ + # 初始化服务 + session_service = InMemorySessionService() # 会话服务 + runner = Runner( # 运行器 + agent=agent, # Agent + app_name="eval_app", # 应用名称 + session_service=session_service, # 会话服务 + ) + + results = [] # 结果列表 + passed = 0 # 通过计数 + + for i, case in enumerate(test_cases): # 遍历用例 + case_id = case.get("case_id", f"case_{i}") # 用例 ID + print(f"\n📋 测试用例: {case_id}") # 打印用例 ID + print(f" 输入: {case['user_input']}") # 打印输入 + + # 创建独立会话 + session_id = f"eval_session_{i}" # 会话 ID + await session_service.create_session( # 创建会话 + app_name="eval_app", # 应用名称 + user_id="eval_user", # 用户 ID + session_id=session_id, # 会话 ID + ) + + # 构造消息 + content = types.Content( + role='user', # 角色 + parts=[types.Part(text=case["user_input"])], # 内容 + ) + + # 运行 Agent + events = runner.run_async( + user_id="eval_user", # 用户 ID + session_id=session_id, # 会话 ID + new_message=content, # 消息 + ) + + # 收集响应 + response_text = "" # 初始化响应 + async for event in events: # 遍历事件 + if event.is_final_response(): # 最终响应 + response_text = event.content.parts[0].text # 提取文本 + + # 检查期望关键词 + case_passed = True # 默认通过 + failure_reason = "" # 失败原因 + + if "expected_keywords" in case: # 如果有关键词要求 + missing = [] # 缺失的关键词 + for kw in case["expected_keywords"]: # 遍历关键词 + if kw not in response_text: # 如果缺失 + missing.append(kw) # 记录缺失 + if missing: # 如果有缺失 + case_passed = False # 标记失败 + failure_reason = f"缺少关键词: {missing}" # 失败原因 + + # 检查不应出现的关键词 + if "not_expected_keywords" in case: # 如果有排除关键词 + for kw in case["not_expected_keywords"]: # 遍历 + if kw in response_text: # 如果出现 + case_passed = False # 标记失败 + failure_reason = f"不应包含关键词: '{kw}'" # 失败原因 + + if case_passed: # 如果通过 + passed += 1 # 递增通过数 + print(f" ✅ 通过") # 打印通过 + else: # 如果失败 + print(f" ❌ 失败: {failure_reason}") # 打印失败原因 + + # 记录结果 + results.append({ # 添加结果 + "case_id": case_id, # 用例 ID + "status": "passed" if case_passed else "failed", # 状态 + "response": response_text[:200], # 响应(截断) + "reason": failure_reason, # 失败原因 + }) + + # 返回汇总 + return { # 返回结果 + "total": len(test_cases), # 总数 + "passed": passed, # 通过数 + "failed": len(test_cases) - passed, # 失败数 + "pass_rate": passed / len(test_cases) if test_cases else 0, # 通过率 + "results": results, # 详细结果 + } + + +# ======================================== +# 示例三:打印评估报告 +# ======================================== + +def print_report(result: dict): + """ + 打印评估报告 + + Args: + result: 评估结果 + """ + print("\n" + "=" * 60) # 分隔线 + print("📊 评估报告") # 标题 + print("=" * 60) # 分隔线 + print(f"总用例数: {result['total']}") # 总数 + print(f"通过: {result['passed']} ✅") # 通过数 + print(f"失败: {result['failed']} ❌") # 失败数 + print(f"通过率: {result['pass_rate']:.1%}") # 通过率 + print("-" * 60) # 分隔线 + + for r in result["results"]: # 遍历详细结果 + icon = "✅" if r["status"] == "passed" else "❌" # 状态图标 + print(f" {icon} {r['case_id']}: {r['status']}") # 打印结果 + if r["reason"]: # 如果有失败原因 + print(f" 原因: {r['reason']}") # 打印原因 + if r["status"] == "passed": # 如果通过 + print(f" 响应: {r['response'][:100]}...") # 打印响应片段 + + print("=" * 60) # 分隔线 + + +# ======================================== +# 主函数 +# ======================================== + +async def main(): + """主函数""" + + # 第一步:创建评估集 + filepath = create_eval_set() # 创建评估集文件 + + # 第二步:加载评估用例 + with open(filepath, "r", encoding="utf-8") as f: # 读取文件 + eval_set = json.load(f) # 解析 JSON + + test_cases = eval_set["eval_cases"] # 获取用例列表 + + # 第三步:运行评估 + result = await evaluate_agent(test_cases) # 执行评估 + + # 第四步:打印报告 + print_report(result) # 打印评估报告 + + +if __name__ == "__main__": # 直接运行 + asyncio.run(main()) # 执行主函数 diff --git a/ADKLearning/google-adk-tutorial/code/hello_world.py b/ADKLearning/google-adk-tutorial/code/hello_world.py new file mode 100644 index 0000000..d707a40 --- /dev/null +++ b/ADKLearning/google-adk-tutorial/code/hello_world.py @@ -0,0 +1,137 @@ +""" +Google ADK Hello World 示例 +一个带有自定义工具的完整 Agent 示例 + +对应教程:第02章 - 快速开始:Hello World +运行方式:adk run hello_world(将此文件放在 hello_world/agent.py 中) +""" + +# 导入 ADK 的 Agent 类 +from google.adk.agents import Agent # Agent 是 LlmAgent 的别名 + +# 导入标准库:日期时间处理 +import datetime # 用于获取当前时间 +from zoneinfo import ZoneInfo # 用于处理时区信息 + + +# ======================================== +# 定义自定义工具函数 +# ======================================== + +def get_current_time(city: str) -> dict: + """ + 获取指定城市的当前时间 + + 这个函数会被 ADK 自动转换为工具(FunctionTool), + Agent 可以在对话中调用这个工具来获取时间信息。 + + Args: + city (str): 需要查询时间的城市名称,例如"北京"、"东京"、"纽约" + + Returns: + dict: 包含状态和结果的字典 + - status: "success" 或 "error" + - report: 时间信息或错误消息 + """ + # 定义城市到时区的映射字典 + city_timezone_map = { # 常见城市对应的时区 + "北京": "Asia/Shanghai", # 北京使用上海时区 + "上海": "Asia/Shanghai", # 上海使用上海时区 + "广州": "Asia/Shanghai", # 广州使用上海时区 + "深圳": "Asia/Shanghai", # 深圳使用上海时区 + "东京": "Asia/Tokyo", # 东京时区 + "首尔": "Asia/Seoul", # 首尔时区 + "纽约": "America/New_York", # 纽约时区 + "伦敦": "Europe/London", # 伦敦时区 + "巴黎": "Europe/Paris", # 巴黎时区 + "洛杉矶": "America/Los_Angeles", # 洛杉矶时区 + } + + # 查找城市对应的时区标识符 + tz_identifier = city_timezone_map.get(city) # 从字典中获取时区 + + # 如果找不到该城市的时区信息 + if not tz_identifier: # 检查是否找到 + return { # 返回错误信息 + "status": "error", # 状态标记为错误 + "error_message": f"未找到'{city}'的时区信息,请尝试其他城市。" # 错误描述 + } + + # 根据时区标识符创建时区对象 + tz = ZoneInfo(tz_identifier) # 创建 ZoneInfo 时区对象 + + # 获取该时区的当前时间 + now = datetime.datetime.now(tz) # 获取指定时区的当前时间 + + # 格式化时间字符串 + time_str = now.strftime( # 将时间格式化为字符串 + "%Y-%m-%d %H:%M:%S %Z%z" # 格式:年-月-日 时:分:秒 时区 + ) + + # 返回成功结果 + return { # 返回包含时间信息的字典 + "status": "success", # 状态标记为成功 + "city": city, # 城市名称 + "time": time_str # 格式化后的时间字符串 + } + + +def get_weather(city: str) -> dict: + """ + 获取指定城市的天气信息(模拟数据) + + Args: + city (str): 城市名称 + + Returns: + dict: 包含天气信息的字典 + """ + # 模拟天气数据(实际应用中应调用真实天气 API) + weather_data = { # 模拟天气数据库 + "北京": {"temp": "25°C", "condition": "晴天", "humidity": "45%"}, # 北京 + "上海": {"temp": "28°C", "condition": "多云", "humidity": "65%"}, # 上海 + "广州": {"temp": "32°C", "condition": "雷阵雨", "humidity": "80%"}, # 广州 + } + + # 查找城市天气数据 + data = weather_data.get(city) # 从字典中获取天气数据 + + # 如果找不到该城市的天气 + if not data: # 检查数据是否存在 + return { # 返回错误信息 + "status": "error", # 状态:错误 + "error_message": f"未找到'{city}'的天气信息" # 错误描述 + } + + # 返回成功结果 + return { # 返回天气信息 + "status": "success", # 状态:成功 + "city": city, # 城市名称 + "temperature": data["temp"], # 温度 + "condition": data["condition"], # 天气状况 + "humidity": data["humidity"], # 湿度 + } + + +# ======================================== +# 定义 Agent(ADK 入口点) +# ======================================== + +# root_agent 是 ADK 自动识别的入口变量 +# 变量名必须是 root_agent,不能修改 +root_agent = Agent( + name="hello_agent", # Agent 的唯一标识名称 + model="gemini-2.0-flash", # 使用 Gemini 2.0 Flash 模型 + description="一个能查询城市时间和天气的友好助手。", # Agent 的描述 + instruction=( # 系统指令(多行字符串) + "你是一个友好的助手,可以帮助用户查询世界各城市的当前时间和天气。\n" + "当用户询问某个城市的时间时,使用 get_current_time 工具来获取。\n" + "当用户询问某个城市的天气时,使用 get_weather 工具来获取。\n" + "如果工具返回错误,请友好地告知用户并建议尝试其他城市。\n" + "回复时请使用中文,语气亲切友好。" + ), + tools=[ # 将自定义函数注册为 Agent 的工具 + get_current_time, # 时间查询工具 + get_weather, # 天气查询工具 + ], +) diff --git a/ADKLearning/google-adk-tutorial/code/llm_agent_demo.py b/ADKLearning/google-adk-tutorial/code/llm_agent_demo.py new file mode 100644 index 0000000..4587d29 --- /dev/null +++ b/ADKLearning/google-adk-tutorial/code/llm_agent_demo.py @@ -0,0 +1,210 @@ +""" +Google ADK LLM 智能体完整示例 +展示 LlmAgent 的各种配置和用法 + +对应教程:第03章 - LLM 智能体详解 +""" + +# 导入 ADK 核心模块 +from google.adk.agents import Agent, LlmAgent # Agent 和 LlmAgent +from google.adk.agents import SequentialAgent # 顺序工作流 +from google.adk.models.lite_llm import LiteLlm # LiteLLM 适配器 +from google.adk.tools import google_search # Google 搜索工具 +from google.adk.tools import code_execution # 代码执行工具 + +# 导入运行相关模块 +from google.adk.runners import Runner # 运行器 +from google.adk.sessions import InMemorySessionService # 内存会话服务 +from google.genai import types # Google GenAI 类型定义 + +# 导入异步库 +import asyncio # 异步编程库 + + +# ======================================== +# 示例一:基础 LlmAgent 配置 +# ======================================== + +# 使用 Gemini 模型(默认) +gemini_agent = Agent( + name="gemini_agent", # Agent 名称 + model="gemini-2.0-flash", # 使用 Gemini 2.0 Flash + instruction="你是一个专业的编程助手。", # 系统指令 + description="擅长编程和代码审查。", # 描述(用于多 Agent 路由) +) + +# 使用 LiteLLM 适配 DeepSeek 模型 +deepseek_agent = Agent( + name="deepseek_agent", # Agent 名称 + model=LiteLlm( # 使用 LiteLLM 适配器 + model="deepseek/deepseek-chat", # 格式:provider/model_name + api_key="你的_DEEPSEEK_API_KEY", # API Key + ), + instruction="你是一个中文写作助手。", # 系统指令 +) + +# 使用 LiteLLM 适配 OpenAI 模型 +openai_agent = Agent( + name="openai_agent", # Agent 名称 + model=LiteLlm( # 使用 LiteLLM 适配器 + model="openai/gpt-4o", # OpenAI GPT-4o + api_key="你的_OPENAI_API_KEY", # API Key + ), + instruction="你是一个数据分析助手。", # 系统指令 +) + + +# ======================================== +# 示例二:使用 Google 内置工具 +# ======================================== + +# 带搜索能力的 Agent +search_agent = Agent( + name="search_agent", # Agent 名称 + model="gemini-2.0-flash", # 使用 Gemini 模型 + instruction=( # 系统指令 + "你是一个信息查询助手。\n" + "使用 Google Search 工具搜索最新信息。\n" + "根据搜索结果提供准确、全面的回答。" + ), + tools=[google_search], # 注册 Google 搜索工具 +) + +# 带代码执行能力的 Agent +code_agent = Agent( + name="code_agent", # Agent 名称 + model="gemini-2.0-flash", # 使用 Gemini 模型 + instruction=( # 系统指令 + "你是一个数据分析助手。\n" + "使用 Code Execution 工具执行 Python 代码。\n" + "确保代码正确且高效。" + ), + tools=[code_execution], # 注册代码执行工具 +) + +# 多功能 Agent(搜索 + 代码执行) +super_agent = Agent( + name="super_agent", # Agent 名称 + model="gemini-2.0-flash", # 使用 Gemini 模型 + instruction="你是一个全能助手,可以搜索信息和执行代码。", # 指令 + tools=[google_search, code_execution], # 注册多个工具 +) + + +# ======================================== +# 示例三:output_key 用法 +# ======================================== + +# 第一步:提取信息的 Agent +extractor = Agent( + name="Extractor", # 信息提取 Agent + model="gemini-2.0-flash", # 模型 + instruction="从用户输入中提取关键信息。只输出提取的信息。", # 指令 + output_key="extracted_info", # 将输出保存到 state['extracted_info'] +) + +# 第二步:分析信息的 Agent(引用第一步的输出) +analyzer = Agent( + name="Analyzer", # 信息分析 Agent + model="gemini-2.0-flash", # 模型 + instruction=( # 指令:通过 {key} 引用 state 中的值 + "分析以下提取的信息,给出深度分析。\n" + "提取的信息:{extracted_info}" + ), + output_key="analysis_result", # 将分析结果保存到 state +) + +# 创建顺序流水线 +pipeline = SequentialAgent( + name="InfoPipeline", # 流水线名称 + sub_agents=[extractor, analyzer], # 按顺序执行 +) + + +# ======================================== +# 示例四:Agent Transfer(动态委派) +# ======================================== + +# 定义专门的子 Agent +booking_agent = LlmAgent( + name="BookingAgent", # 预订 Agent + model="gemini-2.0-flash", # 模型 + description="负责处理航班和酒店的预订请求。", # 描述 + instruction="你是一个预订助手,帮助用户预订航班和酒店。", # 指令 +) + +info_agent = LlmAgent( + name="InfoAgent", # 信息查询 Agent + model="gemini-2.0-flash", # 模型 + description="负责回答一般性问题和提供信息。", # 描述 + instruction="你是一个信息助手,回答用户的各种问题。", # 指令 +) + +# 定义协调器 Agent +coordinator = LlmAgent( + name="Coordinator", # 协调器 + model="gemini-2.0-flash", # 模型 + description="主协调器,负责分配任务。", # 描述 + instruction=( # 指令 + "你是一个协调器。\n" + "预订任务交给 BookingAgent。\n" + "信息查询交给 InfoAgent。\n" + "使用 transfer_to_agent 工具委派任务。" + ), + sub_agents=[booking_agent, info_agent], # 注册子 Agent +) + + +# ======================================== +# 示例五:通过代码运行 Agent +# ======================================== + +# 定义应用信息 +APP_NAME = "llm_demo_app" # 应用名称 +USER_ID = "user_001" # 用户 ID +SESSION_ID = "session_001" # 会话 ID + + +async def run_demo(): + """运行 LlmAgent 演示""" + + # 创建会话服务 + session_service = InMemorySessionService() # 内存会话服务 + + # 创建会话 + session = await session_service.create_session( + app_name=APP_NAME, # 应用名称 + user_id=USER_ID, # 用户 ID + session_id=SESSION_ID, # 会话 ID + ) + + # 创建运行器 + runner = Runner( + agent=gemini_agent, # 使用 Gemini Agent + app_name=APP_NAME, # 应用名称 + session_service=session_service, # 会话服务 + ) + + # 构造用户消息 + content = types.Content( + role='user', # 角色:用户 + parts=[types.Part(text='用 Python 写一个冒泡排序算法')], # 消息 + ) + + # 运行 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(): # 如果是最终响应 + response = event.content.parts[0].text # 提取文本 + print(f"Agent 回复:\n{response}") # 打印回复 + + +# 运行演示 +if __name__ == "__main__": # 当脚本被直接运行时 + asyncio.run(run_demo()) # 执行异步函数 diff --git a/ADKLearning/google-adk-tutorial/code/multi_agent_demo.py b/ADKLearning/google-adk-tutorial/code/multi_agent_demo.py new file mode 100644 index 0000000..bb63826 --- /dev/null +++ b/ADKLearning/google-adk-tutorial/code/multi_agent_demo.py @@ -0,0 +1,275 @@ +""" +Google ADK 多智能体系统完整示例 +展示各种多 Agent 协作模式 + +对应教程:第05章 - 多智能体系统 +""" + +# 导入 ADK 核心模块 +from google.adk.agents import ( # 导入所有 Agent 类型 + Agent, # LLM Agent(别名) + LlmAgent, # LLM Agent(完整名) + SequentialAgent, # 顺序工作流 + ParallelAgent, # 并行工作流 + LoopAgent, # 循环工作流 + BaseAgent, # 基础 Agent(自定义用) +) +from google.adk.events import Event, EventActions # 导入事件类型 +from google.adk.agents.invocation_context import InvocationContext # 导入上下文 +from typing import AsyncGenerator # 导入异步生成器 + + +# ======================================== +# 模式一:协调器/调度器模式 +# ======================================== + +# 定义专门的子 Agent +math_agent = LlmAgent( + name="MathExpert", # 数学专家 + model="gemini-2.0-flash", # 模型 + description="擅长数学计算和统计分析。", # 描述 + instruction="你是数学专家,解决数学问题。给出详细的计算过程。", # 指令 +) + +writing_agent = LlmAgent( + name="WritingExpert", # 写作专家 + model="gemini-2.0-flash", # 模型 + description="擅长文案撰写和内容创作。", # 描述 + instruction="你是写作专家,帮助用户撰写高质量的内容。", # 指令 +) + +coding_agent = LlmAgent( + name="CodingExpert", # 编程专家 + model="gemini-2.0-flash", # 模型 + description="擅长编程和代码调试。", # 描述 + instruction="你是编程专家,帮助用户解决编程问题。提供带注释的代码。", # 指令 +) + +# 定义协调器 +coordinator = LlmAgent( + name="Coordinator", # 协调器 + model="gemini-2.0-flash", # 模型 + description="根据问题类型分配给合适的专家。", # 描述 + instruction=( # 指令 + "你是一个任务协调器。\n" + "根据用户的问题类型,将任务分配给合适的专家:\n" + "- 数学相关 → MathExpert\n" + "- 写作相关 → WritingExpert\n" + "- 编程相关 → CodingExpert\n" + "使用 transfer_to_agent 工具进行任务分配。" + ), + sub_agents=[math_agent, writing_agent, coding_agent], # 注册子 Agent +) + + +# ======================================== +# 模式二:顺序流水线模式 +# ======================================== + +# 步骤一:信息提取 +extractor = LlmAgent( + name="Extractor", # 信息提取 Agent + model="gemini-2.0-flash", # 模型 + instruction="从用户输入中提取关键信息。只输出提取的关键信息。", # 指令 + output_key="extracted_info", # 输出保存到 state +) + +# 步骤二:信息分析 +analyzer = LlmAgent( + name="Analyzer", # 信息分析 Agent + model="gemini-2.0-flash", # 模型 + instruction=( # 指令:引用上一步输出 + "分析以下提取的信息,给出深度分析报告。\n" + "提取的信息:{extracted_info}" + ), + output_key="analysis_result", # 输出保存到 state +) + +# 步骤三:报告生成 +reporter = LlmAgent( + name="Reporter", # 报告生成 Agent + model="gemini-2.0-flash", # 模型 + instruction=( # 指令:引用前两步输出 + "根据分析结果生成一份简洁的报告。\n" + "原始信息:{extracted_info}\n" + "分析结果:{analysis_result}" + ), +) + +# 创建顺序流水线 +pipeline = SequentialAgent( + name="InfoPipeline", # 流水线名称 + sub_agents=[extractor, analyzer, reporter], # 按顺序执行 +) + + +# ======================================== +# 模式三:并行扇出模式 +# ======================================== + +# 并行任务一:天气查询 +weather_agent = LlmAgent( + name="WeatherFetcher", # 天气查询 + model="gemini-2.0-flash", # 模型 + instruction="查询并返回天气信息。只返回天气数据。", # 指令 + output_key="weather_data", # 输出保存到 state +) + +# 并行任务二:新闻查询 +news_agent = LlmAgent( + name="NewsFetcher", # 新闻查询 + model="gemini-2.0-flash", # 模型 + instruction="查询并返回最新新闻。只返回新闻摘要。", # 指令 + output_key="news_data", # 输出保存到 state +) + +# 并行任务三:交通查询 +traffic_agent = LlmAgent( + name="TrafficFetcher", # 交通查询 + model="gemini-2.0-flash", # 模型 + instruction="查询并返回交通状况。只返回交通信息。", # 指令 + output_key="traffic_data", # 输出保存到 state +) + +# 创建并行执行器 +gatherer = ParallelAgent( + name="InfoGatherer", # 并行执行器名称 + sub_agents=[weather_agent, news_agent, traffic_agent], # 并行执行 +) + + +# ======================================== +# 模式四:循环优化模式(Generator-Critic) +# ======================================== + +# 生成器 Agent +generator = LlmAgent( + name="Generator", # 生成器 + model="gemini-2.0-flash", # 模型 + instruction=( # 指令 + "你是一个创意写手。\n" + "根据用户需求创作内容。\n" + "将创作的内容保存到 state['draft']。" + ), + output_key="draft", # 输出保存到 state +) + +# 评审器 Agent +critic = LlmAgent( + name="Critic", # 评审器 + model="gemini-2.0-flash", # 模型 + instruction=( # 指令 + "你是一个严格的内容评审。\n" + "评审以下内容,给出评分(1-10)和改进建议。\n" + "当前草稿:{draft}\n" + "如果评分 >= 8,将 state['approved'] 设为 True。\n" + "否则,将改进建议保存到 state['feedback']。" + ), +) + +# 条件检查 Agent +class ApprovalChecker(BaseAgent): + """检查内容是否通过审批""" + + async def _run_async_impl( + self, + ctx: InvocationContext, # 调用上下文 + ) -> AsyncGenerator[Event, None]: # 返回事件生成器 + """执行检查""" + + approved = ctx.session.state.get("approved", False) # 获取审批状态 + + yield Event( # 生成事件 + author=self.name, # 事件作者 + actions=EventActions( + escalate=approved, # 通过则退出循环 + ), + ) + +# 创建迭代优化循环 +review_loop = LoopAgent( + name="ReviewLoop", # 循环名称 + max_iterations=3, # 最多迭代3次 + sub_agents=[critic, ApprovalChecker(name="Checker"), generator], # 循环体 +) + + +# ======================================== +# 模式五:多层级嵌套 +# ======================================== + +# 第三层:具体执行 Agent +code_reviewer = LlmAgent( + name="CodeReviewer", # 代码审查 + model="gemini-2.0-flash", # 模型 + description="审查代码质量。", # 描述 + instruction="审查代码并给出改进建议。", # 指令 +) + +test_writer = LlmAgent( + name="TestWriter", # 测试编写 + model="gemini-2.0-flash", # 模型 + description="编写单元测试。", # 描述 + instruction="为代码编写单元测试。", # 指令 +) + +# 第二层:开发流水线 +dev_pipeline = SequentialAgent( + name="DevPipeline", # 流水线 + sub_agents=[code_reviewer, test_writer], # 顺序执行 +) + +# 第二层:其他 Agent +doc_writer = LlmAgent( + name="DocWriter", # 文档编写 + model="gemini-2.0-flash", # 模型 + description="编写技术文档。", # 描述 + instruction="编写清晰的技术文档。", # 指令 +) + +# 第一层:根协调器 +root_agent = LlmAgent( + name="RootCoordinator", # 根协调器 + model="gemini-2.0-flash", # 模型 + description="开发团队协调器。", # 描述 + instruction=( # 指令 + "你是一个开发团队协调器。\n" + "代码审查和测试交给 DevPipeline。\n" + "文档编写交给 DocWriter。" + ), + sub_agents=[dev_pipeline, doc_writer], # 子 Agent +) + + +# ======================================== +# 打印结构信息 +# ======================================== + +if __name__ == "__main__": + print("=" * 60) # 分隔线 + print("Google ADK 多智能体系统示例") # 标题 + print("=" * 60) # 分隔线 + + print("\n📦 模式一:协调器/调度器") # 模式一 + print(f" 根 Agent: {coordinator.name}") # 根 Agent + print(f" 子 Agent: {[a.name for a in coordinator.sub_agents]}") # 子 Agent + + print("\n📦 模式二:顺序流水线") # 模式二 + print(f" 流水线: {pipeline.name}") # 流水线 + print(f" 步骤: {[a.name for a in pipeline.sub_agents]}") # 步骤 + + print("\n📦 模式三:并行扇出") # 模式三 + print(f" 并行器: {gatherer.name}") # 并行器 + print(f" 任务: {[a.name for a in gatherer.sub_agents]}") # 任务 + + print("\n📦 模式四:循环优化") # 模式四 + print(f" 循环: {review_loop.name}") # 循环 + print(f" 最大迭代: {review_loop.max_iterations}") # 最大迭代 + + print("\n📦 模式五:多层级嵌套") # 模式五 + print(f" 根: {root_agent.name}") # 根 + print(f" 第二层: {[a.name for a in root_agent.sub_agents]}") # 第二层 + print(f" 第三层: {[a.name for a in dev_pipeline.sub_agents]}") # 第三层 + + print("\n✅ 所有模式定义完成!") # 成功信息 + print("使用 'adk web' 或 'adk run' 运行。") # 运行提示 diff --git a/ADKLearning/google-adk-tutorial/code/session_state_demo.py b/ADKLearning/google-adk-tutorial/code/session_state_demo.py new file mode 100644 index 0000000..1336d00 --- /dev/null +++ b/ADKLearning/google-adk-tutorial/code/session_state_demo.py @@ -0,0 +1,318 @@ +""" +Google ADK 会话与状态管理完整示例 +展示 Session、State、Memory 的使用方法 + +对应教程:第06章 - 会话与状态管理 +""" + +# 导入 ADK 核心模块 +from google.adk.agents import Agent # Agent 类 +from google.adk.runners import Runner # 运行器 +from google.adk.sessions import InMemorySessionService # 内存会话服务 +from google.adk.memory import InMemoryMemoryService # 内存记忆服务 +from google.genai import types # 类型定义 + +# 导入异步库 +import asyncio # 异步编程库 + + +# ======================================== +# 示例一:State 管理(购物车) +# ======================================== + +def add_to_cart(item: str, price: float, ctx) -> dict: + """ + 将商品添加到购物车 + + Args: + item (str): 商品名称 + price (float): 商品价格 + ctx: 工具上下文(自动注入) + + Returns: + dict: 操作结果 + """ + # 获取当前购物车 + cart = ctx.state.get("cart", []) # 从 state 读取购物车 + + # 添加新商品 + cart.append({ # 追加商品 + "item": item, # 商品名 + "price": price, # 价格 + }) + + # 更新 state + ctx.state["cart"] = cart # 写回 state + + # 计算总价 + total = sum(item["price"] for item in cart) # 求和 + + return { # 返回结果 + "status": "success", + "message": f"已将 {item} 添加到购物车", + "cart_items": len(cart), + "total": total, + } + + +def get_cart(ctx) -> dict: + """ + 获取购物车内容 + + Args: + ctx: 工具上下文 + + Returns: + dict: 购物车内容 + """ + cart = ctx.state.get("cart", []) # 读取购物车 + + if not cart: # 如果为空 + return { # 返回空信息 + "status": "success", + "message": "购物车是空的", + "items": [], + "total": 0, + } + + total = sum(item["price"] for item in cart) # 计算总价 + + return { # 返回购物车 + "status": "success", + "items": cart, + "total": total, + } + + +def clear_cart(ctx) -> dict: + """ + 清空购物车 + + Args: + ctx: 工具上下文 + + Returns: + dict: 操作结果 + """ + ctx.state["cart"] = [] # 清空购物车 + + return { # 返回结果 + "status": "success", + "message": "购物车已清空", + } + + +# ======================================== +# 示例二:temp: 临时状态 +# ======================================== + +def step1_process(query: str, ctx) -> dict: + """ + 处理步骤一:预处理数据 + + Args: + query (str): 用户查询 + ctx: 工具上下文 + + Returns: + dict: 预处理结果 + """ + processed = query.strip().lower() # 预处理 + + # 保存到临时状态(当前调用结束后自动清除) + ctx.state["temp:processed_query"] = processed # 临时存储 + + return { # 返回结果 + "status": "success", + "processed_query": processed, + } + + +def step2_enhance(ctx) -> dict: + """ + 处理步骤二:增强数据 + + Args: + ctx: 工具上下文 + + Returns: + dict: 增强结果 + """ + # 从临时状态读取 + processed = ctx.state.get("temp:processed_query") # 读取临时状态 + + if not processed: # 如果没有数据 + return { # 返回错误 + "status": "error", + "error_message": "请先执行预处理步骤。" + } + + enhanced = f"[增强] {processed}" # 增强处理 + + return { # 返回结果 + "status": "success", + "enhanced_query": enhanced, + } + + +# ======================================== +# 创建 Agent +# ======================================== + +shopping_agent = Agent( + name="shopping_assistant", # Agent 名称 + model="gemini-2.0-flash", # 模型 + instruction=( # 指令 + "你是一个购物助手。\n" + "帮助用户管理购物车:\n" + "- 添加商品:使用 add_to_cart\n" + "- 查看购物车:使用 get_cart\n" + "- 清空购物车:使用 clear_cart" + ), + tools=[add_to_cart, get_cart, clear_cart], # 注册工具 +) + + +# ======================================== +# 示例三:Session 管理 +# ======================================== + +async def session_management_demo(): + """演示 Session 管理功能""" + + print("=" * 50) # 分隔线 + print("Session 管理演示") # 标题 + print("=" * 50) # 分隔线 + + # 创建会话服务 + session_service = InMemorySessionService() # 内存会话服务 + + # 创建会话 + session = await session_service.create_session( + app_name="shopping_app", # 应用名称 + user_id="user_001", # 用户 ID + session_id="session_001", # 会话 ID + state={"cart": []}, # 初始化状态 + ) + + print(f"✅ 会话创建成功: {session.id}") # 打印会话 ID + + # 获取会话 + existing = await session_service.get_session( + app_name="shopping_app", + user_id="user_001", + session_id="session_001", + ) + print(f"✅ 获取会话: {existing.id}") # 打印会话信息 + + # 删除会话 + await session_service.delete_session( + app_name="shopping_app", + user_id="user_001", + session_id="session_001", + ) + print("✅ 会话已删除") # 确认删除 + + +# ======================================== +# 示例四:多轮对话 +# ======================================== + +async def multi_turn_demo(): + """演示多轮对话""" + + print("\n" + "=" * 50) # 分隔线 + print("多轮对话演示") # 标题 + print("=" * 50) # 分隔线 + + # 初始化 + session_service = InMemorySessionService() # 会话服务 + await session_service.create_session( # 创建会话 + app_name="chat_app", # 应用名称 + user_id="user_001", # 用户 ID + session_id="chat_001", # 会话 ID + ) + + # 创建 Runner + runner = Runner( + agent=shopping_agent, # Agent + app_name="chat_app", # 应用名称 + session_service=session_service, # 会话服务 + ) + + # 模拟多轮对话 + conversations = [ # 对话列表 + "帮我添加一个苹果,价格5元", # 第一轮 + "再添加一个香蕉,价格3元", # 第二轮 + "查看我的购物车", # 第三轮 + "清空购物车", # 第四轮 + ] + + for msg in conversations: # 遍历对话 + print(f"\n[用户]: {msg}") # 打印用户消息 + + # 构造消息 + content = types.Content( + role='user', # 角色 + parts=[types.Part(text=msg)], # 内容 + ) + + # 运行 Agent + events = runner.run_async( + user_id="user_001", # 用户 ID + session_id="chat_001", # 同一会话 + new_message=content, # 消息 + ) + + # 获取响应 + async for event in events: # 遍历事件 + if event.is_final_response(): # 最终响应 + print(f"[Agent]: {event.content.parts[0].text}") # 打印回复 + + +# ======================================== +# 示例五:Memory 管理 +# ======================================== + +async def memory_demo(): + """演示 Memory 管理功能""" + + print("\n" + "=" * 50) # 分隔线 + print("Memory 管理演示") # 标题 + print("=" * 50) # 分隔线 + + # 创建记忆服务 + memory_service = InMemoryMemoryService() # 内存记忆服务 + + # 添加记忆(从会话事件中提取) + await memory_service.add_session_events_to_memory( + app_name="my_app", # 应用名称 + user_id="user_001", # 用户 ID + session_id="session_001", # 会话 ID + ) + print("✅ 记忆已添加") # 确认添加 + + # 搜索记忆 + results = await memory_service.search( + query="用户偏好", # 搜索查询 + app_name="my_app", # 应用名称 + user_id="user_001", # 用户 ID + ) + print(f"✅ 搜索完成,结果数: {len(results)}") # 打印结果数 + + +# ======================================== +# 主函数 +# ======================================== + +async def main(): + """主函数""" + + # 运行所有演示 + await session_management_demo() # Session 管理 + await multi_turn_demo() # 多轮对话 + await memory_demo() # Memory 管理 + + +if __name__ == "__main__": # 直接运行 + asyncio.run(main()) # 执行主函数 diff --git a/ADKLearning/google-adk-tutorial/code/setup_demo.py b/ADKLearning/google-adk-tutorial/code/setup_demo.py new file mode 100644 index 0000000..7c17f49 --- /dev/null +++ b/ADKLearning/google-adk-tutorial/code/setup_demo.py @@ -0,0 +1,69 @@ +""" +Google ADK 环境搭建验证脚本 +验证 ADK 是否正确安装,并打印版本信息 + +对应教程:第01章 - ADK 简介与环境搭建 +""" + +# 导入 ADK 核心模块,验证安装是否成功 +from google.adk.agents import Agent # 智能体模块 +from google.adk.runners import Runner # 运行器模块 +from google.adk.sessions import InMemorySessionService # 内存会话服务 + + +def verify_installation(): + """验证 ADK 安装是否成功""" + + # 打印分隔线 + print("=" * 50) # 打印分隔线 + print("Google ADK 环境验证") # 打印标题 + print("=" * 50) # 打印分隔线 + + # 验证 Agent 类是否可用 + try: # 尝试导入 + agent = Agent( # 创建一个简单的测试 Agent + name="test_agent", # 设置 Agent 名称 + model="gemini-2.0-flash", # 使用 Gemini 模型 + instruction="你是一个测试助手。", # 设置系统指令 + ) + print(f"✅ Agent 模块可用") # 确认 Agent 可用 + print(f" Agent 名称: {agent.name}") # 打印 Agent 名称 + except ImportError as e: # 如果导入失败 + print(f"❌ Agent 模块导入失败: {e}") # 打印错误信息 + return False # 返回失败 + + # 验证 Runner 类是否可用 + try: # 尝试导入 + print(f"✅ Runner 模块可用") # 确认 Runner 可用 + except ImportError as e: # 如果导入失败 + print(f"❌ Runner 模块导入失败: {e}") # 打印错误信息 + return False # 返回失败 + + # 验证 SessionService 是否可用 + try: # 尝试创建 + session_service = InMemorySessionService() # 创建内存会话服务 + print(f"✅ SessionService 可用") # 确认会话服务可用 + except Exception as e: # 如果创建失败 + print(f"❌ SessionService 创建失败: {e}") # 打印错误信息 + return False # 返回失败 + + # 验证 ADK 版本 + try: # 尝试获取版本 + import importlib.metadata # 导入元数据模块 + version = importlib.metadata.version("google-adk") # 获取版本号 + print(f"✅ ADK 版本: {version}") # 打印版本号 + except Exception: # 如果获取失败 + print("⚠️ 无法获取 ADK 版本") # 打印警告 + + # 打印成功信息 + print("=" * 50) # 打印分隔线 + print("🎉 所有模块验证通过!") # 打印成功信息 + print("可以开始使用 Google ADK 了!") # 提示用户 + print("=" * 50) # 打印分隔线 + + return True # 返回成功 + + +# 当脚本被直接运行时执行验证 +if __name__ == "__main__": # 判断是否直接运行 + verify_installation() # 执行验证函数