OpenAI Function Calling学习笔记02:如何使用聊天模型调用函数

OpenAI Function Calling学习笔记02:如何使用聊天模型调用函数

2024-08-18
Aigc

本文是基于OpenAI官方Cookbook中的《How to call functions with chat models》学习笔记。

通过OpenAI的Chat Completions API,结合自定义函数,可以打造更智能、更强大的对话代理。我们将详细介绍如何利用tools参数定义函数规范,并通过实际案例演示如何让模型生成符合规范的函数参数,从而实现与外部数据的交互。当前在function calling使用过程中可能遇到的问题,如模型对系统提示的遵循程度,这很大程度上是由模型能力决定。

本文介绍如何结合外部函数使用Chat Completions API来扩展模型的能力。

tools是 Chat Completions API中的一个可选参数,可用于提供函数规范。其目的是使模型能够生成符合提供规范的函数参数。请注意,API不会实际执行任何函数调用。开发人员需要使用模型输出执行函数调用。

tools参数中,如果提供了functions参数,则默认情况下,模型将决定何时使用其中一个函数。可以通过将tool_choice参数设置为{"type": "function", "function": {"name": "my_function"}} 来强制 API 使用特定函数。也可以通过将 tool_choice参数设置为"none"来强制 API 不使用任何函数。如果使用了函数,则输出将在响应中包含"finish_reason": "tool_calls",以及包含函数名称和生成函数参数的tool_calls对象。

本文包含以下两部分:

  • 如何生成函数参数:指定一组函数并使用OpenAI API生成函数参数。
  • 如何使用模型生成的参数调用函数:通过实际执行模型生成的函数参数来闭合循环。

1.如何生成函数参数 #

使用poetry创建一个名称为openai-function-calling-with-chat-models-sample的项目。

添加项目依赖:

1poetry add scipy tenacity tiktoken termcolor openai -vvv
  • scipy:科学计算和数值分析库。
  • tenacity:重试机制的实现库。
  • tiktoken:OpenAI开发的一种BPE分词器,处理OpenAI模型的token编码。
  • termcolor:终端文本颜色高亮工具。
  • openai:OpenAI API的Python库。
 1import json
 2from openai import OpenAI
 3from tenacity import retry, wait_random_exponential, stop_after_attempt
 4from termcolor import colored
 5import os
 6
 7OPENAI_API_BASE = os.environ.get("OPENAI_API_BASE")
 8OPENAI_API_KEY = os.environ.get("OPEN_AI_API_KEY")
 9MODEL_NAME = os.environ.get("MODEL_NAME")
10
11client = OpenAI(
12    base_url=OPENAI_API_BASE,
13    api_key=OPENAI_API_KEY,
14)

1.2 实用工具 #

首先,让我们定义一些用于调用Chat Completions API和维护和跟踪对话状态的实用工具。

 1from tenacity import retry, wait_random_exponential, stop_after_attempt
 2from termcolor import colored
 3
 4# 设置重试策略:指数随机等待时间,最多重试3次
 5@retry(wait=wait_random_exponential(multiplier=1, max=40), stop=stop_after_attempt(3))
 6def chat_completion_request(messages, tools=None, tool_choice=None, model=MODEL_NAME):
 7    try:
 8        # 调用OpenAI的chat.completions.create方法,生成对话响应
 9        response = client.chat.completions.create(
10            model=model,
11            messages=messages,
12            tools=tools,
13            tool_choice=tool_choice,
14        )
15        return response
16    except Exception as e:
17        # 异常处理,捕获并打印错误信息
18        print("Unable to generate ChatCompletion response")
19        print(f"Exception: {e}")
20        return e
21
22
23def pretty_print_conversation(messages):
24    # 定义不同角色的输出颜色
25    role_to_color = {
26        "system": "red",
27        "user": "green",
28        "assistant": "blue",
29        "function": "magenta",
30    }
31
32    for message in messages:
33        if message["role"] == "system":
34            # 打印系统消息,红色显示
35            print(
36                colored(
37                    f"system: {message['content']}\n", role_to_color[message["role"]]
38                )
39            )
40        elif message["role"] == "user":
41            # 打印用户消息,绿色显示
42            print(
43                colored(f"user: {message['content']}\n", role_to_color[message["role"]])
44            )
45        elif message["role"] == "assistant" and message.get("function_call"):
46            # 打印助手的函数调用信息,蓝色显示
47            print(
48                colored(
49                    f"assistant: {message['function_call']}\n",
50                    role_to_color[message["role"]],
51                )
52            )
53        elif message["role"] == "assistant" and not message.get("function_call"):
54            # 打印助手的普通消息,蓝色显示
55            print(
56                colored(
57                    f"assistant: {message['content']}\n", role_to_color[message["role"]]
58                )
59            )
60        elif message["role"] == "function":
61            # 打印函数调用结果,品红色显示
62            print(
63                colored(
64                    f"function ({message['name']}): {message['content']}\n",
65                    role_to_color[message["role"]],
66                )
67            )

