ES ▾

API LLM sin censura compatible con OpenAIuncensoredapis.com

InicioErrores

Errores y límites de peticiones: un manual práctico para Uncensored API

El código de producción pasa más tiempo en las rutas de error que en el camino feliz. Esta guía enumera cada código de estado que devuelve Uncensored API, indica cuáles merecen un reintento y te proporciona una limitación de velocidad para el límite de 300 peticiones por minuto en Python asyncio y Node, además de una forma serena de mostrar un 402 a tus propios usuarios.

Actualizado

Puntos clave

  1. Reintenta solo 429, 503 y fallos de transporte; soluciona 400, 401, 403, 404 y 402 en la causa.
  2. Los errores llegan como JSON con un objeto error que contiene code y message, así que ramifica primero por el estado, luego por code.
  3. Un cubo de tokens que se rellena a 5 tokens por segundo mantiene una clave por debajo de 300 peticiones por minuto sin tiempos de espera.
  4. Un 402 significa que el saldo prepago se agotó o la prueba terminó; trátalo como un estado del producto, no como un fallo.

El envelope de error

Cada error devuelve un cuerpo JSON con un único objeto de nivel superior error. Dos campos son importantes: code, una cadena corta para máquinas, y message, texto destinado a registros y personas.

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

Decide en este orden. Mira primero el estado HTTP, porque indica la clase del problema. Usa code solo para separar casos que comparten un estado, y nunca analices message para lógica, ya que el texto puede cambiar. Para la forma de las respuestas exitosas, consulta la referencia de API.

Códigos de estado, causas y acciones

EstadoCódigoCausa típica¿Reintentar?Acción
400solicitud incorrectaJSON malformado, o tokens de prompt más max_tokens por encima de 100.000NoCorrige la carga útil; recorta el historial o reduce max_tokens
401clave inválidaFalta el encabezado, error tipográfico o una clave que fue regeneradaNoRecarga la clave desde tu almacén de secretos
402no_creditSaldo agotado o prueba gratuita expiradaNoRecarga el saldo prepago, luego reanuda
403content_blockedLa petición toca contenido sexual que involucra a menoresNoNo reenvíes; muestra tu propio mensaje de política
404no encontradoRuta incorrecta, como un prefijo /v1 faltanteNoVerifica la URL base
429límite de peticionesMás de 300 peticiones en un minuto en una claveSíRetrocede con jitter; añade throttling del lado del cliente
503upstream_busyPresión de capacidad temporalSíReintenta después de unos segundos

El 403 merece énfasis: es una decisión de política, no un error transitorio. Reintentar la petición idéntica desperdicia cuota y devolverá la misma respuesta. El caso 400 que más sorprende a la gente es la suma de contexto. Un prompt de 60.000 tokens con max_tokens de 8.000 desborda por 4.000 y falla aunque cada número sea válido por separado.

Una política de reintentos que se comporte

Usa una política pequeña y acotada y aplícala solo donde los reintentos puedan ayudar.

  1. Lista blanca de fallos reintentables: 429, 503, restablecimientos de conexión y tiempos de espera. Todo lo demás se eleva de inmediato.
  2. Retroceso exponencial con jitter: espera 1, 2, 4, 8 segundos más una fracción aleatoria de segundo, con un tope de 30. El jitter evita que muchos trabajadores reintenten al mismo tiempo.
  3. Limita los intentos a cinco. Más allá de eso, es más probable una interrupción que una mala suerte, y el trabajo en cola debe fallar de forma visible.
  4. Desactiva los reintentos automáticos del SDK (max_retries=0) cuando ejecutes tu propio bucle, de lo contrario los intentos se multiplican y tu control de límite pierde el recuento.
  5. Reintenta streams desde el inicio. Un stream que falla a mitad de camino no puede reanudarse; descarta el texto parcial y vuelve a emitir la petición, o guárdalo y pide una continuación.

Para un 503, la recomendación es esperar unos segundos, así que inicia tu primer retraso cerca de uno o dos segundos en lugar de golpear inmediatamente. Para un 429, la cura es reducir la velocidad, lo que la siguiente sección automatiza.

Mantenerse por debajo de 300 peticiones por minuto

El límite es de 300 peticiones por minuto por clave, lo que equivale a cinco por segundo en promedio. Un cubo de tokens aplica exactamente eso: retiene hasta una pequeña ráfaga de tokens, se rellena a cinco por segundo y hace que cada petición consuma un token. Si el cubo está vacío, el llamador espera hasta que exista un token.

La versión de Python usa asyncio. Todas las veinte tareas comparten un cubo y un bloqueo, así que las corutinas concurrentes no pueden sobregirarlo. Ten en cuenta que el cubo cuenta cada intento, incluidos los reintentos.

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

Ejecútalo con API_KEY exportado. Veinte peticiones caben dentro de la ráfaga más unos segundos de relleno, así que verás que termina rápidamente; sube el contador a 600 y la ejecución se extiende a aproximadamente dos minutos, que es el límite haciendo su trabajo.

La versión para Node, usando el paquete oficial en un módulo ES, encadena las adquisiciones a través de una cola de promesas para que los clientes sean atendidos en orden.

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 la ráfaga de forma conservadora. Una ráfaga de 10 permite un pico corto mientras la velocidad sostenida se mantiene en cinco por segundo. Si varios procesos comparten una clave, divide la velocidad entre ellos, por ejemplo 100 por minuto cada uno en tres trabajadores, o enruta todo el tráfico a través de una cola.

