PL ▾

LLM API bez cenzury zgodne z OpenAIuncensoredapis.com

Strona głównaBłędy

Błędy i limity: Praktyczna instrukcja dla Uncensored API

W kodzie produkcyjnym ścieżki błędów zajmują więcej czasu niż ścieżka sukcesu. Ten poradnik wymienia każdy kod statusu zwracany przez Uncensored API, wskazuje, które wymagają ponownej próby, i dostarcza mechanizmów ograniczania szybkości zapytań po stronie klienta dla limitu 300 zapytań na minutę w Python asyncio i Node, a także sposób na eleganckie wyświetlenie błędu 402 Twoim użytkownikom.

Zaktualizowano

Kluczowe punkty

  1. Ponawiaj tylko błędy 429, 503 oraz awarie transportu; napraw błędy 400, 401, 403, 404 i 402 w ich źródle.
  2. Błędy przychodzą jako JSON z obiektem błędu zawierającym code i message, więc rozgałęziaj się najpierw po statusie, potem po code.
  3. Wiadro tokenów napełniające się 5 tokenami na sekundę utrzymuje jeden klucz poniżej 300 zapytań na minutę bez odrzucania po stronie serwera.
  4. 402 oznacza, że przedpłacony balans się skończył lub próba się skończyła; traktuj to jako stan produktu, a nie crash.

Envelop błędu

Każdy błąd zwraca ciało JSON z jednym obiektem error na najwyższym poziomie. Istotne są dwa pola: code, krótki ciąg znaków dla maszyn, oraz message, tekst przeznaczony dla logów i ludzi.

{
  "error": {
    "code": "no_credit",
    "message": "Balance used up or trial expired."
  }
}

Decyduj w tej kolejności. Najpierw sprawdź status HTTP, ponieważ określa on klasę problemu. Użyj code tylko do rozróżnienia przypadków, które dzielą ten sam status, i nigdy nie analizuj message pod kątem logiki, ponieważ treść może się zmienić. Aby poznać strukturę poprawnych odpowiedzi, zobacz odniesienie do API.

Kody statusu, przyczyny i działania

StatusKodTypowa przyczynaPonowić?Działanie
400niepoprawne żądanieNiepoprawny format JSON lub suma tokenów promptu plus max_tokens przekraczająca 100 000NieNapraw ładunek; skróć historię lub zmniejsz max_tokens
401nieprawidłowy kluczBrakujący nagłówek, literówka lub klucz, który został wygenerowany ponownieNiePobierz ponownie klucz ze swojego magazynu sekretów
402no_creditSaldo wyczerpane lub darmowy kredyt próbny wygasłNieDoładuj przedpłacony balans, a następnie wznów
403content_blockedZapytanie dotyczy treści seksualnych z udziałem małoletnichNieNie wysyłaj ponownie; pokaż własną wiadomość polityki
404nie znalezionoNieprawidłowa ścieżka, np. brakujący prefiks /v1NieSprawdź Base URL
429limit zapytańPowyżej 300 zapytań w ciągu minuty dla jednego kluczaTakZastosuj wycofanie się z losowym opóźnieniem; dodaj ograniczanie szybkości zapytań po stronie klienta
503upstream_busyTymczasowe obciążenie zasobówTakPonów próbę po kilku sekundach

Błąd 403 zasługuje na podkreślenie: jest to decyzja polityki, a nie błąd przejściowy. Ponawianie identycznego zapytania marnuje limit i zwróci tę samą odpowiedź. Przypadek 400, który najbardziej zaskakuje, to przekroczenie sumy kontekstu. Prompt o długości 60 000 tokenów z max_tokens równym 8000 przekracza limit o 4000 tokenów i kończy się niepowodzeniem, mimo że każda z liczb jest poprawna sama w sobie.

Polityka ponownych prób, która działa poprawnie

