摘要
了解如何开始使用 OpenAI Structured Outputs,理解其新语法,并探索其关键应用。
2024 年 8 月,OpenAI 宣布了其 API 中一项强大的新功能——结构化输出。顾名思义,借助此功能,您可以确保 LLM 仅以您指定的格式生成响应。此功能将使构建需要精确数据格式的应用程序变得更加容易。
在本教程中,您将学习如何开始使用 OpenAI Structured Outputs,了解其新语法,并探索其关键应用。
开发人工智能应用程序
学习使用 OpenAI API 构建 AI 应用程序。
结构化输出在人工智能应用中的重要性
确定性响应,或者换句话说,格式一致的响应,对于许多任务(例如数据输入、信息检索、问答、多步骤工作流等)至关重要。您可能已经体验过 LLM 如何生成格式截然不同的输出,即使提示相同。
例如,考虑这个classify_sentiment
由 GPT-4o 支持的简单函数:
# List of hotel reviews
reviews = [
"The room was clean and the staff was friendly.",
"The location was terrible and the service was slow.",
"The food was amazing but the room was too small.",
]
# Classify sentiment for each review and print the results
for review in reviews:
sentiment = classify_sentiment(review)
print(f"Review: {review}\nSentiment: {sentiment}\n")
output:
Review: The room was clean and the staff was friendly.
Sentiment: Positive
Review: The location was terrible and the service was slow.
Sentiment: Negative
Review: The food was amazing but the room was too small.
Sentiment: The sentiment of the review is neutral.
尽管前两个响应采用相同的单词格式,但最后一个响应却是一整句话。如果其他下游应用程序依赖于上述代码的输出,它就会崩溃,因为它本来期望的是单词响应。
我们可以通过一些提示工程来解决这个问题,但这是一个耗时且反复的过程。即使有完美的提示,我们也不能 100% 确定响应在未来的请求中会符合我们的格式。当然,除非我们使用结构化输出:
def classify_sentiment_with_structured_outputs(review):
"""Sentiment classifier with Structured Outputs"""
...
# Classify sentiment for each review with Structured Outputs
for review in reviews:
sentiment = classify_sentiment_with_structured_outputs(review)
print(f"Review: {review}\nSentiment: {sentiment}\n")
output:
Review: The room was clean and the staff was friendly.
Sentiment: {"sentiment":"positive"}
Review: The location was terrible and the service was slow.
Sentiment: {"sentiment":"negative"}
Review: The food was amazing but the room was too small.
Sentiment: {"sentiment":"neutral"}
借助新功能,classify_sentiment_with_structured_outputs
所有响应均采用相同的格式。
这种强制语言模型采用固定格式的能力非常重要,它可以为您节省大量时间进行快速工程或依赖其他开源工具的时间。
OpenAI 结构化输出入门
在本节中,我们将使用情绪分析器功能的示例来分解结构化输出。
设置你的环境
先决条件
开始之前,请确保您已准备好以下物品:
- 您的系统上安装了 Python 3.7 或更高版本。
- OpenAI API 密钥。您可以通过在OpenAI 网站上注册来获取此密钥。
设置 OpenAI API
1.安装OpenAI Python 包:打开终端并运行以下命令来安装或更新OpenAI Python 包到最新版本:
$ pip install -U openai
供电
2. 设置 API 密钥:您可以将 API 密钥设置为环境变量,也可以直接在代码中设置。要将其设置为环境变量,请运行:
$ export OPENAI_API_KEY='your-api-key'
3.验证安装:创建一个简单的 Python 脚本来验证安装:
from openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Say hello!"}
],
max_tokens=5
)
>>> print(response.choices[0].message.content.strip())
Hello! How can I
运行脚本以确保一切设置正确。您应该看到模型的响应打印在终端中。
除了 OpenAI 包之外,您还需要 Pydantic 库来定义和验证结构化输出的 JSON 模式。使用 pip 安装它:
$ pip install pydantic
通过这些步骤,您的环境现在已经设置为使用 OpenAI 的结构化输出功能。
使用 Pydantic 定义输出架构
要使用结构化输出,您需要使用 Pydantic 模型定义预期的输出结构。Pydantic 是一个 Python 数据验证和设置管理库,允许您使用 Python 类型注释定义数据模型。然后可以使用这些模型来强制执行 OpenAI 模型生成的输出的结构。
以下是用于指定我们的评论情绪分类器格式的 Pydantic 模型示例:
from pydantic import BaseModel
from typing import Literal
class SentimentResponse(BaseModel):
sentiment: Literal["positive", "negative", "neutral"]
在此示例中:
SentimentResponse
是一个 Pydantic 模型,定义输出的预期结构。- 该模型有一个字段
sentiment
,它只能取三个文字值之一:“积极”、“消极”或“中性”。
当我们将该模型作为 OpenAI API 请求的一部分传递时,输出将只是我们提供的单词之一。
让我们看看如何。
使用解析助手
为了在 OpenAI 请求中强制执行我们的 Pydantic 模式,我们要做的就是将其传递给response_format
聊天完成 API 的参数。大致如下:
response = client.beta.chat.completions.parse(
model=MODEL,
messages=[...],
response_format=SentimentResponse
)
如果您注意到,我们不使用,而是client.chat.completions.create
使用client.beta.chat.completions.parse方法。.parse()
这是 Chat Completions API 中专为结构化输出编写的新方法。
现在,让我们通过使用结构化输出重写评论情绪分类器来将所有内容整合在一起。首先,我们进行必要的导入,定义 Pydantic 模型、系统提示和提示模板:
from openai import OpenAI
from pydantic import BaseModel
from typing import Literal
class SentimentResponse(BaseModel):
sentiment: Literal["positive", "negative", "neutral"]
client = OpenAI()
MODEL = "gpt-4o-mini"
SYSTEM_PROMPT = "You are a sentiment classifier assistant."
PROMPT_TEMPLATE = """
Classify the sentiment of the following hotel review as positive, negative, or neutral:\n\n{review}
"""
然后,我们编写一个使用辅助方法的新函数.parse()
:
# Function to classify sentiment using OpenAI's chat completions API with structured outputs
def classify_sentiment_with_structured_outputs(review):
response = client.beta.chat.completions.parse(
model=MODEL,
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": PROMPT_TEMPLATE.format(review=review)},
],
response_format=SentimentResponse
)
return response.choices[0].message
该函数中最重要的一行是response_format=SentimentResponse
,它实际上启用了结构化输出。
让我们在其中一条评论上测试一下:
# List of hotel reviews
reviews = [
"The room was clean and the staff was friendly.",
"The location was terrible and the service was slow.",
"The food was amazing but the room was too small.",
]
result = classify_sentiment_with_structured_outputs(reviews[0])
>>> print(result.content)
{"sentiment":"positive"}
这result是一个消息对象:
>>> type(result)
openai.types.chat.parsed_chat_completion.ParsedChatCompletionMessage[SentimentResponse]
除了.content
检索响应的属性之外,它还有一个.parsed属性,可以将解析的信息作为类返回:
>>> result.parsed
SentimentResponse(sentiment='positive')
如您所见,我们得到了该类的一个实例SentimentResponse
。这意味着我们可以使用属性以字符串而不是字典的形式访问情绪.sentiment
:
>>> result.parsed.sentiment
'positive'
嵌套 Pydantic 模型来定义复杂模式
在某些情况下,您可能需要定义涉及嵌套数据的更复杂的输出结构。Pydantic 允许您将模型嵌套在一起,从而让您能够创建可以处理各种用例的复杂模式。这在处理分层数据或需要为复杂输出强制执行特定结构时特别有用。
让我们考虑一个例子,其中我们需要提取详细的用户信息,包括他们的姓名、联系方式和地址列表。每个地址都应包含街道、城市、州和邮政编码的字段。这需要多个 Pydantic 模型来构建正确的模式。
步骤 1:定义 Pydantic 模型
首先,我们为地址和用户信息定义 Pydantic 模型:
from pydantic import BaseModel
from typing import List
# Define the Pydantic model for an address
class Address(BaseModel):
street: str
city: str
state: str
zip_code: str
# Define the Pydantic model for user information
class UserInfo(BaseModel):
name: str
email: str
phone: str
addresses: List[Address]
在此示例中:
Address
是一个定义地址结构的 Pydantic 模型。UserInfo
是一个 Pydantic 模型,其中包含对象列表Address
以及用户姓名、电子邮件和电话号码的字段。
第 2 步:在 API 调用中使用嵌套的 Pydantic 模型
接下来,我们使用这些嵌套的 Pydantic 模型来强制 OpenAI API 调用中的输出结构:
SYSTEM_PROMPT = "You are a user information extraction assistant."
PROMPT_TEMPLATE = """ Extract the user information from the following text:\n\n{text}"""
# Function to extract user information using OpenAI's chat completions API with structured outputs
def extract_user_info(text):
response = client.beta.chat.completions.parse(
model=MODEL,
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": PROMPT_TEMPLATE.format(text=text)},
],
response_format=UserInfo
)
return response.choices[0].message
# Example text containing user information
text = """John DoeEmail: john.doe@example.comPhone: 123-456-7890Addresses:- 123 Main St, Springfield, IL, 62701- 456 Elm St, Shelbyville, IL, 62702"""
# Extract user information and print the results
user_info = extract_user_info(text)
示例文本完全不可读,关键信息之间缺少空格。让我们看看模型是否成功。我们将使用该json
库来美化响应:
import json
data = json.loads(user_info.content)
pretty_response = json.dumps(data, indent=2)
print(pretty_response)
{
"name": "John Doe",
"email": "john.doe@example.com",
"phone": "123-456-7890",
"addresses": [
{
"street": "123 Main St",
"city": "Springfield",
"state": "IL",
"zip_code": "62701"
},
{
"street": "456 Elm St",
"city": "Shelbyville",
"state": "IL",
"zip_code": "62702"
}
]
}
如您所见,根据我们提供的模式,模型正确地捕获了单个用户的信息及其两个单独的地址。
简而言之,通过嵌套 Pydantic 模型,您可以定义处理分层数据的复杂模式并为复杂输出强制特定结构。
具有结构化输出的函数调用
函数调用(也称为工具调用)是较新的语言模型的广泛特性之一。此功能允许您将语言模型连接到用户定义的函数,从而有效地使它们(模型)能够访问外部世界。
一些常见的例子是:
- 检索实时数据(例如天气、股票价格、体育比分)
- 执行计算或数据分析
- 查询数据库或 API
- 生成图像或其他媒体
- 在多种语言之间翻译文本
- 控制智能家居设备或物联网系统
- 执行自定义业务逻辑或工作流
我们不会在这里详细介绍函数调用的工作原理,但您可以阅读我们的OpenAI 函数调用教程。
重要的是要知道,使用结构化输出,使用 OpenAI 模型进行函数调用变得更加容易。过去,传递给 OpenAI 模型的函数需要编写复杂的 JSON 模式,并使用类型提示概述每个函数参数。以下是一个例子:
{
"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"],
},
}
}
尽管get_current_weather
函数有两个参数,但其 JSON 模式变得非常庞大,并且手动编写容易出错。
通过再次使用 Pydantic 模型,结构化输出解决了这个问题:
from pydantic import BaseModel
from typing import Literal
def get_weather(location: str, unit: str, condition: str):
# Implementation details...
pass
class WeatherData(BaseModel):
location: str
unit: Literal["celsius", "fahrenheit"]
condition: Literal["sunny", "cloudy", "rainy", "snowy"]
首先,编写函数本身及其逻辑。然后,使用 Pydantic 模型再次定义它,并指定预期的输入参数。
然后,要将 Pydantic 模型转换为兼容的 JSON 模式,请调用pydantic_function_tool
:
>>> import openai
>>> openai.pydantic_function_tool(WeatherData)
{'type': 'function',
'function': {'name': 'WeatherData',
'strict': True,
'parameters': {'properties': {'location': {'title': 'Location',
'type': 'string'},
'unit': {'enum': ['celsius', 'fahrenheit'],
'title': 'Unit',
'type': 'string'},
'condition': {'enum': ['sunny', 'cloudy', 'rainy', 'snowy'],
'title': 'Condition',
'type': 'string'}},
'required': ['location', 'unit', 'condition'],
'title': 'WeatherData',
'type': 'object',
'additionalProperties': False}}}
以下是如何将此工具用作请求的一部分:
import openai
client = OpenAI()
tools = [openai.pydantic_function_tool(WeatherData)]
messages = [
{
"role": "system",
"content": "You are a helpful customer support assistant. Use the supplied tools to assist the user.",
},
{
"role": "user",
"content": "What is the weather in Tokyo?",
}
]
response = client.chat.completions.create(
model=MODEL, messages=messages, tools=tools
)
tool_call = response.choices[0].message.tool_calls[0]
>>> tool_call
ChatCompletionMessageToolCall(id='call_QnZZ0DmNN2cxw3bN433JQNIC', function=Function(arguments='{"location":"Tokyo","unit":"celsius","condition":"sunny"}', name='WeatherData'), type='function')
我们将兼容 JSON 格式的 Pydantic 模型传递给tools
Chat Completions API 的参数。然后,根据我们的查询,模型决定是否调用该工具。
由于我们在上面的例子中的查询是“东京的天气如何?”,我们在tool_calls
返回的消息对象中看到一个调用。
请记住,模型不会调用get_weather
函数,而是根据我们提供的 Pydantic 模式为其生成参数:
arguments = json.loads(tool_call.function.arguments)
>>> arguments
{'location': 'Tokyo', 'unit': 'celsius', 'condition': 'sunny'}
我们可以使用提供的参数来调用该函数:
some_result = get_weather(**arguments)
如果您希望模型生成函数的参数并同时调用它,那么您正在寻找人工智能代理。
如果您有兴趣,我们有单独的LangChain Agents 教程。
使用 OpenAI 结构化输出时的最佳实践
使用结构化输出时,需要牢记一些最佳实践和建议。在本节中,我们将概述其中的一些。
- 使用 Pydantic 模型来定义输出模式,因为它们提供了一种干净且类型安全的方式来定义预期的输出结构。
- 保持模式简单而具体以获得最准确的结果。
- 使用适当的数据类型(,,,,,,
str
)来准确表示您的数据。int
float
bool
List
Dict
- 使用
Literal
枚举类型来定义字段的特定允许值。 - 处理模型拒绝。使用新
.parse()
方法时,消息对象有一个新.refusal
属性来指示拒绝:
text = """John DoeEmail: john.doe@example.comPhone: 123-456-7890Addresses:- 123 Main St, Springfield, IL, 62701- 456 Elm St, Shelbyville, IL, 62702"""
user_info = extract_user_info(text)
if user_info.refusal:
print(user_info.refusal)
else:
print(user_info.content)
输出:
{"name":"John Doe","email":"john.doe@example.com","phone":"123-456-7890","addresses":[{"street":"123 Main St","city":"Springfield","state":"IL","zip_code":"62701"},{"street":"456 Elm St","city":"Shelbyville","state":"IL","zip_code":"62702"}]}
6.为Pydantic模型中的每个字段提供清晰简洁的描述,以提高模型输出精度:
from pydantic import BaseModel, Field
class Person(BaseModel):
name: str = Field(..., description="The person's full name")
age: int = Field(..., description="The person's age in years")
occupation: str = Field(..., description="The person's current job or profession")
这些实践将大大有助于在您的应用程序中最有效地利用结构化输出。
结论
在本教程中,我们学习了如何开始使用新的 OpenAI API 功能:结构化输出。我们已经了解了此功能如何强制语言模型以我们指定的格式生成输出。我们已经学习了如何将其与函数调用结合使用,并探索了一些最佳实践以充分利用该功能。
以下是一些相关资料,可以增强您的理解:
- 使用 OpenAI API 课程
- OpenAI 基础课程
- 使用 LangChain 课程开发 LLM 应用程序
- LangChain 与 LlamaIndex:详细比较