Manejo de 402 de forma elegante en tu producto

Un 402 con código no_credit significa que el saldo prepago llegó a cero o que el crédito de prueba expiró. Es el único error que tus usuarios finales nunca deberían ver como un seguimiento de pila, y suele indicar algo que solo tú puedes arreglar: recargar.

Construye tres capas. Primero, detectlo centralmente en un solo envoltorio para que ningún sitio de llamada tenga que recordarlo. Segundo, translatelo: el navegador o la aplicación móvil recibe un estado neutral como capacity_paused y muestra una pantalla amigable, nunca el mensaje del proveedor. Tercero, alertte a ti mismo, porque cada usuario se ve afectado a la vez; envía una notificación al propietario de la cuenta y activa una bandera de función para que la interfaz deje de enviar peticiones.

# 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

Añade también protección proactiva. Suma usage.total_tokens por petición a tu propio libro mayor y avisa en un umbral que elijas, para que una recarga ocurra antes de que un usuario quede bloqueado. Recuerda que el crédito de prueba es de $0,50 y dura 7 días, así que un prototipo puede llegar a 402 solo por expiración con dinero aún sin gastar. Las tarifas para planificación están en la página de precios.

Una vez que el saldo sea positivo de nuevo, reproduce el trabajo que pusiste en cola mientras estaba en pausa. Tu envoltorio debe borrar la bandera de pausa en el primer éxito.

Qué registrar y en qué alertar

Buenos registros hacen que los incidentes sean cortos. Registra, por petición: tu propio id de petición, el completion id cuando esté presente, estado HTTP, error code, número de intento y uso de tokens. Nunca registres el encabezado Authorization ni prompts completos que contengan datos personales.

Alertas útiles, en orden de prioridad: cualquier 402 (inmediato), una ejecución sostenida de 401s (una clave regenerada no se implementó en todas partes), tasa 429 por encima de unos pocos por ciento de llamadas (tu limitador está mal dimensionado) y clusters de 503 (retraso más amplio, considera una cola). Un panel de recuentos de estado por minuto hace que los cuatro sean visibles de un vistazo. Bien manejados, estos códigos se convierten en señales rutinarias en lugar de interrupciones.

Prueba tus rutas de fallo antes del lanzamiento

El manejo de errores que nunca se ha ejecutado es una suposición. Puedes ejercitar la mayoría de las ramas sin esperar una interrupción real, usando peticiones baratas y tus propios dobles de prueba.

  • 401: llama a GET /v1/models con una clave deliberadamente incorrecta y confirma que tu aplicación muestra un error de configuración a los operadores, no a los clientes.
  • 404: solicita /v1/chat/completion (singular) una vez y confirma que la línea de registro nombra la ruta, para que un error tipográfico en una URL base sea obvio en minutos.
  • 400: envía max_tokens de 32.000 con un prompt relleno a aproximadamente 50.000 tokens. La suma supera 100.000 y puedes ver cómo se dispara la ruta de validación.
  • 429 y 503: no puedes invocarlos a demanda, así que simúlalos. Envuelve tu cliente HTTP en un falso que lance el estado para las dos primeras llamadas, luego tenga éxito, y afirma que tu bucle duerme, reintenta y devuelve la respuesta final.
  • 402: simúlalo también y confirma que la interfaz muestra la pantalla de pausa, la alerta se dispara y nada reintenta en un bucle ajustado.

Escribe estas como pruebas automatizadas que se ejecuten en integración continua. Una prueba impulsada por tabla con una fila por estado de la tabla anterior toma minutos escribirla y protege la política de refactorizaciones bien intencionadas. Finalmente, ejecuta una prueba de carga corta contra tu propio limitador con la API simulada: dispara 1.000 adquisiciones y afirma que ninguna ventana de 60 segundos contiene más de 300 concesiones más la ráfaga. Eso demuestra la aritmética antes de que el tráfico real dependa de ella. Cuando más tarde leas la guía del bucle de agente, reutiliza el mismo envoltorio allí, ya que los agentes multiplican los recuentos de peticiones y alcanzan estos límites primero.

Preguntas y respuestas

¿Cuenta un 429 contra mi saldo?

Verifica las cifras de uso en tu propio libro mayor en lugar de asumir. En cualquier caso, la solución es reducir la velocidad y reintentar con retroceso.

¿Debería reintentar una respuesta 403 content_blocked?

No. Es un bloqueo de política y una petición idéntica devuelve el mismo resultado. Muestra tu propio mensaje y sigue adelante.

Mi clave devuelve 401 de repente. ¿Qué cambió?

Lo más probable es que la clave se haya regenerado, lo que invalida la antigua de inmediato. Copia la nueva clave de tu cuenta y reimplementa.

¿Cuántos trabajadores pueden compartir una clave?

Todos comparten el límite de 300 peticiones por minuto. Divide la velocidad entre los trabajadores o canaliza las llamadas a través de una cola.

Tu clave está a un formulario de distancia

Crea una cuenta, copia la clave, cambia la URL base. Esa es toda la configuración.