المزودون الاحتياطيون وقواطع الدائرة
ابنوا تسلسلًا للمزودين ينتقل تلقائيًا من OpenAI إلى Anthropic ثم إلى نموذج محلي عندما يكون المزود الأساسي بطيئًا أو غير متاح، باستخدام نمط قاطع الدائرة.
المزودون الاحتياطيون وقواطع الدائرة درس مجاني في AI Engineering Academy على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في AI Engineering Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة AI Engineering Academy 4 دروس في المجموع.
مخاطر الاعتماد على مزوّد واحد
يؤدي الاعتماد على مزوّد LLM واحد إلى إنشاء نقطة فشل واحدة. فقد شهدت OpenAI حالات انقطاع استغرقت معالجتها من دقائق إلى ساعات. وإذا كان تطبيقك بأكمله يعتمد على توفر GPT-4o، فإن أي حادث لدى المزوّد يتحول فورًا إلى توقف يواجهه المستخدمون. تحافظ استراتيجية مزوّد الانتقال الاحتياطي على استمرارية الخدمة من خلال التوجيه إلى مزوّدين بديلين عند فشل المزوّد الأساسي.
تعريف تسلسل المزوّدين
تسلسل المزوّدين هو قائمة مرتبة بالمزوّدين والنماذج التي تُجرّب بالتتابع. عندما يفشل المزوّد الأساسي أو تنتهي مهلة الانتظار، يجرّب النظام المزوّد التالي تلقائيًا. وقد يكون التسلسل المعتاد مثلًا: OpenAI GPT-4o ← Anthropic Claude 3.5 Sonnet ← نموذج Llama منشور محليًا. يمثل كل مستوى انتقالًا احتياطيًا، بينما يعمل النموذج المحلي كحل أخير لا يمكن أن يتوقف.
from dataclasses import dataclass
from typing import Optional
@dataclass
class Provider:
name: str
base_url: Optional[str]
api_key_env: str
model: str
priority: int # lower = higher priority
CASCADE = [
Provider('openai', None, 'OPENAI_API_KEY', 'gpt-4o', 1),
Provider('anthropic', 'https://api.anthropic.com/v1', 'ANTHROPIC_API_KEY', 'claude-3-5-sonnet', 2),
Provider('local', 'http://localhost:8000/v1', 'LOCAL_KEY', 'llama-3.1-8b-inst', 3),
]تنفيذ حلقة الانتقال الاحتياطي
نفّذ حلقة الانتقال الاحتياطي باستخدام try/except بسيطة تتكرر عبر التسلسل. التقط الأخطاء المؤقتة (انتهاء المهلة، وأخطاء 500 و503) وانتقل إلى المزوّد التالي. لا تلتقط أخطاء المصادقة (401) أو أخطاء الطلب غير الصالح (400)، فهذه أخطاء برمجية ينبغي ظهورها فورًا بدلًا من الانتقال إلى مزوّد آخر.
import openai
import os
TRANSIENT_ERRORS = (openai.APITimeoutError, openai.InternalServerError, openai.APIConnectionError)
async def call_with_fallback(messages: list, **kwargs) -> str:
for provider in CASCADE:
try:
client = openai.AsyncOpenAI(
api_key=os.environ[provider.api_key_env],
base_url=provider.base_url
)
resp = await client.chat.completions.create(
model=provider.model,
messages=messages,
timeout=10.0,
**kwargs
)
return resp.choices[0].message.content
except TRANSIENT_ERRORS as e:
print(f'Provider {provider.name} failed: {e}, trying next...')
raise RuntimeError('All providers failed')ما قاطع الدائرة؟
يمنع قاطع الدائرة إرسال عدد هائل من الطلبات إلى خدمة متعثرة أثناء انقطاعها. وقد سُمّي بهذا الاسم تشبيهًا بقواطع الدوائر الكهربائية، وله ثلاث حالات: مغلق (تمر الطلبات بصورة طبيعية)، ومفتوح (تُرفض الطلبات فورًا)، وشبه مفتوح (يُسمح بمرور طلب اختبار واحد للتحقق من تعافي الخدمة). يحمي ذلك كلًا من الخدمة التابعة وتطبيقك أثناء الحوادث.
# Circuit breaker state machine:
#
# CLOSED --> (failure_count >= threshold) --> OPEN
# ^ |
# | (test_request succeeds) | (timeout expires)
# +------------ HALF_OPEN <-----------------+
#
# In OPEN state: immediately return fallback/error
# In HALF_OPEN: allow one request through to test recovery
# In CLOSED: normal operation, count failuresتنفيذ قاطع دائرة
إليك تنفيذًا مبسطًا لقاطع دائرة. تتبّع عدد مرات الفشل والوقت الذي فُتحت فيه الدائرة. عندما يتجاوز عدد مرات الفشل الحدّ المحدد، افتح الدائرة. وبعد انتهاء مهلة إعادة الضبط القابلة للتهيئة، اسمح بمرور طلب فحص واحد. إذا نجح الطلب، فأغلق الدائرة. وإذا فشل، فأبقِ الدائرة مفتوحة وأعد ضبط المهلة.
import time
from enum import Enum
class State(Enum):
CLOSED = 'closed'
OPEN = 'open'
HALF_OPEN = 'half_open'
class CircuitBreaker:
def __init__(self, failure_threshold=5, reset_timeout=60):
self.state = State.CLOSED
self.failure_count = 0
self.failure_threshold = failure_threshold
self.reset_timeout = reset_timeout
self.opened_at = None
def record_success(self):
self.failure_count = 0
self.state = State.CLOSED
def record_failure(self):
self.failure_count += 1
if self.failure_count >= self.failure_threshold:
self.state = State.OPEN
self.opened_at = time.time()
def can_attempt(self) -> bool:
if self.state == State.CLOSED:
return True
if self.state == State.OPEN:
if time.time() - self.opened_at > self.reset_timeout:
self.state = State.HALF_OPEN
return True # allow one probe
return False
return True # HALF_OPEN: allow probeدمج قواطع الدائرة مع المزوّدين
احتفظ بقاطع دائرة واحد لكل مزوّد. قبل استدعاء مزوّد، تحقّق مما إذا كان قاطع دائرته يسمح بالمحاولة. وبعد كل استدعاء، سجّل النجاح أو الفشل. عندما تُفتح دائرة أحد المزوّدين، تتجاوزه حلقة الانتقال الاحتياطي تلقائيًا وتحاول استخدام المزوّد التالي في التسلسل، من دون انتظار انتهاء المهلة في كل استدعاء.
breakers = {p.name: CircuitBreaker(failure_threshold=5, reset_timeout=60) for p in CASCADE}
async def call_with_circuit_breaker(messages: list) -> str:
for provider in CASCADE:
breaker = breakers[provider.name]
if not breaker.can_attempt():
continue # skip this provider, circuit is open
try:
result = await call_provider(provider, messages)
breaker.record_success()
return result
except TRANSIENT_ERRORS as e:
breaker.record_failure()
print(f'{provider.name} failed ({breaker.failure_count}/{breaker.failure_threshold})')
raise RuntimeError('All providers exhausted')اكتشاف الاستدعاءات البطيئة باعتبارها حالات فشل
من منظور تجربة المستخدم، يكاد المزوّد الذي يستجيب خلال 30 ثانية يكون سيئًا بقدر مزوّد متوقف بالكامل. اضبط مهلة صارمة لكل استدعاء لمزوّد، وتعامل مع استثناءات انتهاء المهلة باعتبارها حالات فشل في قاطع الدائرة. تعني مهلة مدتها 10 ثوانٍ أن الانتقال الاحتياطي سيبدأ بسرعة كافية ليرى المستخدم تأخيرًا قصيرًا فقط، لا شاشة معلّقة.
async def call_provider(provider: Provider, messages: list) -> str:
client = openai.AsyncOpenAI(
api_key=os.environ[provider.api_key_env],
base_url=provider.base_url
)
try:
resp = await asyncio.wait_for(
client.chat.completions.create(model=provider.model, messages=messages),
timeout=10.0 # fail fast, let circuit breaker count it
)
return resp.choices[0].message.content
except asyncio.TimeoutError:
raise openai.APITimeoutError('Provider timed out')لوحة معلومات صحة المزوّدين
اعرض نقطة النهاية /health/providers التي توضّح الحالة الحالية لقاطع الدائرة لكل مزوّد، بما في ذلك عدد مرات الفشل، والحالة (مغلق/مفتوح/شبه مفتوح)، والوقت المتبقي حتى إعادة الضبط. يسهّل ذلك معرفة المزوّدين السليمين في لمحة أثناء الحادث، ويساعدك على اتخاذ قرار بشأن فرض إعادة الضبط يدويًا أو انتظار التعافي التلقائي.
from fastapi import FastAPI
app = FastAPI()
@app.get('/health/providers')
def provider_health():
return {
name: {
'state': cb.state.value,
'failure_count': cb.failure_count,
'seconds_until_reset': (
max(0, cb.reset_timeout - (time.time() - cb.opened_at))
if cb.state == State.OPEN else None
)
}
for name, cb in breakers.items()
}مواءمة مخرجات المزوّدين
تختلف تنسيقات الاستجابة ومرشحات الأمان والإمكانات بين المزوّدين. فعند الانتقال من GPT-4o إلى Claude، قد يرفض النموذج بعض الطلبات التي كان GPT-4o سيجيب عنها. احتفظ بأغلفة مطالبات خاصة بكل مزوّد لتكييف مطالباتك مع اصطلاحات كل مزوّد. اختبر كل مزوّد للانتقال الاحتياطي بشكل مستقل للتأكد من أنه ينتج مخرجات مقبولة لحالة الاستخدام لديك.
def adapt_messages_for_provider(provider: Provider, messages: list) -> list:
if provider.name == 'anthropic':
# Claude prefers explicit task descriptions
system = next((m['content'] for m in messages if m['role'] == 'system'), '')
if 'JSON' not in system:
messages = [{'role': 'system', 'content': system + ' Respond in JSON.'}] + [
m for m in messages if m['role'] != 'system'
]
return messagesاختبار سلوك الانتقال الاحتياطي
اكتب اختبارًا يجبر المزوّد الأساسي على الفشل (من خلال تقديم مفتاح API غير صالح أو استخدام محاكاة ترمي أخطاء)، ويتحقق من بدء الانتقال الاحتياطي وإرجاع استجابة صالحة. اختبر أيضًا أن قاطع الدائرة يُفتح بصورة صحيحة بعد العدد المحدد من حالات الفشل، وأنه يتعافى بعد انتهاء مهلة إعادة الضبط. إن منطق الانتقال الاحتياطي الذي لم يُختبر قط لا يُعتمد عليه أثناء انقطاع حقيقي.
import pytest
from unittest.mock import AsyncMock, patch
@pytest.mark.asyncio
async def test_fallback_on_primary_timeout():
# Primary provider times out
with patch('your_module.call_provider', side_effect=[
openai.APITimeoutError('Timeout'), # primary fails
'Claude response' # fallback succeeds
]):
result = await call_with_circuit_breaker([{'role': 'user', 'content': 'Hello'}])
assert result == 'Claude response'اعتبارات تكلفة تسلسل مزوّدي الخدمة
غالبًا ما تختلف أسعار مزوّدي الخدمة الاحتياطيين عن أسعار المزوّد الأساسي. قد تكون تكلفة Anthropic Claude أعلى أو أقل من تكلفة OpenAI GPT-4o، بحسب فئة النموذج. تتبّع المزوّد الذي عالج كل طلب، واحسب توزيع التكلفة لكل مزوّد على حدة. إذا كان المزوّد الاحتياطي أغلى باستمرار، فتحقّق مما إذا كان المزوّد الأساسي يعاني من نقص في الموارد، وما إذا كانت الترقية إلى فئة ذات حد أعلى لمعدل الطلبات ستكون أكثر فعالية من حيث التكلفة مقارنةً بالاستخدام المتكرر للمزوّد الاحتياطي.
# Approximate costs per 1M tokens (2026):
PROVIDER_COSTS = {
'openai/gpt-4o': {'input': 2.50, 'output': 10.00},
'anthropic/claude-3.5-sonnet': {'input': 3.00, 'output': 15.00},
'openai/gpt-4o-mini': {'input': 0.15, 'output': 0.60},
'local/llama-3.1-8b': {'input': 0.00, 'output': 0.00}, # infra cost only
}
# If fallback adds $0.50/day and a Tier 2 upgrade costs $100/month:
# Tier 2 pays off if you use fallback > 200 requests/dayتحقّق سريع
اختبر مدى فهمك لقواطع الدائرة ومزوّدي الخدمة الاحتياطيين.
مراجعة الدرس
تعلّمت في هذا الدرس أن تسلسلات مزوّدي الخدمة تحدد سلسلة احتياطية مرتبة تبدأ بمزوّد LLM الأساسي ثم المزوّدين الاحتياطيين، وأن قواطع الدائرة تمنع إرسال الطلبات المتكررة إلى مزوّد متعطّل من خلال إيقاف الاتصال سريعًا بعد تجاوز حد معيّن من حالات الفشل، وأن المهلات الزمنية لكل مزوّد تضمن تشغيل المزوّد الاحتياطي بسرعة عند بطء الطلبات بدلًا من حجب المستخدمين. ننتقل بعد ذلك إلى ضبط موازنات المهلة الزمنية وتنفيذ التدهور السلس.
تعلم Python مع معلم ذكاء اصطناعي — مجانًا
اكتب وقم بتشغيل أكوادك الفعلية في المتصفح، واحصل على مساعدة فورية من معلم ذكاء اصطناعي متاح 24/7، واستمر من حيث توقفت على الويب أو في التطبيق.
- الدورات
- 30
- الدروس
- 120
الأسئلة الشائعة
هل درس «المزودون الاحتياطيون وقواطع الدائرة» مجاني؟
نعم — نص درس «المزودون الاحتياطيون وقواطع الدائرة» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة AI Engineering Academy، انتقل إلى CoddyKit PRO. تتضمن دورة AI Engineering Academy 4 دروس في المجموع.
ماذا ستتعلم في «المزودون الاحتياطيون وقواطع الدائرة»؟
ابنوا تسلسلًا للمزودين ينتقل تلقائيًا من OpenAI إلى Anthropic ثم إلى نموذج محلي عندما يكون المزود الأساسي بطيئًا أو غير متاح، باستخدام نمط قاطع الدائرة. تتمرن على AI Engineering Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ AI Engineering Academy؟
لا تُشترط خبرة سابقة. AI Engineering Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.
كم من الوقت يستغرق درس «المزودون الاحتياطيون وقواطع الدائرة»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس AI Engineering Academy هذا؟
نعم. كل درس في AI Engineering Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- قياس زمن استجابة LLM: TTFT وTPOT
- موازنة الحمل واستراتيجيات المفاتيح المتعددة
- المزودون الاحتياطيون وقواطع الدائرة
- ميزانيات المهلة والتدهور السلس