Użyj małej, ograniczonej polityki i stosuj ją tylko tam, gdzie ponowienie może przynieść skutek.

  1. Błędy dopuszczające ponowną próbę: 429, 503, resety połączenia i przekroczenia limitu czasu. Wszystkie inne błędy zgłaszane są natychmiast.
  2. Wycofanie się z wykładniczym wzrostem czasu oczekiwania z losowym opóźnieniem: poczekaj 1, 2, 4, 8 sekund plus losowy ułamek sekundy, z maksymalnym limitem 30 sekund. Losowe opóźnienie zapobiega równoczesnemu ponawianiu prób przez wiele wątków.
  3. Ogranicz liczbę prób do pięciu. Po przekroczeniu tego limitu awaria jest bardziej prawdopodobna niż pech, a oczekujące zadania powinny zostać jawnie odrzucone.
  4. Wyłącz automatyczne ponawianie prób w SDK (max_retries=0), gdy uruchamiasz własną pętlę, w przeciwnym razie liczba prób się mnoży, a ogranicznik szybkości traci licznik.
  5. Ponawiaj strumienie od początku. Strumień, który ulegnie awarii w trakcie, nie może być wznowiony; odrzuć częściowy tekst i ponów zapytanie, lub zachowaj go i poproś o kontynuację.

W przypadku błędu 503 zaleca się odczekanie kilku sekund, więc rozpocznij pierwsze opóźnienie od jednej lub dwóch sekund, zamiast natychmiast bombardować serwer. W przypadku błędu 429 rozwiązaniem jest zwolnienie tempa, co automatyzuje następna sekcja.

Pozostawanie poniżej 300 zapytań na minutę

Limit wynosi 300 zapytań na minutę na klucz, co daje średnio pięć na sekundę. Wiadro tokenów egzekwuje dokładnie ten limit: przechowuje do pewnej liczby tokenów dla krótkiego szczytu, napełnia się pięcioma tokenami na sekundę i pobiera jeden token za każde zapytanie. Jeśli wiadro jest puste, wywołujący usypia się, aż pojawi się token.

Wersja Pythona wykorzystuje asyncio. Wszystkie dwadzieścia zadań dzieli jedno wiadro i blokadę, więc współbieżne korutyny nie mogą go nadużyć. Pamiętaj, że wiadro liczy każdą próbę, w tym ponowienia.

import asyncio, os, random, time
from openai import AsyncOpenAI, APIStatusError, APIConnectionError

class TokenBucket:
    """Allows `rate` acquisitions per `per` seconds, with a small burst."""
    def __init__(self, rate=300, per=60.0, burst=10):
        self.fill = rate / per          # tokens added per second (5.0)
        self.cap = burst
        self.tokens = burst
        self.stamp = time.monotonic()
        self.lock = asyncio.Lock()

    async def acquire(self):
        async with self.lock:
            while True:
                now = time.monotonic()
                self.tokens = min(self.cap, self.tokens + (now - self.stamp) * self.fill)
                self.stamp = now
                if self.tokens >= 1:
                    self.tokens -= 1
                    return
                await asyncio.sleep((1 - self.tokens) / self.fill)

client = AsyncOpenAI(base_url="https://api.uncensoredapis.com/v1",
                     api_key=os.environ["API_KEY"], max_retries=0)
bucket = TokenBucket()

RETRYABLE = {429, 503}

async def ask(prompt, attempts=5):
    for n in range(attempts):
        await bucket.acquire()
        try:
            r = await client.chat.completions.create(
                model="uncensored",
                messages=[{"role": "user", "content": prompt}],
                max_tokens=200,
            )
            return r.choices[0].message.content
        except APIStatusError as e:
            if e.status_code not in RETRYABLE or n == attempts - 1:
                raise
        except APIConnectionError:
            if n == attempts - 1:
                raise
        await asyncio.sleep(min(30, 2 ** n) + random.uniform(0, 1))

async def main():
    prompts = [f"Give one-line fact number {i} about tides." for i in range(1, 21)]
    results = await asyncio.gather(*(ask(p) for p in prompts))
    print(len(results), "answers; first:", results[0])

asyncio.run(main())

Uruchom go z wyeksportowanym API_KEY. Dwadzieścia zapytań mieści się w puli plus kilka sekund odświeżania, więc zobaczysz szybkie zakończenie; zwiększ liczbę do 600, a uruchomienie rozciągnie się na około dwie minuty, co jest limitem wykonującym swoją pracę.

Wersja Node.js, korzystająca z oficjalnego pakietu w module ES, łączy pozyskiwanie tokenów poprzez kolejkę promise, dzięki czemu wywołujący są obsługiwani w kolejności.

import OpenAI from "openai";

