第 22 章 · Google ADK 实战:从单 Agent 到多 Agent 07-09
本章观点
前面 19-21 章我们讲了规划、多智能体协作、知识图谱三个高级模式。这些模式都用伪代码或 Anthropic SDK 演示,本章换一个视角:用 Google ADK 落地。
吴恩达 Agentic AI 课程(Bilibili BV1DfrdByE2H,p35-p36)选 Google ADK 作为知识图谱模块的实战框架,理由是:
Google ADK 把 Agent 拆成工程化组件:Agent / Runner / Session / Memory / Event / ToolContext,每个组件职责清晰,组合起来既能跑单 Agent,也能组织多 Agent 系统。
这个拆法对应本教程"四权模型":
- 控制权:Runner 负责执行事件循环,LLM 负责决策,代码负责执行
- 工具权:工具函数 + docstring 让 LLM 知道怎么用
- 状态权:Session / Memory / State 保存对话和跨步骤记忆
- 停止权:事件循环的终止条件(final response / escalation / error)
本章会讲清楚四件事:
- Google ADK 的核心组件和它们的关系。
- 单 Agent 最小示例:定义 Agent、写工具、跑 Runner。
- 多 Agent 系统:根代理 + 子代理委派 + ToolContext 共享状态。
- ADK 和其他框架(Claude Agent SDK / LangGraph)的对比。
22.1 Google ADK 是什么
Google ADK(Agent Development Kit)是 Google 官方出的 Agent 开发框架,2025 年开源。它和 Claude Agent SDK、OpenAI Agents SDK 是同类产品,但有几个独特设计:
| 特性 | Google ADK | Claude Agent SDK | LangGraph |
|---|---|---|---|
| 主推模型 | Gemini(也支持其他) | Claude | 任意 |
| 多 Agent 委派 | 一等公民(sub_agents) | 支持 | 通过图实现 |
| 状态管理 | Session + Memory + State | 内置 | Graph state |
| 工具上下文 | ToolContext 显式注入 | 通过 closure | 通过 state |
| 跨模型 | LiteLLM 适配器 | 仅 Claude | 任意 |
ADK 的核心抽象是"Agent 是控制流操作符":
Agent = LLM 决策 + 代码执行 + 状态记忆 + 事件循环LLM 决定下一步干什么,代码和工具负责执行,Session 保存记忆,Runner 把这些串成事件循环。这个抽象和第 2 章"Agent = LLM + 工具 + 反馈循环 + 状态记忆 + 停止边界"完全一致。
22.2 核心组件
22.2.1 Agent
Agent 是 ADK 的一等公民。一个 Agent 至少需要:
from google.adk import Agent
agent = Agent(
name="hello_agent",
model="gemini-2.0-flash", # 或通过 LiteLLM 用其他模型
description="一个问候代理,负责向用户问好", # 给其他代理看
instruction="你是问候代理。当用户说名字时,调用 say_hello 工具生成问候语。",
tools=[say_hello], # Python 函数列表
)关键字段:
name:代理唯一标识model:LLM 模型(Gemini / 通过 LiteLLM 接 OpenAI 等)description:给其他代理看,说明这个代理负责什么、何时调用instruction:给当前代理看,说明它如何完成任务、何时用工具tools:Python 函数列表
description 和 instruction 的区分是 ADK 的关键设计:
description决定"什么时候被委派"(路由)instruction决定"被委派后怎么做"(执行)
这个区分在第 20 章多 Agent 协作里很重要:经理代理根据 description 决定调谁,子代理根据 instruction 决定怎么做。
22.2.2 Runner
Runner 负责执行 Agent、调度事件循环、调用 LLM、处理工具调用、生成响应。
from google.adk import Runner
runner = Runner(agent=agent, session_service=session_service)Runner 接收:
- 一个 Agent(或多个 Agent 的根代理)
- SessionService(管理会话)
- 可选:MemoryService(跨会话记忆)
Runner 的工作流:
1. 接收用户消息
2. 包装成 ADK content / part 结构
3. 把消息送进当前 Agent
4. Agent 调用 LLM 决定下一步
5. 如果 LLM 决定调工具 -> 执行工具 -> 把结果回传给 LLM
6. 如果 LLM 决定委派子代理 -> 切换到子代理 -> 子代理走自己的循环
7. 如果 LLM 决定结束 -> 产生 final response 事件
8. 遍历事件,返回最终响应22.2.3 Session / SessionService / Memory
Session 保存一次对话或一次任务运行的上下文、状态和记忆。
from google.adk.sessions import InMemorySessionService
session_service = InMemorySessionService()
session = await session_service.create_session(
app_name="my_app",
user_id="user_001",
session_id="session_001"
)Session 的核心字段:
id:会话 IDstate:会话状态字典(重要!见 22.5 节 ToolContext)events:事件历史user_id/app_name:归属
SessionService 管理多个 Session,支持:
- InMemorySessionService:内存存储,开发用
- DatabaseSessionService:数据库持久化
- VertexAISessionService:Google Cloud 托管
MemoryService 跨会话长期记忆。和 Session 的区别:
- Session 是短期的(一次对话)
- Memory 是长期的(跨多次对话)
22.2.4 Event
事件是 Runner 产生的基本单位。一次 Agent 运行会产生一系列事件:
async for event in runner.run_async(
user_id="user_001",
session_id="session_001",
new_message=user_message
):
if event.is_final_response():
print("最终响应:", event.content)
elif event.get_function_calls():
print("工具调用:", event.get_function_calls())
# ... 其他事件类型事件类型:
message_in:消息进入代理message_out:代理输出消息tool_call:代理决定调工具tool_result:工具返回结果agent_transfer:代理委派给另一个代理final_response:最终响应escalation:升级处理error:错误
事件循环的设计让 Agent 行为可观察、可调试。第 20 章 20.6 节强调"多 Agent 必须看 trace",ADK 的事件就是 trace 的基本单位。
22.2.5 ToolContext
ToolContext 是工具函数的可选第二参数,让工具能读写会话状态。
from google.adk.tools import ToolContext
def say_hello(person_name: str, tool_context: ToolContext) -> dict:
"""问候工具。"""
# 写入状态
tool_context.state["username"] = person_name
return {"status": "success", "greeting": f"你好,{person_name}!"}ToolContext 的核心方法:
tool_context.state:会话状态字典tool_context.state["key"] = value:写入状态tool_context.state.get("key"):读取状态tool_context.actions:触发动作(如 escalate / transfer)
ToolContext 让工具调用不只是"输入 -> 输出",还能更新会话记忆。这是多 Agent 共享状态的基础(见 22.5 节)。
22.3 单 Agent 最小示例
把上面的组件合起来,写一个最小的 ADK Agent:问候代理,能记住用户名字并问候。
22.3.1 配置 LLM
ADK 通过 LiteLLM 支持多种模型。课程示例用 OpenAI 的 GPT-4o,本教程改用 Gemini(ADK 原生支持)。
import os
from google.adk import Agent
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.genai import types
# 方式 1:用 Gemini(需要 GOOGLE_API_KEY)
os.environ["GOOGLE_API_KEY"] = "your-api-key"
# 方式 2:用 LiteLLM 调 OpenAI
# os.environ["OPENAI_API_KEY"] = "your-openai-key"
# model = "litellm/gpt-4o"22.3.2 写工具函数
from google.adk.tools import ToolContext
def say_hello(person_name: str, tool_context: ToolContext) -> dict:
"""生成个性化问候语。
当用户告知姓名时调用此工具,生成问候语并记住用户姓名。
Args:
person_name: 用户的名字
tool_context: 工具上下文(自动注入)
Returns:
dict: 包含 status 和 greeting 字段
- status: "success" 或 "error"
- greeting: 问候语
"""
if not person_name:
return {"status": "error", "message": "名字不能为空"}
# 把名字存到会话状态
tool_context.state["username"] = person_name
return {"status": "success", "greeting": f"你好,{person_name}!很高兴认识你。"}工具设计要点:
- 函数名表达用途:
say_hello一看就知道是问候 - 类型标注:
person_name: str让 LLM 知道参数类型 - docstring 详细:说明用途 / 参数 / 返回 / 错误情况
- 返回结构化字典:
{"status": ..., "greeting": ...}而不是纯文本 - 状态写入:通过
tool_context.state保存可复用信息
docstring 在 ADK 里会被转成工具描述传给 LLM,写得越清楚 LLM 越会用。这是第 4 章 Tool Use 讲过的"工具描述要写'何时使用'"原则的体现。
22.3.3 创建 Agent
hello_agent = Agent(
name="hello_agent",
model="gemini-2.0-flash",
description="一个问候代理,当用户告知姓名或问好时使用。",
instruction="""你是问候代理。
你的职责:
1. 当用户告知姓名时,调用 say_hello 工具生成问候语
2. 当用户问好但没说名字时,先问名字
3. 不要处理非问候类请求
调用工具后,把工具返回的 greeting 字段作为回复。""",
tools=[say_hello],
)注意 description 和 instruction 的分工:
description:"当用户告知姓名或问好时使用" -- 让其他代理知道何时委派给它instruction:"你的职责..." -- 让这个代理知道自己该怎么做
22.3.4 配置运行环境
session_service = InMemorySessionService()
runner = Runner(agent=hello_agent, session_service=session_service)
# 创建 session
session = await session_service.create_session(
app_name="hello_app",
user_id="user_001",
)22.3.5 打包用户消息
ADK 用 types.Content 包装用户消息:
user_message = types.Content(
role="user",
parts=[types.Part.from_text("你好,我是 ABK")]
)role 是消息角色(user / model),parts 是消息内容列表(文本 / 图片 / 工具调用等)。
22.3.6 运行事件循环
async for event in runner.run_async(
user_id="user_001",
session_id=session.id,
new_message=user_message
):
# 工具调用事件
if event.get_function_calls():
for fc in event.get_function_calls():
print(f"[工具调用] {fc.name}({fc.args})")
# 工具结果事件
if event.get_function_responses():
for resp in event.get_function_responses():
print(f"[工具结果] {resp.response}")
# 最终响应
if event.is_final_response():
final_text = event.content.parts[0].text
print(f"[最终响应] {final_text}")22.3.7 封装 AgentCaller
把上述样板封装成可复用工具,方便后续测试:
class AgentCaller:
def __init__(self, agent, app_name="default_app"):
self.session_service = InMemorySessionService()
self.runner = Runner(agent=agent, session_service=self.session_service)
self.app_name = app_name
self.session = None
async def init(self, user_id="user_001"):
self.session = await self.session_service.create_session(
app_name=self.app_name,
user_id=user_id,
)
return self
async def call(self, user_text: str) -> str:
message = types.Content(
role="user",
parts=[types.Part.from_text(user_text)]
)
final = ""
async for event in self.runner.run_async(
user_id=self.session.user_id,
session_id=self.session.id,
new_message=message
):
if event.is_final_response():
final = event.content.parts[0].text
return final
async def get_state(self) -> dict:
return self.session.state if self.session else {}
# 用法
caller = await AgentCaller(hello_agent).init()
print(await caller.call("你好,我是 ABK"))
print("状态:", await caller.get_state())
# 状态: {"username": "ABK"}AgentCaller 的价值:把 Runner / Session / 事件循环的样板收起来,后续测试多 Agent 时只需 caller.call(...)。
22.3.8 多轮测试
# 第一轮
print(await caller.call("你好,我是 ABK"))
# 输出: 你好,ABK!很高兴认识你。
# 第二轮(验证状态保持)
print(await caller.call("你还记得我叫什么吗?"))
# 输出: 你叫 ABK,对吧?
# 查看状态
print(await caller.get_state())
# {"username": "ABK"}第二轮能记得名字,是因为第一轮 say_hello 把名字写进了 tool_context.state["username"],Session 持久化了这个状态。
22.4 多 Agent 系统:根代理 + 子代理
p36 的核心内容是用 ADK 实现多 Agent 委派。我们用根代理协调问候代理和告别代理。
22.4.1 设计通信模式
这是第 20 章 20.3.2 单层层级模式的最小实现:
根代理(不直接干活)
/ \
问候代理 告别代理
工具:say_hello 工具:say_goodbye根代理根据用户意图决定委派给谁,子代理拿到控制权后独立处理。
22.4.2 定义工具
def say_hello(person_name: str, tool_context: ToolContext) -> dict:
"""问候工具。当用户告知姓名或问好时使用。"""
tool_context.state["username"] = person_name
return {"status": "success", "greeting": f"你好,{person_name}!"}
def say_goodbye(tool_context: ToolContext) -> dict:
"""告别工具。当用户说再见、谢谢再见、下次见时使用。"""
username = tool_context.state.get("username", "朋友")
return {"status": "success", "goodbye": f"再见,{username}!期待下次见。"}注意 say_goodbye 从 tool_context.state 读 username。这就是 ToolContext 跨代理共享状态的机制。
22.4.3 创建子代理
greeting_agent = Agent(
name="greeting_agent",
model="gemini-2.0-flash",
description="处理问候请求。当用户说你好、嗨、问好时委派给此代理。",
instruction="""你是问候代理。
当用户告知姓名或问好时:
1. 调用 say_hello 工具
2. 把工具返回的 greeting 作为回复
不要处理告别或其他请求。""",
tools=[say_hello],
)
farewell_agent = Agent(
name="farewell_agent",
model="gemini-2.0-flash",
description="处理告别请求。当用户说再见、谢谢再见、下次见时委派给此代理。",
instruction="""你是告别代理。
当用户告别时:
1. 调用 say_goodbye 工具
2. 把工具返回的 goodbye 作为回复
不要处理问候或其他请求。""",
tools=[say_goodbye],
)注意 description 的写法:"当用户说 X、Y、Z 时委派给此代理" -- 这是给根代理看的路由信号。
22.4.4 创建根代理
root_agent = Agent(
name="coordinator_agent",
model="gemini-2.0-flash",
description="协调代理,负责识别用户意图并委派给合适的子代理。",
instruction="""你是协调代理。
你的职责:
1. 识别用户意图
2. 把问候请求委派给 greeting_agent
3. 把告别请求委派给 farewell_agent
4. 不要自己执行问候或告别,只做委派
委派时把用户的完整消息传给子代理,让子代理处理。""",
sub_agents=[greeting_agent, farewell_agent],
# 注意:根代理没有 tools,只有 sub_agents
)关键设计:
- 根代理
tools=[](空),只有sub_agents - 这强制根代理只做协调,不执行具体任务
- 这是第 20 章 20.7 反模式 3 "经理代理配一堆工具"的工程规避
22.4.5 跑多 Agent 系统
caller = await AgentCaller(root_agent, app_name="multi_agent_app").init()
# 第一轮:问候
print(await caller.call("你好,我是 ABK"))
# [根代理] 识别意图 -> 委派给 greeting_agent
# [问候代理] 调用 say_hello("ABK")
# [问候代理] 工具返回 {"greeting": "你好,ABK!"}
# [问候代理] 最终响应: 你好,ABK!
# 状态: {"username": "ABK"}
# 第二轮:告别
print(await caller.call("谢谢再见"))
# [根代理] 识别意图 -> 委派给 farewell_agent
# [告别代理] 调用 say_goodbye()
# [告别代理] 工具读取 state.username = "ABK"
# [告别代理] 工具返回 {"goodbye": "再见,ABK!"}
# [告别代理] 最终响应: 再见,ABK!期待下次见。第二轮告别代理能叫出名字"ABK",是因为第一轮问候工具把名字写进了会话状态,第二轮告别工具从状态里读出来。这就是 ToolContext 跨代理共享状态的工作机制。
22.4.6 打开 verbose 调试
学习多 Agent 时,强烈建议打开 verbose 看事件流:
async def call_verbose(caller, user_text: str):
message = types.Content(
role="user",
parts=[types.Part.from_text(user_text)]
)
async for event in caller.runner.run_async(
user_id=caller.session.user_id,
session_id=caller.session.id,
new_message=message
):
print(f"[Event] type={event.type}")
if event.author:
print(f" author: {event.author}")
if event.get_function_calls():
print(f" function_calls: {event.get_function_calls()}")
if event.get_function_responses():
print(f" function_responses: {event.get_function_responses()}")
if event.is_final_response():
print(f" final_response: {event.content.parts[0].text}")输出会让你看到:
- 哪个代理创建了初始事件
- 根代理是否正确委派
- 子代理是否接收了完整上下文
- 工具调用名和参数
- 工具返回结构
- 哪个事件被标记为 final response
- session state 的变化
这是第 20 章 20.6 节"多 Agent 必须看 trace"的具体实践。
22.5 ToolContext 与会话状态
ToolContext 是 ADK 多 Agent 系统的关键设计。理解它能让你写好可复用工具。
22.5.1 工具的两种模式
无状态工具:纯输入 -> 输出,不读不写状态。
def calculate(expression: str) -> str:
"""数学计算。"""
return str(eval(expression)) # 仅演示有状态工具:读或写会话状态。
def save_user_goal(goal: str, tool_context: ToolContext) -> dict:
"""保存用户目标到会话状态。"""
tool_context.state["user_goal"] = goal
return {"status": "success"}
def recall_user_goal(tool_context: ToolContext) -> dict:
"""从会话状态读取用户目标。"""
goal = tool_context.state.get("user_goal")
if goal:
return {"status": "success", "goal": goal}
return {"status": "error", "message": "尚未设置目标"}22.5.2 状态的常见用法
第 21 章知识图谱多 Agent 系统里,状态可以这样用:
# 用户意图代理写入
def set_user_intent(intent: str, tool_context: ToolContext) -> dict:
tool_context.state["user_intent"] = intent
return {"status": "success"}
# 文件建议代理读取 + 写入
def suggest_files(intent: str, tool_context: ToolContext) -> dict:
saved_intent = tool_context.state.get("user_intent", "")
files = pick_files(saved_intent)
tool_context.state["confirmed_files"] = files
return {"status": "success", "files": files}
# Schema 提案代理读取
def propose_schema(tool_context: ToolContext) -> dict:
files = tool_context.state.get("confirmed_files", [])
intent = tool_context.state.get("user_intent", "")
schema = generate_schema(files, intent)
tool_context.state["schema_proposal"] = schema
return {"status": "success", "schema": schema}这样每个代理的输出都进入会话状态,下游代理可以直接读,不用重新传参。
22.5.3 output_key:代理输出写入状态
除了 ToolContext,ADK 还支持 output_key 参数:代理的最终输出会写入指定状态键。
research_agent = Agent(
name="research_agent",
model="gemini-2.0-flash",
description="研究代理",
instruction="...",
output_key="research_result", # 最终输出写入 state["research_result"]
)这样下游代理可以直接读 state["research_result"],不用解析消息。
22.6 ADK 多 Agent 调试检查清单
把第 20 章 20.6.3 节的检查清单具体到 ADK:
观察 ADK 事件流时重点看:
1. 初始事件
- 哪个代理创建了第一条事件?
- 是根代理直接处理,还是委派给子代理?
2. 委派是否正确
- 根代理收到用户消息后,是否调用了 sub_agent?
- 调用的是哪个子代理?是不是该调的那个?
- 子代理是否接收了完整用户消息?
3. 工具调用
- 子代理调用了哪个工具?
- 工具参数是否从用户消息中正确提取?
- 工具返回是否符合 docstring 描述的结构?
4. 状态变化
- 每次工具调用后 session.state 改了什么?
- 下一个代理是否能读到上一步写入的状态?
5. 终止条件
- 哪个事件被标记为 final_response?
- 是子代理产生的,还是根代理?
- 是否触发了 escalation 或 error?22.7 ADK vs 其他框架
22.7.1 ADK vs Claude Agent SDK
| 维度 | Google ADK | Claude Agent SDK |
|---|---|---|
| 主推模型 | Gemini(也支持其他) | Claude |
| 多 Agent 委派 | sub_agents 一等公民 | 通过 query / subagent |
| 状态管理 | Session + Memory + State | 内置 |
| 工具上下文 | ToolContext 显式 | closure 隐式 |
| 跨模型 | LiteLLM | 仅 Claude |
| 学习曲线 | 中等 | 简单 |
| 生态 | Gemini 生态 | Claude 生态 |
选型:
- 用 Claude 模型 -> Claude Agent SDK
- 用 Gemini 或多模型 -> Google ADK
- 需要严格多 Agent 委派 -> ADK(sub_agents 更直观)
- 快速原型 -> Claude Agent SDK(更轻量)
22.7.2 ADK vs LangGraph
| 维度 | Google ADK | LangGraph |
|---|---|---|
| 抽象方式 | Agent + Runner | StateGraph + Node + Edge |
| 多 Agent | sub_agents 委派 | 图节点 |
| 通信模式 | 委派 / 工具调用 | 边 / 条件边 |
| 状态 | Session state | Graph state |
| 调试 | 事件流 | 图可视化 |
| 灵活度 | 中等(约定大于配置) | 高(图任意拓扑) |
| 学习曲线 | 中等 | 较陡 |
选型:
- 喜欢"声明角色 + 自动委派" -> ADK
- 喜欢"显式画图 + 完全控制" -> LangGraph
- 简单多 Agent -> ADK
- 复杂状态机 + 多条件分支 -> LangGraph
22.7.3 何时不用 ADK
ADK 不是银弹。以下场景其他选择更合适:
- 极简单 Agent:直接用 Anthropic SDK 写 50 行 loop(第 3 章示例),更透明
- 超复杂状态机:LangGraph 的图抽象更灵活
- 只用 Claude:Claude Agent SDK 集成更深
- TypeScript 项目:考虑 Mastra 或 OpenAI Agents SDK
- 类型安全优先:Pydantic AI 类型检查更强
22.8 把第 21 章的多 Agent 架构落地
p35-p36 的最终目的是为第 21 章知识图谱模块做实战铺垫。把第 21 章的五代理架构用 ADK 实现:
# 1. 顶层对话代理
top_agent = Agent(
name="top_dialogue_agent",
model="gemini-2.0-flash",
description="引导用户完成知识图谱构建流程",
instruction="你是知识图谱构建助手。引导用户:1) 澄清目标 2) 选择文件 3) 设计 schema 4) 抽取实体 5) 构建图谱 6) GraphRAG 查询",
sub_agents=[intent_agent, file_agent, schema_agent, extraction_agent, graphrag_agent],
)
# 2. 用户意图代理
intent_agent = Agent(
name="intent_agent",
description="当用户陈述业务目标时委派。澄清为什么要构建图谱、要回答什么问题。",
instruction="...",
tools=[set_user_intent],
)
# 3. 文件建议代理
file_agent = Agent(
name="file_agent",
description="当需要选择数据文件时委派。基于用户目标推荐相关文件。",
instruction="...",
tools=[list_files, suggest_files, confirm_files],
)
# 4. Schema 提案代理(含提出者+批评者)
schema_proposer = Agent(
name="schema_proposer",
description="基于结构化数据文件生成图构建计划。",
instruction="...",
tools=[analyze_csv, propose_schema],
)
schema_critic = Agent(
name="schema_critic",
description="审查并改进 schema 提案。",
instruction="...",
tools=[review_schema, suggest_improvements],
)
schema_agent = Agent(
name="schema_agent",
description="协调 schema 提案和审查。",
instruction="先让 schema_proposer 提案,再让 schema_critic 审查,循环直到收敛。",
sub_agents=[schema_proposer, schema_critic],
output_key="schema_plan",
)
# 5. 知识提取计划代理
extraction_agent = Agent(
name="extraction_agent",
description="基于非结构化文档生成知识提取计划。",
instruction="...",
tools=[plan_extraction],
output_key="extraction_plan",
)
# 6. 执行工具(不是 Agent,是代码)
def build_graph_tool(schema_plan: dict, extraction_plan: dict, files: list) -> dict:
"""图构建执行工具。基于计划和文件构建三层图。"""
# 调用第 21 章 21.4.3 节的 build_graph 函数
return {"status": "success", "graph_stats": {...}}
# 7. GraphRAG 代理
graphrag_agent = Agent(
name="graphrag_agent",
description="基于已构建图谱回答用户问题。",
instruction="...",
tools=[build_graph_tool, query_graph],
)这个结构把第 21 章的设计图直接映射成 ADK 代码。关键观察:
- 每个代理职责单一
- 用
sub_agents组织层级 - 用
output_key让输出进入会话状态 - 用 ToolContext 在代理间共享数据
- 执行工具(build_graph_tool)是代码,不是 LLM 调用
这是第 21 章 21.4.4 节"计划与执行分离"的工程落地。
前人智慧 / Prior Art
本章把 Google ADK 的核心组件和单 / 多 Agent 用法讲清楚。ADK 的设计思想来自多个源头。
Google ADK 的设计哲学 07-09
Google ADK(2025)的设计哲学:
- Agent 是控制流操作符:Agent = LLM 决策 + 代码执行 + 状态记忆
- LLM 负责决策,代码负责执行:智能行为来自 LLM 推理,真正操作由程序和工具完成
- 多 Agent 是一等公民:sub_agents 让层级委派成为原生能力
- 状态显式管理:Session / State / Memory 不是黑箱,可观察可调试
本章 22.1-22.5 节的组件拆解直接对应 ADK 的设计文档。ADK 的工程贡献是把"Agent"从神秘对象变成"模型 + 提示词 + 工具 + 记忆 + 运行器"的组合。
LiteLLM 的跨模型抽象
LiteLLM 是 ADK 跨模型能力的底层。它把 100+ 个 LLM API 统一成 OpenAI 格式:
from litellm import completion
# 同一个接口,调不同模型
resp = completion(model="gpt-4o", messages=[...])
resp = completion(model="claude-sonnet-4-5-20250929", messages=[...])
resp = completion(model="gemini/gemini-2.0-flash", messages=[...])ADK 通过 LiteLLM 适配器让一个 Agent 能调任意模型。这种"框架 + LiteLLM"的组合是 2025 年 Agent 框架的主流模式--OpenAI Agents SDK、CrewAI、LangGraph 都支持类似抽象。
Anthropic Computer Use 的 Agent 抽象
Anthropic Computer Use(2024)的 Agent 抽象和 ADK 类似:
- Agent = LLM + 工具 + 循环
- 工具是 Python 函数 + docstring
- 状态显式管理
这种抽象已经成为 Agent 框架的共识。ADK / Claude Agent SDK / OpenAI Agents SDK 的核心组件高度相似,差别在具体实现和生态绑定。
Andrew Ng Agentic AI 课程的 ADK 模块 07-09
本章内容主要整理自吴恩达 Agentic AI 课程 Module 6(Bilibili BV1DfrdByE2H,p35-p36)。课程的两节递进:
- p35 Google ADK 简介 Part 1:单 Agent 的最小组成
- p36 Google ADK 简介 Part 2:多 Agent 委派 + ToolContext 共享状态
课程的关键判断:"Agent 不是神秘对象,而是模型、提示词、工具、记忆、会话和运行器的组合"。这正是本章 22.2 节组件拆解的来源。
课程还强调:"description 让其他代理知道这个代理该不该被调用,instruction 让代理知道自己该怎么做"。这是本章 22.2.1 节 description vs instruction 区分的来源,也是第 20 章多 Agent 协作的关键设计。
OpenAI Agents SDK 的对比
OpenAI Agents SDK(2025)和 ADK 在多 Agent 委派上设计相似:
- ADK:
sub_agents=[...] - OpenAI:
handoffs=[...]
两者都让一个代理能把控制权交给另一个代理。差别在:
- ADK 用
description路由 - OpenAI 用
handoff_description路由 - ADK 的 ToolContext 更显式
- OpenAI 的 lifecycle hooks 更丰富
这两个框架代表 2025 年 Agent 框架的两个主流方向:Google 的"状态显式 + 跨模型"vs OpenAI 的"生命周期 + 单模型深度"。
工具 docstring 的工程价值
ADK 把工具函数的 docstring 当成给 LLM 的工具描述。这个设计来自 OpenAI Function Calling:
# docstring 会被自动转成工具描述
def say_hello(person_name: str) -> dict:
"""问候工具。当用户告知姓名时使用。
Args:
person_name: 用户名字
Returns:
dict: {"status": ..., "greeting": ...}
"""这种"docstring 即工具描述"的设计有几个好处:
- 单一信息源:开发者写一次 docstring,LLM 和人类都看
- 类型自动推断:Python 类型标注自动转 JSON schema
- 降低 token:比单独写
description字段更紧凑
第 4 章 Tool Use 讲过"工具描述要写'何时使用'",ADK 的 docstring 设计是这个原则的工程化。
会话状态的演化:从无状态到有状态
Agent 框架的会话状态管理演化:
| 阶段 | 框架 | 状态管理 |
|---|---|---|
| 1.0(2023) | 早期 LangChain | messages 列表,无独立状态 |
| 2.0(2024) | LangGraph | graph state 显式 |
| 3.0(2025) | ADK / Claude SDK | Session + State + Memory 三层 |
ADK 的 Session / State / Memory 三层是当前最完整的状态管理设计:
- Session:一次对话的短期记忆
- State:会话内的键值状态(ToolContext 读写)
- Memory:跨会话的长期记忆
这种分层让状态管理从"塞 messages 列表"演化到"显式分层的记忆系统"。第 5 章 Context Engineering 讲过 context 管理的重要性,ADK 的状态分层是这条思路的工程化。
Neo4j Python Driver 与 ADK 的集成
p35 课程示例中,Neo4j 通过 Python driver 集成到 ADK 工具:
from neo4j import GraphDatabase
class Neo4jWrapper:
def __init__(self, uri, user, password):
self.driver = GraphDatabase.driver(uri, auth=(user, password))
def query(self, cypher: str, params: dict = None) -> dict:
with self.driver.session() as session:
result = session.run(cypher, params or {})
return {"status": "success", "records": [r.data() for r in result]}
neo4j = Neo4jWrapper("bolt://localhost:7687", "neo4j", "password")
def query_graph(cypher: str, tool_context: ToolContext) -> dict:
"""执行 Cypher 查询。当需要从知识图谱检索信息时使用。
Args:
cypher: Cypher 查询语句
tool_context: 工具上下文
Returns:
dict: {"status": "success"|"error", "records": [...]}
"""
try:
return neo4j.query(cypher)
except Exception as e:
return {"status": "error", "message": str(e)}这个集成的关键设计:
- Neo4j 查询结果转字典:让 ADK 容易持久化和消费
- 结构化返回:
status: success/error让 LLM 知道查询状态 - 参数化查询:避免 Cypher 注入
- 错误处理:返回结构化错误,让 LLM 决定怎么办(第 3 章 3.4 节原则)
多 Agent 调试的工程实践
p36 强调用 verbose 模式调试多 Agent。这个实践和第 20 章 20.6 节"必须看 trace"完全一致。
ADK 的 verbose 输出包含:
- 事件类型
- 事件作者(哪个代理)
- 工具调用名和参数
- 工具返回结构
- 状态变化
- 最终响应
这种"事件流即 trace"的设计让调试不需要额外工具,框架自带可观察性。LangSmith / Langfuse 等可观测平台进一步把这种事件流持久化、可视化、可分析。
22.9 小结
- Google ADK 把 Agent 拆成工程化组件:Agent(决策)+ Runner(执行)+ Session(短期记忆)+ Memory(长期记忆)+ Event(事件流)+ ToolContext(工具状态访问)+ State(键值状态)。
- 单 Agent 最小示例:定义 Agent(name/model/description/instruction/tools)-> 写工具(带 docstring 和类型标注)-> 配 Runner + Session -> 运行事件循环。封装 AgentCaller 把样板收起来。
- 多 Agent 系统:根代理配置
sub_agents=[...]做委派,子代理专注单一职责。根代理tools=[](空)强制只做协调。子代理通过description让根代理知道何时委派。 - ToolContext 让工具读写会话状态,实现跨代理共享数据。
output_key让代理最终输出直接写入 state。 - 调试多 Agent 必须看事件流:哪个代理创建事件、委派是否正确、工具调用是否对、状态是否更新、终止条件是否合理。
- ADK vs Claude SDK vs LangGraph:用 Gemini 或多模型选 ADK,用 Claude 选 Claude SDK,需要复杂状态机选 LangGraph。
到此,第 19-22 章形成了一个完整模块:规划(19)-> 多 Agent 模式(20)-> 知识图谱(21)-> ADK 实战(22)。这是 Agentic AI 从单 Agent 走向复杂系统的工程路径。
happy ending, love you.