DEV Community

Cover image for التعافي من أخطاء وكلاء الذكاء الاصطناعي: أنماط إعادة المحاولة، المهلة، التراجع، وقاطع الدائرة
Yusuf Khalidd
Yusuf Khalidd

Posted on • Originally published at apidog.com

التعافي من أخطاء وكلاء الذكاء الاصطناعي: أنماط إعادة المحاولة، المهلة، التراجع، وقاطع الدائرة

وكيلك يستدعي واجهة برمجة تطبيقات (API)، فترد بالرمز 429. يعيد الوكيل المحاولة فورًا، ويتلقى 429 أخرى، ثم يكرر ذلك حتى تتوقف العملية أو ترتفع الفاتورة. هذه الحلقة لا يكتبها أحد عمدًا؛ بل تنتج عن معالجة أخطاء ساذجة، وهي من أكثر المشكلات التي يناقشها المطورون في لوحة مناقشة Anthropic SDK.

جرّب Apidog اليوم

استعادة الأخطاء هي ما يفصل العرض التوضيحي النظيف عن وكيل جاهز للإنتاج. المشكلة ليست في النموذج، بل في تصرف كودك عندما تكون استدعاءات الأدوات بطيئة أو محدودة أو معطلة. يشرح هذا الدليل الأنماط الأساسية التي تحتاجها: إعادة المحاولة مع التراجع التدريجي، والمهل الزمنية، وقواطع الدائرة، ومفاتيح الثبات، ثم اختبارها بمحاكاة قبل أن يكتشف المستخدم المشكلة. للحصول على نظرة أوسع، ابدأ بـلماذا تتعطل وكلاء الذكاء الاصطناعي في بيئة الإنتاج.

لا يمكنك اختبار الاستعادة مقابل واجهة برمجة تطبيقات سليمة

عندما تعمل التبعية بشكل جيد في التطوير، لا يعمل مسار الاستعادة أصلًا. تنجح الاستدعاءات، يبدو العرض التوضيحي جيدًا، ثم يُنشر الوكيل. أول تشغيل فعلي للتراجع التدريجي قد يحدث في الإنتاج، أثناء انقطاع حقيقي ومع مستخدمين حقيقيين.

اختبر الاستعادة بإنتاج الإخفاقات عمدًا. أنشئ محاكاة لواجهة برمجة التطبيقات، ثم برمجها لإرجاع:

  • 429 مع الرأس Retry-After
  • 500 أو 503
  • مهلة زمنية
  • جسم استجابة غير سليم
  • استجابة ناجحة بعد عدة إخفاقات

وجّه أداة الوكيل إلى عنوان المحاكاة بدل الخدمة الحية، ثم تحقق من عدد المحاولات والتأخيرات والرؤوس المرسلة. يتيح Apidog إعداد المحاكاة وبرمجة الاستجابات التي تحتاج إليها.

إعادة المحاولة مع التراجع التدريجي الأسي والارتعاش

إعادة المحاولة الفورية مناسبة نادرًا. عند ضغط الخدمة، سيعيد كل عميل فاشل المحاولة في اللحظة نفسها، فتزداد المشكلة سوءًا.

استخدم التراجع التدريجي الأسي:

المحاولة 1: انتظر 1 ثانية
المحاولة 2: انتظر ثانيتين
المحاولة 3: انتظر 4 ثوانٍ
المحاولة 4: انتظر 8 ثوانٍ
Enter fullscreen mode Exit fullscreen mode

ثم أضف ارتعاشًا (jitter) عشوائيًا حتى لا تتزامن المحاولات بين العملاء.

مثال Python مبسط:

import random
import time

def backoff_delay(attempt: int, base: float = 1.0, cap: float = 30.0) -> float:
    exponential = min(cap, base * (2 ** attempt))
    return random.uniform(0, exponential)

for attempt in range(5):
    try:
        response = call_external_api()
        response.raise_for_status()
        break
    except TransientError:
        if attempt == 4:
            raise

        time.sleep(backoff_delay(attempt))
Enter fullscreen mode Exit fullscreen mode

طبّق القواعد التالية:

  1. أعد المحاولة فقط للأخطاء العابرة، مثل أخطاء الاتصال و429 وبعض استجابات 5xx.
  2. ضع حدًا أقصى للتأخير، حتى لا ينتظر المستخدم دقائق.
  3. ضع حدًا أقصى للمحاولات، حتى لا تستمر العملية إلى الأبد.
  4. لا تعد المحاولة تلقائيًا للأخطاء الدائمة مثل 400 أو 401 أو 403، ما لم يكن لديك سبب واضح.