1.3 基本概念 #

让我们创建一些与天气API接口的函数规范。我们将把这些函数规范传递给Chat Completions API,以生成符合规范的函数参数。

 1def main():
 2    tools = [
 3        {
 4            "type": "function",
 5            "function": {
 6                "name": "get_current_weather",
 7                "description": "Get the current weather",
 8                "parameters": {
 9                    "type": "object",
10                    "properties": {
11                        "location": {
12                            "type": "string",
13                            "description": "The city and state, e.g. San Francisco, CA",
14                        },
15                        "format": {
16                            "type": "string",
17                            "enum": ["celsius", "fahrenheit"],
18                            "description": "The temperature unit to use. Infer this from the users location.",
19                        },
20                    },
21                    "required": ["location", "format"],
22                },
23            },
24        },
25        {
26            "type": "function",
27            "function": {
28                "name": "get_n_day_weather_forecast",
29                "description": "Get an N-day weather forecast",
30                "parameters": {
31                    "type": "object",
32                    "properties": {
33                        "location": {
34                            "type": "string",
35                            "description": "The city and state, e.g. San Francisco, CA",
36                        },
37                        "format": {
38                            "type": "string",
39                            "enum": ["celsius", "fahrenheit"],
40                            "description": "The temperature unit to use. Infer this from the users location.",
41                        },
42                        "num_days": {
43                            "type": "integer",
44                            "description": "The number of days to forecast",
45                        },
46                    },
47                    "required": ["location", "format", "num_days"],
48                },
49            },
50        },
51    ]
52
53
54if __name__ == "__main__":
55    main()

在上述代码中,一共定义了两个函数规范:

  • get_current_weather

    • 获取当前天气信息。
    • 参数:
      • location:城市和州名称,例如“San Francisco, CA”。
      • format:温度单位,可以是“celsius”或“fahrenheit”。
  • get_n_day_weather_forecast

    • 获取N天的天气预报。
    • 参数:
      • location:城市和州名称,例如“San Francisco, CA”。
      • format:温度单位,可以是“celsius”或“fahrenheit”。
      • num_days:预测的天数(整数类型)。

如果用户向模型询问当前天气,如果询问的请求不明确时(如没有给出地点),则模型要会要求用户澄清问题。

 1    messages = []
 2    messages.append(
 3        {
 4            "role": "system",
 5            "content": "Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous.",
 6        }
 7    )
 8    messages.append({"role": "user", "content": "What's the weather like today"})
 9
10    chat_response = chat_completion_request(messages, tools=tools)
11
12    print("finish_reason=", chat_response.choices[0].finish_reason)
13    assistant_message = chat_response.choices[0].message
14    messages.append(assistant_message)
15    print(assistant_message)

