Kimi K2.7 Code is now on CometAPI — Kimi's most intelligent coding model to date, reliably follows instructions in long contexts and completes programming tasks with a higher success rate. Try it now

Cómo depurar generaciones fallidas de la API de IA

CometAPI
AnnaJun 4, 2026
Cómo depurar generaciones fallidas de la API de IA

Las fallas de las API de IA son diferentes de las fallas de las API normales. Una respuesta 200 no significa que tu generación haya tenido éxito. Un campo de contenido null no siempre es un error. Y el mismo prompt que funcionó ayer puede fallar hoy porque un proveedor actualizó su política de contenido.

Esta guía explica cómo leer los errores de las API de IA, qué significa realmente cada modo de falla y cómo crear un manejo de errores que te diga qué se rompió en lugar de solo que algo se rompió.

Nota: Los nombres de modelos como gpt-5.4 y gpt-5.4-mini utilizados en este artículo son identificadores de plataforma de CometAPI. Funcionan únicamente a través de https://api.cometapi.com/v1, no directamente a través de las API de OpenAI o Anthropic. Consulta la lista completa de modelos.

Por qué la depuración de APIs de IA es más difícil que la de APIs normales

Con una API REST típica, un 200 significa éxito y un 4xx significa que hiciste algo mal. Las APIs de IA agregan una tercera categoría: fallos suaves — respuestas que devuelven 200 pero no contienen contenido utilizable.

Tres cosas pueden salir mal:

  1. Fallo duro — error HTTP (4xx, 5xx). La solicitud no se completó.
  2. Fallo suave — HTTP 200, pero finish_reason es content_filter o length, o content es null.
  3. Fallo silencioso — HTTP 200, el contenido parece correcto, pero la salida es errónea de una forma que solo detectas en la capa de aplicación.

La mayoría del manejo de errores solo cubre el caso 1. Los casos 2 y 3 son donde viven la mayoría de los bugs en producción.

Comprende el formato de respuesta de error

El endpoint de completado de texto devuelve una estructura de error consistente:

{  "error": {    "message": "human-readable description (often includes request id)",    "type": "comet_api_error",    "param": "the_problematic_parameter_or_null",    "code": "error_code_or_null"  }}

Los endpoints de imagen y video devuelven formatos de error diferentes — analiza siempre el cuerpo bruto de la respuesta en lugar de asumir una estructura fija entre endpoints.

El campo message normalmente te dice exactamente qué está mal. El campo param te dice qué parámetro lo causó. Registra siempre ambos.

Conoce lo que significa cada código de estado HTTP

EstadoSignificadoCausa comúnSolución
400Solicitud incorrectaFalta el modelo, parámetro incorrecto para este modeloRevisa error.param en la respuesta
401No autorizadoClave de API incorrecta o ausenteVerifica el formato Authorization: Bearer <key>
429Con límite de tasaDemasiadas solicitudesReintentos exponenciales (consulta el Paso 4)
500Error del servidorProblema del proveedor o cuerpo de solicitud mal formadoVuelve a intentar con backoff; verifica el formato de la solicitud
504Tiempo de espera de la puerta de enlaceEl proveedor tardó demasiadoReintenta; considera un modelo más rápido

Fuente**: CometAPI chat completions docs

La distinción 400 vs 500 importa para la lógica de reintentos. Un 400 significa que tu solicitud está mal — reintentar la misma solicitud no ayudará. Un 500 o 504 significa que el servidor tuvo un problema — reintentar tiene sentido.

Revisa finish_reason — el campo más pasado por alto

Una respuesta 200 con finish_reason: "content_filter" significa que tu generación fue bloqueada. El campo content estará null o vacío. Si no revisas esto, tu app devolverá silenciosamente nada.

finish_reasonSignificadoQué hacerSolución
stopFinalización normalNada — es un éxitoRevisa error.param en la respuesta
lengthSe alcanzó el límite de tokensAumenta max_tokens o acorta el promptVerifica el formato Authorization: Bearer <key>
content_filterBloqueado por la política de seguridadReformula el prompt; evita nombres/temas específicosReintentos exponenciales (consulta el Paso 4)
tool_callsEl modelo llamó a una herramienta en lugar de devolver textoGestiona la llamada a la herramienta; el contenido será nullVuelve a intentar con backoff; verifica el formato de la solicitud
504Tiempo de espera de la puerta de enlaceEl proveedor tardó demasiadoReintenta; considera un modelo más rápido

