Vincolare i modelli linguistici di grandi dimensioni con output strutturati: Ollama, Qwen3 e Python o Go

Qualche modo per ottenere output strutturato da Ollama

Indice

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.

anatre in fila

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.loads o 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 null invece 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.Unmarshal e fai fallback se l’analisi fallisce.
  • Mantieni gli schemi semplici — strutture JSON profondamente nidificate o ricorsive potrebbero causare problemi.
  • Consenti campi opzionali (usa omitempty nei 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)

llm-chart

  1. 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]
  1. 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
  1. 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]
  1. 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)
  1. (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.

Iscriviti

Ricevi nuovi articoli su sistemi, infrastruttura e ingegneria AI.