0Pricing
AI Engineering Academy · Lekcja

Ustrukturyzowane dane wyjściowe z Pydantic

Uczestnicy zdefiniują modele Pydantic jako schemat danych wyjściowych, przekażą je do API za pomocą funkcji structured outputs oraz automatycznie zdeserializują odpowiedzi do typowanych obiektów Pythona.

Ustrukturyzowane dane wyjściowe z Pydantic to bezpłatna lekcja AI Engineering Academy na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej AI Engineering Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs AI Engineering Academy zawiera 4 lekcji w sumie.

Dlaczego Pydantic do danych wyjściowych LLM?

Pydantic to biblioteka języka Python do walidacji danych, która definiuje schematy danych za pomocą adnotacji typów języka Python. Doskonale sprawdza się przy walidowaniu i deserializacji danych z zewnętrznych źródeł — a dane wyjściowe LLM należą do najbardziej zawodnych zewnętrznych źródeł, z jakimi można się zetknąć. Połączenie schematów Pydantic z ustrukturyzowanymi danymi wyjściowymi OpenAI zapewnia bezpieczne typowo, zwalidowane i automatycznie deserializowane odpowiedzi modelu AI.

Zamiast pisać data = json.loads(response), a następnie ręcznie wyodrębniać pola i konwertować typy, otrzymujesz w pełni typowany obiekt Python, w którym każdy element ma zagwarantowany prawidłowy typ, a ponadto dostępne jest automatyczne uzupełnianie w IDE i sprawdzanie poprawności w czasie działania. Tak profesjonalne zespoły inżynierii AI obsługują ekstrakcję danych strukturalnych.

Definiowanie podstawowego schematu Pydantic

Model Pydantic to klasa dziedzicząca po BaseModel, której pola definiuje się za pomocą adnotacji typów języka Python. Typami pól mogą być podstawowe typy języka Python, inne modele Pydantic używane do zagnieżdżania lub typy z modułu typing służące do definiowania list, typów opcjonalnych i unii.

from pydantic import BaseModel, Field
from typing import Optional, List
from enum import Enum

class Sentiment(str, Enum):
    positive = 'positive'
    negative = 'negative'
    neutral = 'neutral'

class ReviewAnalysis(BaseModel):
    sentiment: Sentiment
    confidence: float = Field(ge=0.0, le=1.0, description='Confidence score 0-1')
    key_themes: List[str] = Field(description='Main topics mentioned in the review')
    summary: str = Field(max_length=200, description='One-sentence summary')
    product_name: Optional[str] = Field(default=None, description='Product mentioned, if any')
    would_recommend: Optional[bool] = None

# Pydantic validates types and constraints at instantiation
example = ReviewAnalysis(
    sentiment=Sentiment.positive,
    confidence=0.95,
    key_themes=['fast delivery', 'good quality'],
    summary='Customer loves the product and quick shipping.',
    product_name='Wireless Headphones',
    would_recommend=True
)
print(example.model_dump_json(indent=2))

Pydantic z ustrukturyzowanymi danymi wyjściowymi OpenAI

Przekaż klasę modelu Pydantic bezpośrednio do parametru response_format funkcji client.beta.chat.completions.parse(). SDK automatycznie konwertuje model na schemat JSON, wysyła go do API i deserializuje odpowiedź z powrotem do typowanego obiektu Python.

import openai
from pydantic import BaseModel
from typing import List, Optional
from enum import Enum

client = openai.OpenAI()

class Sentiment(str, Enum):
    positive = 'positive'
    negative = 'negative'
    neutral = 'neutral'

class ReviewAnalysis(BaseModel):
    sentiment: Sentiment
    confidence: float
    key_themes: List[str]
    summary: str
    would_recommend: Optional[bool]

review_text = '''
I bought this laptop for my design work and I am blown away. It handles Photoshop
like a dream, the screen colors are beautiful, and it has not slowed down once in
three months. Battery life could be better but overall highly recommend!
'''

result = client.beta.chat.completions.parse(
    model='gpt-4o-mini',
    messages=[
        {'role': 'system', 'content': 'Analyze the customer review and extract structured information.'},
        {'role': 'user', 'content': review_text}
    ],
    response_format=ReviewAnalysis
)

