普通大模型对话只能依托固有知识应答,无法适配真实场景的复杂需求,而工具调用是区分普通对话模型与智能 Agent 的核心关键。承接上篇 LLM 基础能力,本篇聚焦 Agent「手脚能力」搭建,手把手讲解自定义工具函数、标准化工具描述文档、解析模型工具调用指令、执行本地任务并回传结果的完整流程,让模型突破固有能力限制,自主对接外部场景、完成实操任务。
一、安装手脚
完成目标:定义工具函数、解析 tool call/function call。
-
学习指南:
- 写个本地函数: 比如写一个极简的计算器函数
def add(a, b),或者一个假天气查询def get_weather(location)。 - 撰写"工具说明书"(Schema): LLM 看不到你的本地代码,它只能看懂 JSON 格式的说明书。去阅读官方文档中的 Function Calling 部分,学习如何用 JSON Schema 向 LLM 描述你的函数名、描述(description)以及参数类型。
- 捕获"动手的意图": 把你的 Schema 传给大模型。当模型觉得需要调用工具时,它的返回值会发生变化------它不再返回普通的文本回答,而是返回一个
tool_calls对象。你需要写代码来判断:这次返回的是普通聊天,还是一个工具调用请求?
- 写个本地函数: 比如写一个极简的计算器函数
-
验收标准: 当你问"北京天气如何",模型不直接回答,而是返回一个结构化的指令告诉你:"我想调用
get_weather函数,参数是location: 北京"。
1. 定义工具
全局列表增加一个工具描述
位置:写在 chat_history后,generate_response前
python
# 工具描述 Schema
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的当日天气",
"parameters": {
"type": "object",
"required": ["location"],
"properties": {
"location": {"type": "string", "description": "城市名称,如北京、合肥"}
}
}
}
},
]
定义工具函数
python
def get_weather(location: str) -> str:
"""
通过调用 wttr.in API 查询真实的天气信息。
"""
# API端点,请求JSON格式的数据
url = f"https://wttr.in/{location}?format=j1"
try:
# 请求天气数据
response = requests.get(url)
# 检查请求是否成功(为200)
response.raise_for_status()
# 解析JSON响应
data = response.json()
# 提取当前天气状况
current_condition = data['current_condition'][0]
weather_desc = current_condition['weatherDesc'][0]['value']
temp_c = current_condition['temp_C']
# 格式化成自然语言返回
return f"{location}当前天气:{weather_desc},温度:{temp_c}°C"
except Exception as e:
print(f"Error fetching weather data for {location}: {e}")
return "抱歉,生成响应时发生错误。"
2. 修改 AI 生成回复函数
第一轮请求大模型(判断是否需要工具)
python
chat_history.append({"role": "user", "content": prompt})
response = client.chat.completions.create(
model=Model_ID,
# response_format={"type": "json_object"},
messages=chat_history,
tools=tools,
tool_choice="auto"
)
msg = response.choices[0].message
变量解释:
tools:全局数组,写给 AI 看的函数说明书,AI 不知道你本地代码,只能读这份 JSON 规则msg:OpenAI 内置对象,二选一:- 无工具需求:
msg.content存文字,msg.tool_calls为空 - 需调用工具:
msg.tool_calls存调用指令,msg.content是空
- 无工具需求:
判断 AI 是否发起工具调用 if msg.tool_calls
python
if msg.tool_calls:
print("AI 调用了工具:", msg.tool_calls[0].function.name)
# 把模型的工具调用指令存入对话历史
chat_history.append(msg)
# 定义可用工具映射
available_tools = {
"get_weather": get_weather
}
关键说明:
chat_history.append(msg)不能省略!OpenAI 规范:后续二次请求 AI 时,AI 需要看到自己刚刚下发了工具调用指令,上下文才完整,否则会逻辑错乱。available_tools是工具映射字典:函数名字符串 → 本地真实函数,作用为让AI只传函数名文本"get_weather",通过字典找到def get_weather()执行
循环处理所有要调用的工具(支持一次性调用多个函数)
python
# 遍历所有要调用的工具
for tool in msg.tool_calls:
tool_id = tool.id
func_name = tool.function.name
func_args = json.loads(tool.function.arguments)
# 打印调用详情,可视化
print(f"调用工具:{func_name}, 参数:{func_args}")
# 执行本地工具,动态分发调用
tool_func = available_tools.get(func_name)
if tool_func:
tool_result = tool_func(**func_args)
else:
tool_result = "不存在该工具"
print(f"工具返回结果:{tool_result}")
# 存入工具执行结果
chat_history.append({
"role": "tool",
"tool_call_id": tool_id,
"name": func_name,
"content": tool_result
})
重点难懂语法拆解:
json.loads(tool.function.arguments):AI 传的参数是一段 JSON 文本字符串,Python 无法直接读取,必须转成字典:'{"location":"合肥"}'→{"location": "合肥"}tool_func(**func_args):**字典解包语法:func_args = {"location": "合肥"}等价于get_weather(location="合肥")
第二轮请求大模型(结合工具结果生成最终回答)
python
# 工具执行完,二次请求大模型,生成最终回答
print("\n正在结合工具结果,生成最终回复...")
second_resp = client.chat.completions.create(
model=Model_ID,
messages=chat_history,
)
final_content = second_resp.choices[0].message.content.strip()
chat_history.append({"role": "assistant", "content": final_content})
print("===== 工具流程结束 =====\n")
return final_content
作用:
- 第一轮 AI 只下发调用指令,不会生成答案;
- 这一轮把查到的天气数据全部传给 AI,让 AI 按照 system_prompt 规则整理输出 JSON。
else 分支(无需调用工具,普通闲聊)
python
else:
# 无工具调用,直接返回普通文本
print("❌ 模型无需调用工具,直接文字回答")
content = msg.content.strip()
chat_history.append({"role": "assistant", "content": content})
return content
3. 示例输出
注:此处为了让 AI 能够输出自然语言,把强制 AI 输出 JSON 格式的system prompt 和相关代码删去。
text
AI对话程序,输入 quit 结束对话
你:你好,我叫阿言。
❌ 模型无需调用工具,直接文字回答
AI: 你好,阿言!很高兴认识你 😊 我是你的 AI 助手,有什么我可以帮助你的吗?
你:帮我查询合肥明日的天气
AI 调用了工具: get_weather
调用工具:get_weather, 参数:{'location': '合肥'}
工具返回结果:合肥当前天气:Partly Cloudy ,温度:29°C
正在结合工具结果,生成最终回复...
===== 工具流程结束 =====
AI: 你好阿言!我已经查询了这合肥的天气信息:
**合肥** 📍
- 天气状况:局部多云 (Partly Cloudy)
- 温度:29°C
⚠️ **温馨提示:** 以上为当前实时天气数据。如果你需要了解**明日具体天气预报**(包括详细温度范围、降水概率、风力风向等),建议你查看专业天气应用或网站,比如中国气象局官网、墨迹天气或手机自带天气功能,这样能获得更精准的预报信息。
需要我帮你做其他事情吗?😊
你:quit
结束对话。
二、本篇总结 & 下期预告
本篇我们实现了单次工具调用的完整流程,让模型具备了调用外部工具的能力,但目前仍无法自主多轮推理、迭代完成复杂任务,且缺乏工程防护机制。
下一篇 Stage1-下,我们将搭建完整的 Agent 自主循环逻辑,同时新增步数限制、超时控制、分层错误处理三大安全护栏,最终落地一套稳定、可复用、适配线上场景的最小完整 Agent。