Kegagalan API AI berbeza daripada kegagalan API biasa. Respons 200 tidak bermaksud penjanaan anda berjaya. Medan content yang null tidak semestinya satu ralat. Dan gesaan yang berjaya semalam mungkin gagal hari ini kerana penyedia mengemas kini dasar kandungan mereka.
Panduan ini merangkumi cara membaca ralat API AI, maksud setiap mod kegagalan, dan cara membina pengendalian ralat yang memberitahu anda apa yang rosak dan bukannya sekadar sesuatu telah rosak.
Nota: Nama model seperti gpt-5.4 dan gpt-5.4-mini yang digunakan dalam artikel ini ialah pengecam platform CometAPI. Ia berfungsi melalui https://api.cometapi.com/v1 sahaja — bukan terus melalui API OpenAI atau Anthropic. Lihat senarai model penuh.
Mengapa nyahpepijat API AI lebih sukar berbanding nyahpepijat API biasa
Dengan API REST lazim, 200 bermaksud berjaya dan 4xx bermaksud anda melakukan sesuatu yang salah. API AI menambah kategori ketiga: kegagalan lembut — respons yang memulangkan 200 tetapi tidak mengandungi kandungan yang boleh digunakan.
Tiga perkara boleh berlaku:
- Kegagalan keras — ralat HTTP (4xx, 5xx). Permintaan tidak selesai.
- Kegagalan lembut — HTTP 200, tetapi
finish_reasonialahcontent_filterataulength, ataucontentialahnull. - Kegagalan senyap — HTTP 200, kandungan kelihatan baik, tetapi output salah dengan cara yang hanya dapat anda kesan di lapisan aplikasi.
Kebanyakan pengendalian ralat hanya merangkumi kes 1. Kes 2 dan 3 ialah tempat kebanyakan pepijat produksi wujud.
Fahami format respons ralat
Titik akhir penyempurnaan teks memulangkan struktur ralat 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" }}
Titik akhir imej dan video memulangkan format ralat yang berbeza — sentiasa hurai badan respons mentah dan jangan menganggap struktur tetap merentas titik akhir.
Medan message biasanya memberitahu anda tepat apa yang salah. Medan param memberitahu anda parameter mana yang menyebabkannya. Sentiasa log kedua-duanya.
Ketahui maksud setiap kod status HTTP
| Status | Maksud | Punca biasa | Penyelesaian |
|---|---|---|---|
| 400 | Permintaan tidak sah | Model hilang, parameter salah untuk model ini | Semak error.param dalam respons |
| 401 | Tidak dibenarkan | Kunci API salah atau tiada | Sahkan format Authorization: Bearer <key> |
| 429 | Dihadkan kadar | Terlalu banyak permintaan | Penundaan eksponen (rujuk Langkah 4) |
| 500 | Ralat pelayan | Isu pihak penyedia, atau badan permintaan salah bentuk | Cuba semula dengan penundaan; semak format permintaan |
| 504 | Habisan masa pintu masuk | Penyedia mengambil masa terlalu lama | Cuba semula; pertimbangkan model lebih pantas |
Sumber**:** Dokumentasi chat completions CometAPI
Perbezaan 400 vs 500 penting untuk logik cuba semula. 400 bermaksud permintaan anda salah — mencuba semula permintaan yang sama tidak akan membantu. 500 atau 504 bermaksud pelayan mempunyai masalah — mencuba semula masuk akal.
Semak finish_reason — medan yang paling kerap terlepas pandang
Respons 200 dengan finish_reason: "content_filter" bermaksud penjanaan anda disekat. Medan content akan null atau kosong. Jika anda tidak menyemaknya, aplikasi anda akan diam-diam mengembalikan tiada apa-apa.
| finish_reason | Maksud | Apa yang perlu dibuat | Penyelesaian |
|---|---|---|---|
| stop | Penyempurnaan normal | Tiada — ini kejayaan | Semak error.param dalam respons |
| length | Mencapai had token | Tambahkan max_tokens atau pendekkan gesaan | Sahkan format Authorization: Bearer <key> |
| content_filter | Disekat oleh dasar keselamatan | Ubah ayat gesaan; elakkan nama/topik tertentu | Penundaan eksponen (rujuk Langkah 4) |
| tool_calls | Model memanggil alat dan bukannya memulangkan teks | Tangani panggilan alat; content akan null | Cuba semula dengan penundaan; semak format permintaan |
| 504 | Habisan masa pintu masuk | Penyedia mengambil masa terlalu lama | Cuba semula; pertimbangkan model lebih pantas |
Sumber**:** Dokumentasi chat completions CometAPI
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 tidak ditetapkan")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: """ Lengkapkan permintaan chat dengan pengendalian ralat dan finish_reason penuh. Memulangkan {"content": str, "finish_reason": str, "tool_calls": list | None} Naikkan pengecualian pada ralat API. """ 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"Ralat API status={e.status_code} " f"mesej={error_body.get('message')} " f"param={error_body.get('param')}" ) raise except (APIConnectionError, APITimeoutError) as e: logging.error(f"Ralat rangkaian/tamat masa: {e}") raise choice = response.choices[0] finish_reason = choice.finish_reason if finish_reason == "content_filter": raise ValueError( f"Penjanaan disekat oleh penapis kandungan. " f"Model: {model}. Ubah ayat gesaan." ) if finish_reason == "length": used = response.usage.completion_tokens if response.usage else "unknown" logging.warning(f"Output dipotong pada had token. Digunakan {used} token.") # Pulangkan hasil berstruktur supaya pemanggil boleh mengendalikan tool_calls secara eksplisit return { "content": choice.message.content or "", "finish_reason": finish_reason, "tool_calls": choice.message.tool_calls, }# Penggunaanresult = safe_complete( messages=[{"role": "user", "content": "Ringkaskan artikel ini: [text]"}], model="gpt-5.4-mini")if result["finish_reason"] == "tool_calls": # Tangani panggilan alat — content akan kosong print("Model mahu memanggil alat:", result["tool_calls"])else: print(result["content"])
Kesan kegagalan senyap di lapisan aplikasi
Kegagalan senyap paling sukar ditangkap. API memulangkan 200, finish_reason ialah stop, tetapi output salah dari segi semantik. Anda hanya boleh mengesannya di lapisan aplikasi.
Corak biasa:
def validate_completion(result: dict, task: str) -> str: """ Pengesahan pada lapisan aplikasi untuk kegagalan senyap. Naikkan ValueError jika output tidak memenuhi jangkaan asas. """ content = result["content"].strip() # Output kosong yang bukan panggilan alat if not content and result["finish_reason"] != "tool_calls": raise ValueError(f"Output kosong untuk tugasan '{task}' dengan finish_reason='{result['finish_reason']}'") # Semakan khusus tugasan if task == "classify": valid_labels = {"positive", "negative", "neutral"} if content.lower() not in valid_labels: logging.warning( f"Output pengelasan tidak dijangka: '{content}'. " f"Jangka salah satu daripada {valid_labels}. " f"Model mungkin mengembalikan penjelasan dan bukannya label." ) if task == "json_extract": import json try: json.loads(content) except json.JSONDecodeError: raise ValueError( f"Menjangka output JSON tetapi mendapat: '{content[:100]}...'. " f"Cuba tambah 'balas dengan JSON sah sahaja' pada gesaan, " f"atau gunakan response_format={{\"type\": \"json_object\"}}." ) if task == "summarize" and len(content.split()) < 10: logging.warning( f"Ringkasan terlalu pendek ({len(content.split())} perkataan). " f"Semak sama ada input terlalu pendek atau model tersalah faham tugasan." ) return content# Aliran penuh dengan pengesanan kegagalan senyapresult = safe_complete( messages=[{"role": "user", "content": "Kelaskan sebagai positive/negative/neutral: 'Great product!'"}], model="claude-haiku-4-5")label = validate_completion(result, task="classify")
Kegagalan senyap biasanya berpunca daripada salah satu daripada tiga sumber: gesaan adalah samar, model mengabaikan arahan format anda, atau input terlalu pendek/panjang untuk tugasan. Merekodkan output penuh apabila pengesahan gagal ialah cara terpantas untuk mendiagnosis yang mana satu.
Tambah penundaan eksponen untuk had kadar
Ralat had kadar (429) adalah sementara. Tindakan yang betul ialah menunggu dan cuba semula dengan sela yang meningkat — amalan standard untuk mana-mana API yang mempunyai had kadar:
import timeimport randomfrom openai import RateLimitErrordef complete_with_retry( messages: list, model: str = "gpt-5.4-mini", max_retries: int = 3, **kwargs) -> dict: """Cuba semula pada had kadar dan ralat pelayan dengan penundaan eksponen.""" 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: jangan cuba semula, permintaan adalah salah 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 mengelakkan fenomena 'thundering herd' logging.warning(f"Cubaan {attempt + 1} gagal. Menunggu {wait:.1f}s sebelum cuba semula.") time.sleep(wait) raise RuntimeError(f"Semua {max_retries} cubaan gagal") from last_error
Jangan cuba semula pada 400 atau 401 — itu ralat klien yang tidak akan selesai sendiri.
Nyahpepijat kegagalan penjanaan imej
Penjanaan imej mempunyai mod kegagalan tersendiri di atas ralat HTTP standard:
import base64import requestsdef generate_image_safe(prompt: str, model: str = "dall-e-3") -> dict: """ Jana imej dengan pengendalian ralat penuh. Memulangkan {"url": str | None, "bytes": bytes | None, "blocked": bool} """ api_key = os.environ.get("COMETAPI_KEY") if not api_key: raise ValueError("COMETAPI_KEY tidak ditetapkan") 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"Ralat HTTP penjanaan imej: {e.response.status_code} {e.response.text}") raise except requests.exceptions.Timeout: logging.error("Penjanaan imej tamat masa selepas 60s") raise data = response.json().get("data", []) if not data: logging.warning("Penjanaan imej memulangkan data kosong — gesaan mungkin telah ditapis.") return {"url": None, "bytes": None, "blocked": True} item = data[0] if "revised_prompt" in item: logging.info(f"Penyedia menyemak semula gesaan kepada: {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 }
Isu khusus imej yang perlu diperhatikan:
| Simptom | Punca | Penyelesaian |
|---|---|---|
| Tatasusunan data kosong | Gesaan ditapis | Semak revised_prompt; ubah ayat |
| ralat response_format pada GPT Image 2 | Parameter tidak disokong | Guna output_format sebagai ganti |
| n > 1 ralat pada Qwen Image | Had model | Ulang permintaan dalam gelung |
| URL memulangkan 403 kemudian | URL tamat tempoh | Muat turun serta-merta selepas penjanaan |
Sumber**:** Dokumentasi penjanaan imej CometAPI
Nyahpepijat kegagalan penjanaan video
Penjanaan video gagal dengan cara berbeza kerana ia asinkron. Mulakan pembolehubah status sebelum gelung supaya mesej ralat tamat masa sentiasa terbentuk elok:
def submit_and_poll_video( prompt: str, model: str = "veo3-fast", max_wait: int = 600) -> str: """Hantar tugasan video dan tinjau hingga siap. Memulangkan URL video.""" api_key = os.environ.get("COMETAPI_KEY") if not api_key: raise ValueError("COMETAPI_KEY tidak ditetapkan") 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"Penyerahan video gagal: {e.response.status_code} {e.response.text}") raise task_id = response.json()["id"] logging.info(f"Tugasan video dihantar: {task_id}") poll_url = f"https://api.cometapi.com/v1/videos/{task_id}" elapsed = 0 interval = 10 status = "unknown" # inisialisasi sebelum gelung progress = 0 # inisialisasi sebelum gelung 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"Permintaan tinjauan gagal: {e.response.status_code}") raise result = poll_response.json() status = result.get("status", "unknown") progress = result.get("progress", 0) logging.info(f"Tugasan {task_id}: status={status} progress={progress}%") if status == "succeeded": return result["output"][0] elif status in ("failed", "cancelled"): error_detail = result.get("error", "tiada butiran ralat dipulangkan") raise RuntimeError(f"Tugasan video {task_id} gagal: {error_detail}") time.sleep(interval) elapsed += interval raise TimeoutError( f"Tugasan video {task_id} tidak selesai dalam {max_wait}s. " f"Status terakhir: {status}, kemajuan: {progress}%" )
Isu khusus video:
| Simptom | Punca | Penyelesaian |
|---|---|---|
Tugas tersekat dalam queued >10 min | Beban pelayan | Cuba model lain |
| failed tanpa butiran ralat | Gesaan ditapis atau ralat model | Ubah ayat gesaan |
| URL video memulangkan 403 | URL tamat tempoh | Muat turun serta-merta |
| task_not_exist pada tinjauan Runway pertama | Tugas masih memulakan (tingkah laku didokumenkan CometAPI) | Tunggu 5s dan cuba semula |
| Kling memulangkan "succeed" bukan "succeeded" | API Kling menggunakan rentetan status tidak piawai | Tangani kedua-duanya dalam logik tinjauan |
Sumber**:** Dokumentasi video CometAPI untuk veo3, Dokumentasi Kling Video
Versi Node.js
import OpenAI from 'openai';const apiKey = process.env.COMETAPI_KEY;if (!apiKey) throw new Error('COMETAPI_KEY tidak ditetapkan');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(`Ralat klien ${err.status}: ${err.message}`); } else { console.error(`Ralat pelayan/rangkaian: ${err.message}`); } throw err; } const choice = response.choices[0]; const finishReason = choice.finish_reason; if (finishReason === 'content_filter') { throw new Error(`Penjanaan disekat oleh penapis kandungan. Model: ${model}`); } if (finishReason === 'length') { console.warn(`Output dipotong. Digunakan ${response.usage?.completion_tokens ?? 'unknown'} token.`); } 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) { // Jangan cuba semula ralat klien 4xx if (err.status && err.status < 500) throw err; lastError = err; if (attempt < maxRetries - 1) { const wait = (2 ** attempt + Math.random()) * 1000; console.warn(`Cubaan ${attempt + 1} gagal. Mencuba semula dalam ${(wait / 1000).toFixed(1)}s`); await new Promise(r => setTimeout(r, wait)); } } } throw new Error(`Semua ${maxRetries} cubaan gagal: ${lastError?.message}`);}// Penggunaanconst result = await safeComplete( [{ role: 'user', content: 'Kelaskan sebagai positive/negative/neutral: "Great product!"' }], 'claude-haiku-4-5');if (result.finishReason === 'tool_calls') { console.log('Panggilan alat diminta:', result.toolCalls);} else { console.log(result.content);}
Senarai semak nyahpepijat
Apabila penjanaan gagal dan anda tidak pasti di mana hendak bermula:
Untuk penjanaan teks:
- Adakah kunci API ditetapkan dan dalam format
Authorization: Bearer <key>? - Adakah
finish_reasonselainstop? - Adakah
contentnull? Semak sama adafinish_reasonialahtool_calls - Adakah output dipotong? Semak
finish_reason: "length"danusage.completion_tokens - Adakah ralat 4xx (betulkan permintaan) atau 5xx (cuba semula)?
- Adakah output lulus pengesahan lapisan aplikasi anda? (kegagalan senyap)
Untuk penjanaan imej:
- Adakah tatasusunan
datakosong? (penapis kandungan) - Adakah anda menggunakan
response_formatpada GPT Image 2? (tidak disokong — gunaoutput_format) - Adakah anda menetapkan
n > 1pada Qwen Image? (tidak disokong) - Adakah anda memuat turun imej sebelum URL tamat tempoh?
Untuk penjanaan video:
- Adakah tugas tersekat dalam
queued? (cuba model lain) - Adakah anda menyemak medan
errordalam respons tugas yang gagal? - Adakah anda memuat turun video sebelum URL tamat tempoh?
- Adakah anda mengendalikan kedua-dua
"succeed"(Kling) dan"succeeded"(Veo, Runway)?
Soalan Lazim
S: Permintaan saya mengembalikan 200 tetapi tiada kandungan. Apa yang berlaku?
Semak finish_reason. Jika content_filter, penjanaan telah disekat — permintaan berjaya tetapi output ditindas. Jika tool_calls, model memanggil alat dan bukannya memulangkan teks, dan content adalah null secara reka bentuk. Jika finish_reason ialah stop tetapi kandungan masih kosong, itu kegagalan senyap — log keseluruhan respons dan semak gesaan anda.
S: Bagaimana saya tahu jika gesaan saya ditapis?
Untuk teks: semak finish_reason === "content_filter". Untuk imej: semak sama ada tatasusunan data kosong. Untuk video: semak sama ada tugas mencapai status failed sejurus selepas penyerahan tanpa butiran ralat. Dalam semua kes, cuba ubah ayat gesaan supaya lebih neutral.
S: Bila patut saya mencuba semula permintaan yang gagal?
Cuba semula pada 429 dan 5xx menggunakan penundaan eksponen. Jangan cuba semula pada 4xx — permintaan yang tidak sah tidak akan pulih sendiri. Pengecualian ialah 401 jika anda memutar kunci API.
S: Apa itu penundaan eksponen dan mengapa ia penting?
Daripada mencuba semula serta-merta, anda menunggu semakin lama: 1s, 2s, 4s. Menambah jitter (+ random.random()) mengelakkan berbilang klien mencuba semula serentak. Ini amalan standard untuk mana-mana API dengan had kadar — bukan khusus kepada CometAPI.
S: Tugas video tersekat dalam queued selama 10 minit. Adakah ia gagal?
Tidak semestinya — baris giliran boleh sesak apabila beban tinggi. Tunggu sehingga ambang max_wait anda, kemudian naikkan TimeoutError dan cuba semula dengan model berbeza. Log ID tugas supaya anda boleh menyemak status secara manual jika perlu.
S: Bagaimana saya menangkap kegagalan senyap?
Kegagalan senyap memerlukan pengesahan pada lapisan aplikasi — API tidak akan memberitahu anda bahawa output salah dari segi semantik. Semak bahawa output sepadan format yang dijangka (JSON sah, label dijangka, panjang minimum). Log output penuh apabila pengesahan gagal. Punca paling biasa ialah gesaan samar, arahan format diabaikan, atau input terlalu pendek/panjang untuk tugasan.
