Function Calling 使用文档

638 阅读11分钟

一、Function Calling 概述

Function Calling 是一项允许大型语言模型(如 GPT 系列)在生成文本的过程中调用外部函数或服务的功能。它使得开发者能够通过自然语言指令触发预定义的函数,将复杂的操作封装在函数中,然后通过简单的自然语言调用这些函数,从而实现更复杂、更动态的任务处理。这一功能大大增强了大模型的应用潜力,使其可以与外部系统进行交互,处理超出自身知识范围的问题。

(一)核心作用

  1. 扩展模型能力:通过调用外部函数,模型可以处理更复杂的任务,如查询数据库、调用 API、执行计算等,突破了纯文本生成的限制。
  2. 动态任务处理:模型可以根据用户请求动态选择需要调用的函数,实现灵活的任务处理。
  3. 提高准确性:通过调用外部函数获取准确的数据或执行精确的计算,减少模型“编造”信息的可能性。

(二)应用场景

  1. 数据查询:调用外部函数查询数据库或 API,获取实时数据。例如,用户问“今天的天气怎么样?”,模型调用天气 API 获取实时天气数据。
  2. 计算任务:调用外部函数执行复杂的计算任务。例如,用户问“计算 12345 的平方根。”,模型调用计算函数返回结果。
  3. 任务自动化:调用外部函数执行自动化任务。例如,用户问“发送一封邮件给张三。”,模型调用邮件发送函数完成任务。
  4. 知识增强:调用外部函数从知识库中检索信息。例如,用户问“谁是美国的第一任总统?”,模型调用知识库查询函数返回答案。
  5. 智能家居控制:在智能家居系统中,定义函数来控制各个设备,并通过自然语言指令进行控制。
  6. 旅游服务助手:构建旅游服务助手,通过 API 连接到数据库中的产品目录,获取商品列表和进行商品购买;通过检索增强生成(RAG)连接到存储和管理文档数据的存储系统,从非结构化文本中获取相关信息。

二、Function Calling 数据结构

(一)函数定义的数据结构

在使用 Function Calling 时,需要向模型描述可用的函数。函数定义通常使用 JSON Schema 来描述函数的名称、描述、参数等信息,以下是一个示例:

{
    "name": "get_current_weather",
    "description": "Get the current weather in a given location",
    "parameters": {
        "type": "object",
        "properties": {
            "location": {
                "type": "string",
                "description": "The city and state, e.g. San Francisco, CA"
            },
            "unit": {
                "type": "string",
                "enum": ["celsius", "fahrenheit"]
            }
        },
        "required": ["location"]
    }
}
  • name:函数的名称,用于标识函数。
  • description:函数的描述,用于说明函数的作用。
  • parameters:函数的参数,使用 JSON Schema 描述参数的类型、属性等信息。
    • type:参数的类型,如 objectstringinteger 等。
    • properties:参数的属性,每个属性对应一个参数。
    • required:必需的参数列表。

(二)模型生成的函数调用数据结构

当模型决定调用函数时,会生成一个包含函数调用所需参数的结构化输出,通常是一个 JSON 对象,例如:

{
    "tool_calls": [
        {
            "function": {
                "name": "get_current_weather",
                "arguments": "{\"location\": \"Boston, MA\"}"
            }
        }
    ]
}
  • tool_calls:一个数组,包含模型建议调用的函数信息。
    • function:函数调用的详细信息。
      • name:要调用的函数名称。
      • arguments:函数调用所需的参数,以字符串形式存在的 JSON 对象。

三、Function Calling 使用例子

(一)使用 OpenAI API 进行天气查询的例子

以下是一个使用 OpenAI API 进行天气查询的完整例子:

import requests
import os
import json

# 配置 OpenAI API 请求的 URL 和认证信息
url = "https://api.openai.com/v1/chat/completions"
auth = ('', os.getenv('OPENAI_API_KEY'))
headers = {
    "Content-Type": "application/json"
}

# 定义函数描述
functions = [
    {
        "name": "get_current_weather",
        "description": "Get the current weather in a given location",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "The city and state, e.g. San Francisco, CA"
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"]
                }
            },
            "required": ["location"]
        }
    }
]

# 定义用户请求
messages = [
    {"role": "user", "content": "What is the weather like in Boston?"}
]

# 构建请求数据
request_data = {
    "model": "gpt-3.5-turbo-0613",
    "messages": messages,
    "functions": functions
}

# 发送请求
response = requests.post(url, auth=auth, headers=headers, data=json.dumps(request_data))

