AI - 用 Google ADK 打造会“自己选工具”的 Agent
多工具 Agent 是如果选择调用工具
用 Google ADK 打造会“自己选工具”的 Agent
前篇《 AI - 使用 Google ADK 创建你的第一个 AI Agent》用 Google ADK 创建了第一个 Agent,只有一个 Python 函数当作 tool,get_current_time(city),Agent 收到问题后会根据需要调用这个函数,返回结果。
这一篇我们沿着官方 Quickstart 的例子,升级成一个:
既能查天气、又能看时间的多工具(multi‑tool)Agent
并搞清楚一个核心问题: 多个工具放在 tools=[…] 里之后,它们是怎么被自动选择和调用的?
1. 从一个工具,到两个工具
我们第一个 Agent 只有一个工具是这样的(只演示核心代码):
from google.adk.agents.llm_agent import Agent
# Mock tool implementation
def get_current_time(city: str) -> dict:
"""Returns the current time in a specified city."""
return {"status": "success", "city": city, "time": "10:30 AM"}
root_agent = Agent(
model='gemini-3-pro-preview',
name='root_agent',
description="Tells the current time in a specified city.",
instruction="You are a helpful assistant that tells the current time in cities. Use the 'get_current_time' tool for this purpose.",
tools=[get_current_time],
)
现在我们想让它既能回答时间,又能回答天气。我们只需要再加一个函数 get_weather,然后把两个函数都丢进 tools=[…]。
下面是一个完整的多工具示例:
# multi_tool_agent/agent.py
import datetime
from zoneinfo import ZoneInfo
from google.adk.agents import Agent
def get_weather(city: str) -> dict:
"""
查询指定城市的天气(示例里只支持 New York)。
Args:
city: 城市名,比如 "New York"
Returns:
dict: 包含 status, report 或 error_message。
"""
if city.lower() == "new york":
return {
"status": "success",
"report": "New York 今天天气晴,气温约 25℃(77℉)。",
}
else:
return {
"status": "error",
"error_message": f"暂时没有 {city} 的天气数据。",
}
def get_current_time(city: str) -> dict:
"""
返回指定城市的当前时间(示例里只支持 New York)。
Args:
city: 城市名,比如 "New York"
Returns:
dict: 包含 status, report 或 error_message。
"""
if city.lower() == "new york":
tz_identifier = "America/New_York"
else:
return {
"status": "error",
"error_message": f"暂时没有 {city} 的时区信息。",
}
tz = ZoneInfo(tz_identifier)
now = datetime.datetime.now(tz)
report = f"New York 当前时间是 {now.strftime('%Y-%m-%d %H:%M:%S %Z%z')}"
return {"status": "success", "report": report}
root_agent = Agent(
name="weather_time_agent",
model="gemini-3-pro-preview",
description="回答某个城市的天气和时间问题。",
instruction=(
"你是一个乐于助人的助手,可以回答关于城市天气和时间的问题。"
"如果用户问天气,就调用天气工具;如果问时间,就调用时间工具;"
"如果两个都问,就依次调用。"
),
tools=[get_weather, get_current_time], # ✅ 多个工具就这样直接丢进列表
)
会话记录如下:
到这里,我们已经拥有了一个 “多工具 Agent”。但真正有意思的是:我们没有写任何 if / else 去判断“到底用哪个工具”,这个全是 Agent(背后的 LLM)自动搞定的。
接下来就拆它的工作原理。
2. 在 ADK 里,tool 本质上就是“长得好看的函数”
官方文档把这种工具叫做 Function Tool:
只要你把一个 Python 函数塞进 Agent(…, tools=[…]),
ADK 就会自动把它包装成一个 FunctionTool,供 LLM 调用。
更关键的是:ADK 会自动“读懂”你的函数签名和注释:
框架会检查函数的名字、参数、类型标注、默认值,以及 docstring, 自动生成一份 schema,告诉 LLM:
- 这个工具是干嘛的(描述)
- 有哪些入参(参数名 / 类型 / 是否必填)
- 什么时候可以用它
换句话说,当你写:
def get_weather(city: str) -> dict:
"""
查询指定城市的天气。
Args:
city: 城市名,比如 "New York"
"""
...
ADK 大概会生成一份类似这样的“工具说明”(伪代码):
{
"name": "get_weather",
"description": "查询指定城市的天气(示例里只支持 New York)。 Args: city: 城市名,比如 "New York" Returns: dict: 包含 status, report 或 error_message。",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名,比如 'New York'"
}
},
"required": ["city"]
}
}
这份说明会和你的系统提示、用户问题一起发给 LLM,让模型知道:
- 有一个叫 get_weather 的工具
- 干嘛用:查天气
- 怎么调用:需要一个字符串参数 city
同理,get_current_time 也会被包装成另一个 Tool。
所以:只要函数签名和 docstring 写得清楚,工具的“自我介绍”就自动做好了。
3. 多个 tool 是如何被“自动选择”的?
重点来了:当我们把两个函数都写进 tools=[get_weather, get_current_time] 以后,整条调用链大概长这样:
第 1 步:Agent 接到用户问题
例如:
- 「纽约天气怎么样?」
- 「纽约现在几点?」
- 「告诉我纽约现在的天气和时间。」
ADK 把这些信息组装成一个 LLM 请求:
- Agent 的 instruction、description
- 当前会话的历史
- 两个工具的 schema(名字、描述、参数)
第 2 步:LLM 自己决定要不要用工具、用哪个工具
模型看到:
- 工具 A:get_weather(city) → 描述是“查询指定城市的天气”
- 工具 B:get_current_time(city) → 描述是“返回指定城市的当前时间”
然后根据用户问题去“匹配语义”:
- 问“天气” → 更像 get_weather
- 问“时间” → 更像 get_current_time
- 问“天气 + 时间” → 很可能依次调用两个工具
这一步就是 “tool calling” / “函数调用” 能力:
模型不再只输出自然语言,而是可以输出一个「我要调用哪个工具、参数是什么」的结构化指令。
第 3 步:ADK 执行 tool,拿到结果
一旦 LLM 决定调用某个工具,ADK 会做两件事:
- 在 Python 里真正执行这个函数:
result = get_weather(city="New York")
-
把执行结果(通常是一份 dict)再当作“工具结果消息”喂回 LLM。
官方文档建议 Function Tool 返回 dict,并包含 status 和类似 error_message 这样的字段,帮助 LLM 理解操作是否成功。
比如:
{"status": "success", "report": "New York 今天天气晴..."}
或则:
{"status": "error", "error_message": "暂时没有北京的天气数据。"}
第 4 步:LLM 根据工具结果,生成最后的自然语言回答
有了工具结果,模型会再次生成自然语言,像这样:
- 成功时:「New York 今天天气晴朗,气温大约 25℃。」
- 工具报错时:「我暂时查不到北京的天气数据,可以换个城市试试吗?」
如果用户问题里同时问了“天气+时间”,模型可能会:
-
先调用 get_weather
-
再调用 get_current_time
-
然后把两份结果合在一起回复:
「New York 今天天气晴,大约 25℃。
当前时间是 2025‑05‑01 10:30 EDT。」
整个过程你都不需要写 if “天气” in question: …,
全靠工具的 schema + LLM 的推理能力来做判断。
完整过程示例
用户问题:“请问纽约的天气怎样,当前时间是多少?”
- ADK 组装 Agent 信息加上历史会话记录向 LLM 模型发出 Request:

