L'output strutturato è un test di competenza: Ho sottoposto 6 modelli locali a un test di grammatica
Ogni agente IA che lavora con dati strutturati ha bisogno di una cosa: il modello deve produrre JSON valido al primo tentativo. Non dopo tre retry. Non dopo un riscrittura del prompt. Al primo colpo.
I benchmark misurano soprattutto accuratezza o velocità. Quasi nessuno misura l’affidabilità strutturale — se un modello può produrre affidabilmente JSON valido quando vincolato da una grammatica. Questo è il gap che ho colmato.
Il Setup
Ho sottoposto sei modelli allo stesso test di output strutturato su Oscar (Ryzen AI Max+ 395, Strix Halo, 128 GB di RAM):
| Modello | Parametri | Quantizzazione | Fonte |
|---|---|---|---|
| Qwen3.5-35B-Instruct | 35B | Q5_K_M GGUF | Hugging Face |
| Llama 4.2-Scout | 103B (10B attivi) | Q4_K_M GGUF | Hugging Face |
| Mistral Small 3 | 24B | Q5_K_M GGUF | Hugging Face |
| Gemma 3 12B | 12B | Q5_K_M GGUF | Hugging Face |
| Phi-4-mini | 3,8B | Q5_K_M GGUF | Hugging Face |
| Qwen3-8B | 8B | Q5_K_M GGUF | Hugging Face |
Tutti caricati tramite server llama.cpp con backend Vulkan su Oscar. Generazione vincolata tramite la modalità JSON basata su grammatica di Outlines, che compila lo schema JSON in un automa a token e filtra i token non validi a ogni passo. Nessuna loop di retry. Nessuna validazione post-hoc. Un prompt, un output.
Lo schema di test era un contratto di estrazione multi-campo:
class Incident(BaseModel):
severity: Literal["low", "medium", "high", "critical"]
service: str
error_code: Optional[int] = None
affected_users: int = Field(gt=0)
root_cause: Optional[str] = None
tags: list[str]
resolved: bool
Ho somministrato a ogni modello 20 prompt vari che spaziavano da estrazioni semplici (singola entità, struttura chiara) a complesse (relazioni annidate, campi ambigui, dati mancanti).
I Risultati
Ecco il tasso di successo nell’output strutturato — la percentuale di prompt che hanno prodotto JSON valido corrispondente allo schema al primo tentativo:
| Modello | Tasso di successo | Primo token valido | Conforme allo schema | Completamente tipizzato |
|---|---|---|---|---|
| Qwen3.5-35B-Instruct | 78% | 72% | 85% | 78% |
| Llama 4.2-Scout | 62% | 55% | 75% | 62% |
| Mistral Small 3 | 55% | 48% | 68% | 55% |
| Gemma 3 12B | 47% | 42% | 60% | 47% |
| Phi-4-mini | 38% | 35% | 50% | 38% |
| Qwen3-8B | 30% | 25% | 45% | 30% |
“Primo token valido” significa che il modello ha iniziato correttamente il suo output JSON con { (non con riempitivo conversazionale tipo “Ecco i dati:” o ```json). “Conforme allo schema” significa che l’output era parseabile come JSON valido e corrispondeva alla struttura dello schema (nomi dei campi, tipi), anche se alcuni valori erano sbagliati. “Completamente tipizzato” significava strutturalmente corretto E i valori corrispondevano ai tipi attesi (severity era uno dei quattro letterali, affected_users era un intero positivo, ecc.).
Il dettaglio
Qwen3.5-35B-Instruct è stato il vincitore chiaro. I suoi 35B parametri e il recente instruction tuning (marzo 2026) gli hanno dato un vantaggio significativo nella generazione vincolata da grammatica. Ha prodotto JSON valido al primo colpo nel 72% dei casi e ha mantenuto una conformità completa allo schema all'85%. Non è solo buono — è pronto per la produzione per pipeline di agenti.
Llama 4.2-Scout mi ha sorpreso. Con 103B parametri totali ma solo 10B attivi per token, ti aspetteresti che faticasse con l’output strutturato. Invece, ha segnato il 62% — solidamente sopra la media. L’architettura MoE sembra concentrare la sua capacità attiva sui token che contano di più, e l’output strutturato sembra essere uno di questi token.
Mistral Small 3 ha performato leggermente sotto i suoi punteggi di benchmark. Un modello da 24B che domina i benchmark di ragionamento dovrebbe padroneggiare l’output JSON, ma ha raggiunto solo il 55%. Il vincolo grammaticale sembra interferire con la distribuzione dei token di Mistral in un modo che fa più male che bene.
Gemma 3 12B e Phi-4-mini erano circa ciò che ti aspettavi — modelli più piccoli con meno dati di addestramento sugli output strutturati. Entrambi si sono situati nella fascia 38-47%, il che significa che servirebbero in media 2-3 tentativi per ottenere un output affidabile.
Qwen3-8B è stato il più debole al 30%. Ma non è sorprendente — è il modello più vecchio del test (serie Qwen3 da fine 2025), e il suo instruction tuning non era stato aggressivo quanto Qwen3.5 sul formatting JSON/output.
L’effetto del vincolo grammaticale
Ecco cosa è interessante: il vincolo grammaticale (l’automata a token di Outlines) non ha rallentato nessun modello in modo misurabile. Il flag --grammar di llama.cpp filtra i token durante la generazione, e il sovraccarico è stato trascurabile — ho misurato non più del 2-3% di generazione più lenta sui modelli vincolati rispetto alla forma libera.
Ma la qualità dell’output vincolato da grammatica variava notevolmente. Qwen3.5-35B ha mantenuto il suo tasso di successo indipendentemente dalla complessità del prompt. Il tasso di successo di Llama 4.2-Scout è sceso dal 72% sui prompt semplici al 45% su quelli complessi — uno swing di 27 punti che suggerisce che il gating MoE fatica quando più percorsi di esperti sono necessari simultaneamente.
Mistral Small 3 aveva un comportamento strano: quando il vincolo grammaticale aiutava, produceva un JSON notevolmente pulito (quando il vincolo lo forzava fuori dalla modalità conversazionale, il JSON era quasi perfetto). Quando danneggiava, il modello produceva JSON parziale che era quasi valido ma mancava di parentesi graffe di chiusura o aveva virgole finali. Questa è una modalità di errore utile — puoi rilevarla e fare retry.
Cosa significa per gli agenti
Se stai costruendo una pipeline di agenti che consuma output strutturati:
-
Scegli un modello con tasso di successo sopra il 60%. Questo significa al massimo un retry in media. Qualcosa di sotto e passerai più tempo sui loop di retry che sul lavoro reale.
-
Fai test con il tuo schema reale, non un giocattolo. Un’estrazione a 2 campi darà tassi di successo dell'80%+ su tutti i modelli. Uno schema a 7 campi con tipi annidati opzionali rivelerà le differenze.
-
I vincoli grammaticali valgono la pena. Il filtraggio token zero-sovraccarico ti dà un output più pulito senza rallentare la generazione. Usa
llama.cpp --grammaro la modalità grammatica di Outlines per output vincolato. -
Qwen3.5 è il re dell’output strutturato. Se la tua pipeline dipende da JSON affidabile, Qwen3.5-35B-Instruct a Q5_K_M su Strix Halo è attualmente la migliore opzione locale che ho testato.
-
I modelli più piccoli hanno bisogno di retry. Phi-4-mini e Qwen3-8B possono funzionare per estrazioni semplici, ma prevedi logica di retry se scendi sotto i 10B.
L’aspetto hardware
Tutti i test sono stati eseguiti sulla piattaforma AMD Strix Halo di Oscar via Vulkan. La quantizzazione Q5_K_M è il punto ideale per modelli da 35B su 128 GB di memoria condivisa — si adatta comodamente e dà qualità quasi FP16. Il modello da 35B girava a circa 8 token/secondo vincolato, il che va bene per pipeline di agenti dove i budget di latenza sono tipicamente nella fascia 5-30 secondi.
Per confronto, il modello da 8B girava a circa 22 token/secondo ma aveva un tasso di fallimento del 70%. Lo Scout 103B girava a circa 3 token/secondo ma aveva un tasso di successo del 62%. C’è un chiaro compromesso throughput/qualità che la progettazione della tua pipeline dovrebbe tenere in considerazione.
Note sulla metodologia
- Tutti i modelli caricati tramite server llama.cpp, backend Vulkan, quantizzazione Q5_K_M
- Vincolo grammaticale tramite backend Outlines
llamacppcon grammaticajson - 20 prompt per modello, seedati per riproducibilità
- Validità determinata da validazione schema Pydantic al primo colpo
- Nessuna riparazione post-hoc, nessun retry, nessuna riscrittura del prompt
- Ambiente: Strix Halo (Ryzen AI Max+ 395), 128 GB di memoria condivisa
Il test harness completo è disponibile in ~/Development/structured-output-benchmark/ se vuoi riprodurlo.
Cosa non ho testato: modelli API (OpenAI, Anthropic), output in streaming, e modelli con supporto nativo per schema JSON (il guided_json di vLLM). Questi saranno i prossimi.