- Retry z wykładniczym backoffem – powtarza nieudany request do tego samego providera z rosnącymi opóźnieniami.
- Circuit breaker – natychmiast odrzuca wywołania do providera, który wielokrotnie zawodził, a po upływie timeoutu wysyła próbny request.
scope: model – per model. W razie błędu nie przełączają na inny model ani
providera. Failover między modelami opisuje strona
Failover.
Ta strona opisuje dostępne ustawienia. Co robi GoModel, gdy provider zmienia kształt
odpowiedzi, odpowiada 200 bez niczego użytecznego albo pada w trakcie streamu, opisuje
strona Tryby awarii providerów.
Wartości domyślne
Wartości domyślne są dobrane tak, aby były bezpieczne dla większości wdrożeń. Nadpisuj tylko to, czego potrzebujesz.
Aby wyłączyć retry, ustaw
max_retries: 0 – każdy request dostaje dokładnie jedną
próbę. Aby wyłączyć circuit breaker, ustaw circuit_breaker.enabled: false
(lub CIRCUIT_BREAKER_ENABLED=false); progi są zachowywane, ale nigdy nie są
sprawdzane, więc ponowne włączenie przywraca Twoje dostrojenie. Ustawienie
failure_threshold: 0 również wyłącza breaker. Oba przełączniki działają globalnie
lub per provider, a ustawione per provider enabled: true ponownie włącza breaker dla
tego providera, gdy jest on wyłączony globalnie.
Wyłącz breaker, gdy coś przed providerem już ogranicza obciążenie
(load balancer po stronie providera, service mesh) albo gdy krótka seria odpowiedzi
5xx nigdy nie może wstrzymać ruchu do tego providera. W pozostałych przypadkach
zostaw go włączonego: to on sprawia, że martwy upstream nie blokuje każdego requestu
na cały timeout.
Co liczy się jako błąd
Retry są uruchamiane przy błędach transportu (odmowa połączenia, resety, błędy DNS) oraz domyślnie przy odpowiedziach429, 502, 503, 504, 522 i 524. Inne statusy –
w tym 500 – są zwracane do wywołującego bez ponawiania. Poniższe ścieżki
ponawiają rzadziej, niż sugeruje tabela:
- Lokalne timeouty klienta HTTP zwracają błąd od razu, bez ponawiania. Wygaśnięcie ogólnego kontekstu requestu również uniemożliwia kolejne próby.
- Streamingowe requesty nigdy nie są ponawiane po wysłaniu, ponieważ część danych mogła już zostać przekazana.
-
Passthrough requesty są ponawiane tylko wtedy, gdy można je bezpiecznie powtórzyć:
GET,HEAD,OPTIONS,PUTlub dowolny request z nagłówkiemIdempotency-Key.
Idempotency-Key, GoModel przekazuje go bez zmian w wywołaniu providera,
które wykonuje dla tego requestu, i w każdym jego ponowieniu, dzięki czemu provider
deduplikujący po kluczu rozpozna powtórzony request. Próba failoveru do innego celu wysyła
inne body requestu, więc idzie bez klucza – podobnie jak wewnętrzne wywołania
gatewaya, na przykład sprawdzenia guardrails. Klucze muszą być widocznymi znakami ASCII o długości
co najwyżej 255 znaków; każda inna wartość jest ignorowana.
Błędy ukryte za HTTP 200 są demaskowane, zanim zadziała którykolwiek z tych mechanizmów.
Niektórzy providerzy (najbardziej znany jest OpenRouter) odpowiadają 200 OK z samym
body JSON {"error": ...}. GoModel wykrywa takie payloady i obsługuje je jako
błąd, którym faktycznie są: status osadzony w error.code jest zachowywany
(więc ukryte 429 zachowuje się jak prawdziwe), a wszystko inne mapowane jest na 502.
Zmapowany status steruje następnie retry, circuit breakerem i failoverem
dokładnie tak jak prawdziwy status błędu, a odpowiedź nigdy nie trafia do cache’u jako
sukces. Wyjątkiem są endpointy passthrough: przekazują one odpowiedź providera
bajt w bajt.
Streamy, które kończą się przedwcześnie, kończą się jawnym błędem zamiast cichego
EOF. Gdy stream providera zamyka się lub zawodzi przed zdarzeniem końcowym
(data: [DONE] lub finish_reason dla chat completions, końcowe zdarzenie
response.* dla /v1/responses, message_stop dla /v1/messages),
GoModel przekazuje wszystko, co otrzymał do tej pory, a następnie wysyła jedno zdarzenie błędu
w formacie danego endpointu:
- chat completions:
data: {"error":{"code":"stream_incomplete",...}} /v1/responses: zdarzenieerrorz"code":"stream_incomplete"/v1/messages: zdarzenie Anthropicerrorzerror.typeustawionym naapi_error
stream_error.
Circuit breaker liczy jako błędy błędy transportu oraz domyślnie odpowiedzi 429
i wszystkie 5xx. Jedna wyczerpana sekwencja retry liczy się jako jeden
błąd; pojedyncze ponowienia HTTP nie zwiększają licznika breakera. Udana
sekwencja zapisuje sukces. Anulowanie przez klienta jest ignorowane; lokalne timeouty
klienta liczą się jako błędy.
Ustawiaj retry.retry_on_statuses i circuit_breaker.failure_on_statuses
niezależnie. Oba przyjmują dokładne kody i klasy, takie jak 5xx. Pominięte
listy używają wartości domyślnych; jawne listy je zastępują; [] wyłącza wyzwalacze
oparte na statusach, zachowując obsługę błędów transportu. Na przykład
failure_on_statuses: [5xx] wyklucza rate limity z błędów breakera,
także podczas prób odzyskiwania. Nieprawidłowe kody lub scope’y powodują błąd
ładowania konfiguracji.
Utrzymujące się opóźnienia również mogą otworzyć obwód. Ustaw
circuit_breaker.slow_call_threshold (lub
CIRCUIT_BREAKER_SLOW_CALL_THRESHOLD), a każde wywołanie, na które provider odpowie
wolniej, liczy się jako błąd breakera, dokładnie tak jak 5xx. Wolna
odpowiedź nadal jest zwracana wywołującemu; po failure_threshold kolejnych
wolnych lub nieudanych wywołaniach obwód się otwiera, kolejne requesty od razu kończą się
błędem 503 kwalifikującym się do failoveru, a wolna próba odzyskiwania ponownie go otwiera. Zobacz
Failover od wolnego providera.
Pomiar czasu obejmuje całe wywołanie razem z ponowieniami. Dla streamingowych requestów
kończy się na pierwszym zdarzeniu z danymi, więc próg ogranicza czas do pierwszego tokenu,
a nie długość streamu.
Domyślnie jest wyłączony, ponieważ normalne opóźnienie zależy od obciążenia:
niestreamingowe wywołanie modelu reasoningowego może zasadnie trwać kilka minut. Wybierz wartość znacznie
powyżej Twojego najwolniejszego zdrowego requestu i nadpisuj ją per provider, gdy
obciążenia się różnią. Próg nigdy nie anuluje requestu – to zadanie
HTTP_TIMEOUT, a wywołanie, które go przekroczy, już liczy się jako błąd.
Wartość domyślna obejmuje teraz błędy breakera dla 429. Wdrożenia, które wcześniej
polegały na tym, że rate limity pozostawiają breaker zamknięty, mogą ustawić
failure_on_statuses: [5xx], aby zachować to zachowanie.
Gdy obwód jest otwarty, requesty od razu kończą się błędem 503 z komunikatem
circuit breaker is open - provider <name> temporarily unavailable, gdzie
<name> to skonfigurowana nazwa providera (openai-eu, a nie jego typ). Po
upływie timeout przepuszczany jest jeden próbny request, a równoległe
requesty nadal od razu kończą się błędem; success_threshold kolejnych udanych
prób zamyka obwód, a błąd pasujący do polityki breakera ponownie go otwiera. Próby wykonują tylko jedną
próbę HTTP, bez ponowień.
Circuit breaker działa w pamięci i per proces gatewaya: każdy provider
dostaje domyślnie własny breaker. Z scope: model każdy model w ramach
instancji providera dostaje niezależny breaker; requesty bez modelu,
takie jak wykrywanie modeli, używają osobnego breakera providera. Uploady audio multipart
używają jawnie podanych nazw modeli.
Każdy provider przechowuje co najwyżej 1 024 breakery modeli. Bezczynne zamknięte wpisy wygasają
po 10 minutach przy wyszukiwaniu nowego modelu; najstarszy bezczynny zamknięty wpis
jest usuwany wcześniej, gdy limit zostanie osiągnięty. Aktywne i otwarte breakery są
zachowywane. Jeśli każde miejsce jest chronione, nowy model otrzymuje kwalifikujący się do failoveru
błąd 503, dopóki miejsce się nie zwolni.
Stan jest resetowany przy restarcie
i nic nie jest współdzielone między replikami.
Zmienne środowiskowe
Ustawiają globalne wartości domyślne, które obowiązują każdego providera, chyba że zostaną nadpisane w YAML.Retry
Circuit Breaker
YAML
Te same pola są dostępne w globalnym blokuresilience: i można je
nadpisać per provider:
resilience: providera.
Wszystko inne jest dziedziczone z sekcji globalnej, która z kolei
dziedziczy z wbudowanych wartości domyślnych.
Dostrajanie per provider musi pochodzić z YAML. Zmienne środowiskowe ustawiają
tylko globalne wartości domyślne –
RETRY_MAX_RETRIES nie może dotyczyć pojedynczego providera.
Zobacz pułapki config.yaml.Przykład krok po kroku
Dla powyższego YAML efektywne ustawienia per provider wyglądają tak:anthropic, ollama i vllm dziedziczą każde pole, którego jawnie nie
nadpisały. Przy wyłączonym breakerze vllm nie raportuje też stanu breakera
w dashboardzie ani w metryce gomodel_circuit_breaker_state.
Circuit breaker a stan providerów w dashboardzie
Status providera w dashboardzie łączy dwa niezależne sygnały:- Wykrywanie modeli – czy ostatnio udało się pobrać listę modeli providera (szczegóły poniżej).
- Stan requestów – 10-minutowe przesuwne okno rzeczywistych wyników requestów per provider i model, łącznie z bieżącym stanem circuit breakera.
Circuit Open, a półotwarty breaker pokazuje Recovering.
Z scope: model wyświetlany stan breakera na poziomie providera i metryka odzwierciedlają
breaker ostatnio zakończonego requestu, a nie agregat
wszystkich breakerów modeli. Model, którego ostatnie requesty ciągle kończą się błędem (co najmniej 3 błędy stanowiące
połowę lub więcej jego requestów w oknie), oznacza providera jako Degraded, nawet
gdy wykrywanie modeli nadal się udaje – to wyłapuje upstreamy, które poprawnie wypisują
modele, ale zawodzą przy prawdziwych wywołaniach, np. z błędnie raportowanymi błędami 4xx, które
celowo nigdy nie wyzwalają breakera.
Sygnały stanu requestów mogą tylko pogorszyć status oparty na wykrywaniu modeli, nigdy go
nie poprawiają, a śledzenie odbywa się w pamięci per proces gatewaya. Breaker
i tak sam się odblokowuje w ciągu timeout (domyślnie 30s), gdy provider
wróci; gdy metryki są włączone, jego
stan jest też eksportowany jako gauge gomodel_circuit_breaker_state
(0 = zamknięty, 1 = półotwarty, 2 = otwarty).
Wykrywanie modeli jest ponawiane:
- przy starcie,
- przy każdym odświeżeniu rejestru modeli, sterowanym przez
CACHE_REFRESH_INTERVAL(w sekundach, domyślnie3600– co godzinę), - w szybkiej pętli ponownego sprawdzania, która ponownie odpytuje tylko providery, których ostatnie
odświeżenie się nie powiodło, sterowanej przez
PROVIDER_RECHECK_INTERVAL(cache.model.recheck_intervalwconfig.yaml; w sekundach, domyślnie60;0wyłącza), oraz - na żądanie, gdy request prosi o model kwalifikowany providerem
(
provider/model), którego brakuje w rejestrze.
Co się dzieje, gdy provider nie działa
Gdy odświeżenie providera się nie powiedzie, jego wcześniej wykryte modele są oznaczane jako nieaktualne, a dashboard pokazuje providera jako Offline:- Listy modeli (
GET /v1/modelsi lista modeli w dashboardzie) ukrywają modele providera do czasu jego powrotu, więc klientom nie są oferowane modele, których obecnie nie da się obsłużyć. - Bezpośrednie requesty do jego modeli (
provider/model) nadal są rozwiązywane i wysyłane do providera, więc wywołujący dostają rzetelne502/503(a łańcuch failoveru przesłaniającego modelu wirtualnego może zadziałać) zamiast mylącego „model not found”. - Przekierowania modeli wirtualnych pomijają cele tego providera, więc przekierowanie z load balancingiem nadal działa przez swoje zdrowe cele.
PROVIDER_RECHECK_INTERVAL sekund, aktualizując „Last checked” i przywracając
normalny routing zwykle w ciągu minuty od powrotu providera. Provider,
który nie działał już przy starcie (nic jeszcze nie wykryto), jest pokazywany jako
Unhealthy do pierwszego udanego pobrania.
Failover a odporność na awarie
Retry pozostają przy wybranym celu; circuit breaker śledzi skonfigurowany scope providera lub modelu. Jeśli chcesz też, aby GoModel próbował innego modelu lub providera, gdy główny ciągle zawodzi, daj modelowi model wirtualny z więcej niż jednym celem – pozostałe cele tworzą jego łańcuch failoveru. Zobacz Failover.Ponawianie timeoutów Cloudflare, a potem zmiana modelu
Dla niestreamingowych tłumaczonych requestów połącz retry z modelem wirtualnym z failoverem. Scal poniższe ustawienia z istniejącą konfiguracją, używając nazw skonfigurowanych providerów i modeli:model: resilient-chat. Dopóki breaker model1 jest zamknięty, odpowiedź 524
wywołuje do dwóch ponowień do model1. Jeśli wszystkie trzy próby
zawiodą, GoModel próbuje model2 z jego własnym budżetem ponowień. Otwarty breaker model1
pomija jego wywołania upstreamu bez blokowania model2. Przy domyślnym
scope: provider oba modele współdzielą jeden breaker.
Deadline wywołującego musi uwzględniać czas na próby i backoff. Streamingowe
requesty nie dostają tej sekwencji ponowień; częściowo dostarczonej odpowiedzi
nie da się przezroczyście zrestartować.
Failover od wolnego providera
Provider, który nadal odpowiada, ale zajmuje mu to minuty, nigdy nie generuje błędów, które otwierają obwód.slow_call_threshold sprawia, że opóźnienie się liczy:
opencode/deepseek-v3 potrwają dłużej niż 60
sekund (lub zawiodą), jego obwód się otwiera, a requesty resilient-chat trafiają od razu
do openai/gpt-4o-mini bez czekania. Co timeout (domyślnie 30s) jeden
próbny request ponownie próbuje głównego celu; ruch wraca, gdy próby odpowiadają
w ramach progu.
Szybka odpowiedź pomiędzy nimi resetuje licznik, więc sporadyczne wolne wywołania nigdy
nie otwierają obwodu. Preferuj tu scope: model: przy domyślnym
scope: provider szybkie modele tego samego providera ciągle resetują licznik
dla wolnego.