Pattern di Utilizzo degli Strumenti: Interfacce Agente-Strumento Affidabili
Il tuo agente ha chiamato uno strumento e ha ricevuto un blob JSON di 40 righe — risposta API grezza, oggetti annidati, codici di errore sepolti dentro un campo status. Il modello lo ha letto, ha scelto un valore che sembrava plausibile e ha continuato. Il valore era sbagliato. Tre passi dopo, l’agente ha scritto con fiducia un rapporto basato su dati errati.
Lo strumento ha funzionato. L’interfaccia ha fallito.
L’utilizzo degli strumenti è il meccanismo che trasforma un modello linguistico in un agente. Ogni capacità del tuo agente — ricerca in database, scrittura di file, chiamate API, interrogazione di servizi — arriva attraverso un’interfaccia strumento. Se l’interfaccia è progettata male, il modello prende decisioni peggiori anche quando il servizio sottostante funziona correttamente. Questa guida copre cinque pattern per costruire interfacce strumento precise, affidabili e pronte per la produzione.
Prerequisiti: Familiarità con Python e l’API Claude. Per il contesto su MCP come livello di trasporto degli strumenti, vedi Costruire il tuo Primo Server MCP.
Perché il Design dell’Interfaccia è Importante
Quando un agente sceglie e usa uno strumento, prende due decisioni:
- Quale strumento chiamare — guidato dal
namee dalladescriptiondello strumento - Quali argomenti passare — guidato dall’
input_schemadello strumento
Le descrizioni ambigue portano a una selezione errata degli strumenti. Gli schemi troppo permissivi permettono al modello di passare input malformati. I risultati non strutturati lo costringono a indovinare cosa è successo. La maggior parte dei bug degli agenti non vive nel ragionamento — vive al confine dello strumento.
Pattern 1: Progettazione Schema-First
Scrivi il JSON schema prima di scrivere l’implementazione. Uno schema rigido vincola il comportamento del modello nella fase di input — prima che qualsiasi cosa venga eseguita.
import anthropic
client = anthropic.Anthropic()
tools = [ { "name": "search_products", "description": ( "Cerca nel catalogo prodotti per parola chiave. " "Restituisce un elenco di prodotti corrispondenti con ID, nomi e prezzi. " "Usalo quando l'utente vuole trovare o sfogliare prodotti." ), "input_schema": { "type": "object", "properties": { "query": { "type": "string", "description": "Parole chiave da cercare" }, "category": { "type": "string", "enum": ["electronics", "clothing", "food", "home", "all"], "description": "Categoria di prodotto da filtrare. Usa 'all' se non specificato." }, "max_results": { "type": "integer", "minimum": 1, "maximum": 20, "description": "Numero di risultati da restituire. Default: 5" } }, "required": ["query", "category"] } }]
response = client.messages.create( model="claude-sonnet-4-6", max_tokens=1024, tools=tools, messages=[{"role": "user", "content": "Trovami elettronica sotto i 100€"}])Regole dello schema che riducono gli errori:
- Usa
enumper qualsiasi campo con un insieme fisso di valori validi - Imposta
minimum/maximumsui campi numerici per evitare input fuori intervallo - Segna i campi come
requiredsolo quando lo strumento non può davvero funzionare senza di essi - Scrivi le descrizioni dalla prospettiva del modello: “Usalo quando…” dice al modello quando chiamare lo strumento
Quando usare: Per ogni definizione di strumento.
Pattern 2: Risultati degli Strumenti Strutturati
Restituisci risultati tipizzati e leggibili dalle macchine. Non restituire mai risposte API grezze.
import jsonfrom dataclasses import dataclass, asdictfrom typing import Any, Optional
@dataclassclass ToolResult: success: bool data: Optional[Any] = None error: Optional[str] = None
def to_content(self) -> str: return json.dumps(asdict(self))
def search_products( query: str, category: str, max_results: int = 5,) -> ToolResult: try: raw_results = _query_database(query, category, limit=max_results) products = [ {"id": r["product_id"], "name": r["title"], "price": r["price_usd"]} for r in raw_results ] return ToolResult(success=True, data={"products": products, "count": len(products)}) except ConnectionError as e: return ToolResult(success=False, error=f"Database non disponibile: {e}") except Exception as e: return ToolResult(success=False, error=f"Ricerca fallita: {type(e).__name__}: {e}")
def _query_database(query, category, limit): return []La busta consistente {success, data, error} significa che il modello sa sempre dove cercare. Per la gestione elegante dei guasti, vedi Pattern di Recupero Errori degli Agenti.
Pattern 3: Chiamate di Strumenti in Parallelo
Claude può richiedere più strumenti in un’unica risposta. Elaborali in parallelo invece che sequenzialmente.
from concurrent.futures import ThreadPoolExecutor, as_completed
TOOL_REGISTRY = { "search_products": search_products,}
def dispatch_tool(name: str, inputs: dict) -> ToolResult: handler = TOOL_REGISTRY.get(name) if not handler: return ToolResult(success=False, error=f"Strumento sconosciuto: {name}") return handler(**inputs)
def process_tool_calls(response: anthropic.types.Message) -> list[dict]: tool_uses = [ block for block in response.content if block.type == "tool_use" ] if not tool_uses: return []
def execute(tool_use): result = dispatch_tool(tool_use.name, tool_use.input) return { "type": "tool_result", "tool_use_id": tool_use.id, "content": result.to_content(), }
with ThreadPoolExecutor(max_workers=len(tool_uses)) as executor: futures = {executor.submit(execute, tu): tu for tu in tool_uses} results = [] for future in as_completed(futures): results.append(future.result())
return results
def run_agent(user_message: str) -> str: messages = [{"role": "user", "content": user_message}]
while True: response = client.messages.create( model="claude-sonnet-4-6", max_tokens=4096, tools=tools, messages=messages, )
if response.stop_reason == "end_turn": for block in response.content: if hasattr(block, "text"): return block.text return ""
if response.stop_reason == "tool_use": tool_results = process_tool_calls(response) messages.append({"role": "assistant", "content": response.content}) messages.append({"role": "user", "content": tool_results}) else: break
return ""Per orchestrare più agenti che chiamano ciascuno degli strumenti, vedi Pattern Multi-Agente.
Pattern 4: Wrapper Sicuro per le Chiamate agli Strumenti
Non lasciare mai che le eccezioni degli strumenti raggiungano il ciclo dell’agente senza essere gestite.
import signal
def timeout_handler(signum, frame): raise TimeoutError("Esecuzione dello strumento scaduta")
def safe_tool_call( name: str, inputs: dict, timeout_seconds: int = 30,) -> ToolResult: """ Esegue uno strumento con timeout, catturando tutte le eccezioni. Restituisce sempre un ToolResult — non solleva mai eccezioni. """ signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(timeout_seconds) try: return dispatch_tool(name, inputs) except TimeoutError: return ToolResult( success=False, error=f"Lo strumento '{name}' ha superato il timeout di {timeout_seconds}s" ) except Exception as e: return ToolResult( success=False, error=f"Lo strumento '{name}' ha sollevato {type(e).__name__}: {e}" ) finally: signal.alarm(0)Quando uno strumento restituisce success: false, il modello può decidere se riprovare, provare un’alternativa o segnalare il fallimento. Per strategie di retry più ampie, vedi Pattern di Recupero Errori.
Pattern 5: Validazione e Troncatura dei Risultati
Valida i risultati degli strumenti prima di restituirli al modello.
MAX_TOOL_RESULT_CHARS = 8000
def validate_result(result: ToolResult, expected_keys: list[str]) -> ToolResult: if not result.success or not isinstance(result.data, dict): return result missing = [k for k in expected_keys if k not in result.data] if missing: return ToolResult( success=False, error=f"Risposta dello strumento manca dei campi attesi: {missing}" ) return result
def truncate_result(result: ToolResult) -> ToolResult: content = result.to_content() if len(content) <= MAX_TOOL_RESULT_CHARS: return result
truncated_data = { "truncated": True, "chars_omitted": len(content) - MAX_TOOL_RESULT_CHARS, "content": content[:MAX_TOOL_RESULT_CHARS], } return ToolResult( success=result.success, data=truncated_data, error="Risultato troncato — troppo grande per la finestra di contesto", )
def safe_tool_call_validated( name: str, inputs: dict, expected_keys: list[str] | None = None,) -> ToolResult: result = safe_tool_call(name, inputs) if expected_keys: result = validate_result(result, expected_keys) result = truncate_result(result) return resultPer osservare e fare debug di questi fallimenti in produzione, vedi Debug e Osservabilità.
Errori Comuni
Errore 1: Restituire Risposte API Grezze
Il modello riceve un oggetto annidato con 30 campi, la maggior parte irrilevanti. Sceglie quello sbagliato.
Soluzione: Forma la risposta prima di restituirla. Restituisci solo ciò di cui il modello ha bisogno per la sua prossima decisione.
Errore 2: Strumenti con Effetti Collaterali senza Conferma
Uno strumento che invia un’email, elimina un record o addebita un pagamento non dovrebbe eseguirsi silenziosamente.
Soluzione: Per azioni irreversibili, usa un pattern a due strumenti: plan_email restituisce un’anteprima, send_email invia effettivamente.
Errore 3: Responsabilità degli Strumenti Sovrapposte
Due strumenti che fanno cose simili costringono il modello a indovinare quale usare.
Soluzione: Ogni strumento deve avere uno scopo distinto e non sovrapposto.
Errore 4: Nessun Timeout sugli Strumenti Esterni
Una lenta chiamata API di terze parti blocca indefinitamente l’intero ciclo dell’agente.
Soluzione: Imposta sempre un timeout (Pattern 4).
Lista di Controllo per la Produzione
- Ogni strumento ha una descrizione dalla prospettiva del modello (“Usalo quando…”)
- Campi enum per tutti gli input a valore fisso
- Ogni strumento restituisce
{success, data, error}— mai risposte grezze - Chiamate agli strumenti avvolte in
safe_tool_call - Esecuzione parallela per risposte multi-strumento
- Timeout impostato su ogni strumento esterno
- Troncatura dei risultati per payload di dimensione variabile
- Validazione per strumenti che chiamano API esterne
Prossimi Passi
- Inizia con lo schema — scrivi il tuo
input_schemaprima del corpo della funzione - Aggiungi il wrapper
ToolResulta ogni strumento esistente - Integra
safe_tool_callper rafforzare il ciclo dell’agente - Configura la validazione dei risultati per strumenti che chiamano API esterne
Guide correlate:
- Costruire il tuo Primo Server MCP
- Pattern Multi-Agente
- Sistemi di Memoria per Agenti
- Pattern di Recupero Errori degli Agenti
- Debug e Osservabilità