查看原文
其他

如何使用大模型调用函数

程序员叶同学 HelloTech技术派 2024-03-16

介绍如何将聊天完成 API 与外部函数结合使用,以扩展 GPT 模型的功能。

tools 是聊天完成 API 中的可选参数,可用于提供功能规范。这样做的目的是使模型能够生成符合所提供规范的函数参数。

请注意,API 实际上不会执行任何函数调用。开发人员可以使用模型输出执行函数调用。

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

概述

包含以下 2 个部分:

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

如何生成函数参数

安装依赖

pip install scipy
pip install tenacity
pip install tiktoken
pip install termcolor 
pip install openai

创建对象

import json
from openai import OpenAI
from tenacity import retry, wait_random_exponential, stop_after_attempt
from termcolor import colored  

GPT_MODEL = "gpt-3.5-turbo-0613"
client = OpenAI()

封装公共组件

首先,定义一些实用程序,用于调用聊天完成 API,以及维护和跟踪对话状态。

@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("Unable to generate ChatCompletion response")
        print(f"Exception: {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 进行交互。我们会将这些函数规范传递给聊天完成 API,以便生成符合规范的函数参数。

tools = [
    {
        "type""function",
        "function": {
            "name""get_current_weather",
            "description""Get the current weather",
            "parameters": {
                "type""object",
                "properties": {
                    "location": {
                        "type""string",
                        "description""The city and state, e.g. San Francisco, CA",
                    },
                    "format": {
                        "type""string",
                        "enum": ["celsius""fahrenheit"],
                        "description""The temperature unit to use. Infer this from the users location.",
                    },
                },
                "required": ["location""format"],
            },
        }
    },
    {
        "type""function",
        "function": {
            "name""get_n_day_weather_forecast",
            "description""Get an N-day weather forecast",
            "parameters": {
                "type""object",
                "properties": {
                    "location": {
                        "type""string",
                        "description""The city and state, e.g. San Francisco, CA",
                    },
                    "format": {
                        "type""string",
                        "enum": ["celsius""fahrenheit"],
                        "description""The temperature unit to use. Infer this from the users location.",
                    },
                    "num_days": {
                        "type""integer",
                        "description""The number of days to forecast",
                    }
                },
                "required": ["location""format""num_days"]
            },
        }
    },
]

如果我们提示模型了解当前天气,它会回答一些澄清性问题。

messages = []
messages.append({"role""system""content""Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous."})
messages.append({"role""user""content""What's the weather like today"})
chat_response = chat_completion_request(
    messages, tools=tools
)
assistant_message = chat_response.choices[0].message
messages.append(assistant_message)
assistant_message

模型会向我们询问一些信息,

ChatCompletionMessage(content='Sure, I can help you with that. Could you please provide me with your location?', role='assistant', function_call=None, tool_calls=None)

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

messages.append({"role""user""content""I'm in Glasgow, Scotland."})
chat_response = chat_completion_request(
    messages, tools=tools
)
assistant_message = chat_response.choices[0].message
messages.append(assistant_message)
assistant_message

生成函数参数如下:

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

通过以不同的方式提示它,我们可以让它针对我们告诉它的其他函数。

messages = []
messages.append({"role""system""content""Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous."})
messages.append({"role""user""content""what is the weather going to be like in Glasgow, Scotland over the next x days"})
chat_response = chat_completion_request(
    messages, tools=tools
)
assistant_message = chat_response.choices[0].message
messages.append(assistant_message)
assistant_message

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

ChatCompletionMessage(content='Sure! Please provide the number of days you would like to know the weather forecast for.', role='assistant', function_call=None, tool_calls=None)

补全信息,

messages.append({"role""user""content""5 days"})
chat_response = chat_completion_request(
    messages, tools=tools
)
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_HwWHsNZsmkZUroPj6glmEgA5', function=Function(arguments='{\n  "location": "Glasgow, Scotland",\n  "format": "celsius",\n  "num_days": 5\n}', name='get_n_day_weather_forecast'), type='function')]), internal_metrics=[{'cached_prompt_tokens'0'total_accepted_tokens'0'total_batched_tokens'269'total_predicted_tokens'0'total_rejected_tokens'0'total_tokens_in_completion'270'cached_embeddings_bytes'0'cached_embeddings_n'0'uncached_embeddings_bytes'0'uncached_embeddings_n'0'fetched_embeddings_bytes'0'fetched_embeddings_n'0'n_evictions'0'sampling_steps'40'sampling_steps_with_predictions'0'batcher_ttft'0.055008649826049805'batcher_initial_queue_time'0.00098419189453125}])

强制使用特定功能或不使用任何功能

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

强制模型使用 get_n_day_weather_forecast 函数
# in this cell we force the model to use get_n_day_weather_forecast
messages = []
messages.append({"role""system""content""Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous."})
messages.append({"role""user""content""Give me a weather report for Toronto, Canada."})
chat_response = chat_completion_request(
    messages, tools=tools, tool_choice={"type""function""function": {"name""get_n_day_weather_forecast"}}
)
chat_response.choices[0].message
ChatCompletionMessage(content=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_240XQedt4Gi8VZsUwOvFpQfZ', function=Function(arguments='{\n  "location": "Toronto, Canada",\n  "format": "celsius",\n  "num_days": 1\n}', name='get_n_day_weather_forecast'), type='function')])

不强制模型使用 get_n_day_weather_forecast

# if we don't force the model to use get_n_day_weather_forecast it may not
messages = []
messages.append({"role""system""content""Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous."})
messages.append({"role""user""content""Give me a weather report for Toronto, Canada."})
chat_response = chat_completion_request(
    messages, tools=tools
)
chat_response.choices[0].message
ChatCompletionMessage(content=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_lQhrFlzIVPpeYG1QrSv7e3H3', function=Function(arguments='{\n  "location": "Toronto, Canada",\n  "format": "celsius"\n}', name='get_current_weather'), type='function')])

强制模型根本不使用函数

通过这样做,我们阻止它生成正确的函数调用。

messages = []
messages.append({"role""system""content""Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous."})
messages.append({"role""user""content""Give me the current weather (use Celcius) for Toronto, Canada."})
chat_response = chat_completion_request(
    messages, tools=tools, tool_choice="none"
)
chat_response.choices[0].message

ChatCompletionMessage(content='{\n  "location": "Toronto, Canada",\n  "format": "celsius"\n}', role='assistant', function_call=None, tool_calls=None)

并行函数调用

较新的模型,如 gpt-4-1106-preview 或 gpt-3.5-turbo-1106,可以在一个回合内调用多个函数。

messages = []
messages.append({"role""system""content""Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous."})
messages.append({"role""user""content""what is the weather going to be like in San Francisco and Glasgow over the next 4 days"})
chat_response = chat_completion_request(
    messages, tools=tools, model='gpt-3.5-turbo-1106'
)

assistant_message = chat_response.choices[0].message.tool_calls
assistant_message
[ChatCompletionMessageToolCall(id='call_q8k4geh0uGPRtIfOXYPB0yM8', function=Function(arguments='{"location": "San Francisco, CA", "format": "celsius", "num_days": 4}', name='get_n_day_weather_forecast'), type='function'),
 ChatCompletionMessageToolCall(id='call_Hdl7Py7aLswCBPptrD4y5BD3', function=Function(arguments='{"location": "Glasgow", "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("Opened database successfully")

def get_table_names(conn):
    """Return a list of table names."""
    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):
    """Return a list of column names."""
    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):
    """Return a list of dicts containing the table name and columns for each table in the database."""
    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""Use this function to answer user questions about music. Input should be a fully formed SQL query.",
            "parameters": {
                "type""object",
                "properties": {
                    "query": {
                        "type""string",
                        "description"f"""
                                SQL query extracting info to answer the user's question.
                                SQL should be written using this database schema:
                                {database_schema_string}
                                The query should be returned in plain text, not in JSON.
                                """
,
                    }
                },
                "required": ["query"],
            },
        }
    }
]

Executing SQL queries 执行 SQL 查询

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

def ask_database(conn, query):
    """Function to query SQLite database with a provided SQL query."""
    try:
        results = str(conn.execute(query).fetchall())
    except Exception as e:
        results = f"query failed with error: {e}"
    return results

def execute_function_call(message):
    if message.tool_calls[0].function.name == "ask_database":
        query = json.loads(message.tool_calls[0].function.arguments)["query"]
        results = ask_database(conn, query)
    else:
        results = f"Error: function {message.tool_calls[0].function.name} does not exist"
    return results

messages = []
messages.append({"role""system""content""Answer user questions by generating SQL queries against the Chinook Music Database."})
messages.append({"role""user""content""Hi, who are the top 5 artists by number of tracks?"})
chat_response = chat_completion_request(messages, tools)
assistant_message = chat_response.choices[0].message
assistant_message.content = str(assistant_message.tool_calls[0].function)
messages.append({"role": assistant_message.role, "content": assistant_message.content})
if assistant_message.tool_calls:
    results = execute_function_call(assistant_message)
    messages.append({"role""function""tool_call_id": assistant_message.tool_calls[0].id, "name": assistant_message.tool_calls[0].function.name, "content": results})
pretty_print_conversation(messages)

运行结果

[31msystem: Answer user questions by generating SQL queries against the Chinook Music Database.
[0m
[32muser: Hi, who are the top 5 artists by number of tracks?
[0m
[34massistant: Function(arguments='{\n  "query": "SELECT artist.Name, COUNT(track.TrackId) AS num_tracks FROM artist JOIN album ON artist.ArtistId = album.ArtistId JOIN track ON album.AlbumId = track.AlbumId GROUP BY artist.ArtistId ORDER BY num_tracks DESC LIMIT 5"\n}', name='ask_database')
[0m
[35mfunction (ask_database): [('Iron Maiden'213), ('U2'135), ('Led Zeppelin'114), ('Metallica'112), ('Lost'92)]
[0m

参考资料:

How to call functions with chat models | OpenAI Cookbook

https://cookbook.openai.com/examples/how_to_call_functions_with_chat_models


继续滑动看下一个
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存