Structured outputs z JSON Schema: jak wymusić niezawodne odpowiedzi agentów
Ustrukturyzowane wyniki z JSON Schema to technika, w której model LLM jest zmuszony zwrócić odpowiedź dokładnie według z góry określonego schematu JSON. Zamiast swobodnego tekstu model dostarcza obiekt czytelny maszynowo, który można zwalidować. Dzięki temu pipeline'y agentów stają się niezawodne, bo kolejne kroki programu mogą polegać na gwarantowanej strukturze danych.
W skrócie
- Ustrukturyzowane wyniki wymuszają stały schemat JSON i zastępują podatne na błędy parsowanie swobodnego tekstu gwarantowaną strukturą danych, którą można zwalidować.
- Istnieją trzy poziomy gwarancji: sam prompt (brak gwarancji), tryb JSON (poprawny JSON, ale nie Twój schemat) oraz constrained decoding lub Structured Outputs (schemat ściśle wymuszony).
- Constrained decoding gwarantuje składnię i zgodność ze schematem, ale nie poprawność merytoryczną. Walidacja merytoryczna i strategia ponawiania pozostają obowiązkowe.
- W Anthropic Claude schemat wymuszają Structured Outputs, albo jako JSON Output (output_config.format), albo jako Strict Tool Use (strict: true). W OpenAI służą do tego Structured Outputs w trybie strict. Zwykłe wywoływanie narzędzi bez strict nie gwarantuje schematu.
- Dla agentów wieloetapowych to kluczowe: każdy zawodny krok mnoży się w całym łańcuchu i sprawia, że skuteczność end-to-end gwałtownie spada.
- Walidacja za pomocą Pydantic lub Zod plus ograniczona liczba ponowień z informacją zwrotną o błędzie to praktyczny standard dla solidnych pipeline'ów.
Ustrukturyzowane wyniki z JSON Schema to technika, w której model LLM jest zmuszony zwrócić odpowiedź dokładnie według z góry określonego schematu JSON. Zamiast swobodnego tekstu model dostarcza obiekt czytelny maszynowo, który można zwalidować. Dzięki temu pipeline'y agentów stają się niezawodne, bo kolejne kroki programu mogą polegać na gwarantowanej strukturze danych, zamiast parsować podatny na błędy swobodny tekst.
- Co? Model zwraca JSON według stałego schematu, a nie swobodny tekst. Pola, typy i dozwolone wartości są zdefiniowane.
- Dlaczego? Programy mogą bezpośrednio przetwarzać wynik, bez kruchej logiki opartej na regexach czy operacjach na stringach. To podstawa niezawodnych agentów.
- Jak? Za pomocą funkcji dostawców, takich jak Structured Outputs od OpenAI i Anthropic, albo przez constrained decoding w modelach open-weight, uzupełnione o walidację i ponawianie.
Dlaczego parsowanie swobodnego tekstu zawodzi w pipeline'ach agentów
Model językowy domyślnie generuje tekst dla ludzkich czytelników. Dla chatbota to ideał, dla zautomatyzowanego pipeline'u już problem. Gdy tylko program musi wyciągnąć z odpowiedzi jakąś wartość, na przykład kategorię, datę, kwotę lub listę, zaczyna się kruche zajęcie, jakim jest parsowanie. Raz model pisze "Die Kategorie ist Rechnung.", raz "Kategorie: Rechnung", a innym razem dodaje przed tym wyjaśnienie. Każdy z tych wariantów psuje naiwną logikę ekstrakcji.
W agentach wieloetapowych problem drastycznie się nasila, bo zawodność mnoży się w całym łańcuchu. Przykład obliczeniowy: jeśli każdy pojedynczy krok ma skuteczność parsowania na poziomie 95%, skuteczność end-to-end łańcucha złożonego z dziesięciu kroków wynosi już tylko 0,95 do potęgi 10, czyli około 60%. Jeśli dzięki ustrukturyzowanym wynikom podniesiesz skuteczność każdego kroku do 99,5%, ten sam łańcuch osiągnie około 95%. Z tego efektu mnożenia wynika główna teza tego artykułu: ustrukturyzowane wyniki nie są funkcją dla wygody, lecz warunkiem, by pipeline'y agentów w ogóle nadawały się do produkcji.
Trzy poziomy gwarancji w skrócie
Nie każda metoda, która obiecuje JSON, daje tę samą gwarancję. Trzeba rozróżnić trzy poziomy, a różnica między nimi przesądza o niezawodności.
Sam prompt. Grzecznie prosisz model, żeby zwrócił JSON. Często to działa, ale nie zawsze. Model może dodać bloki kodu Markdown, pominąć pola albo w razie niepewności wrócić do zwykłego tekstu. Brak gwarancji.
Tryb JSON. Dostawca gwarantuje, że wynik jest składniowo poprawnym JSON-em. To eliminuje błędy składni, ale nic nie mówi o strukturze: pól może brakować, mogą mieć złe typy albo mogą pojawić się dodatkowe. Gwarancja częściowa.
Constrained decoding / Structured Outputs. Tutaj proces generowania jest ograniczony na poziomie tokenów tak, że mogą powstawać wyłącznie tokeny zgodne ze schematem. Model w ogóle nie może już wyjść poza wynik zgodny ze schematem. Wymagane pola, typy i dozwolone wartości enum są gwarantowane. Na tej technice opierają się OpenAI Structured Outputs (tryb strict) oraz Structured Outputs od Anthropic, czyli JSON Outputs przez output_config.format i Strict Tool Use z strict: true. Zwykłe wywoływanie narzędzi bez strict nie gwarantuje schematu. W modelach open-weight to samo zadanie przejmują biblioteki i stacki inferencyjne, korzystając z ograniczeń gramatycznych.
Porównanie: metoda, gwarancja, kompromis
Metoda | Gwarancja | Kompromis |
|---|---|---|
Sam prompt ("odpowiedz w formacie JSON") | Brak. Model może odbiegać od formatu lub zwrócić zwykły tekst | Zero pracy przy wdrożeniu, ale brak niezawodności na produkcji |
Tryb JSON | Składniowo poprawny JSON, ale nie Twój schemat | Eliminuje błędy składni, ale nie brakujące pola ani pola o złym typie |
Structured Outputs / strict (OpenAI) | Schemat ściśle wymuszony (pola, typy, enumy) | Pierwsze zapytanie dla danego schematu może kosztować opóźnienie przez kompilację schematu; ograniczenia obsługiwanego podzbioru schematu |
JSON Outputs / Strict Tool Use (Anthropic Claude) | Schemat wymuszony przez constrained decoding (output_config.format lub strict: true) | Pierwsze zapytanie dla danego schematu kosztuje opóźnienie przez kompilację gramatyki; ograniczenia obsługiwanego podzbioru schematu |
Constrained decoding (open-weight, lokalne stacki) | Gramatyka wymusza zgodność ze schematem na poziomie tokenów | Wymaga kontroli nad stackiem inferencyjnym; złożoność schematu wpływa na szybkość |
Schemat + walidacja + ponawianie (Pydantic/Zod) | Gwarantowana forma plus kontrola semantyczna | Więcej kodu i logiki, ale najsolidniejszy wariant dla krytycznych pipeline'ów |
Najważniejszy wniosek z tej tabeli: constrained decoding gwarantuje formę, a nie treść. Model na pewno zwróci poprawny obiekt z polem rechnungsdatum, ale żadna metoda dekodowania nie zapewni, że wpisana tam wartość rzeczywiście odpowiada dacie z dokumentu. Dlatego walidacja merytoryczna i strategia ponawiania pozostają niezbędne.
Co oferują dostawcy w 2026 roku
Najwięksi dostawcy wiążą już odpowiedź ze schematem JSON, ale różnią się parametrami i ograniczeniami.
- OpenAI: Structured Outputs z
strict: true, jako format odpowiedzi typujson_schemalub przy function calling. Tryb JSON (json_object) zwraca natomiast tylko poprawny JSON, bez przestrzegania schematu. - Anthropic Claude: JSON Outputs przez
output_config.formatlub Strict Tool Use zstrict: true, w obu przypadkach z próbkowaniem ograniczonym gramatyką. BezstrictClaude przy wywoływaniu narzędzi może zwracać złe typy ("2"zamiast2) albo pomijać wymagane pola. API nie obsługuje schematów rekurencyjnych ani ograniczeń takich jakminimumczymaxLength. Dlatego limity długości, jak w przykładzie poniżej, trafiają dodescriptioni do walidacji. - Google Gemini: API przyjmuje schemat JSON wraz z typem MIME
application/jsonjako format odpowiedzi. Sam Google zaleca mimo to sprawdzanie wartości w aplikacji. - Modele open-weight: tutaj schemat wymusza za pomocą gramatyki stack inferencyjny. vLLM przyjmuje schemat JSON bezpośrednio w swoim serwerze zgodnym z OpenAI, a llama.cpp tłumaczy część JSON Schema na gramatyki GBNF. Jeśli sam utrzymujesz stack, konfigurujesz constrained decoding niezależnie od API dostawcy.
Ważne w praktyce: wielu dostawców inferencji oferuje endpointy zgodne z OpenAI, ale różnie implementuje ustrukturyzowane wyniki. Zanim się zdecydujesz, sprawdź, czy konkretny dostawca rzeczywiście egzekwuje tryb strict lub ograniczenia schematu, czy oferuje tylko tryb JSON.
Przykładowy schemat i strategia ponawiania
Konkretny przykład dobrze pokazuje tę zasadę. Agent ma klasyfikować przychodzące zgłoszenia do supportu i wyodrębniać najważniejsze pola. Schemat JSON (uproszczony) wygląda tak:
```json
{
"type": "object",
"properties": {
"kategorie": {
"type": "string",
"enum": ["rechnung", "technik", "vertrag", "sonstiges"],
"description": "Główna kategoria zgłoszenia"
},
"dringlichkeit": {
"type": "string",
"enum": ["niedrig", "mittel", "hoch"]
},
"kundennummer": {
"type": ["string", "null"],
"description": "Numer klienta, jeśli podano go w tekście, w przeciwnym razie null"
},
"zusammenfassung": {
"type": "string",
"description": "Jedno zdanie, maksymalnie 200 znaków"
}
},
"required": ["kategorie", "dringlichkeit", "kundennummer", "zusammenfassung"],
"additionalProperties": false
}
```
Kluczowe są tu dwie decyzje projektowe. Po pierwsze, enumy zamiast swobodnego tekstu dla kategorie i dringlichkeit: wyklucza to sytuację, w której model wymyśla "Rechnungswesen" albo "sehr hoch", co zepsułoby dalszą logikę przekierowywania. Po drugie, additionalProperties: false, żeby nie pojawiały się niechciane dodatkowe pola. Pola description działają przy tym jak niejawna instrukcja dla modelu.
Na tym schemacie opiera się strategia z ograniczoną liczbą ponowień. Pseudokod wywołuje LLM najwyżej trzy razy:
```
versuch = 0
while versuch < 3:
antwort = llm.call(prompt, schema=support_schema) # ustrukturyzowany wynik
objekt = parse_json(antwort) # forma jest gwarantowana
fehler = validiere(objekt) # kontrola semantyczna
if not fehler:
return objekt
prompt += f"\nPopraw: {fehler}" # informacja zwrotna o błędzie
versuch += 1
eskaliere_an_mensch(antwort) # fallback po 3 próbach
```
Sedno: nawet przy gwarantowanej formie zgodnej ze schematem validiere() sprawdza semantykę, na przykład czy pole kundennummer ma oczekiwany format albo czy pole zusammenfassung mieści się w limicie długości. Jeśli to się nie uda, konkretny błąd wraca do modelu jako informacja zwrotna. Trzy próby to rozsądny limit; potem następuje eskalacja do człowieka, a system nie kręci się w nieskończonej pętli, przepalając pieniądze.
Walidacja z Pydantic i Zod jako praktyczny standard
W praktyce nikt nie pisze schematów JSON ręcznie. W Pythonie definiuje się model Pydantic, w TypeScripcie schemat Zod; oba generują schemat JSON dla zapytania do LLM i od razu przejmują walidację odpowiedzi. Korzyść jest podwójna: ta sama definicja steruje generowaniem i sprawdza wynik, co zapobiega niespójnościom. Błędy typów, brakujące pola wymagane czy naruszone zakresy wartości od razu przy parsowaniu stają się widoczne jako czytelny komunikat o błędzie, który można ponownie wykorzystać jako informację zwrotną przy ponowieniu.
Frameworki takie jak Pydantic AI mają tę pętlę już wbudowaną: sprawdzają odpowiedź względem modelu Pydantic i przy błędzie walidacji proszą model o kolejną próbę, z konfigurowalnym limitem. Do routingu między wieloma dostawcami służą narzędzia takie jak LiteLLM i OpenRouter. Dzięki nim to samo ustrukturyzowane zapytanie trafia do różnych modeli. Zmniejsza to zależność od jednego dostawcy i pozwala na fallback do drugiego modelu, jeśli któryś dostawca nie dostarcza ustrukturyzowanego wyniku niezawodnie.
Najczęstsze pułapki
- Zbyt złożone schematy. Głęboko zagnieżdżone struktury z wieloma opcjonalnymi polami zwiększają odsetek błędów i opóźnienia. Tak ściśle, jak trzeba, tak prosto, jak się da.
- Mylenie formy z treścią. Obiekt zgodny ze schematem nie jest automatycznie poprawny merytorycznie. Zhalucynowane, ale poprawnie otypowane wartości to najgroźniejsza klasa błędów, bo przechodzą niezauważone.
- Nieograniczone ponowienia. Bez górnego limitu grożą nieskończone pętle i eksplozja kosztów. Zawsze stosuj ograniczoną liczbę ponowień z eskalacją jako fallbackiem.
- Ignorowanie różnic między dostawcami. Nie każdy endpoint zgodny z OpenAI naprawdę ściśle wymusza schemat. Przetestuj to przed wdrożeniem produkcyjnym.
Dla agencji i decydentów B2B
Jeśli planujesz w Europie produkcyjnego agenta lub aplikację RAG, potraktuj ustrukturyzowane wyniki od początku jako zasadę architektury, a nie późniejszą naprawę. Właśnie tutaj rozstrzyga się, czy projekt AI stanie się niezawodnym narzędziem, czy pozostanie nieprzewidywalnym demo. Jako agencja z Wiednia Blck Alpaca wspiera firmy w całej Europie w budowie solidnych pipeline'ów RAG i agentowych, od definicji schematu przez walidację i logikę ponawiania aż po decyzję dotyczącą dostawcy i suwerenności. Odezwij się, jeśli Twój projekt RAG lub agentowy potrzebuje niezawodnych, czytelnych maszynowo wyników zamiast zgadywanego swobodnego tekstu.
Otwiera czat z przygotowanym promptem.
Najczęściej zadawane pytania
Czym różni się tryb JSON od Structured Outputs?
Czy constrained decoding gwarantuje poprawną treść?
Jak działają ustrukturyzowane wyniki w Anthropic Claude?
Czy ustrukturyzowane wyniki są mi potrzebne także w prostych aplikacjach?
Jaką rolę dla niezawodności odgrywa projekt schematu?
Chcesz zgłębić temat?
Otrzymuj nowe analizy prosto na swoją skrzynkę albo zobacz, jak wykorzystujemy tę wiedzę w firmach.