Blog/LLMOps és értékelés
LLM rate limitek élesben: újrapróbálás, backoff és szolgáltatóváltás
Hogyan működnek az LLM rate limitek, mit érdemes újrapróbálni, backoff jitterrel, retry egy rétegben, és fallback-láncok, amelyek nem rontják el a kimenetet.
Balázs Csorba··10 perc olvasás
- Rate limits
- Retries
- Fallback
- Reliability
- LLM gateway

A lényeg röviden
- A szolgáltatói limitek token bucketek, percenkénti kérésben és tokenben mérve, de rövidebb intervallumokon érvényesítve, ezért a löketek akkor is elbuknak, ha a percösszeg rendben lévőnek tűnik.
- Újrapróbálás előtt osztályozd a hibát: a retry-afterrel érkező 429 és az 5xx átmeneti, a költéskeret miatti 429, a 400-asok és a hitelesítési hibák nem.
- Exponenciális backoffot használj jitterrel, a retry-aftert tekintsd minimumnak, és korlátozd a próbálkozások számát és a teljes időt is.
- Pontosan egy rétegben próbálj újra. Az SDK, a gateway és az alkalmazás retry-jai összeszorzódnak, és az AWS példája megmutatja, milyen gyorsan.
- A fallback-modell egy másik termék: értékeld ki a saját tesztkészleteden, mielőtt éles forgalmat szolgálna ki.
Minden LLM-funkció, amely valódi forgalmat kap, előbb-utóbb belefut egy 429-esbe. Néha a saját hibánk, például egy batch-feladat ugyanazt a kulcsot használja, mint a chat-végpont. Néha a szolgáltatóé, amely csúcsidőben túlterhelt. A felhasználó mindkét esetben egy töltésjelzőt, majd egy hibát lát, a szokásos reakció pedig az, hogy a hívást becsomagoljuk egy retry-ciklusba, és lépünk tovább. Ez megmenti a demót, és előkészíti a következő leállást.
Ez az útmutató bemutatja, mi történik valójában egy rate limit mögött, mely hibák érdemelnek újrapróbálást és melyek soha, hogyan várakozzunk úgy, hogy ne szinkronizáljuk az összes klienst, miért pontosan egy rétegbe tartozik az újrapróbálás, és hogyan építsünk fallback-láncot egy másik deploymentre vagy szolgáltatóra úgy, hogy közben csendben ne változzon meg, mit ad vissza a funkció. A számok az Anthropic és az OpenAI 2026. októberi dokumentációjából származnak.
Hogyan működnek valójában a szolgáltatói rate limitek
Az Anthropic a Messages API limitjeit percenkénti kérésekben (RPM), percenkénti bemeneti tokenekben (ITPM) és percenkénti kimeneti tokenekben (OTPM) méri, modellosztályonként, és token bucketet használ: a kapacitás folyamatosan töltődik vissza a maximumig, ahelyett hogy minden perc elején nullázódna. A dokumentáció egy részletet is kiemel, amely sok rejtélyes 429-et megmagyaráz: a 60 RPM-es limitet érvényesíthetik másodpercenként egy kérésként is, így egy rövid löket akkor is elbukik, ha a perc egészében a limit alatt maradsz.
Az OpenAI percenként és naponta számolja a kéréseket és a tokeneket, továbbá percenként a képeket, és amelyik előbb betelik, az váltja ki a hibát. Két további részlet fontos a tervezéshez. Az OpenAI egy kérést a max_tokens és a becsült kérésméret maximumával számol a tokenlimit ellen, vagyis a bőkezű max_tokens felfalja a mozgásteret; az Anthropic a kimeneti limitjére az ellenkezőjét írja, ott csak a generált tokenek számítanak. Az OpenAI arra is figyelmeztet, hogy a sikertelen kérések is beleszámítanak a percenkénti limitbe, ezért a szoros retry-ciklusok rontanak a 429-en, nem javítanak.
| Szempont | Anthropic Claude API | OpenAI API |
|---|---|---|
| Dimenziók | RPM, ITPM, OTPM | RPM, RPD, TPM, TPD, IPM |
| Cache-elt bemenet | A cache-olvasás a legtöbb modellnél nem számít bele az ITPM-be | Beleszámít a TPM-be |
| max_tokens | Nem számít; az OTPM a generált tokeneket nézi | max(max_tokens, becslés) számít |
| Fejlécek | anthropic-ratelimit-*, retry-after | x-ratelimit-*, Retry-After |
| Retry-After érkezik | A legtöbb 429-nél, a költéskeret-429-nél nem | 429-es és 503-as válaszoknál |
| Löketvédelem | Gyorsulási limitek hirtelen növekedésnél | Rövidebb érvényesítési ablakok |
Az Anthropic gyorsulási limiteket is alkalmaz: ha a szervezet használata hirtelen megugrik, a szintedhez tartozó limitek alatt is kaphatsz 429-et, és a tanács az, hogy fokozatosan növeld a forgalmat. Ez az indulás napján számít, és akkor, amikor egy visszatöltő feladat teljes sebességgel indul.
Osztályozd a hibát, mielőtt újrapróbálod
Az újrapróbálás csak akkor hasznos, ha a következő kísérlet bármilyen változtatás nélkül sikerülhet. A legtöbb retry-hiba abból ered, hogy minden nem 200-as választ egyformán kezelünk. A Claude API hibalistája elég tisztán elválasztja az eseteket:
| Válasz | Jelentés | Teendő |
|---|---|---|
429 + retry-after | Elértél egy rate limitet | Várj legalább retry-afternyit, aztán próbáld újra |
429 retry-after nélkül, enforced_spend_limit_reached | Elérted a szint havi költéskeretét | Ne próbáld újra; riassz embert vagy válts |
500 api_error, 529 overloaded_error | Átmeneti szerveroldali hiba | Várj, próbáld újra, aztán fallback |
504 timeout_error | Feldolgozás közben lejárt az idő | Egyszer próbáld újra; a hosszú kéréseket streameld |
| 400, 401, 403, 404, 413 | Maga a kérés hibás, vagy saját költéskorlát (400) | Változatlanul soha; naplózd és javítsd |
A streaming még egy esetet hoz. Server-sent events esetén a hiba azután is megérkezhet, hogy az API már 200-at adott vissza, így a státuszkód alapú retry-logika sosem látja. Hogy egy félig streamelt választ újrapróbálsz-e, termékdöntés: chat-felületen mutasd meg, ami megérkezett, és kínálj újrapróbálás gombot; háttérfeladatban dobd el a részleges kimenetet, és ismételd meg az egész kérést.
Exponenciális backoff jitterrel és határidővel
Az OpenAI útmutatója és az AWS Builders Library egyetért a formában: várj egy kicsit, minden hiba után egyre tovább, adj hozzá véletlenszerűséget, hogy a kliensek ne egyszerre próbálkozzanak, és egy határ után állj meg. Az OpenAI hozzáteszi, hogy a Retry-After minimum, nem javaslat. A HTTP-szemantika szerint a fejléc vagy másodperceket, vagy HTTP-dátumot tartalmaz, ezért mindkettőt kezeld.
// 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))
}
}A határidő ugyanannyit számít, mint a próbálkozások száma. Egy chat-kérés, amely 45 másodperc után is még újrapróbál, kudarc, bármi történjék utána, ezért add fel korán, és hagyd, hogy a fallback-lánc vagy a felület kezelje. A háttérfeladatok megengedhetnek hosszabb határidőt, de sokkal erősebben is kell várakozniuk, mert ugyanazokért a limitekért versenyeznek az interaktív forgalommal.
Újrapróbálás pontosan egy rétegben
Az AWS-cikk adja a legvilágosabb figyelmeztetést: egy öt szint mély hívási láncban, ahol minden szint háromszor próbálkozik újra, a legalsó függőség terhelése a 243-szorosára nőhet. Az LLM-stackek ugyanezt a csapdát rejtik. Az Anthropic SDK alapból kétszer próbálja újra a kapcsolati hibákat, a 429-eseket és az 5xx-eseket, és figyelembe veszi a retry-after-t. A Vercel AI SDK-ban a maxRetries alapértéke 2. Egy gatewaynek, például a LiteLLM-nek, num_retries beállítása van. Ha a saját kódod is ciklusban próbálkozik, egy incidens alatt egyetlen kattintásból tucatnyi upstream hívás lesz, éppen akkor, amikor a szolgáltató a legkevésbé bírja.
- Azt a réteget válaszd, amelyik a legtöbbet tudja. Általában a gatewayt, ha van, mert az látja az összes forgalmat és minden deploymentet. Különben a saját wrapperedet.
- Mindenhol máshol állítsd nullára az újrapróbálást.
maxRetries: 0az SDK-kliensben, semmi ciklus a route handlerekben. - Vezess be retry-költségvetést. Az AWS token buckettel korlátozza a helyi újrapróbálást: amíg van token, szabadon mennek, utána csak rögzített ütemben. Így egy leállás nem duplázza meg a kérésszámot.
- Előbb tedd idempotenssé a mellékhatásokat. Egy ágenslépés, amely e-mailt küld vagy rendelést ír, nem futhat le kétszer csak azért, mert az utána következő modellhívást újrapróbáltad.
Fallback-lánc építése
Az újrapróbálás a rövid kiesésekre jó. Ha egy modell tartósan elérhetetlen, vagy kimerült a hozzá tartozó limited, a kérésnek máshová kell mennie. Én aszerint rendezem a lehetőségeket, mennyire változtatják meg a kimenetet: először ugyanaz a modell másik deploymenten vagy régióban, aztán ugyanannak a szolgáltatónak egy testvérmodellje, végül egy másik szolgáltató, a lánc végén pedig kontrollált visszalépés (cache-elt válasz, egyszerűbb funkció, őszinte hibaüzenet).
Ritkán kell magadnak megírnod. A LiteLLM a router_settings alatt egy fallbacks listát vár, amely egy modellcsoporthoz rendezett tartalékokat rendel, és num_retries próbálkozás után lép életbe. Van context_window_fallbacks is a túl hosszú promptokhoz és content_policy_fallbacks a szolgáltatói szűrő általi elutasításokhoz. Az OpenRouter prioritási sorrendben vár egy models tömböt, hiba esetén a következő modellel próbálkozik, rate limitnél, leállásnál és kontextushossz-hibánál is, és azt a modellt számlázza, amelyik ténylegesen kiszolgálta a kérést.
# 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 sizeMiért változtatja meg csendben a fallback a termékedet
Egy 200-at visszaadó fallback is lehet kudarc. Az egy modellre hangolt promptok gyakran másként viselkednek egy másikon; a strukturált kimenet támogatása és a JSON Schema lefedettsége szolgáltatónként eltér; a tool-calling formátumok és az elutasítási stílusok különböznek; a prompt cache pedig szolgáltatónként külön él, így a fallbacken töltött első percek drágábbak és lassabbak. A csapatok ezt akkor veszik észre, amikor az incidens már véget ért, és a supportjegyekben furcsa válaszokról olvasnak.
- Értékeld ki a fallbacket úgy, mint egy migrációt. Futtasd a regressziós készletet a lánc minden modelljén, ahogy az LLM-modellmigrációk regresszió nélkül cikkben leírtam.
- Tarts modellenként külön promptot. Egy fallback-bejegyzés modell, prompt és paraméterek együtt, nem csak egy modellnév.
- Validáld a kimenetet a forrástól függetlenül. A sémavalidáció és az üzleti ellenőrzések minden válaszon lefutnak; a strukturált kimenetek ezt nem teszik feleslegessé.
- Rögzítsd, melyik modell válaszolt. A kiszolgáló modellt írd bele a trace-ekbe, hogy a minőségi dashboardok ne mossanak össze két terméket egy vonallá.
- Ellenőrizd az adattárolás helyét. Egy EU-régióból amerikai végpontra váltó fallback megszegheti az adatfeldolgozási szerződés ígéretét; lásd: GDPR és LLM-adatrezidencia.
Cooldownok és circuit breakerek
Memória nélkül minden kérés újra felfedezi, hogy egy deployment nem működik, és kifizeti az időtúllépést. Martin Fowler circuit breakere ezt három állapottal oldja meg: zárt (a hívások átmennek, a hibákat számolja), nyitott (a hívások azonnal elbuknak, a függőséget meg sem érintik) és félig nyitott (egy várakozás után egyetlen próbahívás dönti el, zárjon-e újra). A LiteLLM deploymentenként egy egyszerűbb változatot ad: ha egy percen belül allowed_fails hiba történik (alapból 3), a deployment cooldown_time másodpercre (alapból 5) kimarad, és a forgalom a többihez megy.
Az AWS-cikk óvatosabb a breakerekkel: nehezen tesztelhető, módváltó viselkedést visznek a rendszerbe, és lassíthatják a helyreállást, ezért az AWS a helyi retry-költségvetést részesíti előnyben. Az én kompromisszumom: rövid cooldown deploymentenként, retry-költségvetés kliensenként, és semmi globális breaker, amely egyetlen zajos perc miatt egy egész funkciót lekapcsolhat.
Emeld a plafont, mielőtt beleütközöl
A legolcsóbb 429 az, amelyik meg sem történik. A legtöbb Claude-modellnél a cache-olvasás nem számít bele az ITPM-be, így a cache növeli a tényleges átviteli kapacitást: az Anthropic példájában egy 2 000 000-es ITPM-limit 80%-os cache-találati aránnyal percenként 10 000 000 bemeneti tokent dolgoz fel. A beállítást a prompt caching és modell-routing cikkben írtam le. További karok:
- Válaszd szét a terheléseket. Az Anthropic workspace-eknek saját, alacsonyabb limitjük lehet, így egy visszatöltés nem éheztetheti ki a chat-végpontot. Máshol külön kulcsok vagy projektek csinálják ugyanezt.
- Vidd le az offline munkát a szinkron API-ról. A batch-végpontoknak saját limitjük van, és nem fogyasztják a Messages API limitjeit.
- Olvasd a fejléceket. Az
anthropic-ratelimit-*-remainingés azx-ratelimit-remaining-*segítségével a kliens maga fékezhet, mielőtt a szolgáltató tenné. - Fokozatosan gyorsíts. A visszatöltéseket lassan indítsd, hogy elkerüld a gyorsulási limiteket, és védd a nyilvános végpontokat a visszaélésektől, lásd: denial of wallet.
Időtúllépések és hosszú kérések
Időkorlát nélkül a retry-szabály hiányos, mert egy beragadt kapcsolat sosem ad olyan hibát, amelyet újra lehetne próbálni. Az AWS azt javasolja, hogy az időkorlátot a függőség késleltetési percentiliseiből számold, ne találgass. LLM-hívásoknál ez külön keretet jelent az első tokenig eltelt időre és a teljes időtartamra. Az Anthropic a tíz percnél tovább futó kérésekhez streaminget vagy a Batches API-t javasolja, mert egyes hálózatok bontják a tétlen kapcsolatokat, és az SDK-k elutasítják azokat a nem streamelt kéréseket, amelyek várhatóan túllépnék a tízperces időkorlátot. Az AI SDK timeout objektumot fogad totalMs, stepMs és chunkMs mezőkkel, ami pontosan ezekre a keretekre illeszkedik.
Minden hibánál naplózd a request-id fejlécet. Ha az incidens a szolgáltatónál van, a supportjuk pontosan ezt az azonosítót kéri, és egy homályos „lassú volt az API” helyett követhető nyomot kapnak. Az újrapróbálásokat, a fallbackeket és a kiszolgáló modellt ugyanabba a trace-be írd, ahogy az LLM-ágensek megfigyelhetősége cikkben, így utólag látod, az incidens mekkora részét nyelte el a láncod.
Források
- 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)
Gyakori kérdések
Mi a különbség a Claude API 429-es és 529-es hibája között?
A 429 rate_limit_error azt jelenti, hogy a szervezeted elérte valamelyik saját limitjét, például a percenkénti kérés- vagy tokenkeretet, vagy a havi költéskeretet. Az 529 overloaded_error azt jelenti, hogy az API átmenetileg minden felhasználó számára túlterhelt. Mindkettőnél általában megéri késleltetve újrapróbálni, kivéve a költéskeret miatti 429-et: ennek nincs retry-after fejléce, és addig bukik, amíg a hozzáférés vissza nem áll.
Hányszor érdemes újrapróbálni egy LLM-kérést?
Két-négy próbálkozás exponenciális backoffal a szokásos tartomány, és az Anthropic SDK és a Vercel AI SDK is alapból kétszer próbál újra. A számnál fontosabb a teljes eltelt idő korlátozása, hogy egy felhasználói kérés ne lógjon perceken át, és hogy a stackben csak egyetlen réteg próbáljon újra.
Beleszámít a max_tokens a rate limitbe?
Szolgáltatótól függ. Az Anthropic szerint a max_tokens nem számít bele a kimeneti tokenlimitbe, amely csak a ténylegesen generált tokeneket számolja. Az OpenAI a max_tokens és a becsült tokenszám maximumát számolja a limit ellen, vagyis a túlméretezett max_tokens elhasználja a mozgásteret.
LiteLLM, OpenRouter vagy saját kód a fallbackhez?
Ha már futtatsz gatewayt, ott konfiguráld a fallbacket, és máshol kapcsold ki az újrapróbálást. A LiteLLM hibatípusonkénti retry-szabályokat, cooldownokat és kontextusablak-fallbacket ad, saját hosztolással; az OpenRouter egy models tömböt ad egyetlen API-kulcs mögött. Egy-két szolgáltatóhoz elég a saját kód, de a végén újraírod a cooldownokat.
Biztonságos-e más szolgáltatóra váltani strukturált kimenetnél?
Csak ha tesztelted. A szolgáltatók eltérnek abban, hogyan kényszerítik ki a JSON Schemát, milyen funkciókat támogatnak és hogyan fogalmazzák meg az elutasítást. Futtasd le ugyanazt az értékelőkészletet a fallback-modellen, és minden választ validálj a sémád ellen, akármelyik modell állította elő.