01-Function Calling 入门:单工具单次调用

从零理解 Function Calling 的完整流程——模型只负责决策和输出参数,真正执行函数的是你自己的代码。通过单工具天气查询示例,掌握工具调用的三步闭环。

2026/6/10
8 分钟阅读
目录

直接看代码注释

"""练习 1:Function Calling —— 单工具、单次调用

本练习只想说明一件事:
    模型【不会】执行函数。它只负责【决定要不要调用】以及【吐出参数】,
    真正去执行 get_weather() 的,是下面我们自己写的 Python 代码。

一次完整的工具调用包含三步:
    1) 我们把「问题 + 工具说明」发给模型;
    2) 模型回一个 tool_call(函数名 + JSON 参数),它自己不执行;
    3) 我们用这些参数真正运行函数,把结果再发回给模型,让它说人话。

云服务用 DeepSeek(OpenAI 兼容接口),密钥/地址/模型名都在根目录 .env:
    DEEPSEEK_API_KEY、DEEPSEEK_BASE_URL、MODEL
"""

import json
import os

from dotenv import load_dotenv
from openai import OpenAI

# 从根目录 .env 读取配置
load_dotenv()

client = OpenAI(
    api_key=os.environ["DEEPSEEK_API_KEY"],
    base_url=os.environ["DEEPSEEK_BASE_URL"],
)
MODEL = os.environ["MODEL"]


# ── 第 1 步:我们的「手」——一个真实的本地函数 ────────────────────────────
# 这就是会被真正执行的代码。这里用假数据,重点不在天气,而在“谁来执行”。
def get_weather(city: str) -> dict:
    """根据城市名返回天气(演示用假数据)。"""
    fake_db = {
        "北京": {"weather": "晴", "temperature": 26},
        "上海": {"weather": "多云", "temperature": 24},
        "广州": {"weather": "雷阵雨", "temperature": 30},
    }
    return fake_db.get(city, {"weather": "未知", "temperature": None})


# ── 第 2 步:工具 schema —— 给模型看的「说明书」 ──────────────────────────
# 注意 description 写的是【什么时候用】,而不是【做什么】。这是 schema 设计的黄金规则。
TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "当用户询问某个城市当前的天气状况或气温时调用。",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "城市名称,例如:北京、上海",
                    }
                },
                "required": ["city"],
            },
        },
    }
]


def run(user_question: str) -> str:
    """跑一遍完整的单工具调用流程,返回模型最终的自然语言回答。"""
    messages = [{"role": "user", "content": user_question}]

    # ── 第 3 步:把问题 + 工具说明发给模型,看它要不要调用 ──────────────
    first = client.chat.completions.create(
        model=MODEL,
        messages=messages,
        tools=TOOLS,
    )
    msg = first.choices[0].message

    # 模型决定不调用工具,直接回答(说明这个问题不需要工具)
    if not msg.tool_calls:
        print("【模型判断】无需工具,直接回答。")
        return msg.content

    # ── 第 4 步:模型决定调用,但它只给了「函数名 + 参数」,并没有执行 ──
    tool_call = msg.tool_calls[0]
    fn_name = tool_call.function.name
    fn_args = json.loads(tool_call.function.arguments)  # 模型吐出的 JSON 参数
    print(f"【模型决定】调用 {fn_name},参数 = {fn_args}")

    # ── 第 5 步:真正执行的是我们的代码,不是模型 ──────────────────────
    if fn_name == "get_weather":
        result = get_weather(**fn_args)
    else:
        result = {"error": f"未知工具 {fn_name}"}
    print(f"【我们执行】{fn_name} 返回 = {result}")

    # ── 第 6 步:把执行结果发回模型,让它结合结果说人话 ────────────────
    messages.append(msg)  # 模型刚才那条「我要调用工具」的消息
    messages.append(
        {
            "role": "tool",
            "tool_call_id": tool_call.id,
            "content": json.dumps(result, ensure_ascii=False),
        }
    )
    second = client.chat.completions.create(
        model=MODEL,
        messages=messages,
        tools=TOOLS,
    )
    return second.choices[0].message.content


if __name__ == "__main__":
    question = "北京今天天气怎么样?"
    print(f"【用户提问】{question}\n")
    answer = run(question)
    print(f"\n【最终回答】{answer}")