ai-machine-learning

JSON Affidabile da Qualsiasi LLM: Pattern Pydantic + Zod per il 2026

Scritto da Mert Batur
Aggiornato May 12, 2026
17 lettura
JSON Affidabile da Qualsiasi LLM: Pattern Pydantic + Zod per il 2026

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:

AspettoDettagli
Cosa èRisposte con schema imposto dagli LLM -- struttura garantita, non "miglior tentativo"
Chi lo supportaOpenAI, Anthropic, Gemini, Cohere, xAI (Grok), più locale tramite Ollama/vLLM
Meccanismo chiaveDecodifica vincolata -- i token non validi vengono mascherati prima del campionamento
Modalità JSON vs. Modalità StrictModalità JSON = solo sintassi valida. Modalità Strict = conformità completa allo schema
Libreria PythonPydantic (BaseModel + Field) per la definizione dello schema
Libreria TypeScriptZod (z.object + .describe) per la definizione dello schema
Miglior approccio inizialeOpenAI con Pydantic o Zod tramite SDK nativo
Miglior libreria di produzioneInstructor (Python) o SDK nativo (TypeScript)
Trabocchetto principaleMettere il campo di ragionamento DOPO il campo risposta -- il modello decide prima di pensare
Overhead di latenza50-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:

  1. Ingegneria dei prompt -- "Per favore restituisci JSON con questi campi." Inaffidabile. Il modello potrebbe conformarsi l'80-90% delle volte.
  2. Modalità JSON -- Garantisce JSON sintatticamente valido, ma non impone il tuo schema. Potresti ottenere {"foo": "bar"} quando ti aspettavi {"name": string, "age": number}.
  3. 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à JSONModalità Strict (Output Strutturati)
Parametro APItype: "json_object"type: "json_schema" con strict: true
Garantisce JSON valido
Garantisce conformità allo schemaNo
MeccanismoBias token post-hocDecodifica vincolata (FSM)
Può restituire campi inaspettatiNo
Può omettere campi obbligatoriNo
Applicazione dei tipiNessunaCompleta (string, number, array, ecc.)
Quando usarloNon hai uno schema in anticipoTutto 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):

python
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

python
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 tipizzato

L'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

python
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

python
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

CaratteristicaOpenAIAnthropicGemini
Parametro APIresponse_formatoutput_config.formatresponse_schema
Input schemaPydantic o JSON SchemaJSON SchemaPydantic o JSON Schema
Modalità strictstrict: trueImplicita con json_schemaImplicita
StreamingSì (JSON parziale)
Gestione rifiuticampo message.refusalRisposta di erroreRisposta di errore
Alternativa tool-useSì (metodo originale)
Cache compilazione schemaSì (lato server)
Ordinamento proprietàNessun supporto nativoNoSì (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

python
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

python
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:

python
# 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

python
# Genera il JSON Schema per qualsiasi modello Pydantic
schema = ProductReview.model_json_schema()
# Passa questo a qualsiasi provider che accetta JSON Schema grezzo

Verdetto: 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

typescript
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

typescript
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

typescript
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 ProductReview

Il 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

typescript
import { zodToJsonSchema } from "zod-to-json-schema";

const jsonSchema = zodToJsonSchema(ProductReview);
// Usa con qualsiasi provider che accetta JSON Schema grezzo

Verdetto: 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.

ScenarioMiglior ApproccioPerché
Estrarre dati dal testoOutput strutturatoDiretto, latenza inferiore, schema singolo
Classificare in categorieOutput strutturatoUna risposta, uno schema
Agente che decide quale strumento chiamareChiamata di funzioneIl modello sceglie tra più strumenti
Orchestrazione multi-stepChiamata di funzioneInvocazioni di strumenti sequenziali
Estrarre dati E decidere la prossima azioneEntrambiOutput 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.

python
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.parsed

Se 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.

python
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:

python
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.

python
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.

LibreriaLinguaggiProviderTentativi AutoStreamingStelle GitHubCurva di Apprendimento
InstructorPython, TS15+11K+Bassa
BAMLPython, TS, Ruby, GoTutti (DSL-agnostico)7K+Media
LangChainPython, TS20+Parziale100K+Media-Alta
API nativeQualsiasi1 per SDKNoN/ABassa

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:

python
# 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: str

Gli 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

ErroreProblemaSoluzione
Campo ragionamento dopo la rispostaIl modello decide prima di pensareSposta il ragionamento prima della risposta
Profondamente annidato (4+ livelli)Tasso di errore più alto, compilazione più lentaAppiattisci a 2-3 livelli
Nessuna descrizione dei campiIl modello indovina cosa vuoiAggiungi .describe() / Field(description=...)
Gestione dei null mancanteIl modello allucinano un valore per riempire il campoUsa Optional / .nullable()
Schemi troppo grandi (50+ campi)Timeout di compilazione, degrado della qualitàDividi in più chiamate
Opzioni enum vagheIl modello sceglie la categoria sbagliataUsa 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:

python
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:

python
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.

Fonti

Tag

llm output strutturatostructured outputsjson schemapydanticzodopenaianthropicgemini

Condividi questo articolo

Il Tuo Prossimo Passo

Hai un progetto in mente? Parliamone.

Prenota una call di 30 minuti. Ti ascoltiamo, capiamo il problema e ti diciamo se possiamo aiutarti.