Kegagalan API AI berbeda dari kegagalan API biasa. Respons 200 tidak berarti generasi Anda berhasil. Field content bernilai null tidak selalu merupakan error. Dan prompt yang kemarin berhasil bisa gagal hari ini karena penyedia memperbarui kebijakan konten mereka.
Panduan ini membahas cara membaca error API AI, apa arti setiap mode kegagalan, dan bagaimana membangun penanganan error yang memberi tahu Anda apa yang rusak alih-alih sekadar bahwa sesuatu rusak.
Catatan: Nama model seperti gpt-5.4 dan gpt-5.4-mini yang digunakan dalam artikel ini adalah pengidentifikasi platform CometAPI. Model ini hanya berfungsi melalui https://api.cometapi.com/v1 — bukan langsung melalui API OpenAI atau Anthropic. Lihat daftar model lengkap.
Mengapa debug API AI lebih sulit daripada debug API biasa
Dengan REST API tipikal, 200 berarti sukses dan 4xx berarti Anda melakukan kesalahan. API AI menambahkan kategori ketiga: kegagalan lunak — respons yang mengembalikan 200 tetapi tidak berisi konten yang dapat digunakan.
Tiga hal yang bisa salah:
- Kegagalan keras — error HTTP (4xx, 5xx). Permintaan tidak selesai.
- Kegagalan lunak — HTTP 200, tetapi
finish_reasonadalahcontent_filterataulength, ataucontentadalahnull. - Kegagalan senyap — HTTP 200, konten terlihat baik, tetapi output salah dengan cara yang hanya bisa Anda tangkap di lapisan aplikasi.
Sebagian besar penanganan error hanya mencakup kasus 1. Kasus 2 dan 3 adalah tempat sebagian besar bug produksi berada.
Pahami format respons error
Endpoint text completions mengembalikan struktur error yang konsisten:
{ "error": { "message": "human-readable description (often includes request id)", "type": "comet_api_error", "param": "the_problematic_parameter_or_null", "code": "error_code_or_null" }}
Endpoint gambar dan video mengembalikan format error yang berbeda — selalu parsing body respons mentah alih-alih mengasumsikan struktur tetap di seluruh endpoint.
Field message biasanya memberi tahu Anda persis apa yang salah. Field param memberi tahu Anda parameter mana yang menyebabkannya. Selalu log keduanya.
Ketahui arti setiap kode status HTTP
| Status | Arti | Penyebab umum | Perbaikan |
|---|---|---|---|
| 400 | Bad request | Model hilang, parameter salah untuk model ini | Periksa error.param dalam respons |
| 401 | Unauthorized | API key salah atau tidak ada | Verifikasi format Authorization: Bearer |
| 429 | Rate limited | Terlalu banyak permintaan | Exponential backoff (lihat Langkah 4) |
| 500 | Server error | Masalah di penyedia, atau body permintaan salah | Retry dengan backoff; periksa format request |
| 504 | Gateway timeout | Penyedia terlalu lama merespons | Retry; pertimbangkan model yang lebih cepat |
Sumber**: CometAPI chat completions docs
Pembedaan 400 vs 500 penting untuk logika retry. 400 berarti permintaan Anda salah — mengulang permintaan yang sama tidak akan membantu. 500 atau 504 berarti server bermasalah — retry masuk akal.
Periksa finish_reason — field yang paling sering diabaikan
Respons 200 dengan finish_reason: "content_filter" berarti generasi Anda diblokir. Field content akan null atau kosong. Jika Anda tidak memeriksa ini, aplikasi Anda akan mengembalikan tidak ada apa-apa tanpa peringatan.
| finish_reason | Arti | Apa yang harus dilakukan | Perbaikan |
|---|---|---|---|
| stop | Selesai normal | Tidak ada — ini sukses | Periksa error.param dalam respons |
| length | Mencapai batas token | Tingkatkan max_tokens atau persingkat prompt | Verifikasi format Authorization: Bearer |
| content_filter | Diblokir oleh kebijakan keamanan | Ubah redaksi prompt; hindari nama/topik tertentu | Exponential backoff (lihat Langkah 4) |
| tool_calls | Model memanggil tool alih-alih mengembalikan teks | Tangani pemanggilan tool; content akan null | Retry dengan backoff; periksa format request |
| 504 | Gateway timeout | Penyedia terlalu lama merespons | Retry; pertimbangkan model yang lebih cepat |
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"])
Deteksi kegagalan senyap di lapisan aplikasi
Kegagalan senyap adalah yang paling sulit ditangkap. API mengembalikan 200, finish_reason adalah stop, tetapi output secara semantik salah. Anda hanya dapat menangkap ini di lapisan aplikasi.
Pola umum:
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")
Kegagalan senyap biasanya berasal dari salah satu dari tiga sumber: prompt ambigu, model mengabaikan instruksi format Anda, atau input terlalu pendek/panjang untuk tugasnya. Melog seluruh output saat validasi gagal adalah cara tercepat untuk mendiagnosis yang mana.
Tambahkan exponential backoff untuk rate limit
Error rate limit (429) bersifat sementara. Respons yang tepat adalah menunggu dan retry dengan jeda yang meningkat — praktik standar untuk API apa pun dengan rate limit:
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
Jangan retry pada 400 atau 401 — itu error sisi klien yang tidak akan teratasi dengan sendirinya.
Debug kegagalan pembuatan gambar
Pembuatan gambar punya mode kegagalan sendiri di atas error HTTP standar:
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 }
Masalah khusus gambar yang perlu diwaspadai:
| Gejala | Penyebab | Perbaikan |
|---|---|---|
| Array data kosong | Prompt difilter | Periksa revised_prompt; ubah redaksi |
error response_format pada GPT Image 2 | Parameter tidak didukung | Gunakan output_format sebagai ganti |
n > 1 error pada Qwen Image | Limitasi model | Lakukan loop permintaan sebagai gantinya |
| URL mengembalikan 403 kemudian | URL kedaluwarsa | Unduh segera setelah pembuatan |
Sumber**: CometAPI image generation docs
Debug kegagalan pembuatan video
Pembuatan video gagal dengan cara berbeda karena bersifat async. Inisialisasi variabel status sebelum loop agar pesan error timeout selalu terbentuk dengan baik:
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}%" )
Masalah khusus video:
| Gejala | Penyebab | Perbaikan |
|---|---|---|
Tugas macet di queued > 10 menit | Beban server | Retry dengan model berbeda |
| failed tanpa detail error | Prompt difilter atau error model | Ubah redaksi prompt |
| URL video mengembalikan 403 | URL kedaluwarsa | Unduh segera |
task_not_exist pada poll pertama Runway | Tugas masih inisialisasi (perilaku terdokumentasi CometAPI) | Tunggu 5 dtk lalu retry |
| Kling mengembalikan "succeed" bukan "succeeded" | API Kling memakai status non-standar | Tangani keduanya dalam polling |
Sumber**: CometAPI video generation docs**, Kling Video docs
Versi 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);}
Daftar periksa debugging
Saat sebuah generasi gagal dan Anda tidak yakin harus mulai dari mana:
Untuk pembuatan teks:
- Apakah API key disetel dan dalam format
Authorization: Bearer <key>? - Apakah
finish_reasonselainstop? - Apakah
contentnull? Periksa apakahfinish_reasonadalahtool_calls - Apakah output terpotong? Periksa
finish_reason: "length"danusage.completion_tokens - Apakah error-nya 4xx (perbaiki request) atau 5xx (retry)?
- Apakah output lulus validasi lapisan aplikasi Anda? (kegagalan senyap)
Untuk pembuatan gambar:
- Apakah array
datakosong? (content filter) - Apakah Anda menggunakan
response_formatpada GPT Image 2? (tidak didukung — gunakanoutput_format) - Apakah Anda menyetel
n > 1pada Qwen Image? (tidak didukung) - Apakah Anda mengunduh gambar sebelum URL kedaluwarsa?
Untuk pembuatan video:
- Apakah tugas macet di
queued? (coba model berbeda) - Apakah Anda memeriksa field
errorpada respons tugas yang gagal? - Apakah Anda mengunduh video sebelum URL kedaluwarsa?
- Apakah Anda menangani
"succeed"(Kling) dan"succeeded"(Veo, Runway)?
FAQ
Q: Permintaan saya mengembalikan 200 tetapi tidak ada konten. Apa yang terjadi?
Periksa finish_reason. Jika content_filter, generasi diblokir — permintaan berhasil tetapi output disembunyikan. Jika tool_calls, model memanggil tool alih-alih mengembalikan teks, dan content null sesuai desain. Jika finish_reason adalah stop tetapi konten tetap kosong, itu kegagalan senyap — log seluruh respons dan periksa prompt Anda.
Q: Bagaimana saya tahu jika prompt saya sedang difilter?
Untuk teks: periksa finish_reason === "content_filter". Untuk gambar: periksa apakah array data kosong. Untuk video: periksa apakah tugas mencapai status failed segera setelah pengiriman tanpa detail error. Dalam semua kasus, coba ubah redaksi prompt menjadi lebih netral.
Q: Kapan saya harus retry permintaan yang gagal?
Retry pada 429 dan 5xx menggunakan exponential backoff. Jangan retry pada 4xx — bad request tidak akan membaik dengan sendirinya. Pengecualian adalah 401 jika Anda memutar API key.
Q: Apa itu exponential backoff dan mengapa penting?
Alih-alih retry segera, Anda menunggu semakin lama: 1 dtk, 2 dtk, 4 dtk. Menambahkan jitter acak (+ random.random()) mencegah banyak klien melakukan retry secara serempak. Ini praktik standar untuk API apa pun dengan rate limit — bukan khusus CometAPI.
Q: Tugas video macet di queued selama 10 menit. Apakah gagal?
Belum tentu — antrean bisa menumpuk saat beban tinggi. Tunggu hingga ambang max_wait Anda, lalu naikkan TimeoutError dan retry dengan model berbeda. Log ID tugas agar Anda dapat memeriksa status secara manual jika diperlukan.
Q: Bagaimana cara menangkap kegagalan senyap?
Kegagalan senyap memerlukan validasi di lapisan aplikasi — API tidak akan memberi tahu bahwa output salah secara semantik. Periksa apakah output sesuai format yang diharapkan (JSON valid, label yang diharapkan, panjang minimum). Log seluruh output saat validasi gagal. Penyebab paling umum adalah prompt ambigu, instruksi format diabaikan, atau input yang terlalu pendek atau terlalu panjang untuk tugasnya.
