Tool use in Claude: il template per output strutturato affidabile
21 automazioni in produzione con output strutturato di Claude, zero parser crash. Il template in 4 elementi: system, tool definition, tool_choice force, messaggio. Pattern replicabile per estrazione dati, classificazione testo e generazione report JSON.
Il problema del JSON-in-testo
21 automazioni in produzione da febbraio 2026. Tutte leggono output strutturato di Claude. Zero parser crash da quando uso questo pattern in modo sistematico.
Il problema con l'approccio 'chiedi JSON nel system prompt' è che funziona nell'80% dei casi e si rompe nel 20% peggiore. Quel 20% sono testi anomali, input con caratteri speciali, risposte borderline dove Claude decide di commentare il JSON invece di restituirlo pulito.
Tool use risolve questo a livello di protocollo, non di prompt engineering.
Quando dici a Claude 'rispondi in JSON', Claude scrive JSON nel testo. È linguaggio naturale che assomiglia a JSON. Il parser lo legge finché l'input è normale. Testa questa pipeline con 100 varianti di testo: 5-10 faranno crashare il parser. Non perché Claude sbagli, ma perché stai usando il testo come canale di comunicazione strutturata.
Il template in 4 elementi
L'API di Anthropic supporta tool use (function calling). Quando definisci un tool e imposti tool_choice su force, Claude non risponde in testo: chiama il tool e ti restituisce un oggetto già parsato secondo il JSON Schema che hai definito.
- System prompt: ruolo + una sola regola ("usa il tool per restituire l'output")
- Tool definition: il JSON Schema dell'oggetto che vuoi
- tool_choice force:
{"type": "tool", "name": "nome_tool"}, forza sempre il tool specifico - Messaggio: solo il contenuto da elaborare, niente istruzioni di formato
Il codice
import anthropic
client = anthropic.Anthropic()
tools = [
{
"name": "estrai_dati",
"description": "Estrae dati strutturati dal testo",
"input_schema": {
"type": "object",
"properties": {
"categoria": {
"type": "string",
"description": "Categoria del documento"
},
"valore": {
"type": "number",
"description": "Importo numerico"
},
"note": {
"type": "string",
"description": "Note aggiuntive"
}
},
"required": ["categoria", "valore"]
}
}
]
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
tool_choice={"type": "tool", "name": "estrai_dati"},
system="Sei un estrattore di dati. Usa il tool per restituire l'output.",
messages=[
{"role": "user", "content": testo_da_elaborare}
]
)
tool_use_block = next(
block for block in response.content
if block.type == "tool_use"
)
dati = tool_use_block.input # dict Python, non stringaL'output dati è già un dizionario Python. Niente json.loads(), niente gestione di edge case di formato.
3 pattern di utilizzo
Estrazione: dati da testo non strutturato (fatture, email, report)
Input_schema con tutti i campi che vuoi estrarre. Description precisa per ogni campo. Claude capisce il contesto e mappa il testo sullo schema.
Classificazione: assegna una categoria a un testo
Usa enum nel JSON Schema:
"categoria": {
"type": "string",
"enum": ["urgente", "bassa_priorita", "spam", "richiesta_info"]
}Claude non può uscire dall'enum. La classificazione è sempre parsable.
Generazione strutturata: genera un report o un oggetto da zero
Input_schema definisce la struttura dell'output. Il messaggio contiene i dati grezzi. Claude riempie lo schema. Nessuna logica di parsing da scrivere.
Il parametro critico: tool_choice
La maggior parte dei bug di struttura viene da tool_choice impostato male.
{"type": "auto"}: Claude decide se usare il tool. Se pensa che il testo sia meglio, lo usa. Non usare in produzione.{"type": "any"}: Claude usa uno tra i tool definiti. Utile con multi-tool, non garantisce quale.{"type": "tool", "name": "nome_tool"}: force. Claude chiama esattamente quel tool. Sempre. Usa questo.
Gestione robusta della risposta
if response.stop_reason == "tool_use":
tool_use_block = next(
block for block in response.content
if block.type == "tool_use"
)
dati = tool_use_block.input
else:
# con force non dovrebbe mai succedere, ma logga l'eccezione
raise ValueError(f"Stop reason inatteso: {response.stop_reason}")Con tool_choice force, stop_reason è sempre tool_use. Il branch else è difensivo, non produzione.
Note pratiche
Il JSON Schema in input_schema è un sottoinsieme di Draft 7. Supporta: type, properties, required, enum, description, items (per array), anyOf. Testa lo schema con input edge case prima di andare in produzione.
Per output strutturato ad alto volume: claude-haiku-4-5-20251001 (veloce, economico). Per estrazione complessa con testo ambiguo: claude-sonnet-4-6. Il costo di input_schema conta come token di input. Description verbose aumentano il costo: bilancia precision e budget token.
Il pattern funziona in Python, Node.js e con qualsiasi client che supporta l'API Anthropic. La struttura di input_schema e tool_choice è identica in tutti gli SDK ufficiali.