إخفاقات واجهات برمجة تطبيقات الذكاء الاصطناعي تختلف عن الإخفاقات المعتادة في واجهات البرمجة. إن استجابة 200 لا تعني أن عملية التوليد نجحت. كما أن الحقل content بقيمة null ليس دائماً خطأ. وقد يفشل نفس المُوجِّه الذي عمل بالأمس اليوم لأن مزوّد الخدمة حدّث سياسة المحتوى لديه.
يغطي هذا الدليل كيفية قراءة أخطاء واجهات الذكاء الاصطناعي، وماذا يعني كل نمط من أنماط الإخفاق فعلياً، وكيفية بناء معالجة أخطاء تُخبرك بما تعطّل بدلاً من مجرد أن شيئاً ما تعطّل.
ملاحظة: أسماء النماذج مثل gpt-5.4 وgpt-5.4-mini المستخدمة في هذا المقال هي معرّفات منصة CometAPI. تعمل عبر https://api.cometapi.com/v1 فقط — وليس مباشرة عبر واجهات OpenAI أو Anthropic. راجع قائمة النماذج الكاملة.
لماذا يُعد تصحيح أخطاء واجهات الذكاء الاصطناعي أصعب من واجهات البرمجة المعتادة
في واجهة REST النموذجية، تُعد 200 نجاحاً و4xx تعني أنك فعلت شيئاً خاطئاً. تضيف واجهات الذكاء الاصطناعي فئة ثالثة: إخفاقات لينة — استجابات تُعيد 200 لكنها لا تحتوي على محتوى قابل للاستخدام.
ثلاثة أمور قد تسوء:
- إخفاق صارم — خطأ HTTP (4xx، 5xx). لم يُستكمل الطلب.
- إخفاق لين — HTTP 200، لكن
finish_reasonهيcontent_filterأوlength، أو أنcontentتساويnull. - إخفاق صامت — HTTP 200، يبدو المحتوى جيداً ظاهرياً، لكن المخرجات خاطئة بطريقة لا تكتشفها إلا على طبقة التطبيق.
معظم معالجات الأخطاء تغطي الحالة 1 فقط. بينما تكمن أغلب علل الإنتاج في الحالتين 2 و3.
فهِم صيغة استجابة الخطأ
يعيد منفذ إكمال النص بنية خطأ متسقة:
{ "error": { "message": "human-readable description (often includes request id)", "type": "comet_api_error", "param": "the_problematic_parameter_or_null", "code": "error_code_or_null" }}
تعيد منافذ الصور والفيديو صيغ أخطاء مختلفة — حلّل دائماً جسم الاستجابة الخام بدلاً من افتراض بنية ثابتة عبر المنافذ.
يُخبرك الحقل message عادةً بماهية الخطأ بالضبط. ويخبرك الحقل param بأي معلمة تسببت به. سجّل الاثنين دائماً.
اعرف ماذا يعني كل رمز حالة HTTP
| Status | Meaning | Common cause | Fix |
|---|---|---|---|
| 400 | طلب غير صالح | نموذج مفقود، معلمة غير مناسبة لهذا النموذج | تحقّق من error.param في الاستجابة |
| 401 | غير مُخوّل | مفتاح API مفقود أو غير صحيح | تحقّق من تنسيق Authorization: Bearer <key> |
| 429 | حد المعدل | عدد كبير جداً من الطلبات | تراجُع أُسي (راجع الخطوة 4) |
| 500 | خطأ خادم | مشكلة من جهة المزوّد، أو جسم طلب غير صحيح | أعد المحاولة مع تراجُع؛ تحقّق من صيغة الطلب |
| 504 | انتهاء مهلة البوابة | استغرق المزوّد وقتاً طويلاً | أعد المحاولة؛ فكّر في نموذج أسرع |
المصدر**: وثائق CometAPI لإكمالات الدردشة
يُهمّ التمييز بين 400 و500 في منطق إعادة المحاولة. تشير 400 إلى أن طلبك خاطئ — إعادة نفس الطلب لن تُجدي. وتشير 500 أو 504 إلى مشكلة في الخادم — من المنطقي إعادة المحاولة.
تحقّق من finish_reason — الحقل الأكثر إغفالاً
تعني استجابة 200 مع finish_reason: "content_filter" أن التوليد تم حجبه. سيكون الحقل content بـnull أو فارغاً. إذا لم تتحقق من ذلك، فسيُرجع تطبيقك لا شيء بصمت.
| finish_reason | Meaning | What to do | Fix |
|---|---|---|---|
| stop | إتمام طبيعي | لا شيء — هذا نجاح | تحقّق من error.param في الاستجابة |
| length | بلوغ حد الرموز | زد max_tokens أو قصّر المُوجّه | تحقّق من تنسيق Authorization: Bearer <key> |
| content_filter | حُجب بسبب سياسة الأمان | أعد صياغة المُوجّه؛ تجنّب أسماء/مواضيع بعينها | تراجُع أُسي (راجع الخطوة 4) |
| tool_calls | استدعى النموذج أداة بدلاً من إرجاع نص | تعامل مع نداء الأداة؛ سيكون المحتوى null | أعد المحاولة مع تراجُع؛ تحقّق من صيغة الطلب |
| 504 | انتهاء مهلة البوابة | استغرق المزوّد وقتاً طويلاً | أعد المحاولة؛ فكّر في نموذج أسرع |
المصدر**: وثائق 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 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"])
اكتشف الإخفاقات الصامتة على طبقة التطبيق
الإخفاقات الصامتة هي الأصعب في الالتقاط. تُعيد الواجهة 200، وfinish_reason هي stop، لكن المخرجات خاطئة دلالياً. لا يمكنك التقاط هذه إلا على طبقة التطبيق.
أنماط شائعة:
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")
تنبع الإخفاقات الصامتة عادةً من ثلاثة مصادر: المُوجّه غامض، تجاهل النموذج تعليمات التنسيق، أو أن الإدخال قصير/طويل جداً بالنسبة للمهمة. إن تسجيل المخرجات كاملةً عند فشل التحقق هو أسرع طريقة لتشخيص السبب.
أضف تراجُعاً أُسياً لقيود المعدل
أخطاء الحد المعدل (429) مؤقتة. الاستجابة الصحيحة هي الانتظار وإعادة المحاولة مع زيادات زمنية — ممارسة قياسية لأي واجهة ذات حدود معدل:
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
لا تُعد المحاولة عند 400 أو 401 — فهذه أخطاء عميل ولن تُحل من تلقاء نفسها.
تصحيح إخفاقات توليد الصور
يملك توليد الصور أنماط إخفاق خاصة به بالإضافة إلى أخطاء HTTP القياسية:
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 }
مشكلات خاصة بالصور يجب الانتباه لها:
| Symptom | Cause | Fix |
|---|---|---|
| مصفوفة data فارغة | تم ترشيح المُوجّه | تحقّق من revised_prompt؛ أعد الصياغة |
| خطأ response_format على GPT Image 2 | معلمة غير مدعومة | استخدم output_format بدلاً منها |
| خطأ n > 1 على Qwen Image | قيود في النموذج | نفّذ الطلبات في حلقة بدلاً من ذلك |
| URL يُعيد 403 لاحقاً | انتهاء صلاحية الرابط | نزّل فوراً بعد التوليد |
المصدر**: وثائق توليد الصور في CometAPI
تصحيح إخفاقات توليد الفيديو
يفشل توليد الفيديو بطريقة مختلفة لأنه غير متزامن. هيّئ متغيرات الحالة قبل الحلقة كي تكون رسالة انتهاء المهلة مُهيكلة دائماً:
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}%" )
مشكلات خاصة بالفيديو:
| Symptom | Cause | Fix |
|---|---|---|
| المهمة عالقة في queued لأكثر من 10 دقائق | ضغط على الخادم | أعد المحاولة بنموذج مختلف |
| failed دون تفاصيل خطأ | تم ترشيح المُوجّه أو خطأ في النموذج | أعد صياغة المُوجّه |
| عنوان الفيديو URL يُعيد 403 | انتهاء صلاحية الرابط | نزّل فوراً |
| task_not_exist في أول استعلام لـ Runway | لازالت المهمة في التهيئة (سلوك موثق في CometAPI) | انتظر 5 ثوانٍ وأعد المحاولة |
| تُعيد Kling "succeed" لا "succeeded" | واجهة Kling تستخدم حالة غير قياسية | تعامل مع الحالتين في المنطق |
المصدر**: وثائق توليد الفيديو في CometAPI**, وثائق Kling Video
نسخة 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);}
قائمة تحقق للتنقيح
عندما تفشل عملية توليد ولست متأكداً من أين تبدأ:
للنصوص:
- هل تم ضبط مفتاح API وبصيغة
Authorization: Bearer <key>؟ - هل
finish_reasonشيء غيرstop؟ - هل
contentnull؟ تحقّق مما إذا كانتfinish_reasonهيtool_calls - هل تم اقتطاع المخرجات؟ افحص
finish_reason: "length"وusage.completion_tokens - هل الخطأ من نوع 4xx (صلّح الطلب) أم 5xx (أعِد المحاولة)؟
- هل اجتازت المخرجات التحقق على طبقة التطبيق؟ (إخفاق صامت)
لتوليد الصور:
- هل مصفوفة
dataفارغة؟ (ترشيح المحتوى) - هل استخدمت
response_formatمع GPT Image 2؟ (غير مدعوم — استخدمoutput_format) - هل ضبطت
n > 1على Qwen Image؟ (غير مدعوم) - هل نزّلت الصورة قبل انتهاء صلاحية الرابط؟
لتوليد الفيديو:
- هل المهمة عالقة في
queued؟ (جرّب نموذجاً مختلفاً) - هل تحقّقت من الحقل
errorفي استجابة المهمة الفاشلة؟ - هل نزّلت الفيديو قبل انتهاء صلاحية الرابط؟
- هل تتعامل مع كلٍ من
"succeed"(Kling) و"succeeded"(Veo، Runway)؟
الأسئلة الشائعة
س: طلبي يُعيد 200 لكن لا يوجد محتوى. ماذا حدث؟
تحقّق من finish_reason. إذا كانت content_filter، فقد تم حجب التوليد — نجح الطلب لكن تم حجب الناتج. إذا كانت tool_calls، فقد استدعى النموذج أداة بدلاً من إرجاع نص، وcontent تكون null بحُكم التصميم. إذا كانت finish_reason هي stop لكن المحتوى لا يزال فارغاً، فهذا إخفاق صامت — سجّل الاستجابة كاملةً وتحقّق من المُوجّه.
س: كيف أعرف إن كان مُوجّهي يُرشَّح؟
للنص: تحقّق من finish_reason === "content_filter". للصور: تحقّق مما إذا كانت مصفوفة data فارغة. للفيديو: تحقّق مما إذا كانت المهمة تصل إلى حالة failed بعد الإرسال بفترة وجيزة دون تفاصيل خطأ. في جميع الحالات، حاول إعادة صياغة المُوجّه ليكون أكثر حياداً.
س: متى يجب أن أعيد محاولة الطلب الفاشل؟
أعِد المحاولة عند 429 و5xx باستخدام تراجُع أُسي. لا تُعد المحاولة عند 4xx — فالطلب السيئ لن يُصلح نفسه. الاستثناء هو 401 إذا كنت تُدوّر مفاتيح API.
س: ما هو التراجُع الأُسي ولماذا يهم؟
بدلاً من إعادة المحاولة فوراً، تنتظر فترات أطول تدريجياً: 1ث، 2ث، 4ث. إضافة تباين عشوائي (+ random.random()) يمنع تزامن العملاء في إعادة المحاولات. هذه ممارسة قياسية لأي واجهة ذات حدود معدل — وليست خاصة بـ CometAPI.
س: المهمة عالقة في queued لمدة 10 دقائق. هل فشلت؟
ليس بالضرورة — قد تتكدّس الطوابير تحت الحمل. انتظر حتى حد max_wait لديك، ثم ارفع TimeoutError وأعِد المحاولة بنموذج مختلف. سجّل معرّف المهمة لتتمكن من فحص الحالة يدوياً عند الحاجة.
س: كيف ألتقط الإخفاقات الصامتة؟
تتطلب الإخفاقات الصامتة تحقّقاً على طبقة التطبيق — لن تُخبرك الواجهة أن المخرجات خاطئة دلالياً. تحقّق من مطابقة المخرجات للتنسيق المتوقع (JSON صالح، تصنيف متوقع، طول أدنى). سجّل المخرجات كاملةً عند فشل التحقق. أكثر الأسباب شيوعاً هي مُوجّهات غامضة، تجاهل تعليمات التنسيق، أو مدخلات قصيرة جداً أو طويلة جداً بالنسبة للمهمة.
