让大模型调用外部工具:Function Calling 与 Tool Calling 完全指南
一、引言
大语言模型(LLM)在文本生成、代码编写、逻辑推理等领域已经展现出令人惊叹的能力。然而,原生大模型有一个根本性的局限:它们只能处理训练数据范围内的知识,无法实时获取最新信息,无法执行代码,无法操作外部系统。
这正是 工具调用(Tool Calling) 技术出现的背景。通过在模型与外部工具之间建立标准化接口,我们可以让大模型:
- 🌐 实时联网搜索,获取最新信息
- 🧮 调用计算器,执行精确数学运算
- 🗄️ 查询数据库,获取业务数据
- 📧 发送邮件,操作第三方 API
- 🔧 执行系统命令,完成运维任务
这种能力的本质,是将大模型从"纯文本生成器"升级为"智能体(Agent)"——一个能够感知环境、做出决策并采取行动的自主系统。
本文面向所有软件从业人员,不要求读者有 AI 专业背景。我们将从原理到实践,系统讲解主流大模型平台的工具调用机制,并提供可直接用于生产的代码示例。
二、为什么需要工具调用
2.1 大模型的内在局限
大语言模型的工作原理是基于训练数据中的统计模式生成文本。这种机制带来了几个固有局限:
| 局限 | 说明 | 示例 |
|---|---|---|
| 知识截止 | 模型的知识停留在训练数据截止的时间点 | 无法回答训练数据截止之后的事件(除非配备联网搜索工具) |
| 无法执行代码 | 模型生成的代码只是文本,不会自动运行 | 让模型"计算 1234 × 5678"可能得到错误答案 |
| 无法操作外部系统 | 模型不能直接查询数据库或调用 API | "帮我查一下订单 #12345 的状态"无法直接完成 |
| 数学精度有限 | 基于概率生成的文本不适合精确计算 | 复杂数学运算容易出错 |
2.2 工具调用如何突破局限
工具调用的核心思路是:让模型知道有哪些工具可用,让模型决定何时调用哪个工具,让外部系统执行工具并将结果返回给模型。
用户提问 → 模型分析意图 → 模型选择工具并生成调用参数 → 外部系统执行工具 → 将结果返回给模型 → 模型结合结果生成最终回答
2.3 典型应用场景
- 智能客服:查询订单状态、物流信息、退换货政策
- 数据分析:查询数据库、生成报表、可视化数据
- 开发辅助:搜索文档、执行测试、部署代码
- 个人助理:查询天气、设置提醒、发送邮件
三、OpenAI Function Calling 详解
3.1 设计原理
OpenAI 在 2023 年 6 月推出了 Function Calling 功能,并在后续版本中持续增强。其核心设计思想是:
- 在 API 请求中声明可用工具:通过
tools参数传递工具定义的列表 - 模型决定是否调用工具:模型分析用户输入后,可以选择调用一个或多个工具
- 模型输出结构化调用请求:当模型决定调用工具时,返回一个包含工具名称和参数的结构化对象
- 外部系统执行工具:由开发者编写的代码实际执行工具逻辑
- 将结果返回给模型:将工具执行结果作为新的消息传递给模型,由其生成最终回答
3.2 工具定义的 JSON Schema 规范
每个工具通过 JSON Schema 格式定义,包含以下关键要素:
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的当前天气信息",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,如 '北京'、'Shanghai'"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度单位,摄氏度或华氏度"
}
},
"required": ["city"],
"additionalProperties": false
}
}
}
关键字段说明:
name:工具名称,使用小写字母和下划线,清晰描述功能description:工具功能描述,直接影响模型是否能正确选择该工具parameters:JSON Schema 格式的参数定义properties:各参数的类型和描述required:必填参数列表enum:枚举值约束,帮助模型选择合法值additionalProperties: false:禁止额外参数,提高调用准确性
3.3 完整代码示例
以下是一个使用 OpenAI Python SDK 的完整示例:
from openai import OpenAI
client = OpenAI(api_key="your-api-key")
# 定义工具
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的当前天气",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称"
}
},
"required": ["city"]
}
}
}
]
# 第一轮:发送用户请求
messages = [
{"role": "user", "content": "北京今天天气怎么样?"}
]
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools
)
# 检查模型是否请求调用工具
message = response.choices[0].message
if message.tool_calls: # tool_calls 为 None 表示模型直接回答
# 模型请求调用工具
tool_call = message.tool_calls[0]
import json
arguments = json.loads(tool_call.function.arguments)
city = arguments["city"]
# 执行工具(实际项目中这里会调用真实的天气 API)
weather_result = f"{city}今天晴天,温度25°C"
# 将工具调用结果返回给模型
messages.append(message) # 模型的 tool_call 请求
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": weather_result
})
# 第二轮:模型生成最终回答
final_response = client.chat.completions.create(
model="gpt-4o",
messages=messages
)
print(final_response.choices[0].message.content)
3.4 并行工具调用(Parallel Function Calling)
从 gpt-4-1106-preview 模型版本开始,OpenAI 支持单次响应中调用多个工具。模型可以在一次回复中请求调用 2 个或更多工具,这些工具可以并行执行:
import json
if message.tool_calls:
results = []
for tc in message.tool_calls:
args = json.loads(tc.function.arguments)
result = execute_tool(tc.function.name, args)
results.append({
"role": "tool",
"tool_call_id": tc.id,
"content": result
})
messages.append(message)
messages.extend(results)
3.5 Structured Outputs:强制结构化输出
OpenAI 推出的 Structured Outputs 功能,确保模型的输出严格符合指定的 JSON Schema。在工具调用场景下,通过在工具定义中设置 strict: true 来启用:
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的当前天气",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称"
}
},
"required": ["city"],
"additionalProperties": False # Structured Outputs 要求
},
"strict": True # 启用严格模式
}
}
]
response = client.chat.completions.create(
model="gpt-4o-2024-08-06",
messages=messages,
tools=tools,
parallel_tool_calls=True # 允许并行工具调用(默认即为 True)
)
启用 Structured Outputs 的要求:
- 工具定义的 JSON Schema 必须完整:所有字段都需要有
description - 必须设置
additionalProperties: false - 必须设置
strict: true - 需要使用支持该功能的模型版本(如 gpt-4o-2024-08-06 及之后版本)
Structured Outputs 的优势:
- 保证输出格式与 JSON Schema 完全一致
- 减少解析错误和无效调用
- 提高复杂参数场景的可靠性
四、通义千问 Tool Calling 详解
4.1 接口概览
通义千问(Qwen)系列模型通过 DashScope API 提供工具调用能力。其接口设计与 OpenAI 高度兼容,这使得开发者可以以最小的改动切换模型后端。
4.2 与 OpenAI 的兼容性
通义千问的工具调用接口在以下方面与 OpenAI 兼容:
| 特性 | OpenAI | 通义千问 |
|---|---|---|
| 工具定义格式 | JSON Schema | JSON Schema(相同格式) |
| 单次调用 | ✅ 支持 | ✅ 支持 |
| 并行调用 | ✅ 支持 | ✅ 支持(部分模型) |
| 流式输出 | ✅ 支持 | ✅ 支持 |
| 结构化输出 | Structured Outputs | 支持 JSON Schema 约束 |
4.3 通义千问特有要求
尽管高度兼容,通义千问在使用工具调用时有一些需要注意的细节:
- 工具名称:建议使用英文字母和下划线,避免中文
- 参数描述:描述越详细、越具体,模型调用越准确
- 系统提示:可以通过 system message 指导模型的工具使用策略
4.4 完整代码示例
以下使用 OpenAI 兼容格式的 Python SDK 调用通义千问:
from openai import OpenAI
# 方式一:使用 OpenAI SDK + DashScope 兼容接口
client = OpenAI(
api_key="your-dashscope-api-key",
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
# 方式二:使用 DashScope 原生 SDK
# from dashscope import Generation
# response = Generation.call(
# model="qwen-plus",
# messages=messages,
# tools=tools,
# result_format="message"
# )
tools = [
{
"type": "function",
"function": {
"name": "query_order",
"description": "根据订单号查询订单详细信息",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "订单编号,如 'ORD20240101001'"
}
},
"required": ["order_id"]
}
}
}
]
messages = [
{"role": "user", "content": "帮我查一下订单 ORD20240101001 的状态"}
]
response = client.chat.completions.create(
model="qwen-plus",
messages=messages,
tools=tools
)
message = response.choices[0].message
if message.tool_calls:
tool_call = message.tool_calls[0]
import json
arguments = json.loads(tool_call.function.arguments)
order_id = arguments["order_id"]
# 执行查询(实际项目中调用数据库或 API)
order_info = f"订单 {order_id} 状态:已发货,预计明日送达"
messages.append(message)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": order_info
})
final_response = client.chat.completions.create(
model="qwen-plus",
messages=messages
)
print(final_response.choices[0].message.content)
五、工具定义规范与最佳实践
工具定义的质量直接决定了模型能否正确选择和使用工具。以下是经过实践验证的最佳实践。
5.1 工具名称命名规范
✅ 推荐:
- get_weather # 动词_名词,清晰描述功能
- query_order_status # 多段描述,用下划线分隔
- send_email # 简洁明了
❌ 避免:
- gw # 缩写无法传达含义
- 获取天气 # 中文名称可能导致解析问题
- getWeatherDataForUser # 过于冗长
规范建议:
- 使用小写英文字母和下划线
- 采用
动词_名词或动词_名词_细节的格式 - 长度控制在 3-30 个字符
- 名称应能独立传达工具的核心功能
5.2 参数描述的清晰度要求
参数描述是模型理解"什么时候传什么值"的关键。好的描述应该包含:
- 语义说明:这个参数代表什么
- 格式要求:期望的格式(日期格式、单位等)
- 示例值:一两个典型示例
{
"start_date": {
"type": "string",
"description": "查询起始日期,格式为 YYYY-MM-DD,例如 '2024-01-01'"
}
}
5.3 必填与可选参数的设计原则
- 必填参数(required):工具执行不可或缺的信息
- 可选参数:有合理默认值或用于细化查询的参数
{
"parameters": {
"properties": {
"city": {
"type": "string",
"description": "城市名称"
},
"days": {
"type": "integer",
"description": "预报天数,默认 1 天,最大 7 天"
}
},
"required": ["city"]
}
}
5.4 枚举值的使用
当参数只能从有限的选项中选择时,使用 enum 约束:
{
"language": {
"type": "string",
"enum": ["zh-CN", "en-US", "ja-JP"],
"description": "回复语言代码"
}
}
好处:
- 防止模型传入无效值
- 缩小模型的决策空间,提高准确率
- 便于后端校验
5.5 复杂参数的处理
对于数组和嵌套对象,使用 JSON Schema 的 items 和嵌套 properties:
{
"filters": {
"type": "array",
"items": {
"type": "object",
"properties": {
"field": {
"type": "string",
"description": "过滤字段名"
},
"operator": {
"type": "string",
"enum": ["eq", "gt", "lt", "contains"]
},
"value": {
"type": "string",
"description": "过滤值"
}
},
"required": ["field", "operator", "value"]
},
"description": "查询条件过滤列表"
}
}
5.6 工具描述对调用准确率的影响
根据开发者社区的经验总结,工具描述的质量对调用准确率有显著影响。描述越清晰、越具体,模型越能正确选择工具并生成准确的参数:
| 描述质量 | 示例 | 经验估算准确率 |
|---|---|---|
| 差 | "查天气" | 较低,模型可能无法理解工具用途 |
| 一般 | "获取城市天气" | 中等,模型能大致理解但可能选错场景 |
| 好 | "获取指定城市的当前天气信息,包括温度、湿度和天气状况" | 较高,模型能准确匹配意图 |
描述编写建议:
- 说明工具的功能(做什么)
- 说明工具的适用场景(什么时候用)
- 说明限制条件(不能做什么)
- 控制在 1-3 句话
六、多工具路由策略
6.1 单工具 vs 多工具场景
单工具场景:用户意图明确,只需要一个工具即可满足需求。
用户:"北京今天天气怎么样?"
→ 模型选择:get_weather(city="北京")
多工具场景:用户请求涉及多个方面,需要组合多个工具。
用户:"帮我查一下订单 #12345 的状态,如果已发货就查物流信息"
→ 模型先调用:query_order(order_id="12345")
→ 根据结果判断是否需要调用:query_logistics(order_id="12345")
6.2 工具选择的自动路由机制
大模型的工具选择依赖于以下因素:
- 工具描述的相关性:模型计算用户意图与各工具描述的语义匹配度
- 参数可填充性:模型判断能否从用户输入中提取所需参数
- 历史上下文:对话历史中的信息可能补充当前请求
6.3 Agent 循环:思考 → 选工具 → 执行 → 观察
工具调用的核心模式是 Agent 循环(ReAct 模式):
┌──────────────────────────┐
│ 思考 (Think) │
│ 分析用户意图,决定下一步 │
└──────────┬───────────────┘
▼
┌──────────────────────────┐
│ 行动 (Act) │
│ 选择合适的工具并生成参数 │
└──────────┬───────────────┘
▼
┌──────────────────────────┐
│ 执行 (Execute) │
│ 外部系统执行工具逻辑 │
└──────────┬───────────────┘
▼
┌──────────────────────────┐
│ 观察 (Observe) │
│ 将结果反馈给模型 │
└──────────┬───────────────┘
▼
┌──── 继续循环 ────┐
│ │
▼ ▼
需要更多工具 已有足够信息
→ 继续循环 → 输出最终回答
每一轮循环中,模型根据观察结果决定:
- 继续调用其他工具
- 已有足够信息,生成最终回答
- 无法完成请求,向用户询问补充信息
6.4 工具组合调用的实践模式
模式一:链式调用(Sequential)
工具 A → 结果 → 工具 B(依赖 A 的结果)→ 结果 → 最终回答
适用于工具之间存在依赖关系的场景。
模式二:并行调用(Parallel)
工具 A ┐
├→ 结果合并 → 最终回答
工具 B ┘
适用于工具之间无依赖关系、可以并行执行的场景。
模式三:条件调用(Conditional)
工具 A → 结果判断
├─ 条件满足 → 工具 B
└─ 条件不满足 → 直接回答
适用于基于中间结果决定后续行为的场景。
七、错误处理与重试机制
7.1 工具调用失败的常见类型
| 失败类型 | 原因 | 示例 |
|---|---|---|
| 参数错误 | 模型传入的参数格式或值不正确 | 日期格式错误、枚举值不在允许范围内 |
| 工具不可用 | 后端服务宕机或网络故障 | API 超时返回 |
| 权限不足 | 工具执行需要但缺少授权 | 无权查询该订单 |
| 业务逻辑错误 | 参数合法但业务上无法执行 | 查询不存在的订单号 |
| 模型未调用 | 模型判断不需要调用工具 | 用户意图理解偏差 |
7.2 错误信息反馈给模型的方式
当工具执行失败时,错误信息的质量和格式直接影响模型能否自行修复:
# ❌ 差的错误信息(模型无法理解)
# 工具返回的 content 字段值为:"Error 500"
# ✅ 好的错误信息(模型可以自行修复)
{
"content": "错误:参数 'start_date' 格式无效。期望格式为 YYYY-MM-DD,收到 '2024/01/01'。请重新提供正确的日期格式。"
}
好的错误信息应该包含:
- 错误类型(参数错误、服务不可用等)
- 具体问题描述
- 期望的正确格式
- 可能的修复建议
7.3 重试策略设计
推荐的重试策略结合了自动重试和模型自修复两层机制:
第一层:系统级自动重试
import time
import json
from openai import OpenAI
def call_with_retry(client, model, messages, tools, max_retries=3):
"""系统级重试:处理 API 超时等临时故障"""
for attempt in range(max_retries):
try:
response = client.chat.completions.create(
model=model,
messages=messages,
tools=tools
)
return response
except Exception as e:
if attempt == max_retries - 1:
raise
# 指数退避:1s, 2s, 4s
wait_time = 2 ** attempt
time.sleep(wait_time)
第二层:模型自修复重试
def agent_loop_with_self_healing(messages, tools, max_turns=5):
"""模型自修复:工具执行失败后,将错误信息返回给模型,让其重新生成调用"""
for turn in range(max_turns):
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools
)
message = response.choices[0].message
if not message.tool_calls or len(message.tool_calls) == 0:
# 模型直接回答(tool_calls 为 None 或空列表),结束循环
return message.content
# 执行工具并检查结果
results = []
for tc in message.tool_calls:
try:
args = json.loads(tc.function.arguments)
result = execute_tool(tc.function.name, args)
except ValueError as e:
# 参数错误:返回描述性错误给模型
result = f"参数错误:{str(e)}。请修正后重试。"
except Exception as e:
result = f"工具执行失败:{str(e)}"
results.append({
"role": "tool",
"tool_call_id": tc.id,
"content": result
})
messages.append(message)
messages.extend(results)
# 超过最大轮次仍未完成
return "抱歉,多次尝试后仍无法完成您的请求。"
7.4 降级策略与兜底方案
当工具调用反复失败时,应有合理的降级方案:
- 降级为通用回答:告知用户当前无法获取实时数据,提供通用建议
- 缓存数据兜底:使用最近一次成功查询的缓存数据(标注时效)
- 转人工处理:超出自动处理能力时,转给人工客服
if turn >= max_turns:
fallback_message = (
"抱歉,暂时无法获取实时数据。"
"您可以稍后重试,或联系人工客服获取帮助。"
)
return fallback_message
八、实战案例:构建一个智能客服 Agent
8.1 场景描述
假设我们要为一个电商平台构建智能客服系统,用户可能会问:
- "我的订单到哪了?"
- "这个商品能退款吗?"
- "发什么快递?"
8.2 工具定义
tools = [
{
"type": "function",
"function": {
"name": "query_order_status",
"description": "根据订单号或用户信息查询订单状态,返回订单详情包括状态、商品、金额等",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "订单编号,如 'ORD20240101001'"
},
"user_phone": {
"type": "string",
"description": "用户手机号(当没有订单号时使用)"
}
},
"required": []
}
}
},
{
"type": "function",
"function": {
"name": "query_logistics",
"description": "根据订单号查询物流信息,包括快递公司、物流单号和物流轨迹",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "订单编号"
}
},
"required": ["order_id"]
}
}
},
{
"type": "function",
"function": {
"name": "check_refund_policy",
"description": "查询商品是否支持退款,基于商品类型和购买时间判断",
"parameters": {
"type": "object",
"properties": {
"product_type": {
"type": "string",
"enum": ["physical", "digital", "service"],
"description": "商品类型:实物商品、虚拟商品、服务类"
},
"days_since_purchase": {
"type": "integer",
"description": "购买距今天数"
}
},
"required": ["product_type", "days_since_purchase"]
}
}
}
]
8.3 对话流程演示
第一轮对话:
用户:帮我查一下订单 ORD20240101001
模型输出(tool_calls):
[
{
"function": {
"name": "query_order_status",
"arguments": {"order_id": "ORD20240101001"}
}
}
]
工具执行结果:
{
"order_id": "ORD20240101001",
"status": "shipped",
"product": "iPhone 15 Pro",
"amount": 7999,
"ship_date": "2024-01-02"
}
第二轮对话:
模型收到结果后,可能进一步调用物流查询:
模型输出(tool_calls):
[
{
"function": {
"name": "query_logistics",
"arguments": {"order_id": "ORD20240101001"}
}
}
]
最终回答:
您的订单 ORD20240101001(iPhone 15 Pro,¥7999)已于 1月2日 发货,快递为顺丰,单号 SF1234567890。最新物流状态:已到达北京分拨中心,预计明天送达。
8.4 完整代码框架
import json
class CustomerServiceAgent:
def __init__(self, client, tools):
self.client = client
self.tools = tools
self.messages = []
def handle(self, user_input):
self.messages.append({"role": "user", "content": user_input})
for _ in range(5): # 最多 5 轮
response = self.client.chat.completions.create(
model="gpt-4o",
messages=self.messages,
tools=self.tools
)
message = response.choices[0].message
if not message.tool_calls or len(message.tool_calls) == 0:
return message.content # 直接回答
# 执行所有工具调用
for tc in message.tool_calls:
result = self.execute_tool(tc)
self.messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": result
})
self.messages.append(message)
return "抱歉,处理您的请求时遇到了困难。"
def execute_tool(self, tool_call):
"""根据工具名称路由到具体执行函数"""
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
if name == "query_order_status":
return self._query_order(args)
elif name == "query_logistics":
return self._query_logistics(args)
elif name == "check_refund_policy":
return self._check_refund(args)
else:
return f"未知工具:{name}"
九、总结与展望
工具调用(Tool Calling / Function Calling)技术正在快速成熟。从 OpenAI 2023 年 6 月首次推出 Function Calling 到现在,这一能力已经成为大模型落地的核心基础设施。
当前趋势
- 标准化:MCP(Model Context Protocol,由 Anthropic 于 2024 年 11 月开源)等协议正在推动工具调用的标准化,让模型与工具的对接更加统一
- 多模态扩展:工具调用不再局限于文本,图像生成、语音合成等多模态工具也可以被模型调用
- Agent 框架成熟:LangChain、LlamaIndex、AutoGen 等框架让构建 Agent 变得更加简单
- 国产模型跟进:通义千问、文心一言、智谱 GLM 等国产模型均已支持工具调用能力
未来展望
- 更强大的自动工具发现:模型能够自主学习和注册新工具
- 更智能的错误恢复:模型遇到工具失败时,能自动尝试替代方案
- 更安全的权限管控:细粒度的工具权限管理,确保 Agent 行为可控
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)