在使用 OpenAI SDK(或兼容接口如 DeepSeek)实现外部工具调用时,你可能会写出类似下面这段代码:
ini
import json
messages = [{"role": "user", "content": "北京天气怎么样"}]
response = client.chat.completions.create(
model='deepseek-reasoner',
messages=messages,
tools=tools,
tool_choice="auto",
temperature=0.3
)
response_message = response.choices[0].message
# 如果模型建议调用工具
if response_message.tool_calls:
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":
function_response = get_weather(function_args["location"])
else:
function_response = "未知工具"
# 将工具调用结果加入对话历史
messages.append({
"tool_call_id": tool_call.id,
"role": "tool",
"name": function_name,
"content": function_response
})
# 最后一次请求,获取最终答案
final_response = client.chat.completions.create(
model='deepseek-reasoner',
messages=messages,
temperature=0.3
)
print(final_response.choices[0].message.content)
这段代码明明已经本地执行了 get_weather 函数,拿到了真实结果,为什么还要再发一次请求给大模型?
要理解这个问题,我们必须先搞清楚:这两次请求,各自承担了什么角色?
第一次请求:LLM 只负责"规划",不负责"执行"
当你第一次调用 chat.completions.create 并传入 tools 参数时,大模型并不会去查天气、不会发 HTTP 请求、更不会操作你的数据库。
它做的只有一件事:判断是否需要调用工具,并生成结构化的调用指令。
例如,对于用户问"北京天气怎么样?",模型返回的 response_message 可能是这样的(伪结构):
json
{
"role": "assistant",
"tool_calls": [
{
"id": "call_abc123",
"function": {
"name": "get_weather",
"arguments": "{"location": "北京"}"
}
}
]
}
👉 这就是 "调用指令" :
- 工具名 :
get_weather - 参数 :
{"location": "北京"}(由 LLM 从自然语言中自动提取并格式化)
但请注意:此时没有任何外部调用发生!
LLM 只是在说:"请帮我调用这个函数,参数是这些。"
开发者执行工具:真正的"行动者"是你
接下来,你的代码会解析 tool_calls,并在本地执行:
ini
function_response = get_weather("北京") # ← 真正的 API 调用发生在这里!
这一步完全运行在你的服务器上,LLM 对此一无所知。它只是"提了个建议",而你是那个"跑腿的人"。
第二次请求:把结果"喂"回去,让 LLM 生成人类语言
执行完工具后,你构造了一条特殊消息:
json
{
"role": "tool",
"tool_call_id": "call_abc123",
"name": "get_weather",
"content": "北京晴,25℃"
}
🔑 关键:"role": "tool" 的作用是什么?
这是 OpenAI API 中专门用于传递工具执行结果的消息类型。它的意义是:
"刚才那个 ID 为
call_abc123的工具调用,我已经执行完了,结果是'北京晴,25℃'。"
只有当你把这条消息加入对话历史并再次请求 LLM 时,模型才能"看到"真实世界的数据,并据此生成自然语言回答:
"北京今天天气晴朗,气温25摄氏度,非常适合外出。"
那么问题来了:既然工具是我自己执行的,为什么不能直接返回结果?
这是一个非常合理的问题!
技术上,你完全可以这样做:
ini
if response_message.tool_calls:
# 执行工具
result = get_weather("北京")
print(result) # 直接输出 "北京晴,25℃"
但这样做的代价是:你放弃了 LLM 最核心的能力------语言理解与上下文推理。
举个例子:
-
用户问:"北京天气怎么样?"
- 直接返回工具结果 →
"25℃" - 经过第二次 LLM 调用 → "北京今天25度,阳光明媚,建议穿短袖。"
- 直接返回工具结果 →
-
用户接着问:"那适合跑步吗?"
-
如果你没做第二次调用,LLM 不知道之前查过天气,只能瞎猜;
-
如果你做了,对话历史里就有"25℃ + 晴"的上下文,LLM 可以回答:
"气温适宜,空气质量良好,非常适合晨跑!"
-
✅ 第二次调用的本质,是让 LLM 基于真实数据"重新思考并表达" ,而不是简单回显。
总结:两步走,缺一不可
| 步骤 | 谁在做 | 做什么 | 为什么必要 |
|---|---|---|---|
| 第一次请求 | LLM | 判断是否调用工具,提取结构化参数 | 让 AI 理解用户意图 |
| 本地执行 | 你(开发者) | 真正调用 API/数据库/函数 | 安全可控地连接外部世界 |
| 第二次请求 | LLM | 基于工具结果生成自然语言回答 | 提供有上下文、有温度的智能体验 |
🌟 工具提供事实,LLM 提供智慧。
少了任何一步,AI 都只是"半智能"。
所以,别嫌多一次调用------那正是你的应用从"数据接口"蜕变为"智能助手"的关键一跃。