实战一:构建你的第一个工具调用 Agent
如果你第一次做 Agent,我不建议一上来就碰“自动改代码”或者“多 Agent 协作”。最合适的起点通常是一个非常小、但能完整跑通的工具调用闭环。
天气查询就是这种很适合练手的例子。它足够简单,不需要复杂业务背景,但又能把 Agent 最关键的几步都串起来:
- 用户提出一个目标
- 模型判断自己缺信息
- 模型调用工具
- 工具返回结果
- 模型基于结果再组织最终回答
这就是 Agent 的最小骨架。
[PROMPT_LAB_BANNER]
1. 准备工作
这里用 Python 和 Gemini 只是为了把例子写得更直接,不代表你必须绑定某个模型厂商。核心思路在 OpenAI、Anthropic 或其他支持 tool calling 的模型里都类似。
我们先安装依赖:
pip install -q -U google-generativeai
2. 定义工具 (Define Tool)
Agent 不会凭空知道“现在墨尔本几度”。如果没有联网能力,它只能猜。
所以第一步是把“查询天气”暴露成一个工具。这里先用一个假的天气函数,把注意力放在调用机制本身,而不是第三方 API 细节。
import google.generativeai as genai
import os
# 模拟的天气 API(真实项目里可以接 OpenWeatherMap、WeatherAPI 等)
def get_current_weather(location: str):
"""获取指定地点的当前天气信息"""
print(f"🔧 工具被调用: get_current_weather('{location}')")
# 这里只是模拟返回
weather_data = {
"Beijing": "Sunny, 25°C",
"Melbourne": "Rainy, 12°C",
"New York": "Cloudy, 18°C"
}
return weather_data.get(location, "Unknown location")
# 将函数放入工具箱
tools = [get_current_weather]
这个示例里有两个刻意简化的地方,你要心里有数:
- 返回值只是纯文本,真实项目更建议返回结构化 JSON。
- 没做异常处理,真实环境里你至少要考虑超时、空结果和非法参数。
比如更稳一点的返回可以是:
{
"location": "Melbourne",
"temperature_c": 12,
"condition": "Rainy",
"source": "weather_api"
}
因为结构化数据更容易让模型稳定消费,也更方便后续程序处理。
3. 初始化模型
工具定义好以后,下一步是把它注册给模型,让模型知道“自己在什么情况下可以调用什么能力”。
genai.configure(api_key="YOUR_API_KEY")
# 使用支持 tool/function calling 的模型
model = genai.GenerativeModel(
model_name='gemini-1.5-flash',
tools=tools # 注入工具
)
# 开启自动工具调用
# SDK 会帮你处理“模型提议调用 -> 执行函数 -> 把结果回传给模型”这段流程
chat = model.startChat(enable_automatic_function_calling=True)
自动模式很适合入门,因为它能让你先把最小闭环跑通。
但别太早依赖它。只要你开始做真实产品,很快就会遇到这些需求:
- 想记录每次工具调用日志
- 想限制某些工具只能调用一次
- 想在敏感操作前插入人工确认
- 想对异常做自定义重试
这时你最终还是会回到“手动控制循环”。
4. 运行 Agent
现在可以发一个真实一点的用户问题,让模型自己判断是否要调工具:
response = chat.send_message("墨尔本和北京现在的天气怎么样?适合穿什么?")
print(f"🤖 Agent 回答:\n{response.text}")
幕后发生了什么?
- User: "墨尔本和北京..."
- Model: 思考 -> 决定调用
get_current_weather(location='Melbourne')和get_current_weather(location='Beijing')。 - Execution (SDK自动处理): 运行函数,获取 "Rainy, 12°C" 和 "Sunny, 25°C"。
- Model: 接收到工具结果 -> 综合生成最终建议。
- Output: "墨尔本现在下雨,12度,建议穿雨衣和保暖衣物;北京..."
5. 进阶:手动控制循环
虽然 SDK 自动调用很省事,但复杂 Agent 系统通常不会把执行权完全交给 SDK。
原因很现实:
- 你不知道模型为什么反复调用同一个工具
- 你没法在关键节点插入审批
- 工具调用失败时,不一定该直接把错误丢回给模型
- 你可能还想对某些工具做限流、鉴权或审计
所以更常见的做法,是自己维护一个显式循环。
手动循环伪代码:
messages = [...] # ...
while True:
# 1. 调用 LLM
response = model.generate_content(messages)
# 2. 检查是否有 Tool Call
if not response.function_calls:
break # 没工具调用,直接结束
# 3. 执行工具
for call in response.function_calls:
tool_result = execute_tool(call.name, call.args)
# 4. 将结果追加到对话历史
messages.append({
"role": "tool",
"name": call.name,
"content": tool_result
})
# 5. 循环继续,LLM 看到工具结果后会生成下一步
这段伪代码背后的重点其实只有一句:
模型负责决定“要不要用工具”,程序负责决定“工具到底怎么安全地被执行”。
这个边界一定要清楚。
6. 真实项目里最容易踩的坑
光把 demo 跑通还不够。工具调用 Agent 最容易翻车的点,通常在这些地方:
工具描述太弱
如果工具 description 写得太简略,模型可能根本不知道什么时候该调用它,或者乱调。
比如只写:
获取天气
就太弱了。更好的写法应该告诉模型:
- 这个工具适用于什么问题
- 输入参数是什么
- 返回什么格式
- 什么时候不该调用
把副作用工具和只读工具混在一起
查询天气这类工具是只读的,风险很低。
但一旦工具变成:
- 发邮件
- 改数据库
- 删除文件
- 提交 PR
风险就完全变了。真实系统里最好对工具做分级,至少分成:
- 只读工具
- 可写但低风险工具
- 高风险工具,需要人审
工具失败时没有兜底
真实 API 经常会:
- 超时
- 429 限流
- 返回空结果
- 参数校验失败
如果你直接把原始报错扔给模型,它有时会继续“编”。更稳的方式通常是:
- 把错误标准化
- 明确告诉模型这是失败,不是数据
- 决定让它重试、换方案,还是终止
7. 可以怎么继续升级这个实验
如果这个天气 Agent 你已经跑通,后面可以逐步加这几层能力:
- 把假数据换成真实天气 API。
- 把工具输出改成结构化 JSON。
- 加一个日志层,记录每次 tool call。
- 给工具调用加最大次数限制,防止死循环。
- 再加一个新工具,比如城市时区查询,观察模型如何选择工具。
做到这里,你就已经不是在玩“聊天增强”,而是在搭一个真正意义上的工具型 Agent。
小结
工具调用 Agent 的本质,真的可以压缩成一个 while loop:
- Ask:问模型下一步想做什么。
- Act:由程序安全地执行动作。
- Observe:把结果回传给模型。
- Repeat:直到任务结束。
看起来很简单,但大多数复杂 Agent,最后其实都是从这个骨架慢慢长出来的。