让 AI 学会动手
摘要:Tool Calling(工具调用/函数调用)是 LLM 应用开发中最关键的进阶能力之一。它让大模型不再只会「动嘴」,而是能真正「动手」------查数据库、调 API、发邮件、执行本地命令。本文从零讲解工具调用的工作机制,分别使用 OpenAI SDK 和 Anthropic SDK 提供完整可运行的 Python 代码示例,帮你迈出 AI Agent 开发的第一步。
一、引言
大语言模型非常强大,但它们有一个本质局限:模型的知识是静态的。GPT-4 的训练数据截止于 2023 年底,它不知道今天的天气、查不到你的数据库记录、也发不了邮件。
如果想让 AI 帮你处理实际工作,它必须能够与外部世界交互。这就是 Tool Calling 要解决的问题。
读完这篇文章,你将收获:
- 理解 Tool Calling 的完整工作流程
- 能用 OpenAI SDK 实现一个带工具调用的 AI 助手
- 能用 Anthropic SDK 实现同样的功能
- 掌握并行工具调用、结果传回等进阶技巧
- 理解何时使用、何时不使用工具调用
谁该读:有 Python 基础,了解 LLM API 基本用法,想开发 AI Agent 应用的开发者。
二、什么是 Tool Calling
2.1 一个直观的例子
假设你问一个普通的大模型:「北京今天天气怎么样?」
它只能回答:「我无法获取实时天气数据,建议你查看天气 App。」
但如果给模型配置了一个 get_weather 工具,整个对话会变成这样:
用户: "北京今天天气怎么样?"
↓
模型: [不会直接回答,而是返回一个 tool_call]
{ "name": "get_weather", "arguments": { "city": "北京" } }
↓
你的代码: 调用真实的天气 API,得到结果 28°C
↓
模型: "北京今天晴天,气温 28°C,适合出行。"
这就是 Tool Calling 的核心------模型不执行工具,它只是「提议」调用哪个工具,真正执行的是你的代码。
2.2 完整工作流程
用户消息 → 模型分析 → 模型返回 tool_call
↓
你的代码执行工具
↓
结果发回给模型
↓
模型基于结果生成最终回复 → 返回用户
Tool Calling 循环的时序图:
外部工具/API 你的代码 LLM 模型 用户 外部工具/API 你的代码 LLM 模型 用户 "北京今天天气怎么样?" 返回 tool_call: get_weather(city="北京") 调用天气 API {"temp": 28, "condition": "晴天"} 传回 tool_result(天气数据) "北京今天晴天,气温 28°C"
关键点:
- 模型不会自动调用工具,它只输出一个结构化的调用请求
- 你的代码负责执行,并将结果传回模型
- 这个过程可以循环多次(一次对话中调用多个工具)
2.3 与普通 API 调用的区别
| 特性 | 普通聊天 | Tool Calling |
|---|---|---|
| 模型输出 | 纯文本 | 文本 或 结构化工具调用 |
| 交互次数 | 一次请求 → 一次回复 | 可能多轮:请求→调用→执行→结果→回复 |
| 外部能力 | 无 | 查数据库、调 API、操作文件等 |
| 代码复杂度 | 低 | 中等,需要工具调度循环 |
三、核心概念:Tool Definition
无论使用哪个 SDK,定义一个工具都需要三个要素:
3.1 工具的三要素
python
# 一个典型的工具定义(伪代码概念版)
tool = {
"name": "get_weather", # ① 工具名:唯一标识
"description": "获取指定城市的天气", # ② 描述:告诉模型这工具是干嘛的
"parameters": { # ③ 参数定义:JSON Schema 格式
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,如'北京'、'tokyo'"
}
},
"required": ["city"]
}
}
description 是整个工具定义中最重要的字段。模型就是靠读这段描述来决定「该不该用这个工具」。描述写得不好,模型要么不调用,要么乱传参数。
3.2 什么是好的描述
好的描述应该:
- 说清楚功能:「获取指定城市今天的实时天气信息,包括温度、湿度、天气状况」
- 说清楚使用时机:「当用户询问某地当前天气时使用此工具」
- 说清楚限制:「仅支持城市级别查询,不支持区县级」
差的描述:「查询天气」------模型根本不知道什么时候该用。
四、实战:OpenAI SDK 实现
4.1 环境准备
bash
$ pip install openai
确保你有 OpenAI API Key,建议使用环境变量:
bash
$ export OPENAI_API_KEY="sk-xxx"
4.2 定义工具
python
# 模拟的天气查询函数
def get_weather(city: str) -> dict:
"""实际项目中替换为真实 API 调用"""
# 这是模拟数据
weather_data = {
"北京": {"temperature": 28, "condition": "晴天", "humidity": "45%"},
"上海": {"temperature": 32, "condition": "多云", "humidity": "70%"},
"tokyo": {"temperature": 25, "condition": "小雨", "humidity": "80%"},
}
return weather_data.get(city, {"error": f"未找到 {city} 的天气数据"})
# 工具定义 ------ JSON Schema 格式
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市今天的实时天气信息,包括温度、天气状况和湿度。当用户询问某地天气时使用此工具。",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,中文或英文。例如:'北京'、'上海'、'tokyo'",
}
},
"required": ["city"],
},
},
}
]
4.3 工具调用循环
python
import json
from openai import OpenAI
client = OpenAI()
def run_conversation(user_message: str):
messages = [{"role": "user", "content": user_message}]
# 第一轮:发送用户消息 + 工具定义
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
)
response_message = response.choices[0].message
# 检查模型是否想调用工具
if response_message.tool_calls:
# 将模型的回复加入对话历史
messages.append(response_message)
for tool_call in response_message.tool_calls:
function_name = tool_call.function.name
function_args = json.loads(tool_call.function.arguments)
# 执行对应的函数
if function_name == "get_weather":
result = get_weather(function_args["city"])
else:
result = {"error": f"未知工具: {function_name}"}
# 将执行结果加入对话
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result, ensure_ascii=False),
})
# 第二轮:把结果发回模型,生成最终回复
final_response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
)
return final_response.choices[0].message.content
# 模型不需要工具,直接返回文本
return response_message.content
# 测试
print(run_conversation("北京今天天气怎么样?"))
# 输出: 北京今天晴天,气温 28°C,湿度 45%,适合出行。
4.4 OpenAI 并行工具调用
OpenAI 支持在一次响应中返回多个 tool_calls,当用户说「查一下北京和上海的天气」时,模型会同时提议调用两次 get_weather。
python
# 并行工具调用示例
def run_with_parallel_tools(user_message: str):
messages = [{"role": "user", "content": user_message}]
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
)
response_message = response.choices[0].message
if response_message.tool_calls:
messages.append(response_message)
# 并行执行所有工具调用
for tool_call in response_message.tool_calls:
function_name = tool_call.function.name
function_args = json.loads(tool_call.function.arguments)
if function_name == "get_weather":
result = get_weather(function_args["city"])
else:
result = {"error": f"未知工具: {function_name}"}
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result, ensure_ascii=False),
})
final_response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
)
return final_response.choices[0].message.content
return response_message.content
print(run_with_parallel_tools("北京和上海的天气分别怎么样?"))
# 模型会并行调用 get_weather("北京") 和 get_weather("上海")
# 输出: 北京今天晴天,28°C;上海今天多云,32°C。
五、实战:Anthropic SDK 实现
Anthropic(Claude)的工具调用在设计哲学上与 OpenAI 不同。OpenAI 强调并行执行,Anthropic 强调工具使用的深度集成到对话流中。
5.1 环境准备
bash
$ pip install anthropic
bash
$ export ANTHROPIC_API_KEY="sk-ant-xxx"
5.2 定义工具
python
import anthropic
client = anthropic.Anthropic()
# Anthropic 的工具定义格式
tools = [
{
"name": "get_weather",
"description": "获取指定城市今天的实时天气信息,包括温度、天气状况和湿度。当用户询问某地天气时使用此工具。",
"input_schema": { # 注意:Anthropic 用 input_schema,OpenAI 用 parameters
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,中文或英文。例如:'北京'、'上海'、'tokyo'",
}
},
"required": ["city"],
},
}
]
Agent 循环流程(Anthropic SDK 版本):
否
是
用户消息 + 工具定义
调用 messages.create()
响应中有 tool_use 块?
返回文本回复给用户
提取所有 tool_use blocks
执行对应的工具函数
构造 tool_result 并加入对话
5.3 工具调用循环
python
import json
def run_conversation_anthropic(user_message: str):
messages = [{"role": "user", "content": user_message}]
while True:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=messages,
tools=tools,
)
# 检查响应中是否包含 tool_use 块
tool_use_blocks = [
block for block in response.content if block.type == "tool_use"
]
if not tool_use_blocks:
# 模型直接返回了文本,没有工具调用
return response.content[0].text
# 将模型回复加入对话
messages.append({"role": "assistant", "content": response.content})
# 执行每个工具调用
tool_results = []
for tool_block in tool_use_blocks:
function_name = tool_block.name
function_args = tool_block.input # Anthropic 直接返回 dict,无需 json.loads
if function_name == "get_weather":
result = get_weather(function_args["city"])
else:
result = {"error": f"未知工具: {function_name}"}
tool_results.append({
"type": "tool_result",
"tool_use_id": tool_block.id,
"content": json.dumps(result, ensure_ascii=False),
})
messages.append({"role": "user", "content": tool_results})
# 循环继续,直到模型不再返回 tool_use
print(run_conversation_anthropic("北京今天天气怎么样?"))
5.4 OpenAI vs Anthropic 关键差异速览
| 对比点 | OpenAI | Anthropic |
|---|---|---|
| 参数定义字段 | parameters |
input_schema |
| 调用返回格式 | tool_calls 数组 |
tool_use content block |
| 参数解析 | json.loads(tool_call.function.arguments) |
tool_block.input 直接是 dict |
| 结果回传角色 | role: "tool" |
role: "user" |
| 并行调用 | 一次请求可返回多个 tool_call | 一次返回多个 tool_use block |
| 流式处理 | 支持 | 支持(逐 token 增量) |
| tool_choice | "none"/"auto"/"required" |
tool_choice 参数,支持 "any"/"auto"/tool |
六、进阶:多工具协作
一个真正的 AI Agent 通常需要多个工具协作。比如一个「智能客服助手」可能需要:
python
tools = [
{
"type": "function",
"function": {
"name": "query_order",
"description": "根据订单号查询订单状态和详情",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "订单号,格式如 ORD-20231201-001"
}
},
"required": ["order_id"],
},
},
},
{
"type": "function",
"function": {
"name": "send_email",
"description": "发送邮件给指定收件人",
"parameters": {
"type": "object",
"properties": {
"to": {"type": "string", "description": "收件人邮箱"},
"subject": {"type": "string", "description": "邮件主题"},
"body": {"type": "string", "description": "邮件正文"},
},
"required": ["to", "subject", "body"],
},
},
},
]
模型会根据用户意图自动选择调用哪个工具。当用户说「帮我查一下订单 ORD-20231201-001」时调用 query_order,说「发邮件通知客户」时调用 send_email。
查询订单
发送邮件
查询天气
用户输入
LLM 分析意图
(基于工具描述)
query_order
send_email
get_weather
执行工具 → 返回结果
模型整合结果
生成最终回复
多工具选择的三个技巧
- 描述差异化:如果两个工具功能相近,描述必须明确边界。比如一个查「国内天气」一个查「国际天气」。
- 避免重叠:不要让两个工具能做同样的事,模型会困惑。
- 数量克制:工具不是越多越好。超过 10 个工具时,模型选择准确率明显下降。
七、Tool Calling 的适用场景
适合用 Tool Calling
- 需要实时数据(天气、股价、新闻)
- 需要访问私有数据(公司数据库、CRM)
- 需要执行操作(发邮件、创建工单、操作文件)
- 需要精确计算(复杂数学、日期计算)
不适合用 Tool Calling
- 纯文本生成任务(翻译、摘要、写作)
- 模型自身知识可以覆盖的简单问答
- 对延迟极度敏感的场景(多轮调用增加响应时间)
八、总结
这篇文章覆盖了 Tool Calling 的核心要点:
- Tool Calling 的本质:模型提议调用哪个工具,你的代码执行,结果传回模型生成回复
- 工具定义的三要素:name(标识)、description(最关键,决定模型会不会用)、parameters(JSON Schema)
- OpenAI SDK :
tools参数传入定义,通过response.choices[0].message.tool_calls获取调用请求,结果通过role: "tool"传回 - Anthropic SDK :工具定义用
input_schema,调用请求在content中以tool_useblock 出现,结果通过role: "user"传回 - 生产实践:描述要精准、工具数量要克制、要考虑并行调用和错误处理