如何使用大模型调用函数
介绍如何将聊天完成 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