跳转至

大模型Function Call的应用

学习目标

  • 了解什么是Function Call
  • 理解Function Call工作的原理
  • 掌握Function Call开发应用的代码

1 什么是Function Call

2023年6月13日 OpenAI 公布了 Function Call(函数调用) 功能,Function Call 允许开发者向 GPT-4 和 GPT-3.5-turbo 模型描述函数,模型会智能地选择输出一个包含调用这些函数参数的JSON对象。这是一种更可靠地将 GPT 的功能与外部工具和 API 相连接的新方法。

GPT4 及 GPT-3.5-turbo 模型之所以能够使用函数 Function Call 功能,是因为这些模型经过训练,不仅可以检测到何时需要调用函数(根据用户的输入),并且又可以回复符合函数参数的 JSON对象,而不是直接返回常规的文本,简而言之:函数调用使开发者能够更可靠地从模型中获得结构化数据。


那么 Function Call 可以解决大模型什么问题:

  • 信息实时性:大模型训练时使用的数据集往往有时间限制,无法包含最新的信息,如最新的新闻、实时股价等。通过Function Call,模型可以实时获取最新数据,提供更加时效的服务。
  • 数据局限性:模型的训练数据量庞大但有限,无法覆盖所有可能的查询,如医学、法律等领域的专业咨询。Function Call允许模型调用外部数据库或API,获取特定领域的详细信息。
  • 功能扩展性:大模型虽然功能强大,但不可能内置所有可能需要的功能。通过Function Call,可以轻松扩展模型能力,如调用外部工具进行复杂计算、数据分析等。

总的来说,Function Call功能的出现,极大地提升了大型语言模型的实用性和灵活性,使其能够更好地服务于用户的各种需求。

目前支持Function Call功能的模型除了GPT模型外,国内的模型也支持,如:百度文心一言,ChatGLM3-6B、讯飞星火3.0等。


2 Function Call 工作原理

接下来,我们通过举例分别对比有无Function Call功能时GPT模型工作流程的差异:


当没有函数调用(funciton-call)时候,我们调用GPT构建AI应用的模式非常简单。

  • 主要步骤:
  • 用户(client)发请求给我们的服务(chat server)
  • 我们的服务(chat server)给GPT提示词
  • 重复执行


当有函数调用(funciton-call)时候,我们调用GPT构建AI应用的模式比之前要复杂一些。

  • 主要步骤:
  • 用户(client)发请求提示词以及可以调用的函数给我们的服务(chat server)
  • GPT模型根据用户的提示词,判断是用普通文本还是函数调用的格式响应我们的服务(chat server)
  • 如果是函数调用格式,那么Chat Server就会执行这个函数,并且将结果返回给GPT
  • 然后模型使用提供的数据,用连贯的文本响应。返回


需要注意的是,大模型的 Function call 不会执行任何函数调用,仅返回调用函数所需要的参数。开发者可以利用模型输出的参数在应用中执行函数调用。

3 Function Call 实践应用

假设我们要创建一个具备查询实时天气的聊天机器人。

3.1 定义外部函数

  • 查询某地的天气函数:get_current_weather(location: str)
def get_current_weather(location):
    """得到给定地址的当前天气信息"""
    with open('./cityCode_use.json', 'r') as file:
        # 使用 json.load() 函数加载 JSON 数据
        data = json.load(file)
    city_code = ""
    weather_info = {}
    for loc in data:
        if location == loc["市名"]:
            city_code = loc["编码"]
    if city_code:
        weather_url = "http://t.weather.itboy.net/api/weather/city/" + city_code
        response = requests.get(weather_url)
        result1 = eval(response.text)
        forecast = result1["data"]["forecast"][0]
        weather_info = {
            "location": location,
            "high_temperature": forecast["high"],
            "low_temperature": forecast["low"],
            "week": forecast["week"],
            "type": forecast["type"],
        }
    return json.dumps(weather_info, ensure_ascii=False)

3.2 描述函数功能

  • 为了向模型描述外部函数库,需要向 tools 字段传入可以调用的函数列表。参数如下表:
