cd /news/ai-agents/stop-letting-flaky-apis-crash-your-a… Β· home β€Ί topics β€Ί ai-agents β€Ί article
[ARTICLE Β· art-115734] src=dev.to β†— pub= topic=ai-agents verified=true sentiment=Β· neutral

Stop Letting Flaky APIs Crash Your AI Agents

A developer detailed a production-grade pattern for making AI agents resilient to flaky external APIs, combining exponential backoff, circuit breakers, and graceful fallbacks. The approach wraps every tool call in a defensive pipeline that returns degraded results with metadata, allowing the LLM to adjust its reasoning instead of crashing. The implementation uses the tenacity and circuitbreaker libraries to ensure agents survive rate limits, timeouts, and outages.

read2 min views9 publishedAug 30, 2026

How to combine exponential backoff, circuit breakers, and graceful fallbacks for production-grade agentic workflows.

AI agents are only as reliable as the tools they invoke. When an LLM decides to search the web, scrape a URL, or fetch database records, it depends entirely on network stability.

In production, external APIs fail constantly. A sudden surge causes 429 rate limits, a third-party microservice throws a 504 timeout, or a target endpoint goes down entirely. The naive approachβ€”executing raw tool calls directly inside the agent loopβ€”is a ticking time bomb:

def execute_agent_tool(tool_name: str, payload: dict):
    response = requests.post(f"https://api.service.internal/{tool_name}", json=payload)
    return response.json()

When this call breaks, the unhandled exception crashes the runtime. You lose the entire reasoning graph, waste LLM tokens, and degrade the user experience.

To keep multi-step agents alive, you need a defensive execution pipeline wrapped around every tool. Instead of allowing errors to bubble up and kill the agent, we handle failures across three distinct layers:

[ Agent Core ] 
      β”‚
      β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚     Circuit Breaker Check     β”‚
β”‚   (Is Primary Service Up?)    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       OPEN    β”‚   CLOSED (Healthy)
       β”Œβ”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”
       β–Ό                β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Fallback   β”‚  β”‚ Retry Engine (Backoff)    β”‚
β”‚  Provider   β”‚  β”‚ └──> Primary API Endpoint β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚                        β”‚ SUCCESS
       β”‚ (Degraded)             β”‚
       β–Ό                        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚      Structured Response + Metadata        β”‚
β”‚   (LLM receives context of degradation)    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

By returning a degraded result accompanied by metadata (e.g., status: "degraded", source: "fallback_cache"

), the LLM can adjust its downstream reasoning rather than hallucinating over missing data.

We combine tenacity

for retry logic with circuitbreaker

to isolate failing services. The following production-ready pattern ensures failures are caught and handled before reaching the LLM orchestrator.

from tenacity import retry, stop_after_attempt, wait_exponential
from circuitbreaker import circuit, CircuitBreakerError

@circuit(failure_threshold=3, recovery_timeout=60)
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10))
def fetch_primary_data(query: str) -> dict:
    resp = requests.get("https://api.flaky-service.com/v1/search", params={"q": query}, timeout=3)
    resp.raise_for_status()
    return {"data": resp.json(), "source": "primary", "degraded": False}

def execute_tool_safely(query: str) -> dict:
    try:
        return fetch_primary_data(query)
    except (CircuitBreakerError, Exception) as err:
        cached_result = local_cache.get(query) or "No fresh data available."
        return {
            "data": cached_result,
            "source": "cache_fallback",
            "degraded": True,
            "error_context": str(err)
        }

This snippet ensures three critical guarantees:

"Note: Live search is offline. Using cached results from 2 hours ago."

). This keeps the model's responses

── more in #ai-agents 4 stories Β· sorted by recency
── more on @tenacity 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/stop-letting-flaky-a…] indexed:0 read:2min 2026-08-30 Β· β€”