# 解析响应
response_data = response.json()
if 'choices' in response_data and len(response_data['choices']) > 0:
    message = response_data['choices'][0]['message']
    if 'function_call' in message:
        function_call = message['function_call']
        function_name = function_call['name']
        function_args = json.loads(function_call['arguments'])
        print(f"Function name: {function_name}")
        print(f"Function arguments: {function_args}")
        # 这里可以根据函数名和参数调用实际的天气 API
        # 假设调用天气 API 后得到结果
        weather_result = "22 degrees Celsius, sunny"
        # 将函数调用结果再次发送给模型
        messages.append(message)
        messages.append({
            "role": "function",
            "name": function_name,
            "content": weather_result
        })
        # 再次发送请求获取最终回复
        request_data = {
            "model": "gpt-3.5-turbo-0613",
            "messages": messages
        }
        response = requests.post(url, auth=auth, headers=headers, data=json.dumps(request_data))
        final_response = response.json()
        print(f"Final response: {final_response['choices'][0]['message']['content']}")

(二)通义千问大模型的 Function Call 例子

from dashscope import Generation
from datetime import datetime
import random
import json
import requests

# 定义工具列表
tools = [
    # 工具 1:获取当前时刻的时间
    {
        "type": "function",
        "function": {
            "name": "get_current_time",
            "description": "当你想知道现在的时间时非常有用。",
            "parameters": {}  # 因为获取当前时间无需输入参数,因此 parameters 为空字典
        }
    },
    # 工具 2:获取指定城市的天气
    {
        "type": "function",
        "function": {
            "name": "get_current_weather",
            "description": "当你想查询指定城市的天气时非常有用。",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "城市或县区,比如北京市、杭州市、余杭区等。"
                    }
                },
                "required": ["location"]
            }
        }
    }
]

# 基于外部网站的天气查询工具
def get_current_weather(location):
    api_key = "9fa42a532a9338bf217afd7ac47a565a"  # 替换为你自己的 OpenWeatherMap API 密钥
    url = f"http://api.openweathermap.org/data/2.5/weather?q={location}&appid={api_key}&units=metric"
    response = requests.get(url)
    if response.status_code == 200:
        data = response.json()
        weather = data["weather"][0]["description"]
        temp = data["main"]["temp"]
        return json.dumps({"location": location, "weather": weather, "temperature": temp})
    else:
        return json.dumps({"location": location, "error": "Unable to fetch weather data"})

# 查询当前时间的工具
def get_current_time():
    # 获取当前日期和时间
    current_datetime = datetime.now()
    # 格式化当前日期和时间
    formatted_time = current_datetime.strftime('%Y-%m-%d %H:%M:%S')
    # 返回格式化后的当前时间
    return f"当前时间:{formatted_time}。"

# 封装模型响应函数
def get_response(messages):
    response = Generation.call(
        model='qwen-plus',
        messages=messages,
        tools=tools,
        seed=random.randint(1, 10000),  # 设置随机数种子
        result_format='message'  # 将输出设置为 message 形式
    )
    return response

# 主函数
def call_with_messages():
    print('\n')
    messages = [
        {
            "content": input('请输入:'),  # 提问示例:"现在几点了?" "一个小时后几点" "北京天气如何?"
            "role": "user"
        }
    ]
    # 模型的第一轮调用
    first_response = get_response(messages)
    assistant_output = first_response.output.choices[0].message
    print(f"\n大模型第一轮输出信息:{first_response}\n")
    messages.append(assistant_output)
    if 'tool_calls' not in assistant_output:  # 如果模型判断无需调用工具,则将 assistant 的回复直接打印出来,无需进行模型的第二轮调用
        print(f"最终答案:{assistant_output.content}")
        return
    # 如果模型选择的工具是 get_current_weather
    elif assistant_output['tool_calls'][0]['function']['name'] == 'get_current_weather':
        tool_call = assistant_output['tool_calls'][0]
        function_name = tool_call['function']['name']
        function_args = json.loads(tool_call['function']['arguments'])
        location = function_args['location']
        result = get_current_weather(location)
        messages.append({
            "role": "function",
            "name": function_name,
            "content": result
        })
        # 模型的第二轮调用
        second_response = get_response(messages)
        print(f"最终答案:{second_response.output.choices[0].message.content}")
    elif assistant_output['tool_calls'][0]['function']['name'] == 'get_current_time':
        tool_call = assistant_output['tool_calls'][0]
        function_name = tool_call['function']['name']
        result = get_current_time()
        messages.append({
            "role": "function",
            "name": function_name,
            "content": result
        })
        # 模型的第二轮调用
        second_response = get_response(messages)
        print(f"最终答案:{second_response.output.choices[0].message.content}")

if __name__ == '__main__':
    call_with_messages()

四、Function Calling 返回值

(一)模型生成的函数调用返回值

当模型决定调用函数时,会返回一个包含函数调用信息的 JSON 对象,如上述例子中的 function_call 字段:

{
    "name": "get_current_weather",
    "arguments": "{\"location\": \"Boston, MA\"}"
}
  • name:要调用的函数名称。
  • arguments:函数调用所需的参数,以字符串形式存在的 JSON 对象。

(二)函数执行后的返回值

函数执行后的返回值取决于具体的函数实现。例如,天气查询函数可能返回一个包含天气信息的 JSON 对象:

{"location": "Boston, MA", "weather": "sunny", "temperature": 22}

