Formato del cuerpo de error y orden de lectura
Todas las respuestas fallidas tienen la misma estructura:
{"error":{"code":"...","message":"..."}}
El orden de lectura tiene tres pasos fijos:
- Código de estado HTTP, para la categoría;
error.codeindica la causa específica; úsalo para hacer ramificación en el código;error.message, para humanos y logs, no para coincidencia de cadenas.
Define primero la frontera: los que se resuelven con reintento (429, 503 y timeout de red) y los que requieren cambio (el resto). Mezclarlos es la causa raíz más común de incidentes, como reintentar 402 y generar docenas de peticiones inválidas por segundo.
Registra siempre cuatro campos: hora, código de estado, error.code y el valor estimado de prompt_tokens de la petición. Así verás si aumentan errores específicos o si falla todo. Configura una alerta: 402 notifica al instante porque indica servicio no disponible; 429 evalúa la proporción: si es esporádico, ignóralo; si es continuo, hay un problema de concurrencia.
Búsqueda rápida de códigos de estado
| Código de estado | error.code | Significado | ¿Reintentar? |
|---|---|---|---|
| 400 | — | Petición inválida, ej. prompt + max_tokens > 100k | No |
| 401 | — | Clave inválida o ausente | No |
| 402 | no_credit | Saldo agotado o prueba expirada | No |
| 403 | content_blocked | Contenido bloqueado | No |
| 404 | — | Endpoint no encontrado | No |
| 429 | — | Límite de peticiones excedido | Sí, con retroceso |
| 503 | upstream_busy | Servicio ocupado temporalmente | Sí, en unos segundos |
El «—» indica que no hay un código de cadena fijo que requiera manejo especial; usa la ramificación por código de estado.
4xx: Cambia la petición, no reintentar
400 Petición inválida
- Causa 1: Error de formato JSON, común al construir cadenas a mano sin escapar las comillas.
- Causa 2: prompt + max_tokens superan 100,000. Es lo más fácil de activar en conversaciones largas.
- Causa 3: Cuerpo de petición > 8 MB.
- Solución: serializa con una librería JSON; estima los tokens antes de enviar y, si excedes la ventana de contexto, recorta el historial o reduce max_tokens; consulta contexto largo.
401 Problemas con la clave
- Falta el Header o el prefijo
Bearer. - Regeneraste la clave; la antigua caducó pero un servidor aún la usa.
- La variable de entorno no se pasó al contenedor o tarea programada.
402 no_credit
Se agotó el saldo o la prueba de 7 días ha expirado. Ve a la página de tu cuenta para recargar el saldo prepago y reanudar. Te recomendamos monitorear el saldo de tu servicio para que no te sorprendan los errores de los usuarios.
403 content_blocked
Contenido bloqueado. El contenido adulto legítimo, la ficción y los temas controvertidos no se rechazan, pero el contenido sexual que involucra a menores se bloquea siempre, tanto en novelas como en roleplay. Si obtienes un 403, revisa si el prompt o el historial contienen este tipo de contenido; no cambies las palabras e intentes de nuevo.
404 endpoint no encontrado
Solo hay dos endpoints: POST /v1/chat/completions y GET /v1/models. Una ruta incorrecta (falta /v1, sobra una barra, error de ortografía) o solicitar endpoints no soportados como embeddings o imágenes provocará un 404.
Otro error 400 fácil de malinterpretar: los tokens de un diálogo largo crecen linealmente con las rondas. Todo funciona bien durante las pruebas diurnas, pero tras decenas de rondas empiezan a aparecer 400. No es inestabilidad de la API, es que se ha alcanzado la ventana de contexto. La solución es limitar el historial: descartar las rondas más antiguas al superar un umbral o resumir el contenido antiguo. No esperes al error para gestionarlo; estima el consumo antes de enviar la petición.
Un truco para depurar 401: imprime en los logs los cuatro primeros y los cuatro últimos caracteres de la clave, no el contenido completo, y compáralo con lo que muestra la página de tu cuenta. Así confirmas si el proceso está leyendo la clave que crees, algo crucial en entornos de contenedores o tareas programadas donde la variable de entorno puede faltar o contener un valor antiguo.
429 y 503: los dos errores que sí requieren reintentos
429 límite de peticiones
300 peticiones por minuto y por clave. Las tareas por lotes, el uso compartido de una clave entre múltiples instancias y las tormentas de reintentos pueden activarlo. La corrección tiene dos capas: primero limita las peticiones desde el cliente (ver código más abajo) y luego aplica reintentos con retroceso ante un 429.
503 upstream_busy
El servicio está temporalmente ocupado; reintenta tras unos segundos. No envíes peticiones en cascada ni reintentes diez veces en un segundo, ya que solo empeorarás la situación.
Tiempo de espera de red
La generación de texto largo lleva tiempo; no configures un timeout demasiado corto, usa 120 segundos como ejemplo. Ten cuidado al reintentar tras un timeout: la petición original puede haberse ejecutado en el servidor, por lo que una llamada duplicada podría consumir saldo dos veces. Para peticiones de generación, reduce el número de reintentos y, de ser posible, usa streaming para acortar la espera.
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"])Clave: la fórmula de retroceso es min(cap, base × 2^n) × factor aleatorio; el jitter aleatorio evita que múltiples clientes reintenten al mismo tiempo y colisionen. No reintentes nunca los códigos de estado de la colección FATAL.
Límite de peticiones y peticiones de diagnóstico en el cliente
En lugar de esperar a un 429 para aplicar retroceso, es mejor limitar las peticiones de antemano. La siguiente herramienta garantiza que el número de peticiones por minuto no supere un valor establecido, con seguridad para hilos múltiples:
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))Para depurar, la forma más limpia es aislarte del código de negocio y enviar una petición mínima con curl. Usa -i para ver simultáneamente la línea de estado y los encabezados de respuesta:
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"}]}'Si curl funciona pero tu código de negocio falla, el problema está en tu código o entorno (proxy, variables de entorno, codificación). Si curl tampoco funciona, revisa la clave, el saldo y la red.
Por qué el límite es 240 y no 300: en despliegues con múltiples instancias, cada una aplica su propio límite y la suma puede aún superar el límite total; además, los reintentos consumen cuota extra. Deja un 20 % de margen. Si tienes varias instancias, reparte el total equitativamente o usa un contador compartido.
Lista de verificación para solucionar problemas
- Lee el campo
error.codedel body, no te limites a ver el código de estado. - 401: prefijo de la clave, variables de entorno, si se ha regenerado.
- 400: ¿Es JSON válido? ¿prompt + max_tokens superan 100,000? ¿El cuerpo de la petición supera 8 MB?
- 402: Saldo y validez de la prueba.
- 403: si el prompt o el historial contienen contenido prohibido.
- 404: si la ruta es /v1/chat/completions o /v1/models.
- 429: si varias instancias comparten una clave, si se aplica límite de peticiones en el cliente.
- 503: si se aplica retroceso con reintentos y el intervalo es de al menos varios segundos.
- Timeout: si el tiempo de espera es suficiente, si puedes cambiar a streaming.
- Si se descartan todas las anteriores: reproduce el error con una petición mínima en curl.
Si acabas de empezar, consulta primero el tutorial de integración. Más parámetros están en la documentación.
Uso de la lista: descarta problemas de arriba a abajo, sin saltarte pasos. La mayoría de las fallas «extrañas» terminan siendo una de las primeras cuatro.
Un consejo adicional: escribe las conclusiones de cada depuración en la documentación del equipo. Así, la próxima vez que aparezca el mismo error, podrás identificarlo de inmediato.