عادةً تكفي ثلاث إلى خمس محاولات للأخطاء العابرة. يعيد Anthropic SDK محاولة بعض أخطاء الاتصال ورموز الحالة لاستدعاءاته الخاصة باستخدام التراجع التدريجي الأسي، ويمكنك ضبط الحد عبر max_retries. لكن ذلك لا يغطي واجهات برمجة التطبيقات الأخرى التي تستدعيها أدوات وكيلك. راجع أيضًا منطق إعادة المحاولة لواجهات برمجة التطبيقات عالية المخاطر.

تعيين مهلة زمنية لكل استدعاء

إعادة المحاولة لا تساعد إذا لم يفشل الطلب إطلاقًا. قد تقبل التبعية الاتصال ثم تتوقف دون إرسال استجابة. من دون مهلة زمنية، يبقى الوكيل محجوزًا خلف اتصال واحد معطل.

حدد ثلاث ميزانيات:

  • مهلة اتصال (connect timeout): لإنشاء الاتصال.
  • مهلة قراءة (read timeout): لانتظار الاستجابة.
  • مهلة إجمالية لتشغيل الوكيل: لمنع سلسلة من الاستدعاءات البطيئة من استهلاك وقت المستخدم بالكامل.

مثال باستخدام httpx:

import httpx

timeout = httpx.Timeout(
    connect=3.0,
    read=15.0,
    write=10.0,
    pool=5.0,
)

with httpx.Client(timeout=timeout) as client:
    response = client.get("https://api.example.com/data")
    response.raise_for_status()
Enter fullscreen mode Exit fullscreen mode

عند انتهاء المهلة، تعامل معها كخطأ عابر قابل لإعادة المحاولة، مع احترام الحد الأقصى للمحاولات.

اختر القيم من بيانات زمن الاستجابة الفعلية، لا من التخمين. اجعل المهلة أعلى من p99 للتبعية مع هامش مناسب. المهلة القصيرة جدًا تلغي طلبات كانت ستنجح، والطويلة جدًا تجعل التبعية العالقة تستهلك وقت الوكيل دون فائدة. خصص ميزانية منفصلة للاستجابات المتدفقة، لأن الإكمالات الطويلة قد تكون بطيئة بشكل مشروع.

تشغيل قاطع الدائرة عندما تكون تبعية معطلة

التراجع التدريجي مناسب لخدمة مشغولة مؤقتًا، لكنه ليس مناسبًا لخدمة معطلة بالكامل. إذا كانت التبعية تفشل منذ دقيقة، فمن المرجح أن يفشل الطلب التالي أيضًا.

قاطع الدائرة (Circuit Breaker) يحل هذه المشكلة بثلاث حالات:

الحالة السلوك
مغلق (Closed) تمر الطلبات ويُحسب عدد الإخفاقات.
مفتوح (Open) تُرفض الطلبات بسرعة خلال فترة تهدئة.
شبه مفتوح (Half-open) يُسمح بطلب اختبار واحد للتحقق من التعافي.

التدفق العملي:

طلبات ناجحة → Closed
إخفاقات تتجاوز العتبة → Open
انتهاء فترة التهدئة → Half-open
طلب اختبار ناجح → Closed
طلب اختبار فاشل → Open
Enter fullscreen mode Exit fullscreen mode

اربط قاطع دائرة بكل تبعية على حدة. لا تجعل تعطل واجهة البحث يمنع الوكيل من استدعاء واجهة الفوترة السليمة.

مثال مبسط للسياسة:

CIRCUIT_FAILURE_THRESHOLD = 5
CIRCUIT_RESET_TIMEOUT_SECONDS = 30
Enter fullscreen mode Exit fullscreen mode

النتيجة: بدل أربعين مهلة بطيئة لخدمة دفع معطلة، يحصل الوكيل على فشل سريع وواضح يمكنه التعامل معه أو عرضه للمستخدم.

اجعل عمليات إعادة المحاولة آمنة باستخدام مفاتيح الثبات

كل ما سبق يفترض أن إعادة المحاولة آمنة، لكنها ليست كذلك دائمًا.

تخيل هذا السيناريو:

  1. يرسل الوكيل POST /charge.
  2. يعالج الخادم عملية الدفع.
  3. تنتهي مهلة الاستجابة قبل أن تصل إلى الوكيل.
  4. يعتقد الوكيل أن العملية فشلت ويعيد المحاولة.
  5. تُحصّل الدفعة مرتين.