参数名称 类型 是否必填 参数说明
type String 设置为function
function Object
name String 函数名称
description String 用于描述函数功能,模型会根据这段描述决定函数调用方式。
parameters Object parameters字段需要传入一个Json Schema对象,以准确地定义函数所接受的参数。若调用函数时不需要传入参数,省略该参数即可。
required 指定哪些属性在数据中必须被包含。
  • 样例:
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_current_weather",
            "description": "获取给定位置的当前天气",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "城市或区,例如北京、海淀",
                    },
                },
                "required": ["location"],
            },
        }
    }
]

3.3 模型应用Function Call

3.3.1创建 client

  • 这里默认使用ZhipuAI,第一次注册免费赠送100万token,没有 key 可以自己去 智普开发平台注册一下。
zhupu_api = "494b*******************************"
client = ZhipuAI(api_key=zhupu_ak) 

3.3.2 模型应用(第一次)

  • 第一次模型得到回复时符合函数参数的 JSON对象

  • 比如:我们想查询"今天北京的天气如何?"。我们向模型提供这个信息:

def chat_completion_request(messages, tools=None, tool_choice=None, model="glm-4"):
    try:
        response = client.chat.completions.create(
            model=model,
            messages=messages,
            tools=tools,
            tool_choice=tool_choice,
        )
        return response
    except Exception as e:
        print("Unable to generate ChatCompletion response")
        print(f"Exception: {e}")
        return e

messages = []
messages.append({"role": "system","content": "你是一个天气播报小助手,你需要根据用户提供的地址来回答当地的天气情况,如果用户提供的问题具有不确定性,不要自己编造内容,提示用户明确输入"})
messages.append({"role": "user", "content": "今天北京的天气如何?"})
response=chat_completion_request(messages,tools=tools,tool_choice="auto",model="glm-4")
# 模型返回结果:
print(response.choices[0].message)
# {'content': None, 'role': 'assistant', 'tool_calls':[{'id':'call_8688150014463468290', 'function': {'arguments': '{"location":"北京"}', 'name': 'get_current_weather'}, 'type': 'function'}]}

关于 tool_choice 如果不写,则默认情况下模型将决定何时适合使用其中一个函数。

如果要控制模型如何选择函数调用,需要设置 tool_choice 参数。参数默认值为auto,此时模型根据上下文信息自行选择是否返回函数调用。

若将其设置为 {"name": "your_function_name"} 时,可以强制 API 返回特定函数的调用。

还可以通过将 tool_choice 参数设置为 "none" 来强制 API 不返回任何函数的调用。

  • 可以看到此时**模型成功触发对 get_current_weather 函数的应用** 参数为location=北京"。

3.3.3 定义处理 Function call 的函数

  • 本质 Function call,就是通过大模型选择函数以及获取函数的参数。然后进行函数的运行
def parse_response(response):
    response_message = response.choices[0].message
    # 检测是否需要调用函数
    if response_message.tool_calls:
        # 调用函数
        available_functions = {
            "get_current_weather": get_current_weather,
        }  # only one function test in this example, but you can have multiple
        function_name = response_message.tool_calls[0].function.name
        fuction_to_call = available_functions[function_name]
        function_args = json.loads(response_message.tool_calls[0].function.arguments)
        function_response = fuction_to_call(
            location=function_args.get("location"),
        )
        return function_response
  • 上述parse_response函数得到真实函数运行的结果

3.3.4 模型应用(第二次)

  • 基于上下文以及函数返回的结果,重新作为prompt输入模型,得到最终的结果
    # extend conversation with assistant's reply
    messages.append(assistant_message.model_dump())  
    function_name = response.choices[0].message.tool_calls[0].function.name
    print(f'function_name--》{function_name}')
    function_id = response.choices[0].message.tool_calls[0].id
    print(f'function_id--》{function_id}')
    function_response = parse_response(response)
    # extend conversation with function response
    messages.append(
        {
            "role": "tool",
            "tool_call_id": function_id,
            "name": function_name,
            "content": function_response,
        }
    )  
    last_response = chat_completion_request(
        messages, tools=tools, tool_choice="auto"
    )
    print(f'last_response--》{last_response.choices[0].message}')