上面的代码在系统消息中(“role=system”)给出了提示: Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous.即系统消息提示模型不要对函数参数做假设,并在用户请求不明确时请求澄清。而用户消息(“role=user”)询问消息时没有给出具体的地点,因此询问的请求是不明确的。

在不面向不同的LLM执行上面的代码时,可能会出现LLM违背系统消息提示的情况。

这里分别面向KIMI(moonshot-v1-8k), 通义千问(qwen2-72b-instruct), 本地部署的(qwen2-7b-instruct), 豆包(Doubao-lite-4k),只有豆包遵循了系统消息的提示,并没有生成函数调用的参数,而是进一步要求用户澄清问题。其他模型多次调用只有极少数次遵循系统消息提示,绝大多数情况(几乎全部情况)还是为get_current_weather函数填入了具体的参数值(如"New York, NY" 和 “fahrenheit”)。

产生这种情况可能有以下原因(臆测):

  • 模型实现的问题:尽管有明确的提示,模型可能没有完全遵循这些提示。AI模型有时会表现出这种不一致性。
  • 默认行为:模型可能有一个默认行为,在没有明确位置信息时使用一个常见的城市(如纽约)。
  • 上下文理解:模型可能认为"今天的天气如何"这个问题足够明确,不需要进一步澄清。
  • 训练数据影响:模型的训练数据可能包含了大量类似的查询,其中如"New York"等常被用作默认位置。

为了解决这个问题,我尝试了优化提示词,强化系统消息,将系统消息修改的更为明确和严格,如"You must always ask for clarification on location and temperature format before calling any weather-related functions.",除了豆包之外,其他模型还是不行。

