Back to blog
Prompt Engineering

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
Primary nodetool execution
Routing modeCALL-GATE-7
StatusPUBLISHED
A tool call passes through argument validation, permission and result verification gates
CALL_GATE_7_V01: a proposal passes through validation, authorization and reconciliation.
TERMINAL_PREVIEW.LOG
$ tool --contract CALL-GATE-7
> validate: arguments / references / rights
> execute: stable operation ID
> route: verify / reconcile / review
Architecture, validation, failure modes and production checks

Function 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

FailureResponse
Unknown tool or extra argumentReject; record contract version
Wrong type or malformed dateReject before execution; one bounded re-proposal if safe
Valid shape, inaccessible ticketDeny; do not disclose ticket details to the model
Rate limit or unavailable tool before sendBackoff within a budget; respect provider limits
Timeout after send, result unknownReconcile by operation ID; do not blindly retry
Verified duplicate receiptReturn the prior result without a second action
Repeated mismatch or no reliable lookupStop 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.

CODE_BLOCK.TXT
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);