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  # 这才是给用户看的自然语言
相关推荐
DigitalOcean16 小时前
在 DigitalOcean 如何提示词缓存:降低 AI 推理成本与延迟
llm·agent
only-qi18 小时前
美的AI Agent面试题的解析与思考
人工智能·ai·llm·agent·react
武子康18 小时前
VLA 落地先签动作合同:从视觉语言输入到可执行控制指令
人工智能·llm·agent
武子康1 天前
SWE-1.7 的提升究竟来自哪里?接受大量强化学习后训练的 Kimi K2.7 为起点,继续进行大规模 RL
人工智能·llm·agent
用户469368483201 天前
kimi-code 深度掌握系列文章-工具系统:从注册到执行(六)
llm·agent
今日无bug1 天前
用 Prompt 做 NLP 任务开发:几分钟构建一个推理系统
llm·nlp
AINative软件工程1 天前
LLM Streaming 背压工程实践:慢客户端、断流重连与服务端缓冲区的生产设计
node.js·llm
hboot1 天前
AI工程师第六课 - RAG检索增强生成
后端·langchain·llm
用户469368483202 天前
kimi-code 深度掌握系列文章-对话循环TurnFlow(五)
llm·agent
lucas_AI2 天前
Skill-α:教 Agent 学会自己'改说明书'
llm·agent