3.4 Function Call应用完整代码

  • 该 Function Call 应用目的:创建一个具备查询实时天气的聊天机器人。

  • 完整代码包含两个部分:一个main.py、tools.py。另外还包含一个cityCode的json文件,该文件的目的是实现城市名称到城市编码的映射,因为天气接口API是只能基于编码的结果进行查询。


  • cityCode_use.json数据
[
  {
    "市名": "北京",
    "编码": "101010100"
},

  {
    "市名": "昌平",
    "编码": "101010700"
}
]

  • tools.py完整代码
import json
import requests

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_current_weather",
            "description": "获取给定位置的当前天气",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "城市或区,例如北京、海淀",
                    },
                },
                "required": ["location"],
            },
        }
    }
]

# todo:1.调用API接口,实现天气查询
def get_current_weather(location):
    """得到给定地址的当前天气信息"""
    with open('./cityCode_use.json', 'r') as file:
        # 使用 json.load() 函数加载 JSON 数据
        data = json.load(file)
    city_code = ""
    weather_info = {}
    for loc in data:
        if location == loc["市名"]:
            city_code = loc["编码"]
    if city_code:
        weather_url = "http://t.weather.itboy.net/api/weather/city/" + city_code
        response = requests.get(weather_url)
        result1 = eval(response.text)
        forecast = result1["data"]["forecast"][0]
        weather_info = {
            "location": location,
            "high_temperature": forecast["high"],
            "low_temperature": forecast["low"],
            "week": forecast["week"],
            "type": forecast["type"],
        }
    return json.dumps(weather_info, ensure_ascii=False)

# todo: 2.根据模型回复来确定使用工具函数:

def parse_response(response):
    response_message = response.choices[0].message
    # 检测是否需要调用函数
    if response_message.tool_calls:
        # 调用函数
        available_functions = {
            "get_current_weather": get_current_weather,
        }  # only one function test in this example, but you can have multiple
        function_name = response_message.tool_calls[0].function.name
        fuction_to_call = available_functions[function_name]
        function_args = json.loads(response_message.tool_calls[0].function.arguments)
        function_response = fuction_to_call(
            location=function_args.get("location"),
        )
        return function_response

  • main.py主函数
import os
from dotenv import load_dotenv, find_dotenv
from tools import *
from zhipuai import ZhipuAI
_ = load_dotenv(find_dotenv()) # 需要创建.env文件,存放zhupu_api
zhupu_ak = os.environ['zhupu_api']
client = ZhipuAI(api_key=zhupu_ak) # 填写您自己的APIKey
ChatGLM = "glm-4"


def chat_completion_request(messages, tools=None, tool_choice=None, model=ChatGLM):
    try:
        response = client.chat.completions.create(
            model=model,
            messages=messages,
            tools=tools,
            tool_choice=tool_choice,
        )
        return response
    except Exception as e:
        print("Unable to generate ChatCompletion response")
        print(f"Exception: {e}")
        return e


def main():
    messages = []
    messages.append({"role": "system",
                     "content": "你是一个天气播报小助手,你需要根据用户提供的地址来回答当地的天气情况,如果用户提供的问题具有不确定性,不要自己编造内容,提示用户明确输入"})
    messages.append({"role": "user", "content": "今天北京的天气如何"})
    print(messages)
    response = chat_completion_request(
        messages, tools=tools, tool_choice="auto"
    )
    assistant_message = response.choices[0].message
    print(f'assistant_message-->{assistant_message}')
    # extend conversation with assistant's reply
    messages.append(assistant_message.model_dump())
    function_name = response.choices[0].message.tool_calls[0].function.name
    print(f'function_name--》{function_name}')
    function_id = response.choices[0].message.tool_calls[0].id
    print(f'function_id--》{function_id}')
    function_response = parse_response(response)
    messages.append(
        {
            "role": "tool",
            "tool_call_id": function_id,
            "name": function_name,
            "content": function_response,
        }

    )  # extend conversation with function response
    last_response = chat_completion_request(
        messages, tools=tools, tool_choice="auto"
    )
    print(f'last_response--》{last_response.choices[0].message}')


