Cursus
In augustus 2024 kondigde OpenAI een krachtige nieuwe functie in hun API aan — Structured Outputs. Met deze functie kun je, zoals de naam al suggereert, ervoor zorgen dat LLM's alleen antwoorden genereren in het door jou opgegeven formaat. Deze mogelijkheid maakt het aanzienlijk eenvoudiger om applicaties te bouwen die een nauwkeurige gegevensopmaak vereisen.
In deze tutorial leer je hoe je begint met OpenAI Structured Outputs, de nieuwe syntaxis begrijpt en de belangrijkste toepassingen verkent.
Belang van Structured Outputs in AI-toepassingen
Deterministische antwoorden, of met andere woorden: antwoorden met consistente opmaak, zijn cruciaal voor veel taken zoals gegevensinvoer, informatieophaling, vraagbeantwoording, meerstapsworkflows, enzovoort. Je hebt vast wel gemerkt dat LLM's uitvoer in sterk verschillende formaten kunnen genereren, zelfs als de prompt hetzelfde is.
Neem bijvoorbeeld deze eenvoudige classify_sentiment-functie op basis van 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")
Uitvoer:
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.
Hoewel de eerste twee antwoorden in hetzelfde eenwoordformaat waren, is het laatste een volledige zin. Als een andere downstream-applicatie afhankelijk was van de uitvoer van bovenstaande code, zou die crashen omdat er een eenwoordantwoord werd verwacht.
We kunnen dit probleem oplossen met wat prompt engineering, maar dat is een tijdrovend, iteratief proces. Zelfs met een perfecte prompt kunnen we niet 100% zeker zijn dat de antwoorden in toekomstige verzoeken aan ons formaat voldoen. Tenzij we natuurlijk Structured Outputs gebruiken:
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")
Uitvoer:
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"}
Met de nieuwe functie classify_sentiment_with_structured_outputs zijn de antwoorden allemaal in hetzelfde formaat.
Deze mogelijkheid om taalmodellen in een strak formaat te dwingen is significant en bespaart je talloze uren aan prompt engineering of afhankelijkheid van andere open-sourcetools.
Aan de slag met OpenAI Structured Outputs
In deze sectie leggen we structured outputs uit aan de hand van het voorbeeld van de sentimentsanalysator-functie.
Je omgeving instellen
Vereisten
Zorg voordat je begint dat je het volgende hebt:
- Python 3.7 of later op je systeem geïnstalleerd.
- Een OpenAI API-sleutel. Je kunt deze verkrijgen door je aan te melden op de OpenAI-website.
De OpenAI API instellen
1. Installeer het OpenAI Python-pakket: Open je terminal en voer de volgende opdracht uit om het OpenAI Python-pakket te installeren of te updaten naar de nieuwste versie:
$ pip install -U openai
2. Stel je API-sleutel in: Je kunt je API-sleutel instellen als omgevingsvariabele of direct in je code. Om deze als omgevingsvariabele in te stellen, voer je uit:
$ export OPENAI_API_KEY='your-api-key'
3. Verifieer de installatie: Maak een eenvoudig Python-script om de installatie te verifiëren:
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
Voer het script uit om te controleren of alles correct is ingesteld. Je zou de reactie van het model in de terminal moeten zien.
Naast het OpenAI-pakket heb je de Pydantic-bibliotheek nodig om JSON-schema's voor Structured Outputs te definiëren en te valideren. Installeer deze met pip:
$ pip install pydantic
Met deze stappen is je omgeving nu ingericht om de Structured Outputs-functie van OpenAI te gebruiken.
Een uitvoerschema definiëren met Pydantic
Om Structured Outputs te gebruiken, moet je de verwachte uitvoerstructuur definiëren met Pydantic-modellen. Pydantic is een bibliotheek voor gegevensvalidatie en instellingenbeheer voor Python, waarmee je gegevensmodellen kunt definiëren met typeannotaties in Python. Deze modellen kunnen vervolgens worden gebruikt om de structuur van de uitvoer die door de modellen van OpenAI wordt gegenereerd, af te dwingen.
Hier is een voorbeeld van een Pydantic-model om het formaat voor onze sentimentsclassificatie van reviews te specificeren:
from pydantic import BaseModel
from typing import Literal
class SentimentResponse(BaseModel):
sentiment: Literal["positive", "negative", "neutral"]
In dit voorbeeld:
SentimentResponseis een Pydantic-model dat de verwachte structuur van de uitvoer definieert.- Het model heeft één veld
sentiment, dat slechts een van drie letterlijke waarden kan aannemen: "positive", "negative" of "neutral".
Wanneer we dit model als onderdeel van onze OpenAI API-verzoeken doorgeven, zal de uitvoer slechts een van de door ons opgegeven woorden zijn.
Laten we bekijken hoe dat werkt.
De parse-helper gebruiken
Om ons Pydantic-schema in OpenAI-verzoeken af te dwingen, hoeven we het alleen maar door te geven aan de parameter response_format van de Chat Completions API. Globaal ziet dat er zo uit:
response = client.beta.chat.completions.parse(
model=MODEL,
messages=[...],
response_format=SentimentResponse
)
Merk op dat we in plaats van client.chat.completions.create de methode client.beta.chat.completions.parse gebruiken. .parse() is een nieuwe methode in de Chat Completions API die specifiek is geschreven voor Structured Outputs.
Laten we nu alles samenbrengen door de sentimentsclassificatie van reviews te herschrijven met Structured Outputs. Eerst doen we de benodigde imports, definiëren we het Pydantic-model, de systeemprompt en een prompttemplate:
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}
"""
Vervolgens schrijven we een nieuwe functie die de helpermethode .parse() gebruikt:
# 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
De belangrijkste regel in de functie is response_format=SentimentResponse, wat Structured Outputs daadwerkelijk inschakelt.
Laten we het testen op een van de reviews:
# 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"}
Hier is result een message-object:
>>> type(result)
openai.types.chat.parsed_chat_completion.ParsedChatCompletionMessage[SentimentResponse]
Naast het attribuut .content, waarmee je de reactie ophaalt, heeft het een attribuut .parsed dat de geparseerde informatie als klasse teruggeeft:
>>> result.parsed
SentimentResponse(sentiment='positive')
Zoals je ziet, hebben we een instantie van de klasse SentimentResponse. Dit betekent dat we via het attribuut .sentiment de sentimentwaarde als string kunnen benaderen in plaats van als dictionary:
>>> result.parsed.sentiment
'positive'
Pydantic-modellen nesten voor complexe schema's
In sommige gevallen moet je complexere uitvoerstructuren definiëren die geneste gegevens bevatten. Pydantic laat je modellen in elkaar nesten, zodat je ingewikkelde schema's kunt maken die een verscheidenheid aan use-cases aankunnen. Dit is vooral handig bij hiërarchische gegevens of wanneer je een specifieke structuur voor complexe uitvoer wilt afdwingen.
Laten we een voorbeeld bekijken waarin we gedetailleerde gebruikersinformatie moeten extraheren, waaronder naam, contactgegevens en een lijst met adressen. Elk adres moet velden bevatten voor straat, stad, staat en postcode. Dit vereist meer dan één Pydantic-model om het juiste schema op te bouwen.
Stap 1: Definieer de Pydantic-modellen
Eerst definiëren we de Pydantic-modellen voor het adres en de gebruikersinformatie:
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]
In dit voorbeeld:
Addressis een Pydantic-model dat de structuur van een adres definieert.UserInfois een Pydantic-model dat een lijst metAddress-objecten bevat, samen met velden voor de naam, e-mail en het telefoonnummer van de gebruiker.
Stap 2: Gebruik de geneste Pydantic-modellen in API-calls
Vervolgens gebruiken we deze geneste Pydantic-modellen om de uitvoerstructuur in een OpenAI API-call af te dwingen:
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)
De voorbeeldtekst is volledig onleesbaar en mist spaties tussen belangrijke informatiestukken. Laten we kijken of het model slaagt. We gebruiken de json-bibliotheek om de reactie mooi weer te geven:
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"
}
]
}
Zoals je ziet, heeft het model op basis van ons schema correct de informatie van één gebruiker vastgelegd, samen met zijn twee afzonderlijke adressen.
Kortom, door Pydantic-modellen te nesten kun je complexe schema's definiëren die hiërarchische gegevens aankunnen en specifieke structuren afdwingen voor ingewikkelde uitvoer.
Function calling met Structured Outputs
Een van de wijdverspreide functies van nieuwere taalmodellen is function calling (ook wel tool calling genoemd). Hiermee kun je taalmodellen verbinden met door de gebruiker gedefinieerde functies, waardoor je (de modellen) effectief toegang geeft tot de buitenwereld.
Enkele veelvoorkomende voorbeelden zijn:
- Realtime data ophalen (bijv. weer, aandelenkoersen, sportuitslagen)
- Berekeningen of data-analyse uitvoeren
- Databases of API's bevragen
- Afbeeldingen of andere media genereren
- Tekst tussen talen vertalen
- Slimme apparaten of IoT-systemen bedienen
- Aangepaste bedrijfslogica of workflows uitvoeren
We gaan hier niet in op de details van hoe function calling werkt, maar je kunt onze OpenAI Function Calling-tutorial lezen.
Belangrijk om te weten is dat function calling met OpenAI-modellen veel eenvoudiger wordt met Structured Outputs. In het verleden vereisten de functies die je aan OpenAI-modellen doorgaf het schrijven van complexe JSON-schema's, waarbij je elke functieparameter met typehints moest beschrijven. Hier is een voorbeeld:
{
"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"],
},
}
}
Hoewel de functie get_current_weather twee parameters heeft, wordt het JSON-schema enorm en foutgevoelig om handmatig te schrijven.
Dit wordt opgelost in Structured Outputs door opnieuw Pydantic-modellen te gebruiken:
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"]
Eerst schrijf je de functie zelf en de bijbehorende logica. Vervolgens definieer je die opnieuw met een Pydantic-model waarin je de verwachte invoerparameters specificeert.
Om het Pydantic-model vervolgens om te zetten in een compatibel JSON-schema, roep je pydantic_function_tool aan:
>>> 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}}}
Zo gebruik je deze tool als onderdeel van een verzoek:
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')
We geven het Pydantic-model in een compatibel JSON-formaat door aan de parameter tools van de Chat Completions API. Vervolgens beslist het model, afhankelijk van onze vraag, of de tool wel of niet wordt aangeroepen.
Omdat onze vraag in bovenstaand voorbeeld “Wat is het weer in Tokio?” is, zien we een call in de tool_calls van het geretourneerde message-object.
Onthoud: het model roept de functie get_weather niet aan, maar genereert er argumenten voor op basis van het door ons aangeleverde Pydantic-schema:
arguments = json.loads(tool_call.function.arguments)
>>> arguments
{'location': 'Tokyo', 'unit': 'celsius', 'condition': 'sunny'}
Het is aan ons om de functie met de aangeleverde argumenten aan te roepen:
some_result = get_weather(**arguments)
Als je wilt dat het model de argumenten voor de functie genereert en deze tegelijkertijd aanroept, dan heb je een AI-agent nodig.
We hebben een aparte LangChain Agents-tutorial als je daarin geïnteresseerd bent.
Best practices bij het gebruik van OpenAI Structured Outputs
Bij het gebruik van Structured Outputs zijn er een aantal best practices en aanbevelingen om in gedachten te houden. In deze sectie zetten we er een paar op een rij.
- Gebruik Pydantic-modellen om uitvoerschema's te definiëren; ze bieden een nette en typesafe manier om verwachte uitvoerstructuren te definiëren.
- Houd schema's eenvoudig en specifiek om zo nauwkeurig mogelijke resultaten te krijgen.
- Gebruik passende datatypen (
str,int,float,bool,List,Dict) om je data nauwkeurig weer te geven. - Gebruik
Literal-typen voor enums om specifieke toegestane waarden voor velden te definiëren. - Ga om met weigeringen van het model. Bij gebruik van de nieuwe methode
.parse()hebben de message-objecten een nieuw attribuut.refusalom een weigering aan te geven:
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)
Uitvoer:
{"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. Geef duidelijke en beknopte beschrijvingen voor elk veld in je Pydantic-modellen om de nauwkeurigheid van de modeluitvoer te verbeteren:
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")
Deze practices helpen je om Structured Outputs zo effectief mogelijk in je applicaties te gebruiken.
Conclusie
In deze tutorial hebben we geleerd hoe je aan de slag gaat met een nieuwe OpenAI API-functie: Structured Outputs. We hebben gezien hoe deze functie taalmodellen dwingt om uitvoer te produceren in het door ons gespecificeerde formaat. We hebben geleerd hoe je het in combinatie met function calling gebruikt en enkele best practices verkend om het maximale uit de functie te halen.
Hier zijn enkele gerelateerde bronnen om je begrip te vergroten:
Structured Outputs: veelgestelde vragen
Hoe werken Pydantic-modellen met Structured Outputs?
Pydantic-modellen worden gebruikt om het schema voor de gewenste uitvoerstructuur te definiëren, dat vervolgens aan de OpenAI API wordt doorgegeven om het reactieformaat af te dwingen.
Kunnen Structured Outputs worden gebruikt met function calling?
Ja, Structured Outputs kunnen worden gebruikt met function calling om het definiëren van functieparameters en verwachte uitvoer te vereenvoudigen.
Wat zijn de voordelen van het gebruik van Structured Outputs?
Voordelen zijn onder andere consistente reactieformaten, minder behoefte aan post-processing, verbeterde betrouwbaarheid in AI-toepassingen en eenvoudigere integratie met bestaande systemen.
Zijn er beperkingen aan het gebruik van Structured Outputs?
Hoewel krachtig, kunnen Structured Outputs de flexibiliteit van de AI in reacties beperken en vereist het zorgvuldige schema-ontwerp om structuur in balans te brengen met het gewenste detailniveau in de uitvoer.
Ik ben een contentmaker op het gebied van data science met meer dan 2 jaar ervaring en een van de grootste achterbannen op Medium. Ik schrijf graag uitgebreide artikelen over AI en ML met een vleugje sarcastische stijl, want je moet toch íets doen om ze wat minder saai te maken. Ik heb meer dan 130 artikelen en een DataCamp-cursus gemaakt, en er is nog een nieuwe in de maak. Mijn content is door meer dan 5 miljoen paar ogen gezien, van wie 20k volgers werden op zowel Medium als LinkedIn.

