LLM Function Calling 实战指南:让 AI 模型调用外部工具

# LLM Function Calling 实战指南:让 AI 模型调用外部工具

## 背景介绍

大语言模型的能力边界在哪里?早期的 GPT 模型只能生成文本,无法获取实时信息,也无法执行具体操作。但现在,Function Calling 功能让 LLM 获得了”动手能力”——它可以根据用户需求调用外部 API、查询数据库、执行计算,甚至操作其他系统。

Function Calling 首次大规模出现在 GPT-4 Turbo 和 Claude 3 系列模型中,现在已经成为主流 LLM 的标准配置。这项技术的核心价值在于:模型不再是一个封闭的文本生成器,而是一个可以与真实世界交互的智能代理。

然而,实际实现 Function Calling 并不是一件简单的事情。开发者经常遇到以下问题:模型生成的函数调用参数格式错误、复杂参数类型(如嵌套对象、数组)处理失败、没有合适的调试工具、不知道如何设计有效的工具描述。这些问题极大地限制了 Function Calling 的实用性。

本文将通过一个完整的实战案例,带你从零实现 Function Calling 功能。我们会构建一个简化的”天气助手”,让用户询问天气时,模型自动调用外部天气 API 获取数据。

## 问题描述

在使用 LLM 的 Function Calling 功能时,开发者通常会遇到以下几类问题:

**第一类是参数格式问题。** 模型生成的 JSON 参数经常缺少必要的引号、逗号,或者使用了错误的数据类型。例如,本应该是字符串的字段被错误地识别为数字,或者数组被错误地拆分 为多个独立字段。

**第二类是复杂类型处理问题。** 当工具函数包含嵌套参数(如对象数组、可选字段、枚举类型)时,模型的表现会明显下降。它可能无法正确理解参数之间的层级关系,或者遗漏某些可选字段。

**第三类是工具描述设计问题。** 怎么样的描述才能让模型准确理解函数的用途和参数格式?描述太简单会导致模型乱调用,描述太复杂又会让模型无所适从。

**第四类是调试和监控问题。** Function Calling 涉及多个步骤的交互:用户输入 → 模型识别意图 → 生成调用参数 → 执行函数 → 返回结果 → 生成最终回复。如何追踪这个流程中的每一个环节?

## 详细步骤

### 第一步:设计工具函数

首先,我们需要定义一个外部工具。在这个例子中,我们设计一个简化的天气查询工具:

def get_weather(city: str, date: str = "today") -> dict:
    """
    查询指定城市的天气信息
    
    参数:
        city: 城市名称,如 "北京"、"上海"
        date: 日期,可选值为 "today"、"tomorrow"、或具体日期 "2024-01-15"
    
    返回:
        包含天气状况、温度、湿度等信息的字典
    """
    # 模拟的天气数据
    weather_data = {
        "北京": {"today": {"condition": "晴", "temperature": "15°C", "humidity": "45%"}},
        "上海": {"today": {"condition": "多云", "temperature": "18°C", "humidity": "62%"}}
    }
    
    city_data = weather_data.get(city, {})
    date_data = city_data.get(date, city_data.get("today", {}))
    
    return {
        "city": city,
        "date": date,
        "condition": date_data.get("condition", "未知"),
        "temperature": date_data.get("temperature", "N/A"),
        "humidity": date_data.get("humidity", "N/A")
    }

这个工具函数的设计遵循以下原则:参数类型尽量简单、使用默认值减少必填字段、返回结构化的字典对象。在实际开发中,你可能需要调用真实的天气 API(如 OpenWeatherMap),但核心模式是相同的。

### 第二步:定义工具描述

工具描述是模型理解函数用途的关键。以下是我们为天气工具定义的描述:

{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "查询指定城市和日期的天气状况。当用户询问天气、温度、湿度等信息时使用。",
        "parameters": {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "城市名称,如 北京、上海、杭州"
                },
                "date": {
                    "type": "string",
                    "description": "查询的日期,可选 'today'、'tomorrow' 或具体日期如 '2024-01-15'",
                    "default": "today"
                }
            },
            "required": ["city"]
        }
    }
}

描述设计的关键点包括:function 的 name 必须与实际函数名称一致;description 要说明函数的适用场景而不是仅仅重复参数名称;parameters 中的每个字段都需要有清晰的 description;必填字段放在 required 数组中。

### 第三步:实现调用逻辑

现在我们来实现完整的 Function Calling 流程。这里使用 OpenAI 的 API 格式,你可以根据使用的模型进行相应调整:

import json
import openai
from typing import Any, Dict, List, Optional

# 配置你的 API Key
openai.api_key = "your-api-key-here"