if __name__ == '__main__':
    main()

最终结果:last_response--》content='根据API调用结果,今天北京的天气情况如下:今天是星期五,北京有小雨,最高温度为21℃,最低温度为17℃。' role='assistant' tool_calls=None

4 多个Functions的应用实践

  • 该 Function Call 应用目的:假设我们要创建一个具备查询航班功能的聊天机器人。
  • 完整代码包含三个部分:一个muti_function_zhipu.py、airplane_function_tools.py、muti_utils.py。

  • airplane_function_tools.py

  • 定义需要的工具类函数:描述函数功能
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_plane_number",
            "description": "根据始发地、目的地和日期,查询对应日期的航班号",
            "parameters": {
                "type": "object",
                "properties": {
                    "start": {
                        "description": "出发地",
                        "type": "string"
                    },
                    "end": {
                        "description": "目的地",
                        "type": "string"
                    },
                    "date": {
                        "description": "日期",
                        "type": "string",
                    }
                },
                "required": ["start", "end", "date"]
            },
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_ticket_price",
            "description": "查询某航班在某日的价格",
            "parameters": {
                "type": "object",
                "properties": {
                    "number": {
                        "description": "航班号",
                        "type": "string"
                    },
                    "date": {
                        "description": "日期",
                        "type": "string",
                    }
                },
                "required": [ "number", "date"]
            },
        }
    },
]
  • muti_utils.py
  • 定义两个外部函数供模型选择调用:查询两地之间某日航班号函数:get_flight_number(departure: str, destination: str, date: str);查询某航班某日票价函数:get_ticket_price(flight_number: str, date: str)
  • 定义解析Function Call的函数
import json


def get_plane_number(date, start , end):
    plane_number = {
        "北京": {
            "深圳": "126",
            "广州": "356",
        },
        "郑州": {
            "北京": "1123",
            "天津": "3661",
        }
    }
    return {"date": date, "number": plane_number[start][end]}


def get_ticket_price(date:str , number:str):
    print(date)
    print(number)
    return {"ticket_price": "668"}


def parse_function_call(model_response):
    '''

    :param model_response: 模型返回的结果
    :return: 返回函数的结果
    '''
    function_result = ''
    if model_response.choices[0].message.tool_calls:
        tool_call = model_response.choices[0].message.tool_calls[0]
        args = tool_call.function.arguments
        function_result = {}
        if tool_call.function.name == "get_plane_number":
            function_result = get_plane_number(**json.loads(args))
        if tool_call.function.name == "get_ticket_price":
            function_result = get_ticket_price(**json.loads(args))
    return function_result
  • muti_function_zhipu.py
  • 主逻辑函数
import json

from zhipuai import ZhipuAI
from dotenv import load_dotenv, find_dotenv
from muti_utils import *
from airplane_function_tools import *
import os
_ = load_dotenv(find_dotenv())
# 获取环境变量 ZhiPu_API_KEY
zhupu_ak = os.environ['zhupu_api']
client = ZhipuAI(api_key=zhupu_ak) # 填写您自己的APIKey
ChatGLM = "glm-4"


def chat_completion_request(messages, tools=None, tool_choice=None, model=ChatGLM):
    try:
        response = client.chat.completions.create(
            model=model,
            messages=messages,
            tools=tools,
            tool_choice=tool_choice,
        )
        return response
    except Exception as e:
        print("Unable to generate ChatCompletion response")
        print(f"Exception: {e}")
        return e

