7. Strukturierte Ausgabe mit Pydantic
In den vorherigen Kapiteln haben wir mit LLM-Ausgaben als rohen Textstrings gearbeitet. Das funktioniert gut für Chatbots, bei denen Menschen die Antworten lesen, aber beim Erstellen von AI-Agents, bei denen Programme LLM-Ausgaben parsen und interpretieren müssen, benötigen wir vorhersehbare, strukturierte Daten. In diesem Kapitel lernen Sie, wie Sie Pydantic-Schemas verwenden, um das LLM strukturierte Python-Objekte zurückgeben zu lassen.
7.1) Warum strukturierte Ausgabe?
Das Problem mit Freitext-LLM-Ausgabe
Beginnen wir damit zu verstehen, warum rohe Textantworten in realen Anwendungen Probleme verursachen. Betrachten Sie dieses häufige Szenario:
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
llm = ChatOpenAI(model="gpt-4o-mini")
# Fragen Sie das LLM nach einem Produkt
message = HumanMessage(content="""
Extrahieren Sie die Produktinformationen aus diesem Text:
"Das UltraWidget Pro kostet 299,99 $ und ist derzeit auf Lager."
""")
response = llm.invoke([message])
print(response.content)Ausgabe:
Produktname: UltraWidget Pro
Preis: 299,99 $
Verfügbarkeit: Auf LagerDie Ausgabe sieht gut aus. Aber nehmen Sie nun an, Sie müssen diese Daten in Ihrer Python-Anwendung verwenden. Wie extrahieren Sie den Preis als Zahl? Wie prüfen Sie die Verfügbarkeit programmatisch? Sie könnten versuchen, String-Parsing wie folgt durchzuführen:
# Fragiler Parsing-Ansatz
text = response.content
price_line = [line for line in text.split('\n') if 'Preis:' in line][0]
price_str = price_line.split('$')[1]
price = float(price_str) # Fragil - was, wenn sich das Format ändert?Dieses Parsing scheint zu funktionieren. Aber es funktioniert eigentlich nicht. Hier ist der Grund:
Warum dieser Ansatz fehlschlägt:
- Das LLM könnte die Antwort beim nächsten Mal anders formatieren ("Preis: 299,99 USD" oder "Verkaufspreis: 299,99 $")
Hier sind Beispiele für verschiedene Ausgaben, die für dieselbe Eingabeaufforderung auftreten können:
# Beispiel 1
"Das Produkt ist UltraWidget Pro, zum Preis von 299,99 $, und es ist verfügbar."
# Beispiel 2
"Produkt: UltraWidget Pro
Kosten: 299,99 Dollar
Status: Verfügbar"
# Beispiel 3
"UltraWidget Pro - 299,99 $ (auf Lager)"
# Beispiel 4
"Ich habe das UltraWidget Pro gefunden. Es kostet 299,99 $ und ist derzeit zum Kauf verfügbar."Wenn sich die LLM-Antwort ändert, benötigen Sie völlig andere Parsing-Logik. Das macht es schwierig, zuverlässige Anwendungen zu erstellen.
- LLM-Antworten sind unvorhersehbar: Dieselbe Eingabeaufforderung kann jedes Mal unterschiedliche Formate erzeugen
- String-Parsing ist schwieriger als es aussieht: Sie müssen
$, Leerzeichen, Zeilenumbrüche, Kommas und mehr handhaben - Keine Typsicherheit: Sie können nicht sicher sein, ob
priceein Float, String oder None ist - Fehlerbehandlung ist schwierig: Wenn das LLM "Preis nicht verfügbar" sagt, stürzt Ihr
float()-Aufruf ab - Nicht wartbar: Ändern Sie die Eingabeaufforderung leicht und Sie schreiben den gesamten Parsing-Code neu
Die Kernidee: Python braucht Verträge, keine Prosa
Denken Sie daran, wann Sie in Python mit einem API-Server kommunizieren. Wenn Sie eine bestimmte REST-API aufrufen, erwarten Sie, dass sie eine definierte JSON-Antwort zurückgibt:
# Sie erwarten diese Struktur
{
"product_name": "UltraWidget Pro",
"price": 299.99,
"in_stock": true
}Beim Erstellen von AI-Anwendungen benötigen Sie dasselbe Prinzip. LLM-Ausgabe sollte als Daten mit einer definierten Struktur zurückgegeben werden, nicht jedes Mal als Freitext.
Prosa vs. Vertrag:
- Prosa: Freiformiger natürlicher Text. Gut für Menschen zum Lesen, aber schwer für Programme zu verarbeiten.
- Vertrag: Daten mit definierter Struktur und Typen. Ein Versprechen, dass "diese Felder mit diesen Typen existieren werden."
Strukturierte Ausgabe bedeutet, einen Vertrag zu definieren: "LLM, ich brauche genau diese Felder, mit genau diesen Typen, in genau diesem Format."
Hier kommt Pydantic ins Spiel. Pydantic ist Pythons beliebteste Datenvalidierungsbibliothek, und LangChain verwendet sie, um LLM-Ausgaben in strukturierter Form zu empfangen.
Die mentale Modellverschiebung:
- Vorher: "LLM, erzähl mir von diesem Produkt" → Unvorhersehbaren Text parsen
- Nachher: "LLM, antworte in einem definierten Format" → Strukturiertes Python-Objekt erhalten
Diese Verschiebung von Prosa zu Verträgen ist grundlegend für den Aufbau zuverlässiger AI-Agents. Wenn ein Agent seine nächste Aktion basierend auf LLM-Antworten entscheiden muss (z. B. kaufen, wenn auf Lager, für Benachrichtigung registrieren, wenn nicht), muss er Antworten in einem definierten Format erhalten.
7.2) Ihre erste strukturierte Ausgabe
In 7.1 haben wir gelernt, warum LLMs mit definierter Struktur anstatt mit Freitext antworten sollten. Jetzt sehen wir uns an, wie man dies tatsächlich implementiert.
Die Schlüsselidee: Einfach das LLM zu bitten "bitte antworte in diesem Format" reicht nicht aus. Sie müssen die genaue Datenstruktur im Python-Code definieren und LangChain sie an das LLM übergeben lassen. Diese definierte Datenstruktur wird als Schema bezeichnet.
Was ist ein Schema?
Ein Schema ist ein Blueprint, der die Struktur von Daten definiert. Es spezifiziert:
- Welche Felder vorhanden sein müssen
- Welchen Typ jedes Feld haben sollte (String, Zahl, Boolean usw.)
- Welche Einschränkungen gelten (optional vs. erforderlich, gültige Bereiche usw.)
In Python definieren wir Schemas mit der BaseModel-Klasse von Pydantic. Hier ist das einfachste mögliche Beispiel:
from pydantic import BaseModel
class ProductInfo(BaseModel):
product_name: str
price: float
in_stock: boolDieses Schema sagt: "Ein ProductInfo-Objekt muss genau drei Felder haben: einen product_name (String), einen price (Float) und ein in_stock (Boolean)."
Das Drei-Schritte-Muster: Definieren, Binden, Aufrufen
Die Verwendung strukturierter Ausgabe ist einfach. Merken Sie sich einfach drei Schritte:
- Definieren: Erstellen Sie ein Schema mit einer Pydantic-Klasse
- Binden: Verbinden Sie das Schema mit dem LLM mit
.with_structured_output() - Aufrufen: Rufen Sie
.invoke()auf, um ein typisiertes Objekt zu erhalten
Dies ist die Standardvorlage, die Sie für die meisten strukturierten Extraktionsaufgaben verwenden werden:
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from pydantic import BaseModel, Field
# Schritt 1: Definieren Sie das Schema
class ProductInfo(BaseModel):
product_name: str = Field(description="Der vollständige Produktname")
price: float = Field(description="Preis in USD")
in_stock: bool = Field(description="Ob das Produkt verfügbar ist")
# Schritt 2: Binden Sie das Schema an das LLM
llm = ChatOpenAI(model="gpt-4o-mini")
structured_llm = llm.with_structured_output(ProductInfo)
# Schritt 3: Rufen Sie auf und erhalten Sie ein typisiertes Objekt
message = HumanMessage(content="""
Extrahieren Sie Produktinformationen aus diesem Text:
"Das UltraWidget Pro kostet 299,99 $ und ist derzeit auf Lager."
""")
result = structured_llm.invoke([message])
# result ist jetzt ein ProductInfo-Objekt, kein String
print(type(result)) # <class '__main__.ProductInfo'>
print(result.product_name) # UltraWidget Pro
print(result.price) # 299.99
print(result.in_stock) # TrueWas ist gerade passiert?
- Schema-Definition: Wir haben die Felder und Typen definiert, die wir wollen
- Bindung:
.with_structured_output(ProductInfo)konfiguriert das LLM zur Verwendung strukturierter Ausgabe - Aufruf & Antwort: Wenn
.invoke()aufgerufen wird, übergibt LangChain das JSON-Schema an das LLM, und das LLM antwortet mit JSON, das dieser Struktur entspricht - Automatische Konvertierung: LangChain konvertiert das JSON in ein
ProductInfo-Objekt - kein Parsing-Code erforderlich
Kein Parsing. Keine Typkonvertierung. Keine Fehler.
Verwenden Sie dieses 3-Schritte-Muster als Ihre Vorlage. Befolgen Sie es, wann immer Sie strukturierte Ausgabe benötigen.
Feldbeschreibungen: Der Schlüssel zur Steuerung des LLM
Im obigen Schema-Definitionsbeispiel haben wir Field(description="...") verwendet. Diese Beschreibung ist nicht nur Dokumentation. Es sind Anweisungen, die das LLM liest und befolgt.
Bei typischer Pydantic-Verwendung sind Field-Beschreibungen optional:
# Reguläres Pydantic - Beschreibung ist Dokumentation für Menschen
class User(BaseModel):
name: str = Field(description="Name des Benutzers") # Funktioniert gut ohne sieAber bei der Arbeit mit LLMs sind sie essentiell:
# Mit LLMs - Beschreibung bestimmt LLM-Verhalten
class CustomerFeedback(BaseModel):
sentiment: str = Field(
description="Gesamtstimmung: 'positive', 'negative' oder 'neutral'"
)Das LLM liest diese Beschreibung und verwendet sie, um zu entscheiden, wie es antworten soll.
Sehen wir uns dies in Aktion an:
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from pydantic import BaseModel, Field
class CustomerFeedback(BaseModel):
sentiment: str = Field(
description="Gesamtstimmung: 'positive', 'negative' oder 'neutral'"
)
main_issue: str = Field(
description="Die primäre Beschwerde oder das Anliegen, falls vorhanden. Verwenden Sie 'none', wenn keine Probleme erwähnt wurden."
)
urgency: str = Field(
description="Wie dringend ist das Problem: 'low', 'medium' oder 'high'"
)
llm = ChatOpenAI(model="gpt-4o-mini")
structured_llm = llm.with_structured_output(CustomerFeedback)
message = HumanMessage(content="""
Analysieren Sie dieses Kundenfeedback:
"Das Produkt funktioniert gut, aber der Versand dauerte 3 Wochen. Ich habe bereits meine Projektfrist verpasst. Bitte antworten Sie sofort."
""")
result = structured_llm.invoke([message])
print(result.sentiment) # negative
print(result.main_issue) # Slow shipping
print(result.urgency) # highWie Beschreibungen LLM-Entscheidungen formen:
sentiment-Beschreibung → LLM lernt, dass die gültigen Werte 'positive', 'negative', 'neutral' sind → Versandproblem verursachte verpasste Frist, also wählt es 'negative'main_issue-Beschreibung → LLM wird angewiesen, "die primäre Beschwerde zu finden" → Identifiziert "langsamer Versand" als das Problemurgency-Beschreibung → LLM lernt, dass Dringlichkeit 'low', 'medium' oder 'high' sein muss → Sieht "Bitte antworten Sie sofort" und wählt 'high'
Was passiert ohne Beschreibungen?
sentiment: str # Keine BeschreibungDas LLM könnte "negative", "bad", "unsatisfied", "2/5", "disappointed" in unvorhersehbaren Formaten zurückgeben, was es schwierig macht, die Werte in Ihrem Code zu handhaben.
Wichtiger Punkt: Feldbeschreibungen sind Teil Ihres Codes, der das LLM-Verhalten steuert. Schreiben Sie sie klar und spezifisch.
Kategorische Felder: Zulässige Werte spezifizieren
Im obigen Beispiel kann das sentiment-Feld nur drei Werte haben: 'positive', 'negative' oder 'neutral'. Felder, die eines aus einer bestimmten Menge von Werten sein müssen, werden kategorische Felder genannt.
Für kategorische Felder listen Sie alle möglichen Werte in der Beschreibung auf:
sentiment: str = Field(
description="Stimmung: genau 'positive', 'negative' oder 'neutral' (Kleinbuchstaben)"
)Durch die Angabe von "genau" und "(Kleinbuchstaben)" betonen wir, dass das LLM mit genau einem dieser drei Werte antworten sollte.
Es gibt jedoch keine Garantie, dass das LLM immer mit einem der angegebenen Werte antwortet. Deshalb müssen Sie defensiven Code schreiben.
Fälle, in denen das LLM unerwartete Werte zurückgibt:
result.sentiment = "Positive" # Großgeschrieben
result.sentiment = "NEGATIVE" # Alles Großbuchstaben
result.sentiment = "good" # Völlig anderes WortDefensiven Code schreiben:
allowed = {"positive", "negative", "neutral"}
# In Kleinbuchstaben konvertieren und prüfen
sentiment = result.sentiment.lower()
if sentiment not in allowed:
sentiment = "neutral" # Standardwert für unerwartete Werte verwenden
# Jetzt ist sentiment garantiert einer der erlaubten WerteWichtige Erkenntnis:
- Zulässige Werte in Beschreibung angeben → LLM antwortet wahrscheinlicher korrekt
- Im Code validieren → Unerwartete Werte sicher handhaben
Hinweis: Kapitel 18 zeigt stärkere Muster mit Python-Enums zur Durchsetzung.
Vergleich: Manuelles Parsing vs. strukturierte Ausgabe
Vergleichen wir dieselbe Aufgabe mit und ohne strukturierte Ausgabe, um den Unterschied zu sehen:
Manuelles Parsing:
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
llm = ChatOpenAI(model="gpt-4o-mini")
message = HumanMessage(content="""
Extrahieren Sie den Produktnamen, Preis und die Verfügbarkeit aus:
"Das UltraWidget Pro kostet 299,99 $ und ist derzeit auf Lager."
Format: Name | Preis | Verfügbarkeit
""")
response = llm.invoke([message])
text = response.content
# Manuelles Parsing
parts = text.split('|')
product_name = parts[0].strip()
price_str = parts[1].strip().replace('$', '')
price = float(price_str)
availability = parts[2].strip().lower()
in_stock = 'auf lager' in availability or 'verfügbar' in availability
print(f"Name: {product_name}")
print(f"Preis: ${price}")
print(f"Auf Lager: {in_stock}")Strukturierte Ausgabe:
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from pydantic import BaseModel, Field
class ProductInfo(BaseModel):
product_name: str = Field(description="Der vollständige Produktname")
price: float = Field(description="Preis in USD")
in_stock: bool = Field(description="Ob das Produkt verfügbar ist")
llm = ChatOpenAI(model="gpt-4o-mini")
structured_llm = llm.with_structured_output(ProductInfo)
message = HumanMessage(content="""
Extrahieren Sie Produktinformationen aus:
"Das UltraWidget Pro kostet 299,99 $ und ist derzeit auf Lager."
""")
result = structured_llm.invoke([message])
print(f"Name: {result.product_name}")
print(f"Preis: ${result.price}")
print(f"Auf Lager: {result.in_stock}")Hauptunterschiede:
- Keine Parsing-Logik: Die strukturierte Version hat null Parsing-Code
- Typsicherheit:
result.priceist garantiert ein Float - Einfacherer Code: Kein Regex, kein String-Splitting, keine manuelle Typkonvertierung
- Validierung: Pydantic stellt sicher, dass alle erforderlichen Felder vorhanden sind
- Wartbarkeit: Das Ändern des Schemas ist einfacher als das Aktualisieren der Parsing-Logik
7.3) Überlegungen zum Schema-Design
Jetzt, da Sie wissen, wie man strukturierte Ausgabe verwendet, lernen wir, wie man gute Schemas entwirft. Dieser Abschnitt behandelt praktische Designprinzipien zur Unterscheidung erforderlicher von optionalen Feldern.
Erforderliche Felder
Standardmäßig sind alle Felder in einem Pydantic-Modell erforderlich. Das bedeutet, dass das LLM für jedes erforderliche Feld einen Wert aus der Eingabeaufforderung des Benutzers extrahieren oder ableiten und ihn in der Antwort bereitstellen muss.
from pydantic import BaseModel
class ProductInfo(BaseModel):
product_name: str
price: float
in_stock: boolWenn Sie dieses Schema verwenden, wird das LLM versuchen, Werte für alle drei Felder (product_name, price, in_stock) im Eingabetext zu finden.
Aber was passiert, wenn der Eingabeaufforderung Informationen für ein erforderliches Feld fehlen?
Wir könnten folgendes Verhalten erwarten:
- Das LLM kann die Informationen in der Eingabeaufforderung nicht finden
- Das LLM lässt dieses Feld in seiner Antwort weg
- LangChain kann keine gültige
ProductInfo-Instanz erstellen ValidationErrorwird ausgelöst
Dies geschieht jedoch nicht immer.
Der Grund ist, dass verschiedene LLMs fehlende Informationen unterschiedlich handhaben können.
Einige LLMs (wie OpenAI-Modelle) neigen dazu, Werte zu generieren, selbst wenn die erforderlichen Informationen nicht in der Eingabeaufforderung vorhanden sind. In diesem Fall tritt kein ValidationError auf, aber dies kann größere Probleme verursachen, da Ihre Python-Anwendung erfundene Informationen verarbeiten könnte, als wären sie echt.
Wir werden uns ansehen, wie man dieses Problem in Abschnitt 7.4: Wenn Dinge schiefgehen löst.
Seien Sie sich vorerst nur bewusst, dass nicht alle LLMs fehlende Informationen auf die gleiche Weise handhaben.
Optionale Felder
Sie benötigen möglicherweise Felder, die legitim vorhanden oder abwesend sein können, selbst in normalen Fällen. Zum Beispiel kann eine Liefernotiz (delivery_note) vom Kunden bereitgestellt werden oder auch nicht, selbst für eine gültige Bestellung.
Wann Optional verwendet werden sollte:
- Die Daten selbst existieren möglicherweise nicht (z. B. wenn anonyme Bewertungen erlaubt sind, haben anonyme Bewertungen keinen Bewertername)
- Sie möchten, dass das LLM fehlende Informationen explizit anzeigt, anstatt einen Wert zu erfinden
Um ein Feld optional zu machen, verwenden Sie Pythons Optional-Typ aus dem typing-Modul:
from typing import Optional
class ProductReview(BaseModel):
rating: int
review_text: str
reviewer_name: Optional[str] = None # Anonyme Bewertungen haben keinen BewerternameHinweis: Python 3.10+ Benutzer können
str | Noneanstelle vonOptional[str]verwenden.
Wenn ein Feld Optional ist:
- Das LLM kann es in der Antwort weglassen, wenn die Informationen nicht in der Eingabeaufforderung gefunden werden
- Weggelassene Felder werden auf den Standardwert (
None) gesetzt
Hier ist ein vollständiges Beispiel:
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from pydantic import BaseModel, Field
from typing import Optional
class ProductInfo(BaseModel):
product_name: str
price: float
in_stock: bool
discount_percentage: Optional[float] = None
warranty_years: Optional[int] = None
llm = ChatOpenAI(model="gpt-4o-mini")
structured_llm = llm.with_structured_output(ProductInfo)
message = HumanMessage(content="""
Extrahieren Sie Produktinformationen: "Das UltraWidget Pro kostet 299,99 $ und ist auf Lager."
""")
result = structured_llm.invoke([message])
print(result.product_name) # UltraWidget Pro
print(result.price) # 299.99
print(result.in_stock) # True
print(result.discount_percentage) # None (nicht erwähnt)
print(result.warranty_years) # None (nicht erwähnt)Checkliste für Schema-Design
Bevor Sie Ihr Schema finalisieren, fragen Sie sich:
Feldauswahl:
- Sind erforderliche Felder wirklich essentiell? (Was passiert, wenn dieses Feld in der Eingabeaufforderung fehlt?)
- Können optionale Felder legitim abwesend sein, selbst in normalen Fällen?
Feldspezifikation:
- Hat jedes Feld eine klare Beschreibung?
- Sind kategorische Felder explizit eingeschränkt? (z. B. "muss genau 'A', 'B' oder 'C' sein")
7.4) Wenn Dinge schiefgehen
Zwei Probleme können bei der Verwendung strukturierter Ausgabe auftreten:
- LLM lässt erforderliche Feldwerte weg → ValidationError tritt auf
- LLM erfindet fehlende Informationen → Kein ValidationError, aber Ihr Code verarbeitet falsche Daten
Dieser Abschnitt behandelt, wie man mit jedem umgeht.
Validierungsfehler verstehen
Wenn der Eingabeaufforderung des Benutzers Informationen für im Schema definierte erforderliche Felder fehlen, kann das LLM keine Werte für diese Felder bereitstellen. Das Python-Programm löst dann einen ValidationError aus:
from langchain_anthropic import ChatAnthropic
from langchain_core.messages import HumanMessage
from pydantic import BaseModel, ValidationError
class ProductInfo(BaseModel):
product_name: str
price: float
in_stock: bool
llm = ChatAnthropic(model='claude-sonnet-4-5')
structured_llm = llm.with_structured_output(ProductInfo)
# Eingabe fehlt erforderliche Informationen
message = HumanMessage(content="""
Extrahieren Sie Produktinformationen aus: "Das Widget ist großartig! Sehr empfehlenswert."
""")
try:
result = structured_llm.invoke([message])
print(result)
except ValidationError as e:
print("ValidationError ist aufgetreten")Hinweis: Wenn der Eingabeaufforderung Informationen für erforderliche Felder fehlen, können einige LLMs Werte erfinden und sie in der Antwort bereitstellen. In diesem Fall tritt kein
ValidationErrorauf, aber ein größeres Problem entsteht. Wir werden dies im nächsten Abschnitt behandeln.
Wenn der Eingabeaufforderung Informationen für erforderliche Felder fehlen und ValidationError auftritt, ist dies tatsächlich hilfreich für Ihre Python-Anwendung. Die Anwendung kann erkennen, dass ein Problem aufgetreten ist, und den Fehler auf kontrollierte Weise behandeln. Fehlerwiederherstellungsstrategien werden in Kapitel 14 (Fehlerwiederherstellung auf Agent-Ebene) und Kapitel 17 (Wiederholungslogik mit Zustandsverwaltung) behandelt.
Das größere Problem: LLM erfindet fehlende Informationen
Wie wir in Abschnitt 7.3 besprochen haben, zeigen einige LLMs gefährlicheres Verhalten: Sie erfinden Werte und stellen sie in Antworten bereit, wenn Informationen in der Eingabeaufforderung fehlen.
Wie das Problem auftritt:
- Eingabeaufforderung fehlt erforderliche Informationen
- LLM generiert trotzdem plausibel aussehende Werte
ValidationErrortritt NICHT auf- Ihre Python-Anwendung verarbeitet erfundene Daten, als wären sie echt
Beispiel:
class ProductInfo(BaseModel):
product_name: str
price: float
in_stock: bool
message = HumanMessage(content="""
Extrahieren Sie Produktinformationen aus: "Das Widget ist großartig!"
""")
# Wenn einige LLMs Werte für product_name, price und in_stock erfinden
result = structured_llm.invoke([message])
# Kein Fehler ausgelöst!
print(result.product_name) # "widget" (aus Text extrahiert)
print(result.price) # 0.0 (erfunden!)
print(result.in_stock) # False (erfunden!)
# Problem: Sie können nicht erkennen, welche Werte echt vs. erfunden sindDies ist schlimmer als ein ValidationError, weil:
- Ihre Python-Anwendung mit schlechten Daten weiterläuft
- Sie nicht wissen, welche Felder echt vs. erfunden sind
- Nachgelagerte Logik kann falsche Entscheidungen basierend auf gefälschten Daten treffen
Lösung: Verwenden Sie optionale Felder mit Validierung
Die Lösung besteht darin, alle erforderlichen Felder als Optional zu definieren und dann einen Validator zu verwenden, um zu prüfen, dass alle erforderlichen Felder Werte haben.
from typing import Optional
from pydantic import BaseModel, model_validator, ValidationError
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
class ProductInfo(BaseModel):
# Diese sind tatsächlich erforderlich, aber als Optional deklariert
# Echte Validierung erfolgt im Validator unten
product_name: Optional[str] = None
price: Optional[float] = None
in_stock: Optional[bool] = None
@model_validator(mode='after')
def check_required_fields(self):
"""Validiert, dass alle wesentlichen Felder vorhanden sind"""
if self.product_name is None or self.price is None or self.in_stock is None:
raise ValueError("Alle Felder (product_name, price, in_stock) müssen bereitgestellt werden")
return self
llm = ChatOpenAI(model="gpt-4o-mini")
structured_llm = llm.with_structured_output(ProductInfo)
# Test mit unvollständigen Daten
message = HumanMessage(content="""
Extrahieren Sie Produktinformationen aus: "Das Widget ist großartig!"
""")
try:
result = structured_llm.invoke([message])
# Wenn wir hier ankommen, sind alle Felder garantiert vorhanden
print(f"Produkt: {result.product_name}")
print(f"Preis: ${result.price}")
except ValidationError as e:
# Erforderliche Felder fehlen - Extraktion fehlgeschlagen
print(f"Unvollständige Extraktion: {e}")Was passiert hier:
@model_validatorist Pydantics Decorator, der benutzerdefinierte Validierungslogik hinzufügtmode='after'bedeutet, dass die Validierung ausgeführt wird, nachdem alle Felder geparst wurden- Wenn ein Feld
Noneist, lösen wirValueErroraus, um unvollständige Daten zu signalisieren - Pydantic verpackt diesen
ValueErrorautomatisch in einenValidationError
Warum das funktioniert:
Wenn der Eingabeaufforderung Informationen für Felder fehlen:
- Das LLM erfindet keine Werte und lässt diese Felder in der Antwort weg
- In diesem Fall werden diese Felder zu
None - Wenn diese Felder tatsächlich erforderlich sind, löst der Pydantic-Validator
ValueErroraus - Pydantic verpackt es als
ValidationError
Auf diese Weise erhält Ihre Python-Anwendung einen expliziten Fehler zur Behandlung, anstatt erfundener Daten.
Wichtige Erkenntnis: Wenn der Eingabeaufforderung Informationen für erforderliche Felder fehlen, ist das Erhalten eines ValidationError völlig normal und erwartet. Die echte Gefahr sind erfundene Daten. Verwenden Sie optionale Felder mit Validatoren, um zu verhindern, dass das LLM fehlende Informationen erfindet, während Sie explizit erkennen, wenn erforderliche Felder fehlen.
Kapitelzusammenfassung:
In diesem Kapitel haben Sie gelernt, wie Sie LLM-Ausgabe in zuverlässige Python-Objekte umwandeln:
- Warum es wichtig ist: Freitext-Parsing ist fragil; schemabasierte Ausgabe bietet Typsicherheit
- Wie man es verwendet: Definieren Sie Schemas mit Pydantic
BaseModel→ Binden mit.with_structured_output() - Designprinzipien: Wählen Sie erforderliche vs. optionale Felder, leiten Sie das LLM mit Feldbeschreibungen
- Probleme behandeln: ValidationError ist normal; echte Gefahr sind erfundene Daten