Kurs
Im August 2024 hat OpenAI eine leistungsstarke neue Funktion in seiner API vorgestellt — Structured Outputs. Wie der Name schon andeutet, kannst du damit sicherstellen, dass LLMs Antworten ausschließlich im von dir vorgegebenen Format erzeugen. Diese Fähigkeit macht es deutlich einfacher, Anwendungen zu bauen, die präzise Datenformate erfordern.
In diesem Tutorial lernst du, wie du mit OpenAI Structured Outputs startest, die neue Syntax verstehst und die wichtigsten Einsatzszenarien erkundest.
KI-Anwendungen entwickeln
Warum Structured Outputs in KI-Anwendungen wichtig sind
Deterministische Antworten bzw. einheitliche Formate sind für viele Aufgaben entscheidend, etwa für Datenerfassung, Informationsabruf, Question Answering, mehrstufige Workflows und mehr. Du hast sicher schon erlebt, dass LLMs völlig unterschiedliche Formate liefern können, selbst wenn der Prompt gleich bleibt.
Betrachte zum Beispiel diese einfache classify_sentiment-Funktion auf Basis von 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")
Ausgabe:
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.
Obwohl die ersten beiden Antworten jeweils ein einzelnes Wort sind, besteht die letzte aus einem ganzen Satz. Würde eine nachgelagerte Anwendung ein Ein-Wort-Ergebnis erwarten, käme es hier zu einem Fehler.
Wir könnten das mit etwas Prompt Engineering beheben, doch das ist zeitaufwändig und iterativ. Selbst mit einem perfekten Prompt gibt es keine 100%ige Garantie, dass zukünftige Antworten immer dem gewünschten Format entsprechen. Es sei denn, wir nutzen Structured Outputs:
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")
Ausgabe:
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"}
Mit der neuen Funktion classify_sentiment_with_structured_outputs sind alle Antworten im gleichen Format.
Diese Möglichkeit, Sprachmodelle zu einem strikten Format zu zwingen, ist ein echter Gewinn und spart dir unzählige Stunden Prompt Engineering oder den Einsatz zusätzlicher Open-Source-Tools.
Erste Schritte mit OpenAI Structured Outputs
In diesem Abschnitt zerlegen wir Structured Outputs am Beispiel einer Sentiment-Analyse-Funktion.
Deine Umgebung einrichten
Voraussetzungen
Bevor du startest, stelle Folgendes sicher:
- Python 3.7 oder neuer ist installiert.
- Ein OpenAI API-Schlüssel. Du erhältst ihn, indem du dich auf der OpenAI-Website anmeldest.
Die OpenAI API einrichten
1. OpenAI-Python-Paket installieren: Öffne dein Terminal und führe folgenden Befehl aus, um das OpenAI-Python-Paket zu installieren oder zu aktualisieren:
$ pip install -U openai
2. API-Schlüssel setzen: Du kannst deinen API-Schlüssel als Umgebungsvariable oder direkt im Code setzen. Als Umgebungsvariable geht das so:
$ export OPENAI_API_KEY='your-api-key'
3. Installation prüfen: Erstelle ein kurzes Python-Skript zur Überprüfung:
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
Führe das Skript aus, um sicherzustellen, dass alles korrekt eingerichtet ist. Du solltest die Antwort des Modells im Terminal sehen.
Zusätzlich zum OpenAI-Paket benötigst du die Pydantic-Bibliothek, um JSON-Schemas für Structured Outputs zu definieren und zu validieren. Installiere sie mit pip:
$ pip install pydantic
Damit ist deine Umgebung bereit für die Nutzung von OpenAIs Structured-Outputs-Funktion.
Ein Ausgabeschema mit Pydantic definieren
Um Structured Outputs zu nutzen, definierst du die erwartete Ausgabestruktur mit Pydantic-Modellen. Pydantic ist eine Bibliothek für Datenvalidierung und Konfigurationsmanagement in Python, mit der du Datenmodelle per Type Hints beschreibst. Diese Modelle erzwingen dann die Struktur der von OpenAIs Modellen generierten Ausgaben.
Hier ein Beispielmodell für unseren Sentiment-Classifier für Rezensionen:
from pydantic import BaseModel
from typing import Literal
class SentimentResponse(BaseModel):
sentiment: Literal["positive", "negative", "neutral"]
In diesem Beispiel:
SentimentResponseist ein Pydantic-Modell, das die erwartete Struktur der Ausgabe definiert.- Das Modell hat ein einziges Feld
sentiment, das nur einen von drei Literalwerten annehmen darf: "positive", "negative" oder "neutral".
Wenn wir dieses Modell in unseren OpenAI-API-Requests übergeben, werden die Ausgaben nur eines der vorgegebenen Wörter enthalten.
Schauen wir uns an, wie das funktioniert.
Den parse-Helper verwenden
Um unser Pydantic-Schema in OpenAI-Requests zu erzwingen, übergeben wir es an den Parameter response_format der Chat Completions API. Grob sieht das so aus:
response = client.beta.chat.completions.parse(
model=MODEL,
messages=[...],
response_format=SentimentResponse
)
Wie du siehst, nutzen wir statt client.chat.completions.create die Methode client.beta.chat.completions.parse. .parse() ist eine neue Methode der Chat Completions API, die speziell für Structured Outputs entwickelt wurde.
Jetzt setzen wir alles zusammen und schreiben den Sentiment-Classifier für Rezensionen mit Structured Outputs neu. Zuerst importieren wir die nötigen Pakete, definieren das Pydantic-Modell, den System-Prompt und eine Prompt-Vorlage:
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}
"""
Anschließend schreiben wir eine neue Funktion, die die .parse()-Hilfsmethode verwendet:
# 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
Die entscheidende Zeile ist response_format=SentimentResponse, denn sie aktiviert Structured Outputs.
Testen wir das an einer Rezension:
# 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"}
Hierbei ist result ein Message-Objekt:
>>> type(result)
openai.types.chat.parsed_chat_completion.ParsedChatCompletionMessage[SentimentResponse]
Neben dem Attribut .content, das die Antwort liefert, gibt es auch .parsed, das die geparste Information als Klasse zurückgibt:
>>> result.parsed
SentimentResponse(sentiment='positive')
Wie du siehst, erhalten wir eine Instanz der Klasse SentimentResponse. Dadurch können wir das Sentiment als String statt über ein Dictionary per .sentiment-Attribut auslesen:
>>> result.parsed.sentiment
'positive'
Pydantic-Modelle schachteln, um komplexe Schemas zu definieren
In manchen Fällen brauchst du komplexere Ausgabestrukturen mit verschachtelten Daten. Pydantic erlaubt das Schachteln von Modellen, sodass du ausgefeilte Schemas für vielfältige Anwendungsfälle definieren kannst. Das ist besonders nützlich bei hierarchischen Daten oder wenn du eine bestimmte Struktur für komplexe Ausgaben erzwingen willst.
Nehmen wir ein Beispiel, in dem wir detaillierte Nutzerinformationen extrahieren: Name, Kontaktdaten sowie eine Liste von Adressen. Jede Adresse enthält Straße, Stadt, Bundesstaat und Postleitzahl. Dafür benötigen wir mehr als ein Pydantic-Modell.
Schritt 1: Pydantic-Modelle definieren
Zuerst definieren wir die Pydantic-Modelle für Adresse und Nutzerinformationen:
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 diesem Beispiel:
Addressist ein Pydantic-Modell, das die Struktur einer Adresse definiert.UserInfoenthält eine Liste vonAddress-Objekten sowie Felder für Name, E-Mail und Telefonnummer.
Schritt 2: Verschachtelte Pydantic-Modelle in API-Calls nutzen
Als Nächstes erzwingen wir mit diesen verschachtelten Modellen die Ausgabestruktur in einem OpenAI-API-Call:
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)
Der Beispieltext ist kaum lesbar und enthält keine Abstände zwischen wichtigen Informationen. Schauen wir, ob das Modell es trotzdem schafft. Wir nutzen die json-Bibliothek, um die Antwort schön formatiert auszugeben:
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"
}
]
}
Wie du siehst, hat das Modell die Daten für eine Person samt zweier separater Adressen korrekt gemäß unserem Schema extrahiert.
Kurz gesagt: Durch das Schachteln von Pydantic-Modellen kannst du komplexe Schemas für hierarchische Daten definieren und klare Strukturen für anspruchsvolle Ausgaben sicherstellen.
Function Calling mit Structured Outputs
Eine weit verbreitete Funktion neuerer Sprachmodelle ist Function Calling (auch Tool Calling genannt). Damit kannst du Sprachmodelle mit eigenen Funktionen verbinden und ihnen so Zugang zur Außenwelt verschaffen.
Häufige Beispiele sind:
- Echtzeitdaten abrufen (z. B. Wetter, Aktienkurse, Sportergebnisse)
- Berechnungen oder Datenanalysen durchführen
- Datenbanken oder APIs abfragen
- Bilder oder andere Medien generieren
- Text zwischen Sprachen übersetzen
- Smart-Home-Geräte oder IoT-Systeme steuern
- Individuelle Geschäftslogik oder Workflows ausführen
Wir gehen hier nicht ins Detail, wie Function Calling funktioniert, aber du kannst unser OpenAI Function Calling Tutorial lesen.
Wichtig ist: Mit Structured Outputs wird Function Calling mit OpenAI-Modellen deutlich einfacher. Früher musstest du für die an Modelle übergebenen Funktionen komplexe JSON-Schemas manuell schreiben und alle Parameter mit Typen versehen. Ein Beispiel:
{
"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"],
},
}
}
Obwohl get_current_weather nur zwei Parameter hat, ist das JSON-Schema umfangreich und fehleranfällig in der manuellen Erstellung.
Structured Outputs löst das erneut mit Pydantic-Modellen:
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"]
Zuerst schreibst du die Funktion selbst und ihre Logik. Dann definierst du sie nochmals als Pydantic-Modell mit den erwarteten Eingabeparametern.
Um das Pydantic-Modell in ein kompatibles JSON-Schema zu überführen, rufst du pydantic_function_tool auf:
>>> 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}}}
So nutzt du dieses Tool in einem Request:
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')
Wir übergeben das Pydantic-Modell im kompatiblen JSON-Format an den Parameter tools der Chat Completions API. Je nach Anfrage entscheidet das Modell, ob es das Tool aufruft.
Da unsere Frage oben „What is the weather in Tokyo?“ lautet, sehen wir einen Aufruf in den tool_calls des zurückgegebenen Message-Objekts.
Beachte: Das Modell ruft nicht direkt die Funktion get_weather auf, sondern erzeugt Argumente dafür auf Basis des bereitgestellten Pydantic-Schemas:
arguments = json.loads(tool_call.function.arguments)
>>> arguments
{'location': 'Tokyo', 'unit': 'celsius', 'condition': 'sunny'}
Wir sind dafür zuständig, die Funktion mit diesen Argumenten aufzurufen:
some_result = get_weather(**arguments)
Wenn das Modell sowohl die Argumente generieren als auch die Funktion direkt ausführen soll, suchst du nach einem KI-Agenten.
Dazu haben wir ein eigenes LangChain-Agents-Tutorial, falls dich das interessiert.
Best Practices für OpenAI Structured Outputs
Bei der Arbeit mit Structured Outputs gibt es einige Empfehlungen und bewährte Vorgehensweisen. Hier sind die wichtigsten.
- Nutze Pydantic-Modelle, um Ausgabeschemas zu definieren. Sie bieten eine saubere und typsichere Möglichkeit, erwartete Strukturen festzulegen.
- Halte Schemas einfach und spezifisch, um präzisere Ergebnisse zu erhalten.
- Verwende passende Datentypen (
str,int,float,bool,List,Dict), um deine Daten korrekt abzubilden. - Nutze
Literal-Typen für Enums, um erlaubte Werte für Felder exakt festzulegen. - Gehe mit Verweigerungen des Modells um. Bei der neuen
.parse()-Methode haben Message-Objekte ein neues Attribut.refusal, das eine Verweigerung anzeigt:
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)
Ausgabe:
{"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. Vergib klare, prägnante Beschreibungen für jedes Feld deiner Pydantic-Modelle, um die Ausgabepräzision zu erhöhen:
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")
Mit diesen Praktiken holst du das Maximum aus Structured Outputs in deinen Anwendungen heraus.
Fazit
In diesem Tutorial hast du eine neue Funktion der OpenAI API kennengelernt: Structured Outputs. Du hast gesehen, wie Modelle damit Ausgaben in genau dem von dir vorgegebenen Format erzeugen. Außerdem hast du gelernt, wie sich die Funktion mit Function Calling kombinieren lässt, und Best Practices für den Einsatz kennengelernt.
Hier sind einige weiterführende Quellen, um dein Verständnis zu vertiefen:
Verdiene eine Top-KI-Zertifizierung
Structured Outputs: Häufige Fragen
Wie funktionieren Pydantic-Modelle mit Structured Outputs?
Pydantic-Modelle definieren das Schema für die gewünschte Ausgabestruktur, die dann an die OpenAI API übergeben wird, um das Antwortformat zu erzwingen.
Können Structured Outputs mit Function Calling verwendet werden?
Ja, Structured Outputs lassen sich mit Function Calling kombinieren und vereinfachen dabei die Definition von Funktionsparametern und erwarteten Ausgaben.
Welche Vorteile bietet die Nutzung von Structured Outputs?
Vorteile sind konsistente Antwortformate, weniger Bedarf an Nachbearbeitung, höhere Zuverlässigkeit in KI-Anwendungen und eine leichtere Integration in bestehende Systeme.
Gibt es Einschränkungen bei der Nutzung von Structured Outputs?
So mächtig die Funktion ist, sie kann die Flexibilität der Antworten einschränken und erfordert ein sorgfältig gestaltetes Schema, um Struktur und gewünschte Detailtiefe auszubalancieren.
Ich bin Content-Creator im Bereich Data Science mit über zwei Jahren Erfahrung und zähle zu den größten Stimmen auf Medium. Ich schreibe gern ausführliche Artikel über KI und ML – mit einer Prise Sarkasmus, damit das Ganze nicht zu trocken wird. Bisher habe ich über 130 Artikel veröffentlicht und einen DataCamp-Kurs produziert, ein weiterer ist in Arbeit. Meine Inhalte wurden von über 5 Millionen Menschen gelesen, 20.000 davon folgen mir auf Medium und LinkedIn.
