多工具AI Agent实战——让大模型自动选择计算、天气、时间工具(Day13完整笔记)
今天学什么?
Day 12 你只有一个工具(calculate)。今天加到三个:
| 工具 | 大模型什么时候调 | 用到的之前知识 |
|---|---|---|
calculate |
用户问数学题 | Day 1 eval |
get_weather |
用户问天气 | Day 6 API 调用 |
get_current_time |
用户问时间 | Day 8 time 模块 |
最终效果:用户问什么,大模型自动选最合适的工具。
用户:"123×456等于多少?" → 大模型选 calculate
用户:"北京天气怎么样?" → 大模型选 get_weather
用户:"现在几点了?" → 大模型选 get_current_time
用户:"你好" → 大模型不用工具,直接聊天
和 Day 12 的区别
| Day 12(一个工具) | Day 13(三个工具 + 多轮对话) |
|---|---|
| 只有 calculate | calculate + get_weather + get_current_time |
| 问一次就结束 | while 循环,可以一直问 |
| if 判断一个函数 | if-elif 判断多个函数 |
tools JSON 怎么写三个工具?
tools 是一个列表,每个元素是一个工具(一个字典):
tools = [
{ # 工具 1:calculate
"type": "function",
"function": {
"name": "calculate",
"description": "计算数学公式,如 123*456 或 100+200",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "要计算的数学公式"
}
},
"required": ["expression"]
}
}
},
{ # 工具 2:get_current_time(无参数)
"type": "function",
"function": {
"name": "get_current_time",
"description": "获取当前时间,如现在几点了",
"parameters": {
"type": "object",
"properties": {} # 没有参数,空对象
}
}
},
{ # 工具 3:get_weather
"type": "function",
"function": {
"name": "get_weather",
"description": "查询城市天气,如北京今天天气怎么样",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,如北京、上海"
}
},
"required": ["city"]
}
}
}
]
记忆口诀:tools 是工具箱,每个 {} 是一把工具。加工具就是往工具箱里多放一把。
get_current_time 为什么没有参数?
因为 def get_current_time() -> str: 函数本身不需要参数,所以 properties 是空的 {}。
get_weather 的参数名为什么是 city 不是 expression?
因为函数签名是 def get_weather(city: str),参数叫 city。tools 里的参数名必须和函数签名里的参数名一致。
三步之间的字段对应关系
| tools 里你定义了什么 | 大模型返回了什么 | 你的代码读取什么 |
|---|---|---|
name: "get_weather" |
tool_call.function.name = "get_weather" |
function_name |
properties.city |
tool_call.function.arguments = '{"city":"北京"}' |
arguments["city"] |
完整代码
"""
Day 13:多工具 AI 个人助理
三个工具:计算、天气、时间
输入 q 退出
"""
import time, os
from dotenv import load_dotenv
import httpx
from openai import OpenAI
# -- 第 1 步:定义三个工具函数 --
def calculate(expression: str) -> str:
"""计算数学公式"""
try:
result = eval(expression)
return str(result)
except:
return "计算错误"
def get_current_time() -> str:
"""获取当前时间"""
return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
def get_weather(city: str) -> str:
"""查询城市天气"""
load_dotenv()
api_key = os.getenv("API_KEY")
api_host = os.getenv("API_HOST")
headers = {"X-QW-Api-Key": api_key}
try:
with httpx.Client() as client:
geo_url = f"https://{api_host}/geo/v2/city/lookup?location={city}"
geo_resp = client.get(geo_url, headers=headers)
city_id = geo_resp.json()["location"][0]["id"]
weather_url = f"https://{api_host}/v7/weather/now?location={city_id}"
weather_resp = client.get(weather_url, headers=headers)
data = weather_resp.json()
return f"{city}:{data['now']['text']},{data['now']['temp']}°C,体感{data['now']['feelsLike']}°C"
except:
return "天气查询失败"
# -- 第 2 步:定义 tools JSON --
tools = [
{
"type": "function",
"function": {
"name": "calculate",
"description": "计算数学公式,如 123*456 或 100+200",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "要计算的数学公式"
}
},
"required": ["expression"]
}
}
},
{
"type": "function",
"function": {
"name": "get_current_time",
"description": "获取当前时间,如现在几点了",
"parameters": {
"type": "object",
"properties": {}
}
}
},
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询城市天气,如北京今天天气怎么样",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,如北京、上海"
}
},
"required": ["city"]
}
}
}
]
# -- 第 3 步:初始化客户端 --
load_dotenv()
client = OpenAI(
api_key=os.getenv("LLM_API_KEY"),
base_url=os.getenv("LLM_BASE_URL")
)
# -- 第 4 步:交互循环 --
messages = [
{"role": "system", "content": "你是一个智能助手,可以帮用户计算数学题、查询天气、查看当前时间。请用中文回答。"}
]
print("AI 助手(输入 q 退出)")
print("=" * 40)
while True:
user_input = input("\n你:")
if user_input == "q":
print("再见!")
break
messages.append({"role": "user", "content": user_input})
# 第一次调用 API:让大模型决定是否调用工具
response = client.chat.completions.create(
model="mimo-v2.5-pro",
messages=messages,
tools=tools
)
message = response.choices[0].message
# 如果大模型要调用工具
if message.tool_calls:
messages.append(message)
for tool_call in message.tool_calls:
function_name = tool_call.function.name
arguments = eval(tool_call.function.arguments)
if function_name == "calculate":
result = calculate(arguments["expression"])
elif function_name == "get_weather":
result = get_weather(arguments["city"])
elif function_name == "get_current_time":
result = get_current_time()
else:
result = "未知工具"
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result
})
# 第二次调用 API:大模型基于工具结果生成自然语言回答
final_response = client.chat.completions.create(
model="mimo-v2.5-pro",
messages=messages
)
print(f"AI:{final_response.choices[0].message.content}")
messages.append(final_response.choices[0].message)
# 如果大模型不需要调用工具(直接聊天)
else:
print(f"AI:{message.content}")
messages.append(message)
完整数据流图
用户:"北京天气怎么样"
↓
messages.append({"role": "user", ...})
↓
第一次调 API:messages + tools → 发给大模型
↓
大模型返回:tool_calls = [{name: "get_weather", arguments: '{"city":"北京"}'}]
↓
你的代码读取:function_name = "get_weather",arguments = {"city": "北京"}
↓
if-elif 分发:get_weather("北京") → "北京:晴,25°C,体感27°C"
↓
messages.append({"role": "tool", "content": "北京:晴,25°C..."})
↓
第二次调 API:messages(含工具结果)→ 发给大模型
↓
大模型回答:"北京今天天气晴朗,温度25°C,体感27°C,适合出门!"
↓
用户看到最终回答
你踩过的坑
坑 1:required 放在了 properties 里面
# ❌ required 在 properties 里面,大模型收不到
"properties": {
"expression": {"type": "string", "description": "..."},
"required": ["expression"] # 错!
}
# ✅ required 和 properties 同级
"properties": {
"expression": {"type": "string", "description": "..."}
},
"required": ["expression"] # 对!
坑 2:用户输入用了 “system” 角色
# ❌ 用户输入当成系统指令发了
messages.append({"role": "system", "content": user_input})
# ✅ 用户输入必须用 "user" 角色
messages.append({"role": "user", "content": user_input})
这是最关键的 bug。 用了 system 角色,大模型以为是"系统设定"而不是"用户问题",所以不调用工具,直接用聊天回答。
坑 3:message 写成了 messages(复数)
# ❌ messages(复数)不存在
message = response.choices[0].messages
# ✅ message(单数)
message = response.choices[0].message
坑 4:function_name 写成了 function.name
# ❌ 下划线写法(不存在)
function_name = tool_call.function_name
# ✅ 点号写法
function_name = tool_call.function.name
坑 5:arguments 读取错误
# ❌ function.parameters 不存在
arguments = eval(tool_call.function.parameters)
# ✅ 正确的属性名是 arguments
arguments = eval(tool_call.function.arguments)
坑 6:get_current_time 加了多余的参数
函数本身不需要参数,但 tools 里给它加了 expression 参数。正确写法是 properties: {} 空对象。
坑 7:get_weather 参数名写成 expression
函数签名是 def get_weather(city: str),参数叫 city。tools 里的参数名必须和函数签名一致。
你问过的好问题
Q1:tools JSON 里多个工具是增加 function 还是增加 tools?
→ 增加 tools 列表里的元素。tools 是工具箱,每个 {} 是一把工具。
Q2:tools 里的 expression 参数是从哪里拿到的?
→ 大模型自己从用户的问题里"理解"出来的。你在 description 里写清楚参数是什么意思,大模型就会自动填值。
Q3:if-elif 分发是在 while 循环外面还是里面?
→ 里面。while 外面是初始化(client、messages、tools),while 里面是每轮对话的完整流程。
Q4:geo_url 那种 URL 是固定的吗?
→ 不是 Python 语法,是和风天气官方 API 文档规定的地址。每个 API 都有自己的 URL 格式,看文档才知道怎么拼。
Q5:第一次调用 API 怎么知道返回数据的结构?
→ 先 print(data) 看原始 JSON 长什么样,再写提取代码。这是调任何新 API 的标准流程。
概念速查表
| 概念 | 一句话理解 |
|---|---|
| Function Calling | 让大模型能调用你写的 Python 函数 |
| tools | 工具箱,告诉大模型有哪些工具可用 |
| name | 工具名,必须和 Python 函数名一致 |
| description | 工具描述,大模型根据这个决定什么时候调用 |
| properties | 函数参数定义,告诉大模型需要传什么参数 |
| required | 哪些参数是必填的 |
| tool_calls | 大模型返回的调用指令 |
| role: “user” | 用户输入必须用这个角色 |
| role: “system” | 系统设定,不是用户问题 |
| role: “tool” | 工具执行结果 |
Day 1-13 串联回顾
Day 1-2 猜数字 / 学生管理 → Python 基础语法
Day 3 文件 I/O + JSON → 数据持久化
Day 4 面向对象 class → 代码结构化
Day 5 argparse → 命令行参数
Day 6 API 调用 → 连接互联网
Day 7 异步编程 → 提升性能
Day 8 高级特性 → 代码简化
Day 9 类型提示 + Pydantic → 数据校验
Day 10 FastAPI → 变成 Web 服务
Day 11 LLM API + Prompt → 调用大模型
Day 12 Function Calling → 让大模型调用函数
Day 13 多工具 Agent → 三个工具 + 多轮对话 ← 你在这里
从猜数字到多工具 AI Agent,你只用了 13 天。
一句话总结
多工具 Agent = 多个函数 + tools 列表 + if-elif 分发 + while 循环。
大模型是"大脑"(决定调哪个工具),你的代码是"手脚"(执行函数)。
Day 1-12 的知识全都用上了:循环判断、API 调用、时间模块、LLM API、Function Calling——Day 13 是所有技能的汇聚点。
本文是 Python 熟练度特训 Day 13 的笔记。Phase 2(AI 核心技能)进入 Agent 实战阶段。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)