analysis = result.choices[0].message.parsed
print(f'Sentiment: {analysis.sentiment.value}')
print(f'Confidence: {analysis.confidence}')
print(f'Themes: {analysis.key_themes}')
print(f'Recommend: {analysis.would_recommend}')

Zagnieżdżone modele Pydantic

Schematy Pydantic mogą odwoływać się do innych modeli Pydantic, umożliwiając tworzenie ustrukturyzowanych danych wyjściowych zagnieżdżonych na dowolnej głębokości. Jest to idealne rozwiązanie do wyodrębniania hierarchicznych danych z dokumentów takich jak umowy, faktury, życiorysy i dokumentacja medyczna.

from pydantic import BaseModel
from typing import List, Optional

class Address(BaseModel):
    street: Optional[str]
    city: str
    country: str
    postal_code: Optional[str]

class ContactInfo(BaseModel):
    email: Optional[str]
    phone: Optional[str]
    address: Optional[Address]

class Person(BaseModel):
    full_name: str
    age: Optional[int]
    job_title: Optional[str]
    contact: ContactInfo
    skills: List[str]

# When you pass Person to response_format, the API generates:
# {
#   "full_name": "...",
#   "contact": {
#     "email": "...",
#     "address": { "city": "...", "country": "..." }
#   },
#   "skills": ["...", "..."]
# }
print('Nested model defined - pass to response_format for extraction')

Walidacja pól za pomocą walidatorów Pydantic

Walidatory Pydantic umożliwiają dodanie niestandardowej logiki walidacji wykraczającej poza proste sprawdzanie typów. Możesz sprawdzić, czy wynik pewności mieści się w przedziale od 0 do 1, czy cena nie jest ujemna albo czy ciąg znaków reprezentujący datę ma prawidłowy format. Gdy LLM zwróci wartość, która nie przejdzie walidacji, Pydantic zgłosi wyjątek ValidationError, który można przechwycić i obsłużyć.

from pydantic import BaseModel, Field, field_validator
from typing import Optional
import re

class ExtractedContact(BaseModel):
    name: str
    email: Optional[str] = None
    phone: Optional[str] = None
    confidence: float = Field(ge=0.0, le=1.0)

    @field_validator('email')
    @classmethod
    def validate_email(cls, v):
        if v is not None:
            # Basic email format check
            if not re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', v):
                raise ValueError(f'Invalid email format: {v}')
        return v

    @field_validator('phone')
    @classmethod
    def normalize_phone(cls, v):
        if v is not None:
            # Remove non-digit characters for normalization
            digits = re.sub(r'[^0-9+]', '', v)
            return digits
        return v

try:
    contact = ExtractedContact(name='Alice', email='not-an-email', confidence=0.9)
except Exception as e:
    print(f'Validation error: {e}')

Ekstrakcja list obiektów

Typowym zadaniem jest wyodrębnienie z dokumentu wielu wystąpień tej samej encji — wszystkich pozycji z faktury, wszystkich elementów działań z transkrypcji spotkania czy wszystkich encji z artykułu informacyjnego. Aby przejrzyście obsłużyć taki przypadek, opakuj model w model kontenera zawierający pole listy.

import openai
from pydantic import BaseModel
from typing import List

client = openai.OpenAI()

class ActionItem(BaseModel):
    task: str
    assignee: str
    due_date: str  # or use datetime with proper parsing
    priority: str  # high / medium / low

class MeetingNotes(BaseModel):
    meeting_title: str
    action_items: List[ActionItem]
    key_decisions: List[str]

meeting_transcript = '''
Q3 Planning Meeting - June 2025
Decision: Launch new feature in July.
Decision: Extend free trial to 30 days.
Action: Alice to finalize designs by June 30th - High priority.
Action: Bob to write API docs by July 5th - Medium priority.
Action: Carol to set up staging environment by June 28th - High priority.
'''

result = client.beta.chat.completions.parse(
    model='gpt-4o-mini',
    messages=[
        {'role': 'system', 'content': 'Extract structured data from meeting notes.'},
        {'role': 'user', 'content': meeting_transcript}
    ],
    response_format=MeetingNotes
)
notes = result.choices[0].message.parsed
for item in notes.action_items:
    print(f'[{item.priority.upper()}] {item.task} -> {item.assignee} by {item.due_date}')

