PydanticAI, agent, którego wyjście da się otypować
PydanticAI powstał w zespole odpowiedzialnym za bibliotekę walidacyjną, na której opiera się większość nowoczesnego Pythona, i widać to w każdym elemencie. Odpowiedź modelu wraca jako obiekt zgodny ze schematem, zależności wstrzykujesz jak w porządnym frameworku webowym, a testowanie nie wymaga wywoływania modelu.
Co odróżnia to podejście
Większość bibliotek agentowych traktuje odpowiedź modelu jako tekst, który potem parsujesz. Tutaj punktem wyjścia jest typ, a biblioteka odpowiada za to, żeby wynik do niego pasował.
pip install pydantic-aifrom pydantic import BaseModel
from pydantic_ai import Agent
class Zgloszenie(BaseModel):
kategoria: str
priorytet: int
wymaga_czlowieka: bool
agent = Agent(
"openai:gpt-5-mini",
output_type=Zgloszenie,
system_prompt="Klasyfikujesz zgloszenia klientow. Priorytet od 1 do 5.",
)
wynik = agent.run_sync("Nie moge sie zalogowac od wczoraj, pilne")
print(wynik.output.priorytet)Zwrócony obiekt jest już zwalidowany, więc dostęp do pola nie wymaga sprawdzania, czy istnieje. Jeśli model odpowie niezgodnie ze schematem, biblioteka poprosi go o poprawkę, przekazując komunikat walidacji, a Ty widzisz dopiero poprawny wynik albo wyjątek.
To rozwiązuje najczęstszy problem integracji: obsługę odpowiedzi, która wygląda dobrze, a nie da się jej sparsować. Kod, który dotąd zajmował się wyrażeniami regularnymi i przypadkami brzegowymi, znika.
Zależności zamiast zmiennych globalnych
Drugim elementem, który odróżnia tę bibliotekę, jest sposób przekazywania kontekstu do narzędzi. Zamiast sięgać po zmienne globalne albo domknięcia, deklarujesz typ zależności, a biblioteka podaje go przy każdym wywołaniu.
from dataclasses import dataclass
from pydantic_ai import Agent, RunContext
@dataclass
class Zaleznosci:
baza: PolaczenieBazy
id_klienta: str
agent = Agent("openai:gpt-5", deps_type=Zaleznosci)
@agent.tool
async def historia_zamowien(ctx: RunContext[Zaleznosci], limit: int = 5) -> list[dict]:
"""Zwraca ostatnie zamowienia biezacego klienta.
Wywolaj, gdy pytanie dotyczy wczesniejszych zakupow.
"""
return await ctx.deps.baza.zamowienia(ctx.deps.id_klienta, limit)
wynik = await agent.run("Co ostatnio kupowalem?", deps=Zaleznosci(baza=db, id_klienta="42"))Ten układ ma trzy konsekwencje. Narzędzie nie wie, skąd bierze się połączenie do bazy, więc w teście podstawiasz atrapę. Identyfikator klienta pochodzi z kontekstu wywołania, a nie z argumentu, którym model mógłby manipulować. Kod narzędzia zostaje czysty, bo cała konfiguracja siedzi w jednym miejscu.
Ostatni punkt jest ważniejszy, niż wygląda. Agent obsługujący wielu użytkowników nigdy nie powinien pozwalać modelowi decydować, czyje dane pobrać. Przekazanie identyfikatora przez zależności, a nie przez schemat narzędzia, zamyka tę drogę na poziomie architektury.
Testowanie bez wywoływania modelu
To obszar, w którym ta biblioteka daje najwięcej, a jednocześnie najrzadziej się z tego korzysta. Testy agenta zwykle albo nie istnieją, albo kosztują tokeny przy każdym uruchomieniu.
from pydantic_ai.models.test import TestModel
def test_klasyfikacja_zwraca_priorytet():
with agent.override(model=TestModel()):
wynik = agent.run_sync("Nie dziala platnosc")
assert 1 <= wynik.output.priorytet <= 5Model testowy generuje odpowiedzi pasujące do zadeklarowanego schematu, więc sprawdzasz przepływ, obsługę zależności i walidację bez sieci i bez kosztu. Do sprawdzenia konkretnej ścieżki możesz też podstawić model zwracający ustaloną odpowiedź.
Osobno warto testować narzędzia jako zwykłe funkcje. Skoro zależności są wstrzykiwane, wywołanie narzędzia w teście nie wymaga agenta ani modelu, tylko obiektu zależności z atrapami.
Trzecia warstwa to ocena jakości na zestawie przypadków, do której służy osobny pakiet z tej samej rodziny. To już nie jest test jednostkowy, tylko pomiar, więc uruchamiaj go świadomie, bo zużywa tokeny.
Obserwowalność i trwałe wykonanie
Biblioteka wpina się w standard telemetryczny, więc ślady wywołań trafiają do narzędzia obserwacyjnego tego samego producenta albo do dowolnego zgodnego systemu. Widzisz wtedy wywołania narzędzi, ponowienia po nieudanej walidacji i zużycie tokenów w jednym miejscu.
Ta ostatnia liczba bywa najbardziej pouczająca. Agent, który dwa razy poprawia odpowiedź niezgodną ze schematem, zużywa trzy razy więcej tokenów niż wynikałoby z pojedynczego wywołania, a bez śladów ta różnica jest niewidoczna.
Osobnym tematem jest trwałe wykonanie, czyli odporność na awarie w trakcie długiego zadania. Biblioteka integruje się z silnikami przepływów, dzięki czemu proces przerwany błędem sieci albo restartem procesu wznawia się od miejsca zatrzymania, zamiast zaczynać od zera.
To ma znaczenie przy zadaniach wielokrokowych, gdzie każdy krok kosztuje. Wznowienie procesu, który wykonał już siedem z dziesięciu wywołań, oszczędza nie tylko czas, ale też pieniądze wydane na tokeny.
Narzędzia, MCP i zestawy
Narzędzie definiujesz dekoratorem, a schemat argumentów powstaje z adnotacji typów i dokumentacji funkcji. To ten sam mechanizm, który znasz z frameworków webowych tego zespołu, więc nie ma osobnego formatu do nauczenia.
Zestawy narzędzi pozwalają grupować funkcje i podłączać je do agenta w całości, co przy dziesięciu narzędziach porządkuje kod. Biblioteka obsługuje też podłączanie serwerów w protokole MCP, więc gotowe integracje wpinasz bez pisania własnych opakowań.
Osobną kategorią są narzędzia odroczone, czyli takie, których wykonanie wymaga decyzji człowieka albo dłuższego procesu poza agentem. Agent zgłasza wtedy potrzebę wywołania, a Ty realizujesz je we własnym tempie i wracasz z wynikiem. To mechanizm potrzebny wszędzie tam, gdzie działanie dotyka pieniędzy albo komunikacji z klientem.
Strumieniowanie i odpowiedź częściowa
Aplikacja z interfejsem potrzebuje pokazać coś wcześniej niż po ośmiu sekundach. Biblioteka obsługuje strumieniowanie także wtedy, gdy wyjście jest otypowane, co brzmi sprzecznie, a działa dzięki walidacji częściowej.
async with agent.run_stream("Opisz awarie z ostatniej doby") as strumien:
async for fragment in strumien.stream_output(debounce_by=0.1):
aktualizuj_widok(fragment)Kolejne wersje obiektu przychodzą w miarę, jak model uzupełnia pola, a biblioteka waliduje to, co już dotarło. Widok dostaje najpierw kategorię, potem opis, a nie pusty ekran do końca generowania.
Parametr opóźnienia ma tu znaczenie praktyczne. Bez niego dostajesz aktualizację po każdym tokenie, co przy odświeżaniu interfejsu potrafi obciążyć przeglądarkę bardziej niż samo wywołanie modelu. Wartość rzędu jednej dziesiątej sekundy zwykle wystarcza.
Osobno warto pamiętać, że strumieniowanie nie zmniejsza kosztu. Płacisz za te same tokeny, zyskujesz wyłącznie odczuwalny czas reakcji, ale przy dłuższych odpowiedziach to różnica między aplikacją używalną a taką, którą użytkownik zamyka.
Przepływy wieloetapowe i grafy
Pojedynczy agent wystarcza do zadań, które da się opisać jednym zestawem narzędzi. Gdy proces ma wyraźne etapy z rozgałęzieniami, biblioteka udostępnia osobny pakiet do budowania grafów, gdzie każdy węzeł jest krokiem, a przejścia deklarujesz typami zwracanymi.
To podejście różni się od popularnego modelu ze wspólnym stanem. Zamiast słownika, do którego każdy węzeł dopisuje pola, przekazujesz obiekty konkretnych klas, a typ zwracany węzła określa, gdzie proces może przejść dalej. Błędne połączenie wychodzi przy sprawdzaniu typów, a nie przy trzecim uruchomieniu.
W praktyce warto zacząć od jednego agenta z kilkoma narzędziami i sięgnąć po graf dopiero wtedy, gdy pojawi się potrzeba zatrzymania procesu na decyzję człowieka albo wznowienia go po przerwie. Wcześniejsze wprowadzenie grafu dodaje kodu, nie dodając kontroli.
Dobrym sygnałem, że pora na graf, jest moment, w którym prompt systemowy zaczyna opisywać kolejność kroków. Skoro kolejność jest znana, lepiej wyrazić ją w kodzie niż liczyć, że model za każdym razem ją odtworzy.
Ocena jakości zamiast wrażeń
Zmiana promptu, która subiektywnie poprawia odpowiedzi, często psuje inny przypadek. Biblioteka do ocen z tej samej rodziny pozwala zdefiniować zestaw przypadków i mierzyć wynik po każdej zmianie.
from pydantic_evals import Case, Dataset
zestaw = Dataset(cases=[
Case(name="logowanie", inputs="Nie moge sie zalogowac", expected_output={"priorytet": 4}),
Case(name="faktura", inputs="Prosze o duplikat faktury", expected_output={"priorytet": 2}),
])
raport = zestaw.evaluate_sync(zadanie)Trzydzieści przypadków wystarczy, żeby zobaczyć, czy zmiana pomaga. Zbieraj je z prawdziwego ruchu, a zwłaszcza z sytuacji, w których agent zawiódł, bo to one najczęściej wracają.
Warto rozdzielić dwa rodzaje sprawdzeń. Ocena deterministyczna, czyli porównanie pola z oczekiwaną wartością, jest tania i szybka. Ocena z użyciem modelu jako sędziego kosztuje tokeny i wprowadza własny szum, więc rezerwuj ją do rzeczy, których nie da się sprawdzić prościej.
PydanticAI kontra alternatywy
| Narzędzie | Mocna strona | Słabość | Kiedy wybrać |
|---|---|---|---|
| PydanticAI | Typowanie wyjścia i zależności, testowanie bez modelu, spójność z ekosystemem | Mniejsza baza gotowych integracji | Aplikacja produkcyjna w Pythonie, wymagana przewidywalność |
| LangChain | Największy zbiór integracji, agent w jednej funkcji | Więcej abstrakcji do rozgryzienia | Projekt łączący wiele modeli, narzędzi i źródeł |
| CrewAI | Czytelny model ról, szybki start | Mniej kontroli nad kształtem wyniku | Zespół agentów z podziałem zadań |
| LangGraph | Pełna kontrola nad przepływem, trwały stan | Więcej pracy przy prostych przypadkach | Proces z rozgałęzieniami i zatrzymaniami |
Wybór zależy od tego, co ma być gwarantowane. Jeśli najważniejsze jest, żeby wynik dał się bezpiecznie przekazać dalej w kodzie, typowanie wygrywa. Jeśli najważniejsza jest liczba gotowych integracji, wygrywa większy ekosystem.
Warto wiedzieć, że te biblioteki nie wykluczają się. Agent zbudowany tutaj może korzystać z serwerów MCP udostępnianych przez inne narzędzia, a wynik przekazywać do przepływu zbudowanego gdzie indziej.
Ustawienia modelu, limity i model zapasowy
Agent produkcyjny prędzej czy później trafi na przeciążenie dostawcy albo limit zapytań. Biblioteka pozwala zadeklarować model zapasowy, który przejmuje wywołanie, gdy podstawowy zwróci błąd, bez zmiany reszty kodu.
Ten sam mechanizm bywa używany do oszczędzania. Prostszy przypadek obsługuje tańszy model, a droższy wchodzi dopiero wtedy, gdy tańszy nie zmieści się w schemacie. Warto jednak zmierzyć, czy taki układ faktycznie oszczędza, bo dwa wywołania zamiast jednego potrafią kosztować więcej niż od razu użycie mocniejszego modelu.
Ustawienia modelu, między innymi limit tokenów odpowiedzi i czas oczekiwania, deklarujesz raz przy agencie albo nadpisujesz przy pojedynczym uruchomieniu. Limit czasu jest tym, o którym najczęściej się zapomina, a to on decyduje, czy zawieszone wywołanie zablokuje proces na kwadrans.
Osobno ustaw limity na sam przebieg: maksymalną liczbę wywołań modelu i maksymalną liczbę wywołań narzędzi. Agent w pętli, który wywołuje to samo narzędzie czterdzieści razy, to nie jest sytuacja teoretyczna, a bez limitu zobaczysz ją dopiero na rachunku.
Kiedy nie warto po to sięgać
Do pojedynczego wywołania modelu, gdzie chcesz tylko tekst, ta biblioteka jest nadmiarowa. Klient dostawcy wystarczy i doda mniej zależności.
Podobnie przy prototypie, którego celem jest sprawdzenie, czy model w ogóle poradzi sobie z zadaniem. Typowanie pomaga, gdy wynik ma trafić do kodu, a nie gdy patrzysz na niego oczami.
Trzeci przypadek to zespół pracujący głównie w TypeScripcie. Biblioteka jest pythonowa, więc integracja przez osobną usługę dokłada warstwę, której lepiej uniknąć, jeśli reszta systemu żyje w innym języku.
Granica jest dość czytelna. Jeśli odpowiedź modelu ma trafić do funkcji, która coś z nią zrobi, typowanie zwraca się od pierwszego dnia. Jeśli odpowiedź czyta człowiek, zysk jest niewielki.
Typowe błędy
Pierwszy to zbyt złożony schemat wyjścia. Model wypełniający strukturę z piętnastoma polami i zagnieżdżonymi listami częściej trafia na walidację, a każda poprawka kosztuje. Zacznij od trzech pól i rozszerzaj.
Drugi to przekazywanie identyfikatora użytkownika w schemacie narzędzia zamiast w zależnościach. Model może wtedy podać cudzy identyfikator, a Ty nie masz na to zabezpieczenia poza pilnowaniem promptu.
Trzeci to brak limitu ponowień przy walidacji. Model, który uporczywie zwraca niezgodny wynik, powtórzy próbę tyle razy, ile pozwolisz, więc ustaw limit i traktuj jego przekroczenie jako sygnał do poprawy schematu.
Czwarty to dokumentacja funkcji pisana dla programisty. Model czyta ten sam tekst, więc zdanie mówiące, kiedy narzędzie wywołać, jest ważniejsze niż opis implementacji.
Piąty to pomijanie testów, bo agent i tak jest niedeterministyczny. Model testowy pozwala sprawdzić wszystko poza samą treścią odpowiedzi, a to właśnie tam kryje się większość błędów.
FAQ
Czy PydanticAI jest darmowy?
Tak, biblioteka jest otwarta na licencji MIT i darmowa także komercyjnie. Płacisz wyłącznie za wywołania modelu u wybranego dostawcy. Producent prowadzi osobno narzędzie do obserwowalności, które ma plan darmowy z pulą dziesięciu milionów zapisów miesięcznie, wstrzymywaną po przekroczeniu limitu, oraz plany płatne od 49 dolarów miesięcznie. Biblioteka działa bez niego i wysyła telemetrię do dowolnego zgodnego systemu.
PydanticAI czy LangChain?
PydanticAI wybierasz, gdy zależy Ci na przewidywalnym kształcie wyniku, testowalności i spójności z resztą kodu w Pythonie. LangChain ma znacznie więcej gotowych integracji i lepiej pasuje, gdy potrzebujesz podłączyć wiele źródeł danych bez pisania własnych adapterów.
Czy działa z modelami innymi niż OpenAI?
Tak, warstwa modelu jest niezależna od dostawcy i obsługuje między innymi Claude, Gemini oraz modele lokalne uruchomione przez Ollamę. Zmiana sprowadza się do podmiany identyfikatora modelu, przy czym prompt zwykle wymaga dostrojenia.
Co daje typowanie wyjścia w praktyce?
Znika warstwa kodu obsługująca parsowanie i przypadki, w których odpowiedź nie pasuje do oczekiwań. Wynik wraca jako obiekt zgodny ze schematem albo nie wraca wcale, więc kolejne kroki w kodzie mogą na nim polegać bez sprawdzania. Przy zadaniach klasyfikacyjnych i ekstrakcji danych to największa oszczędność.
Czy nadaje się do produkcji?
Tak, przy dwóch warunkach. Ustaw limity ponowień i obsługę błędów, bo bez nich nieudana walidacja zamienia się w pętlę kosztującą tokeny. Podłącz też telemetrię, ponieważ bez śladów wywołań diagnozowanie zachowania agenta sprowadza się do zgadywania.
Dokumentacja stoi na stronie projektu, a kod źródłowy w repozytorium na GitHubie.