Fuente**: CometAPI chat completions docs

import osimport loggingfrom openai import OpenAI, APIStatusError, APIConnectionError, APITimeoutErrorfrom dotenv import load_dotenv​load_dotenv()​api_key = os.environ.get("COMETAPI_KEY")if not api_key:    raise ValueError("COMETAPI_KEY is not set")​client = OpenAI(    base_url="https://api.cometapi.com/v1",    api_key=api_key,)​def safe_complete(messages: list, model: str = "gpt-5.4-mini", **kwargs) -> dict:    """    Complete a chat request with full error and finish_reason handling.    Returns {"content": str, "finish_reason": str, "tool_calls": list | None}    Raises on API errors.    """    try:        response = client.chat.completions.create(            model=model,            messages=messages,            **kwargs        )    except APIStatusError as e:        error_body = {}        try:            error_body = e.response.json().get("error", {})        except Exception:            pass        logging.error(            f"API error status={e.status_code} "            f"message={error_body.get('message')} "            f"param={error_body.get('param')}"        )        raise    except (APIConnectionError, APITimeoutError) as e:        logging.error(f"Network/timeout error: {e}")        raise​    choice = response.choices[0]    finish_reason = choice.finish_reason​    if finish_reason == "content_filter":        raise ValueError(            f"Generation blocked by content filter. "            f"Model: {model}. Rephrase the prompt."        )​    if finish_reason == "length":        used = response.usage.completion_tokens if response.usage else "unknown"        logging.warning(f"Output truncated at token limit. Used {used} tokens.")​    # Return structured result so callers can handle tool_calls explicitly    return {        "content": choice.message.content or "",        "finish_reason": finish_reason,        "tool_calls": choice.message.tool_calls,    }​# Usageresult = safe_complete(    messages=[{"role": "user", "content": "Summarize this article: [text]"}],    model="gpt-5.4-mini")​if result["finish_reason"] == "tool_calls":    # Handle tool call — content will be empty    print("Model wants to call a tool:", result["tool_calls"])else:    print(result["content"])

Detecta fallos silenciosos en la capa de aplicación

Los fallos silenciosos son los más difíciles de detectar. La API devuelve 200, finish_reason es stop, pero la salida es semánticamente incorrecta. Solo puedes detectarlos en la capa de aplicación.

Patrones comunes:

def validate_completion(result: dict, task: str) -> str:    """    Application-layer validation for silent failures.    Raises ValueError if the output doesn't meet basic expectations.    """    content = result["content"].strip()​    # Empty output that isn't a tool call    if not content and result["finish_reason"] != "tool_calls":        raise ValueError(f"Empty output for task '{task}' with finish_reason='{result['finish_reason']}'")​    # Task-specific checks    if task == "classify":        valid_labels = {"positive", "negative", "neutral"}        if content.lower() not in valid_labels:            logging.warning(                f"Unexpected classification output: '{content}'. "                f"Expected one of {valid_labels}. "                f"Model may have returned explanation instead of label."            )​    if task == "json_extract":        import json        try:            json.loads(content)        except json.JSONDecodeError:            raise ValueError(                f"Expected JSON output but got: '{content[:100]}...'. "                f"Try adding 'respond with valid JSON only' to the prompt, "                f"or use response_format={{\"type\": \"json_object\"}}."            )​    if task == "summarize" and len(content.split()) < 10:        logging.warning(            f"Suspiciously short summary ({len(content.split())} words). "            f"Check if the input was too short or the model misunderstood the task."        )​    return content​​# Full flow with silent failure detectionresult = safe_complete(    messages=[{"role": "user", "content": "Classify as positive/negative/neutral: 'Great product!'"}],    model="claude-haiku-4-5")label = validate_completion(result, task="classify")

