DE ▾

OpenAI-kompatible unzensierte LLM-APIuncensoredapis.com

StartseiteFehler

Fehler und Ratenlimits: Ein praktischer Leitfaden für Uncensored API

Produktionscode verbringt mehr Zeit mit Fehlerpfaden als mit dem Happy Path. Dieser Leitfaden listet jeden Statuscode auf, den Uncensored API zurückgibt, zeigt, welche sich für eine Wiederholung eignen, und gibt dir Client-seitiges Throttling für das Limit von 300 Anfragen pro Minute in Python asyncio und Node sowie eine ruhige Methode, um ein 402 gegenüber deinen Nutzern darzustellen.

Aktualisiert am

Wichtige Punkte

  1. Wiederhole nur 429, 503 und Transportfehler; behebe 400, 401, 403, 404 und 402 an der Ursache.
  2. Fehler kommen als JSON mit einem Fehlerobjekt, das code und message enthält, also verzweige zuerst nach Status, dann nach code.
  3. Ein Token-Bucket, der 5 Token pro Sekunde auffüllt, hält einen Schlüssel unter 300 Anfragen pro Minute, ohne dass der Server widerspricht.
  4. Ein 402 bedeutet, dass das Prepaid-Guthaben aufgebraucht ist oder die Testphase endete; behandle es als Produktzustand, nicht als Absturz.

Die Fehlerhülle

Jeder Fehler gibt einen JSON-Body mit einem einzelnen Top-Level-error-Objekt zurück. Zwei Felder sind wichtig: code, ein kurzer maschinenlesbarer String, und message, Text für Logs und Menschen.

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

Entscheide in dieser Reihenfolge. Schau zuerst auf den HTTP-Status, denn er sagt dir die Klasse des Problems. Nutze code nur, um Fälle zu unterscheiden, die denselben Status teilen, und parst message niemals für die Logik, da sich die Formulierung ändern kann. Zur Struktur erfolgreicher Antworten sieh dir die API-Referenz an.

Statuscodes, Ursachen und Aktionen

StatusCodeTypische UrsacheWiederholen?Aktion
400schlechte AnfrageUngültiges JSON oder Prompt-Token plus max_tokens über 100.000NeinKorrigiere den Payload; kürze den Verlauf oder setze max_tokens herab
401ungültiger SchlüsselFehlender Header, Tippfehler oder ein neu generierter SchlüsselNeinLade den Schlüssel aus deinem Secret-Store neu
402no_creditGuthaben aufgebraucht oder kostenlose Testphase abgelaufenNeinLade das Prepaid-Guthaben auf und fahre fort
403content_blockedAnfrage enthält sexuelle Inhalte mit MinderjährigenNeinSende nicht erneut; zeige deine eigene Richtlinien-Nachricht
404nicht gefundenFalscher Pfad, z. B. fehlendes /v1-PräfixNeinPrüfe die Base URL
429RatenlimitMehr als 300 Anfragen pro Minute auf einem SchlüsselJaBackoff mit Jitter; Client-Throttling hinzufügen
503upstream_busyVorübergehender KapazitätsdruckJaNach einigen Sekunden wiederholen

Die 403 verdient besondere Beachtung: Sie ist eine Policy-Entscheidung, kein vorübergehender Fehler. Das Wiederholen derselben Anfrage verschwendet Quota und liefert dieselbe Antwort. Der 400-Fall, der die meisten überrascht, ist die Kontextsumme. Ein Prompt mit 60.000 Token und max_tokens von 8.000 überläuft um 4.000 und schlägt fehl, obwohl jede Zahl für sich gültig ist.

Eine Retry-Richtlinie, die sich verhält

Verwende eine kleine, begrenzte Policy und wende sie nur dort an, wo Wiederholungen helfen.

  1. Whitelist für wiederholbare Fehler: 429, 503, Verbindungsabbrüche und Timeouts. Alles andere löst sofort aus.
  2. Exponentielle Backoff mit Jitter: warte 1, 2, 4, 8 Sekunden plus einen zufälligen Bruchteil einer Sekunde, begrenzt auf 30. Jitter verhindert, dass viele Worker im Takt wiederholen.
  3. Begrenze die Versuche auf fünf. Danach ist ein Ausfall wahrscheinlicher als Pech, und gestaute Arbeit sollte sichtbar fehlschlagen.
  4. Deaktiviere die automatischen SDK-Wiederholungen (max_retries=0), wenn du deine eigene Schleife ausführst, da sich die Versuche sonst multiplizieren und dein Throttling den Überblick verliert.
  5. Wiederhole Streams von Anfang an. Ein Stream, der in der Mitte fehlschlägt, kann nicht fortgesetzt werden; verwerfe den teilweisen Text und sende die Anfrage erneut, oder behalte ihn und fordere eine Fortsetzung an.

