How catbuddy Survives Three LLM APIs, Rate Limits, and Truncated Output Without Crashing
Most agent demos paper over the reality that LLM APIs fail in incompatible, provider-specific ways. catbuddy's design shows how to absorb those differences behind one interface, chain fallback models without the runner knowing, and recover from empty or truncated responses with targeted prompt injections—patterns that turn a brittle prototype into something that can run unattended.
A single abstract LLMProvider base class isolates every API quirk—system prompts as top-level parameters versus messages, tool-result role mismatches, and streaming tool-call chunk assembly—behind two concrete adapters. An OpenAICompatProvider handles any OpenAI-compatible service, while an AnthropicProvider translates Claude's dialect. The trickiest detail is DeepSeek's reasoning_content: the reasoning chain must be saved from one turn and replayed on the next, or multi-turn tool calls derail into nonsense.
Above the adapters, a FallbackProvider wraps a primary model and a list of fallbacks into a single decorator that tries each in sequence. It does not distinguish transient from permanent errors before switching; the cost of skipping a usable provider outweighs the cost of an extra attempt. A hot-swap mechanism triggered by the /model command compares configuration signatures to avoid reinitializing providers when nothing changed, letting the next turn pick up the new model without restarting the agent.
Inside a single call, three retry layers catch progressively higher-level failures: exponential-backoff retries at the provider level for 429s and 5xx errors, empty-response injection that nudges the model to try again, and max_tokens truncation recovery that tells the model to continue exactly where it left off without recapping. Every layer swallows only the errors it can fix and passes the rest upward, so the user never sees a blank crash screen.
Treating the OpenAI chat/completions format as a de-facto standard and routing everything except Anthropic through a single compat provider is a pragmatic bet that pays off in reduced maintenance surface.
The reasoning_content round-trip requirement for DeepSeek reveals that API compatibility is not just about wire format—some models leak state into the conversation that the adapter must manage.
FallbackProvider's refusal to classify errors before switching is a useful counterpoint to the common instinct to gate failover on transient-error detection; the heuristic is often wrong, and the cost of an extra call is low.
Hardcoding 'no recap, no apology' into the truncation-recovery prompt is a small but high-leverage optimization: without it, models burn tokens summarizing what they already wrote instead of continuing.
AbortError penetration through all retry layers is a UX requirement, not a technical one—users who hit stop expect the agent to stop immediately, not after a backoff timer expires.