def main():
    messages = []
    messages.append({"role": "system",
                     "content": "现在你是一个航班查询助手,将根据用户问题提供答案,但是不要假设或猜测传入函数的参数值。如果用户的描述不明确,请要求用户提供必要信息 "})
    messages.append({"role": "user", "content": "帮我查询2024年4月2日,郑州到北京的航班的票价"})
    # 1.得到第一次回复:调用:get_plane_number函数
    first_response = chat_completion_request(
        messages, tools=tools, tool_choice="auto")

    assistant_message1 = first_response.choices[0].message
    print(f'assistant_message1-->{assistant_message1}')
    # 2. 将第一次得到的模型回复结果加入messages
    messages.append(first_response.choices[0].message.model_dump())
    # 3. 第一次得到函数的结果
    first_function = parse_function_call(model_response=first_response)
    print(f'first_function--》{first_function}')
    tool_call = first_response.choices[0].message.tool_calls[0]
    # 4. 将函数的结果添加到messages中,继续送入模型问答

    messages.append({"role": "tool",
                     "tool_call_id": tool_call.id,
                     "content": str(json.dumps(first_function))})
    # 5. 第二次调用模型
    print(messages)
    second_response = chat_completion_request(
        messages, tools=tools, tool_choice="auto")
    print(f'second_response--》{second_response.choices[0].message}')
    # 6. 将第二次得到函数结果加入信息中
    messages.append(second_response.choices[0].message.model_dump())
    second_function = parse_function_call(model_response=second_response)
    print(f'second_function--》{second_function}')
    tool2_call = second_response.choices[0].message.tool_calls[0]
    # 4. 将函数的结果添加到messages中,继续送入模型问答
    messages.append({"role": "tool",
                     "tool_call_id": tool2_call.id,
                     "content": str(json.dumps(second_function))})

    last_response = chat_completion_request(
        messages, tools=tools, tool_choice="auto")
    print(f'last_response--》{last_response.choices[0].message}')
if __name__ == '__main__':
    main()

5 使用Function Call功能应用Sql查询

  • 该 Function Call 应用目的:假设我们要创建一个具备sql语句查询的聊天机器人。
  • 完整代码包含三个部分:一个sql_function_tools.py、sql_zhipu.py。
  • sql_function_tools.py
  • 定义需要的工具类函数:描述函数功能
  • 定义表的结构
  • 定义表查询的函数
  • 定义解析function call函数
import json
import requests
import os
import pymysql
from dotenv import load_dotenv, find_dotenv


# todo: 1.描述数据库表结构(单一个表格)
database_schema_string = """
  CREATE TABLE `emp` (
  `empno` int DEFAULT NULL, --员工编号, 默认为空
  `ename` varchar(50) DEFAULT NULL, --员工姓名, 默认为空
  `job` varchar(50) DEFAULT NULL,--员工工作, 默认为空
  `mgr` int DEFAULT NULL,--员工领导, 默认为空
  `hiredate` date DEFAULT NULL,--员工入职日期, 默认为空
  `sal` int DEFAULT NULL,--员工的月薪, 默认为空
  `comm` int DEFAULT NULL,--员工年终奖, 默认为空
  `deptno` int DEFAULT NULL,--员工部分编号, 默认为空
)"""


# todo: 2.描述数据库表结构(多个表格)
database_schema_string1 = """
CREATE TABLE `emp` (
  `empno` int DEFAULT NULL, --员工编号, 默认为空
  `ename` varchar(50) DEFAULT NULL, --员工姓名, 默认为空
  `job` varchar(50) DEFAULT NULL,--员工工作, 默认为空
  `mgr` int DEFAULT NULL,--员工领导, 默认为空
  `hiredate` date DEFAULT NULL,--员工入职日期, 默认为空
  `sal` int DEFAULT NULL,--员工的月薪, 默认为空
  `comm` int DEFAULT NULL,--员工年终奖, 默认为空
  `deptno` int DEFAULT NULL,--员工部分编号, 默认为空
);

CREATE TABLE `DEPT` (
  `DEPTNO` int NOT NULL, -- 部门编码, 默认为空
  `DNAME` varchar(14) DEFAULT NULL,--部门名称, 默认为空
  `LOC` varchar(13) DEFAULT NULL,--地点, 默认为空
  PRIMARY KEY (`DEPTNO`)
);

"""
tools = [
    {
        "type": "function",
        "function": {
            "name": "ask_database",
            "description": "使用此函数回答业务问题,要求输出是一个SQL查询语句",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {
                        "type": "string",
                        "description": f"SQL查询提取信息以回答用户的问题。"
                                       f"SQL应该使用以下数据库模式编写:{database_schema_string1}"
                                       f"查询应该以纯文本返回,而不是JSON。"
                                       f"查询应该只包含MySQL支持的语法。",
                    }
                },
                "required": ["query"],
            },
        }
    }
]