Bei einem 503 ist die Empfehlung, ein paar Sekunden zu warten, also starte deine erste Verzögerung bei ein oder zwei Sekunden, statt sofort zu hammern. Bei einem 429 ist die Lösung, das Tempo zu drosseln, was der nächste Abschnitt automatisiert.

Unter 300 Anfragen pro Minute bleiben

Das Limit beträgt 300 Anfragen pro Minute pro Schlüssel, was durchschnittlich fünf pro Sekunde entspricht. Ein Token-Bucket erzwingt genau das: Er hält bis zu einem kleinen Burst an Token, füllt sich mit fünf pro Sekunde auf und kostet jede Anfrage einen Token. Wenn der Bucket leer ist, schläft der Anrufer, bis ein Token verfügbar ist.

Die Python-Version nutzt asyncio. Alle zwanzig Aufgaben teilen sich einen Bucket und ein Lock, sodass parallele Coroutines ihn nicht überziehen können. Beachte, dass der Bucket jeden Versuch zählt, inklusive Wiederholungen.

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())

Führe es mit exportiertem API_KEY aus. Zwanzig Anfragen passen in den Burst plus ein paar Sekunden Auffüllung, also siehst du, wie es schnell endet; erhöhe die Anzahl auf 600, und die Ausführung dauert etwa zwei Minuten, was das Limit bei der Arbeit zeigt.

Die Node-Version, die das offizielle Paket in einem ES-Modul nutzt, verknüpft die Akquisitionen über eine Promise-Warteschlange, sodass Aufrufer in Reihenfolge bedient werden.

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]);

Dimensioniere den Burst konservativ. Ein Burst von 10 erlaubt einen kurzen Anstieg, während die dauerhafte Rate bei fünf pro Sekunde bleibt. Wenn mehrere Prozesse einen Schlüssel teilen, teile die Rate unter ihnen auf, zum Beispiel 100 pro Minute jeweils für drei Worker, oder leite den gesamten Traffic durch eine Warteschlange.

402 in deinem Produkt graceful handhaben

Ein 402 mit Code no_credit bedeutet, dass das Prepaid-Guthaben Null erreicht hat oder das kostenlose Testguthaben abgelaufen ist. Dies ist der einzige Fehler, den deine Endbenutzer niemals als Stack Trace sehen sollten, und er signalisiert meist etwas, das nur du beheben kannst: Nachladen.

Baue drei Schichten. Erstens, detect es zentral in einem Wrapper, damit keine Aufrufstelle sich merken muss. Zweitens, translate es: Der Browser oder die mobile App erhält einen neutralen Zustand wie capacity_paused und zeigt einen freundlichen Bildschirm, niemals die Upstream-Nachricht. Drittens, alert dich selbst, weil jeder Benutzer gleichzeitig betroffen ist; sende eine Benachrichtigung an den Kontoinhaber und setze ein Feature-Flag, damit die UI keine Anfragen mehr sendet.

# 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

Füge auch proaktiven Schutz hinzu. Summiere usage.total_tokens pro Anfrage in deine eigene Ledger und warne bei einem Schwellenwert deiner Wahl, damit ein Top-up erfolgt, bevor ein Benutzer blockiert wird. Denke daran, dass das Testguthaben 0,50 $ beträgt und 7 Tage dauert, also kann ein Prototyp aufgrund des Ablaufs 402 erhalten, obwohl noch Geld vorhanden ist. Die Raten zur Planung findest du auf der Preisseite.

Sobald das Guthaben wieder positiv ist, spiele die Arbeit nach, die du während der Pause gestaut hast. Dein Wrapper sollte das Pausen-Flag bei der ersten erfolgreichen Antwort löschen.

Was du protokollieren und auf Alarm stellen sollst

