IT ▾

API LLM senza censura compatibile con OpenAIuncensoredapis.com

HomeErrori

Errori e limiti di richiesta: una guida pratica per Uncensored API

Il codice di produzione passa più tempo sui percorsi di errore che su quelli positivi. Questa guida elenca ogni codice di stato restituito da Uncensored API, indica quali meritano un nuovo tentativo e fornisce un throttling lato client per il limite di 300 richieste al minuto in Python asyncio e Node, oltre a un modo elegante per presentare un 402 ai tuoi utenti.

Aggiornato il

Punti chiave

  1. Ritenta solo 429, 503 e i guasti di trasporto; risolvi 400, 401, 403, 404 e 402 alla radice.
  2. Gli errori arrivano in JSON con un oggetto error che contiene code e message, quindi fai il branch sullo status prima, poi sul code.
  3. Un bucket a token che si riempie di 5 token al secondo mantiene una chiave sotto le 300 richieste al minuto senza respingimento dal server.
  4. Un 402 significa che il saldo prepagato è esaurito o che la prova è scaduta; trattalo come uno stato del prodotto, non come un crash.

Il pacchetto di errore

Ogni errore restituisce un corpo JSON con un unico oggetto di primo livello error. Due campi sono importanti: code, una stringa breve per le macchine, e message, testo destinato ai log e agli esseri umani.

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

Decidi in questo ordine. Guarda prima lo stato HTTP, perché ti indica la classe del problema. Usa code solo per separare casi che condividono uno stato, e non analizzare mai message per la logica, poiché la formulazione può cambiare. Per la struttura delle risposte di successo, consulta il riferimento API.

Codici di stato, cause e azioni

StatusCodeCausa tipicaRetry?Azione
400bad requestJSON non valido, o token del prompt più max_tokens superiori a 100.000NoCorreggi il payload; riduci la cronologia o abbassa max_tokens
401invalid keyHeader mancante, errore di battitura o chiave rigenerataNoRicarica la chiave dal tuo archivio di segreti
402no_creditSaldo esaurito o prova gratuita scadutaNoRicarica il credito prepagato, poi riprendi
403content_blockedLa richiesta tocca contenuti sessuali che coinvolgono minoriNoNon reinviare; mostra il tuo messaggio di policy
404not foundPercorso errato, come la mancanza del prefisso /v1NoControlla il base URL
429rate limitPiù di 300 richieste in un minuto su una chiaveSìRallenta con jitter; aggiungi il throttling lato client
503upstream_busyPressione temporanea sulla capacitàSìRiprova dopo qualche secondo

Il 403 merita enfasi: è una decisione di policy, non un errore transitorio. Ritentare la stessa richiesta spreca quota e restituirà lo stesso risultato. Il caso 400 che sorprende di più è la somma del contesto. Un prompt da 60.000 token con max_tokens di 8.000 supera il limite di 4.000 e fallisce anche se ogni numero è valido da solo.

Una policy di retry che si comporta

Usa una policy piccola e limitata e applicala solo dove il retry può aiutare.

  1. Lista bianca dei fallimenti retryable: 429, 503, reset della connessione e timeout. Tutto il resto genera un errore immediatamente.
  2. Ritardo esponenziale con oscillazione: attendi 1, 2, 4, 8 secondi più una frazione casuale di secondo, con un massimo di 30. L'oscillazione evita che molti worker riprovino in blocco.
  3. Limita i tentativi a cinque. Oltre questo limite, è più probabile un'interruzione che una sfortuna, e il lavoro in coda dovrebbe fallire in modo visibile.
  4. Disabilita i retry automatici dell'SDK (max_retries=0) quando esegui il tuo loop, altrimenti i tentativi si moltiplicano e il tuo throttling perde il conteggio.
  5. Ritenta gli stream dall'inizio. Uno stream che fallisce a metà non può essere ripreso; scarta il testo parziale e ritenta la richiesta, oppure mantienilo e chiedi una continuazione.

Per un 503, la guida è di attendere qualche secondo, quindi avvia il primo ritardo intorno a uno o due secondi invece di colpire immediatamente. Per un 429, la cura è rallentare, cosa che la sezione successiva automatizza.

Rimanere sotto le 300 richieste al minuto

Il limite è di 300 richieste al minuto per chiave, che sono cinque al secondo in media. Un bucket a token lo applica esattamente: trattiene fino a un piccolo burst di token, si riempie a cinque al secondo e fa sì che ogni richiesta costi un token. Se il bucket è vuoto, il chiamante resta in attesa finché non esiste un token.

La versione Python usa asyncio. Tutti i venti task condividono un bucket e un lock, quindi i coroutine concorrenti non possono andare in rosso. Nota che il bucket conta ogni tentativo, inclusi i retry.

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

Esegui con API_KEY esportato. Venti richieste rientrano nel burst più alcuni secondi di riempimento, quindi vedrai che termina rapidamente; aumenta il conteggio a 600 e l'esecuzione si allunga a circa due minuti, che è il limite che fa il suo lavoro.

La versione Node, che usa il pacchetto ufficiale in un modulo ES, concatena le acquisizioni tramite una coda di promise così che i chiamanti serviti in ordine.

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

