Function Calling(工具调用)
普通聊天时,模型只能根据训练数据猜答案。你问「北京现在几度?」,它可能编一个听起来合理的数字。
一、Function Calling 解决的是什么
模型
判断「要不要调工具、调哪个、参数是什么」
你的代码
真正执行函数(查 API、算数、读数据库......)
模型(第二次)
根据函数返回的真实结果,用自然语言回答用户
一句话:模型负责决策,Python 负责执行。
二、完整流程
get_weather() 大模型 你的代码 用户 get_weather() 大模型 你的代码 用户 #mermaid-svg-qjPVvatVh63D5e2s{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-qjPVvatVh63D5e2s .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-qjPVvatVh63D5e2s .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-qjPVvatVh63D5e2s .error-icon{fill:#552222;}#mermaid-svg-qjPVvatVh63D5e2s .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-qjPVvatVh63D5e2s .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-qjPVvatVh63D5e2s .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-qjPVvatVh63D5e2s .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-qjPVvatVh63D5e2s .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-qjPVvatVh63D5e2s .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-qjPVvatVh63D5e2s .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-qjPVvatVh63D5e2s .marker{fill:#333333;stroke:#333333;}#mermaid-svg-qjPVvatVh63D5e2s .marker.cross{stroke:#333333;}#mermaid-svg-qjPVvatVh63D5e2s svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-qjPVvatVh63D5e2s p{margin:0;}#mermaid-svg-qjPVvatVh63D5e2s .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-qjPVvatVh63D5e2s text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-qjPVvatVh63D5e2s .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-qjPVvatVh63D5e2s .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-qjPVvatVh63D5e2s .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-qjPVvatVh63D5e2s .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-qjPVvatVh63D5e2s #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-qjPVvatVh63D5e2s .sequenceNumber{fill:white;}#mermaid-svg-qjPVvatVh63D5e2s #sequencenumber{fill:#333;}#mermaid-svg-qjPVvatVh63D5e2s #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-qjPVvatVh63D5e2s .messageText{fill:#333;stroke:none;}#mermaid-svg-qjPVvatVh63D5e2s .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-qjPVvatVh63D5e2s .labelText,#mermaid-svg-qjPVvatVh63D5e2s .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-qjPVvatVh63D5e2s .loopText,#mermaid-svg-qjPVvatVh63D5e2s .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-qjPVvatVh63D5e2s .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-qjPVvatVh63D5e2s .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-qjPVvatVh63D5e2s .noteText,#mermaid-svg-qjPVvatVh63D5e2s .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-qjPVvatVh63D5e2s .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-qjPVvatVh63D5e2s .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-qjPVvatVh63D5e2s .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-qjPVvatVh63D5e2s .actorPopupMenu{position:absolute;}#mermaid-svg-qjPVvatVh63D5e2s .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-qjPVvatVh63D5e2s .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-qjPVvatVh63D5e2s .actor-man circle,#mermaid-svg-qjPVvatVh63D5e2s line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-qjPVvatVh63D5e2s :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 北京今天天气怎么样?messages + tools 定义tool_calls(要调 get_weather,city=北京)执行 get_weather("北京")返回真实天气数据把 tool 结果追加进 messages,再请求一次自然语言回复打印最终答案
注意是至少两次 API 调用:
- 第一次:模型可能返回 tool_calls,而不是普通文字。
- 你执行函数后,把结果以 role: "tool" 的消息塞回 messages。
- 第二次:模型根据工具结果生成最终回复。
三、三个核心概念
1. tools ------ 告诉模型「你能用什么」
用 JSON Schema 描述函数名、用途、参数:
JSON
tools = [
{
"type": "function",
"function": {
"name": "get_weather", # 必须和 Python 函数名对应
"description": "查询指定城市的当前天气", # 模型靠这个决定何时调用
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,如北京、上海",
}
},
"required": ["city"], # 必填参数
},
},
}
]
description 写清楚很重要:太模糊,模型就不知道何时该调工具。
2. tool_calls ------ 模型返回的「调用指令」
py
response = client.chat.completions.create(
model=model,
messages=messages,
tools=tools,
)
message = response.choices[0].message
if message.tool_calls:
# 模型想调工具,不是直接回答
若模型决定调工具,message结构大致如下:
py
message.role # "assistant"
message.content # 往往是 None(没直接回答用户)
message.tool_calls # 非空列表,里面是「调用指令」
tool_calls 里通常有:
- id:这次调用的唯一 ID(回传结果时要带上)
- function.name:函数名,如 "get_weather"
- function.arguments:JSON 字符串,如 '{"city": "北京"}'
用 json.loads() 解析参数:
py
import json
args = json.loads(tool_call.function.arguments)
city = args["city"]
3. role: "tool" ------ 把执行结果还给模型
py
# ① 保留模型的第一次响应(tool_calls 消息)
messages.append(message)
# ② 每条 tool_call 对应一条 tool 结果
result = get_weather(city)
{
"city": "北京",
"temp": 25,
"condition": "晴",
"humidity": 40,
"wind": "东北风3级",
"aqi": 85
}
messages.append({
"role": "tool",
"tool_call_id": tool_call.id, # 必须和上面的 id 对上
"content": result
})
然后再调一次 create(),这次模型才会用自然语言总结。
四、对应代码骨架
py
# 第一次调用
response = client.chat.completions.create(model=model, messages=messages, tools=tools)
message = response.choices[0].message
if message.tool_calls:
messages.append(message) # 保留模型的 tool_calls 决策
for tool_call in message.tool_calls:
args = json.loads(tool_call.function.arguments)
city = args["city"]
# ← "北京:晴,25℃" 来自这里,不是模型
result = get_weather(city)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result, # 你函数返回的字符串
})
# 第二次调用:模型根据 tool 结果生成最终回复
response2 = client.chat.completions.create(model=model, messages=messages, tools=tools)
final_answer = response2.choices[0].message.content # 这才是给用户看的自然语言