class TokenBucket {
  constructor(rate = 300, perMs = 60000, burst = 10) {
    this.fillPerMs = rate / perMs;
    this.cap = burst;
    this.tokens = burst;
    this.stamp = Date.now();
    this.queue = Promise.resolve();
  }
  acquire() {
    // Serialize callers so the bucket math never races.
    this.queue = this.queue.then(async () => {
      for (;;) {
        const now = Date.now();
        this.tokens = Math.min(this.cap, this.tokens + (now - this.stamp) * this.fillPerMs);
        this.stamp = now;
        if (this.tokens >= 1) { this.tokens -= 1; return; }
        await new Promise((r) => setTimeout(r, Math.ceil((1 - this.tokens) / this.fillPerMs)));
      }
    });
    return this.queue;
  }
}

const client = new OpenAI({
  baseURL: "https://api.uncensoredapis.com/v1",
  apiKey: process.env.API_KEY,
  maxRetries: 0,
});
const bucket = new TokenBucket();

async function ask(prompt, attempts = 5) {
  for (let n = 0; n < attempts; n++) {
    await bucket.acquire();
    try {
      const r = await client.chat.completions.create({
        model: "uncensored",
        messages: [{ role: "user", content: prompt }],
        max_tokens: 200,
      });
      return r.choices[0].message.content;
    } catch (err) {
      const retryable = err.status === 429 || err.status === 503 || err.status === undefined;
      if (!retryable || n === attempts - 1) throw err;
      await new Promise((r) => setTimeout(r, Math.min(30000, 2 ** n * 1000) + Math.random() * 1000));
    }
  }
}

const prompts = Array.from({ length: 20 }, (_, i) => `Give one-line fact number ${i + 1} about tides.`);
const answers = await Promise.all(prompts.map((p) => ask(p)));
console.log(answers.length, "answers; first:", answers[0]);

Dostosuj limit szczytowy ostrożnie. Szczyt o wartości 10 pozwala na krótką spike, podczas gdy utrzymywane tempo pozostaje na poziomie pięciu na sekundę. Jeśli kilka procesów dzieli jeden klucz, podziel tempo między nie, na przykład 100 na minutę każdy dla trzech wątków, lub przekaż cały ruch przez jedną kolejkę.

Eleganckie obsłużenie błędu 402 w Twojej aplikacji

Błąd 402 z kodem no_credit oznacza, że przedpłacony balans osiągnął zero lub darmowy kredyt próbny wygasł. To jedyny błąd, którego użytkownicy końcowi nie powinni widzieć jako stack trace, i zwykle sygnalizuje coś, co możesz naprawić tylko Ty: doładowanie.

Zbuduj trzy warstwy. Po pierwsze, detect to centralnie w jednym wrapperze, aby żadna strona wywołania nie musiała pamiętać. Po drugie, translate to: przeglądarka lub aplikacja mobilna otrzymuje neutralny stan taki jak capacity_paused i wyświetla przyjazny ekran, nigdy komunikat od serwera. Po trzecie, alert siebie, ponieważ każdy użytkownik jest dotknięty jednocześnie; wyślij powiadomienie do właściciela konta i przełącz flagę funkcji, aby interfejs UI przestał wysyłać zapytania.

# server side: translate the upstream 402 into a product state
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from openai import AsyncOpenAI, APIStatusError
import os

app = FastAPI()
client = AsyncOpenAI(base_url="https://api.uncensoredapis.com/v1", api_key=os.environ["API_KEY"])

@app.post("/api/reply")
async def reply(body: dict):
    try:
        r = await client.chat.completions.create(
            model="uncensored", messages=body["messages"], max_tokens=400)
        return {"text": r.choices[0].message.content}
    except APIStatusError as e:
        if e.status_code == 402:
            # Tell the client app to show a "temporarily unavailable" screen,
            # and page yourself to top up the prepaid balance.
            return JSONResponse({"state": "capacity_paused"}, status_code=503)
        if e.status_code == 429 or e.status_code == 503:
            return JSONResponse({"state": "busy_retry"}, status_code=503)
        raise

Dodaj również ochronę proaktywną. Sumuj usage.total_tokens dla każdego zapytania w swoim własnym rejestrze i ostrzegaj przy wybranym przez Ciebie progu, aby doładowanie nastąpiło zanim użytkownik zostanie zablokowany. Pamiętaj, że kredyt próbny wynosi $0,50 i trwa 7 dni, więc prototyp może osiągnąć błąd 402 tylko z powodu wygaśnięcia, mimo że pieniądze nie zostały jeszcze wydane. Stawki do planowania znajdziesz na stronie z cennikiem.

