OpenAI API - Function calling 的概念与使用(二)

目录

概述

安装必要的依赖包

导入必要的模块并初始化OpenAI客户端

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

并行函数调用

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

指定一个执行 SQL 查询的函数

提取数据库架构的表示

定义函数规范

执行SQL查询

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

总结


概述

        继 OpenAI API - Function calling 的概念与使用(一)文章,这次,我以学习笔记的形式来介绍如何结合外部函数使用Chat Completions API来扩展GPT模型的功能,

学习笔记整理不易,如果对您有帮助还望大家多多点赞收藏+关注!谢谢大家!

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

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

安装必要的依赖包
  • 首先需要安装几个Python库,包括 scipytenacitytiktokentermcoloropenai
!pip install scipy --quiet
!pip install tenacity --quiet
!pip install tiktoken --quiet
!pip install termcolor --quiet
!pip install openai --quiet
导入必要的模块并初始化OpenAI客户端
  • 导入 json 模块,OpenAI 模块,以及 tenacity 模块中的 retry, wait_random_exponential, stop_after_attempt。另外,还需要导入 termcolor 模块中的 colored
  • 初始化 OpenAI 客户端。
import json
from openai import OpenAI
from tenacity import retry, wait_random_exponential, stop_after_attempt
from termcolor import colored  

GPT_MODEL = "gpt-4o"
client = OpenAI()

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

重试机制的装饰器 @retry

        这个装饰器使用了 tenacity 库,用于在调用API时实现重试机制。wait_random_exponential 设置了指数退避策略,最大等待时间为40秒,stop_after_attempt(3) 设置了最大重试次数为3次。

chat_completion_request 函数

        该函数用于向Chat Completions API发送请求。函数接受消息、工具和工具选择作为参数,并返回API的响应。如果请求失败,捕获异常并打印错误信息。

pretty_print_conversation 函数

        这个函数用于美化打印对话内容。根据消息的角色(系统、用户、助手、函数),用不同的颜色打印消息内容。颜色由 termcolor 库的 colored 函数实现。

from tenacity import retry, wait_random_exponential, stop_after_attempt
from termcolor import colored  

GPT_MODEL = "gpt-4o"
client = OpenAI()

# 定义重试机制的装饰器,设置重试策略
@retry(wait=wait_random_exponential(multiplier=1, max=40), stop=stop_after_attempt(3))
def chat_completion_request(messages, tools=None, tool_choice=None, model=GPT_MODEL):
    try:
        response = client.chat.completions.create(
            model=model,
            messages=messages,
            tools=tools,
            tool_choice=tool_choice,
        )
        return response
    except Exception as e:
        print("无法生成ChatCompletion响应")
        print(f"异常: {e}")
        return e

# 定义一个函数用于美化打印对话内容
def pretty_print_conversation(messages):
    role_to_color = {
        "system": "red",
        "user": "green",
        "assistant": "blue",
        "function": "magenta",
    }
    
    for message in messages:
        if message["role"] == "system":
            print(colored(f"system: {message['content']}\n", role_to_color[message["role"]]))
        elif message["role"] == "user":
            print(colored(f"user: {message['content']}\n", role_to_color[message["role"]]))
        elif message["role"] == "assistant" and message.get("function_call"):
            print(colored(f"assistant: {message['function_call']}\n", role_to_color[message["role"]]))
        elif message["role"] == "assistant" and not message.get("function_call"):
            print(colored(f"assistant: {message['content']}\n", role_to_color[message["role"]]))
        elif message["role"] == "function":
            print(colored(f"function ({message['name']}): {message['content']}\n", role_to_color[message["role"]]))

