هناك طريقتان شائعتان لاستدعاء Qwen API: واجهة DashScope الأصلية في Alibaba Cloud Bailian، والواجهة المتوافقة مع OpenAI. بنية الأخطاء ليست متطابقة بينهما، لكن ترتيب التشخيص واحد: انظر أولًا إلى رمز حالة HTTP، ثم اقرأ code وmessage في جسم الاستجابة، ثم ابحث عن request_id، وأعد إنتاج المشكلة بطلب أدنى، ولا تعدّل المعاملات أو تغيّر النموذج إلا في النهاية.
يقسم هذا المقال الأخطاء إلى 401 و404 و429 و400 و5xx والمهلة، ويعرض جدول رموز الخطأ وحقول السجل وشيفرة إعادة المحاولة وقائمة التحقق قبل مخاطبة الدعم. النماذج والحصص والفوترة مرجعها الوثائق الرسمية وصفحة أسعار النماذج.
ملاحظة: Qwen وTongyi Qianwen وAlibaba Cloud وBailian وDashScope علامات تجارية لمجموعة Alibaba ومَن يملك حقوقها. هذا المقال مخصص للنقاش التقني، ولا توجد علاقة تبعية أو وكالة أو شراكة مع الشركات المذكورة. قد تتغير رموز الخطأ وقواعد حدود المعدل وسلوك الواجهات مع الإصدارات، والمرجع النهائي هو الوثائق الرسمية والاستجابة الفعلية.
أولًا: ميّز نمط الواجهة وبنية الخطأ
تُرجع واجهة DashScope الأصلية عادة code وmessage وrequest_id في المستوى الأعلى من الاستجابة؛ أما الواجهة المتوافقة مع OpenAI فتتبع أسلوب OpenAI، ويكون جسم الخطأ بالشكل {"error": {"message": "...", "type": "...", "code": "..."}}. وقد يظهر request_id في الوضع المتوافق داخل ترويسات الاستجابة أو جسمها، والاسم الفعلي يتبع ما تعيده الواجهة.
لا تحلل بنية واحدة فقط في الكود. سجّل جسم الاستجابة الكامل كما هو أولًا، ثم اقرأ القيمة بترتيب error.code ثم code في المستوى الأعلى. بهذا لا تحتاج إلى إعادة كتابة معالجة الأخطاء عند تغيير نمط الواجهة.
ثانيًا: 401: فشل المصادقة، افحص المنطقة أولًا
يعني 401 أن الهوية لم تجتز التحقق، ولا علاقة له عادة بقدرات النموذج أو الرصيد. الأخطاء الثلاثة الشائعة في Bailian هي:
InvalidApiKeyأوinvalid_api_key: المفتاح خاطئ أو نُسخ ناقصًا، أو أن Base URL لا يطابق منطقة المفتاح.invalid access token or token expired: انتهت صلاحية الرمز، أو استُخدم عنوان خاص بالباقة مع مفتاح عادي.NOT AUTHORIZED: WorkspaceId غير صالح، أو الحساب ليس ضمن مساحة العمل تلك.
يرتبط مفتاح Qwen بالمنطقة، ولا يمكن خلط مفتاح بكين ومفتاح سنغافورة. هذا هو البند الأكثر إغفالًا: دون تغيير الكود، ومجرد تبديل Base URL، يتكرر الخطأ 401 بثبات.
اتبع هذا الترتيب في التشخيص:
- تأكد من أن الترويسة
Authorization: Bearer <key>، مع مسافة واحدة بعد النقطتين. - اطبع بادئة متغير البيئة للتأكد من أنه ليس فارغًا أو قديمًا أو يحتوي سطرًا جديدًا.
- تحقق من تطابق Base URL مع منطقة المفتاح.
- تأكد من تطابق نوع المفتاح مع الواجهة: عناوين الدفع حسب الاستخدام وباقات Coding وباقات Token مختلفة.
- جرّب مفتاحًا جديدًا بطلب أدنى للتمييز بين مشكلة المفتاح ومشكلة الوسيط أو البوابة.
ثالثًا: 404 و400: اسم النموذج وحالة التفعيل والسياق
غالبًا لا يعني 404 عطلًا في الخدمة، بل مشكلة في اسم النموذج أو توفره. عند ظهور ModelNotFound أو model_not_found افحص ثلاثة أمور: هل الاسم مكتوب خطأ، وهل النموذج مفعّل، وهل هو متاح في المنطقة الحالية. أما model_not_supported فيعني أن النموذج لا يدعم الوضع المتوافق مع OpenAI، ويلزم تغيير الواجهة أو النموذج.
أسباب 400 أكثر تنوعًا. InvalidParameter تعني تجاوز معامل للحدود، مثل temperature أو top_p خارج النطاق؛ وInvalidParameter.NotSupportEnableThinking تعني أن النموذج لا يدعم معامل وضع التفكير. تجاوز السياق يعيد عادة Range of input length should be [1, xxx]، ورمزه الأساسي InternalError.Algo.InvalidParameter، وقد يكون السبب أيضًا تجاوز الطول الكلي للرسائل حد النموذج. الحل هو تقليم التاريخ أو تلخيصه على شكل مقاطع أو استخدام نموذج بسياق أطول، لا تكرار المحاولة.
رابعًا: 429: ميّز أربعة أنواع من الحدود واثنين من الحصص
الخطأ 429 هو الأكثر عرضة للمعالجة الخاطئة. يشمل 429 في Bailian على الأقل الفئات التالية؛ ويعرض الجدول أيضًا أخطاء التأخر في السداد وعدم التفعيل التي قد تُخلط مع حدود المعدل:
| HTTP | رمز الخطأ | المعنى | هل يُناسب إعادة المحاولة |
|---|---|---|---|
| 429 | Throttling.RateQuota، LimitRequests |
تجاوز عدد الطلبات في الدقيقة (RPM) | نعم، بعد تباعد |
| 429 | Throttling.AllocationQuota، insufficient_quota |
تجاوز عدد الرموز في الدقيقة (TPM) | نعم، مع تقليل الإدخال |
| 429 | Throttling.BurstRate |
حماية من اندفاع قصير | نعم، بتنعيم الإرسال |
| 429 | Throttling.Concurrency |
تزامن مرتفع جدًا | نعم، بخفض التزامن |
| 429 | AllocationQuota.FreeTierOnly |
نفاد الحصة المجانية مع تفعيل خيار الاقتصار عليها | لا، اشحن أو أغلق الخيار |
| 429 | BudgetLimitExceeded |
نفاد ميزانية الحساب | لا، عدّل الميزانية |
| 400 | Arrearage |
تأخر سداد أو حالة حساب غير طبيعية | لا، أصلح الحساب أولًا |
| 403 | AccessDenied.Unpurchased |
الخدمة غير مفعّلة | لا، فعّلها أولًا |
تُحسب حدود المعدل على مستوى الحساب الرئيسي في Alibaba Cloud، وتُجمع استدعاءات حسابات RAM الفرعية ومساحات العمل والمفاتيح المتعددة. لذلك لا تتجاوز «مفتاح لكل خدمة» الحصة الكلية، بل يمنع فقط التنافس بينها. تتعافى الحدود عادة خلال دقائق، فلا تكرر المحاولة بكثافة أثناء الانتظار.
خامسًا: المهلة و5xx: حدد الموضع على ثلاث مراحل
تُقسم المهلة إلى ثلاث مراحل: مهلة الاتصال تكون عادة بسبب DNS أو منفذ الخروج أو تعذر الوصول إلى الوسيط؛ وبطء أول رمز يعود غالبًا إلى صف الانتظار في المنبع أو إدخال طويل؛ ومهلة القراءة قد تعود إلى إخراج طويل أو استدلال مستغرق. تختلف معالجة الثلاث، ولا يكفي تكبير قيمة المهلة.
يعني RequestTimeOut في Bailian انتهاء مهلة الطلب، والحد الرسمي للطلب 300 ثانية؛ ويرتبط GatewayTimeout.InputDownload بتنزيل ملف الإدخال. قد تعيد الواجهة المتوافقة مع OpenAI أيضًا 503 عند overload المحرك. عند انقطاع طلب متدفق قد لا يوجد رمز خطأ، لذا سجّل المحتوى المستلم وآخر جزء وسبب الإنهاء، ثم حدد هل السبب تذبذب الشبكة أو انقطاع الوسيط أو مهلة المنبع.
سادسًا: request_id والسجلات: دليل التشخيص
توضح الوثائق الرسمية أن request_id يُعاد مع الاستجابة، وقد يكون في الترويسات أو الجسم. يُنصح بتسجيل مسارين: أن يولّد العميل trace_id يبقى ثابتًا عبر كل المحاولات، مع حفظ معرّف الطلب في الترويسات وrequest_id في الجسم كما هما. وإذا لم يُسجَّل شيء، يمكن البحث في سجلات مراقبة النماذج في لوحة التحكم بشرط تفعيل سجلات الاستدلال.
يجب أن تتضمن السجلات على الأقل: الوقت، trace_id، Base URL، المنطقة، اسم النموذج، رمز HTTP، رمز الخطأ، رسالة الخطأ، request_id، المدة، عدد المحاولات، وهل الطلب متدفق. يُسجَّل من المفتاح أول أحرف فقط أو بصمة تجزئة، ولا تُخزَّن نصوص المستخدم والاستجابات الكاملة ككتل. عند مخاطبة الدعم، يكون سجل واحد بهذه الحقول أنفع بكثير من عبارة «يحدث خطأ أحيانًا».
سابعًا: إعادة المحاولة والتباعد: شيفرة مثال
تُغلق الشيفرة التالية إعادة المحاولة التلقائية في SDK، وتعيد المحاولة فقط لـ 429 و5xx القابلة للاستعادة، وتطبع رمز الحالة ورمز الخطأ وrequest_id معًا. التأخر في السداد ونفاد الحصة المجانية وتجاوز الميزانية خارج نطاق إعادة المحاولة.
import os
import random
import time
import uuid
import httpx
from openai import OpenAI, APIConnectionError, APIStatusError, APITimeoutError
client = OpenAI(
api_key=os.environ["DASHSCOPE_API_KEY"],
base_url=os.environ.get(
"DASHSCOPE_BASE_URL",
"https://dashscope.aliyuncs.com/compatible-mode/v1",
),
timeout=httpx.Timeout(connect=5.0, read=180.0, write=30.0, pool=10.0),
max_retries=0, # إيقاف إعادة المحاولة التلقائية في SDK؛ تتولاها الشيفرة أدناه
)
RETRYABLE_429 = {
"Throttling",
"Throttling.RateQuota",
"Throttling.AllocationQuota",
"Throttling.BurstRate",
"Throttling.Concurrency",
"LimitRequests",
"limit_requests",
"ResourceExhausted",
}
def unpack_error(exc: APIStatusError):
body = {}
try:
body = exc.response.json()
except Exception:
pass
err = body.get("error", body) if isinstance(body, dict) else {}
code = err.get("code") if isinstance(err, dict) else None
request_id = None
if isinstance(body, dict):
request_id = body.get("request_id")
request_id = (
request_id
or exc.response.headers.get("x-request-id")
or exc.response.headers.get("x-dashscope-request-id")
)
return code, request_id, exc.response.text[:500]
def ask(prompt: str, model: str, max_attempts: int = 4) -> str:
trace_id = uuid.uuid4().hex[:12]
for attempt in range(1, max_attempts + 1):
try:
resp = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
)
return resp.choices[0].message.content
except APIStatusError as exc:
code, request_id, body = unpack_error(exc)
print(
f"trace={trace_id} attempt={attempt} "
f"http={exc.status_code} code={code} "
f"request_id={request_id} body={body}"
)
retryable = exc.status_code >= 500 or (
exc.status_code == 429 and code in RETRYABLE_429
)
if not retryable or attempt == max_attempts:
raise
except (APITimeoutError, APIConnectionError) as exc:
print(f"trace={trace_id} attempt={attempt} err={type(exc).__name__}")
if attempt == max_attempts:
raise
wait = min(2 ** (attempt - 1), 30) + random.uniform(0, 1)
time.sleep(wait)
raise RuntimeError("نفدت محاولات إعادة المحاولة")
ثلاث قواعد إضافية: احترم Retry-After إن أعاده الخادم؛ اضبط ميزانية إعادة المحاولة على مستوى النظام حتى لا تضاعف عدة خدمات الحركة في الوقت نفسه؛ وقبل إعادة إرسال طلب ذي أثر جانبي تأكد أولًا هل نُفّذ فعلًا.
ثامنًا: حوكمة التزامن: أنفع من تكبير عدد المحاولات
مشكلة حدود المعدل لا تُحل عادة بإعادة المحاولة، بل بحوكمة المدخل:
- قسّم المفاتيح حسب الأعمال، واضبط سقف تزامن لكل مفتاح حتى لا تُسقط مهمة دفعية الخدمة المباشرة.
- جزّئ المهام الدفعية ووزّعها خارج أوقات الذروة لتجنب نبضات الحركة عند رؤوس الساعات.
- استخدم طوابير لتنعيم القمم، وأدخل المهام الفاشلة في طابور تأخير بدل الدوران في المكان.
- راقب نسبة 429 وكمون p95، واخفض التزامن أولًا عند الارتفاع المفاجئ.
- فضّل واجهة Batch للمهام غير المتصلة بالوقت لتجنب حدود المعدل اللحظية.
- جهّز نموذجًا بديلًا أو مدخلًا بديلًا، وتأكد من توافق المعاملات قبل التبديل.
تُضبط عتبات الحدود حسب النموذج والحساب، ولا يؤدي الشحن تلقائيًا إلى رفع RPM أو TPM. عند الحاجة إلى حصة أعلى اتبع طلبًا تجاريًا ولا تنسخ أرقام فريق آخر.
تاسعًا: قائمة التحقق قبل مخاطبة الدعم
- وقت الحدوث والمنطقة الزمنية، وهل الأعطال متجمعة أم متفرقة.
- Base URL والمنطقة ومسار الواجهة واسم النموذج وطريقة الطلب.
- رمز حالة HTTP ورمز الخطأ ورسالة الخطأ الكاملة.
request_id؛ وإن لم يوجد فقدمtrace_idالذي ولّدته.- طلب curl أدنى قابل لإعادة الإنتاج مع إخفاء المفتاح.
- هل الطلب متدفق، وهل السياق طويل، وهل عُدّلت الإعدادات حديثًا.
- نطاق التأثير: مفتاح واحد أو مساحة عمل واحدة أم كل الخدمات.
عاشرًا: الأسئلة الشائعة
1. هل 429 يعني دائمًا تجاوز حد المعدل؟
لا. قد يعني أيضًا نفاد الحصة المجانية أو تجاوز الميزانية؛ وقد يعيد التأخر في السداد 400 برمز Arrearage، وقد يعيد عدم تفعيل الخدمة 403. اقرأ رمز الخطأ أولًا ثم قرر: تباعد أم شحن أم تعديل إعداد.
2. ماذا أفعل إذا فشل 401 بعد تغيير المفتاح؟
ركز على المنطقة ونوع المفتاح. مفتاح Qwen مرتبط بالمنطقة، ومفتاح بكين مع عنوان سنغافورة يعيد 401؛ والعنوان الخاص بباقة مع مفتاح عادي يفشل أيضًا. ثم افحص الترويسات ومتغيرات البيئة وهل يحذف الوسيط العكسي معلومات المصادقة.
3. كيف نعالج تجاوز السياق؟
قلّم رسائل التاريخ واحذف المرفقات غير ذات الصلة والمقاطع المكررة؛ ثم لخّص المستندات الطويلة على شكل مقاطع أو استخدم الاسترجاع؛ وإن لم يكفِ ذلك فانتقل إلى نموذج بسياق أطول. لا تحاول حل تجاوز الإدخال برفع max_tokens.
4. هل يجب إعادة المحاولة عند المهلة؟
أعد المحاولة بعدد محدود فقط لفشل الاتصال ومهلة القراءة، مع تباعد أُسّي وسقف. أما العمليات ذات الأثر الجانبي فتأكد أولًا هل نُفّذت لتجنب تكرار الطلب أو الكتابة.
5. أين أجد request_id؟
انظر أولًا في ترويسات الاستجابة وجسمها، والاسم الفعلي يتبع الواجهة؛ وإن لم يكن مسجلًا فابحث في سجلات مراقبة النماذج في لوحة التحكم بشرط تفعيل سجلات الاستدلال. عند مخاطبة الدعم قدم trace_id وطلب إعادة إنتاج أدنى معًا.
الخطوة التالية
أنشئ مفتاح اختبار في ZSQH AI Gateway، ونفّذ تمرينًا على 401 و404 و429 وتجاوز السياق والمهلة، ثم اربط مراقبة الإنتاج. النماذج المتاحة وطريقة الفوترة في أسعار النماذج.