# 定义可用工具
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "查询指定城市和日期的天气状况。当用户询问天气、温度、湿度等信息时使用。",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "城市名称,如 北京、上海、杭州"
                    },
                    "date": {
                        "type": "string",
                        "description": "查询的日期,可选 'today'、'tomorrow' 或具体日期",
                        "default": "today"
                    }
                },
                "required": ["city"]
            }
        }
    }
]

# 函数实现映射
function_map = {
    "get_weather": get_weather
}

def chat_with_functions(messages: List[Dict]) -> str:
    """
    处理带有函数调用的对话
    """
    # 第一次调用:让模型决定是否需要调用函数
    response = openai.ChatCompletion.create(
        model="gpt-4-turbo",
        messages=messages,
        tools=tools,
        tool_choice="auto"
    )
    
    response_message = response["choices"][0]["message"]
    messages.append(response_message)
    
    # 检查模型是否请求了函数调用
    tool_calls = response_message.get("tool_calls")
    
    if tool_calls:
        # 处理每个函数调用
        for tool_call in tool_calls:
            function_name = tool_call["function"]["name"]
            function_args = json.loads(tool_call["function"]["arguments"])
            
            # 执行实际的函数
            function_response = function_map[function_name](**function_args)
            
            # 将函数结果添加到对话中
            messages.append({
                "tool_call_id": tool_call["id"],
                "role": "tool",
                "name": function_name,
                "content": json.dumps(function_response, ensure_ascii=False)
            })
        
        # 第二次调用:让模型根据函数结果生成最终回复
        final_response = openai.ChatCompletion.create(
            model="gpt-4-turbo",
            messages=messages
        )
        
        return final_response["choices"][0]["message"]["content"]
    
    # 如果没有函数调用,直接返回模型回复
    return response_message["content"]

这段代码展示了 Function Calling 的完整流程:第一次调用时,模型分析用户输入,决定是否需要调用工具;如果需要,生成调用参数;执行函数后,将结果返回给模型;第二次调用时,模型结合函数结果生成最终回复。

### 第四步:测试与调试

让我们测试几个典型场景:

# 测试用例
test_cases = [
    "北京明天天气怎么样?",
    "上海今天冷吗?",
    "告诉我杭州的天气"
]

for user_input in test_cases:
    print(f"\n用户: {user_input}")
    messages = [{"role": "user", "content": user_input}]
    result = chat_with_functions(messages)
    print(f"助手: {result}")

## 运行结果

执行测试后,我们得到以下输出:

用户: 北京明天天气怎么样?
助手: 根据查询结果,明天北京天气为晴,气温 15°C,湿度 45%。

用户: 上海今天冷吗?
助手: 上海今天多云,气温 18°C,湿度 62%。总体来说体感应该比较舒适,不算冷。

用户: 告诉我杭州的天气
助手: 抱歉,我没有杭州市的天气数据。不过可以使用真实的天气 API(如 OpenWeatherMap)来获取更多城市的数据。

从结果可以看出几个要点:模型成功识别了”明天”这个日期参数,正确调用了 get_weather(city=”北京”, date=”tomorrow”);模型不仅返回了原始数据,还理解了”冷吗”这个问法,给出了更人性化的回复��最��一个测试用例展示了工具的边界——当城市不在预设数据中时,模型能够给出合理的回复而不是胡乱回答。

## 总结

Function Calling 这项技术为 LLM 打开了一扇通往外部世界的大门。通过本文的实战案例,我们总结了以下关键点:

**在工具设计层面**,函数签名要尽量简单,避免过于复杂的参数类型;必填参数尽量少,给可选参数设置合理的默认值;返回结构化的 JSON 数据,方便模型解析。

**在描述设计层面**,description 要说明函数的适用场景,而不是简单重复参数名称;每个参数的 description 都要写清楚具体的值格式和示例;required 数组只包含真正的必填参数。

**在实现层面**,Function Calling 通常需要两次 API 调用:第一次让模型决定是否调用函数,第二次让模型结合函数结果生成回复;要注意处理函数执行失败的情况,返回有意义的错误信息;保持函数名称和工具描述中的名称一致。

**在调试层面**,保存完整的 message 历史可以帮助你追踪问题;打印 intermediate steps(中间步骤)可以帮助你理解模型的决策过程;在生产环境中,要做好日志记录和监控。

Function Calling 还在快速发展中。现在已经有一些框架(如 LangChain、LlamaIndex)提供了更抽象的 Function Calling 能力封装。对于复杂的应用场景,建议直接使用这些框架,它们处理好了很多边界情况。随着模型的进一步改进,Function Calling 将会更加智能和可靠,成为 AI Agent 系统的核心组件。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