Vincolare i modelli linguistici di grandi dimensioni con output strutturati: Ollama, Qwen3 e Python o Go
Qualche modo per ottenere output strutturato da Ollama
I Large Language Models (LLM) sono potenti, ma in produzione raramente vogliamo paragrafi liberi. Invece, vogliamo dati prevedibili: attributi, fatti o oggetti strutturati che puoi alimentare in un’applicazione. Questo è l’Output Strutturato degli LLM.
L’implementazione dello schema riduce la frequenza con cui logit errati si trasformano in JSON non valido, ma la temperatura e le penalità rimangono importanti per le tempeste di retry; consulta i parametri di inferenza agentica per Qwen e Gemma quando combini vincoli di format con gli agenti.
Qualche tempo fa Ollama ha introdotto il supporto per l’output strutturato (annuncio), rendendo possibile vincolare le risposte di un modello per farle corrispondere a uno schema JSON. Ciò sblocca pipeline di estrazione dati coerenti per attività come la catalogazione delle funzionalità degli LLM, il benchmark dei modelli o l’automazione dell’integrazione dei sistemi.

In questo post, copriremo:
- Cos’è l’output strutturato e perché è importante
- Un modo semplice per ottenere output strutturato dagli LLM
- Come funziona la nuova funzionalità di Ollama
- Esempi di estrazione delle capacità degli LLM:
Cos’è l’Output Strutturato?
Normalmente, gli LLM generano testo libero:
“Il Modello X supporta il ragionamento con chain-of-thought, ha una finestra di contesto da 200K e parla inglese, cinese e spagnolo.”
È leggibile, ma difficile da analizzare.
Invece, con l’output strutturato chiediamo uno schema rigoroso:
{
"name": "Modello X",
"supports_thinking": true,
"max_context_tokens": 200000,
"languages": ["Inglese", "Cinese", "Spagnolo"]
}
Questo JSON è facile da validare, memorizzare in un database o alimentare un’interfaccia utente.
Modo semplice per ottenere Output Strutturato dagli LLM
Gli LLM a volte capiscono qual è lo schema e possiamo chiedere all’LLM di restituire l’output in JSON utilizzando uno schema particolare. Il modello Qwen3 di Alibaba è ottimizzato per il ragionamento e le risposte strutturate. Puoi istruirlo esplicitamente a rispondere in JSON.
Esempio 1: Utilizzo di Qwen3 con ollama in Python, richiedendo JSON con schema
import json
import ollama
prompt = """
Sei un estrattore di dati strutturati.
Restituisci solo JSON.
Testo: "Elon Musk ha 53 anni e vive ad Austin."
Schema: { "name": string, "age": int, "city": string }
"""
response = ollama.chat(model="qwen3", messages=[{"role": "user", "content": prompt}])
output = response['message']['content']
# Analizza JSON
try:
data = json.loads(output)
print(data)
except Exception as e:
print("Errore nell'analisi del JSON:", e)
Output:
{"name": "Elon Musk", "age": 53, "city": "Austin"}
Validazione dello Schema con Pydantic
Per evitare output malformati, puoi validare contro uno schema Pydantic in Python.
from pydantic import BaseModel
class Person(BaseModel):
name: str
age: int
city: str
# Supponiamo che 'output' sia la stringa JSON da Qwen3
data = Person.model_validate_json(output)
print(data.name, data.age, data.city)
Ciò garantisce che l’output conformi alla struttura attesa.
Output Strutturato di Ollama
Ollama ora ti permette di passare uno schema nel parametro format. Il modello è quindi vincolato a rispondere solo in JSON che conformi allo schema (documentazione).
In Python, tipicamente definisci il tuo schema con Pydantic e lasci che Ollama lo utilizzi come schema JSON.
Esempio 2: Estrai Metadati delle Funzionalità LLM
Supponi di avere un frammento di testo che descrive le abilità di un LLM:
“Qwen3 ha un forte supporto multilingue (Inglese, Cinese, Francese, Spagnolo, Arabo). Permette passaggi di ragionamento (chain-of-thought). La finestra di contesto è di 128K token.”
Vuoi dati strutturati:
from pydantic import BaseModel
from typing import List
from ollama import chat
class LLMFeatures(BaseModel):
name: str
supports_thinking: bool
max_context_tokens: int
languages: List[str]
prompt = """
Analizza la seguente descrizione e restituisci le funzionalità del modello solo in JSON.
Descrizione del modello:
'Qwen3 ha un forte supporto multilingue (Inglese, Cinese, Francese, Spagnolo, Arabo).
Permette passaggi di ragionamento (chain-of-thought).
La finestra di contesto è di 128K token.'
"""
resp = chat(
model="qwen3",
messages=[{"role": "user", "content": prompt}],
format=LLMFeatures.model_json_schema(),
options={"temperature": 0},
)
print(resp.message.content)
Output possibile:
{
"name": "Qwen3",
"supports_thinking": true,
"max_context_tokens": 128000,
"languages": ["Inglese", "Cinese", "Francese", "Spagnolo", "Arabo"]
}
Esempio 3: Confronto di Più Modelli
Inserisci descrizioni di più modelli ed estrai in forma strutturata:
from typing import List
class ModelComparison(BaseModel):
models: List[LLMFeatures]
prompt = """
Estrai le funzionalità di ciascun modello in JSON.
1. Llama 3.1 supporta il ragionamento. La finestra di contesto è di 128K. Lingue: solo Inglese.
2. GPT-4 Turbo supporta il ragionamento. La finestra di contesto è di 128K. Lingue: Inglese, Giapponese.
3. Qwen3 supporta il ragionamento. La finestra di contesto è di 128K. Lingue: Inglese, Cinese, Francese, Spagnolo, Arabo.
"""
resp = chat(
model="qwen3",
messages=[{"role": "user", "content": prompt}],
format=ModelComparison.model_json_schema(),
options={"temperature": 0},
)
print(resp.message.content)
Output:
{
"models": [
{
"name": "Llama 3.1",
"supports_thinking": true,
"max_context_tokens": 128000,
"languages": ["Inglese"]
},
{
"name": "GPT-4 Turbo",
"supports_thinking": true,
"max_context_tokens": 128000,
"languages": ["Inglese", "Giapponese"]
},
{
"name": "Qwen3",
"supports_thinking": true,
"max_context_tokens": 128000,
"languages": ["Inglese", "Cinese", "Francese", "Spagnolo", "Arabo"]
}
]
}
Ciò rende banale benchmarkare, visualizzare o filtrare i modelli per le loro funzionalità.
Esempio 4: Rileva Lacune Automaticamente
Puoi anche consentire valori null quando un campo manca:
from typing import Optional
class FlexibleLLMFeatures(BaseModel):
name: str
supports_thinking: Optional[bool]
max_context_tokens: Optional[int]
languages: Optional[List[str]]
Ciò garantisce che il tuo schema rimanga valido anche se alcune informazioni sono sconosciute.
Vantaggi, Avvertenze e Best Practices
L’uso dell’output strutturato tramite Ollama (o qualsiasi sistema che lo supporti) offre molti vantaggi — ma ha anche alcune avvertenze.
Vantaggi
- Garanzie più solide: Al modello viene chiesto di conformarsi a uno schema JSON piuttosto che a testo libero.
- Analisi più facile: Puoi usare direttamente
json.loadso validare con Pydantic / Zod, invece di regex o euristiche. - Evoluzione basata su schema: Puoi versionare il tuo schema, aggiungere campi (con valori predefiniti) e mantenere la compatibilità retroattiva.
- Interoperabilità: I sistemi downstream si aspettano dati strutturati.
- Determinismo (migliore con temperatura bassa): Quando la temperatura è bassa (es. 0), il modello è più propenso a attenersi rigidamente allo schema. La documentazione di Ollama lo raccomanda.
Avvertenze & Insidie
- Disallineamento dello schema: Il modello potrebbe comunque deviare—es. perdere una proprietà richiesta, riordinare le chiavi o includere campi extra. Hai bisogno di validazione.
- Schemi complessi: Schemi JSON molto profondi o ricorsivi potrebbero confondere il modello o portare a fallimenti.
- Ambiguità nel prompt: Se il tuo prompt è vago, il modello potrebbe indovinare campi o unità in modo errato.
- Incoerenza tra i modelli: Alcuni modelli potrebbero essere migliori o peggiori nel rispettare i vincoli strutturati.
- Limiti dei token: Lo schema stesso aggiunge costi in token al prompt o alla chiamata API.
Best Practices & Consigli (tratti dal blog di Ollama + esperienza)
- Usa Pydantic (Python) o Zod (JavaScript) per definire i tuoi schemi e generare automaticamente schemi JSON. Questo evita errori manuali.
- Includi sempre istruzioni come “rispondi solo in JSON” o “non includere commenti o testo extra” nel tuo prompt.
- Usa temperature = 0 (o molto bassa) per minimizzare la casualità e massimizzare l’adesione allo schema. Ollama raccomanda il determinismo.
- Valida e potenzialmente fai fallback (es. retry o pulizia) quando l’analisi JSON fallisce o la validazione dello schema fallisce.
- Inizia con uno schema più semplice, poi estendilo gradualmente. Non complicare troppo inizialmente.
- Includi istruzioni di errore utili ma vincolanti: es. se il modello non può compilare un campo richiesto, rispondi con
nullinvece di ometterlo (se il tuo schema lo consente).
Esempio Go 1: Estrazione delle Funzionalità LLM
Ecco un semplice programma Go che chiede a Qwen3 l’output strutturato sulle funzionalità di un LLM.
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"github.com/ollama/ollama/api"
)
type LLMFeatures struct {
Name string `json:"name"`
SupportsThinking bool `json:"supports_thinking"`
MaxContextTokens int `json:"max_context_tokens"`
Languages []string `json:"languages"`
}
func main() {
client, err := api.ClientFromEnvironment()
if err != nil {
log.Fatal(err)
}
prompt := `
Analizza la seguente descrizione e restituisci le funzionalità del modello solo in JSON.
Descrizione:
"Qwen3 ha un forte supporto multilingue (Inglese, Cinese, Francese, Spagnolo, Arabo).
Permette passaggi di ragionamento (chain-of-thought).
La finestra di contesto è di 128K token."
`
// Definisci lo schema JSON per l'output strutturato
formatSchema := map[string]any{
"type": "object",
"properties": map[string]any{
"name": map[string]string{
"type": "string",
},
"supports_thinking": map[string]string{
"type": "boolean",
},
"max_context_tokens": map[string]string{
"type": "integer",
},
"languages": map[string]any{
"type": "array",
"items": map[string]string{
"type": "string",
},
},
},
"required": []string{"name", "supports_thinking", "max_context_tokens", "languages"},
}
// Converti lo schema in JSON
formatJSON, err := json.Marshal(formatSchema)
if err != nil {
log.Fatal("Impossibile serializzare lo schema del formato:", err)
}
req := &api.GenerateRequest{
Model: "qwen3:8b",
Prompt: prompt,
Format: formatJSON,
Options: map[string]any{"temperature": 0},
}
var features LLMFeatures
var rawResponse string
err = client.Generate(context.Background(), req, func(response api.GenerateResponse) error {
// Accumula il contenuto mentre viene trasmesso in streaming
rawResponse += response.Response
// Analizza solo quando la risposta è completa
if response.Done {
if err := json.Unmarshal([]byte(rawResponse), &features); err != nil {
return fmt.Errorf("errore di analisi JSON: %v", err)
}
}
return nil
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Struttura analizzata: %+v\n", features)
}
Per compilare ed eseguire questo esempio di programma Go - supponiamo di avere questo file main.go in una cartella ollama-struct,
Dobbiamo eseguire all’interno di questa cartella:
# inizializza il modulo
go mod init ollama-struct
# scarica tutte le dipendenze
go mod tidy
# compila & esegui
go build -o ollama-struct main.go
./ollama-struct
Output di Esempio
Struttura analizzata: {Name:Qwen3 SupportsThinking:true MaxContextTokens:128000 Languages:[Inglese Cinese Francese Spagnolo Arabo]}
Esempio Go 2: Confronto di Più Modelli
Puoi estendere questo per estrarre un elenco di modelli per il confronto.
type ModelComparison struct {
Models []LLMFeatures `json:"models"`
}
prompt = `
Estrai le funzionalità dalle seguenti descrizioni dei modelli e restituiscile come JSON:
1. PaLM 2: Questo modello ha capacità di ragionamento limitate e si concentra sulla comprensione di base della lingua. Supporta una finestra di contesto di 8.000 token. Supporta principalmente solo la lingua inglese.
2. LLaMA 2: Questo modello ha capacità di ragionamento moderate e può gestire alcuni compiti logici. Può elaborare fino a 4.000 token nel suo contesto. Supporta le lingue inglese, spagnola e italiana.
3. Codex: Questo modello ha forti capacità di ragionamento specificamente per la programmazione e l'analisi del codice. Ha una finestra di contesto di 16.000 token. Supporta le lingue inglese, Python, JavaScript e Java.
Restituisci un oggetto JSON con un array "models" contenente tutti i modelli.
`
// Definisci lo schema JSON per il confronto dei modelli
comparisonSchema := map[string]any{
"type": "object",
"properties": map[string]any{
"models": map[string]any{
"type": "array",
"items": map[string]any{
"type": "object",
"properties": map[string]any{
"name": map[string]string{
"type": "string",
},
"supports_thinking": map[string]string{
"type": "boolean",
},
"max_context_tokens": map[string]string{
"type": "integer",
},
"languages": map[string]any{
"type": "array",
"items": map[string]string{
"type": "string",
},
},
},
"required": []string{"name", "supports_thinking", "max_context_tokens", "languages"},
},
},
},
"required": []string{"models"},
}
// Converti lo schema in JSON
comparisonFormatJSON, err := json.Marshal(comparisonSchema)
if err != nil {
log.Fatal("Impossibile serializzare lo schema di confronto:", err)
}
req = &api.GenerateRequest{
Model: "qwen3:8b",
Prompt: prompt,
Format: comparisonFormatJSON,
Options: map[string]any{"temperature": 0},
}
var comp ModelComparison
var comparisonResponse string
err = client.Generate(context.Background(), req, func(response api.GenerateResponse) error {
// Accumula il contenuto mentre viene trasmesso in streaming
comparisonResponse += response.Response
// Analizza solo quando la risposta è completa
if response.Done {
if err := json.Unmarshal([]byte(comparisonResponse), &comp); err != nil {
return fmt.Errorf("errore di analisi JSON: %v", err)
}
}
return nil
})
if err != nil {
log.Fatal(err)
}
for _, m := range comp.Models {
fmt.Printf("%s: Contesto=%d, Lingue=%v\n", m.Name, m.MaxContextTokens, m.Languages)
}
Output di Esempio
PaLM 2: Contesto=8000, Lingue=[Inglese]
LLaMA 2: Contesto=4000, Lingue=[Inglese Spagnolo Italiano]
Codex: Contesto=16000, Lingue=[Inglese Python JavaScript Java]
A proposito, qwen3:4b su questi esempi funziona bene, così come qwen3:8b.
Best Practices per Sviluppatori Go
- Imposta la temperatura a 0 per la massima aderenza allo schema.
- Valida con
json.Unmarshale fai fallback se l’analisi fallisce. - Mantieni gli schemi semplici — strutture JSON profondamente nidificate o ricorsive potrebbero causare problemi.
- Consenti campi opzionali (usa
omitemptynei tag delle strutture Go) se ti aspetti dati mancanti. - Aggiungi retry se il modello occasionalmente emette JSON non valido.
Esempio Completo - Disegno di un Grafico con Specifiche LLM (Step-by-step: dal JSON strutturato alle tabelle di confronto)

- Definisci uno schema per i dati che vuoi
Usa Pydantic così puoi sia (a) generare uno Schema JSON per Ollama e (b) validare la risposta del modello.
from pydantic import BaseModel
from typing import List, Optional
class LLMFeatures(BaseModel):
name: str
supports_thinking: bool
max_context_tokens: int
languages: List[str]
- Chiedi a Ollama di restituire solo JSON in quella forma
Passa lo schema in format= e abbassa la temperatura per il determinismo.
from ollama import chat
prompt = """
Estrai le funzionalità per ciascun modello. Restituisci solo JSON corrispondente allo schema.
1) Qwen3 supporta chain-of-thought; contesto 128K; Inglese, Cinese, Francese, Spagnolo, Arabo.
2) Llama 3.1 supporta chain-of-thought; contesto 128K; Inglese.
3) GPT-4 Turbo supporta chain-of-thought; contesto 128K; Inglese, Giapponese.
"""
resp = chat(
model="qwen3",
messages=[{"role": "user", "content": prompt}],
format={"type": "array", "items": LLMFeatures.model_json_schema()},
options={"temperature": 0}
)
raw_json = resp.message.content # Lista JSON di LLMFeatures
- Valida & Normalizza
Valida sempre prima di usare in produzione.
from pydantic import TypeAdapter
adapter = TypeAdapter(list[LLMFeatures])
models = adapter.validate_json(raw_json) # -> list[LLMFeatures]
- Costruisci una tabella di confronto (pandas)
Trasforma i tuoi oggetti validati in un DataFrame che puoi ordinare/filtrare ed esportare.
import pandas as pd
df = pd.DataFrame([m.model_dump() for m in models])
df["languages_count"] = df["languages"].apply(len)
df["languages"] = df["languages"].apply(lambda xs: ", ".join(xs))
# Riordina le colonne per leggibilità
df = df[["name", "supports_thinking", "max_context_tokens", "languages_count", "languages"]]
# Salva come CSV per ulteriore utilizzo
df.to_csv("llm_feature_comparison.csv", index=False)
- (Opzionale) Visualizzazioni rapide
Grafici semplici ti aiutano a vedere a occhio le differenze tra i modelli rapidamente.
import matplotlib.pyplot as plt
plt.figure()
plt.bar(df["name"], df["max_context_tokens"])
plt.title("Finestra di Contesto Massima per Modello (token)")
plt.xlabel("Modello")
plt.ylabel("Token di Contesto Massimi")
plt.xticks(rotation=20, ha="right")
plt.tight_layout()
plt.savefig("max_context_window.png")
TL;DR
Con il nuovo supporto per l’output strutturato di Ollama, puoi trattare gli LLM non solo come chatbot ma come motori di estrazione dati.
Gli esempi sopra hanno mostrato come estrarre automaticamente metadati strutturati sulle funzionalità degli LLM come supporto al pensiero, dimensione della finestra di contesto e lingue supportate — compiti che altrimenti richiederebbero parsing fragile.
Che tu stia costruendo un catalogo di modelli LLM, una dashboard di valutazione o un assistente di ricerca alimentato da IA, gli output strutturati rendono l’integrazione fluida, affidabile e pronta per la produzione.
Link Utili
- https://ollama.com/blog/structured-outputs
- Ollama cheatsheet
- Python Cheatsheet
- AWS SAM + AWS SQS + Python PowerTools
- Golang Cheatsheet
- Comparing Go ORMs for PostgreSQL: GORM vs Ent vs Bun vs sqlc
- Reranking text documents with Ollama and Qwen3 Embedding model - in Go
- AWS lambda performance: JavaScript vs Python vs Golang