- LLM 模型匹配语义决定调用哪些工具 Response:

- ADK 执行 tools 拿到结果

- LLM 模型根据工具结果,生成最后的自然语言回答

4. 为什么返回值里会有 status 和 report?
在示例里,你会发现两个工具的返回结构很相似:
return {
"status": "success",
"report": "xxx 正常情况的描述 ..."
}
# 或者
return {
"status": "error",
"error_message": "具体失败原因 ..."
}
这是有讲究的:
- LLM 要“看得懂”你的返回值
- status → 这是成功还是失败?
- report → 能直接拿来转成自然语言的内容
- error_message → 出错时可以直接告诉用户,或者自动换个工具 / 换种回答方式
- 多个工具统一一个返回结构,LLM 更好“拼”结果
当你有很多工具时,如果都用类似的结构(例如都包含 status + report),模型可以用几乎一样的逻辑来组合输出。 - 这是官方文档推荐的最佳实践
Function Tools 文档里明确建议:- 使用 dict/Map 作为返回类型
- 尽量返回带语义的字段(例如 status, error_message),而不是冷冰冰的数字码。
5. 小结:写多工具 Agent 的几个实用建议
- 每个工具就是一个职责单一的 Python 函数
- 名字直白:get_weather、get_current_time
- 参数清晰:有类型标注,必填/可选明确
- 返回 dict:包含 status、report、error_message 等
- Docstring 写好就是在给 LLM 做“工具使用说明书”
ADK 会把 docstring 发给 LLM,所以越清晰,模型越容易选对工具。 - Agent 的 instruction 里可以给一点“调度建议”
比如:“如果用户问天气,用 get_weather;如果问时间,用 get_current_time;如果都问,就依次调用这两个工具。”
这样模型在选择工具时就有了明确的策略提示。 - 可以在 Dev UI / CLI 里观察工具调用情况
- 用 adk run multi_tool_agent 在终端试:输入不同问题看看 Agent 调用哪个工具。
- 用 adk web 打开 Dev UI,可以看到 event 流和 tool 调用记录,更方便调试。
更多推荐



所有评论(0)