الحل هو مفتاح الثبات (Idempotency Key). أنشئ مفتاحًا فريدًا لكل إجراء منطقي وأرسله مع الطلب:

POST /charge
Idempotency-Key: 5c6f5e51-47d7-4f09-a883-3a1d9a1f995f
Enter fullscreen mode Exit fullscreen mode

يخزن الخادم المفتاح عند أول طلب. إذا استقبل المفتاح نفسه مرة أخرى، يعيد النتيجة الأصلية بدل تنفيذ الإجراء مرة أخرى.

القاعدة المهمة: أنشئ المفتاح مرة واحدة قبل حلقة إعادة المحاولة، وليس داخلها.

import uuid

idempotency_key = str(uuid.uuid4())

for attempt in range(5):
    response = client.post(
        "https://api.example.com/charge",
        json={"amount": 1000},
        headers={"Idempotency-Key": idempotency_key},
    )

    if response.is_success:
        break
Enter fullscreen mode Exit fullscreen mode

أي استدعاء ينشئ أو يغير حالة يحتاج إلى مفتاح ثبات: الرسوم، الطلبات، رسائل البريد، والسجلات. اقرأ دليل مفاتيح الثبات لتفاصيل التوليد والتعامل على جانب الخادم.

النجاة من حدود المعدل وحلقة خطأ تجاوز حد المعدل

تحتاج استجابات 429 إلى تعامل خاص لأنها غالبًا تحتوي على تعليمات مباشرة. تحمل استجابة تجاوز حد المعدل عادةً الرأس Retry-After، الذي يحدد مدة الانتظار بالثواني أو كتاريخ.

احترم هذا الرأس. إذا طلب الخادم الانتظار 30 ثانية ثم أعدت المحاولة بعد ثانيتين، ستتلقى 429 أخرى وقد تدخل حلقة RateLimitError متكررة. يناقش موضوع SDK منفصل المشكلة نفسها.

مثال للتعامل مع Retry-After:

import time

def retry_after_seconds(response) -> float | None:
    value = response.headers.get("Retry-After")
    if not value:
        return None

    try:
        return float(value)
    except ValueError:
        return None

if response.status_code == 429:
    delay = retry_after_seconds(response)

    if delay is None:
        delay = backoff_delay(attempt)

    time.sleep(delay)
Enter fullscreen mode Exit fullscreen mode

التنفيذ الصحيح:

  1. عند 429، اقرأ Retry-After.
  2. انتظر المدة المحددة على الأقل.
  3. إذا غاب الرأس، استخدم التراجع التدريجي الأسي مع الارتعاش.
  4. توقف بعد الحد الأقصى للمحاولات.
  5. أعد خطأ واضحًا بدل انتظار لا نهائي.

يمكنك أيضًا منع الوصول إلى الحد من الأساس باستخدام محدد معدل محلي، مثل token bucket. الاستعادة تعالج التقييد بعد وقوعه؛ وتوزيع الطلبات يمنع التقييد قبل وقوعه.

كيفية اختبار مسار الاستعادة

اختبر كل نمط عبر محاكاة قابلة للتحكم، وليس عبر خدمة حية. اتبع هذا السيناريو:

  1. حاكِ التبعية: أنشئ محاكاة لواجهة API التي تستدعيها أداة الوكيل.
  2. برمج تسلسل الاستجابات: أرجع 429 مع Retry-After: 2، ثم 500، ثم 200 مع جسم صالح.
  3. شغّل الوكيل مقابل المحاكاة: استبدل عنوان الخدمة الحقيقية بعنوان المحاكاة.
  4. تحقق من السلوك: افحص التأخيرات، وعدد المحاولات، والاستجابة النهائية.

مثال لمصفوفة اختبارات مفيدة:

السيناريو استجابات المحاكاة ما يجب التحقق منه
تقييد مؤقت 429 ثم 200 الانتظار لمدة Retry-After قبل المحاولة التالية
عطل عابر 500 ثم 200 تنفيذ التراجع التدريجي والنجاح لاحقًا
فشل دائم 500 دائمًا التوقف عند الحد الأقصى وإرجاع خطأ نظيف
تبعية معطلة إخفاقات متتالية فتح قاطع الدائرة والفشل السريع
استدعاء متغير قبول الطلب ثم إسقاط الاستجابة إرسال مفتاح الثبات نفسه في إعادة المحاولة

