Vai al contenuto
Liguori
§ 00 | GIOVANNI
Costruisco automazioni AI con Claude per PMI e freelancer italiani. 21 sistemi in produzione, zero dipendenti, 60+ ore/mese risparmiate. Stack: Python, GCP.
ACCEPTING NEW CLIENTS
Risorse · DM · SCHEMA

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.

Commenta SCHEMA sotto il reel|4 luglio 2026|Giovanni Liguori
Contenuto assistito da AI

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].input

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

  1. Definisci lo schema come tool
  2. Forza Claude a usare quel tool con tool_choice
  3. Valida la risposta con Pydantic
  4. 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.