Los fallos silenciosos suelen provenir de una de tres fuentes: el prompt es ambiguo, el modelo ignoró tus instrucciones de formato o la entrada era demasiado corta/larga para la tarea. Registrar la salida completa cuando falle la validación es la forma más rápida de diagnosticar cuál es el caso.

Añade reintentos exponenciales para límites de tasa

Los errores de límite de tasa (429) son temporales. La respuesta correcta es esperar y reintentar con demoras crecientes — una práctica estándar para cualquier API con límites de tasa:

import timeimport randomfrom openai import RateLimitError​def complete_with_retry(    messages: list,    model: str = "gpt-5.4-mini",    max_retries: int = 3,    **kwargs) -> dict:    """Retry on rate limits and server errors with exponential backoff."""    last_error = None​    for attempt in range(max_retries):        try:            return safe_complete(messages, model=model, **kwargs)​        except APIStatusError as e:            if e.status_code < 500:                raise  # 4xx: don't retry, request is wrong            last_error = e​        except RateLimitError as e:            last_error = e​        except (APIConnectionError, APITimeoutError) as e:            last_error = e​        if attempt < max_retries - 1:            wait = (2 ** attempt) + random.random()  # jitter prevents thundering herd            logging.warning(f"Attempt {attempt + 1} failed. Waiting {wait:.1f}s before retry.")            time.sleep(wait)​    raise RuntimeError(f"All {max_retries} attempts failed") from last_error

No reintentes en 400 o 401 — esos son errores del cliente que no se resolverán por sí solos.

Depura fallos de generación de imágenes

La generación de imágenes tiene sus propios modos de falla además de los errores HTTP estándar:

import base64import requests​def generate_image_safe(prompt: str, model: str = "dall-e-3") -> dict:    """    Generate an image with full error handling.    Returns {"url": str | None, "bytes": bytes | None, "blocked": bool}    """    api_key = os.environ.get("COMETAPI_KEY")    if not api_key:        raise ValueError("COMETAPI_KEY is not set")​    BASE64_MODELS = {"gpt-image-2", "qwen-image"}​    headers = {        "Authorization": f"Bearer {api_key}",        "Content-Type": "application/json"    }​    payload = {"model": model, "prompt": prompt, "size": "1024x1024"}    if model in BASE64_MODELS:        payload["output_format"] = "png"    else:        payload["response_format"] = "url"​    try:        response = requests.post(            "https://api.cometapi.com/v1/images/generations",            json=payload,            headers=headers,            timeout=60        )        response.raise_for_status()    except requests.exceptions.HTTPError as e:        logging.error(f"Image generation HTTP error: {e.response.status_code} {e.response.text}")        raise    except requests.exceptions.Timeout:        logging.error("Image generation timed out after 60s")        raise​    data = response.json().get("data", [])​    if not data:        logging.warning("Image generation returned empty data — prompt may have been filtered.")        return {"url": None, "bytes": None, "blocked": True}​    item = data[0]​    if "revised_prompt" in item:        logging.info(f"Provider revised prompt to: {item['revised_prompt']}")​    if "url" in item:        return {"url": item["url"], "bytes": None, "blocked": False}​    return {        "url": None,        "bytes": base64.b64decode(item["b64_json"]),        "blocked": False    }

Problemas específicos de imagen a tener en cuenta:

SíntomaCausaSolución
Matriz de datos vacíaPrompt filtradoRevisa revised_prompt; reformula
error de response_format en GPT Image 2Parámetro no admitidoUsa output_format en su lugar
error de n > 1 en Qwen ImageLimitación del modeloHaz solicitudes en bucle
La URL devuelve 403 más tardeURL expiradaDescarga inmediatamente tras la generación

Fuente**: CometAPI image generation docs

Depura fallos de generación de video

La generación de video falla de forma diferente porque es asíncrona. Inicializa variables de estado antes del bucle para que el mensaje de error por tiempo de espera siempre esté bien formado:

