Function Calling Error Handling: Validate Arguments and Retry Safely
Validate before execution
Reconcile before retry
Original CALL-GATE-7 architecture
Arguments, permissions, receipts and safe retry

$ tool --contract CALL-GATE-7
> validate: arguments / references / rights
> execute: stable operation ID
> route: verify / reconcile / reviewFunction calling connects a model's proposal to an application action. A model may choose a plausible tool and still supply an invalid account ID, an unauthorized tenant, or duplicate a write after a timeout. The prompt is a helpful interface, but the application owns validation, permission checks, execution and recovery.
This guide addresses the narrow question of function calling error handling. For tool definitions, see tool schema design; for output shape, see structured outputs. The broader prompt engineering service and AI specialist guide for Armenia cover project decisions.
Problem and requirements
Consider a synthetic support workflow. An assistant may call createFollowUp(ticketId, assigneeId, dueAt). The model proposes arguments. The application must verify that the ticket exists, the actor can see it, the assignee belongs to the allowed team, and the date is valid. A valid JSON object is not proof of any of these facts.
The acceptance contract should name the tool and version, validate exact argument types and bounds, bind the call to actor and tenant, specify whether the operation reads or writes, and define the result receipt. For writes, assign a stable operation ID before the external call. Decide how to reconcile an unknown outcome before enabling retries.
Original CALL-GATE-7 architecture
This is a design pattern, not a measured performance result.
01 PROPOSE model returns tool name and arguments 02 PARSE accept one bounded object for an allowlisted tool 03 VALIDATE check schema, references and business invariants 04 AUTHORIZE bind actor, tenant and action to current policy 05 EXECUTE send once with stable operation ID and timeout 06 RECONCILE read destination state on ambiguous outcome 07 ROUTE report verified result, bounded retry or review
The execution boundary, not the model, owns the operation ID and credentials. Validation must occur again when a repeated proposal arrives; a previous approval is not a standing permission. Retain only the logs needed for diagnosis under the project's privacy rules.
Minimal contract and pseudocode
const call = parseOneToolCall(modelOutput, allowedTools); const args = schemas[call.name].parse(call.arguments); const actor = await loadActor(request.actorId); await authorize(actor, call.name, args.ticketId); const operationId = stableId(request.id, call.name, args); const known = await findReceipt(operationId); if (known) return known; try { const receipt = await executeOnce(call.name, args, operationId); return verifyReceipt(receipt, args); } catch (error) { if (isUnknownOutcome(error)) return reconcileOrReview(operationId); if (isTransientBeforeSend(error)) return boundedRetry(operationId); return rejectWithClass(error); }
stableId, executeOnce and reconcileOrReview need real destination semantics; this is pseudocode. Do not compute an idempotency key from mutable model prose. A destination without idempotency or lookup may require human review after a timeout rather than another write.
Failure modes and routes
| Failure | Response |
|---|---|
| Unknown tool or extra argument | Reject; record contract version |
| Wrong type or malformed date | Reject before execution; one bounded re-proposal if safe |
| Valid shape, inaccessible ticket | Deny; do not disclose ticket details to the model |
| Rate limit or unavailable tool before send | Backoff within a budget; respect provider limits |
| Timeout after send, result unknown | Reconcile by operation ID; do not blindly retry |
| Verified duplicate receipt | Return the prior result without a second action |
| Repeated mismatch or no reliable lookup | Stop and route to a human |
Retries belong to a state machine. Keep separate budgets for model re-proposals and tool transport retries. Add jitter to transient transport backoff and cap elapsed time. Never turn an authorization failure, malformed arguments or known permanent error into a retry storm.
Testing and production checks
Test fixtures should cover a normal ticket, wrong tenant, deleted ticket, invalid date, unexpected field, duplicate proposal, failure before send, timeout after send, delayed receipt and conflicting destination state. Assert both the user-visible result and the absence of an extra write. Use a controllable fake destination to inject each failure; a success-only mock cannot prove retry safety.
Before release, version the tool schema, map error classes to routes, confirm policy ownership, set latency and retry budgets, verify the receipt lookup, and test rollback or manual recovery. Monitor rejected arguments, denied calls, unknown outcomes, reconciliations and duplicate suppression separately. These are diagnostic categories, not claimed KPIs.
For a process review, request a technical audit. See AI automation for the wider workflow and its human approval points.
const args = schema.parse(call.arguments);
await authorize(actor, call.name, args.ticketId);
const id = stableId(request.id, call.name, args);
return executeOnceOrReconcile(id, args);