五、支持 Function Calling 的模型

目前,多个先进的 AI 大模型已经支持 Function Calling 功能,包括但不限于:

  • OpenAI 的 GPT 系列:从 GPT - 3.5 开始引入了 Function Calling 的功能,支持的模型有 gpt - 4o、gpt - 4o - 2024 - 05 - 13、gpt - 4 - turbo、gpt - 4 - turbo - 2024 - 04 - 09、gpt - 4 - turbo - preview、gpt - 4 - 0125 - preview、gpt - 4 - 1106 - preview、gpt - 4、gpt - 4 - 0613、gpt - 3.5 - turbo、gpt - 3.5 - turbo - 0125、gpt - 3.5 - turbo - 1106 和 gpt - 3.5 - turbo - 0613 等。
  • 阿里的 Qwen 系列:大部分的 Qwen 系列模型都支持 Function Calling,如通义千问 - Max、通义千问 - Plus(非思考模式)、通义千问 - Turbo(非思考模式)、通义千问 - Long、Qwen3(非思考模式)、Qwen2.5、Qwen2、Qwen1.5 等。
  • DeepSeek 系列:硅基流动平台上的满血版和蒸馏版模型也明确支持 Function Calling。
  • 智谱系列:智谱 AI 的大模型实测也支持 Function Calling。
  • Anthropic 的 Claude:Claude 模型同样支持 Function Calling,这使得它能够与外部系统更紧密地集成。
  • 谷歌的 Gemini API:最近也开始支持函数调用。

六、Function Calling 的工作流程

(一)基本流程

  1. 用户输入请求:用户通过自然语言向模型提出问题或请求。这些问题或请求可能需要调用外部函数来获取答案或执行某些操作。
  2. 模型解析意图:模型接收到用户输入后,会解析并理解输入内容。模型会根据其训练数据和算法判断是否需要调用函数,并确定要调用的函数及其参数。
  3. 生成函数调用:如果模型确定需要调用函数,它会生成一个包含函数调用所需参数的结构化输出。这通常是一个 JSON 对象,其中包含函数名、参数列表等信息。这个 JSON 对象是以字符串形式存在的,需要在实际调用函数之前进行解析。
  4. 函数调用执行:在代码中,需要解析这个字符串化的 JSON 对象,将其转换为有效的数据结构(如字典或对象),并使用这些参数调用相应的函数。这个过程是在代码环境中完成的,而不是在模型内部。模型只是提供了调用函数所需的参数和信息。
  5. 处理函数结果:函数调用执行完成后,需要将函数的结果返回给模型。这通常通过将结果附加到模型中再次调用模型来实现。模型会接收并处理这些结果,然后生成一个自然语言回复给用户,总结或解释函数调用的结果。

(二)以 OpenAI API 为例的详细流程

  1. 代码调用 LLM API:应用程序向 API 发送请求,其中包含提示(prompt)以及 LLM 可以调用的函数的定义。
  2. LLM 做出决策:LLM 会根据提示和函数定义,决定是直接回复用户,还是“可以调用”一个或多个函数。
  3. LLM API 返回结果:API 会将 LLM 的决策返回给应用程序,其中包含需要调用的函数名称以及对应的参数。
  4. 执行函数:应用程序根据 LLM API 返回的信息,调用指定的函数,并传入对应的参数。
  5. 再次调用 LLM API:应用程序将函数执行的结果以及原始提示再次发送给 LLM API,以便 LLM 能够结合新的信息生成更准确的回复。

七、使用 Function Calling 的注意事项

(一)函数定义方面

需要正确定义函数及其参数,确保模型能够生成正确的函数调用。函数的描述要清晰准确,参数的类型和属性要使用 JSON Schema 进行规范描述。

(二)错误处理方面

需要处理函数调用过程中可能出现的错误,如 API 调用失败、参数错误等。可以在代码中添加适当的错误处理逻辑,确保程序的健壮性。

(三)安全性方面

需要确保函数调用的安全性,防止恶意请求或数据泄露。例如,在调用外部 API 时,要对用户输入进行严格的验证和过滤,避免 SQL 注入、跨站脚本攻击等安全问题。

(四)令牌方面

函数在底层被注入到系统消息中,并且模型已经对其进行了训练。这意味着函数会计入模型的上下文限制,并且会被视为输入令牌进行计费。如果遇到上下文限制,建议限制函数的数量或者为函数参数提供的文档的长度,也可以使用微调来减少所使用的令牌数量。

(五)模型选择方面

并不是所有的 LLM 模型都支持 Function Calling 功能。在选择模型时,需要确保所选模型经过了相关训练以支持该功能。对于不支持的模型,整合 LangChain 框架有机会实现 Function Calling 的效果,但可能在精确度上会有落差。

综上所述,Function Calling 是一项强大的功能,它为大模型与外部系统的交互提供了便利,扩展了大模型的应用场景和能力。通过合理使用 Function Calling,可以构建出更加智能、灵活的应用程序。