Error Handling & Retries
Failure is part of the loop
Tool calls fail: timeouts, bad args, service errors, rate limits. A typed error envelope returned to the model continues the loop gracefully; robust retry logic with backoff keeps agents working under transient failures.
What you will learn
- Return typed error envelopes to the model
- Implement backoff retries for transients
- Distinguish retryable vs permanent errors
What is this?
Error handling in function calling means errors stay INSIDE the loop: a tool that fails returns a structured envelope describing what failed — so the model can recover or report honestly. Retry logic applies to transient failures (rate limits, timeouts) with exponential backoff; permanent errors (bad args, auth) surface and restructure instead.
Why do you need it?
- Silent 'empty result' failures poison the model's reasoning.
- Typed envelopes let the model self-correct or fail honestly.
- Retry with backoff absorbs provider throttling without spikes.
- Error taxonomy (transient vs permanent) is the backbone of the retry policy.
Mental model
Kitchen ticket with a '86'd' note
Typed error envelope
When the kitchen is out of a dish, the waiter is told '86 short ribs', not silence. Back to the guest: apologize, offer the alternative (model recovers), or flag the table honestly. Silence would mean the guest waits forever — exactly what silent failures do to a model loop.
How it works
- Wrap each tool execution in try/catch (plus timeout).
- Format errors: code, message, optional details — as a tool result envelope.
- Let the model retry on transient, adapt on permanent.
- Apply backoff (e.g. 1s, 2s, 4s) for external rate limits.
- Count retries; after cap, return the honest failure to the user.
Error flow.
Examples
Envelope + backoff
function run(tool) { for (let attempt = 1; attempt <= 3; attempt++) { try { return { ok: true, result: tool() }; // success } catch (e) { const transient = /rate|timeout|429|503/.test(e.message); if (transient && attempt < 3) { wait(2 ** attempt * 1000); continue; } return { ok: false, error: { code: "tool_error", message: e.message, transient } }; } }}Backoff retry for transients; permanent errors as net envelopes.
In production
- Read/write wrappers return envelopes so the loop self-heals.
- Rate-limited providers get backoff before failure reaches the model.
- Eval harness asserts graceful degradation on injected errors.
Best practices
- Distinguish transient vs permanent by error code.
- Never expose raw stack traces to the model.
- Cap retries AND aggregate time.
- Send the model a short, useful reason, not a wall of text.
Common mistakes
- Retrying permanent errors forever.
- Returning 'no data' on exception (the model reasons off a lie).
- Ignoring rate-limit headers so retries collide harder.
Common issues
Rate limit stormBackoff missing
Exponential backoff + jitter; honor Retry-After.
Model retries a bad schema call endlesslyPermanent error unmarked
Tag permanent errors; tell the model to adapt, not repeat.
Debugging tips
- Same tool keeps failing → check it's not a permanent error mislabeled.
- Retries amplify load → add jitter + respect Retry-After.
- Model confused after failure → envelope too vague; add concrete reason.
Interview checkpoint
Hands-on lab
- Raise a typed error for invalid tool arguments
- Coerce and retry a malformed call exactly once
- Cap retries so a stuck loop gives up cleanly
Retry Malformed Tool Calls
25 min · intermediate
Quick quiz
A rate-limit error is usually…
An empty result on failure beats an error envelope.