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

Hoe je mislukte AI-API-generaties debugt

CometAPI
AnnaJun 4, 2026
Hoe je mislukte AI-API-generaties debugt

AI-API-fouten verschillen van reguliere API-fouten. Een 200-response betekent niet dat je generatie is geslaagd. Een veld content met waarde null is niet altijd een fout. En dezelfde prompt die gisteren werkte, kan vandaag falen omdat een provider zijn inhoudsbeleid heeft bijgewerkt.

Deze gids behandelt hoe je AI-API-fouten leest, wat elke faalmodus werkelijk betekent, en hoe je foutafhandeling bouwt die vertelt wat er stukging in plaats van alleen dát er iets stuk is.

Opmerking: Modelnamen zoals gpt-5.4 en gpt-5.4-mini die in dit artikel worden gebruikt, zijn platformidentifiers van CometAPI. Ze werken alleen via https://api.cometapi.com/v1 — niet rechtstreeks via de API’s van OpenAI of Anthropic. Zie de full model list.

Waarom debuggen van AI-API’s lastiger is dan bij gewone API’s

Bij een typische REST-API betekent 200 succes en 4xx dat jij iets fout deed. AI-API’s voegen een derde categorie toe: zachte fouten — responses die 200 teruggeven maar geen bruikbare inhoud bevatten.

Er kunnen drie dingen misgaan:

  1. Harde fout — HTTP-fout (4xx, 5xx). Het verzoek is niet voltooid.
  2. Zachte fout — HTTP 200, maar finish_reason is content_filter of length, of content is null.
  3. Stille fout — HTTP 200, inhoud lijkt oké, maar de output is fout op een manier die je pas op de applicatielaag vangt.

De meeste foutafhandeling dekt alleen geval 1. Gevallen 2 en 3 zijn waar de meeste bugs in productie zitten.

Begrijp het foutresponsformaat

De text completions-endpoint retourneert een consistent foutobject:

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

Image- en video-endpoints geven andere foutformaten terug — parse altijd de ruwe response-body in plaats van uit te gaan van een vaste structuur over endpoints heen.

Het veld message vertelt meestal precies wat er mis is. Het veld param vertelt welk parameter dit veroorzaakte. Log ze altijd allebei.

Weet wat elke HTTP-statuscode betekent

StatusBetekenisVeelvoorkomende oorzaakOplossing
400Ongeldig verzoekOntbrekend model, verkeerde parameter voor dit modelControleer error.param in de response
401Niet geautoriseerdVerkeerde of ontbrekende API-sleutelVerifieer Authorization: Bearer -formaat
429Rate limit bereiktTe veel verzoekenExponentiële backoff (zie Stap 4)
500ServerfoutProbleem aan providerzijde of ongeldig request bodyOpnieuw proberen met backoff; controleer requestformaat
504Gateway-time-outProvider deed er te lang overOpnieuw proberen; overweeg een sneller model

Bron**: CometAPI chat completions docs

Het onderscheid 400 vs 500 is belangrijk voor retrylogica. Een 400 betekent dat je verzoek fout is — dezelfde aanvraag opnieuw proberen helpt niet. Een 500 of 504 betekent dat de server een probleem had — opnieuw proberen is logisch.

Controleer finish_reason — het meest over het hoofd geziene veld

Een 200-response met finish_reason: "content_filter" betekent dat je generatie is geblokkeerd. Het veld content is null of leeg. Als je dit niet controleert, geeft je app stilzwijgend niets terug.

finish_reasonBetekenisWat te doenOplossing
stopNormale voltooiingNiets — dit is succesControleer error.param in de response
lengthTokengrens bereiktVerhoog max_tokens of verkort de promptVerifieer Authorization: Bearer <key>-formaat
content_filterGeblokkeerd door veiligheidsbeleidHerschrijf de prompt; vermijd specifieke namen/onderwerpenExponentiële backoff (zie Stap 4)
tool_callsModel riep een tool aan i.p.v. tekst te gevenVerwerk de tool-aanroep; content is nullOpnieuw proberen met backoff; controleer requestformaat
504Gateway-time-outProvider deed er te lang overOpnieuw proberen; overweeg een sneller model

Bron**: 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"])

Detecteer stille fouten op de applicatielaag

Stille fouten zijn het lastigst te vangen. De API retourneert 200, finish_reason is stop, maar de output is semantisch onjuist. Je kunt dit alleen op de applicatielaag detecteren.

Veelvoorkomende patronen:

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

Stille fouten komen meestal uit een van drie bronnen: de prompt is ambigu, het model negeerde je formaat-instructies, of de input was te kort/te lang voor de taak. Het loggen van de volledige output wanneer validatie faalt is de snelste manier om te diagnosticeren welke van de drie het is.

Voeg exponentiële backoff toe voor rate limits

Rate-limitfouten (429) zijn tijdelijk. De juiste reactie is wachten en opnieuw proberen met oplopende vertragingen — een standaardpraktijk voor elke API met rate limits:

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

Herhaal niet bij 400 of 401 — dat zijn clientfouten die niet vanzelf verdwijnen.

Debuggen van afbeeldingsgeneratiefouten

