引言

在人工智能领域,大模型的应用日益广泛,但其本身的不稳定性却成为了一个亟待解决的痛点。这种不稳定性常常导致模型产生“幻觉”,即生成与事实不符的内容,给用户带来了困扰。为了应对这一挑战,研究者们提出了多种创新解决方案,其中最引人注目的便是功能调用(Function Calling)和检索增强生成(RAG)。这些方法不仅旨在提高模型的可靠性,还希望通过更智能的方式来提升用户体验。接下来,我们将深入探讨功能调用的原理及其在解决大模型幻觉问题中的应用。

一、什么是Function calling

用官方的解释,就一句话,“Enable models to fetch data and take actions.”,可以使大模型获取数据并采取行动。
简单来说,外部系统的API或功能就像一个工具箱,提供了多种工具供大模型选择。大模型能够自主判断并选择最合适的工具来获取所需的数据,随后根据这些工具的结果进行下一步的操作和决策。这种灵活性不仅提升了大模型的智能水平,也使其在处理复杂任务时更加高效。

官方地址:https://platform.openai.com/docs/guides/function-calling#overview

二、Function calling运行流程

在这里插入图片描述用户向大模型发起提问,大模型自己判断是否需要调用外部工具,如果需要则调用具体的函数并获取到函数的返回结果,根据返回结果二次加工润色回答,最后返回给用户。如果不需要则直接返回给用户答案。
比如,我们询问大模型”今天济南的天气怎么样?适合爬山吗?“这种实时类的问题,大模型无法作答,只能通过外部函数去获取信息,如专用的获取天气的api,更甚者,胡乱生成一个回答返回给用户,也就是常说的”幻觉“。

三、Function calling步骤

Function calling的步骤大致分为:
定义函数(工具)、将工具赋予大模型、大模型选择调用工具、大模型执行工具获取结果、将解析的结果再次传入大模型进行二次响应、返回给用户

案例

由于代码使用Jupyter写的,我直接将执行结果贴在相应代码下面。
案例中获取天气api的key经过处理。

import requests
import os
from dotenv import load_dotenv
from openai import OpenAI
# 秘钥
load_dotenv()
WILDCARD_API_KEY = os.getenv('WILDCARD_API_KEY')
WILDCARD_API = os.getenv('WILDCARD_API')
client = OpenAI(api_key=WILDCARD_API_KEY, base_url=WILDCARD_API)
  1. 定义函数(工具),这里定义了两个函数,一个是获取天气的函数、一个是做参数累加的函数。
def get_weather(location):
    """
    获取天气
    :param location: 地区
    :return: 天气信息
    """
    url = f"https://apis.tianapi.com/tianqi/index?key=a69b86e670dc4e86001f7c7795&city={location}&type=1"
    response = requests.get(url)
    data = response.json()
    # print(data.get('result').get('weather'))
    return f"{location}的天气是{data['result']['weather']},温度{data['result']['real']}。Tips:{data['result']['tips']}"

def add_sum(*args):
    """
    定义一个add_sum函数,可传入多个参数,对所有参数进行相加运算
    :param args: 参数,传入的是一个元组
    :return: 相加后的结果
    """
    # print(f"add_sum函数打印内容:{args},{type(args)}")
    result = 0
    for num in args[0]:
        result += int(num)
    return result
  1. 将工具赋予大模型,将我们封装好的函数赋予给大模型。工具集是列表,列表里的元素是每个工具,每个工具是一个字典,多个工具就多个字典。
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "用于获取天气情况的函数,获取城市或者地区当前的天气情况",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "城市或地区,例如北京、香港",
                    },
                },
                "required": ["location"],
            },
        }
    },
    {
        "type": "function",
        "function": {
            "name": "add_sum",
            "description": "用于计算多个数累加求和的函数,比如从1累加到100",
            "parameters": {
                "type": "object",
                "properties": {
                    "*args": {
                        "type": "array",
                        "items": {
                            "type": "number"
                        },
                    },
                },
                "required": ["*args"],
            },
        }
    },
]

