
L'output strutturato LLM è il meccanismo che garantisce che la risposta di un modello linguistico sia conforme a uno schema predefinito -- non solo JSON valido, ma JSON valido secondo lo schema con esattamente i campi, i tipi e i vincoli che hai specificato. Tutti i principali provider ora lo supportano nativamente, e ha cambiato il modo in cui vengono costruite le applicazioni LLM in produzione.
Riepilogo Rapido: Output Strutturati a Colpo d'Occhio
Se hai poco tempo, ecco il panorama nel 2026:
| Aspetto | Dettagli |
|---|---|
| Cosa è | Risposte con schema imposto dagli LLM -- struttura garantita, non "miglior tentativo" |
| Chi lo supporta | OpenAI, Anthropic, Gemini, Cohere, xAI (Grok), più locale tramite Ollama/vLLM |
| Meccanismo chiave | Decodifica vincolata -- i token non validi vengono mascherati prima del campionamento |
| Modalità JSON vs. Modalità Strict | Modalità JSON = solo sintassi valida. Modalità Strict = conformità completa allo schema |
| Libreria Python | Pydantic (BaseModel + Field) per la definizione dello schema |
| Libreria TypeScript | Zod (z.object + .describe) per la definizione dello schema |
| Miglior approccio iniziale | OpenAI con Pydantic o Zod tramite SDK nativo |
| Miglior libreria di produzione | Instructor (Python) o SDK nativo (TypeScript) |
| Trabocchetto principale | Mettere il campo di ragionamento DOPO il campo risposta -- il modello decide prima di pensare |
| Overhead di latenza | 50-200ms alla prima chiamata (compilazione schema), poi in cache |
Ora analizziamo ogni parte.
Cosa Sono gli Output Strutturati LLM?
L'output strutturato è la differenza tra sperare che un LLM restituisca JSON valido e garantirlo. Quando abiliti l'output strutturato, il modello fisicamente non può produrre token che violino il tuo schema. Definisci un JSON Schema (o un modello Pydantic, o uno schema Zod), lo passi all'API e ricevi ogni volta una risposta che corrisponde.
Perché è importante? Prima degli output strutturati, gli sviluppatori scrivevano fragili parser regex, avvolgevano ogni chiamata LLM in blocchi try/catch JSON.parse, e avevano comunque a che fare con risposte "quasi corrette" -- JSON valido a cui mancava un campo o aveva il tipo sbagliato. Tutta quella classe di bug è scomparsa.
Ci sono tre livelli di applicazione della struttura, e rappresentano una chiara evoluzione:
- Ingegneria dei prompt -- "Per favore restituisci JSON con questi campi." Inaffidabile. Il modello potrebbe conformarsi l'80-90% delle volte.
- Modalità JSON -- Garantisce JSON sintatticamente valido, ma non impone il tuo schema. Potresti ottenere
{"foo": "bar"}quando ti aspettavi{"name": string, "age": number}. - Modalità Strict / Decodifica vincolata -- Garantisce il 100% di conformità allo schema. Il modello letteralmente non può emettere token non validi. Questo è ciò che significa "output strutturato" nel 2026.
Dall'inizio del 2026, OpenAI, Anthropic e Google Gemini supportano tutti nativamente l'output strutturato. L'ecosistema ha convergito.
Verdetto: Se stai analizzando le risposte LLM con regex o JSON.parse in produzione, lo stai facendo nel modo difficile. L'output strutturato nativo elimina tutta quella categoria di fallimento.
Modalità JSON vs. Modalità Strict: Cosa È Cambiato Davvero?
Questa distinzione confonde molti sviluppatori perché i nomi suonano simili. Non lo sono.
| Funzionalità | Modalità JSON | Modalità Strict (Output Strutturati) |
|---|---|---|
| Parametro API | type: "json_object" | type: "json_schema" con strict: true |
| Garantisce JSON valido | Sì | Sì |
| Garantisce conformità allo schema | No | Sì |
| Meccanismo | Bias token post-hoc | Decodifica vincolata (FSM) |
| Può restituire campi inaspettati | Sì | No |
| Può omettere campi obbligatori | Sì | No |
| Applicazione dei tipi | Nessuna | Completa (string, number, array, ecc.) |
| Quando usarlo | Non hai uno schema in anticipo | Tutto in produzione |
La cronologia: OpenAI ha introdotto la Modalità JSON alla fine del 2023. Era un passo avanti, ma gli sviluppatori si sono presto resi conto che "JSON valido" non era sufficiente -- avevano bisogno di JSON valido secondo lo schema. Ad agosto 2024, OpenAI ha lanciato gli Output Strutturati con la Modalità Strict, che usa la decodifica vincolata per garantire la conformità allo schema. Entro il 2025-2026, ogni grande provider aveva adottato lo stesso approccio.
La Modalità JSON ha ancora un caso d'uso ristretto: quando non conosci davvero la forma della risposta in anticipo e vuoi solo un JSON valido per l'esplorazione non strutturata. Ma questo è raro in produzione.
Verdetto: Usa la Modalità Strict per tutto in produzione. La Modalità JSON è effettivamente deprecata per i casi d'uso legati a schemi. Se hai uno schema (e dovresti averlo), usa type: "json_schema" con strict: true.
Come Funziona Realmente la Decodifica Vincolata?
Ecco il meccanismo che rende possibile il 100% di conformità allo schema -- non il 99,9%, ma letteralmente il 100%.
Quando invii un JSON Schema a un provider con la Modalità Strict abilitata, lo schema viene compilato in una macchina a stati finiti (FSM). Questa FSM rappresenta ogni percorso valido attraverso il tuo schema. Ad ogni fase di generazione dei token, il motore di inferenza controlla quali token manterrebbero l'output su un percorso valido e quali no. I token non validi ottengono i loro logit impostati su infinito negativo prima del campionamento, il che significa che hanno probabilità zero di essere selezionati.
<!-- IMAGE: constrained-decoding diagram showing FSM token masking during structured output generation -->Pensala come il completamento automatico con gli steroidi. Se il modello ha appena emesso {"rating": e il tuo schema dice che rating è un intero, i soli token permessi successivamente sono token di cifre. Virgolette, lettere, parentesi -- tutto mascherato. Il modello non può emettere "cinque" anche se lo "vuole".
Questo è lo stesso meccanismo di base usato da XGrammar (il motore dietro vLLM, SGLang e la maggior parte dei server di inferenza locali) e Outlines (la libreria Python open-source per la generazione vincolata). I provider API l'hanno semplicemente integrato nella loro infrastruttura di inferenza.
C'è un compromesso da conoscere: la prima richiesta con un nuovo schema comporta un costo di latenza di compilazione (tipicamente 50-200ms) mentre viene costruita la FSM. Le richieste successive con lo stesso schema usano una FSM in cache e aggiungono un overhead quasi nullo. C'è anche una considerazione di qualità sottile -- vincolare il vocabolario dei token può occasionalmente ridurre la qualità dell'output per campi creativi o in forma libera, quindi mantieni i tuoi schemi focalizzati sui dati veramente strutturati.
Verdetto: La decodifica vincolata è ciò che separa "di solito funziona" da "funziona sempre." È l'ingegneria che rende gli output strutturati pronti per la produzione.
Implementazione Multi-Provider: OpenAI, Anthropic e Gemini
Ecco qualcosa che nessuna delle altre guide ti mostra: la stessa attività di estrazione implementata su tutti e tre i principali provider. Estrarremo una recensione di prodotto strutturata da testo non strutturato.
Lo schema Pydantic (condiviso tra tutti i provider):
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")Implementazione 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, # Modello Pydantic direttamente
)
review = response.choices[0].message.parsed # Oggetto ProductReview tipizzatoL'implementazione di OpenAI è la più matura. Il metodo parse() accetta un modello Pydantic direttamente e restituisce un oggetto tipizzato. Un vincolo: la Modalità Strict di OpenAI supporta un sottoinsieme di JSON Schema -- nessun $ref, anyOf limitato, e tutti i campi devono essere obbligatori con additionalProperties: false.
Implementazione 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)L'output strutturato nativo di Anthropic usa output_config.format con un JSON Schema. Ha raggiunto la disponibilità generale all'inizio del 2026. Anthropic supporta anche il vecchio pattern di definire uno strumento "falso" ed estrarre tramite tool_use -- quello funziona ancora, ma l'output strutturato nativo è più pulito per l'estrazione pura.
Implementazione 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, # Modello Pydantic direttamente
}
)
import json
review = ProductReview(**json.loads(response.text))Gemini supporta direttamente i modelli Pydantic nell'SDK Python tramite response_schema. Una caratteristica unica: Gemini rispetta propertyOrdering nello schema, permettendoti di controllare l'ordine di output dei campi (utile per il pattern ragionamento-prima).
Confronto tra Provider
| Caratteristica | OpenAI | Anthropic | Gemini |
|---|---|---|---|
| Parametro API | response_format | output_config.format | response_schema |
| Input schema | Pydantic o JSON Schema | JSON Schema | Pydantic o JSON Schema |
| Modalità strict | strict: true | Implicita con json_schema | Implicita |
| Streaming | Sì (JSON parziale) | Sì | Sì |
| Gestione rifiuti | campo message.refusal | Risposta di errore | Risposta di errore |
| Alternativa tool-use | Sì | Sì (metodo originale) | Sì |
| Cache compilazione schema | Sì (lato server) | Sì | Sì |
| Ordinamento proprietà | Nessun supporto nativo | No | Sì (propertyOrdering) |
Verdetto: OpenAI ha la DX più raffinata con il suo metodo parse(). Anthropic offre i modelli sottostanti più capaci. L'ordinamento delle proprietà di Gemini è unicamente utile. Tutti e tre fanno il lavoro -- scegli in base alla tua relazione esistente con il provider.
Pattern Pydantic per Sviluppatori Python
Pydantic è lo standard de facto per definire schemi di output strutturato in Python. Ecco i pattern che contano.
Schema Base con Descrizioni
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")Quelle stringhe description non sono solo per la documentazione -- diventano parte del JSON Schema inviato al modello e influenzano direttamente ciò che il modello genera. Considerale come ingegneria dei prompt all'interno dello schema.
Modelli Annidati
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 # Modello annidato
key_products: list[str] = Field(description="Top 3 products or services")Mantieni l'annidamento a un massimo di 2-3 livelli. Gli schemi profondamente annidati aumentano i tassi di errore e rallentano la compilazione dello schema.
Il Pattern Ragionamento-Prima
Questo è il pattern di progettazione degli schemi con il maggiore impatto. Metti un campo reasoning prima dei tuoi campi risposta:
# Cattivo -- il modello si impegna in una risposta prima di pensare
class ClassificationBad(BaseModel):
category: Literal["spam", "ham"]
confidence: float
# Buono -- il modello ragiona prima attraverso il problema
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)Gli LLM generano token da sinistra a destra. L'ordine dei campi è l'ordine del prompt. Ragionamento prima significa che il modello deve lavorare il problema prima di impegnarsi in una categoria.
Esportazione JSON Schema
# Genera il JSON Schema per qualsiasi modello Pydantic
schema = ProductReview.model_json_schema()
# Passa questo a qualsiasi provider che accetta JSON Schema grezzoVerdetto: Pydantic + campi descrittivi + ordine ragionamento-prima è il trittico Python per gli output strutturati. Padroneggia questi tre pattern e gestirai il 90% dei casi d'uso.
Pattern Zod per Sviluppatori TypeScript
Zod è l'equivalente TypeScript di Pydantic -- ed è altrettanto centrale per i flussi di lavoro degli output strutturati.
Schema Base con Descrizioni
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"),
});
// Inferire il tipo TypeScript automaticamente
type ProductReview = z.infer<typeof ProductReview>;Come Field(description=...) di Pydantic, .describe() di Zod diventa parte del JSON Schema e guida l'output del modello.
Integrazione con 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; // Tipizzato!Integrazione con 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 è completamente tipizzato come ProductReviewIl Vercel AI SDK usa Zod nativamente con generateObject(), rendendolo l'integrazione TypeScript più pulita. Funziona con OpenAI, Anthropic, Gemini e altri provider attraverso un'API unificata.
Conversione JSON Schema
import { zodToJsonSchema } from "zod-to-json-schema";
const jsonSchema = zodToJsonSchema(ProductReview);
// Usa con qualsiasi provider che accetta JSON Schema grezzoVerdetto: Zod + .describe() + il Vercel AI SDK è lo stack TypeScript per gli output strutturati. Se sei nell'ecosistema Node/Next.js, questo è il percorso di minore resistenza.
Output Strutturato vs. Chiamata di Funzione: Quando Usare Quale?
Questa è una delle fonti di confusione più comuni. Entrambe coinvolgono schemi, entrambe restituiscono dati strutturati -- ma risolvono problemi diversi.
L'output strutturato dice: "Dammi dati in questa forma esatta." È per l'estrazione, la classificazione e la formattazione. Stai estraendo informazioni strutturate da testo non strutturato.
La chiamata di funzione (uso degli strumenti) dice: "Ecco le azioni che puoi intraprendere -- decidi quale eseguire e fornisci gli argomenti." È per i flussi di lavoro degli agenti dove il modello sceglie tra più strumenti e attiva azioni.
La confusione ha senso storicamente. L'"output strutturato" originale di Anthropic era letteralmente una chiamata di funzione -- definivi un falso strumento chiamato extract_review e prendevi gli argomenti. Quello funziona ancora, ma l'output strutturato nativo è più semplice per l'estrazione pura.
| Scenario | Miglior Approccio | Perché |
|---|---|---|
| Estrarre dati dal testo | Output strutturato | Diretto, latenza inferiore, schema singolo |
| Classificare in categorie | Output strutturato | Una risposta, uno schema |
| Agente che decide quale strumento chiamare | Chiamata di funzione | Il modello sceglie tra più strumenti |
| Orchestrazione multi-step | Chiamata di funzione | Invocazioni di strumenti sequenziali |
| Estrarre dati E decidere la prossima azione | Entrambi | Output strutturato per l'estrazione, chiamata di funzione per l'orchestrazione |
L'output strutturato alimenta le pipeline di chiamata degli strumenti nei sistemi di agenti IA. Consulta la nostra guida sugli agenti IA per le aziende per vedere come si inseriscono nei flussi di lavoro di produzione.
Verdetto: Usa l'output strutturato quando sai che forma devono avere i dati. Usa la chiamata di funzione quando il modello deve scegliere un'azione. In pratica, la maggior parte delle applicazioni usa entrambi -- output strutturato per l'estrazione dei dati e chiamata di funzione per l'orchestrazione degli agenti.
Pattern di Produzione: Errori, Tentativi e Streaming
Far funzionare gli output strutturati in una demo è facile. Mantenerli affidabili in produzione richiede di gestire tre cose: rifiuti, errori di validazione e streaming.
Gestione dei Rifiuti
A volte un modello rifiuta di generare l'output richiesto -- tipicamente perché i filtri di sicurezza hanno segnalato l'input. Quando ciò accade, le API di output strutturato non restituiscono il tuo schema. Restituiscono un rifiuto.
response = client.beta.chat.completions.parse(
model="gpt-4o-2024-08-06",
messages=messages,
response_format=ProductReview,
)
# Controlla SEMPRE il rifiuto prima di accedere al contenuto analizzato
if response.choices[0].message.refusal:
print(f"Model refused: {response.choices[0].message.refusal}")
else:
review = response.choices[0].message.parsedSe salti il controllo del rifiuto e provi ad accedere a .parsed su un rifiuto, otterrai None e un errore a valle confuso. Controlla prima, sempre.
Pattern di Tentativi con Feedback di Validazione
La conformità allo schema è garantita dalla decodifica vincolata, ma la correttezza semantica non lo è. Il modello potrebbe restituire {"rating": 1, "sentiment": "positive"} -- schema valido, contenuto contraddittorio. È lì che entrano in gioco validazione + tentativi.
import instructor
client = instructor.from_openai(OpenAI())
# Instructor gestisce i tentativi automaticamente
review = client.chat.completions.create(
model="gpt-4o",
response_model=ProductReview,
max_retries=3, # Tentativi con feedback dell'errore di validazione
messages=[
{"role": "user", "content": review_text}
],
)Instructor reinvia l'errore di validazione al modello al nuovo tentativo, in modo che possa auto-correggersi. Per pattern di tentativi manuali senza Instructor:
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
# Esegui ulteriore validazione semantica qui
break
except ValidationError as e:
messages.append({"role": "assistant", "content": str(response)})
messages.append({"role": "user", "content": f"Validation error: {e}. Fix it."})Streaming di Output Strutturato
Per risposte strutturate grandi -- array lunghi, molti campi, oggetti annidati complessi -- lo streaming ti permette di renderizzare progressivamente risultati parziali.
import instructor
client = instructor.from_openai(OpenAI())
# Trasmetti risultati parziali man mano che i campi si popolano
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:
# I campi si popolano uno per uno man mano che i token arrivano in streaming
if partial_review.summary:
print(f"Summary so far: {partial_review.summary}")Un trabocchetto: i singoli chunk di streaming non sono schema-validi da soli. Il campo reasoning potrebbe essere popolato mentre rating è ancora None. Pianifica la tua UI di conseguenza -- mostra uno stato di caricamento per i campi non popolati.
Verdetto: I controlli dei rifiuti sono non negoziabili. I tentativi con feedback di validazione catturano errori semantici. Lo streaming vale la pena per qualsiasi risposta che richiede più di un paio di secondi.
Confronto delle Librerie di Output Strutturato
Puoi usare gli output strutturati tramite API native, ma le librerie aggiungono validazione, tentativi, streaming e supporto multi-provider. Ecco il panorama.
Instructor è l'opzione più popolare con 11K+ stelle GitHub e 3M+ download mensili. Avvolge OpenAI, Anthropic, Gemini, Cohere, Ollama e altro con un'interfaccia unificata basata su Pydantic. Caratteristiche chiave: tentativi automatici con feedback di validazione, streaming tramite create_partial(), e configurazione semplice (instructor.from_openai(client)). Se sei un team Python, inizia qui.
BAML adotta un approccio diverso: schema-prima tramite un DSL personalizzato. Definisci schemi in file .baml e auto-generi client per Python, TypeScript, Ruby e altro. Il suo algoritmo SAP (schema-aligned parsing) gestisce con grazia gli output disordinati del modello. Ideale per team cross-language o quando vuoi contratti tra il tuo livello LLM e il livello applicativo. Compromesso: passo di build aggiuntivo e nuova sintassi da imparare.
LangChain offre .with_structured_output(schema) per output strutturato indipendente dal provider. Conveniente se sei già nell'ecosistema LangChain. Compromesso: è una dipendenza pesante, e l'astrazione può nascondere funzionalità specifiche del provider di cui potresti aver bisogno.
Le API native -- chiamate dirette con response_format / output_config -- non richiedono dipendenze oltre all'SDK del provider. Ottieni pieno controllo e piena visibilità. Ideale per casi d'uso semplici o team che preferiscono astrazione minima.
| Libreria | Linguaggi | Provider | Tentativi Auto | Streaming | Stelle GitHub | Curva di Apprendimento |
|---|---|---|---|---|---|---|
| Instructor | Python, TS | 15+ | Sì | Sì | 11K+ | Bassa |
| BAML | Python, TS, Ruby, Go | Tutti (DSL-agnostico) | Sì | Sì | 7K+ | Media |
| LangChain | Python, TS | 20+ | Parziale | Sì | 100K+ | Media-Alta |
| API native | Qualsiasi | 1 per SDK | No | Sì | N/A | Bassa |
Scegliere la giusta libreria di output strutturato fa parte di una decisione più ampia sullo stack IA. Analizziamo lo stack completo nella nostra guida Best AI Stack per SaaS.
Consulta le nostre Migliori Librerie per gli Output Strutturati LLM [prossimamente] per un confronto approfondito di Instructor, BAML, Mirascope e altro.
Verdetto: Inizia con Instructor per Python, API native per TypeScript. Passa a BAML se hai bisogno di contratti di schema cross-language. Evita LangChain solo per gli output strutturati -- è eccessivo.
Migliori Pratiche di Progettazione degli Schemi (e Errori Comuni)
La progettazione del tuo schema impatta direttamente la qualità dell'output. Ecco i pattern che contano e gli errori che ti costano in accuratezza.
Mettere il Ragionamento Prima delle Risposte
L'abbiamo trattato nella sezione Pydantic, ma vale la pena ripeterlo perché è la decisione di progettazione con il maggiore impatto:
# Prima: il modello indovina la risposta, poi la razionalizza
class Bad(BaseModel):
answer: str
reasoning: str
# Dopo: il modello pensa prima, poi si impegna
class Good(BaseModel):
reasoning: str = Field(description="Think step by step")
answer: strGli LLM generano da sinistra a destra. L'ordine dei campi è l'ordine del prompt. Ragionamento prima significa che il modello deve lavorare il problema prima di impegnarsi in una risposta.
La Tabella degli Anti-Pattern
| Errore | Problema | Soluzione |
|---|---|---|
| Campo ragionamento dopo la risposta | Il modello decide prima di pensare | Sposta il ragionamento prima della risposta |
| Profondamente annidato (4+ livelli) | Tasso di errore più alto, compilazione più lenta | Appiattisci a 2-3 livelli |
| Nessuna descrizione dei campi | Il modello indovina cosa vuoi | Aggiungi .describe() / Field(description=...) |
| Gestione dei null mancante | Il modello allucinano un valore per riempire il campo | Usa Optional / .nullable() |
| Schemi troppo grandi (50+ campi) | Timeout di compilazione, degrado della qualità | Dividi in più chiamate |
| Opzioni enum vaghe | Il modello sceglie la categoria sbagliata | Usa opzioni specifiche e non sovrapposte |
Gestire i Null Esplicitamente
Se un campo potrebbe non avere dati nel testo sorgente, rendilo opzionale. Forzare un campo obbligatorio quando i dati non esistono porta all'allucinazione:
class PersonInfo(BaseModel):
name: str # Sempre presente
email: Optional[str] = Field(None, description="Email if mentioned, null otherwise")
phone: Optional[str] = Field(None, description="Phone if mentioned, null otherwise")Mantenere gli Schemi Focalizzati
Uno schema per attività. Non cercare di estrarre tutto in un unico schema massiccio. Se hai bisogno di 50+ campi, dividi in più chiamate di estrazione. La Modalità Strict di OpenAI ha limiti pratici sulla complessità degli schemi, e anche quando funziona, gli schemi molto grandi degradano la qualità dell'output.
Verdetto: Ragionamento-prima, campi descrittivi, null espliciti e schemi focalizzati. Fai bene questi quattro e la tua accuratezza negli output strutturati aumenta in modo misurabile.
Output Strutturato con LLM Locali
Non hai bisogno di un provider API per gli output strutturati. I motori di inferenza locali lo supportano attraverso la decodifica vincolata basata sulla grammatica -- lo stesso meccanismo fondamentale, in esecuzione sul tuo hardware.
Ollama
Il percorso più semplice per gli output strutturati locali. Ollama accetta un JSON Schema tramite il parametro 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 usa XGrammar internamente per la decodifica vincolata. La stessa garanzia dei provider API: 100% conformità allo schema.
vLLM e SGLang
Per l'inferenza locale di qualità produzione, vLLM e SGLang supportano entrambi gli output strutturati tramite i parametri guided_json e guided_regex. XGrammar è il backend predefinito, fornendo overhead quasi nullo sulla generazione JSON -- fino a 3,5x più veloce dei motori grammaticali alternativi.
Outlines
Outlines è la libreria Python open-source che ha aperto la strada alla generazione vincolata basata sulla grammatica. Funziona con qualsiasi modello Hugging Face e supporta vincoli JSON Schema, regex e grammatica context-free completa (CFG/EBNF). È anche integrata in vLLM e SGLang come opzione di backend grammaticale.
La differenza chiave con i provider API: l'output strutturato locale non ha limitazioni di sottoinsieme di schema. Controlli completamente la grammatica. Ma la qualità del modello varia di più -- un modello locale a 7B parametri non raggiungerà GPT-4o o Claude su attività di estrazione complesse. Lo schema sarà sempre valido; la qualità del contenuto dipende dal modello.
Verdetto: Ollama per lo sviluppo, vLLM/SGLang con XGrammar per la produzione. Gli output strutturati locali sono abbastanza maturi per la maggior parte dei casi d'uso, con la precisazione che i modelli più piccoli producono contenuti di qualità inferiore all'interno dello schema.
FAQ
Cos'è l'output strutturato negli LLM?
L'output strutturato è un meccanismo che garantisce che la risposta di un LLM sia conforme a un JSON Schema predefinito. A differenza del testo normale o anche della Modalità JSON, l'output strutturato usa la decodifica vincolata per garantire che ogni campo, tipo e vincolo nel tuo schema sia rispettato -- il 100% delle volte, non "di solito".
Qual è la differenza tra Modalità JSON e Output Strutturati?
La Modalità JSON garantisce JSON sintatticamente valido ma non impone il tuo schema -- potresti ottenere qualsiasi oggetto JSON valido. Gli Output Strutturati (Modalità Strict) garantiscono la piena conformità allo schema attraverso la decodifica vincolata. Usa la Modalità Strict per la produzione; la Modalità JSON è rilevante solo quando non hai uno schema in anticipo.
Quali provider LLM supportano nativamente gli output strutturati?
OpenAI (dall'agosto 2024), Google Gemini (2024, ampliato 2026), Anthropic (beta novembre 2025, GA inizio 2026), Cohere e xAI (Grok) supportano tutti nativamente gli output strutturati. Sul lato locale, Ollama, vLLM e SGLang li supportano attraverso la decodifica vincolata basata sulla grammatica.
Come garantisce la decodifica vincolata la conformità allo schema?
Il JSON Schema viene compilato in una macchina a stati finiti (FSM). Ad ogni fase di generazione dei token, sono permessi solo i token che mantengono l'output su un percorso valido attraverso la FSM -- i token non validi ottengono i loro logit impostati su infinito negativo. Ciò significa che i token non validi hanno probabilità zero di essere generati, dandoti una garanzia matematica, non statistica.
Dovrei usare l'output strutturato o la chiamata di funzione?
Usa l'output strutturato per l'estrazione e la classificazione -- quando vuoi dati in una forma specifica. Usa la chiamata di funzione per i flussi di lavoro degli agenti -- quando il modello deve decidere quale azione intraprendere. Molte applicazioni di produzione usano entrambi: output strutturato per l'estrazione dei dati e chiamata di funzione per l'orchestrazione.
Posso fare streaming degli output strutturati?
Sì. OpenAI supporta lo streaming con il metodo parse(), e Instructor fornisce create_partial() per lo streaming di modelli Pydantic che si popolano campo per campo. Tieni presente che i singoli chunk di streaming non sono individualmente schema-validi -- i campi si popolano in modo incrementale.
Cos'è la libreria Instructor?
Instructor è la libreria di output strutturato più popolare (11K+ stelle GitHub, 3M+ download mensili). Avvolge gli SDK dei provider con la validazione basata su Pydantic, tentativi automatici con feedback di validazione e supporto allo streaming. Funziona con OpenAI, Anthropic, Gemini, Cohere, Ollama e 10+ altri provider.
Gli output strutturati funzionano con LLM locali?
Sì. Ollama supporta gli output strutturati tramite il parametro format con JSON Schema. vLLM e SGLang li supportano tramite i parametri guided_json. Tutti e tre usano XGrammar o Outlines per la decodifica vincolata. La garanzia di conformità allo schema è la stessa dei provider API; la qualità del contenuto dipende dal modello.
Quali sono gli errori comuni nella progettazione degli schemi?
I principali errori: mettere il campo di ragionamento dopo il campo risposta (il modello decide prima di pensare), schemi profondamente annidati (4+ livelli aumentano gli errori), descrizioni di campi mancanti (il modello indovina l'intento), nessuna gestione dei null per i dati opzionali (forza l'allucinazione), e schemi troppo grandi (50+ campi degradano la qualità).
L'output strutturato aggiunge latenza?
C'è un overhead di compilazione dello schema alla prima richiesta -- tipicamente 50-200ms mentre viene costruita la FSM. Le richieste successive con lo stesso schema usano una FSM in cache e aggiungono latenza quasi nulla. Per la maggior parte delle applicazioni, questo è trascurabile rispetto al tempo totale di inferenza del modello.
Posso usare gli output strutturati con immagini o input multimodali?
Sì. L'output strutturato si applica al formato della risposta, non all'input. Puoi inviare un'immagine a GPT-4o o Gemini con uno schema di output strutturato e ricevere un'analisi conforme allo schema dell'immagine. Questo è potente per i flussi di lavoro di estrazione visiva -- estrarre dati strutturati da ricevute, moduli o immagini di prodotti.