AI-API-feil er annerledes enn vanlige API-feil. En 200-respons betyr ikke at genereringen lyktes. Et null-felt for content er ikke alltid en feil. Og den samme prompten som fungerte i går kan feile i dag fordi en leverandør oppdaterte innholdspolitikken sin.
Denne veiledningen forklarer hvordan du leser AI-API-feil, hva hver feiltyper faktisk betyr, og hvordan du bygger feilhåndtering som forteller deg hva som gikk i stykker i stedet for bare at noe gikk i stykker.
Note: Modellnavn som gpt-5.4 og gpt-5.4-mini brukt i denne artikkelen er CometAPI sine plattformidentifikatorer. De fungerer kun via https://api.cometapi.com/v1 — ikke direkte via OpenAI- eller Anthropics API-er. Se den fulle modelllisten.
Hvorfor feilsøking for AI-API-er er vanskeligere enn for vanlige API-er
Med et typisk REST-API betyr 200 suksess og 4xx betyr at du gjorde noe feil. AI-API-er legger til en tredje kategori: soft failures — responser som returnerer 200, men uten brukbart innhold.
Tre ting kan gå galt:
- Hard feil — HTTP-feil (4xx, 5xx). Forespørselen ble ikke fullført.
- Myk feil — HTTP 200, men
finish_reasonercontent_filterellerlength, ellercontenternull. - Stille feil — HTTP 200, innholdet ser fint ut, men resultatet er feil på en måte du bare fanger på applikasjonslaget.
De fleste feilhåndteringer dekker bare punkt 1. Punkt 2 og 3 er der de fleste produksjonsfeil lever.
Forstå formatet på feilsvar
Text completions-endepunktet returnerer en konsistent feilstruktur:
{ "error": { "message": "human-readable description (often includes request id)", "type": "comet_api_error", "param": "the_problematic_parameter_or_null", "code": "error_code_or_null" }}
Bilde- og videoendepunkter returnerer andre feilformater — parse alltid råresponsen i stedet for å anta en fast struktur på tvers av endepunkter.
Feltet message forteller vanligvis nøyaktig hva som er galt. Feltet param forteller hvilken parameter som forårsaket det. Logg alltid begge.
Kjenn betydningen av hver HTTP-statuskode
| Status | Betydning | Vanlig årsak | Løsning |
|---|---|---|---|
| 400 | Bad request | Manglende modell, feil parameter for modellen | Kontroller error.param i responsen |
| 401 | Unauthorized | Feil eller manglende API-nøkkel | Verifiser Authorization: Bearer |
| 429 | Rate limited | For mange forespørsler | Eksponentiell backoff (se Trinn 4) |
| 500 | Server error | Feil hos leverandør, eller ugyldig request body | Prøv igjen med backoff; kontroller forespørselformat |
| 504 | Gateway timeout | Leverandøren brukte for lang tid | Prøv igjen; vurder en raskere modell |
Kilde**:* CometAPI chat completions-dokumentasjon
Distinksjonen mellom 400 og 500 er viktig for retry-logikk. En 400 betyr at forespørselen din er feil — å gjenta samme forespørsel hjelper ikke. En 500 eller 504 betyr at serveren hadde et problem — å prøve igjen gir mening.
Sjekk finish_reason — det mest oversette feltet
En 200-respons med finish_reason: "content_filter" betyr at genereringen ble blokkert. Feltet content vil være null eller tomt. Hvis du ikke sjekker dette, returnerer appen din stille ingenting.
| finish_reason | Betydning | Hva du gjør | Løsning |
|---|---|---|---|
| stop | Normal fullføring | Ingenting — dette er suksess | Kontroller error.param i responsen |
| length | Traff token-grensen | Øk max_tokens eller kort ned prompten | Verifiser Authorization: Bearer |
| content_filter | Blokkert av sikkerhetspolicy | Omformuler prompten; unngå spesifikke navn/temaer | Eksponentiell backoff (se Trinn 4) |
| tool_calls | Modellen kalte et verktøy i stedet for tekst | Håndter verktøykallet; innhold vil være null | Prøv igjen med backoff; kontroller forespørselformat |
| 504 | Gateway timeout | Leverandøren brukte for lang tid | Prøv igjen; vurder en raskere modell |
import osimport loggingfrom openai import OpenAI, APIStatusError, APIConnectionError, APITimeoutErrorfrom dotenv import load_dotenvload_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"])
Oppdag stille feil på applikasjonslaget
Stille feil er de vanskeligste å fange. API-et returnerer 200, finish_reason er stop, men outputen er semantisk feil. Du kan bare fange disse på applikasjonslaget.
Vanlige mønstre:
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 feil kommer vanligvis fra ett av tre steder: prompten er tvetydig, modellen ignorerte formatinstruksene dine, eller inputen var for kort/for lang for oppgaven. Å logge hele outputen når validering feiler er den raskeste måten å diagnostisere hvilken.
Legg til eksponentiell backoff for rate limits
Rate limit-feil (429) er midlertidige. Riktig respons er å vente og prøve igjen med økende pauser — en standard praksis for alle API-er med rate limits:
import timeimport randomfrom openai import RateLimitErrordef 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
Ikke prøv igjen på 400 eller 401 — det er klientfeil som ikke løser seg selv.
Feilsøk feil ved bildegenerering
Bildegenerering har egne feiltyper i tillegg til standard HTTP-feil:
import base64import requestsdef 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 }
Bilde-spesifikke problemer å se etter:
| Symptom | Årsak | Løsning |
|---|---|---|
| Tom data-array | Prompt filtrert | Sjekk revised_prompt; omformuler |
| response_format-feil på GPT Image 2 | Parameter ikke støttet | Bruk output_format i stedet |
| n > 1-feil på Qwen Image | Modellbegrensning | Løp flere forespørsler i loop |
| URL returnerer 403 senere | URL utløpt | Last ned umiddelbart etter generering |
Kilde**:* CometAPI image generation-dokumentasjon
Feilsøk feil ved videogenerering
Videogenerering feiler annerledes fordi den er asynkron. Initialiser statusvariabler før løkken slik at timeout-feilmeldingen alltid er velutformet:
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}%" )
Video-spesifikke problemer:
| Symptom | Årsak | Løsning |
|---|---|---|
| Oppgave fast i queued i 10+ min | Serverbelastning | Prøv med en annen modell |
| failed uten feildetaljer | Prompt filtrert eller modellfeil | Omformuler prompt |
| Video-URL returnerer 403 | URL utløpt | Last ned umiddelbart |
| task_not_exist ved første Runway-poll | Oppgave initierer fortsatt (CometAPI-dokumentert atferd) | Vent 5s og prøv igjen |
| Kling returnerer "succeed" ikke "succeeded" | Klings API bruker en ikke-standard statusstreng | Håndter begge i poll-logikken |
Kilde**:* CometAPI video generation-dokumentasjon**, Kling Video-dokumentasjon
Node.js-versjon
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);}
En sjekkliste for feilsøking
Når en generering feiler og du ikke vet hvor du skal begynne:
For tekstgenerering:
- Er API-nøkkelen satt og i
Authorization: Bearer <key>-format? - Er
finish_reasonnoe annet ennstop? - Er
contentnull? Sjekk omfinish_reasonertool_calls - Ble outputen trunkert? Sjekk
finish_reason: "length"ogusage.completion_tokens - Er feilen en 4xx (fiks forespørselen) eller 5xx (prøv igjen)?
- Består outputen valideringen på applikasjonslaget? (stille feil)
For bildegenerering:
- Er
data-arrayet tomt? (innholdsfilter) - Brukte du
response_formatpå GPT Image 2? (ikke støttet — brukoutput_format) - Satt du
n > 1på Qwen Image? (ikke støttet) - Lastet du ned bildet før URL-en utløp?
For videogenerering:
- Er oppgaven fast i
queued? (prøv en annen modell) - Sjekket du
error-feltet i den feilede oppgaveresponsen? - Lastet du ned videoen før URL-en utløp?
- Håndterer du både
"succeed"(Kling) og"succeeded"(Veo, Runway)?
FAQ
Q: Forespørselen min returnerer 200, men det er ikke noe innhold. Hva skjedde?
Sjekk finish_reason. Hvis den er content_filter, ble genereringen blokkert — forespørselen lyktes, men outputen ble undertrykt. Hvis den er tool_calls, kalte modellen et verktøy i stedet for å returnere tekst, og content er null per design. Hvis finish_reason er stop men innholdet fortsatt er tomt, er det en stille feil — logg hele responsen og sjekk prompten din.
Q: Hvordan vet jeg om prompten min blir filtrert?
For tekst: sjekk finish_reason === "content_filter". For bilder: sjekk om data-arrayet er tomt. For video: sjekk om oppgaven når failed-status kort tid etter innsending uten feildetaljer. I alle tilfeller, prøv å omformulere prompten mer nøytralt.
Q: Når bør jeg prøve en feilet forespørsel på nytt?
Prøv igjen på 429 og 5xx med eksponentiell backoff. Ikke prøv igjen på 4xx — en dårlig forespørsel fikser ikke seg selv. Unntaket er 401 hvis du roterer API-nøkler.
Q: Hva er eksponentiell backoff og hvorfor betyr det noe?
I stedet for å prøve igjen umiddelbart venter du gradvis lenger: 1s, 2s, 4s. Å legge til tilfeldig jitter (+ random.random()) hindrer at flere klienter prøver igjen i takt. Dette er en standard praksis for alle API-er med rate limits — ikke spesifikt for CometAPI.
Q: Videooppgaven sitter fast i queued i 10 minutter. Er den feilet?
Ikke nødvendigvis — køer kan bygges opp under last. Vent opp til din max_wait-terskel, kast deretter en TimeoutError og prøv igjen med en annen modell. Logg oppgave-ID-en slik at du kan sjekke status manuelt ved behov.
Q: Hvordan fanger jeg stille feil?
Stille feil krever validering på applikasjonsnivå — API-et forteller deg ikke at outputen er semantisk feil. Sjekk at outputen samsvarer med forventet format (gyldig JSON, forventet etikett, minimumslengde). Logg hele outputen når validering feiler. De vanligste årsakene er tvetydige prompter, ignorerte formatinstrukser, eller input som er for kort eller for lang for oppgaven.
