03 Function Calling

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 调用:

  1. 第一次:模型可能返回 tool_calls,而不是普通文字。
  2. 你执行函数后,把结果以 role: "tool" 的消息塞回 messages。
  3. 第二次:模型根据工具结果生成最终回复。

三、三个核心概念

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  # 这才是给用户看的自然语言
相关推荐
孙启超2 小时前
【AI应用开发】ReAct 原理是什么?和普通直接提问 LLM 差别在哪?
前端·人工智能·llm·agent·react·rag·ai应用开发
孙启超3 小时前
【AI应用开发】LangChain 中 Chain 和 Agent 核心区别?
java·人工智能·langchain·llm·rag·ai应用开发·agent loop
liferecords6 小时前
Qwen3.8-Max 开源了:该不该从 Claude 切过去?
开源·llm·qwen
prog_61037 小时前
【笔记】用cursor手搓cursor(八)
笔记·llm·大语言模型·agent
武子康7 小时前
MiniMax H3 的多模态参考为什么比 2K 更重要?
人工智能·llm·agent
武子康8 小时前
开放权重不是唯一控制权:Kimi K3、Tencent Hy3 与 ByteDance Seed 的九项交付比较
人工智能·llm·agent
circuitsosk9 小时前
知识问答领域大语言模型设计:对标豆包与DeepSeek的工程实践
人工智能·python·语言模型·自然语言处理·llm·豆包·deepseek
llwszx19 小时前
【Java/Go后端手撸原生Agent(第十一篇):RAG检索——给Agent加上“查资料“能力】
llm·向量检索·rag·ai智能体·大模型应用·agent开发·后端转ai
sg_knight1 天前
ZCode 3.0 安装全攻略:下载、配置、跑通第一个任务
llm·agent·ai编程·coding·ai工具·智谱·zcode