Pola opcjonalne i wartości domyślne

Dokumenty z rzeczywistego świata są niekompletne. W życiorysie może nie być numeru telefonu, na fakturze może nie być numeru faktury, a opinia o produkcie może nie zawierać nazwy produktu. Projektuj modele Pydantic tak, aby łagodnie obsługiwały brakujące dane, używając pól Optional z odpowiednimi wartościami domyślnymi.

Pole opatrzone adnotacją Optional[str] = None informuje zarówno Pydantic, jak i LLM, że może go nie być. Model zwróci w JSON wartość null dla pól, których nie uda mu się wyodrębnić, a Pydantic zdeserializuje ją do wartości None języka Python. Dzięki temu można ją poprawnie obsłużyć w dalszej części procesu bez wyjątków KeyError.

Konwertowanie modeli Pydantic na schemat JSON

Zdefiniowany model Pydantic jest automatycznie konwertowany na schemat JSON po przekazaniu go do API. Możesz przeanalizować ten schemat, aby dokładnie zrozumieć, jakie reguły wyegzekwuje API. Jest to pomocne podczas debugowania przypadków, w których model nie zwraca oczekiwanej struktury.

from pydantic import BaseModel, Field
from typing import List, Optional
import json

class ProductExtraction(BaseModel):
    name: str = Field(description='Product name as mentioned in the text')
    price_usd: Optional[float] = Field(default=None, description='Price in USD')
    features: List[str] = Field(default_factory=list)
    in_stock: bool = Field(description='Whether the product is currently available')

# See the JSON Schema that will be sent to the API
schema = ProductExtraction.model_json_schema()
print(json.dumps(schema, indent=2))
# This shows exactly what constraints the API will enforce

Obsługa niepowodzeń ekstrakcji

Nawet w przypadku ustrukturyzowanych danych wyjściowych ekstrakcja może zakończyć się niepowodzeniem na dwa sposoby: model odmówi odpowiedzi (zwróci odmowę) albo dokument rzeczywiście nie zawiera żądanych informacji, więc model zwróci wartości null dla wymaganych pól — co wywoła błąd walidacji Pydantic, ponieważ pola wymagane nie mogą mieć wartości null.

Najbezpieczniejsze podejście polega na oznaczeniu wszystkich pól jako Optional i nadaniu im wartości domyślnych, akceptowaniu wartości null dla brakujących danych oraz zastosowaniu własnej walidacji logiki biznesowej po ekstrakcji. Pozwala to oddzielić ekstrakcję (uzyskiwanie danych z tekstu) od walidacji (sprawdzanie, czy dane spełniają wymagania).

import openai
from pydantic import BaseModel, ValidationError
from typing import Optional

client = openai.OpenAI()

class ContactExtraction(BaseModel):
    name: Optional[str] = None
    email: Optional[str] = None
    phone: Optional[str] = None

try:
    result = client.beta.chat.completions.parse(
        model='gpt-4o-mini',
        messages=[
            {'role': 'system', 'content': 'Extract contact information.'},
            {'role': 'user', 'content': 'I would like to discuss partnership opportunities.'}
        ],
        response_format=ContactExtraction
    )
    msg = result.choices[0].message
    if msg.refusal:
        print('Refused:', msg.refusal)
    else:
        contact = msg.parsed
        if not any([contact.name, contact.email, contact.phone]):
            print('No contact information found in text')
        else:
            print(contact.model_dump())
except ValidationError as e:
    print('Validation failed:', e)

Używanie Pydantic z biblioteką instructor

Biblioteka instructor to popularny pakiet innej firmy, który modyfikuje klienta OpenAI, aby obsługiwał ekstrakcję opartą na Pydantic z automatycznym ponawianiem prób po nieudanej walidacji. Jeśli model zwróci dane wyjściowe, które nie przejdą walidacji Pydantic, instructor automatycznie ponowi próbę, używając promptu zawierającego błąd walidacji. Dzięki temu model może skorygować odpowiedź.

Jest to szczególnie przydatne w potokach ekstrakcji wsadowej, w których nie można ręcznie sprawdzić każdego wyniku i zależy Państwu na samoczynnym korygowaniu systemu bez udziału człowieka.

