In meinem Neuester Beitrag zu strukturierten Ausgabendie drei Hauptansätze, um maschinenlesbare Antworten von einem LLM zu erhalten. Dies sind der JSON-Modus, Funktionsaufrufe und die strukturierten Ausgaben von OpenAI. Wenn Sie diesen Beitrag noch nicht gelesen haben, lohnt es sich, ihn vor diesem Beitrag kurz zu lesen, da wir direkt darauf aufbauen.
Deshalb gehen wir heute noch einen Schritt weiter und sprechen über etwas, das die Artwork und Weise verändert, wie sich strukturierte Ausgaben in der Praxis anfühlen. Das heißt Pydantisch. Genauer gesagt, während Strukturierte Ausgaben von OpenAI Obwohl die Funktion garantiert, dass das Modell gültiges, schemakonformes JSON zurückgibt, müssen wir auf der Python-Seite noch etwas mit diesem JSON tun. Insbesondere müssen wir es analysieren, die Datentypen validieren, auf die Felder zugreifen, mit unerwarteten Werten umgehen und so weiter. Und hier kommt Pydantic ins Spiel.
Meiner Meinung nach ist die Kombination von Pydantic und OpenAIs Structured Outputs das derzeit sauberste Setup für die Erstellung zuverlässiger LLM-basierter Anwendungen in Python. Am Ende dieses Beitrags werden Sie genau sehen, warum.
Was ist mit Pydantic?
Pydantisch ist eine Python-Bibliothek zur Datenvalidierung mithilfe von Typanmerkungen. Dies bedeutet, dass Sie die Kind und Typen Ihrer Daten als Python-Klasse definieren und dann überprüfen können, ob alle von Ihnen übergebenen Daten tatsächlich dieser Beschreibung entsprechen. Wenn dies nicht der Fall ist, meldet Pydantic einen eindeutigen, beschreibenden Fehler, anstatt zuzulassen, dass sich fehlerhafte Daten stillschweigend durch Ihr System verbreiten.
Hier ist das einfachste mögliche Pydantic-Modell:
from pydantic import BaseModel
class PersonInfo(BaseModel):
title: str
age: int
metropolis: str
Auf diese Weise haben wir jetzt ein Schema, das dies durchsetzt title Und metropolis werden immer eine Zeichenfolge sein, wohingegenage ist immer eine ganze Zahl. Wenn jemand versucht, eine zu erstellen PersonInfo mit age="thirty-two" Pydantic wird es sofort erkennen und uns genau sagen, was schief gelaufen ist.
Aber ist das nicht genau das, was wir bereits mit Funktionsaufrufen und strukturierten Ausgaben gemacht haben? Ja, aber mit einem sehr wichtigen Unterschied.
Einerseits kann das Modell im JSON-Modus oder beim Funktionsaufruf eine Antwort zurückgeben, die dem Schema entspricht, das wir uns vorgestellt haben, oder auch nicht. Selbst wenn das Schema richtig ist, gibt es immer noch eine einfache JSON-Zeichenfolge zurück, sodass wir Felder manuell analysieren, Typen umwandeln und Werte auf der Python-Seite validieren müssen.
Auf der anderen Seite verbessert Structured Outputs dies, indem es dank der eingeschränkten Dekodierung auf Modellebene garantiert, dass der zurückgegebene JSON immer unserem definierten Schema entspricht. Dennoch ist es immer noch nur eine JSON-Zeichenfolge, die wir in Python selbst verwalten müssen.
Mit Pydantic definieren wir unser Schema als Python-Klasse, und die Integration mit der OpenAI-API bedeutet, dass wir zurückkommen ein richtiges Python-Objekt, kein Wörterbuchwobei alle Felder automatisch eingegeben und validiert werden. Das JSON-Schema, das die API benötigt, wird hinter den Kulissen aus unserem Pydantic-Modell generiert. Wir müssen es nie selbst schreiben. Mit anderen Worten, Pydantic ist die Schemadefinitionsschicht, die zwischen unserem Python-Code und der OpenAI-API liegtDadurch wird das gesamte strukturierte Ausgabeerlebnis sauberer, sicherer und wartbarer.
🍨 DataCream ist ein Publication zu KI, Daten und Technologie. Wenn Sie sich für diese Themen interessieren, Abonnieren Sie hier!
Aber sehen wir uns in der Praxis an, was wir durch die Verwendung von Pydantic im Vergleich zur bloßen Verwendung strukturierter Ausgaben gewinnen.

