Validazione Tool e Retry Hint
Perché Questo È Importante
Goa-AI combina le validazioni design-time di Goa con un modello di errore tool strutturato per dare ai planner LLM un modo potente per riparare automaticamente le chiamate tool invalide.
Invece di:
- spargere validazione ad-hoc negli executor,
- indovinare quali campi mancano da stringhe di errore opache, o
- costringere gli utenti a correggere manualmente ogni chiamata sbagliata,
Goa-AI ti permette di:
- descrivere schemi payload precisi e validazioni nel design Goa,
- mostrare i fallimenti come valori
ToolError+RetryHintstrutturati, e - insegnare al tuo planner a ritentare con input migliori basandosi su quegli hint.
Questo pattern è una delle migliori ragioni per usare Goa-AI per agenti che usano tool.
Tipi Core: ToolError e RetryHint
A livello planner (runtime/agent/planner):
ToolError
Alias perruntime/agent/toolerrors.ToolError:Message string– riepilogo leggibile.Cause *ToolError– causa annidata opzionale (preserva le catene attraverso retry e hop agent-as-tool).- Costruttori e helper:
planner.NewToolError(msg string)planner.NewToolErrorWithCause(msg string, cause error)planner.ToolErrorFromError(err error)planner.ToolErrorf(format, args...).
RetryHint
Hint lato planner usato dal runtime e dal motore policy:type RetryHint struct { Reason RetryReason Tool tools.Ident RestrictToTool bool MissingFields []string ExampleInput map[string]any PriorInput map[string]any ClarifyingQuestion string Message string }Valori
RetryReasoncomuni:invalid_arguments– il payload ha fallito la validazione (schema/tipo).missing_fields– mancano campi obbligatori.malformed_response– il tool ha restituito dati che non possono essere decodificati.timeout,rate_limited,tool_unavailable– problemi di esecuzione/infra.
Il runtime converte gli hint del planner in una forma runtime/policy (policy.RetryHint) con gli stessi campi; i motori policy e le UI possono quindi reagire (regolare cap, disabilitare tool, mostrare suggerimenti di riparazione).
Da Dove Viene la Validazione
La validazione è design-driven:
- Nel tuo design Goa, descrivi i payload dei tool (
Args) con:- tipi
Attribute, Required(...),- vincoli (
MinLength,Maximum,Enum, ecc.), - descrizioni ed esempi.
- tipi
- Il codegen Goa-AI emette:
- struct payload/result tipizzate, e
- validatori/codec che applicano quelle regole al confine del tool.
Quando un payload tool fallisce la validazione (es., un campo obbligatorio mancante), il codice generato e il runtime possono:
- produrre un
ToolErrorcon un messaggio conciso, e - allegare un
RetryHintche dice al planner esattamente come riparare la chiamata.
Non hai bisogno di scrivere parser custom per stringhe di errore; il pattern è standardizzato.
ToolResult: Trasportare Errori e Hint
Ogni invocazione tool emerge come planner.ToolResult:
type ToolResult struct {
Name tools.Ident
Result any
Error *ToolError
RetryHint *RetryHint
Telemetry *telemetry.ToolTelemetry
ToolCallID string
ChildrenCount int
RunLink *run.Handle
}
Punti chiave:
- In caso di successo:
Resultcontiene il risultato decodificato (o JSON raw come fallback).ErroreRetryHintsono nil.
- In caso di fallimento di validazione o esecuzione:
Errordescrive cosa è andato storto in modo planner/UI-friendly.RetryHint(quando presente) spiega come riprovare.
Il runtime:
- riceve un output tool logico dal tuo executor o attività,
- wrappa qualsiasi messaggio di fallimento in un
ToolError, - allega qualsiasi
RetryHintprodotto al confine, e - pubblica un
ToolResultReceivedEvent(e corrispondente eventostream.ToolEnd) con un payloadtoolerrors.ToolErrorstrutturato per le UI.
Pattern: Auto-Riparazione di Chiamate Tool Invalide
Il pattern raccomandato è:
- Design tool con schemi payload forti (design Goa).
- Lascia che executor/tool mostrino i fallimenti di validazione come
ToolError+RetryHintinvece di fare panic o nascondere errori. - Insegna al tuo planner a:
- ispezionare
ToolResult.ErroreToolResult.RetryHint, - riparare il payload quando possibile (o chiedere all’utente), e
- ritentare la chiamata tool se appropriato.
- ispezionare
Esempio Executor (Pseudo-Codice)
Executor concettuale per un tool che fa upsert di un record:
func Execute(ctx context.Context, meta runtime.ToolCallMeta, call planner.ToolRequest) (*planner.ToolResult, error) {
// Decodifica usando codec generato
args, err := spec.UnmarshalUpsertPayload(call.Payload)
if err != nil {
// Errore di validazione/decode → ToolError + RetryHint strutturati
return &planner.ToolResult{
Name: call.Name,
Error: planner.NewToolError("payload invalido"),
RetryHint: &planner.RetryHint{
Reason: planner.RetryReasonInvalidArguments,
Tool: call.Name,
RestrictToTool: true,
Message: "Il payload non corrisponde allo schema atteso.",
// Opzionalmente: MissingFields, ExampleInput, ClarifyingQuestion...
},
}, nil
}
// Chiama il servizio sottostante
res, err := client.Upsert(ctx, args)
if err != nil {
return &planner.ToolResult{
Name: call.Name,
Error: planner.ToolErrorFromError(err),
}, nil
}
// Successo: restituisci risultato come al solito
return &planner.ToolResult{
Name: call.Name,
Result: res,
}, nil
}
Esempio Logica Planner
In PlanResume, il tuo planner può ispezionare l’ultimo risultato tool:
func (p *MyPlanner) PlanResume(ctx context.Context, in *planner.PlanResumeInput) (*planner.PlanResult, error) {
if len(in.ToolResults) == 0 {
// Nulla da fare
return &planner.PlanResult{}, nil
}
last := in.ToolResults[len(in.ToolResults)-1]
if last.Error != nil && last.RetryHint != nil {
hint := last.RetryHint
switch hint.Reason {
case planner.RetryReasonMissingFields, planner.RetryReasonInvalidArguments:
// Strategia 1: Chiedi all'utente chiarimenti (AwaitClarification)
return &planner.PlanResult{
Await: &planner.Await{
Clarification: &planner.AwaitClarification{
ID: "fix-" + string(hint.Tool),
Question: hint.ClarifyingQuestion,
MissingFields: hint.MissingFields,
RestrictToTool: hint.Tool,
ExampleInput: hint.ExampleInput,
ClarifyingPrompt: hint.Message,
},
},
}, nil
case planner.RetryReasonTimeout, planner.RetryReasonRateLimited:
// Strategia 2: Backoff o cambia tool (specifico dell'implementazione)
}
}
// Default: sintetizza una risposta finale dai risultati tool
// ...
return &planner.PlanResult{/* FinalResponse, prossime ToolCalls, ... */}, nil
}
Questo pattern permette all’LLM (tramite il tuo codice planner) di riparare le chiamate tool invece di fallire completamente il run.
Come Goa-AI Usa Goa per Far Funzionare Questo
Goa-AI sfrutta il design e codegen di Goa in diversi modi:
Validazione design-time
Il design Goa per il payload di un tool definisce:- campi obbligatori vs opzionali,
- valori permessi, formati e range,
- descrizioni ed esempi.
Validatori e codec generati
Per ogni payload tool:- Goa-AI emette struct tipizzate e logica di validazione.
- Gli errori di validazione al confine possono essere mappati in messaggi concisi e hint a livello di campo.
Costruzione hint runtime
Il runtime e il layer attività:- catturano i fallimenti di validazione al confine del tool (prima di eseguire il servizio sottostante),
- preservano le catene di errore come
ToolError, - allegano qualsiasi
RetryHintdisponibile alToolResult, e - evitano di interrompere l’intero workflow quando una singola chiamata tool è invalida.
Poiché design e runtime concordano sugli schemi, puoi fare affidamento su un contratto errore/hint uniforme per tutti i tool, che siano:
- service-backed,
- agent-backed (agent-as-tool),
- o MCP-backed.
Best Practice
Metti le validazioni nel design, non nei planner
Usa il DSL degli attributi di Goa (Required,MinLength,Enum, ecc.). Lascia che validatori e codec generati le applichino; mostra errori strutturati e hint al confine del tool.Restituisci ToolError + RetryHint dagli executor
Preferisci:return &planner.ToolResult{ Name: call.Name, Error: planner.NewToolError("..."), RetryHint: &planner.RetryHint{ /* guida strutturata */ }, }, nilrispetto a panic o return
errorsemplici.Mantieni gli hint concisi ma azionabili
Concentrati su:- quali campi sono mancanti/invalidi,
- una breve domanda chiarificatrice,
- una piccola mappa
ExampleInputcon valori corretti.
Insegna ai planner a leggere gli hint
Rendi la gestione diRetryHintuna parte di prima classe del tuo planner:- ripara e riprova quando è sicuro,
- chiedi all’utente tramite
AwaitClarificationquando necessario, - altrimenti fallback a risposte finali.
Evita ri-validazione dentro i servizi
Goa-AI assume che la validazione avvenga al confine del tool; la logica interna del servizio dovrebbe fidarsi degli input validati e concentrarsi sul comportamento di dominio.
Per un approfondimento sui pattern di retry guidati dalla validazione e miglioramenti futuri (builder di hint schema-aware, issue di campo più ricchi), consulta i package runtime e planner di Goa-AI nel repo goa-ai (es., runtime/agent/toolerrors e runtime/agent/planner).