# pip install instructor
import instructor
import openai
from pydantic import BaseModel, Field
from typing import Optional

# Patch the OpenAI client with instructor
client = instructor.from_openai(openai.OpenAI())

class ProductInfo(BaseModel):
    name: str
    price_usd: float = Field(gt=0, description='Price must be positive')
    brand: Optional[str] = None

# instructor automatically retries if Pydantic validation fails
product = client.chat.completions.create(
    model='gpt-4o-mini',
    messages=[
        {'role': 'user', 'content': 'The Sony WH-1000XM5 headphones cost $279.99 at Best Buy.'}
    ],
    response_model=ProductInfo,  # instructor-specific parameter
    max_retries=3
)
print(f'{product.name}: ${product.price_usd} by {product.brand}')

Unie rozróżniane i dynamiczne schematy

Pydantic obsługuje unie rozróżniane — schematy, w których struktura zależy od wartości pola rozróżniającego. Jest to przydatne, gdy różne typy dokumentów mają wspólną podstawę, ale różnią się dodatkowymi polami. Na przykład raport wydatków może zawierać rachunek za lot (z miejscem wylotu i przylotu) albo rachunek za hotel (z datami zameldowania i wymeldowania).

Używając typów Union z polem rozróżniającym Literal, można zdefiniować pojedynczy schemat ekstrakcji obsługujący wiele wariantów dokumentów, a model wybierze właściwy podtyp na podstawie treści dokumentu. Pydantic automatycznie przeprowadzi walidację względem właściwego podtypu na podstawie wartości pola rozróżniającego.

from pydantic import BaseModel
from typing import Union, Literal, Optional

class FlightExpense(BaseModel):
    expense_type: Literal['flight']
    airline: str
    departure_city: str
    arrival_city: str
    amount_usd: float

class HotelExpense(BaseModel):
    expense_type: Literal['hotel']
    hotel_name: str
    check_in: str
    check_out: str
    amount_usd: float

class MealExpense(BaseModel):
    expense_type: Literal['meal']
    restaurant: Optional[str]
    amount_usd: float

class ExpenseReport(BaseModel):
    submitter: str
    expenses: list[Union[FlightExpense, HotelExpense, MealExpense]]
    total_usd: float

print('Discriminated union schema - model selects correct subtype per item')

Szybkie sprawdzenie

Sprawdź swoją znajomość zagadnień inżynierii AI omówionych w tej lekcji.

Podsumowanie lekcji

W tej lekcji dowiedziałeś się, że: podklasy Pydantic BaseModel definiują silnie typowane schematy ekstrakcji, które ustrukturyzowane dane wyjściowe OpenAI egzekwują na poziomie API, zagnieżdżone modele, pola Optional i typy List obsługują złożone struktury dokumentów z rzeczywistego świata oraz biblioteka instructor dodaje automatyczne ponawianie prób po nieudanej walidacji, zapewniając niezawodne potoki ekstrakcji wsadowej. W następnej części zbudujemy kompletny potok ekstrakcji informacji z nieustrukturyzowanych źródeł tekstowych.

Często zadawane pytania

Czy lekcja „Ustrukturyzowane dane wyjściowe z Pydantic” jest bezpłatna?

Tak — pełny tekst „Ustrukturyzowane dane wyjściowe z Pydantic” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu AI Engineering Academy, przejdź na CoddyKit PRO. Kurs AI Engineering Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Ustrukturyzowane dane wyjściowe z Pydantic”?

Uczestnicy zdefiniują modele Pydantic jako schemat danych wyjściowych, przekażą je do API za pomocą funkcji structured outputs oraz automatycznie zdeserializują odpowiedzi do typowanych obiektów Pyth… Ćwiczysz AI Engineering Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć AI Engineering Academy?

Nie wymagamy żadnego doświadczenia. AI Engineering Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.

Ile czasu zajmuje lekcja „Ustrukturyzowane dane wyjściowe z Pydantic”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji AI Engineering Academy?

Tak. Każda lekcja AI Engineering Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Tryb JSON i response_format
  2. Ustrukturyzowane dane wyjściowe z Pydantic
  3. Ekstrakcja danych z nieustrukturyzowanego tekstu
  4. Walidowanie i ponawianie dla niepoprawnych danych wyjściowych
← Powrót do AI Engineering Academy