Gdy saldo ponownie stanie się dodatnie, odtwórz zadania, które zablokowałeś podczas pauzy. Twój wrapper powinien usunąć flagę pauzy przy pierwszym sukcesie.

Co logować i na co reagować alertami

Dobre zapisy skracają czas trwania incydentów. Loguj dla każdego zapytania: własne ID zapytania, completion id (jeśli występuje), status HTTP, błąd code, numer próby oraz zużycie tokenów. Nigdy nie loguj nagłówka Authorization ani pełnych promptów zawierających dane osobowe.

Użyteczne alerty, w kolejności priorytetu: dowolny błąd 402 (natychmiastowy), ciągłe występowanie błędów 401 (odnowiony klucz nie został wdrożony wszędzie), limit 429 powyżej kilku procent wywołań (Twój ogranicznik jest źle skonfigurowany) oraz klastry 503 (zastosuj szersze wycofanie się, rozważ kolejkę). Dashboard z liczbą statusów na minutę sprawia, że wszystkie cztery są widoczne na pierwszy rzut oka. Dobrze obsłużone, stają się one rutynowymi sygnałami zamiast awarii.

Testowanie ścieżek błędów przed uruchomieniem

Obsługa błędów, która nigdy nie została uruchomiona, to zgadywanie. Możesz przetestować większość gałęzi bez czekania na prawdziwą awarię, używając tanich zapytań i własnych obiektów testowych.

  • 401: wywołaj GET /v1/models z celowo błędnym kluczem i potwierdź, że Twoja aplikacja pokazuje błąd konfiguracji operatorom, a nie klientom.
  • 404: wyślij zapytanie /v1/chat/completion (pojedyncze) raz i potwierdź, że linia logu wskazuje ścieżkę, aby literówka w Base URL była widoczna w ciągu minut.
  • 400: wyślij max_tokens równy 32 000 z promptem wypełnionym do około 50 000 tokenów. Suma przekracza 100 000, a Ty możesz obserwować uruchomienie ścieżki walidacji.
  • 429 i 503: nie możesz ich wywołać na żądanie, więc je zmockuj. Owiń swój klient HTTP mockiem, który zwraca ten status dla dwóch pierwszych wywołań, a następnie zwraca sukces, i sprawdź, czy Twoja pętla wykonuje sleep, retry i zwraca ostateczną odpowiedź.
  • 402: zmockuj to również i potwierdź, że UI pokazuje ekran pauzy, alert się pojawia, a nic nie retryuje w ścisłej pętli.

Napisz to jako automatyczne testy uruchamiane w ciągłej integracji. Test sterowany tabelą z jednym wierszem dla każdego statusu z powyższej tabeli zajmuje kilka minut na napisanie i chroni politykę przed dobrze myślącymi refaktoryzacjami. Na koniec uruchom krótki test obciążenia przeciwko swojemu ogranicznikowi ze stubowanym API: wyślij 1000 żądań i sprawdź, czy żadne okno 60-sekundowe nie zawiera więcej niż 300 przyznań plus limit szczytowy. To dowodzi arytmetyki zanim prawdziwy ruch na niej zależy. Kiedy później przeczytasz przewodnik po pętli agenta, użyj tego samego wrappera tam, ponieważ agenci mnożą liczbę zapytań i osiągają te limity jako pierwsi.

Pytania i odpowiedzi

Czy 429 liczy się do mojego salda?

Sprawdź figury użycia w swojej własnej księdze, zamiast zakładać. W każdym razie rozwiązaniem jest zwolnienie tempa i ponowienie z opóźnieniem.

Czy powinienem ponowić próbę odpowiedzi 403 content_blocked?

Nie. To blokada polityki, a identyczne zapytanie zwróci ten sam wynik. Wyświetl własną wiadomość i przejdź dalej.

Mój klucz nagle zwraca 401. Co się zmieniło?

Najczęściej klucz został odnowiony, co unieważnia stary natychmiast. Skopiuj nowy klucz ze swojego konta i wdróż go ponownie.

Ile wątków może dzielić jeden klucz?

Wszystkie one dzielą limit 300 zapytań na minutę. Podziel tempo między wątki lub przekaż wywołania przez jedną kolejkę.

Twój klucz jest o jeden formularz stąd

Utwórz konto, skopiuj klucz, zmień bazowy URL. To cała konfiguracja.