关键字解释:

  • tools: 这是一个包含工具描述的列表。在这个上下文中,每个工具都是一个可以被调用的函数或操作。

  • type: 这个关键字指定工具的类型。在这里,它被设置为 "function",表明这个工具是一个可调用的函数。

  • function: 这是一个对象,包含关于这个函数的详细描述。

    • name: 这是函数的名称(标识),在这里是 "get_weather"

    • description: 这是对函数功能的简要描述。这里描述越清晰越好,大模型是否可以调用到这个工具,除了大模型本身能力以外,其次就与这里的描述相关。

    • parameters: 这个对象定义了函数所需的参数。

      • type: 参数的类型,object意味着函数参数应该作为一个对象提供。这里一般为object

      • properties: 这是一个对象,定义了这个函数需要哪些具体的参数。定义每个参数及每个参数的类型,一般为objectstringnumberarraynull

      • required: 这是一个数组,列出了调用这个函数时所必需的参数。

  1. 大模型选择调用工具
messages = [
    {"role": "system", "content": "你是一名全能小助手,无所不能,可以执行各种函数功能,如加法计算、获取天气等。在需要时调用适当的函数来处理。对于回答不作任何解释"}, # system提示词
    # {"role": "user", "content": "今天济南天气怎么样?"}
    {"role": "user", "content": "桌子上有6个苹果3本书,吃了2个苹果,桌子上还有多少个水果"}
]

response = client.chat.completions.create(
    model="gpt-3.5-turbo",
    temperature=0,
    messages=messages,
    tools=tools,
    tool_choice="auto"
    # tool_choice={"type": "function", "function": {"name": "get_weather"}}
)

response.choices[0].message

关键字解释:

  • model="gpt-3.5-turbo": 指定要使用的模型,我这里使用的是 gpt-3.5-turbo

  • temperature=0: 控制生成文本的随机性。temperature=0 会使输出更加确定和一致,适合需要精确回答的场景。在这里比较符合Function calling调用场景。

  • messages=messages: 这个参数通常包含一个消息列表,表示对话的上下文。每个消息通常是一个字典,包含角色(如 “user” 或 “assistant”)和内容。

  • tools=tools: 这个参数是Function calling的关键参数,指定大模型可以使用的工具。

  • tool_choice="auto": auto默认参数,调用零个、一个或多个函数。 也可以为RequiredForced FunctionRequired表示调用一个或多个函数,Forced Function表示只调用特点函数。

运行结果

ChatCompletionMessage(content=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_qZAY38tJIktqCA1kaL2IJqTe', function=Function(arguments='{"args":[6,-2]}', name='add_sum'), type='function')])

从结果可以看到大模型调用了add_sum工具,还打印出了工具的类型、名称、参数。如果调用不到工具tools_calls字段为空。
这里一定要保证tool_call.function.arguments的格式准确(json格式),如果格式错误后面解析参数会异常,导致执行函数报错。

  1. 大模型执行工具,并获取结果。具体的函数执行就是在这里,大模型从这一步骤拿到工具的返回结果。
import json
response_message = response.choices[0].message
tool_calls = response_message.tool_calls

# 如果调用到了某个工具
if tool_calls:
    available_functions = {
        "get_weather": get_weather,
        "add_sum": add_sum,
    }
    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) # 获取调用工具的参数

        # 不同的函数处理方式不同
        if function_name == "add_sum":
            # print(type(function_args['args']))
            function_response = function_to_call(function_args['args'])
            function_response = str(function_response) + f"回答计算结果,不要判断对错" # 优化提示词
        else:
            function_response = function_to_call(**function_args)
        
        print(function_response) # add_sum(6, -2)    4 
  1. 将解析的结果再次传入大模型进行二次响应,返回结果给用户
