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  # 这才是给用户看的自然语言
相关推荐
墨抒颖 msy.plus3 小时前
矩阵、注意力与 GPU:拆解 DeepGEMM 与 FlashMLA
人工智能·llm·gpu·源码解析·1024程序员节·deepseek
救救孩子把6 小时前
RAG 还是 Fine-Tuning?从语义切分、QLoRA 到安全防护的企业级 LLM 优化实战
安全·llm·rag·fine-tuning·qlora
slacker-kian8 小时前
RAG 实战:用 LangChain + LLM 对私有文档做摘要提取
ai·langchain·llm·agent·rag
goehou11 小时前
Agent 卡在第三步,怎么查,一次请求的可观测性
llm·agent·日志·trace·可观测性
goehou11 小时前
Agent 把用户地址发给了模型,数据脱敏该在哪一层做
llm·agent·数据脱敏·隐私合规·工程实践
先吃饱再说11 小时前
LangGraph 状态图:从节点、边到中断恢复,手写一个多 Agent 工作流
人工智能·langchain·llm
AI技术新视界12 小时前
递归自我改进(RSI)是什么
人工智能·llm·1024程序员节·rsi
冬奇Lab1 天前
一天一个开源项目(第246篇):rec —— Recursive 自研的本地推理引擎,在 Mac 上跑 Flint 编码模型,称比 llama.cpp 快 1.4–2.6 倍
人工智能·开源·llm
嘎嘎风1 天前
MiniMind 学习笔记之 25 Web UI 与 LLaMA-Factory:两条“不写代码”的路径
gpt·llm·源码阅读
沛东的认知和实践1 天前
模型会自己伪造工具结果:一次 Agent 评测暴露的 Harness 工程问题——从一句 `count=0`,看模型输出、工具执行和上下文权威的边界
llm·agent