前面代码在各个模型上的输出结果:

  • KIMI(moonshot-v1-8k)

    1finish_reason= tool_calls
    2ChatCompletionMessage(content='', refusal=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='get_current_weather:0', function=Function(arguments='{\n  "location": "San Francisco, CA",\n  "format": "celsius"\n}', name='get_current_weather'), type='function', index=0)])
    
  • 通义千问(qwen2-72b-instruct)

    1finish_reason= tool_calls
    2ChatCompletionMessage(content='To provide you with the current weather, I need to know your location. Could you please tell me the city and state or allow location access if you\'re using an application that supports it? For example, you can say "San Francisco, CA".', refusal=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_0d11088c611949da856526', function=Function(arguments='{"location": "New York City, NY", "format": "fahrenheit"}', name='get_current_weather'), type='function', index=0)])
    
  • 本地部署的(qwen2-7b-instruct)

    1finish_reason= tool_calls
    2ChatCompletionMessage(content='I should use the get_current_weather tool to find the current weather.', refusal=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_3e42f8cf-1e08-4aeb-8aa6-5ce6823641b4', function=Function(arguments='{"location": "New York, NY", "format": "fahrenheit"}', name='get_current_weather'), type='function')])
    
  • 豆包(Doubao-lite-4k)

    1finish_reason= stop
    2ChatCompletionMessage(content=' You can use the get_current_weather function to get the current weather. Please provide the required parameters.', refusal=None, role='assistant', function_call=None, tool_calls=None)
    

各个模型的输出结果总结如下:

  • KIMI (moonshot-v1-8k):
    • 没有遵循系统消息的指示。
    • 直接调用了 get_current_weather 函数。
    • 使用了假设的参数值:“San Francisco, CA” 和 “celsius”。
    • 没有要求用户澄清位置信息。
  • 通义千问 (qwen2-72b-instruct):
    • 部分遵循了系统消息,但行为不一致。
    • 在内容中询问了用户的位置,表现出了对澄清的需求。
    • 然而,仍然调用了 get_current_weather 函数,使用了假设的参数值:“New York City, NY” 和 “fahrenheit”。
  • 本地部署的 qwen2-7b-instruct:
    • 没有遵循系统消息的指示。
    • 直接调用了 get_current_weather 函数。
    • 使用了假设的参数值:“New York, NY” 和 “fahrenheit”。
    • 没有要求用户澄清位置信息。
  • 豆包 (Doubao-lite-4k):
    • 完全遵循了系统消息的指示。
    • 没有调用任何函数或假设参数值。
    • 明确表示需要用户提供必要的参数。
    • 是唯一一个完全符合要求的模型响应。

这些结果体现了在处理fuction calling时,不同模型的推理能力,以及对系统提示的遵循程度存在显著差异,因此在实际应用中确保模型严格遵守提示是一个不小的挑战。

一旦用户提供缺失的地点信息,模型就会为我们生成合适的函数参数。

1    messages.append({"role": "user", "content": "I'm in Glasgow, Scotland."})
2    chat_response = chat_completion_request(messages, tools=tools)
3    print("finish_reason=", chat_response.choices[0].finish_reason)
4    assistant_message = chat_response.choices[0].message
5    messages.append(assistant_message)
6    print(assistant_message)

代码输出:

  • KIMI(moonshot-v1-8k)

    1finish_reason= tool_calls
    2ChatCompletionMessage(content='', refusal=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='get_current_weather:0', function=Function(arguments='{\n    "location": "Glasgow, Scotland",\n    "format": "celsius"\n}', name='get_current_weather'), type='function', index=0)])
    
  • 通义千问(qwen2-72b-instruct)

    1finish_reason= tool_calls
    2ChatCompletionMessage(content='', refusal=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_e0551ee5653e438aaa4d4d', function=Function(arguments='{"location": "Glasgow, Scotland", "format": "celsius"}', name='get_current_weather'), type='function', index=0)])
    
  • 本地部署的(qwen2-7b-instruct)

    1finish_reason= tool_calls
    2ChatCompletionMessage(content='I need to use the get_current_weather API to find out the current weather in Glasgow, Scotland.', refusal=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_60557c45-fd4f-48f6-882a-88da487101dc', function=Function(arguments='{"location": "Glasgow, Scotland", "format": "celsius"}', name='get_current_weather'), type='function')])
    
  • 豆包(Doubao-lite-4k)

    1finish_reason= stop
    2ChatCompletionMessage(content="Sorry, I can't find the weather information of Glasgow, Scotland. Please provide other cities or zip codes, and I will try to help you.", refusal=None, role='assistant', function_call=None, tool_calls=None)
    
    1finish_reason= tool_calls
    2ChatCompletionMessage(content='\n当前提供了 2 个工具,分别是["get_current_weather","get_n_day_weather_forecast"],需要查询格拉斯哥当前的天气,调用 get_current_weather。', refusal=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_k4d31iu6yig5piohlwg8vnzs', function=Function(arguments='{"location": "Glasgow, Scotland", "format": "fahrenheit"}', name='get_current_weather'), type='function')])
    

四个模型的输出都不理想,前3个将函数参数format选择为celsius,这显然对格拉斯哥是不合适的,应该是fahrenheit才对。而豆包(Doubao-lite-4k)多次执行的结果不一致。

下面将城市换成Shanghai, China

1    messages.append({"role": "user", "content": "I'm in Shanghai, China."})
2    chat_response = chat_completion_request(messages, tools=tools)
3    print("finish_reason=", chat_response.choices[0].finish_reason)
4    assistant_message = chat_response.choices[0].message
5    messages.append(assistant_message)
6    print(assistant_message)

豆包(Doubao-lite-4k)输出如下:

1finish_reason= tool_calls
2ChatCompletionMessage(content='\n当前提供了 2 个工具,分别是["get_current_weather","get_n_day_weather_forecast"],需要查询上海的天气,调用 get_current_weather。', refusal=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_x786m8l81luw1vdkmdpu25ai', function=Function(arguments='{"location": "Shanghai, China", "format": "celsius"}', name='get_current_weather'), type='function')])

从助手消息(role='assistant')中可以看出,模型选择了get_current_weather函数,并未为函数生成了正确的参数。

通过不同的提示,我们可以让它指向我们告诉它的另一个函数。

 1    messages = []
 2    messages.append(
 3        {
 4            "role": "system",
 5            "content": "Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous.",
 6        }
 7    )
 8    messages.append(
 9        {
10            "role": "user",
11            "content": "what is the weather going to be like in Shanghai, China over the next x days",
12        }
13    )
14    chat_response = chat_completion_request(messages, tools=tools)
15    print("finish_reason=", chat_response.choices[0].finish_reason)
16    assistant_message = chat_response.choices[0].message
17    messages.append(assistant_message)
18    print(assistant_message)

本地部署的(qwen2-7b-instruct):

1finish_reason= stop
2ChatCompletionMessage(content='I need to use the API to get an N-day weather forecast for Shanghai, China to determine the weather going to be like for the next x days.\nAction: get_n_day_weather_forecast\nAction Input: {"location": "Shanghai, China", "format": "celsius", "num_days": x}\nObservation:', refusal=None, role='assistant', function_call=None, tool_calls=[])

模型再次要求我们澄清,因为它还没有足够的信息。在这种情况下,它已经知道预报的位置,但它需要知道预报需要多少天。

1    messages.append({"role": "user", "content": "5 days"})
2    chat_response = chat_completion_request(messages, tools=tools)
3    print("finish_reason=", chat_response.choices[0].finish_reason)
4    assistant_message = chat_response.choices[0].message
5    messages.append(assistant_message)
6    print(assistant_message)

本地部署的(qwen2-7b-instruct):

1finish_reason= tool_calls
2ChatCompletionMessage(content='With the clarification, I will make the API call to get the weather forecast over the next 5 days for Shanghai, China.', refusal=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_f9a3d75e-c441-4074-8d5c-2b97a1d040c6', function=Function(arguments='{"location": "Shanghai, China", "format": "celsius", "num_days": 5}', name='get_n_day_weather_forecast'), type='function')])

1.3.1 强制使用特定函数或不使用函数 #

可以强制模型使用特定的函数,例如通过使用function_call参数来使用get_n_day_weather_forecast。通过这样做,我们强制模型对如何使用它做出假设。

 1    # 强制模型使用函数get_n_day_weather_forecast
 2    messages = []
 3    messages.append(
 4        {
 5            "role": "system",
 6            "content": "Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous.",
 7        }
 8    )
 9    messages.append(
10        {"role": "user", "content": "Give me a weather report for Toronto, Canada."}
11    )
12    chat_response = chat_completion_request(
13        messages,
14        tools=tools,
15        tool_choice={
16            "type": "function",
17            "function": {"name": "get_n_day_weather_forecast"},
18        },
19    )
20    print("finish_reason=", chat_response.choices[0].finish_reason)
21    assistant_message = chat_response.choices[0].message
22    messages.append(assistant_message)
23    print(assistant_message)

KIMI(moonshot-v1-8k)输出:

1finish_reason= tool_calls
2ChatCompletionMessage(content='', refusal=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='get_n_day_weather_forecast:0', function=Function(arguments='{\n  "location": "Toronto, Canada",\n  "format": "celsius",\n  "num_days": 1\n}', name='get_n_day_weather_forecast'), type='function', index=0)])

我们还可以强制模型完全不使用函数。通过这样做,我们阻止它生成正确的函数调用。

 1messages = []
 2    messages.append(
 3        {
 4            "role": "system",
 5            "content": "Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous.",
 6        }
 7    )
 8    messages.append(
 9        {
10            "role": "user",
11            "content": "Give me the current weather (use Celcius) for Toronto, Canada.",
12        }
13    )
14    chat_response = chat_completion_request(messages, tools=tools, tool_choice="none")
15    print("finish_reason=", chat_response.choices[0].finish_reason)
16    assistant_message = chat_response.choices[0].message
17    messages.append(assistant_message)
18    print(assistant_message)

上面的代码只在通义千问(qwen2-72b-instruct)上得到了期望的输出:

1finish_reason= stop
2ChatCompletionMessage(content='I don\'t have real-time data access, so I can\'t provide the current weather for Toronto, Canada, or any other location. However, you can easily find this information by checking a reliable weather website, using a weather app on your smartphone, or searching online for "current weather in Toronto, Canada" to get the most up-to-date conditions, including temperature in Celsius.', refusal=None, role='assistant', function_call=None, tool_calls=None)

1.4 Parallel Function Calling #

更新的模型,如 gpt-4 或 gpt-3.5-turbo,可以在一次交互中调用多个函数。

1messages = []
2messages.append({"role": "system", "content": "Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous."})
3messages.append({"role": "user", "content": "what is the weather going to be like in San Francisco and Glasgow over the next 4 days"})
4chat_response = chat_completion_request(
5    messages, tools=tools, model=GPT_MODEL
6)
7
8assistant_message = chat_response.choices[0].message.tool_calls
9assistant_message

输出:

1[ChatCompletionMessageToolCall(id='call_ObhLiJwaHwc3U1KyB4Pdpx8y', function=Function(arguments='{"location": "San Francisco, CA", "format": "fahrenheit", "num_days": 4}', name='get_n_day_weather_forecast'), type='function'),
2 ChatCompletionMessageToolCall(id='call_5YRgeZ0MGBMFKE3hZiLouwg7', function=Function(arguments='{"location": "Glasgow, SCT", "format": "celsius", "num_days": 4}', name='get_n_day_weather_forecast'), type='function')]

这个试了一下国内的这几个模型还都不可用。

2.如何使用模型生成的参数调用函数 #

下一个示例中,我们将演示如何执行输入由模型生成的函数,并使用它来实现一个可以回答有关数据库问题的agent。为了简单起见,我们将使用Chinook示例数据库

注意:在生产环境中生成SQL可能存在高风险,因为模型在生成正确SQL方面并不完全可靠。

2.1 指定一个函数来执行SQL查询 #

首先,让我们定义一些有实用函数来从SQLite数据库提取数据。

1import sqlite3
2
3
4def main():
5    conn = sqlite3.connect("data/chinook.db")
6    print("Opened database successfully")

各个使用函数定义如下:

 1def get_table_names(conn):
 2    """返回数据库中的所有表名列表。"""
 3    table_names = []
 4    tables = conn.execute("SELECT name FROM sqlite_master WHERE type='table';")
 5    for table in tables.fetchall():
 6        table_names.append(table[0])
 7    return table_names
 8
 9
10def get_column_names(conn, table_name):
11    """返回指定表的列名列表。"""
12    column_names = []
13    columns = conn.execute(f"PRAGMA table_info('{table_name}');").fetchall()
14    for col in columns:
15        column_names.append(col[1])
16    return column_names
17
18
19def get_database_info(conn):
20    """返回数据库中每个表的字典列表,包含表名和列名。"""
21    table_dicts = []
22    for table_name in get_table_names(conn):
23        columns_names = get_column_names(conn, table_name)
24        table_dicts.append({"table_name": table_name, "column_names": columns_names})
25    return table_dicts

现在可以使用这些实用函数来提取数据库schema。

1    database_schema_dict = get_database_info(conn)
2    database_schema_string = "\n".join(
3        [
4            f"Table: {table['table_name']}\nColumns: {', '.join(table['column_names'])}"
5            for table in database_schema_dict
6        ]
7    )
8    print(database_schema_string)

和之前一样,我们将定义一个函数规范,用于指定我们希望 API 生成参数的函数。请注意,我们将数据库模式插入到函数规范中。这对模型了解数据库和表结构很重要。

 1 tools = [
 2        {
 3            "type": "function",
 4            "function": {
 5                "name": "ask_database",
 6                "description": "Use this function to answer user questions about music. Input should be a fully formed SQL query.",
 7                "parameters": {
 8                    "type": "object",
 9                    "properties": {
10                        "query": {
11                            "type": "string",
12                            "description": f"""
13                                SQL query extracting info to answer the user's question.
14                                SQL should be written using this database schema:
15                                {database_schema_string}
16                                The query should be returned in plain text, not in JSON.
17                                """,
18                        }
19                    },
20                    "required": ["query"],
21                },
22            },
23        }
24    ]

2.2 执行SQL查询 #

现在让我们实现一个函数,用于实际执行数据库查询。

1def ask_database(conn, query):
2    """使用提供的 SQL 查询查询 SQLite 数据库的函数"""
3    try:
4        results = str(conn.execute(query).fetchall())
5    except Exception as e:
6        results = f"query failed with error: {e}"
7    return results

2.3 使用Chat Completions API 调用函数的步骤: #

  • 第一步:向模型提供可能导致模型选择使用工具的内容。工具的描述,如函数名称和签名,定义在tools列表中,并作为API调用的一部分传递给模型。如果模型选择调用,函数名称和参数将包含在响应中。
  • 第二步:以编程方式检查模型是否想调用函数。如果是,则继续执行步骤3。
  • 第三步:从响应中提取函数名称和参数,并使用参数调用函数。将结果添加到消息中。
  • 第四步:使用消息列表调用Chat Completions API以获取响应。
 1    # 第一步:使用可能触发函数调用的内容进行提示。
 2    # 在这种情况下,模型可以识别出用户请求的信息,其可能存在于传递给模型的工具描述中的数据库模式中。
 3    messages = [
 4        {
 5            "role": "user",
 6            "content": "What is the name of the album with the most tracks?",
 7        }
 8    ]
 9
10    response = chat_completion_request(
11        messages=messages, tools=tools, tool_choice="auto"
12    )
13    print("finish_reason=", response.choices[0].finish_reason)
14    response_message = response.choices[0].message
15    messages.append(response_message)
16    print(response_message)

本地部署的(qwen2-7b-instruct):

1ChatCompletionMessage(content='To find the album with the most tracks, I need to access the "tracks" table.', refusal=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_c87ba6af-ebfb-4f20-bd53-6c5276667eef', function=Function(arguments='{"query": "SELECT Title FROM tracks JOIN albums ON tracks.AlbumId = albums.AlbumId GROUP BY Title ORDER BY COUNT(*) DESC LIMIT 1"}', name='ask_database'), type='function')])

下面将使用模型生成的参数来调用函数:

 1    # 第2步:确定模型的响应是否包含工具调用。
 2    tool_calls = response_message.tool_calls
 3    if tool_calls:
 4        # 如果为真,模型将返回要调用的工具/函数名称和参数
 5        tool_call_id = tool_calls[0].id
 6        tool_function_name = tool_calls[0].function.name
 7        tool_query_string = json.loads(tool_calls[0].function.arguments)["query"]
 8
 9        # 第3步:调用函数并检索结果。将结果附加到消息列表。
10        if tool_function_name == "ask_database":
11            results = ask_database(conn, tool_query_string)
12
13            messages.append(
14                {
15                    "role": "tool",
16                    "tool_call_id": tool_call_id,
17                    "name": tool_function_name,
18                    "content": results,
19                }
20            )
21
22            # 第4步:调用聊天补全API,将函数响应附加到消息列表中
23            # 注意,角色为“tool”的消息必须是响应前一条带有“tool_calls”的消息
24            model_response_with_function_call = chat_completion_request(
25                messages=messages,
26            )  # 获取模型的新响应,它可以看到函数的响应
27            print(model_response_with_function_call.choices[0].message.content)
28        else:
29            print(f"Error: function {tool_function_name} does not exist")
30    else:
31        # 模型未识别出要调用的函数,结果可以返回给用户
32        print(response_message.content)

本地部署的(qwen2-7b-instruct):

1The album with the most tracks is "Greatest Hits".

参考 #

© 2024 青蛙小白
comments powered by Disqus