我们再来创建一些函数规范,以便与假设的天气API进行接口。我们将这些函数规范传递给Chat Completions API,以生成符合规范的函数参数。 

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_current_weather",
            "description": "获取当前天气",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "城市和州,例如 San Francisco, CA",
                    },
                    "format": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "description": "使用的温度单位。从用户的位置推断。",
                    },
                },
                "required": ["location", "format"],
            },
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_n_day_weather_forecast",
            "description": "获取N天的天气预报",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "城市和州,例如 San Francisco, CA",
                    },
                    "format": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "description": "使用的温度单位。从用户的位置推断。",
                    },
                    "num_days": {
                        "type": "integer",
                        "description": "预测的天数",
                    }
                },
                "required": ["location", "format", "num_days"]
            },
        }
    },
]

如果我们提示模型提供当前天气,它将会提出一些澄清问题。

messages = []
messages.append({"role": "system", "content": "不要对函数的值做出假设。如果用户请求模糊,请求澄清。"})
messages.append({"role": "user", "content": "今天的天气怎么样?"})
chat_response = chat_completion_request(
    messages, tools=tools
)
assistant_message = chat_response.choices[0].message
messages.append(assistant_message)
print(assistant_message)
# 输出
ChatCompletionMessage(content="I need to know your location to provide you with the current weather. Could you please specify the city and state (or country) you're in?", role='assistant', function_call=None, tool_calls=None)

当我们提供缺失的信息后,它将生成适当的函数参数。

messages.append({"role": "user", "content": "我在苏格兰格拉斯哥。"})
chat_response = chat_completion_request(
    messages, tools=tools
)
assistant_message = chat_response.choices[0].message
messages.append(assistant_message)
print(assistant_message)

