OpenAI Tools完全指南:从原理到实战入门
一、核心概念:为什么需要Tools?
想象一下,大语言模型(LLM)像一个学识渊博但"与世隔绝"的学者------它知道很多知识,但无法直接获取实时股价、查询数据库或发送邮件。Tools参数正是为LLM开启的"行动接口",让它能通过你定义的函数与外部世界交互。
关键技术演进
- 旧方式(已弃用) :
functions+function_call参数 - 新方式(当前标准) :
tools+tool_choice参数
二、核心原理:Tools如何工作?
1. 基础工作流程
当模型使用Tools时,对话不再是简单的"一问一答",而是变成一个决策-执行-回复的循环:
外部工具 OpenAI API 你的程序 用户 外部工具 OpenAI API 你的程序 用户 提问(自然语言) 发送请求(含tools定义) 返回工具调用指令(而非答案) 执行对应工具/函数 返回执行结果 发送结果给模型 生成最终自然语言回复 显示最终答案
2. 模型的两个决策点
在流程中,模型实际上做了两次关键判断:
- 是否需要调用工具? (通过
tool_choice控制) - 如果需要,调用哪个工具并传入什么参数? (通过
tools定义控制)
三、详细参数解析
1. tools参数结构
python
# 这是tools参数的标准结构
tools = [
{
"type": "function", # 目前唯一支持的类型
"function": {
"name": "函数名", # 如 get_weather
"description": "清晰描述函数用途", # 这是模型理解何时调用的关键!
"parameters": { # 使用JSON Schema格式定义
"type": "object",
"properties": {
"参数名": {
"type": "string/number/boolean等",
"description": "参数说明"
}
},
"required": ["必须的参数"],
"additionalProperties": False # 推荐设为False,限制模型只使用定义的参数
},
"strict": True # 强烈建议开启,强制模型严格遵守Schema
}
}
# 可以定义多个工具...
]
2. tool_choice参数详解
这个参数控制模型的"主动性":
| 值 | 行为 | 适用场景 |
|---|---|---|
"auto"(默认) |
模型自主决定是否/如何调用工具 | 大多数通用场景 |
"none" |
模型不调用任何工具,只生成文本回复 | 明确不需要工具交互时 |
{"type": "function", "function": {"name": "xxx"}} |
强制模型调用指定工具 | 需要引导对话流程时 |
3. 新增配置:parallel_tool_calls
这是实现一次调用多个工具的关键:
python
# 默认已开启,如需关闭可显式设置False
response = client.chat.completions.create(
model="gpt-4",
messages=messages,
tools=tools,
parallel_tool_calls=True # 允许模型一次返回多个工具调用请求
)
四、完整实战示例
场景:同时查询多家公司股价
这个例子完整展示了从定义工具到获取最终答案的全过程:
python
import json
from openai import OpenAI
# 初始化客户端
client = OpenAI(api_key="your-api-key")
# ========== 1. 定义工具 ==========
tools = [
{
"type": "function",
"function": {
"name": "get_stock_price",
"description": "获取指定公司的实时股票价格",
"parameters": {
"type": "object",
"properties": {
"company_name": {
"type": "string",
"description": "公司的中文或英文名称,如:'苹果'、'Apple'、'小米'"
}
},
"required": ["company_name"],
"additionalProperties": False
},
"strict": True # 确保模型输出完全符合定义的格式
}
}
]
# ========== 2. 模拟的股票查询函数 ==========
def get_stock_price(company_name):
"""实际环境中,这里会调用真实的股票API"""
mock_data = {
"苹果": {"price": 189.85, "currency": "USD", "change": "+1.2%"},
"小米": {"price": 18.72, "currency": "HKD", "change": "-0.5%"},
"腾讯": {"price": 365.4, "currency": "HKD", "change": "+0.8%"}
}
return mock_data.get(company_name, {"error": "公司未找到"})
# ========== 3. 用户提问触发并行调用 ==========
user_question = "苹果公司和小米公司今天的股价分别是多少?走势如何?"
print("=== 第一步:用户提问 ===")
print(f"用户:{user_question}\n")
# ========== 4. 首次API调用:模型决定调用工具 ==========
print("=== 第二步:模型分析,决定调用工具 ===")
first_response = client.chat.completions.create(
model="gpt-4-turbo",
messages=[{"role": "user", "content": user_question}],
tools=tools,
tool_choice="auto", # 让模型自主决定
)
assistant_msg = first_response.choices[0].message
# 检查模型是否要求调用工具
if assistant_msg.tool_calls:
print(f"模型决定调用 {len(assistant_msg.tool_calls)} 个工具")
# 存储所有工具执行结果
tool_results_messages = []
# ========== 5. 执行每个工具调用 ==========
for i, tool_call in enumerate(assistant_msg.tool_calls):
func_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
print(f"\n工具调用 #{i+1}:")
print(f" 函数名:{func_name}")
print(f" 参数:{arguments}")
# 执行对应的函数
if func_name == "get_stock_price":
result = get_stock_price(arguments["company_name"])
print(f" 执行结果:{result}")
# 将结果格式化为API需要的消息格式
tool_results_messages.append({
"role": "tool",
"tool_call_id": tool_call.id, # 关键:ID必须匹配!
"content": json.dumps(result)
})
# ========== 6. 第二次API调用:生成最终回复 ==========
print("\n=== 第三步:将工具结果传回模型,生成最终回复 ===")
# 构建完整的对话历史
full_conversation = [
{"role": "user", "content": user_question},
assistant_msg, # 包含工具调用的消息
*tool_results_messages # 所有工具执行结果
]
second_response = client.chat.completions.create(
model="gpt-4-turbo",
messages=full_conversation,
# 注意:这里可以不带tools参数,但带上更安全
tools=tools,
)
final_answer = second_response.choices[0].message.content
print(f"\n最终答案:{final_answer}")
else:
# 如果模型没有调用工具,直接输出内容
print(f"模型直接回复:{assistant_msg.content}")
关键输出说明:
当模型决定调用工具时,响应中的关键字段如下:
json
{
"id": "chatcmpl-xxx",
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": null, // 注意:这里不是最终答案!
"tool_calls": [{ // 核心:工具调用请求数组
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_stock_price",
"arguments": "{\"company_name\":\"苹果\"}" // JSON字符串格式!
}
}, {
"id": "call_def456",
"type": "function",
"function": {
"name": "get_stock_price",
"arguments": "{\"company_name\":\"小米\"}"
}
}]
},
"finish_reason": "tool_calls" // 停止原因是触发了工具调用
}]
}
五、核心要点与最佳实践
1. 工具描述决定一切
模型完全依赖function.description来判断何时调用工具。描述要:
- 明确说明工具的功能
- 包含可能触发调用的关键词
- 避免歧义
2. 严格模式是你的朋友
设置"strict": true和"additionalProperties": false能:
- 减少模型输出错误格式的几率
- 提高参数提取的准确性
- 避免意外参数导致的程序错误
3. 完整对话历史管理
成功的工具调用需要维护完整的对话上下文:
python
messages = [
{"role": "user", "content": "苹果股价多少?"},
{"role": "assistant", "content": null, "tool_calls": [...]}, # 模型要求调用
{"role": "tool", "tool_call_id": "call_abc123", "content": "189.85"}, # 工具结果
# 可以继续对话...
]
4. 错误处理与限制
- 数量限制:最多128个工具/请求
- 参数解析:始终验证和清理模型返回的JSON
- 超时处理:为工具执行设置超时,避免阻塞
- 成本注意:工具调用通常需要2次API请求,成本翻倍
5. 高级技巧:链式调用
通过持续对话,可以实现复杂的链式调用:
python
# 示例:先查天气,再根据天气推荐活动
# 1. 用户:今天北京天气如何?
# 2. 模型:调用get_weather(北京)
# 3. 程序:返回天气结果
# 4. 模型:根据天气,再调用recommend_activity(天气数据)
# 5. 最终:生成包含天气和活动建议的完整回复
六、常见问题解答
Q1:模型能直接输出${get_stock_price(苹果)}这样的代码吗?
不能。模型只会输出结构化的工具调用请求(JSON格式),不会输出编程语言风格的函数调用代码。
Q2:一次能调用多少个不同的工具?
理论上没有硬性限制,但受限于模型的上下文窗口。实践中,一次响应中通常不会超过5-10个工具调用。
Q3:工具执行失败怎么办?
应该将错误信息返回给模型,让它决定如何回应:
python
result = {"error": "API暂时不可用", "details": "网络超时"}
# 模型收到后可能会说:"抱歉,暂时无法获取股价信息,请稍后再试。"
Q4:如何让输出格式更符合要求?
通过系统提示词(system message) 控制:
python
messages = [
{"role": "system", "content": "你是一个财经助手。请用清晰的Markdown列表格式回复,包含公司名称、股价和涨跌幅。"},
{"role": "user", "content": "苹果和小米的股价"}
]
总结
OpenAI的Tools参数是一个强大的桥梁,让大语言模型从"知识库"变成了"智能执行者"。关键记住:
- 定义清晰:好的工具描述是成功的一半
- 流程完整:遵循"请求→调用→执行→回复"的完整循环
- 格式严格:使用strict模式和完整JSON Schema
- 历史完整:妥善管理包含tool消息的对话历史
通过合理使用Tools,你可以创建出真正能与现实世界交互的智能应用,从简单的数据查询到复杂的多步骤工作流,都能轻松实现。