Format błędu i kolejność odczytu
Wszystkie odpowiedzi z błędem mają tę samą strukturę:
{"error":{"code":"...","message":"..."}}
Kolejność odczytu to trzy kroki:
- Kod stanu HTTP decyduje o kategorii;
error.codeokreśla konkretną przyczynę i służy do warunków w kodzie programu;error.messagejest przeznaczony do wyświetlania użytkownikowi i zapisu w logach; nie używaj go do dopasowywania ciągów znaków.
Zdefiniuj granicę: błędy, które rozwiążesz przez ponowne wysłanie (429, 503 i timeouty sieciowe) oraz błędy, które rozwiążesz poprzez zmianę (pozostałe). Mieszanie ich to najczęstsza przyczyna awarii, np. ciągłe ponawianie 402, co generuje dziesiątki niepotrzebnych zapytań na sekundę.
W logach zapisuj cztery pola: czas, kod stanu, error.code i szacowaną liczbę tokenów w promptcie. Dzięki temu od razu widać, czy wzrosła liczba konkretnych błędów, czy ogólna liczba niepowodzeń. Ustaw regułę alertu: 402 wywołuje natychmiastowe powiadomienie (usługa jest niedostępna), a 429 sprawdzaj pod kątem proporcji — pojedyncze nie wymagają interwencji, ale utrzymujący się limit oznacza problem z projektowaniem współbieżności.
Szybki podgląd kodów
| Kod stanu | error.code | Znaczenie | Ponowić? |
|---|---|---|---|
| 400 | — | Nieprawidłowe zapytanie, np. suma długości promptu i max_tokens przekracza 100k | Nie |
| 401 | — | Nieprawidłowy lub brakujący klucz | Nie |
| 402 | no_credit | Brak środków lub wygaśnięcie kredytu próbnego | Nie |
| 403 | content_blocked | Treść zablokowana | Nie |
| 404 | — | Endpoint nie istnieje | Nie |
| 429 | — | Przekroczenie limitu | Tak, z opóźnieniem |
| 503 | upstream_busy | Serwer tymczasowo zajęty | Tak, po kilku sekundach |
Znak „—” w tabeli oznacza brak stałego ciągu znaków code, który wymagałby specjalnej obsługi; wystarczy obsłużyć kod statusu.
4xx: zmień żądanie, nie ponawiaj
400 Nieprawidłowe żądanie
- Przyczyna 1: Błąd formatu JSON, często wynikający z ręcznego budowania ciągu znaków bez odpowiedniego escapowania cudzysłowów.
- Przyczyna 2: prompt + max_tokens > 100,000 tokenów. Długie konwersacje to częsta przyczyna.
- Przyczyna 3: ciało żądania > 8 MB.
- Poprawka: serializuj przez bibliotekę JSON; oszacuj tokeny przed wysłaniem; obetnij historię lub zmniejsz max_tokens; zob. Długi kontekst w praktyce.
401 Problemy z kluczem
- Brak nagłówka lub prefiksu
Bearer. - Wygeneruj ponownie klucz API, przez co stary przestaje być ważny, ale jakaś maszyna nadal go używa.
- Zmienne środowiskowe nie są dostępne w kontenerze lub zadaniach cyklicznych.
402 no_credit
Saldo się wyczerpało lub 7-dniowy kredyt próbny wygasł. Przejdź do strony konta, aby doładować przedpłacony kredyt. Zalecamy monitorowanie salda przez własny serwis, aby nie czekać na zgłoszenie błędu przez użytkownika.
403 content_blocked
Treść została zablokowana. Legalna treść dla dorosłych, fikcja i tematy kontrowersyjne nie są odrzucane, ale treści seksualne z udziałem małoletnich są zawsze blokowane, w tym w powieściach i roleplay. Przy błędzie 403 sprawdź prompt i historię pod kątem takich treści zamiast zmieniać sformułowanie i ponawiać.
404 endpoint nie istnieje
Dostępne są tylko dwa endpointy: POST /v1/chat/completions i GET /v1/models. Błąd 404 wystąpi przy błędnej ścieżce (brak /v1, zbędny slash, literówka) lub próbie użycia nieobsługiwanych interfejsów embeddings, obrazów itp.
Kolejną kategorią błędów 400, którą łatwo błędnie zdiagnozować, jest liniowy wzrost liczby tokenów w długich rozmowach. Testy w ciągu dnia przebiegają poprawnie, ale po kilkunastu turach nagle pojawia się błąd 400. Nie wynika to z niestabilności interfejsu, lecz z wyczerpania okna kontekstu. Rozwiązaniem jest ustawienie limitu historii: gdy próg zostanie przekroczony, należy odrzucać najstarsze tury lub kompresować starsze treści w postaci streszczenia. Nie oczekuj na błąd, lecz oszacuj zużycie przed wysłaniem zapytania.
Praktyczna wskazówka przy debugowaniu błędu 401: wypisz do logów pierwsze i ostatnie cztery znaki klucza API, nie wypisuj go w całości i porównaj z danymi na stronie konta. Dzięki temu zweryfikujesz, czy proces odczytuje ten klucz, którego się spodziewasz. W środowiskach kontenerowych i zadań cyklicznych najczęstszą przyczyną jest brak zmiennych środowiskowych lub odczytanie przestarzałej wartości.
429 i 503: kiedy należy zastosować retry
429 limit zapytań
Limit to 300 zapytań na minutę na klucz. Wywołują go zadania batchowe, współdzielenie jednego klucza przez wiele instancji oraz burza ponownych prób. Rozwiązanie ma dwa etapy: najpierw zastosuj limit zapytań po stronie klienta (kod poniżej), a następnie zastosuj wykładnicze ponawianie dla 429.
503 upstream_busy
Serwer jest tymczasowo obciążony. Spróbuj ponownie po kilku sekundach. Nie wysyłaj zapytań natychmiast za sobą i nie powtarzaj ich dziesięciokrotnie w ciągu sekundy, co może pogorszyć sytuację.
Przekroczenie limitu czasu sieci
Generowanie długich tekstów trwa dłużej, dlatego timeout nie powinien być zbyt krótki; w przykładzie ustawiono go na 120 sekund. Przy retry’ach po przekroczeniu limitu czasu pamiętaj, że pierwsze zapytanie mogło zostać już wykonane po stronie serwera, co może skutkować podwójnym obciążeniem kredytu. W przypadku generowania tekstu ogranicz liczbę retry’i i rozważ użycie strumieniowania, aby skrócić czas oczekiwania na pojedynczą odpowiedź.
import os
import random
import time
import requests
URL = "https://api.wushenchaapi.com/v1/chat/completions"
HEADERS = {
"Authorization": "Bearer " + os.environ["API_KEY"],
"Content-Type": "application/json",
}
RETRYABLE = {429, 503} # 只重试这两类
FATAL = {400, 401, 402, 403, 404}
class ApiError(Exception):
def __init__(self, status, code, message):
super().__init__(f"{status} {code}: {message}")
self.status, self.code = status, code
def call(payload, max_retries=5, base=1.0, cap=30.0):
for attempt in range(max_retries + 1):
try:
r = requests.post(URL, headers=HEADERS, json=payload, timeout=120)
except (requests.ConnectionError, requests.Timeout):
r = None # 网络层失败,按可重试处理
if r is not None and r.ok:
return r.json()
if r is not None:
try:
err = r.json().get("error", {})
except ValueError:
err = {}
if r.status_code in FATAL or r.status_code not in RETRYABLE:
raise ApiError(r.status_code, err.get("code"), err.get("message"))
if attempt == max_retries:
raise ApiError(r.status_code if r is not None else 0, "retry_exhausted", "重试次数用尽")
delay = min(cap, base * (2 ** attempt)) * random.uniform(0.5, 1.0)
time.sleep(delay)
if __name__ == "__main__":
out = call({"model": "uncensored", "max_tokens": 100,
"messages": [{"role": "user", "content": "回复一个字:好"}]})
print(out["choices"][0]["message"]["content"])Kluczowa zasada: wzór na opóźnienie to min(cap, base × 2^n) × współczynnik losowy. Losowe zakłócenia zapobiegają kolizji, gdy klienci ponawiają zapytania jednocześnie. Kodów z kolekcji FATAL nie ponawia się nigdy.
Ograniczanie ruchu po stronie klienta i zapytania diagnostyczne
Zamiast czekać na błąd 429 i stosować retry, lepiej ograniczyć ruch wcześniej. Poniższe narzędzie zapewnia, że liczba zapytań w ciągu minuty nie przekroczy ustalonego limitu i jest bezpieczne dla wątków:
import threading
import time
class RateGate:
"""简单的客户端限速:保证一分钟内请求数不超过 limit。"""
def __init__(self, limit=240): # 留出余量,低于 300/分钟
self.limit, self.stamps, self.lock = limit, [], threading.Lock()
def wait(self):
while True:
with self.lock:
now = time.time()
self.stamps = [t for t in self.stamps if now - t < 60]
if len(self.stamps) < self.limit:
self.stamps.append(now)
return
sleep_for = 60 - (now - self.stamps[0])
time.sleep(max(sleep_for, 0.05))Najczystszą metodą diagnostyczną jest wykonanie minimalnego zapytania za pomocą curla, niezależnie od kodu biznesowego. Opcja -i pozwala zobaczyć zarówno wiersz statusu, jak i nagłówki odpowiedzi:
curl -i https://api.wushenchaapi.com/v1/chat/completions \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"uncensored","max_tokens":20,"messages":[{"role":"user","content":"ping"}]}'Jeśli curl działa, a kod biznesowy nie, problem leży w Twoim kodzie lub środowisku (proxy, zmienne środowiskowe, kodowanie). Jeśli curl również nie działa, sprawdź klucz API, stan konta i sieć.
Dlaczego limit ustawiono na 240, a nie 300? W przypadku wdrożeń wieloinstancyjnych każda instancja stosuje własny limit, co może prowadzić do przekroczenia łącznego limitu. Dodatkowo retry’e zużywają dodatkową pulę. Zostawienie 20% marginesu bezpieczeństwa jest rozsądnym podejściem. Jeśli masz wiele instancji, podziel limit równomiernie między nie lub użyj współdzielonego licznika do koordynacji.
Lista kontrolna debugowania
- Sprawdź
error.codew ciele odpowiedzi, a nie tylko kod statusu HTTP. - 401: prefiks klucza, zmienne środowiskowe, czy klucz został wygenerowany ponownie.
- 400: czy JSON jest poprawny, czy prompt + max_tokens nie przekraczają 100,000 tokenów, czy ciało zapytania nie przekracza 8 MB.
- 402: stan konta i ważność okresu próbnego.
- 403: czy prompt i historia zawierają zablokowane treści.
- 404: czy ścieżka to /v1/chat/completions lub /v1/models.
- 429: czy wiele instancji współdzieli jeden klucz API, czy zastosowano limit po stronie klienta.
- 503: Czy zastosowano backoff retry, czy odstępy między zapytaniami wynoszą co najmniej kilka sekund.
- Timeout: czy limit czasu jest wystarczający, czy można przełączyć się na strumieniowanie.
- Jeśli wykluczono powyższe przyczyny: powtórz błąd za pomocą minimalnego zapytania curl.
Jeśli dopiero zaczynasz, przeczytaj przewodnik po integracji. Więcej parametrów znajdziesz w dokumentacji.
Jak korzystać z listy kontrolnej: przy wystąpieniu problemu eliminuj przyczyny kolejno od góry do dołu, nie przeskakuj punktów. Większość „dziwnych” błędów wynika z pierwszych czterech pozycji.
Dodatkowa rada: zapisuj wnioski z debugowania w dokumentacji zespołu. Dzięki temu przy kolejnym wystąpieniu tego samego błędu będziesz mógł go szybko zidentyfikować.