اختبر الثبات بدقة. برمج المحاكاة لقبول استدعاء متغير، ثم إسقاط الاستجابة حتى يظن الوكيل أنه فشل، ثم قبول إعادة المحاولة. تأكد من أن الطلبين يحملان Idempotency-Key نفسه، وأن المحاكاة تسجل إجراءً منطقيًا واحدًا لا اثنين.

لإعداد أوسع، راجع اختبار الوكلاء الذين يستدعون واجهات برمجة التطبيقات الخاصة بك.

قائمة مراجعة استعادة الأخطاء

قبل نقل الوكيل إلى الإنتاج، راجع هذه القائمة:

  • [ ] كل استدعاء خارجي لديه مهلة اتصال ومهلة قراءة وميزانية تشغيل إجمالية.
  • [ ] تستخدم إعادة المحاولة التراجع التدريجي الأسي مع الارتعاش.
  • [ ] يوجد حد أقصى للتأخير وحد أقصى للمحاولات.
  • [ ] تقرأ استجابات 429 الرأس Retry-After وتحترمه.
  • [ ] لكل تبعية قاطع دائرة مستقل.
  • [ ] كل استدعاء يغير الحالة يحمل مفتاح ثبات مستقرًا عبر المحاولات.
  • [ ] مسار التخلي يعيد خطأ نظيفًا بدل الانتظار أو التكرار إلى الأبد.
  • [ ] لكل بند اختبار محاكاة يفرض الفشل صراحةً.

عند تطبيق هذه البنود، يتعافى الوكيل بتصميم واضح بدل الاعتماد على الحظ.

أين يتناسب Apidog (وأين لا يتناسب)

من المهم تحديد الدور بدقة: Apidog ليس إطار عمل لوكلاء الذكاء الاصطناعي، ولا مضيفًا للنموذج، ولا بيئة تشغيل، ولا أداة لتقييم مخرجات النموذج. دوره هو طبقة واجهة برمجة التطبيقات التي يستدعيها الوكيل.

واجهة Apidog

يمكن استخدام Apidog في ثلاثة أجزاء عملية من اختبار الاستعادة:

  1. محاكاة التبعيات بدل استدعاء الخدمات الحية.
  2. برمجة استجابات الإخفاق، مثل 429 مع Retry-After و500 والمهلات والأجسام غير السليمة.
  3. التحقق من الطلبات المستلمة، مثل وجود مفتاح الثبات وثباته وعدد الاستدعاءات وشكل الطلب.

بهذا تكتشف الإرسال المزدوج أو الرأس المفقود داخل اختبار، لا بعد وصول المشكلة إلى العميل.

الأسئلة الشائعة

ألا يتعامل Anthropic SDK مع عمليات إعادة المحاولة نيابة عني؟

لاستدعاءاته الخاصة، نعم: يعيد SDK محاولة أخطاء محددة باستخدام التراجع التدريجي الأسي ويحترم Retry-After، ويمكنك ضبط الحد باستخدام max_retries. لكنه لا يغطي واجهات برمجة التطبيقات الأخرى التي تستدعيها أدوات وكيلك، لذلك يجب تطبيق الأنماط نفسها في طبقة أدواتك.

متى أحتاج إلى مفتاح ثبات؟

في أي استدعاء ينشئ أو يغير حالة: رسوم، طلبات، رسائل مرسلة، أو سجلات جديدة. تكون استدعاءات القراءة فقط آمنة عادةً لإعادة المحاولة من دون مفتاح ثبات. أنشئ المفتاح مرة واحدة لكل إجراء منطقي.

تدرب على فشل واحد هذا الأسبوع

لا تحتاج إلى بناء كل الأنماط دفعة واحدة. ابدأ بأكبر خطر في نظامك، وغالبًا يكون حلقة حدود المعدل أو إعادة محاولة لاستدعاء متغير من دون مفتاح ثبات.

برمج 429، أو أسقط استجابة بعد قبول الطلب، ثم راقب سلوك الوكيل:

  • هل انتظر قبل إعادة المحاولة؟
  • هل احترم Retry-After؟
  • هل توقف بعد عدد محدود من المحاولات؟
  • هل حملت كل المحاولات مفتاح الثبات نفسه؟

عندما ترى تراجعًا تدريجيًا نظيفًا ومفتاح ثبات واحدًا في سيناريو كنت تخشى فيه تحصيلًا مزدوجًا، ستثق في الوكيل لسبب أفضل من نجاح عرض توضيحي واحد.

Top comments (0)