Output strutturato Claude: i 3 pattern in produzione
I 3 metodi per ottenere JSON strutturato da Claude senza errori di formato: tool_use, JSON mode, Pydantic.
21 automazioni. 0 errori di formato. Ecco come.
Ogni automazione che ho in produzione restituisce JSON strutturato. In 5 mesi non ho mai ricevuto un output fuori schema che sia entrato in produzione. Non è fortuna: è architettura.
Ecco i 3 pattern nell'ordine in cui li uso.
Pattern 1: tool_use + JSON schema
È il metodo più robusto. Definisci lo schema come un oggetto tool, e Claude non può rispondere fuori da quello schema. Fisicamente non può.
import anthropic
client = anthropic.Anthropic()
schema = {
"name": "extract_data",
"description": "Estrai i dati strutturati",
"input_schema": {
"type": "object",
"properties": {
"titolo": {"type": "string"},
"categoria": {"type": "string", "enum": ["news", "analisi", "tutorial"]},
"score": {"type": "integer", "minimum": 1, "maximum": 10}
},
"required": ["titolo", "categoria", "score"]
}
}
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
tools=[schema],
tool_choice={"type": "tool", "name": "extract_data"},
messages=[{"role": "user", "content": "Analizza questo articolo: ..."}]
)
result = response.content[0].inputIl blocco tool_choice con type: tool forza Claude a usare esattamente quello schema. Nessuna interpretazione libera.
Pattern 2: JSON mode con system prompt
Più semplice del tool_use, utile per output flessibili o prototipi rapidi.
Con Claude, il metodo più affidabile è system prompt esplicito:
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
system="Rispondi SOLO in formato JSON valido. Nessun testo prima o dopo il JSON.",
messages=[{
"role": "user",
"content": "Classifica questo testo: ... Formato atteso: {\"categoria\": string, \"confidenza\": float}"
}]
)
import json
result = json.loads(response.content[0].text)Il rischio: Claude occasionalmente aggiunge testo introduttivo. Per questo il Pattern 3 è sempre obbligatorio.
Pattern 3: layer di validazione Pydantic
Qualunque metodo usi sopra, aggiungi sempre un layer Pydantic. È il gate che intercetta il 100% degli errori di formato prima che entrino in produzione.
from pydantic import BaseModel, ValidationError
from typing import Literal
class ArticleData(BaseModel):
titolo: str
categoria: Literal["news", "analisi", "tutorial"]
score: int
class Config:
extra = "forbid" # blocca campi non dichiarati
try:
validated = ArticleData(**result)
except ValidationError as e:
# retry, log, fallback
print(f"Errore formato: {e}")extra = "forbid" è critico: se Claude aggiunge un campo non dichiarato, Pydantic lo blocca. Senza questo, gli errori passano silenziosamente.
L'architettura completa
Nelle automazioni uso sempre Pattern 1 + Pattern 3 in combinazione:
- Definisci lo schema come tool
- Forza Claude a usare quel tool con tool_choice
- Valida la risposta con Pydantic
- In caso di errore: retry automatico (max 2 tentativi)
Il retry con tool_use è rarissimo. In 5 mesi di produzione ho visto fallimenti a singola chiamata meno di una volta al mese.
Quando usare quale pattern
- Automazioni critiche (dati che vanno in produzione): sempre Pattern 1 + 3
- Prototipazione rapida: Pattern 2 + 3 (più veloce da scrivere)
- Output con struttura variabile: Pattern 1 con schema ampio + 3
Il Pattern 3 è sempre obbligatorio. Non importa quanto sei sicuro del prompt.