messages = messages[0:2] # 多次调用大模型时,清除messages,避免垃圾消息影响结果。
# 将工具的返回结果,也就是上一步的function_response,封装到messages中,重新给到大模型
messages.append(
    {
        "role": "function", # 这里使用function,表示是函数的结果
        "tool_call_id": tool_call.id,  # 标记当前函数调用的 ID
        "name": function_name, # 函数名
        "content": str(function_response), # 函数相应结果,将返回值转换为字符串格式,确保其可以存储在 JSON 格式的消息中。
    }
)

# print(messages)

# 再次调用大模型
fin_response = client.chat.completions.create(
    model="gpt-3.5-turbo",
    messages=messages,
)
print("---")
print(fin_response.choices[0].message.content)

运行结果

---
4个水果(4个苹果)

测试获取天气工具(完整代码)

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2025/1/22
# @Author : liangpp
# @Email : mobaicloud@163.com
# @Software : PyCharm 2024

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

# 秘钥
load_dotenv()
WILDCARD_API_KEY = os.getenv('WILDCARD_API_KEY')
WILDCARD_API = os.getenv('WILDCARD_API')
client = OpenAI(api_key=WILDCARD_API_KEY, base_url=WILDCARD_API)


def get_weather(location):
    """
    获取天气
    :param location: 地区
    :return: 天气信息
    """
    url = f"https://apis.tianapi.com/tianqi/index?key=a69b86e670dc4e86001f7c7795&city={location}&type=1"
    response = requests.get(url)
    data = response.json()
    # print(data.get('result').get('weather'))
    return f"{location}的天气是{data['result']['weather']},温度{data['result']['real']}。Tips:{data['result']['tips']}"


def add_sum(*args):
    """
    定义一个add_sum函数,可传入多个参数,对所有参数进行相加运算
    :param args: 参数,传入的是一个元组
    :return: 相加后的结果
    """
    # print(f"add_sum函数打印内容:{args},{type(args)}")
    result = 0
    for num in args[0]:
        result += int(num)
    return result


tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "用于获取天气情况的函数,获取城市或者地区当前的天气情况",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "城市或地区,例如北京、香港",
                    },
                },
                "required": ["location"],
            },
        }
    },
    {
        "type": "function",
        "function": {
            "name": "add_sum",
            "description": "用于计算多个数累加求和的函数,比如从1累加到100",
            "parameters": {
                "type": "object",
                "properties": {
                    "*args": {
                        "type": "array",
                        "items": {
                            "type": "number"
                        },
                    },
                },
                "required": ["*args"],
            },
        }
    },
]

messages = [
    {"role": "system",
     "content": "你是一名全能小助手,无所不能,可以执行各种函数功能,如加法计算、获取天气等。在需要时调用适当的函数来处理。对于回答不作任何解释"},
    {"role": "user", "content": "今天济南天气怎么样?适合爬山吗"}
]

response = client.chat.completions.create(
    model="gpt-3.5-turbo",
    temperature=0,
    messages=messages,
    tools=tools,
    tool_choice="auto"
)

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

if tool_calls:
    available_functions = {
        "get_weather": get_weather,
        "add_sum": add_sum,
    }
    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)
        if function_name == "add_sum":
            function_response = function_to_call(function_args['args'])
            function_response = str(function_response) + f"回答计算结果,不要判断对错"
        else:
            function_response = function_to_call(**function_args)


        messages = messages[0:2]
        messages.append(
            {
                "role": "function",
                "tool_call_id": tool_call.id,
                "name": function_name,
                "content": str(function_response),
            }
        )

        # print(messages)

        fin_response = client.chat.completions.create(
            model="gpt-3.5-turbo",
            messages=messages,
        )
        print("---")
        print(fin_response.choices[0].message.content)

运行结果
在这里插入图片描述天气情况
在这里插入图片描述当然你可以询问任何关于天气的问题,比如温度,适宜的活动,穿衣等。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