Afbeeldingsgeneratie heeft eigen faalmodi bovenop de standaard HTTP-fouten:

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    }

Afbeeldingsspecifieke aandachtspunten:

SymptoomOorzaakOplossing
Lege data-arrayPrompt gefilterdControleer revised_prompt; herformuleer
response_format-fout op GPT Image 2Parameter niet ondersteundGebruik in plaats daarvan output_format
n > 1-fout op Qwen ImageModellimiteitIn plaats daarvan verzoeken loopen
URL geeft later 403URL verlopenMeteen na generatie downloaden

Bron**: CometAPI image generation docs

Debuggen van videogeneratiefouten

Videogeneratie faalt anders omdat het asynchroon is. Initialiseer statusvariabelen vóór de lus zodat de time-outerrorboodschap altijd goed gevormd is:

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}%"    )

Videospecifieke aandachtspunten:

SymptoomOorzaakOplossing
Taak blijft 10+ min in queued hangenServerbelastingProbeer een ander model
failed zonder errordetailPrompt gefilterd of modelfoutHerformuleer prompt
Video-URL geeft 403URL verlopenMeteen downloaden
task_not_exist bij eerste poll RunwayTaak initialiseert nog (gedocumenteerd gedrag bij CometAPI)Wacht 5s en probeer opnieuw
Kling retourneert "succeed" i.p.v. "succeeded"API van Kling gebruikt niet-standaard statusstringVerwerk beide in je polllogica

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

Node.js-versie

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

Een debugging-checklist

Wanneer een generatie faalt en je niet zeker weet waar te beginnen:

Voor tekstgeneratie:

  • Is de API-sleutel gezet en in het formaat Authorization: Bearer <key>?
  • Is finish_reason iets anders dan stop?
  • Is content null? Controleer of finish_reason tool_calls is
  • Is de output afgekapt? Controleer finish_reason: "length" en usage.completion_tokens
  • Is de fout een 4xx (repareer het verzoek) of 5xx (opnieuw proberen)?
  • Slaagt de output voor je validatie op applicatielaag? (stille fout)

Voor afbeeldingsgeneratie:

  • Is de data-array leeg? (contentfilter)
  • Heb je response_format gebruikt op GPT Image 2? (niet ondersteund — gebruik output_format)
  • Heb je n > 1 ingesteld op Qwen Image? (niet ondersteund)
  • Heb je de afbeelding gedownload voordat de URL verliep?

Voor videogeneratie:

  • Zit de taak vast in queued? (probeer een ander model)
  • Heb je het error-veld gecontroleerd in de mislukte taakresponse?
  • Heb je de video gedownload voordat de URL verliep?
  • Verwerk je zowel "succeed" (Kling) als "succeeded" (Veo, Runway)?

FAQ

Q: Mijn request geeft 200 terug maar er is geen content. Wat is er gebeurd?

Controleer finish_reason. Als dit content_filter is, is de generatie geblokkeerd — het verzoek is geslaagd maar de output is onderdrukt. Als het tool_calls is, heeft het model een tool aangeroepen in plaats van tekst terug te geven, en content is bij ontwerp null. Als finish_reason stop is maar de content nog steeds leeg is, is dat een stille fout — log de volledige response en controleer je prompt.

Q: Hoe weet ik of mijn prompt wordt gefilterd?

Voor tekst: controleer finish_reason === "content_filter". Voor afbeeldingen: controleer of de data-array leeg is. Voor video: controleer of de taak kort na indienen de status failed bereikt zonder errordetail. Probeer in alle gevallen de prompt neutraler te formuleren.

Q: Wanneer moet ik een mislukt verzoek opnieuw proberen?

Probeer opnieuw bij 429 en 5xx met exponentiële backoff. Niet opnieuw proberen bij 4xx — een ongeldig verzoek lost zichzelf niet op. De uitzondering is 401 als je API-sleutels roteert.

Q: Wat is exponentiële backoff en waarom is het belangrijk?

In plaats van onmiddellijk opnieuw te proberen, wacht je steeds langer: 1s, 2s, 4s. Willekeurige jitter (+ random.random()) voorkomt dat meerdere clients synchroon opnieuw proberen. Dit is een standaardpraktijk voor elke API met rate limits — niet specifiek voor CometAPI.

Q: De videotask blijft 10 minuten in queued. Is deze mislukt?

Niet per se — wachtrijen kunnen vollopen bij belasting. Wacht tot je max_wait-drempel, gooi dan een TimeoutError en probeer een ander model. Log de taak-ID zodat je de status handmatig kunt controleren indien nodig.

Q: Hoe vang ik stille fouten?

Stille fouten vereisen validatie op applicatielaag — de API vertelt je niet dat de output semantisch onjuist is. Controleer of de output aan het verwachte formaat voldoet (geldige JSON, verwacht label, minimumlengte). Log de volledige output wanneer validatie faalt. De meest voorkomende oorzaken zijn ambigue prompts, genegeerde formateringseisen of inputs die te kort of te lang zijn voor de taak.

Klaar om de AI-ontwikkelingskosten met 20% te verlagen?

Start gratis in enkele minuten. Gratis proeftegoeden inbegrepen. Geen creditcard vereist.

Lees Meer