def submit_and_poll_video(    prompt: str,    model: str = "veo3-fast",    max_wait: int = 600) -> str:    """Submit video task and poll to completion. Returns video URL."""    api_key = os.environ.get("COMETAPI_KEY")    if not api_key:        raise ValueError("COMETAPI_KEY is not set")​    headers = {"Authorization": f"Bearer {api_key}"}​    try:        response = requests.post(            "https://api.cometapi.com/v1/videos",            headers=headers,            files={                "prompt": (None, prompt),                "model": (None, model),                "size": (None, "16x9")            },            timeout=30        )        response.raise_for_status()    except requests.exceptions.HTTPError as e:        logging.error(f"Video submit failed: {e.response.status_code} {e.response.text}")        raise​    task_id = response.json()["id"]    logging.info(f"Video task submitted: {task_id}")​    poll_url = f"https://api.cometapi.com/v1/videos/{task_id}"    elapsed = 0    interval = 10    status = "unknown"   # initialize before loop    progress = 0         # initialize before loop​    while elapsed < max_wait:        try:            poll_response = requests.get(poll_url, headers=headers, timeout=30)            poll_response.raise_for_status()        except requests.exceptions.HTTPError as e:            logging.error(f"Poll request failed: {e.response.status_code}")            raise​        result = poll_response.json()        status = result.get("status", "unknown")        progress = result.get("progress", 0)​        logging.info(f"Task {task_id}: status={status} progress={progress}%")​        if status == "succeeded":            return result["output"][0]        elif status in ("failed", "cancelled"):            error_detail = result.get("error", "no error detail returned")            raise RuntimeError(f"Video task {task_id} failed: {error_detail}")​        time.sleep(interval)        elapsed += interval​    raise TimeoutError(        f"Video task {task_id} did not complete within {max_wait}s. "        f"Last status: {status}, progress: {progress}%"    )

Problemas específicos de video:

SíntomaCausaSolución
Tarea atascada en queued más de 10 minCarga del servidorReintenta con un modelo diferente
failed sin detalle de errorPrompt filtrado o error del modeloReformula el prompt
La URL del video devuelve 403URL expiradaDescarga de inmediato
task_not_exist en la primera consulta de RunwayLa tarea aún se está inicializando (comportamiento documentado por CometAPI)Espera 5 s y reintenta
Kling devuelve "succeed" y no "succeeded"La API de Kling usa una cadena de estado no estándarGestiona ambas en la lógica de sondeo

Fuente**: CometAPI video generation docs**, Kling Video docs

Versión en Node.js