# todo:1.连接数据库,进行sql语句的查询


def ask_database(query):
    """连接数据库,进行查询"""
    # 1.连接到 MySQL 数据库
    print("进入函数内部")
    conn = pymysql.connect(
        host='localhost',
        port=3306,
        user='',
        password='密码',
        database='数据库名称',
        charset='utf8mb4',  # 指定游标类,返回结果为字典
    )
    # 2. 创建游标
    cursor = conn.cursor()
    print(f'开始测试')
    # 3. 执行sql语句测试
    # 示例:执行 SQL 查询
    # sql = "SELECT * FROM emp"
    print(f'query--》{query}')
    cursor.execute(query)
    # 4. 获取查询结果
    result = cursor.fetchall()
    # 5.关闭游标
    cursor.close()
    # 6.关闭连接
    conn.close()
    return result

# # todo: 2.根据模型回复来确定使用工具函数:


def parse_response(response):
    response_message = response.choices[0].message
    # 检测是否需要调用函数
    if response_message.tool_calls:
        # 调用函数
        available_functions = {
            "ask_database": ask_database
        }  # only one function test in this example, but you can have multiple
        function_name = response_message.tool_calls[0].function.name
        fuction_to_call = available_functions[function_name]
        function_args = json.loads(response_message.tool_calls[0].function.arguments)
        function_response = fuction_to_call(
            query=function_args.get("query"),
        )
        return function_response


if __name__ == '__main__':
    query = "select count(*) from emp"
    a = ask_database(query)
    print(a)
  • sql_zhipu.py
  • 主逻辑函数
from zhipuai import ZhipuAI
from dotenv import load_dotenv, find_dotenv
from sql_function_tools import *
import os
_ = load_dotenv(find_dotenv())
# 获取环境变量 ZhiPu_API_KEY
zhupu_ak = os.environ['zhupu_api']
client = ZhipuAI(api_key=zhupu_ak) # 填写您自己的APIKey
ChatGLM = "glm-4"

def chat_completion_request(messages, tools=None, tool_choice=None, model=ChatGLM):
    try:
        response = client.chat.completions.create(
            model=model,
            messages=messages,
            tools=tools,
            tool_choice=tool_choice,
        )
        return response
    except Exception as e:
        print("Unable to generate ChatCompletion response")
        print(f"Exception: {e}")
        return e


def main():
    messages = []
    messages.append({"role": "system",
                     "content": "通过针对业务数据库生成 SQL 查询来回答用户的问题"})
    messages.append({"role": "user", "content": "查询一下最高工资的员工姓名及对应的工资"})
    response = chat_completion_request(
        messages, tools=tools, tool_choice="auto"
    )
    assistant_message = response.choices[0].message
    print(f'assistant_message1-->{assistant_message}')
    function_name = response.choices[0].message.tool_calls[0].function.name
    function_id = response.choices[0].message.tool_calls[0].id
    function_response = parse_response(response)
    print(f'assistant_message.model_dump()-->{assistant_message.model_dump()}')
    messages.append(assistant_message.model_dump())  # extend conversation with assistant's reply
    messages.append(
        {
            "role": "tool",
            "tool_call_id": function_id,
            "name": function_name,
            "content": str(function_response),
        }

    )  # extend conversation with function response
    print(f'messages-->{messages}')
    last_response = chat_completion_request(
        messages, tools=tools, tool_choice="auto"
    )
    print(f'last_response--》{last_response}')
    print(f'last_response--》{last_response.choices[0].message}')
if __name__ == '__main__':
    main()

6 小节总结

本章节介绍了大模型 Function call 功能的基本概念和使用方法,包括定义外部函数、描述函数功能、代码编写等。