大模型本质上是“只会算、不知道实时世界”的推理引擎。
Tool Calling(工具调用) 的意义,就是让大模型学会在合适的时机调用外部工具,把真实世界的数据拉进对话里。
这篇文章通过一个最小可行的“查天气工具”示例,从零拆解:
- 用 Python 封装一个可复用的工具函数
- 用 JSON Schema 把函数描述成大模型可调用的 Tool
- 大模型如何发起
tool_calls,本地代码如何路由并执行 - Tool 调用的完整闭环
- 最后单独总结:哪些是工程&面试中的关键考点
一、用 Python 封装天气 Tool:实现层
先只关注业务逻辑,用 Python 写一个“查天气”的函数:
import requests
def get_weather(location: str) -> str:
url = "https://api.seniverse.com/v3/weather/now.json"
params = {
"key": "YOUR_API_KEY",
"location": location,
"language": "zh-Hans",
}
try:
resp = requests.get(url, params=params, timeout=10)
data = resp.json()
print(data) # 调试阶段可查看真实返回
if "results" in data:
r = data["results"][0]
city = r["location"]["name"]
now = r["now"]
text = now["text"]
temp = now["temperature"]
return f"{city}当前天气:{text},气温 {temp}度"
else:
return "查询失败"
except Exception as e:
return f"异常:{e}"
关键点
-
类型标注
def get_weather(location: str) -> str:- 明确入参和返回值类型,利于 IDE 提示、团队协作。
- 为后续配合
mypy等静态工具打基础。
-
HTTP 请求与超时
resp = requests.get(url, params=params, timeout=10) data = resp.json()- 使用
params自动拼接查询参数。 timeout防止长时间阻塞,是对接第三方服务的基本要求。
- 使用
-
JSON 结构解析与异常兜底
if "results" in data: ... else: return "查询失败"- 在访问深层字段前先检查顶层 key。
- 捕获异常,统一转成可读字符串,便于上层处理。
只看这一段,这就是一个标准的业务 Tool 函数:清晰的入参、出参,内部完成外部 API 调用和结果整理。
二、从函数到 Tool:用 JSON Schema 描述工具契约
大模型并不会直接执行你的 Python 代码,它看到的是一段 Tool 描述(JSON 格式):
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的当前天气",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市名称,如'北京'",
}
},
"required": ["location"],
},
},
}
]
关键点
-
name / description
name:大模型在tool_calls里引用的函数名。description:自然语言说明,影响模型是否、何时选择该工具。
-
parameters(JSON Schema)
type: "object":参数是一个对象。properties.location:必须是字符串类型。required: ["location"]:必填字段。
这里实际上定义了一份 Tool 接口契约:
Tool 的“长相”和“参数约束”与具体语言无关,可以被任意兼容 OpenAI 协议的大模型理解与使用。
三、初始化大模型客户端:让模型“知道有这些 Tool”
以兼容 OpenAI 协议的客户端为例:
from openai import OpenAI
client = OpenAI(
api_key="YOUR_DEEPSEEK_API_KEY",
base_url="https://api.deepseek.com/v1",
)
后续所有与大模型的交互(包括 Tool 调用),都通过这个 client 完成。
四、第一轮调用:模型决定是否使用 Tool(产生 tool_calls)
构造一轮对话,附带上文定义好的 tools:
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
print(response_message)
messages.append(response_message)
关键点
-
messages 协议
role: "user":用户输入。- 后面会出现
role: "assistant"(模型)、role: "tool"(工具结果)。
-
tool_choice="auto"
- 模型根据问题内容和 Tool 描述决定是否调用 Tool。
- 问题依赖实时数据(天气、股价)时更容易触发 Tool 调用。
-
tool_calls 字段
-
当模型认为需要调用 Tool 时,
response_message.tool_calls会包含:- 要调用的
function.name。 - 对应的
arguments(JSON 字符串)。
- 要调用的
-
这一轮调用的核心目的,是让模型给出 “我要调用哪个 Tool、用什么参数” 的决策。
五、本地执行 Tool:解析 tool_calls 并路由到实现
接下来处理第一轮回复中的 tool_calls,在本地真正执行对应的 Python 函数:
if response_message.tool_calls:
for tool_call in response_message.tool_calls:
function_name = tool_call.function.name
# arguments 是 JSON 字符串,需要反序列化
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,
})
else:
# 没有使用工具,模型直接给出了答案
print(response_message.content)
关键点
-
arguments 反序列化
function_args = json.loads(tool_call.function.arguments)- arguments 本质是一个 JSON 字符串。
- 需要转成字典后再从中取出
location等字段。
-
Tool 路由分发
if function_name == "get_weather": ...-
简单情况下使用 if-else 即可。
-
多 Tool 场景可以用映射表统一管理,例如:
TOOL_MAP = {"get_weather": get_weather}
-
-
把 Tool 结果写回 messages
messages.append({ "tool_call_id": tool_call.id, "role": "tool", "name": function_name, "content": function_response, })role: "tool":告诉大模型这是“工具执行结果”。tool_call_id:把这条结果和前面那次调用关联起来。
这一段是整个 Tool 调用链路中的“执行中枢”:
从大模型的调用意图(tool_calls)过渡到本地实际执行的工具函数。
六、第二轮调用:模型基于 Tool 结果生成自然语言回答
到这里,messages 已经包含了:
- 用户原始问题。
- 模型发起的工具调用信息。
- 工具执行后的结果(以
role: "tool"的消息形式)。
再发起一次对话,让模型利用这些信息组织自然语言回复:
final_response = client.chat.completions.create(
model="deepseek-reasoner",
messages=messages,
temperature=0.3,
)
print(final_response.choices[0].message.content)
这一轮调用的重点,是让模型把工具结果视为“外部事实”,然后用自然语言进行总结、解释和扩展,例如:
- 当前天气现象、温度
- 简单的穿衣/出行建议
- 其他基于天气信息的补充说明
至此,一个标准的 Tool Calling 闭环 完成:
- 模型基于用户问题和 Tool 描述,决定是否要调用 Tool。
- 代码本地执行 Tool,实现真实世界数据抓取。
- 工具结果回流到对话上下文中。
- 模型基于结果输出自然语言回答。
七、Tool Calling 全流程小结
把整条链路压缩成几步,方便整体把握:
- 实现层:
用 Python 写出一个职责单一的业务函数(如get_weather)。 - 契约层(Tool 描述) :
用 JSON Schema 描述函数的名称、用途和参数结构,形成语言无关的工具契约。 - 决策层(第一轮模型调用) :
携带tools调用聊天接口,让模型产生tool_calls。 - 执行层(本地路由 & 调用) :
解析tool_calls,映射到真实函数执行,把结果写入messages作为role: "tool"。 - 生成层(第二轮模型调用) :
再次调用聊天接口,让模型基于工具结果生成自然语言回答。
八、面试与工程实践中可重点展开的考点
-
外部 HTTP 调用的健壮性
外部 API 调用中,超时、重试、状态码检查、JSON 解析错误处理等都是必备要素;需要区分网络错误和业务错误,并为上层提供清晰的错误信息。
-
Tool 的 JSON Schema 设计
使用 JSON Schema 精确定义参数类型、必填字段和取值范围,是大模型正确构造
arguments的基础;工具功能演进时,需要保证 schema 兼容性和易扩展性。 -
Tool 路由与扩展机制
对多个 Tool 的管理可以从 if-else 过渡到集中式路由表或注册中心;保证新增 Tool 时最小化对现有代码的入侵,形成插件化结构。
-
两段式 Tool 调用交互模式
Tool Calling 通常采用“先让模型生成调用计划,再执行工具并返回结果,最后生成回答”的两段式交互;这为并行调用多个 Tool、缓存部分结果等优化提供了空间。
总结
这套示例从一个简单的 get_weather 函数出发,完整走了一遍:
- 用 Python 调天气接口。
- 用 JSON Schema 描述本地函数,暴露给大模型。
- 通过
tools和tool_calls协议,让模型自己“决定用什么工具、用什么参数”。 - 把工具执行结果重新注入对话,生成自然语言回答。
可以把 tool 理解成:
“对大模型公开的一个函数接口(JSON 描述) + 在你自己程序里的真实实现,它让大模型不只会说话,还能让你这套代码去『干活』。”
- 对模型:只看到 tools 数组里的 JSON,知道「有什么能力」「需要什么参数」。
- 对你:就是把各种 API、数据库查询、本地逻辑封装成函数,然后注册成 tool。
- 作用:把“纯语言模型”升级成“会用工具的软件中枢”。