理论部分

前面我们做的事情,本质上都是:给模型一段输入,让模型生成一段输出
但当我们希望模型“帮我们做事”时,仅靠生成文本往往不够:

  • 我们想要实时数据(天气、股价、数据库查询结果),模型本身拿不到
  • 我们想要模型触发动作(发请求、查文件、执行计算),文本并不能直接执行

Function Calling(工具调用)就是把“模型输出”升级为“可执行的结构化意图”:

  1. 我们把可用工具(函数)及参数规则告诉模型
  2. 模型根据用户问题,决定要不要调用工具、调用哪个工具、传什么参数
  3. 我们在代码里执行真实函数,把函数结果回传给模型
  4. 模型基于工具结果生成最终回答

从工程视角看,这里最关键的是:模型只负责决定“调用什么 + 参数是什么”,真正执行仍然由我们的程序掌控。这使得工具调用更可控、更可验证,也更容易接入业务系统。

一个完整的工具调用链路里,会出现三类关键信息:

  • Tool Definition(工具描述):函数名、用途描述、参数 schema(类型、枚举、必填字段)
  • Tool Call(工具调用请求):模型生成的结构化调用意图(函数名 + arguments)
  • Tool Result(工具执行结果):我们执行函数后的返回值(通常是 JSON 字符串或可解析文本)

实践部分

本案例做什么

我们实现一个“智能天气助手”,让模型在需要时自动调用 get_current_weather 工具:

  1. 用户提问(比如“北京天气怎么样?”)
  2. 模型决定调用工具,并给出参数(location、unit)
  3. 我们执行函数(这里用模拟数据代替真实天气 API)
  4. 把工具返回结果喂回模型,生成最终答复

主要代码

本篇使用项目脚本:src/4.1_weather_agent.py

import os
import json
from dotenv import load_dotenv
from openai import OpenAI

# 加载环境变量
load_dotenv()

client = OpenAI(
    api_key=os.getenv("ZHIPUAI_API_KEY"),
    base_url=os.getenv("ZHIPUAI_BASE_URL")
)

# === 1. 定义实际的工具函数 ===
# 这里我们用模拟数据,实际项目中可以调用真实的天气 API
def get_current_weather(location, unit="celsius"):
    """获取指定城市的当前天气"""
    print(f"DEBUG: 正在查询 {location} 的天气...")
    if "北京" in location:
        return json.dumps({"location": "北京", "temperature": "22", "unit": unit, "description": "晴朗"})
    elif "上海" in location:
        return json.dumps({"location": "上海", "temperature": "25", "unit": unit, "description": "多云"})
    elif "广州" in location:
        return json.dumps({"location": "广州", "temperature": "30", "unit": unit, "description": "雷阵雨"})
    else:
        return json.dumps({"location": location, "temperature": "unknown"})

# === 2. 定义工具描述 (Tool Definitions) ===
# 告诉 LLM 有哪些工具可用,以及参数格式
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_current_weather",
            "description": "获取指定城市的当前天气",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "城市名称,如:北京、上海",
                    },
                    "unit": {
                        "type": "string", 
                        "enum": ["celsius", "fahrenheit"],
                        "description": "温度单位,默认为摄氏度"
                    },
                },
                "required": ["location"],
            },
        },
    }
]

# === 3. 对话主循环 ===
def run_conversation():
    print("=== 🌦️ 智能天气助手 (Function Calling Demo) ===")
    print("试着问我:'北京天气怎么样?' 或 '上海和广州哪里更热?' (输入 q 退出)")

    messages = [] # 维护上下文

    while True:
        user_input = input("\n👤 你: ")
        if user_input.lower() in ['q', 'quit']:
            break
            
        # 将用户消息加入历史
        messages.append({"role": "user", "content": user_input})

        # 第一次调用模型:查看是否需要调用工具
        response = client.chat.completions.create(
            model="glm-4-flash", # 确保模型支持 Function Calling
            messages=messages,
            tools=tools,
            tool_choice="auto",  # 让模型自动决定是否调用工具
        )

        response_message = response.choices[0].message
        tool_calls = response_message.tool_calls

        # 如果模型决定调用工具
        if tool_calls:
            # 必须先把模型的这条回复(包含工具调用请求)加入历史
            messages.append(response_message)
            
            print(f"🤖 AI 决定调用工具: {len(tool_calls)} 个请求")

            # 建立函数名到实际函数的映射
            available_functions = {
                "get_current_weather": get_current_weather,
            }

            # 遍历所有工具调用请求
            for tool_call in tool_calls:
                function_name = tool_call.function.name
                function_to_call = available_functions[function_name]
                function_args = json.loads(tool_call.function.arguments)
                
                # 执行真正的函数
                function_response = function_to_call(
                    location=function_args.get("location"),
                    unit=function_args.get("unit"),
                )
                
                print(f"   --> 调用 {function_name}({function_args})")
                print(f"   <-- 返回 {function_response}")

                # 将工具执行结果作为 tool 类型的消息加入历史
                messages.append(
                    {
                        "tool_call_id": tool_call.id,
                        "role": "tool",
                        "name": function_name,
                        "content": function_response,
                    }
                )
            
            # 第二次调用模型:让模型根据工具结果生成最终回答
            print("🤖 AI 正在根据工具结果生成回答...")
            final_response = client.chat.completions.create(
                model="glm-4-flash",
                messages=messages,
            )
            ai_reply = final_response.choices[0].message.content
            print(f"🤖 AI: {ai_reply}")
            
            # 把最终回答也加入历史
            messages.append({"role": "assistant", "content": ai_reply})
            
        else:
            # 不需要调用工具,直接回复
            ai_reply = response_message.content
            print(f"🤖 AI: {ai_reply}")
            messages.append({"role": "assistant", "content": ai_reply})

if __name__ == "__main__":
    run_conversation()

运行方式

在项目根目录执行:

python3 src/4.1_weather_agent.py

运行结果示例

我们可以输入一个需要实时信息的问题,例如:

  • “北京天气怎么样?”
  • “上海和广州哪里更热?”

运行时会看到类似输出(模型的措辞可能不同,但工具调用链路一致):

=== 🌦️ 智能天气助手 (Function Calling Demo) ===
试着问我:'北京天气怎么样?' 或 '上海和广州哪里更热?' (输入 q 退出)

👤 你: 上海和广州哪里更热?
🤖 AI 决定调用工具: 2 个请求
DEBUG: 正在查询 上海 的天气...
   --> 调用 get_current_weather({'location': '上海', 'unit': 'celsius'})
   <-- 返回 {"location": "上海", "temperature": "25", "unit": "celsius", "description": "多云"}
DEBUG: 正在查询 广州 的天气...
   --> 调用 get_current_weather({'location': '广州', 'unit': 'celsius'})
   <-- 返回 {"location": "广州", "temperature": "30", "unit": "celsius", "description": "雷阵雨"}
🤖 AI 正在根据工具结果生成回答...
🤖 AI: ...

总结

这一篇我们跑通了 Function Calling 的完整闭环:模型先决定是否调用工具以及工具参数,我们负责执行真实函数并把结果回传,最后再由模型整合工具结果生成最终回答。

更多推荐