import OpenAI from 'openai';​const apiKey = process.env.COMETAPI_KEY;if (!apiKey) throw new Error('COMETAPI_KEY is not set');​const client = new OpenAI({  baseURL: 'https://api.cometapi.com/v1',  apiKey,});​async function safeComplete(messages, model = 'gpt-5.4-mini', options = {}) {  let response;​  try {    response = await client.chat.completions.create({ model, messages, ...options });  } catch (err) {    if (err.status && err.status < 500) {      console.error(`Client error ${err.status}: ${err.message}`);    } else {      console.error(`Server/network error: ${err.message}`);    }    throw err;  }​  const choice = response.choices[0];  const finishReason = choice.finish_reason;​  if (finishReason === 'content_filter') {    throw new Error(`Generation blocked by content filter. Model: ${model}`);  }​  if (finishReason === 'length') {    console.warn(`Output truncated. Used ${response.usage?.completion_tokens ?? 'unknown'} tokens.`);  }​  return {    content: choice.message.content ?? '',    finishReason,    toolCalls: choice.message.tool_calls ?? null,  };}​async function completeWithRetry(messages, model = 'gpt-5.4-mini', maxRetries = 3) {  let lastError;​  for (let attempt = 0; attempt < maxRetries; attempt++) {    try {      return await safeComplete(messages, model);    } catch (err) {      // Don't retry 4xx client errors      if (err.status && err.status < 500) throw err;​      lastError = err;      if (attempt < maxRetries - 1) {        const wait = (2 ** attempt + Math.random()) * 1000;        console.warn(`Attempt ${attempt + 1} failed. Retrying in ${(wait / 1000).toFixed(1)}s`);        await new Promise(r => setTimeout(r, wait));      }    }  }​  throw new Error(`All ${maxRetries} attempts failed: ${lastError?.message}`);}​// Usageconst result = await safeComplete(  [{ role: 'user', content: 'Classify as positive/negative/neutral: "Great product!"' }],  'claude-haiku-4-5');​if (result.finishReason === 'tool_calls') {  console.log('Tool call requested:', result.toolCalls);} else {  console.log(result.content);}

Una lista de verificación de depuración

Cuando una generación falla y no sabes por dónde empezar:

Para generación de texto:

  • ¿La clave de API está configurada y en formato Authorization: Bearer <key>?
  • ¿finish_reason es algo distinto de stop?
  • ¿content es null? Comprueba si finish_reason es tool_calls
  • ¿La salida se truncó? Revisa finish_reason: "length" y usage.completion_tokens
  • ¿El error es un 4xx (corrige la solicitud) o un 5xx (reintenta)?
  • ¿La salida pasa tu validación en la capa de aplicación? (fallo silencioso)

Para generación de imágenes:

  • ¿El arreglo data está vacío? (filtro de contenido)
  • ¿Usaste response_format en GPT Image 2? (no admitido — usa output_format)
  • ¿Configuraste n > 1 en Qwen Image? (no admitido)
  • ¿Descargaste la imagen antes de que caducara la URL?

Para generación de video:

  • ¿La tarea está atascada en queued? (prueba un modelo diferente)
  • ¿Revisaste el campo error en la respuesta de la tarea fallida?
  • ¿Descargaste el video antes de que caducara la URL?
  • ¿Estás manejando tanto "succeed" (Kling) como "succeeded" (Veo, Runway)?

Preguntas frecuentes

P: Mi solicitud devuelve 200 pero no hay contenido. ¿Qué pasó?

Revisa finish_reason. Si es content_filter, la generación fue bloqueada — la solicitud tuvo éxito pero la salida fue suprimida. Si es tool_calls, el modelo llamó a una herramienta en lugar de devolver texto, y content es null por diseño. Si finish_reason es stop pero el contenido sigue vacío, es un fallo silencioso — registra la respuesta completa y revisa tu prompt.

P: ¿Cómo sé si mi prompt está siendo filtrado?

Para texto: comprueba finish_reason === "content_filter". Para imágenes: verifica si el arreglo data está vacío. Para video: comprueba si la tarea llega a estado failed poco después del envío sin detalle de error. En todos los casos, intenta reformular el prompt para que sea más neutral.

P: ¿Cuándo debo reintentar una solicitud fallida?

Reintenta en 429 y 5xx utilizando backoff exponencial. No reintentes en 4xx — una solicitud incorrecta no se arreglará sola. La excepción es 401 si estás rotando claves de API.

P: ¿Qué es el backoff exponencial y por qué importa?

En lugar de reintentar inmediatamente, esperas progresivamente más: 1s, 2s, 4s. Agregar jitter aleatorio (+ random.random()) evita que múltiples clientes reintenten a la vez. Es una práctica estándar para cualquier API con límites de tasa — no específica de CometAPI.

P: La tarea de video está atascada en queued durante 10 minutos. ¿Está fallida?

No necesariamente — las colas pueden saturarse bajo carga. Espera hasta tu umbral max_wait, luego lanza un TimeoutError y reintenta con un modelo diferente. Registra el ID de la tarea para poder comprobar el estado manualmente si es necesario.

P: ¿Cómo detecto fallos silenciosos?

Los fallos silenciosos requieren validación en la capa de aplicación — la API no te dirá que la salida es semánticamente incorrecta. Comprueba que la salida coincide con el formato esperado (JSON válido, etiqueta esperada, longitud mínima). Registra la salida completa cuando falle la validación. Las causas más comunes son prompts ambiguos, instrucciones de formato ignoradas o entradas demasiado cortas o largas para la tarea.

¿Listo para reducir los costos de desarrollo de IA en un 20%?

Comienza gratis en minutos. Créditos de prueba gratuitos incluidos. No se requiere tarjeta de crédito.

Leer Más