So sah die strukturierte Ausgabe vor der Pydantic-Integration aus:
from openai import OpenAI
import json
consumer = OpenAI(api_key="your_api_key")
# outline schema as a uncooked dictionary
instruments = (
{
"kind": "perform",
"perform": {
"title": "extract_person_info",
"strict": True,
"parameters": {
"kind": "object",
"properties": {
"title": {"kind": "string"},
"age": {"kind": "integer"},
"metropolis": {"kind": "string"}
},
"required": ("title", "age", "metropolis"),
"additionalProperties": False
}
}
}
)
response = consumer.chat.completions.create(
mannequin="gpt-4o-mini",
instruments=instruments,
tool_choice={"kind": "perform", "perform": {"title": "extract_person_info"}},
messages=(
{"position": "consumer", "content material": "Extract data from: 'Maria is 32 years outdated and lives in Athens.'"}
)
)
# parse manually — we get again a plain dictionary
end result = json.hundreds(response.decisions(0).message.tool_calls(0).perform.arguments)
print(end result("title")) # "Maria" — however what if the secret is lacking? KeyError.
print(end result("age")) # is perhaps "32" as a substitute of 32 — kind not assured
Und hier ist das Gleiche mit Pydantic:
from openai import OpenAI
from pydantic import BaseModel
consumer = OpenAI(api_key="your_api_key")
class PersonInfo(BaseModel):
title: str
age: int
metropolis: str
response = consumer.beta.chat.completions.parse(
mannequin="gpt-4o-mini",
messages=(
{"position": "consumer", "content material": "Extract data from: 'Maria is 32 years outdated and lives in Athens.'"}
),
response_format=PersonInfo
)
# we get again a correct Python object, absolutely typed and validated
end result = response.decisions(0).message.parsed
print(end result.title) # "Maria" — at all times a string
print(end result.age) # 32 — at all times an integer, by no means a string
print(end result.metropolis) # "Athens" — at all times a string
Offensichtlich ist die Pydantic-Model kürzer, lesbarer und sicherer. Beachten Sie, wie wir unser Pydantic-Modell an übergeben response_formatund das OpenAI SDK, das die Generierung des JSON-Schemas übernimmt, führt lediglich den API-Aufruf durch strict: Trueund analysiert die Antwort wieder in eine richtige Antwort PersonInfo Objekt. Auf diese Weise erhalten wir Zugriff auf Felder mit Typsicherheit, Validierung und Punktnotation.
Beachten Sie darüber hinaus auch die .parse() Methode, statt der üblichen .create(). Das liegt daran .parse() ist eine Methode im OpenAI SDK, die speziell für die Arbeit mit Pydantic-Modellen entwickelt wurde und die gesamte strukturierte Ausgabepipeline durchgängig verarbeitet.
Aufbau mit Pydantic
1. verschachtelte Modelle und Hear
Einer der Bereiche, in denen Pydantic wirklich glänzt, sind verschachtelte Datenstrukturen. Das Schreiben eines rohen JSON-Schemas für verschachtelte Objekte ist mühsam, aber bei Verwendung von Pydantic werden nur Python-Klassen definiert:
from pydantic import BaseModel
from typing import Checklist
class Handle(BaseModel):
road: str
metropolis: str
nation: str
class ContactInfo(BaseModel):
title: str
electronic mail: str
handle: Handle
phone_numbers: Checklist(str)
response = consumer.beta.chat.completions.parse(
mannequin="gpt-4o-mini",
messages=(
{
"position": "consumer",
"content material": """Extract contact data from:
'Maria Mouschoutzi, (electronic mail protected),
Ermou 15, Athens, Greece.
Telephone: +30 210 1234567, +30 697 8901234'"""
}
),
response_format=ContactInfo
)
contact = response.decisions(0).message.parsed
print(contact.title) # "Maria Mouschoutzi"
print(contact.handle.metropolis) # "Athens"
print(contact.phone_numbers(0)) # "+30 210 1234567"
Der Handle Das Modell ist darin verschachtelt ContactInfo Natürlich übernimmt Pydantic die vollständige Validierung der verschachtelten Struktur. Wir können dann auf Felder mit sauberer Punktnotation zugreifen, wie zum Beispiel: contact.handle.metropolis statt end result("handle")("metropolis")was erhöhen kann KeyError wenn ein Zwischenschlüssel fehlt. Eine der Aufgaben, bei denen Pydantic wirklich praktisch ist, ist die Notwendigkeit verschachtelter Datenstrukturen.
2. Validierung mit Pydantic-Feld
Eine weitere Aufgabe, bei der Pydantic sehr nützlich ist, ist die Validierung der zurückgegebenen Daten. Diese Validierung umfasst die Prüfung von Datentypen, geht aber auch darüber hinaus, da Pydantic auch die Durchsetzung klarer Anweisungen zum tatsächlichen Inhalt der zurückgegebenen Werte ermöglicht. Genauer gesagt können wir mit explizite Validierungseinschränkungen hinzufügen Subjectund gibt dem Modell Anweisungen darüber, welche Werte akzeptabel sind und welche nicht.
Nehmen wir zum Beispiel an, wir haben ein Setup, das Daten zu Produktbewertungen zurückgibt:
from pydantic import BaseModel, Subject
from typing import Elective
class ProductReview(BaseModel):
product_name: str = Subject(description="The title of the product being reviewed")
score: int = Subject(ge=1, le=5, description="Score from 1 to five stars")
sentiment: str = Subject(description="Considered one of: optimistic, impartial, detrimental")
abstract: str = Subject(max_length=200, description="A quick abstract of the assessment")
verified_purchase: Elective(bool) = Subject(default=None, description="Whether or not it is a verified buy")
response = consumer.beta.chat.completions.parse(
mannequin="gpt-4o-mini",
messages=(
{
"position": "consumer",
"content material": """Extract a structured assessment from:
'Completely love this espresso machine!
Makes good espresso each time.
5 stars, would suggest to anybody.'"""
}
),
response_format=ProductReview
)
assessment = response.decisions(0).message.parsed
print(assessment.product_name) # "espresso machine"
print(assessment.score) # 5
print(assessment.sentiment) # "optimistic"
print(assessment.abstract) # "Makes good espresso each time."
Insbesondere die ge=1, le=5 auf der score Das Feld teilt dem Modell mit, dass gültige Bewertungen zwischen 1 und 5 liegen max_length=200 auf der abstract stellt sicher, dass wir nie eine Antwort erhalten, die länger als 200 Zeichen ist.
Darüber hinaus ist die description Felder fungieren als Anweisungen für das Modell und helfen ihm dabei, genau zu verstehen, was jedes Feld enthalten soll. Dies ist im Wesentlichen das Äquivalent von description Felder, die wir manuell in Funktionsaufrufschemata geschrieben haben, um dem Modell zu zeigen, was in die einzelnen Felder eingefügt werden soll.
3. Ablehnungen
Hervorzuheben ist auch die Verwendung .parse()verarbeitet das OpenAI SDK auch Modellablehnungen elegant. Genauer gesagt, für den Fall, dass das Mannequin sich weigert, einer Anfrage nachzukommen (z. B. weil der Inhalt gegen die Richtlinien des Fashions verstößt), wird die parsed Feld wird sein None und die refusal Das Feld enthält den Grund. Dies ermöglicht es uns, auch im Falle einer Ablehnung ein strukturiertes Ergebnis zu erhalten und den Anwendungsbenutzer zumindest über den Grund für die Ablehnung seiner Anfrage zu informieren. Dies ist von besonderer Bedeutung für KI-gestützte Apps, die in der Produktion laufen, wo wir nicht wirklich vorhersagen können, welche seltsamen Wünsche der Benutzer möglicherweise haben wird.
So würde sich additionally eine Modellverweigerung bei Pydantic auswirken:
response = consumer.beta.chat.completions.parse(
mannequin="gpt-4o-mini",
messages=({"position": "consumer", "content material": "Extract data from the job posting above."}),
response_format=JobPosting
)
message = response.decisions(0).message
if message.refusal:
print(f"Mannequin refused: {message.refusal}")
else:
job = message.parsed
print(f"Extracted: {job.job_title} at {job.company_name}")
Ein Beispiel aus der Praxis: Extraktion von Dokumentinformationen
Lassen Sie uns alles anhand eines realistischeren Beispiels zusammenfassen. Stellen Sie sich vor, wir bauen eine Pipeline auf, die Stellenausschreibungen verarbeitet und daraus strukturierte Informationen extrahiert. Dies ist genau die Artwork von Aufgabe, bei der strukturierte Ausgaben glänzen: unstrukturierte Eingaben, klar definierte Ausgabeschemata und ein nachgelagertes System, das das Ergebnis in eine Datenbank einfügen muss.
from pydantic import BaseModel, Subject
from typing import Checklist, Elective
from openai import OpenAI
consumer = OpenAI(api_key="your_api_key")
class SalaryRange(BaseModel):
min_salary: Elective(int) = Subject(default=None, description="Minimal wage in USD per yr")
max_salary: Elective(int) = Subject(default=None, description="Most wage in USD per yr")
forex: str = Subject(default="USD", description="Foreign money code")
class JobPosting(BaseModel):
job_title: str = Subject(description="The job title or position title")
company_name: str = Subject(description="The title of the hiring firm")
location: str = Subject(description="Job location, e.g. 'Athens, Greece' or 'Distant'")
employment_type: str = Subject(description="Considered one of: full-time, part-time, contract, freelance")
required_skills: Checklist(str) = Subject(description="Checklist of required technical expertise")
years_of_experience: Elective(int) = Subject(default=None, description="Minimal years of expertise required")
wage: Elective(SalaryRange) = Subject(default=None, description="Wage vary if talked about")
remote_friendly: bool = Subject(description="Whether or not distant work is allowed")
job_post_text = """
Senior Python Engineer at pialgorithms (Athens, Greece / Distant)
We're searching for an skilled Python developer to hitch our AI group.
You'll work on our doc administration platform, constructing clever
search and extraction options utilizing LLMs and RAG pipelines.
Necessities:
- 5+ years of Python expertise
- Sturdy information of FastAPI, LangChain, and vector databases
- Expertise with OpenAI API and Pydantic
- Familiarity with Docker and cloud deployment (AWS/GCP)
Wage: €60,000 - €85,000 per yr
Full-time place. Distant-friendly.
"""
response = consumer.beta.chat.completions.parse(
mannequin="gpt-4o-mini",
messages=(
{
"position": "system",
"content material": "You're an knowledgeable at extracting structured data from job postings."
},
{
"position": "consumer",
"content material": f"Extract all related data from this job posting:nn{job_post_text}"
}
),
response_format=JobPosting
)
job = response.decisions(0).message.parsed
print(f"Title: {job.job_title}")
print(f"Firm: {job.company_name}")
print(f"Location: {job.location}")
print(f"Distant: {job.remote_friendly}")
print(f"Expertise: {', '.be a part of(job.required_skills)}")
print(f"Expertise: {job.years_of_experience} years")
if job.wage:
print(f"Wage: {job.wage.min_salary} - {job.wage.max_salary} {job.wage.forex}")
Und die Ausgabe sieht in etwa so aus:
Title: Senior Python Engineer
Firm: pialgorithms
Location: Athens, Greece / Distant
Distant: True
Expertise: Python, FastAPI, LangChain, vector databases, OpenAI API, Pydantic, Docker, AWS, GCP
Expertise: 5 years
Wage: 60000 - 85000 EUR
Dies ist ein produktionsbereiter Extraktionscode. Das Verschachtelte SalaryRange Das Modell verarbeitet die optionalen Gehaltsinformationen sauber. Elective Felder sind standardmäßig auf None wenn Informationen fehlen, und jedes Feld in der Antwort wird automatisch eingegeben und validiert. Das Ergebnis kann ohne zusätzliche Verarbeitung direkt in eine Datenbank eingefügt oder an den nächsten Schritt in einer Pipeline übergeben werden.
In meinen Gedanken
Was mich an der Kombination aus Pydantic und OpenAI am meisten erfreut, ist, wie intestine sie mit der Denkweise eines Python-Entwicklers übereinstimmt. Es definiert Datenstrukturen als Klassen, verwendet Typhinweise und erkennt Typfehler frühzeitig und deutlich. All dies sind traditionell die unvorhersehbarsten Teile jeder KI-Anwendung, vor allem weil wir all dies manuell auf der Python-Seite der KI-App erledigen mussten. Pydantic fungiert als bessere und robustere Verbindung zwischen dem KI-Modell und dem Relaxation unseres Python-Codes.
✨ Danke fürs Lesen! ✨
Wenn du es bis hierher geschafft hast, Vielleicht finden Sie Pialgorithmen nützlich: eine von uns entwickelte Plattform, die Groups dabei hilft, organisatorisches Wissen sicher an einem Ort zu verwalten.
Hat Ihnen dieser Beitrag gefallen? Begleiten Sie mich auf 💌 Unterstapel und 💼 LinkedIn
Alle Bilder vom Autor, sofern nicht anders angegeben.
