
Strukturalne dane wyjściowe LLM to mechanizm, który gwarantuje, że odpowiedź modelu językowego jest zgodna z predefiniowanym schematem – nie tylko poprawnym składniowo JSON-em, ale zgodnym ze schematem JSON-em, zawierającym dokładnie te pola, typy i ograniczenia, które określiłeś. Każdy główny dostawca obsługuje to teraz natywnie, co zmieniło sposób budowania produkcyjnych aplikacji opartych na LLM.
Szybkie podsumowanie: Strukturalne dane wyjściowe w pigułce
Jeśli masz mało czasu, oto przegląd sytuacji w 2026 roku:
| Aspekt | Szczegóły |
|---|---|
| Czym jest | Odpowiedzi z LLM wymuszone schematem, gwarantowana struktura, a nie „najlepsze staranie” |
| Kto wspiera | OpenAI, Anthropic, Gemini, Cohere, xAI (Grok), plus lokalnie przez Ollama/vLLM |
| Kluczowy mechanizm | Dekodowanie z ograniczeniami, nieważne tokeny są maskowane przed próbkowaniem |
| Tryb JSON vs Tryb Ścisły | Tryb JSON = tylko poprawna składnia. Tryb Ścisły = pełna zgodność ze schematem |
| Biblioteka Pythona | Pydantic (BaseModel + Field) do definiowania schematu |
| Biblioteka TypeScript | Zod (z.object + .describe) do definiowania schematu |
| Najlepsze podejście na start | OpenAI z Pydantic lub Zod przez natywne SDK |
| Najlepsza biblioteka produkcyjna | Instructor (Python) lub natywne SDK (TypeScript) |
| Największa pułapka | Umieszczenie pola rozumowania PO polu odpowiedzi; model decyduje przed myśleniem |
| Narzut czasowy | 50-200 ms przy pierwszym wywołaniu (kompilacja schematu), później buforowane |
Przeanalizujmy teraz każdy element z osobna.
Czym są strukturalne dane wyjściowe LLM?
Strukturalne dane wyjściowe to różnica między nadzieją, że LLM zwróci poprawny JSON, a gwarancją. Gdy włączysz strukturalne dane wyjściowe, model fizycznie nie może wygenerować tokenów łamiących Twój schemat. Definiujesz JSON Schema (lub model Pydantic, albo schemat Zod), przekazujesz go do API i otrzymujesz odpowiedź, która za każdym razem jest z nim zgodna.
Dlaczego to ma znaczenie? Przed wprowadzeniem strukturalnych danych wyjściowych programiści pisali kruche parsery wyrażeń regularnych, otaczali każde wywołanie LLM blokami try/catch JSON.parse i nadal mieli do czynienia z odpowiedziami „prawie dobrymi” – poprawnymi składniowo JSON-ami, którym brakowało pola lub miały zły typ. Cała ta klasa błędów odeszła do lamusa.
Istnieją trzy poziomy wymuszania struktury, które reprezentują wyraźną ewolucję:
- Inżynieria promptów: „Proszę zwrócić JSON z tymi polami”. Niezawodne. Model może spełnić prośbę w 80-90% przypadków.
- Tryb JSON: Gwarantuje składniowo poprawny JSON, ale nie wymusza Twojego schematu. Możesz otrzymać
{"foo": "bar"}, gdy oczekiwałeś{"name": string, "age": number}. - Tryb Ścisły / Dekodowanie z ograniczeniami: Gwarantuje 100% zgodności ze schematem. Model dosłownie nie może wyprowadzić nieważnych tokenów. To właśnie oznacza „strukturalne dane wyjściowe” w 2026 roku.
Na początku 2026 roku OpenAI, Anthropic i Google Gemini obsługują natywne strukturalne dane wyjściowe. Ekosystem się ujednolicił.
Werdykt: Jeśli w środowisku produkcyjnym parsujesz odpowiedzi LLM za pomocą regexów lub JSON.parse, robisz to w trudny sposób. Natywne strukturalne dane wyjściowe eliminują ten cały tryb awarii.
Tryb JSON vs Tryb Ścisły: Co tak naprawdę się zmieniło?
To rozróżnienie myli wielu deweloperów, ponieważ nazwy brzmią podobnie. A jednak nie są takie same.
| Cecha | Tryb JSON | Tryb Ścisły (Strukturalne Dane Wyjściowe) |
|---|---|---|
| Parametr API | type: "json_object" | type: "json_schema" z strict: true |
| Gwarantuje poprawny JSON | Tak | Tak |
| Gwarantuje zgodność ze schematem | Nie | Tak |
| Mechanizm | Post-hoc bias tokenów | Dekodowanie z ograniczeniami (FSM) |
| Może zwrócić nieoczekiwane pola | Tak | Nie |
| Może pominąć wymagane pola | Tak | Nie |
| Wymuszanie typów | Brak | Pełne (string, number, array itp.) |
| Kiedy używać | Nie masz z góry określonego schematu | Wszystko w produkcji |
Oś czasu: OpenAI wprowadziło Tryb JSON pod koniec 2023 roku. Był to krok naprzód, ale deweloperzy szybko zrozumieli, że „poprawny JSON” to za mało – potrzebowali JSON-a zgodnego ze schematem. W sierpniu 2024 roku OpenAI uruchomiło Strukturalne Dane Wyjściowe z Trybem Ścisłym, który wykorzystuje dekodowanie z ograniczeniami do zagwarantowania zgodności ze schematem. Do lat 2025-2026 każdy główny dostawca przyjął to samo podejście.
Tryb JSON wciąż ma wąskie zastosowanie: gdy naprawdę nie znasz z góry kształtu odpowiedzi i chcesz po prostu jakiegoś poprawnego JSON-a do nieustrukturyzowanej eksploracji. Ale w produkcji zdarza się to rzadko.
Werdykt: Używaj Trybu Ścisłego do wszystkiego w produkcji. Tryb JSON jest de facto przestarzały dla przypadków użycia związanych ze schematami. Jeśli masz schemat (a powinieneś), użyj type: "json_schema" z strict: true.
Jak właściwie działa dekodowanie z ograniczeniami?
Oto mechanizm, który sprawia, że 100% zgodności ze schematem jest możliwe – nie 99,9%, ale dosłownie 100%.
Gdy wysyłasz JSON Schema do dostawcy z włączonym Trybem Ścisłym, schemat jest kompilowany do automatu skończonego (FSM). Ten FSM reprezentuje każdą poprawną ścieżkę przez Twój schemat. Na każdym etapie generowania tokenów silnik wnioskowania sprawdza, które tokeny utrzymałyby dane wyjściowe na poprawnej ścieżce, a które nie. Nieważne tokeny mają swoje logity ustawione na minus nieskończoność przed próbkowaniem, co oznacza, że mają zerowe prawdopodobieństwo zostania wybranymi.
<!-- IMAGE: constrained-decoding diagram showing FSM token masking during structured output generation -->Myśl o tym jak o autouzupełnianiu na sterydach. Jeśli model właśnie wyprowadził {"rating":, a Twój schemat mówi, że rating jest liczbą całkowitą, jedynymi dozwolonymi następnymi tokenami są cyfry. Cudzysłowy, litery, nawiasy – wszystkie zamaskowane. Model nie może wyprowadzić "five", nawet jeśli „chce”.
To ten sam podstawowy mechanizm używany przez XGrammar (silnik stojący za vLLM, SGLang i większością lokalnych serwerów wnioskowania) oraz Outlines (bibliotekę open-source w Pythonie do generowania z ograniczeniami). Dostawcy API po prostu wbudowali to w swoją infrastrukturę wnioskowania.
Jest jeden kompromis, o którym warto wiedzieć: pierwsze żądanie z nowym schematem ponosi koszt opóźnienia kompilacji (zazwyczaj 50-200 ms) podczas budowania FSM. Kolejne żądania z tym samym schematem używają buforowanego FSM i dodają bliski zeru narzut. Istnieje też subtelna kwestia jakości: ograniczanie słownika tokenów może czasami obniżać jakość danych wyjściowych dla pól kreatywnych lub swobodnych, dlatego trzymaj swoje schematy skupione na prawdziwie strukturalnych danych.
Werdykt: Dekodowanie z ograniczeniami to to, co oddziela „zazwyczaj działa” od „zawsze działa”. To inżynieria, która sprawia, że strukturalne dane wyjściowe są gotowe do produkcji.
Implementacja u wielu dostawców: OpenAI, Anthropic i Gemini
Oto coś, czego nie pokazują inne przewodniki: to samo zadanie ekstrakcji zaimplementowane u trzech głównych dostawców. Wyekstrahujemy strukturalną recenzję produktu z nieustrukturyzowanego tekstu.
Schemat Pydantic (wspólny dla wszystkich dostawców):
from pydantic import BaseModel, Field
from typing import Literal
class ProductReview(BaseModel):
reasoning: str = Field(description="Think through the review before scoring")
rating: int = Field(description="Rating from 1-5", ge=1, le=5)
sentiment: Literal["positive", "negative", "neutral"]
pros: list[str] = Field(description="Key positive points")
cons: list[str] = Field(description="Key negative points")
summary: str = Field(description="One-sentence summary")Implementacja w OpenAI
from openai import OpenAI
client = OpenAI()
response = client.beta.chat.completions.parse(
model="gpt-4o-2024-08-06",
messages=[
{"role": "system", "content": "Extract a structured review from the text."},
{"role": "user", "content": review_text}
],
response_format=ProductReview, # Pydantic model directly
)
review = response.choices[0].message.parsed # Typed ProductReview objectImplementacja OpenAI jest najbardziej dojrzała. Metoda parse() przyjmuje bezpośrednio model Pydantic i zwraca obiekt z typami. Jedno ograniczenie: Tryb Ścisły OpenAI obsługuje podzbiór JSON Schema, brak $ref, ograniczone anyOf, a wszystkie pola muszą być wymagane z additionalProperties: false.
Implementacja w Anthropic
from anthropic import Anthropic
client = Anthropic()
response = client.messages.create(
model="claude-sonnet-4-5-20250514",
max_tokens=1024,
messages=[
{"role": "user", "content": f"Extract a structured review:\n\n{review_text}"}
],
output_config={
"format": {
"type": "json_schema",
"json_schema": ProductReview.model_json_schema()
}
}
)
import json
review_data = json.loads(response.content[0].text)
review = ProductReview(**review_data)Natywne strukturalne dane wyjściowe Anthropic używają output_config.format z JSON Schema. Osiągnęły status GA na początku 2026 roku. Anthropic obsługuje również starszy wzorzec definiowania „fałszywego” narzędzia i ekstrakcji przez tool_use, który nadal działa, ale natywne strukturalne dane wyjściowe są czystsze dla samej ekstrakcji.
Implementacja w Gemini
from google import genai
client = genai.Client()
response = client.models.generate_content(
model="gemini-2.0-flash",
contents=f"Extract a structured review:\n\n{review_text}",
config={
"response_mime_type": "application/json",
"response_schema": ProductReview, # Pydantic model directly
}
)
import json
review = ProductReview(**json.loads(response.text))Gemini obsługuje modele Pydantic bezpośrednio w SDK Pythona przez response_schema. Unikalna cecha: Gemini respektuje propertyOrdering w schemacie, więc możesz kontrolować kolejność wyprowadzania pól (przydatne dla wzorca „najpierw rozumowanie”).
Porównanie dostawców
| Cecha | OpenAI | Anthropic | Gemini |
|---|---|---|---|
| Parametr API | response_format | output_config.format | response_schema |
| Wejście schematu | Pydantic lub JSON Schema | JSON Schema | Pydantic lub JSON Schema |
| Tryb ścisły | strict: true | Implikowany przez json_schema | Implikowany |
| Streaming | Tak (częściowy JSON) | Tak | Tak |
| Obsługa odmowy | Pole message.refusal | Odpowiedź błędu | Odpowiedź błędu |
| Alternatywa tool-use | Tak | Tak (oryginalna metoda) | Tak |
| Buforowanie kompilacji schematu | Tak (po stronie serwera) | Tak | Tak |
| Kolejność właściwości | Brak natywnego wsparcia | Nie | Tak (propertyOrdering) |
Werdykt: OpenAI ma najbardziej dopracowane DX dzięki metodzie parse(). Anthropic oferuje najzdolniejsze modele bazowe. Kolejność właściwości w Gemini jest wyjątkowo przydatna. Wszystkie trzy realizują zadanie, wybierz w oparciu o istniejącą relację z dostawcą.
Wzorce Pydantic dla programistów Pythona
Pydantic to de facto standard definiowania schematów strukturalnych danych wyjściowych w Pythonie. Oto wzorce, które mają znaczenie.
Podstawowy schemat z opisami
from pydantic import BaseModel, Field
from typing import Literal, Optional
class ExtractedEntity(BaseModel):
reasoning: str = Field(description="Think step by step about the entity")
name: str = Field(description="Full name of the entity")
entity_type: Literal["person", "company", "location"]
confidence: float = Field(description="Confidence score 0.0-1.0", ge=0.0, le=1.0)
context: Optional[str] = Field(description="Surrounding context, if relevant")Te ciągi description nie służą tylko dokumentacji – stają się częścią JSON Schema wysyłanego do modelu i bezpośrednio wpływają na to, co model generuje. Myśl o nich jak o inżynierii promptów wewnątrz schematu.
Zagnieżdżone modele
class Address(BaseModel):
street: str
city: str
country: str
postal_code: Optional[str] = None
class Company(BaseModel):
reasoning: str = Field(description="Analysis of the company details")
name: str
industry: Literal["tech", "finance", "healthcare", "retail", "other"]
headquarters: Address # Nested model
key_products: list[str] = Field(description="Top 3 products or services")Ogranicz zagnieżdżenie do maksymalnie 2-3 poziomów. Głęboko zagnieżdżone schematy zwiększają wskaźnik błędów i spowalniają kompilację schematu.
Wzorzec „Najpierw Rozumowanie”
To pojedynczy, najbardziej wpływowy wzorzec projektowania schematu. Umieść pole reasoning przed polami odpowiedzi:
# Reliable JSON from Any LLM: Pydantic + Zod Patterns for 2026
class ClassificationBad(BaseModel):
category: Literal["spam", "ham"]
confidence: float
# Good -- model reasons through the problem first
class ClassificationGood(BaseModel):
reasoning: str = Field(description="Analyze the text before classifying")
category: Literal["spam", "ham"]
confidence: float = Field(ge=0.0, le=1.0)LLM generują tokeny od lewej do prawej. Jeśli category jest pierwsze, model wybiera kategorię, a następnie ją racjonalizuje. Jeśli reasoning jest pierwsze, model pracuje nad problemem i dopiero potem zobowiązuje się do kategorii. To chain-of-thought wbudowane w schemat.
Eksport do JSON Schema
# Generate the JSON Schema for any Pydantic model
schema = ProductReview.model_json_schema()
# Pass this to any provider that accepts raw JSON SchemaWerdykt: Pydantic + opisowe pola + kolejność „najpierw rozumowanie” to trifecta strukturalnych danych wyjściowych w Pythonie. Opanuj te trzy wzorce, a obsłużysz 90% przypadków użycia.
Wzorce Zod dla programistów TypeScript
Zod to TypeScriptowy odpowiednik Pydantic i jest równie kluczowy dla przepływów pracy ze strukturalnymi danymi wyjściowymi.
Podstawowy schemat z opisami
import { z } from "zod";
const ProductReview = z.object({
reasoning: z.string().describe("Think through the review before scoring"),
rating: z.number().int().min(1).max(5),
sentiment: z.enum(["positive", "negative", "neutral"]),
pros: z.array(z.string()).describe("Key positive points"),
cons: z.array(z.string()).describe("Key negative points"),
summary: z.string().describe("One-sentence summary"),
});
// Infer the TypeScript type automatically
type ProductReview = z.infer<typeof ProductReview>;Podobnie jak Field(description=...) w Pydantic, .describe() w Zod staje się częścią JSON Schema i kieruje danymi wyjściowymi modelu.
Integracja z OpenAI Node SDK
import OpenAI from "openai";
import { zodResponseFormat } from "openai/helpers/zod";
const client = new OpenAI();
const response = await client.beta.chat.completions.parse({
model: "gpt-4o-2024-08-06",
messages: [
{ role: "system", content: "Extract a structured review." },
{ role: "user", content: reviewText },
],
response_format: zodResponseFormat(ProductReview, "product_review"),
});
const review = response.choices[0].message.parsed; // Typed!Integracja z Vercel AI SDK
import { generateObject } from "ai";
import { openai } from "@ai-sdk/openai";
const { object: review } = await generateObject({
model: openai("gpt-4o"),
schema: ProductReview,
prompt: `Extract a structured review:\n\n${reviewText}`,
});
// review is fully typed as ProductReviewVercel AI SDK używa Zod natywnie z generateObject(), co czyni go najczystszą integracją w TypeScript. Działa z OpenAI, Anthropic, Gemini i innymi dostawcami przez zunifikowane API.
Konwersja do JSON Schema
import { zodToJsonSchema } from "zod-to-json-schema";
const jsonSchema = zodToJsonSchema(ProductReview);
// Use with any provider that accepts raw JSON SchemaWerdykt: Zod + .describe() + Vercel AI SDK to stos strukturalnych danych wyjściowych w TypeScript. Jeśli jesteś w ekosystemie Node/Next.js, to jest droga najmniejszego oporu.
Strukturalne dane wyjściowe vs Wywoływanie funkcji: Kiedy używać którego?
To jedno z najczęstszych źródeł nieporozumień. Obie dotyczą schematów, obie zwracają strukturalne dane, ale rozwiązują różne problemy.
Strukturalne dane wyjściowe mówią: „Daj mi dane w tym dokładnym kształcie”. Służą do ekstrakcji, klasyfikacji i formatowania. Wyciągasz strukturalne informacje z nieustrukturyzowanego tekstu.
Wywoływanie funkcji (użycie narzędzi) mówi: „Oto działania, które możesz podjąć, zdecyduj, które uruchomić i podaj argumenty”. Służy do przepływów pracy agentów, gdzie model wybiera spośród wielu narzędzi i uruchamia działania.
Historyczne nieporozumienie ma sens. Oryginalne „strukturalne dane wyjściowe” Anthropic to było dosłownie wywoływanie funkcji – definiowałeś fałszywe narzędzie o nazwie extract_review i pobierałeś argumenty. To nadal działa, ale natywne strukturalne dane wyjściowe są prostsze dla samej ekstrakcji.
| Scenariusz | Najlepsze podejście | Dlaczego |
|---|---|---|
| Ekstrakcja danych z tekstu | Strukturalne dane wyjściowe | Bezpośrednie, mniejsze opóźnienie, pojedynczy schemat |
| Klasyfikacja do kategorii | Strukturalne dane wyjściowe | Jedna odpowiedź, jeden schemat |
| Agent decydujący, które narzędzie wywołać | Wywoływanie funkcji | Model wybiera spośród wielu narzędzi |
| Orkiestracja wieloetapowa | Wywoływanie funkcji | Sekwencyjne wywołania narzędzi |
| Ekstrakcja danych ORAZ decyzja o następnej akcji | Obie | Strukturalne dane wyjściowe do ekstrakcji, wywoływanie funkcji do orkiestracji |
Strukturalne dane wyjściowe napędzają potoki wywoływania narzędzi w systemach agentów AI. Zobacz nasz przewodnik po agentach AI dla biznesu, aby dowiedzieć się, jak pasują one do przepływów pracy produkcyjnej.
Werdykt: Używaj strukturalnych danych wyjściowych, gdy wiesz, jaki kształt powinny mieć dane. Używaj wywoływania funkcji, gdy model musi wybrać działanie. W praktyce większość aplikacji używa obu: strukturalnych danych wyjściowych do ekstrakcji danych i wywoływania funkcji do orkiestracji agentów.
Wzorce produkcyjne: Błędy, ponowienia i streaming
Uzyskanie działania strukturalnych danych wyjściowych w demo jest łatwe. Utrzymanie niezawodności w produkcji wymaga obsługi trzech rzeczy: odmów, błędów walidacji i streamingu.
Obsługa odmów
Czasami model odmawia wygenerowania żądanych danych wyjściowych, zazwyczaj dlatego, że filtry bezpieczeństwa oznaczyły dane wejściowe. Gdy tak się stanie, API strukturalnych danych wyjściowych nie zwraca Twojego schematu. Zwracają odmowę.
response = client.beta.chat.completions.parse(
model="gpt-4o-2024-08-06",
messages=messages,
response_format=ProductReview,
)
# ALWAYS check for refusal before accessing parsed content
if response.choices[0].message.refusal:
print(f"Model refused: {response.choices[0].message.refusal}")
else:
review = response.choices[0].message.parsedJeśli pominiesz sprawdzanie odmowy i spróbujesz uzyskać dostęp do .parsed przy odmowie, otrzymasz None i mylący błąd downstream. Zawsze sprawdzaj najpierw.
Wzorce ponowień z informacją zwrotną walidacji
Zgodność ze schematem jest gwarantowana przez dekodowanie z ograniczeniami, ale poprawność semantyczna już nie. Model może zwrócić {"rating": 1, "sentiment": "positive"}, co jest poprawnym schematem, ale sprzeczną treścią. Tutaj wchodzą walidacja + ponowienia.
import instructor
client = instructor.from_openai(OpenAI())
# Instructor handles retries automatically
review = client.chat.completions.create(
model="gpt-4o",
response_model=ProductReview,
max_retries=3, # Retries with validation error feedback
messages=[
{"role": "user", "content": review_text}
],
)Instructor przekazuje błąd walidacji z powrotem do modelu przy ponowieniu, dzięki czemu może się on samodzielnie skorygować. Dla ręcznych wzorców ponowień bez Instruktora:
from pydantic import ValidationError
for attempt in range(3):
try:
response = client.beta.chat.completions.parse(
model="gpt-4o-2024-08-06",
messages=messages,
response_format=ProductReview,
)
review = response.choices[0].message.parsed
# Run additional semantic validation here
break
except ValidationError as e:
messages.append({"role": "assistant", "content": str(response)})
messages.append({"role": "user", "content": f"Validation error: {e}. Fix it."})Streaming strukturalnych danych wyjściowych
W przypadku dużych strukturalnych odpowiedzi, długich tablic, wielu pól, złożonych zagnieżdżonych obiektów, streaming pozwala na progresywne renderowanie częściowych wyników.
import instructor
client = instructor.from_openai(OpenAI())
# Stream partial results as fields populate
review_stream = client.chat.completions.create_partial(
model="gpt-4o",
response_model=ProductReview,
messages=[{"role": "user", "content": review_text}],
)
for partial_review in review_stream:
# Fields populate one by one as tokens stream in
if partial_review.summary:
print(f"Summary so far: {partial_review.summary}")Jedna pułapka: pojedyncze fragmenty streamingu nie są samodzielnie zgodne ze schematem. Pole reasoning może być wypełnione, podczas gdy rating nadal wynosi None. Zaplanuj swój UI odpowiednio, pokazując stan ładowania dla niewypełnionych pól.
Werdykt: Sprawdzanie odmów jest koniecznością. Ponowienia z informacją zwrotną walidacji wychwytują błędy semantyczne. Streaming opłaca się przy każdej odpowiedzi, która zajmuje więcej niż kilka sekund.
Porównanie bibliotek strukturalnych danych wyjściowych
Możesz używać strukturalnych danych wyjściowych przez natywne API, ale biblioteki dodają walidację, ponowienia, streaming i obsługę wielu dostawców. Oto przegląd.
Instructor to najpopularniejsza opcja z ponad 11 tys. gwiazdek na GitHubie i ponad 3 mln miesięcznych pobrań. Otacza OpenAI, Anthropic, Gemini, Cohere, Ollama i inne zunifikowanym interfejsem opartym na Pydantic. Kluczowe funkcje: automatyczne ponowienia z informacją zwrotną walidacji, streaming przez create_partial() i niezwykle prosta konfiguracja (instructor.from_openai(client)). Jeśli jesteś zespołem Pythonowym, zacznij tutaj.
BAML przyjmuje inne podejście: najpierw schemat przez niestandardowy DSL. Definiujesz schematy w plikach .baml i automatycznie generujesz klientów dla Pythona, TypeScript, Ruby i innych. Jego algorytm SAP (parsing zgodny ze schematem) gracefully radzi sobie z bałaganiarskimi danymi wyjściowymi modeli. Najlepszy dla zespołów wielojęzycznych lub gdy chcesz kontraktów między warstwą LLM a warstwą aplikacji. Kompromis: dodatkowy krok budowania i nowa składnia do nauczenia.
LangChain oferuje .with_structured_output(schema) dla strukturalnych danych wyjściowych niezależnych od dostawcy. Wygodne, jeśli jesteś już w ekosystemie LangChain. Kompromis: to ciężka zależność, a abstrakcja może ukrywać specyficzne dla dostawcy funkcje, których możesz potrzebować.
Natywne API, bezpośrednie wywołania z response_format / output_config, wymagają zerowych zależności poza SDK dostawcy. Masz pełną kontrolę i widoczność. Najlepsze dla prostych przypadków użycia lub zespołów preferujących minimalną abstrakcję.
| Biblioteka | Języki | Dostawcy | Auto Ponowienia | Streaming | Gwiazdki GitHub | Krzywa nauki |
|---|---|---|---|---|---|---|
| Instructor | Python, TS | 15+ | Tak | Tak | 11K+ | Niska |
| BAML | Python, TS, Ruby, Go | Wszystkie (DSL-agnosticzne) | Tak | Tak | 7K+ | Średnia |
| LangChain | Python, TS | 20+ | Częściowo | Tak | 100K+ | Średnia-Wysoka |
| Natywne API | Dowolny | 1 na SDK | Nie | Tak | N/A | Niska |
Wybór odpowiedniej biblioteki strukturalnych danych wyjściowych jest częścią szerszej decyzji dotyczącej stosu AI. Rozkładamy pełny stos w naszym Przewodniku po najlepszym stosie AI dla SaaS.
Zobacz nasze Najlepsze biblioteki do strukturalnych danych wyjściowych LLM [wkrótce] dla dogłębnego porównania Instructor, BAML, Mirascope i innych.
Werdykt: Zacznij od Instruktora dla Pythona, natywnych API dla TypeScript. Przejdź do BAML, jeśli potrzebujesz kontraktów schematów między językami. Unikaj LangChain tylko dla strukturalnych danych wyjściowych, to przesada.
Najlepsze praktyki projektowania schematów (i typowe błędy)
Twój projekt schematu bezpośrednio wpływa na jakość danych wyjściowych. Oto wzorce, które mają znaczenie, oraz błędy, które kosztują Cię dokładność.
Umieść rozumowanie przed odpowiedziami
Omówiliśmy to w sekcji Pydantic, ale warto powtórzyć, ponieważ jest to decyzja projektowa o największym wpływie:
# Before: model guesses the answer, then rationalizes
class Bad(BaseModel):
answer: str
reasoning: str
# After: model thinks first, then commits
class Good(BaseModel):
reasoning: str = Field(description="Think step by step")
answer: strLLM generują od lewej do prawej. Kolejność pól to kolejność promptu. Rozumowanie pierwsze oznacza, że model musi przepracować problem, zanim zobowiąże się do odpowiedzi.
Tabela antywzorców
| Błąd | Problem | Poprawka |
|---|---|---|
| Pole rozumowania po odpowiedzi | Model decyduje przed myśleniem | Przenieś rozumowanie przed odpowiedź |
| Głębokie zagnieżdżenie (4+ poziomy) | Wyższy wskaźnik błędów, wolniejsza kompilacja | Spłaszcz do 2-3 poziomów |
| Brak opisów pól | Model zgaduje, czego chcesz | Dodaj .describe() / Field(description=...) |
| Brak obsługi null | Model halucynuje wartość, by wypełnić pole | Użyj Optional / .nullable() |
| Zbyt duże schematy (50+ pól) | Timeout kompilacji, degradacja jakości | Podziel na wiele wywołań |
| Niejasne opcje enum | Model wybiera złą kategorię | Użyj konkretnych, nie nakładających się opcji |
Jawna obsługa wartości Null
Jeśli pole może nie mieć danych w tekście źródłowym, uczyń je opcjonalnym. Wymuszanie wymaganego pola, gdy dane nie istnieją, prowadzi do halucynacji:
class PersonInfo(BaseModel):
name: str # Always present
email: Optional[str] = Field(None, description="Email if mentioned, null otherwise")
phone: Optional[str] = Field(None, description="Phone if mentioned, null otherwise")Trzymaj schematy skupione
Jeden schemat na zadanie. Nie próbuj ekstrahować wszystkiego w jednym ogromnym schemacie. Jeśli potrzebujesz 50+ pól, podziel na wiele wywołań ekstrakcji. Tryb Ścisły OpenAI ma praktyczne limity złożoności schematu, a nawet gdy działa, bardzo duże schematy obniżają jakość danych wyjściowych.
Werdykt: Najpierw rozumowanie, opisowe pola, jawne nulle i skupione schematy. Dopracuj te cztery elementy, a dokładność strukturalnych danych wyjściowych zauważalnie wzrośnie.
Strukturalne dane wyjściowe z lokalnymi LLM
Nie potrzebujesz dostawcy API do strukturalnych danych wyjściowych. Lokalne silniki wnioskowania obsługują je poprzez dekodowanie z ograniczeniami oparte na gramatyce – ten sam podstawowy mechanizm, działający na Twoim własnym sprzęcie.
Ollama
Najłatwiejsza ścieżka do lokalnych strukturalnych danych wyjściowych. Ollama akceptuje JSON Schema przez parametr format:
import ollama
from pydantic import BaseModel
class Country(BaseModel):
name: str
capital: str
languages: list[str]
response = ollama.chat(
model="llama3.2",
messages=[{"role": "user", "content": "Tell me about Japan."}],
format=Country.model_json_schema(),
)
import json
country = Country(**json.loads(response.message.content))Ollama używa pod spodem XGrammar do dekodowania z ograniczeniami. Ta sama gwarancja co u dostawców API: 100% zgodności ze schematem.
vLLM i SGLang
Do lokalnego wnioskowania klasy produkcyjnej, vLLM i SGLang obsługują strukturalne dane wyjściowe przez parametry guided_json i guided_regex. XGrammar jest domyślnym backendem, zapewniając bliski zeru narzut przy generowaniu JSON, do 3,5x szybciej niż alternatywne silniki gramatyczne.
Outlines
Outlines to biblioteka open-source w Pythonie, która jako pierwsza wprowadziła generowanie z ograniczeniami oparte na gramatyce. Działa z dowolnym modelem Hugging Face i obsługuje JSON Schema, regex oraz pełne ograniczenia gramatyki bezkontekstowej (CFG/EBNF). Jest również zintegrowana z vLLM i SGLang jako opcja backendu gramatycznego.
Kluczowa różnica w stosunku do dostawców API: lokalne strukturalne dane wyjściowe nie mają ograniczeń podzbioru schematu. Kontrolujesz gramatykę całkowicie. Ale jakość modelu jest bardziej zróżnicowana – lokalny model 7B parametrów nie dorówna GPT-4o ani Claude w złożonych zadaniach ekstrakcji. Schemat zawsze będzie poprawny; jakość treści zależy od modelu.
Werdykt: Ollama do rozwoju, vLLM/SGLang z XGrammar do produkcji. Lokalne strukturalne dane wyjściowe są wystarczająco dojrzałe do większości przypadków użycia, z zastrzeżeniem, że mniejsze modele produkują treści o niższej jakości w ramach schematu.
FAQ
Czym są strukturalne dane wyjściowe w LLM?
Strukturalne dane wyjściowe to mechanizm, który gwarantuje, że odpowiedź LLM jest zgodna z predefiniowanym JSON Schema. W przeciwieństwie do zwykłego tekstu, a nawet Trybu JSON, strukturalne dane wyjściowe używają dekodowania z ograniczeniami, aby zapewnić spełnienie każdego pola, typu i ograniczenia w Twoim schemacie – w 100% przypadków, a nie „zazwyczaj”.
Jaka jest różnica między Trybem JSON a Strukturalnymi Danymi Wyjściowymi?
Tryb JSON gwarantuje składniowo poprawny JSON, ale nie wymusza Twojego schematu – możesz otrzymać dowolny poprawny obiekt JSON. Strukturalne Dane Wyjściowe (Tryb Ścisły) gwarantują pełną zgodność ze schematem poprzez dekodowanie z ograniczeniami. Używaj Trybu Ścisłego w produkcji; Tryb JSON jest istotny tylko wtedy, gdy nie masz z góry określonego schematu.
Którzy dostawcy LLM obsługują natywnie strukturalne dane wyjściowe?
OpenAI (od sierpnia 2024), Google Gemini (2024, rozszerzone 2026), Anthropic (beta listopad 2025, GA początek 2026), Cohere i xAI (Grok) obsługują natywne strukturalne dane wyjściowe. Po stronie lokalnej, Ollama, vLLM i SGLang obsługują je poprzez dekodowanie z ograniczeniami oparte na gramatyce.
Jak dekodowanie z ograniczeniami gwarantuje zgodność ze schematem?
JSON Schema jest kompilowana do automatu skończonego (FSM). Na każdym etapie generowania tokenów dozwolone są tylko tokeny, które utrzymują dane wyjściowe na poprawnej ścieżce przez FSM, nieważne tokeny mają logity ustawione na minus nieskończoność. Oznacza to, że nieważne tokeny mają zerowe prawdopodobieństwo wygenerowania, dając matematyczną gwarancję, a nie statystyczną.
Czy powinienem używać strukturalnych danych wyjściowych, czy wywoływania funkcji?
Używaj strukturalnych danych wyjściowych do ekstrakcji i klasyfikacji, gdy chcesz dane w określonym kształcie. Używaj wywoływania funkcji do przepływów pracy agentów, gdy model musi zdecydować, jakie działanie podjąć. Wiele aplikacji produkcyjnych używa obu: strukturalnych danych wyjściowych do ekstrakcji danych i wywoływania funkcji do orkiestracji.
Czy mogę streamować strukturalne dane wyjściowe?
Tak. OpenAI obsługuje streaming z metodą parse(), a Instructor udostępnia create_partial() do streamowania modeli Pydantic, które wypełniają się pole po polu. Pamiętaj, że pojedyncze fragmenty streamingu nie są indywidualnie zgodne ze schematem, pola wypełniają się stopniowo.
Czym jest biblioteka Instructor?
Instructor to najpopularniejsza biblioteka strukturalnych danych wyjściowych (11K+ gwiazdek na GitHubie, 3M+ miesięcznych pobrań). Otacza SDK dostawców walidacją opartą na Pydantic, automatycznymi ponowieniami z informacją zwrotną walidacji i obsługą streamingu. Działa z OpenAI, Anthropic, Gemini, Cohere, Ollama i 10+ innymi dostawcami.
Czy strukturalne dane wyjściowe działają z lokalnymi LLM?
Tak. Ollama obsługuje strukturalne dane wyjściowe przez parametr format z JSON Schema. vLLM i SGLang obsługują je przez parametry guided_json. Wszystkie trzy używają XGrammar lub Outlines do dekodowania z ograniczeniami. Gwarancja zgodności ze schematem jest taka sama jak u dostawców API; jakość treści zależy od modelu.
Jakie są typowe błędy w projektowaniu schematów?
Najczęstsze błędy: umieszczanie pola rozumowania po polu odpowiedzi (model decyduje przed myśleniem), głęboko zagnieżdżone schematy (4+ poziomy zwiększają błędy), brak opisów pól (model zgaduje intencje), brak obsługi null dla opcjonalnych danych (wymusza halucynacje) i zbyt duże schematy (50+ pól obniża jakość).
Czy strukturalne dane wyjściowe dodają opóźnienie?
Występuje narzut kompilacji schematu przy pierwszym żądaniu, zazwyczaj 50-200 ms podczas budowania FSM. Kolejne żądania z tym samym schematem używają buforowanego FSM i dodają bliskie zeru opóźnienie. Dla większości aplikacji jest to pomijalne w porównaniu do całkowitego czasu wnioskowania modelu.
Czy mogę używać strukturalnych danych wyjściowych z obrazami lub danymi multimodalnymi?
Tak. Strukturalne dane wyjściowe dotyczą formatu odpowiedzi, a nie danych wejściowych. Możesz wysłać obraz do GPT-4o lub Gemini ze schematem strukturalnych danych wyjściowych i otrzymać z powrotem analizę obrazu zgodną ze schematem. Jest to potężne narzędzie do przepływów pracy wizualnej ekstrakcji, wydobywania strukturalnych danych z paragonów, formularzy lub zdjęć produktów.
Źródła
- Przewodnik po Strukturalnych Danych Wyjściowych OpenAI
- Dokumentacja Użycia Narzędzi Anthropic
- Strukturalne Dane Wyjściowe Google Gemini
- Dokumentacja Biblioteki Instructor
- Dokumentacja BAML
- Dokumentacja Pydantic
- Dokumentacja Zod
- Biblioteka Outlines
- XGrammar GitHub
- Strukturalne Dane Wyjściowe Ollama
- Vercel AI SDK