Blog/LLMOps & Evals
LLM-Rate-Limits in Produktion: Retries, Backoff und Provider-Fallback
Wie LLM-Rate-Limits funktionieren, welche Fehler man wiederholt, Backoff mit Jitter, Retries in nur einer Schicht und Fallback-Ketten ohne kaputte Ausgaben.
Balázs Csorba··10 Min. Lesezeit
- Rate limits
- Retries
- Fallback
- Reliability
- LLM gateway

Das Wichtigste in Kürze
- Provider-Limits sind Token-Buckets, gemessen pro Minute in Anfragen und Tokens, aber über kürzere Intervalle durchgesetzt, deshalb scheitern Bursts auch bei unauffälliger Minutensumme.
- Fehler vor dem Retry klassifizieren: 429 mit retry-after und 5xx sind vorübergehend, ein 429 wegen Ausgabenlimit, 400er und Auth-Fehler nicht.
- Exponentielles Backoff mit Jitter verwenden, retry-after als Minimum respektieren und sowohl Versuche als auch Gesamtzeit begrenzen.
- Retries in genau einer Schicht. SDK-, Gateway- und App-Retries multiplizieren sich, und das AWS-Beispiel zeigt, wie schnell das wächst.
- Ein Fallback-Modell ist ein anderes Produkt: es auf dem eigenen Testsatz evaluieren, bevor es Produktionsverkehr bedient.
Jedes LLM-Feature, das echten Verkehr bekommt, trifft irgendwann auf einen 429. Manchmal ist man selbst schuld, etwa weil ein Batch-Job denselben Key wie der Chat-Endpunkt nutzt. Manchmal liegt es am Anbieter, der zu Stoßzeiten überlastet ist. So oder so sieht der Nutzer einen Spinner und dann einen Fehler, und die übliche Reaktion ist, den Aufruf in eine Retry-Schleife zu packen und weiterzumachen. Das rettet die Demo und erzeugt den nächsten Ausfall.
Dieser Leitfaden zeigt, was hinter einem Rate-Limit tatsächlich passiert, welche Fehler einen Retry verdienen und welche nie, wie man zurückweicht, ohne alle Clients zu synchronisieren, warum Retries in genau eine Schicht gehören und wie man eine Fallback-Kette zu einem anderen Deployment oder Anbieter baut, ohne stillschweigend zu ändern, was das Feature zurückgibt. Die Zahlen stammen aus der Dokumentation von Anthropic und OpenAI, Stand Oktober 2026.
Wie Provider-Rate-Limits tatsächlich funktionieren
Anthropic misst die Limits der Messages API in Anfragen pro Minute (RPM), Input-Tokens pro Minute (ITPM) und Output-Tokens pro Minute (OTPM), jeweils pro Modellklasse, und verwendet einen Token-Bucket: die Kapazität füllt sich laufend bis zum Maximum auf, statt zu jeder vollen Minute zurückgesetzt zu werden. Die Dokumentation nennt ein Detail, das viele rätselhafte 429er erklärt: ein Limit von 60 RPM kann als eine Anfrage pro Sekunde durchgesetzt werden, ein kurzer Burst scheitert also, obwohl die Minute insgesamt unter dem Limit bleibt.
OpenAI zählt Anfragen und Tokens pro Minute und pro Tag sowie Bilder pro Minute, und was zuerst erreicht wird, löst den Fehler aus. Zwei weitere Details sind fürs Design wichtig. OpenAI rechnet eine Anfrage mit dem Maximum aus max_tokens und der geschätzten Anfragegröße gegen das Token-Limit, ein großzügiges max_tokens frisst also Spielraum; Anthropic schreibt für sein Output-Limit das Gegenteil, dort zählen nur erzeugte Tokens. Und OpenAI weist darauf hin, dass auch fehlgeschlagene Anfragen gegen das Minutenlimit zählen, weshalb enge Retry-Schleifen einen 429 schlimmer statt besser machen.
| Aspekt | Anthropic Claude API | OpenAI API |
|---|---|---|
| Dimensionen | RPM, ITPM, OTPM | RPM, RPD, TPM, TPD, IPM |
| Gecachter Input | Cache-Reads zählen bei den meisten Modellen nicht zum ITPM | Zählt zum TPM |
| max_tokens | Zählt nicht; OTPM nutzt erzeugte Tokens | Zählt als max(max_tokens, Schätzung) |
| Header | anthropic-ratelimit-*, retry-after | x-ratelimit-*, Retry-After |
| Retry-After kommt bei | Den meisten 429ern, nicht beim Ausgabenlimit-429 | 429- und 503-Antworten |
| Burst-Schutz | Beschleunigungslimits bei starkem Anstieg | Kürzere Durchsetzungsfenster |
Anthropic wendet außerdem Beschleunigungslimits an: steigt die Nutzung der Organisation stark an, kann man 429er auch unterhalb der Tier-Limits bekommen, und die Empfehlung lautet, den Verkehr schrittweise hochzufahren. Das zählt am Launch-Tag und wenn ein Backfill-Job mit voller Geschwindigkeit startet.
Den Fehler klassifizieren, bevor man wiederholt
Ein Retry ist nur sinnvoll, wenn der nächste Versuch ohne jede Änderung gelingen kann. Die meisten Retry-Bugs entstehen, weil man jeden Nicht-200-Status gleich behandelt. Die Fehlerliste der Claude API macht die Trennung recht klar:
| Antwort | Bedeutung | Was tun |
|---|---|---|
429 + retry-after | Ein Rate-Limit wurde erreicht | Mindestens retry-after warten, dann wiederholen |
429 ohne retry-after, enforced_spend_limit_reached | Monatliches Ausgabenlimit des Tiers erreicht | Nicht wiederholen; Menschen alarmieren oder umschalten |
500 api_error, 529 overloaded_error | Vorübergehendes Serverproblem | Zurückweichen, wiederholen, dann Fallback |
504 timeout_error | Zeitüberschreitung bei der Verarbeitung | Einmal wiederholen; lange Anfragen streamen |
| 400, 401, 403, 404, 413 | Die Anfrage selbst ist falsch oder ein selbst gesetztes Ausgabenlimit (400) | Nie unverändert wiederholen; loggen und beheben |
Streaming fügt einen weiteren Fall hinzu. Bei Server-Sent Events kann ein Fehler eintreffen, nachdem die API bereits 200 geliefert hat, statusbasierte Retry-Logik sieht ihn also nie. Ob man eine halb gestreamte Antwort wiederholt, ist eine Produktentscheidung: in einer Chat-Oberfläche zeigt man, was angekommen ist, und bietet einen Retry-Button an; in einem Hintergrundjob verwirft man die Teilausgabe und wiederholt die ganze Anfrage.
Exponentielles Backoff mit Jitter und einer Deadline
Der OpenAI-Leitfaden und die AWS Builders Library sind sich in der Form einig: kurz warten, nach jedem Fehler länger, Zufall hinzufügen, damit Clients nicht im Gleichschritt wiederholen, und nach einer Grenze aufhören. OpenAI ergänzt, dass Retry-After als Minimum gilt, nicht als Vorschlag. Laut HTTP-Semantik enthält der Header entweder Sekunden oder ein HTTP-Datum, also beides parsen.
// One retry wrapper for the whole app. Turn SDK retries off (maxRetries: 0) when you use it.
const RETRYABLE = new Set([408, 429, 500, 502, 503, 504, 529])
function retryAfterMs(h: Headers): number | null {
const v = h.get('retry-after')
if (!v) return null
const s = Number(v)
if (!Number.isNaN(s)) return s * 1000
const t = Date.parse(v) // HTTP-date form
return Number.isNaN(t) ? null : Math.max(0, t - Date.now())
}
export async function withRetry<T>(call: () => Promise<Response>, parse: (r: Response) => Promise<T>,
{ attempts = 4, baseMs = 500, capMs = 20_000, deadlineMs = 45_000 } = {}): Promise<T> {
const start = Date.now()
for (let i = 0; ; i++) {
const res = await call()
if (res.ok) return parse(res)
const body = await res.clone().json().catch(() => ({}))
const spendCap = body?.error?.details?.error_code === 'enforced_spend_limit_reached'
if (!RETRYABLE.has(res.status) || spendCap || i + 1 >= attempts) throw new Error(`LLM ${res.status}`)
const backoff = Math.random() * Math.min(capMs, baseMs * 2 ** i) // "full jitter"
const wait = Math.max(retryAfterMs(res.headers) ?? 0, backoff)
if (Date.now() - start + wait > deadlineMs) throw new Error(`LLM ${res.status}: retry budget spent`)
await new Promise((r) => setTimeout(r, wait))
}
}Die Deadline zählt so viel wie die Zahl der Versuche. Eine Chat-Anfrage, die nach 45 Sekunden noch wiederholt, ist gescheitert, egal was danach passiert, also früh aufgeben und die Fallback-Kette oder die Oberfläche übernehmen lassen. Hintergrundjobs vertragen längere Deadlines, sollten aber auch deutlich stärker zurückweichen, denn sie konkurrieren mit dem interaktiven Verkehr um dieselben Limits.
Retries in genau einer Schicht
Der AWS-Artikel enthält die klarste Warnung: bei einem fünf Ebenen tiefen Aufrufstapel, in dem jede Ebene dreimal wiederholt, kann die Last auf der untersten Abhängigkeit um das 243-Fache wachsen. LLM-Stacks verstecken dieselbe Falle. Das Anthropic-SDK wiederholt Verbindungsfehler, 429er und 5xx standardmäßig zweimal und respektiert retry-after. Das Vercel AI SDK hat maxRetries mit Standardwert 2. Ein Gateway wie LiteLLM hat num_retries. Wiederholt zusätzlich der eigene Code, wird ein Klick während eines Vorfalls zu Dutzenden Upstream-Aufrufen, genau dann, wenn der Anbieter sie am wenigsten verkraftet.
- Die Schicht wählen, die am meisten weiß. Meist das Gateway, falls vorhanden, weil es den gesamten Verkehr und alle Deployments sieht. Sonst der eigene Wrapper.
- Überall sonst Retries auf null setzen.
maxRetries: 0im SDK-Client, keine Schleifen in Route-Handlern. - Ein Retry-Budget einführen. AWS begrenzt lokale Retries mit einem Token-Bucket: solange Tokens da sind, laufen Retries frei, danach nur noch mit fester Rate. So verdoppelt ein Ausfall nicht das Anfragevolumen.
- Seiteneffekte zuerst idempotent machen. Ein Agentenschritt, der eine E-Mail sendet oder eine Bestellung schreibt, darf nicht doppelt laufen, weil der Modellaufruf danach wiederholt wurde.
Eine Fallback-Kette aufbauen
Retries helfen bei kurzen Aussetzern. Bleibt ein Modell unerreichbar oder ist das Limit dafür erschöpft, braucht die Anfrage ein anderes Ziel. Ich ordne die Optionen danach, wie stark sie die Ausgabe verändern: zuerst dasselbe Modell auf einem anderen Deployment oder in einer anderen Region, dann ein Geschwistermodell desselben Anbieters, zuletzt ein anderer Anbieter und am Ende eine kontrollierte Degradierung (gecachte Antwort, einfacheres Feature, ehrliche Fehlermeldung).
Selbst schreiben muss man das selten. LiteLLM nimmt in router_settings eine fallbacks-Liste, die eine Modellgruppe auf geordnete Ersatzmodelle abbildet und nach num_retries Versuchen greift. Dazu gibt es context_window_fallbacks für zu lange Prompts und content_policy_fallbacks für Ablehnungen durch einen Provider-Filter. OpenRouter nimmt ein models-Array in Prioritätsreihenfolge, probiert bei einem Fehler das nächste Modell, auch bei Rate-Limits, Ausfällen und Kontextlängenfehlern, und berechnet das Modell, das die Anfrage tatsächlich bearbeitet hat.
# LiteLLM proxy: retries live here, nowhere else
router_settings:
num_retries: 2
allowed_fails: 3 # failures per minute before a deployment cools down
cooldown_time: 30 # seconds the deployment is skipped
retry_policy:
RateLimitErrorRetries: 3
BadRequestErrorRetries: 0
AuthenticationErrorRetries: 0
DefaultRetries: 2
fallbacks: [{"support-primary": ["support-secondary"]}]
enable_pre_call_checks: true # needed for context-window fallbacks to check input sizeWarum Fallbacks das Produkt stillschweigend verändern
Ein Fallback, der 200 liefert, kann trotzdem ein Fehlschlag sein. Auf ein Modell abgestimmte Prompts verhalten sich auf einem anderen oft anders; Unterstützung für strukturierte Ausgabe und JSON-Schema-Abdeckung unterscheiden sich zwischen Anbietern; Tool-Calling-Formate und Ablehnungsstile sind verschieden; und Prompt-Caches gelten pro Anbieter, die ersten Minuten auf einem Fallback sind also teurer und langsamer. Teams merken das, wenn der Vorfall vorbei ist und Support-Tickets von seltsamen Antworten berichten.
- Den Fallback wie eine Migration evaluieren. Den Regressionssatz gegen jedes Modell der Kette laufen lassen, wie in LLM-Modellmigrationen ohne Regressionen beschrieben.
- Einen Prompt pro Modell pflegen. Ein Fallback-Eintrag ist Modell plus Prompt und Parameter, nicht nur ein Modellname.
- Ausgabe unabhängig von der Quelle validieren. Schema-Validierung und fachliche Prüfungen laufen bei jeder Antwort; strukturierte Ausgaben machen sie nicht überflüssig.
- Festhalten, welches Modell geantwortet hat. Das bedienende Modell in die Traces schreiben, damit Qualitäts-Dashboards nicht zwei Produkte zu einer Linie vermischen.
- Datenresidenz prüfen. Ein Fallback von einer EU-Region auf einen US-Endpunkt kann das Versprechen im AVV brechen; siehe DSGVO-Datenresidenz für LLM-APIs.
Cooldowns und Circuit Breaker
Ohne Gedächtnis entdeckt jede Anfrage erneut, dass ein Deployment ausgefallen ist, und bezahlt den Timeout. Martin Fowlers Circuit Breaker löst das mit drei Zuständen: geschlossen (Aufrufe gehen durch, Fehler werden gezählt), offen (Aufrufe scheitern sofort, ohne die Abhängigkeit zu berühren) und halboffen (nach einer Wartezeit entscheidet ein Probeaufruf, ob wieder geschlossen wird). LiteLLM liefert eine einfachere Variante pro Deployment: nach allowed_fails Fehlern in einer Minute (Standard 3) wird ein Deployment für cooldown_time Sekunden (Standard 5) pausiert und der Verkehr geht an die anderen.
Der AWS-Artikel ist bei Breakern vorsichtiger: sie fügen schwer testbares modales Verhalten hinzu und können die Erholung verlangsamen, weshalb AWS lokale Retry-Budgets bevorzugt. Mein Kompromiss sind kurze Cooldowns pro Deployment, ein Retry-Budget pro Client und kein globaler Breaker, der wegen einer lauten Minute ein ganzes Feature abschalten kann.
Die Decke anheben, bevor man sie erreicht
Der günstigste 429 ist der, der nie passiert. Bei den meisten Claude-Modellen zählen Cache-Reads nicht zum ITPM, Caching erhöht also den effektiven Durchsatz: Anthropics Beispiel ist ein ITPM-Limit von 2.000.000 mit 80 % Cache-Trefferquote, das 10.000.000 Input-Tokens pro Minute verarbeitet. Das Setup steht in Prompt-Caching und Modell-Routing. Weitere Hebel:
- Workloads trennen. Anthropic-Workspaces können eigene, niedrigere Limits haben, damit ein Backfill den Chat-Endpunkt nicht aushungert. Getrennte Keys oder Projekte leisten anderswo dasselbe.
- Offline-Arbeit von der synchronen API nehmen. Batch-Endpunkte haben eigene Limits und verbrauchen nicht die Limits der Messages API.
- Die Header lesen. Mit
anthropic-ratelimit-*-remainingundx-ratelimit-remaining-*kann sich ein Client selbst drosseln, bevor es der Anbieter tut. - Schrittweise hochfahren. Backfills langsam starten, um Beschleunigungslimits zu meiden, und öffentliche Endpunkte vor Missbrauch schützen, siehe Denial of Wallet.
Timeouts und lange Anfragen
Eine Retry-Policy ohne Timeout ist unvollständig, denn eine hängende Verbindung erzeugt nie einen Fehler, den man wiederholen könnte. AWS empfiehlt, Timeouts aus Latenzperzentilen der Abhängigkeit abzuleiten statt zu raten. Für LLM-Aufrufe heißt das getrennte Budgets für die Zeit bis zum ersten Token und die Gesamtdauer. Anthropic empfiehlt Streaming oder die Batches API für Anfragen, die länger als zehn Minuten laufen können, weil manche Netze inaktive Verbindungen trennen, und die SDKs lehnen nicht gestreamte Anfragen ab, die voraussichtlich ein Zehn-Minuten-Timeout überschreiten. Das AI SDK nimmt ein timeout-Objekt mit totalMs, stepMs und chunkMs, das genau auf diese Budgets passt.
Bei jedem Fehler den request-id-Header loggen. Liegt ein Vorfall beim Anbieter, fragt dessen Support genau nach dieser ID, und aus einem vagen „die API war langsam“ wird etwas Nachverfolgbares. Retries, Fallbacks und das bedienende Modell in denselben Trace schreiben, wie in Observability für LLM-Agenten, dann sieht man hinterher, wie viel eines Vorfalls die Kette abgefangen hat.
Quellen
- Claude Platform Docs: Rate limits
- Claude Platform Docs: API errors
- OpenAI API Docs: Rate limits
- AWS Builders Library: Timeouts, retries and backoff with jitter
- MDN: Retry-After header
- Martin Fowler: Circuit Breaker (2014)
- LiteLLM Docs: Router, retries and cooldowns
- LiteLLM Docs: Proxy fallbacks and reliability
- OpenRouter Docs: Model fallbacks
- AI SDK Docs: Settings (maxRetries, timeout)
Häufige Fragen
Was ist der Unterschied zwischen 429 und 529 bei der Claude API?
Ein 429 rate_limit_error bedeutet, dass die eigene Organisation ein eigenes Limit erreicht hat, etwa Anfragen oder Tokens pro Minute oder das monatliche Ausgabenlimit. Ein 529 overloaded_error bedeutet, dass die API vorübergehend für alle Nutzer überlastet ist. Beide lohnen meist einen verzögerten Retry, außer dem 429 wegen Ausgabenlimit: er hat keinen retry-after-Header und scheitert weiter, bis der Zugang wieder freigegeben ist.
Wie oft sollte man eine LLM-Anfrage wiederholen?
Zwei bis vier Versuche mit exponentiellem Backoff sind üblich, und sowohl das Anthropic-SDK als auch das Vercel AI SDK wiederholen standardmäßig zweimal. Wichtiger als die Zahl ist eine Obergrenze für die Gesamtzeit, damit eine Nutzeranfrage nicht minutenlang hängt, und dass nur eine Schicht im Stack wiederholt.
Zählt max_tokens gegen die Rate-Limits?
Das hängt vom Anbieter ab. Anthropic schreibt, dass max_tokens nicht in das Output-Token-Limit eingeht, das nur erzeugte Tokens zählt. OpenAI rechnet die Anfrage mit dem Maximum aus max_tokens und der geschätzten Tokenzahl gegen das Limit, ein überdimensioniertes max_tokens verbraucht also Spielraum.
LiteLLM, OpenRouter oder eigener Code für Fallbacks?
Wer schon ein Gateway betreibt, konfiguriert Fallbacks dort und schaltet Retries anderswo ab. LiteLLM bietet Retry-Policies pro Fehlertyp, Cooldowns und Kontextfenster-Fallbacks zum Selbsthosten; OpenRouter bietet ein models-Array hinter einem API-Key. Eigener Code reicht für ein oder zwei Anbieter, aber am Ende baut man Cooldowns nach.
Ist ein Fallback auf einen anderen Anbieter bei strukturierter Ausgabe sicher?
Nur wenn man es getestet hat. Anbieter unterscheiden sich darin, wie sie JSON Schema durchsetzen, welche Features sie unterstützen und wie sie Ablehnungen formulieren. Denselben Evaluationssatz gegen das Fallback-Modell laufen lassen und jede Antwort gegen das Schema validieren, egal welches Modell sie erzeugt hat.