Dimensiona il burst in modo conservativo. Un burst di 10 permette un breve picco mentre il tasso sostenuto rimane a cinque al secondo. Se più processi condividono una chiave, dividi il tasso tra di loro, ad esempio 100 al minuto ciascuno su tre worker, oppure instrada tutto il traffico tramite un unico code.

Gestire il 402 con grazia nel tuo prodotto

Un 402 con codice no_credit significa che il saldo prepagato ha raggiunto zero o che la prova gratuita è scaduta. È l'unico errore che i tuoi utenti finali non dovrebbero mai vedere come stack trace, e di solito segnala qualcosa che solo tu puoi risolvere: ricaricare.

Costruisci tre livelli. Primo, detectlo centralmente in un wrapper così che nessun punto di chiamata debba ricordarselo. Secondo, translatelo: il browser o l'app mobile ricevono uno stato neutro come capacity_paused e mostrano uno schermo amichevole, mai il messaggio upstream. Terzo, alertte stesso, perché ogni utente è interessato contemporaneamente; invia una notifica al proprietario dell'account e attiva un flag funzione così che l'UI smetta di inviare richieste.

# 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

Aggiungi anche una protezione proattiva. Somma usage.total_tokens per richiesta nel tuo registro e avvisa a una soglia che scegli, così un top-up avviene prima che un utente venga bloccato. Ricorda che il credito di prova è di $0,50 e dura 7 giorni, quindi un prototipo può raggiungere il 402 per sola scadenza con denaro ancora non speso. I tassi per la pianificazione sono nella pagina prezzi.

Una volta che il saldo è di nuovo positivo, ripeti il lavoro che hai messo in coda mentre era in pausa. Il tuo wrapper dovrebbe cancellare il flag di pausa al primo successo.

Cosa registrare e su cosa impostare gli alert

Buoni registri rendono gli incidenti brevi. Logga, per richiesta: il tuo ID richiesta, l'ID di completamento id quando presente, lo stato HTTP, l'errore code, il numero di tentativi e l'utilizzo dei token. Non loggare mai l'intestazione Authorization o i prompt completi che contengono dati personali.

Alert utili, in ordine di priorità: qualsiasi 402 (immediato), una serie sostenuta di 401 (una chiave rigenerata non è stata distribuita ovunque), un tasso 429 superiore a qualche percentuale di chiamate (il tuo throttling è dimensionato male) e cluster 503 (riduci il carico, considera un code). Un dashboard dei conteggi di stato al minuto rende tutti e quattro visibili a colpo d'occhio. Gestiti bene, questi codici diventano segnali di routine invece che interruzioni.

Testare i tuoi percorsi di errore prima del lancio

La gestione degli errori che non è mai stata eseguita è un'ipotesi. Puoi esercitare la maggior parte dei rami senza aspettare un'interruzione reale, usando richieste economiche e i tuoi falsi di test.

  • 401: chiama GET /v1/models con una chiave deliberatamente errata e conferma che la tua app mostra un errore di configurazione agli operatori, non ai clienti.
  • 404: Richiesta /v1/chat/completion (singolare) una volta e conferma che la riga di log nomina il percorso, così un errore di battitura in un base URL è ovvio in pochi minuti.
  • 400: invia max_tokens di 32.000 con un prompt riempito a circa 50.000 token. La somma supera 100.000 e puoi osservare il percorso di validazione attivarsi.
  • 429 e 503: non puoi generarli su richiesta, quindi simulali. Avvolgi il tuo client HTTP in un fake che solleva lo stato per le prime due chiamate, poi ha successo, e afferma che il tuo loop dorme, ritenta e restituisce il risultato finale.
  • 402: simula anche quello, e conferma che l'UI mostra lo schermo in pausa, l'alert scatta e nulla ritenta in un loop stretto.

Scrivi questi come test automatizzati che vengono eseguiti nell'integrazione continua. Un test guidato dalla tabella con una riga per stato dalla tabella sopra richiede minuti per essere scritto e protegge la policy da refactoring ben intenzionati. Infine, esegui un breve test di carico contro il tuo throttling con l'API simulata: lancia 1.000 acquisizioni e afferma che nessuna finestra di 60 secondi contiene più di 300 concessioni più il burst. Questo dimostra l'aritmetica prima che il traffico reale ne dipenda. Quando in seguito leggi la guida al loop dell'agente, riutilizza lo stesso wrapper lì, poiché gli agenti moltiplicano i conteggi delle richieste e colpiscono questi limiti per primi.

Domande e risposte

Un 429 conta sul mio saldo?

Controlla le cifre di utilizzo nel tuo ledger piuttosto che assumere. In ogni caso, la soluzione è rallentare e riprovare con backoff.

Devo riprovare una risposta 403 content_blocked?

No. È un blocco di policy e una richiesta identica restituisce lo stesso risultato. Mostra il tuo messaggio e vai avanti.

La mia chiave improvvisamente restituisce 401. Cosa è cambiato?

La maggior parte delle volte la chiave è stata rigenerata, il che invalida quella vecchia immediatamente. Copia la nuova chiave dal tuo account e ridistribuiscila.

Quanti worker possono condividere una chiave?

Tutti condividono il limite di 300 richieste al minuto. Dividi il tasso tra i worker o instrada le chiamate attraverso una coda.

La tua chiave è a un modulo di distanza

Crea un account, copia la chiave, cambia il base URL. È tutta la configurazione.