Gute Protokolle machen Vorfälle kurz. Logge pro Anfrage: deine eigene Request-ID, die completion id (falls vorhanden), HTTP-Status, Fehler-code, Versuchsnummer und Token-Nutzung. Logge niemals den Authorization-Header oder vollständige Prompts mit personenbezogenen Daten.

Nützliche Alerts, in Prioritätsreihenfolge: Jeder 402 (sofort), eine anhaltende Reihe von 401ern (ein regenerierter Schlüssel wurde nicht überall bereitgestellt), 429-Rate über ein paar Prozent der Aufrufe (deine Drosselung ist falsch dimensioniert) und 503-Cluster (weiter zurückweichen, eine Warteschlange in Erwägung ziehen). Ein Dashboard mit Statuszählungen pro Minute macht alle vier auf einen Blick sichtbar. Gut gehandhabt, werden diese Codes zu routinemäßigen Signalen statt zu Ausfällen.

Teste deine Fehlerpfade vor dem Start

Fehlerbehandlung, die noch nie ausgeführt wurde, ist eine Annahme. Du kannst die meisten Zweige ausführen, ohne auf einen echten Ausfall zu warten, indem du günstige Anfragen und deine eigenen Testdoubles verwendest.

  • 401: rufe GET /v1/models mit einem absichtlich falschen Schlüssel auf und bestätige, dass deine App einen Konfigurationsfehler für Operatoren anzeigt, nicht für Kunden.
  • 404: Anfrage unter /v1/chat/completion (einzeln) einmal aufrufen und bestätigen, dass die Log-Zeile den Pfad benennt, damit ein Tippfehler in der Base URL innerhalb von Minuten offensichtlich ist.
  • 400: sende max_tokens von 32.000 mit einem Prompt, der auf etwa 50.000 Token aufgefüllt ist. Die Summe überschreitet 100.000, und du kannst beobachten, wie der Validierungszweig auslöst.
  • 429 und 503: Du kannst diese nicht auf Abruf auslösen, also simuliere sie. Verpacke deinen HTTP-Client in einen Fake, der bei den ersten beiden Aufrufen den Status auslöst und danach erfolgreich ist, und stelle sicher, dass deine Schleife schläft, wiederholt und die finale Antwort zurückgibt.
  • 402: Stubs auch dies und bestätige, dass die UI den angehaltenen Bildschirm zeigt, der Alert ausgelöst wird und nichts in einer engen Schleife wiederholt.

Schreibe diese als automatisierte Tests, die in der Continuous Integration ausgeführt werden. Ein tabellengesteuerter Test mit einer Zeile pro Status aus der obigen Tabelle lässt sich in Minuten schreiben und schützt die Richtlinie vor wohlmeinenden Refactorings. Führe abschließend einen kurzen Lasttest gegen deinen eigenen Throttling-Mechanismus durch, wobei die API simuliert wird: Sende 1.000 Akquisitionen und stelle sicher, dass kein 60-Sekunden-Fenster mehr als 300 Genehmigungen zuzüglich des Bursts enthält. Das beweist die Arithmetik, bevor der echte Datenverkehr davon abhängt. Wenn du später den Agent-Schleifen-Leitfaden liest, verwende denselben Wrapper dort, da Agenten die Anfragemengen multiplizieren und diese Limits zuerst erreichen.

Fragen und Antworten

Zählt ein 429 gegen mein Guthaben?

Überprüfe die Nutzungsfiguren in deinem eigenen Ledger, anstatt anzunehmen. Wie auch immer, die Lösung ist, das Tempo zu drosseln und mit Backoff wiederzuholen.

Sollte ich eine 403 content_blocked-Antwort wiederholen?

Nein. Es ist eine Policy-Blockade und eine identische Anfrage liefert dasselbe Ergebnis. Zeige deine eigene Nachricht und fahre fort.

Mein Schlüssel gibt plötzlich 401 zurück. Was hat sich geändert?

Am häufigsten wurde der Schlüssel regeneriert, was den alten sofort ungültig macht. Kopiere den neuen Schlüssel aus deinem Konto und stelle ihn erneut bereit.

Wie viele Worker können sich einen Schlüssel teilen?

Alle teilen sich das Limit von 300 Anfragen pro Minute. Teile die Rate unter den Workern auf oder leite Aufrufe durch eine Warteschlange.

Dein Schlüssel ist nur ein Formular entfernt

Erstelle ein Konto, kopiere den Schlüssel, ändere die Base URL. Das ist die gesamte Einrichtung.