# 输出
ChatCompletionMessage(content=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_Dn2RJJSxzDm49vlVTehseJ0k', function=Function(arguments='{"location":"Glasgow, Scotland","format":"celsius"}', name='get_current_weather'), type='function')])

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

messages = []
messages.append({"role": "system", "content": "不要对函数的值做出假设。如果用户请求模糊,请求澄清。"})
messages.append({"role": "user", "content": "接下来几天苏格兰格拉斯哥的天气怎么样?"})
chat_response = chat_completion_request(
    messages, tools=tools
)
assistant_message = chat_response.choices[0].message
messages.append(assistant_message)
print(assistant_message)

# 输出
ChatCompletionMessage(content='请指定你想要预测的天数(x天)的天气预报。', role='assistant', function_call=None, tool_calls=None)

在这种情况下,模型已经知道了位置,但需要知道预测的天数。

messages.append({"role": "user", "content": "5天"})
chat_response = chat_completion_request(
    messages, tools=tools
)
print(chat_response.choices[0])

# 输出
Choice(finish_reason='tool_calls', index=0, logprobs=None, message=ChatCompletionMessage(content=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_Yg5ydH9lHhLjjYQyXbNvh004', function=Function(arguments='{"location":"Glasgow, Scotland","format":"celsius","num_days":5}', name='get_n_day_weather_forecast'), type='function')]))
强制使用特定函数或不使用函数

我们可以通过使用 function_call 参数强制模型使用特定函数,例如 get_n_day_weather_forecast。通过这种方式,我们强制模型使用它。

# 在这个例子中我们强制模型使用 get_n_day_weather_forecast
messages = []
messages.append({"role": "system", "content": "不要对函数的值做出假设。如果用户请求模糊,请求澄清。"})
messages.append({"role": "user", "content": "给我一个多伦多,加拿大的天气报告。"})
chat_response = chat_completion_request(
    messages, tools=tools, tool_choice={"type": "function", "function": {"name": "get_n_day_weather_forecast"}}
)
print(chat_response.choices[0].message)

# 输出
ChatCompletionMessage(content=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_aP8ZEtGcyseL0btTMYxTCKbk', function=Function(arguments='{"location":"Toronto, Canada","format":"celsius","num_days":1}', name='get_n_day_weather_forecast'), type='function')])

 如果我们不强制模型使用 get_n_day_weather_forecast,它可能不会使用。

messages = []
messages.append({"role": "system", "content": "不要对函数的值做出假设。如果用户请求模糊,请求澄清。"})
messages.append({"role": "user", "content": "给我一个多伦多,加拿大的天气报告。"})
chat_response = chat_completion_request(
    messages, tools=tools
)
print(chat_response.choices[0].message)

# 输出
ChatCompletionMessage(content=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_5HqCVRaAoBuU0uTlO3MUwaWX', function=Function(arguments='{"location": "Toronto, Canada", "format": "celsius"}', name='get_current_weather'), type='function'), ChatCompletionMessageToolCall(id='call_C9kCha28xHEsxYl4PxZ1l5LI', function=Function(arguments='{"location": "Toronto, Canada", "format": "celsius", "num_days": 3}', name='get_n_day_weather_forecast'), type='function')])

我们也可以强制模型不使用任何函数。通过这样做,我们防止它生成一个正确的函数调用。

messages = []
messages.append({"role": "system", "content": "不要对函数的值做出假设。如果用户请求模糊,请求澄清。"})
messages.append({"role": "user", "content": "给我多伦多,加拿大的当前天气(使用摄氏温度)。"})
chat_response = chat_completion_request(
    messages, tools=tools, tool_choice="none"
)
print(chat_response.choices[0].message)

# 输出
ChatCompletionMessage(content="我会获取多伦多,加拿大的当前天气,使用摄氏温度。", role='assistant', function_call=None, tool_calls=None)
并行函数调用

较新的模型如 gpt-4o 或 gpt-3.5-turbo 可以在一次请求中调用多个函数。

  1. 定义消息

    • 在消息列表中添加一条系统消息,告知模型不要对函数的值做出假设。如果用户的请求不明确,应该请求澄清。
    • 添加用户消息,询问接下来4天旧金山和格拉斯哥的天气情况。
  2. 调用Chat Completions API

    • 使用 chat_completion_request 函数发送消息和工具列表,并指定模型。
    • 接收模型响应并解析出 tool_calls
  3. 解析并行函数调用

    • 输出中包含两个并行的函数调用,分别获取旧金山和格拉斯哥的4天天气预报。
messages = []
messages.append({"role": "system", "content": "不要对函数的值做出假设。如果用户请求模糊,请求澄清。"})
messages.append({"role": "user", "content": "接下来4天旧金山和格拉斯哥的天气如何?"})
chat_response = chat_completion_request(
    messages, tools=tools, model=GPT_MODEL
)

assistant_message = chat_response.choices[0].message.tool_calls
print(assistant_message)

# 输出
[ChatCompletionMessageToolCall(id='call_pFdKcCu5taDTtOOfX14vEDRp', function=Function(arguments='{"location": "San Francisco, CA", "format": "fahrenheit", "num_days": 4}', name='get_n_day_weather_forecast'), type='function'),
 ChatCompletionMessageToolCall(id='call_Veeyp2hYJOKp0wT7ODxmTjaS', function=Function(arguments='{"location": "Glasgow, Scotland", "format": "celsius", "num_days": 4}', name='get_n_day_weather_forecast'), type='function')]
如何使用模型生成的参数调用函数

在这个例子中,我们将展示如何执行输入由模型生成的函数,并用它来实现一个可以回答有关数据库问题的代理。为了简化,我们将使用 Chinook 示例数据库。

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

  • 指定一个执行 SQL 查询的函数

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

import sqlite3

conn = sqlite3.connect("data/Chinook.db")
print("数据库打开成功")

def get_table_names(conn):
    """返回表名列表。"""
    table_names = []
    tables = conn.execute("SELECT name FROM sqlite_master WHERE type='table';")
    for table in tables.fetchall():
        table_names.append(table[0])
    return table_names

def get_column_names(conn, table_name):
    """返回列名列表。"""
    column_names = []
    columns = conn.execute(f"PRAGMA table_info('{table_name}');").fetchall()
    for col in columns:
        column_names.append(col[1])
    return column_names

def get_database_info(conn):
    """返回包含数据库中每个表的表名和列名的字典列表。"""
    table_dicts = []
    for table_name in get_table_names(conn):
        columns_names = get_column_names(conn, table_name)
        table_dicts.append({"table_name": table_name, "column_names": columns_names})
    return table_dicts
  • 提取数据库架构的表示

使用这些实用函数提取数据库架构的表示。

database_schema_dict = get_database_info(conn)
database_schema_string = "\n".join(
    [
        f"Table: {table['table_name']}\nColumns: {', '.join(table['column_names'])}"
        for table in database_schema_dict
    ]
)
  • 定义函数规范

和之前一样,我们将定义一个函数规范,供 API 生成参数。注意,我们将数据库架构插入到函数规范中。这对于让模型了解数据库架构非常重要。

tools = [
    {
        "type": "function",
        "function": {
            "name": "ask_database",
            "description": "使用此函数回答用户有关音乐的问题。输入应该是一个完全成型的 SQL 查询。",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {
                        "type": "string",
                        "description": f"""
                                SQL 查询用于提取信息以回答用户的问题。
                                SQL 应使用以下数据库架构编写:
                                {database_schema_string}
                                查询应以纯文本形式返回,而不是 JSON。
                                """,
                    }
                },
                "required": ["query"],
            },
        }
    }
]
  • 执行SQL查询

现在让我们实现一个实际对数据库执行查询的函数。

def ask_database(conn, query):
    """使用提供的SQL查询查询SQLite数据库的函数。"""
    try:
        results = str(conn.execute(query).fetchall())
    except Exception as e:
        results = f"查询失败,错误信息: {e}"
    return results

---------------------------------------------------------------------------------------------------------------------------------

使用Chat Completions API调用函数的步骤

步骤1:使用可能导致模型选择使用工具的内容提示模型。工具的描述(如函数名称和签名)在 tools 列表中定义,并在API调用中传递给模型。如果被选择,函数名称和参数会包含在响应中。

步骤2:以编程方式检查模型是否想要调用函数。如果是,继续执行步骤3。

步骤3:从响应中提取函数名称和参数,并使用这些参数调用函数。将结果附加到消息列表中。

步骤4:使用消息列表调用Chat Completions API以获取响应。

# 步骤1
messages = [{
    "role": "user",
    "content": "哪张专辑的曲目最多?"
}]

response = client.chat.completions.create(
    model='gpt-4o',
    messages=messages,
    tools=tools,
    tool_choice="auto"
)

response_message = response.choices[0].message
messages.append(response_message)
print(response_message)

# 步骤2:确定模型的响应是否包含工具调用。
tool_calls = response_message.tool_calls
if tool_calls:
    # 步骤3:提取函数名称和参数,并调用函数
    tool_call_id = tool_calls[0].id
    tool_function_name = tool_calls[0].function.name
    tool_query_string = eval(tool_calls[0].function.arguments)['query']
    
    if tool_function_name == 'ask_database':
        results = ask_database(conn, tool_query_string)
        
        messages.append({
            "role": "tool",
            "tool_call_id": tool_call_id,
            "name": tool_function_name,
            "content": results
        })
        
        # 步骤4:使用消息列表调用Chat Completions API以获取最终响应
        model_response_with_function_call = client.chat.completions.create(
            model="gpt-4o",
            messages=messages,
        )
        print(model_response_with_function_call.choices[0].message.content)
    else:
        print(f"错误:函数 {tool_function_name} 不存在")
else:
    # 模型未识别出要调用的函数,可以将结果返回给用户
    print(response_message.content)
总结

        Function calling 部分内容有点多,大家可以多点耐心,在操作过程中难免会出现一些错误遇到一些问题,或者也有我的笔记不正确的地方,所以实践出真知,多思考多动手~ 互勉,感谢大家,我们下期再见!

  • 34
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值