AI API-fejl er anderledes end almindelige API-fejl. Et 200-svar betyder ikke, at din generering lykkedes. Et null content-felt er ikke altid en fejl. Og den samme prompt, der virkede i går, kan fejle i dag, fordi en udbyder opdaterede deres indholdspolitik.
Denne guide beskriver, hvordan du læser AI API-fejl, hvad hver fejlsituation faktisk betyder, og hvordan du bygger fejlhåndtering, der fortæller dig, hvad der gik i stykker i stedet for bare at noget gik i stykker.
Bemærk: Modelnavne som gpt-5.4 og gpt-5.4-mini brugt i denne artikel er CometAPI’s platformidentifikatorer. De virker kun via https://api.cometapi.com/v1 — ikke direkte via OpenAI- eller Anthropics API’er. Se den fulde modelliste.
Hvorfor AI API-fejlsøgning er sværere end almindelig API-fejlsøgning
Med et typisk REST API betyder 200 succes og 4xx betyder, at du gjorde noget forkert. AI API’er tilføjer en tredie kategori: bløde fejl — svar der returnerer 200 men ikke indeholder brugbart indhold.
Tre ting kan gå galt:
- Hård fejl — HTTP-fejl (4xx, 5xx). Forespørgslen blev ikke fuldført.
- Blød fejl — HTTP 200, men
finish_reasonercontent_filterellerlength, ellercontenternull. - Stiltiende fejl — HTTP 200, indholdet ser fint ud, men outputtet er forkert på en måde, du kun opfanger på applikationslaget.
De fleste fejlhåndteringer dækker kun tilfælde 1. Tilfælde 2 og 3 er der, hvor de fleste produktionsfejl lever.
Forstå formatet for fejlrespons
Tekst-completions-endpointet returnerer en konsistent fejlstruktur:
{ "error": { "message": "human-readable description (often includes request id)", "type": "comet_api_error", "param": "the_problematic_parameter_or_null", "code": "error_code_or_null" }}
Billed- og video-endpoints returnerer andre fejlformater — parse altid den rå svarbody i stedet for at antage en fast struktur på tværs af endpoints.
Feltet message fortæller dig som regel præcist, hvad der er galt. Feltet param fortæller dig, hvilken parameter der forårsagede det. Log altid begge.
Kend betydningen af hver HTTP-statuskode
| Status | Betydning | Almindelig årsag | Løsning |
|---|---|---|---|
| 400 | Bad request | Manglende model, forkert parameter for denne model | Tjek error.param i svaret |
| 401 | Unauthorized | Forkert eller manglende API-nøgle | Verificér formatet Authorization: Bearer <key> |
| 429 | Rate limited | For mange forespørgsler | Eksponentiel backoff (se trin 4) |
| 500 | Server error | Problem hos udbyder eller misformet request-body | Prøv igen med backoff; tjek request-format |
| 504 | Gateway timeout | Udbyderen tog for lang tid | Prøv igen; overvej en hurtigere model |
Kilde: CometAPI chat completions dokumentation
Skelnen mellem 400 og 500 er vigtig for retry-logik. En 400 betyder, at din forespørgsel er forkert — at prøve igen med den samme forespørgsel hjælper ikke. En 500 eller 504 betyder, at serveren havde et problem — her giver det mening at prøve igen.
Tjek finish_reason — det mest oversete felt
Et 200-svar med finish_reason: "content_filter" betyder, at din generering blev blokeret. Feltet content vil være null eller tomt. Hvis du ikke tjekker dette, returnerer din app stiltiende ingenting.
| finish_reason | Betydning | Hvad skal du gøre | Løsning |
|---|---|---|---|
| stop | Normal afslutning | Intet — dette er succes | Tjek error.param i svaret |
| length | Ramt token-grænse | Øg max_tokens eller forkort prompten | Verificér formatet Authorization: Bearer <key> |
| content_filter | Blokeret af sikkerhedspolitik | Omformuler prompten; undgå specifikke navne/emner | Eksponentiel backoff (se trin 4) |
| tool_calls | Modellen kaldte et værktøj i stedet for at returnere tekst | Håndter værktøjskaldet; content vil være null | Prøv igen med backoff; tjek request-format |
| 504 | Gateway timeout | Udbyderen tog for lang tid | Prøv igen; overvej en hurtigere model |
Kilde: CometAPI chat completions dokumentation
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"])
Opfang stiltiende fejl på applikationslaget
Stiltiende fejl er de sværeste at fange. API’et returnerer 200, finish_reason er stop, men outputtet er semantisk forkert. Du kan kun fange disse på applikationslaget.
Almindelige 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")
Stiltiende fejl kommer typisk fra én af tre kilder: prompten er tvetydig, modellen ignorerede dine formatinstruktioner, eller inputtet var for kort/langt til opgaven. Log det fulde output, når valideringen fejler — det er den hurtigste måde at diagnosticere, hvilken det er.
Tilføj eksponentiel backoff for rate limits
Rate limit-fejl (429) er midlertidige. Den rigtige reaktion er at vente og prøve igen med stigende forsinkelser — en standardpraksis for ethvert API 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
Forsøg ikke igen på 400 eller 401 — det er klientfejl, som ikke løser sig selv.
Fejlsøg fejl i billedgenerering
Billedgenerering har sine egne fejlsituationer oven på de standard HTTP-fejl:
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 }
Billedspecifikke problemer at holde øje med:
| Symptom | Årsag | Løsning |
|---|---|---|
| Tom data-array | Prompt filtreret | Tjek revised_prompt; omformuler |
| response_format-fejl på GPT Image 2 | Parameter ikke understøttet | Brug output_format i stedet |
| n > 1-fejl på Qwen Image | Modellimitering | Kør forespørgsler i en løkke i stedet |
| URL returnerer 403 senere | URL udløbet | Download med det samme efter generering |
Kilde: CometAPI billedgenereringsdokumentation
Fejlsøg fejl i videogenerering
Videogenerering fejler anderledes, fordi den er asynkron. Initialisér statusvariabler før løkken, så timeout-fejlmeddelelsen altid er veludformet:
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}%" )
Videospecifikke problemer:
| Symptom | Årsag | Løsning |
|---|---|---|
| Opgave sidder fast i queued 10+ min | Serverbelastning | Prøv igen med en anden model |
| failed uden fejlbesked | Prompt filtreret eller model-fejl | Omformuler prompt |
| Video-URL returnerer 403 | URL udløbet | Download med det samme |
| task_not_exist ved første poll på Runway | Opgave initialiseres stadig (CometAPI-dokumenteret adfærd) | Vent 5s og prøv igen |
| Kling returnerer "succeed" ikke "succeeded" | Klings API bruger en ikke-standard statusstreng | Håndtér begge i polling-logik |
Kilde: CometAPI videogenereringsdokumentation**, Kling Video-dokumentation
Node.js-version
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 fejlsøgnings-tjekliste
Når en generering fejler, og du ikke ved, hvor du skal starte:
For tekstgenerering:
- Er API-nøglen sat og i formatet
Authorization: Bearer <key>? - Er
finish_reasonnoget andet endstop? - Er
contentnull? Tjek omfinish_reasonertool_calls - Blev outputtet trunkeret? Tjek
finish_reason: "length"ogusage.completion_tokens - Er fejlen en 4xx (ret forespørgslen) eller 5xx (prøv igen)?
- Består outputtet din validering på applikationslaget? (stiltiende fejl)
For billedgenerering:
- Er
data-arrayet tomt? (indholdsfilter) - Brugte du
response_formatpå GPT Image 2? (ikke understøttet — brugoutput_format) - Satte du
n > 1på Qwen Image? (ikke understøttet) - Downloadede du billedet, før URL’en udløb?
For videogenerering:
- Sidder opgaven fast i
queued? (prøv en anden model) - Tjekkede du feltet
errori svaret for en mislykket opgave? - Downloadede du videoen, før URL’en udløb?
- Håndterer du både
"succeed"(Kling) og"succeeded"(Veo, Runway)?
FAQ
Sp.: Min forespørgsel returnerer 200, men der er intet indhold. Hvad skete der?
Tjek finish_reason. Hvis det er content_filter, blev genereringen blokeret — forespørgslen lykkedes, men outputtet blev undertrykt. Hvis det er tool_calls, kaldte modellen et værktøj i stedet for at returnere tekst, og content er null med vilje. Hvis finish_reason er stop, men indholdet stadig er tomt, er det en stiltiende fejl — log det fulde svar og tjek din prompt.
Sp.: Hvordan ved jeg, om min prompt bliver filtreret?
For tekst: tjek finish_reason === "content_filter". For billeder: tjek om data-arrayet er tomt. For video: tjek om opgaven når status failed kort efter indsendelse uden fejlbesked. I alle tilfælde: prøv at omformulere prompten til at være mere neutral.
Sp.: Hvornår skal jeg forsøge igen med en mislykket forespørgsel?
Prøv igen på 429 og 5xx med eksponentiel backoff. Forsøg ikke igen på 4xx — en dårlig forespørgsel retter ikke sig selv. Undtagelsen er 401, hvis du roterer API-nøgler.
Sp.: Hvad er eksponentiel backoff, og hvorfor er det vigtigt?
I stedet for at forsøge igen med det samme venter du gradvist længere: 1s, 2s, 4s. At tilføje tilfældig jitter (+ random.random()) forhindrer, at flere klienter forsøger igen samtidigt. Dette er standardpraksis for ethvert API med rate limits — ikke specifikt for CometAPI.
Sp.: Videoopgaven sidder fast i queued i 10 minutter. Er den fejlet?
Ikke nødvendigvis — køer kan vokse under belastning. Vent op til din max_wait-tærskel, hæv derefter en TimeoutError og prøv igen med en anden model. Log opgave-ID’et, så du kan tjekke status manuelt om nødvendigt.
Sp.: Hvordan fanger jeg stiltiende fejl?
Stiltiende fejl kræver validering på applikationslaget — API’et fortæller dig ikke, at outputtet er semantisk forkert. Tjek, at outputtet matcher det forventede format (gyldig JSON, forventet label, minimumslængde). Log det fulde output, når valideringen fejler. De mest almindelige årsager er tvetydige prompts, ignorerede formatinstruktioner eller input, der er for kort eller for langt til opgaven.
