أجرى وكيل الـ AI الخاص بك للتو محادثة مدتها 30 دقيقة مع مستخدم. ناقشا متطلبات المشروع، وتبادلا التفضيلات، واتخذا القرارات. ثم يكتب المستخدم /new لبدء جلسة جديدة.
يحاول الوكيل دمج تلك المحادثة في الذاكرة طويلة الأمد. يفشل استدعاء الـ LLM. بسبب الـ Rate limit، أو الـ Timeout، أو لأن النموذج يعيد نصاً بدلاً من استدعاء الأداة (tool) المطلوبة.
تختفي الذاكرة. ثلاثون دقيقة من السياق، تبخرت.
يحدث هذا أكثر مما تعتقد. في بيئة تشغيل Hermes Agent، كان لمعدل فشل دمج الذاكرة حوالي 15% على أي نموذج منفرد قبل أن نضيف نظام Fallback مخصصاً. بالنسبة لميزة يُفترض أن تكون بنية تحتية غير مرئية، فإن هذا أمر غير مقبول.
إذا كنت تبني واجهة المنتج المحيطة بدلاً من مجرد نظام الذاكرة الفرعي، فاقرن هذه الصفحة بـ دليل بناء chatbot بمفتاح واحد و دليل الـ Rate limiting لـ AI API. متانة الذاكرة لا تهم إلا عندما يعيش الوكيل فعلياً داخل تطبيق قابل للاستخدام.
كيف تتعامل أطر العمل الأخرى مع هذا (إنها لا تفعل)
تتعامل معظم أطر عمل وكلاء الـ AI مع دمج الذاكرة كاستدعاء LLM بسيط. إذا نجح، فهذا رائع. وإذا لم ينجح، تضيع الذاكرة.
استخدم الخط الأساسي لـ Hermes Agent نفس النموذج للدمج كما هو الحال في المحادثة. استدعاء Claude Sonnet يكلف 0.003 دولار ويستغرق أكثر من 8 ثوانٍ، فقط لتلخيص دردشة لن يراها المستخدم أبداً. عندما يفشل هذا الاستدعاء (بسبب Rate limit، أو Timeout، أو خطأ في النموذج)، يسجل إطار العمل تحذيراً ويستمر. يختفي سياق المستخدم.
إطار عمل nanobot، وهو إطار عمل شائع آخر، لديه نفس البنية. نموذج واحد، محاولة واحدة، ولا يوجد Fallback. وظيفة الدمج لا تحتوي حتى على Timeout. يؤدي البطء في المصدر (أخطاء Cloudflare 524 شائعة) إلى حظر الجلسة بأكملها حتى ينقطع الاتصال.
لا يفصل أي من إطاري العمل الدمج عن النموذج الرئيسي. ولا يملك أي منهما منطق Fallback لعمليات الذاكرة. ولا يميز أي منهما بين "فشل استدعاء الـ API" و "نجح استدعاء الـ API ولكن النموذج لم يفعل ما طلبناه".
هذه ليست حالات نادرة. مع معدلات فشل تصل إلى 15% على أي نموذج واحد، فإن إطار العمل الذي يقوم بـ 100 عملية دمج يومياً يفقد الذاكرة في 15 منها. على مدار أسبوع، يعني ذلك 105 محادثة ينسى فيها الوكيل كل شيء.
المشكلة أعمق من منطق إعادة المحاولة (Retry Logic)
الإصلاح البديهي هو إعادة المحاولة مع التراجع الأسي (exponential backoff). كان لدينا ذلك، وهو يعالج أخطاء HTTP العابرة بشكل جيد:
# Retry loop: 1s → 2s → 4s backoff
for attempt in range(3):
try:
response = await acompletion(**kwargs)
return await self._collect_stream(response)
except (RateLimitError, APIConnectionError) as e:
await asyncio.sleep(RETRY_DELAYS[attempt])
هذا يلتقط أخطاء 429 والاضطرابات في الشبكة. ولكن هناك وضعان للفشل يتسللان من خلاله:
وضع الفشل 1: لا يستطيع النموذج القيام بـ tool calling. بعض النماذج، خاصة الصغيرة منها التي تعمل على محركات استنتاج سريعة، تفشل أحياناً في إنشاء استدعاءات دوال (function calls) صالحة بناءً على مطالبات (prompts) معقدة. تعيد الـ API رمز 200 مع ServiceUnavailableError مغلف داخل MidStreamFallbackError. يرى منطق إعادة المحاولة الخاص بك استثناءً، فيعيد محاولة نفس النموذج، ويحصل على نفس الخطأ.
وضع الفشل 2: "ينجح" النموذج ولكنه لا يستدعي الأداة. يعيد الـ LLM استجابة صالحة تماماً. HTTP 200. لا توجد أخطاء. ولكن بدلاً من استدعاء save_memory ببيانات منظمة، فإنه يكتب ملخصاً نصياً عادياً. يعتبر محرك إعادة المحاولة الخاص بك هذا نجاحاً. تتحقق وظيفة الدمج من استدعاءات الأدوات، فلا تجد شيئاً، وتستسلم.
وضع الفشل الثاني هو الأكثر خطورة. تعتقد طبقة النقل (transport layer) أن كل شيء سار على ما يرام، بينما تعرف طبقة الأعمال (business layer) أنه لم ينجح. لن يؤدي أي قدر من عمليات إعادة المحاولة على مستوى HTTP إلى إصلاح نموذج لا يفهم مخطط الأداة (tool schema) الخاص بك.
بنية Fallback ثنائية الطبقات
لقد حللنا ذلك من خلال حلقتي Fallback مستقلتين تعملان على مستويات مختلفة:
User sends /new
│
▼
consolidate() ─── Business Layer Fallback
│ "Did the model call save_memory?"
│ No → try next model in chain
│
▼
_chat_with_retry() ─── Transport Layer Fallback
│ HTTP errors → exponential backoff
│ All retries exhausted → walk fallback chain
│
▼
MODEL_MAP fallback chain:
llama-3.3-70b ─$0.59/M─→ qwen3-32b ─$0.29/M─→ llama-4-scout ─$0.11/M─→ gpt-4.1-mini ─→ claude-haiku
(394 TPS) (662 TPS) (594 TPS) (reliable) (last resort)
تتعامل الطبقة 1 مع إخفاقات النقل. وتتعامل الطبقة 2 مع إخفاقات منطق الأعمال. تتم مشاركة سلسلة الـ Fallback بين الطبقتين، ويتم تعريفها مرة واحدة في كتالوج مركزي.
هذا نهج مختلف تماماً عن "إعادة محاولة نفس النموذج". عندما يفشل نموذج في استدعاء أداة، نادراً ما يفيد إعادة محاولته بنفس الـ prompt. بينما يفيد التبديل إلى نموذج مختلف بأوزان مختلفة وسلوك استدعاء أدوات مختلف.
كتالوج النماذج: مصدر واحد للحقيقة
يحتوي كل نموذج في الكتالوج الخاص بنا على حقل fallback اختياري يشير إلى النموذج التالي الذي يجب تجربته:
@dataclass(frozen=True)
class ModelEntry:
id: str
label: str
tier: str
description: str
fallback: str | None = None
hidden: bool = False # Hidden from user-facing /model list
MODEL_CATALOG = [
# User-visible models (16 models users can switch between)
ModelEntry("claude-sonnet-4-6", "Claude Sonnet 4.6", "standard",
"Recommended", fallback="claude-sonnet-4-5"),
ModelEntry("gpt-4.1-mini", "GPT-4.1 Mini", "economy",
"Stable tool calling", fallback="claude-haiku-4-5"),
# Hidden consolidation models (internal use only)
ModelEntry("llama-3.3-70b-versatile", "Llama 3.3 70B (Groq)", "economy",
"394 TPS", fallback="qwen3-32b", hidden=True),
ModelEntry("qwen3-32b", "Qwen3 32B (Groq)", "economy",
"662 TPS", fallback="llama-4-scout-17b-16e-instruct", hidden=True),
# ...
]
تبقي علامة hidden=True النماذج الداخلية بعيدة عن قائمة /model التي يراها المستخدم، مع استمرار مشاركتها في سلاسل الـ Fallback. يرى المستخدمون 16 نموذجاً يمكنهم التبديل بينها، بينما يستخدم النظام 19 نموذجاً. النماذج الثلاثة المخفية موجودة حصرياً للمهام الخلفية مثل دمج الذاكرة، حيث تهم السرعة والتكلفة أكثر من جودة المحادثة.
هذا الكتالوج هو المصدر الوحيد للحقيقة لجميع عمليات توجيه النماذج. إضافة نموذج جديد إلى سلسلة الـ Fallback يعني إضافة سطر واحد فقط. لا توجد ملفات إعداد للمزامنة، ولا متغيرات بيئة للتحديث، ولا نصوص برمجية للنشر لتعديلها.
طبقة النقل: Fallback متسلسل مع اكتشاف الحلقات
يتنقل محرك إعادة المحاولة عبر سلسلة الـ Fallback باستخدام مجموعة "visited" لمنع الحلقات اللانهائية:
async def _chat_with_retry(self, kwargs, original_model):
# Phase 1: Exponential backoff on the primary model
for attempt in range(3):
try:
response = await acompletion(**kwargs)
return await self._collect_stream(response)
except (RateLimitError, APIConnectionError, APIError) as e:
await asyncio.sleep(RETRY_DELAYS[attempt])
except AuthenticationError:
return LLMResponse(content="API key invalid.", finish_reason="error")
# Phase 2: Walk the fallback chain
visited = {original_model}
current = original_model
while True:
entry = MODEL_MAP.get(current)
if not entry or not entry.fallback or entry.fallback in visited:
break
current = entry.fallback
visited.add(current)
# Resolve correct gateway for this model
gw = self._resolve_gateway_for_model(current)
resolved = self._resolve_model(current, gateway=gw)
fb_kwargs = {**kwargs, "model": resolved}
# Fix api_base for the target model's protocol
if gw and gw.default_api_base:
fb_kwargs["api_base"] = gw.default_api_base
try:
response = await acompletion(**fb_kwargs)
return await self._collect_stream(response)
except Exception:
continue # Try next in chain
return LLMResponse(content="Service unavailable.", finish_reason="error")
مجموعة visited بالغة الأهمية. بدونها، فإن سلسلة مثل A→B→A ستدخل في حلقة للأبد. معها، يجرب المحرك كل نموذج مرة واحدة بالضبط.
دقة البوابة (Gateway resolution) تهم أيضاً. تحتاج النماذج المختلفة إلى تنسيقات API مختلفة. يتم توجيه نماذج Claude عبر بوابة بتنسيق Anthropic (بدون لاحقة /v1). يتم توجيه نماذج GPT عبر بوابة متوافقة مع OpenAI (مع /v1). تستخدم نماذج Groq نقطة نهاية أخرى. يقوم محرك الـ Fallback بتحديد البوابة الصحيحة لكل نموذج في السلسلة، مما يمنع عدم تطابق البروتوكولات مثل إرسال طلبات Anthropic إلى نقطة نهاية OpenAI.
هذا تفصيل تتجاهله معظم أطر العمل تماماً. يفترضون أن جميع النماذج تتحدث نفس البروتوكول. في بيئة الإنتاج، مع 19 نموذجاً عبر 4 تنسيقات API مختلفة، ينهار هذا الافتراض فوراً.
طبقة الأعمال: التحقق من استدعاء الأدوات
تضيف وظيفة الدمج حلقة Fallback خاصة بها في الأعلى:
async def consolidate(self, session, provider, model, **kwargs):
visited = set()
current_model = model
while current_model and current_model not in visited and len(visited) <= 3:
visited.add(current_model)
response = await asyncio.wait_for(
provider.chat(messages=messages, tools=SAVE_MEMORY_TOOL, model=current_model),
timeout=30,
)
if response.has_tool_calls:
# Success: extract and save memory
args = response.tool_calls[0].arguments
self.write_long_term(args["memory_update"])
self.append_history(args["history_entry"])
return True
# Model didn't call the tool — try next in chain
entry = MODEL_MAP.get(current_model)
next_model = entry.fallback if entry else None
if next_model and next_model not in visited:
current_model = next_model
continue
return False # No more fallbacks
return False
هذا يلتقط الحالة التي تعيد فيها _chat_with_retry استجابة ناجحة (HTTP 200، محتوى صالح) ولكن النموذج لم يستخدم الأداة. تتحقق وظيفة الدمج من has_tool_calls، وإذا كانت مفقودة، تنتقل إلى النموذج التالي في السلسلة.
كما أن مغلف المهلة (asyncio.wait_for) يفعل الـ Fallback أيضاً. إذا استغرق النموذج أكثر من 30 ثانية (وهو أمر شائع مع أخطاء Cloudflare 524 على المصادر البطيئة)، تلتقط الوظيفة TimeoutError وتجرب النموذج التالي بدلاً من حظر جلسة المستخدم إلى أجل غير مسمى.
لماذا Groq لعملية الدمج
دمج الذاكرة هو مهمة خلفية. لا يرى المستخدم المخرجات، هو فقط يحتاج إليها لتعمل. وهذا يجعلها مرشحاً مثالياً للنماذج السريعة والرخيصة.
تستخدم معظم أطر العمل نفس النموذج الباهظ لكل شيء. إذا كنت تستخدم Claude Sonnet للمحادثة، فأنت تستخدمه أيضاً لدمج الذاكرة. هذا يعني 3 دولارات لكل مليون token مدخلات وأكثر من 8 ثوانٍ لكل عملية دمج، لمهمة تنتج مخرجات لا يقرأها أي إنسان أبداً.
لقد فصلنا الدمج عن نموذج المحادثة تماماً. تستخدم المحادثة أي نموذج يختاره المستخدم. بينما يستخدم الدمج سلسلة مخصصة من النماذج المستضافة على Groq:
| النموذج | السرعة | تكلفة المدخلات | تكلفة المخرجات |
|---|---|---|---|
| llama-3.3-70b-versatile | 394 TPS | $0.59/M | $0.79/M |
| qwen3-32b | 662 TPS | $0.29/M | $0.59/M |
| llama-4-scout-17b-16e | 594 TPS | $0.11/M | $0.34/M |
| gpt-4.1-mini (السابق) | ~150 TPS | $0.40/M | $1.60/M |
يقوم النموذج الأساسي (llama-3.3-70b) بدمج جلسة مكونة من 60 رسالة في حوالي 5 ثوانٍ. استغرق النموذج الافتراضي السابق (gpt-4.1-mini) أكثر من 8 ثوانٍ. انخفضت التكلفة لكل عملية دمج من حوالي 0.003 دولار إلى حوالي 0.001 دولار.
المقايضة: نماذج Groq لديها استدعاء أدوات أقل موثوقية في الـ prompts المعقدة. وهذا هو بالضبط سبب وجود الـ Fallback ثنائي الطبقات. عندما يفشل llama-3.3-70b في استدعاء الأداة، يتولى qwen3-32b المهمة. وإذا فشل ذلك أيضاً، يحاول llama-4-scout. وإذا فشلت جميع نماذج Groq الثلاثة، يتعامل gpt-4.1-mini مع الأمر بموثوقية استدعاء أدوات تقارب 100%.
في بيئة الإنتاج، نرى النموذج الأساسي ينجح في حوالي 85% من الحالات. تصل السلسلة إلى gpt-4.1-mini في أقل من 2% من عمليات الدمج. معدل الفشل الإجمالي: صفر فعلياً.
نتائج الإنتاج
لقد نشرنا هذا في مثيلين من بيئة تشغيل Hermes Agent واختبرناه بمحادثات Telegram حقيقية.
النشر الأول (Fallback أحادي الطبقة فقط):
Memory consolidation (archive_all): 56 messages
llama-3.3-70b-versatile → "Failed to call a function"
Falling back → qwen3-32b
qwen3-32b: LLM did not call save_memory, skipping
→ "Memory archival failed, session not cleared."
التقطت طبقة النقل الفشل الأول وقامت بالـ Fallback. لكن qwen3-32b أعاد نصاً دون استدعاء الأداة. لم يتمكن الـ Fallback أحادي الطبقة من التعامل مع هذا. هذا هو السيناريو الدقيق الذي قد يفقد فيه أي إطار عمل آخر الذاكرة بصمت.
النشر الثاني (Fallback ثنائي الطبقات):
Memory consolidation (archive_all): 60 messages
model=llama-3.3-70b-versatile → success
Memory consolidation done: 60 messages remaining
نفس النموذج، نفس حجم الرسائل. هذه المرة نجح من المحاولة الأولى. الطبيعة المتقطعة لفشل استدعاء الأدوات هي بالضبط سبب حاجتك إلى سلسلة Fallback بدلاً من نموذج احتياطي واحد.
عندما يفشل النموذج الأساسي فعلياً، تلتقطه السلسلة:
llama-3.3-70b → tool call failed
→ consolidate() fallback → qwen3-32b
→ qwen3-32b didn't call tool
→ consolidate() fallback → llama-4-scout
→ llama-4-scout didn't call tool
→ consolidate() fallback → gpt-4.1-mini
→ gpt-4.1-mini called save_memory ✓
Memory consolidation done
تمت تجربة أربعة نماذج، وحُفظت الذاكرة. يرى المستخدم "بدأت جلسة جديدة." وليس لديه أدنى فكرة عن حدوث أي من هذا.
الفجوة المعمارية
نظام الذاكرة مقابل البدائل، ميزة بميزة:
| القدرة | إطار عمل وكيل AI نموذجي | تصميم Fallback ثنائي الطبقات |
|---|---|---|
| نموذج الدمج | نفس نموذج المحادثة (مكلف، بطيء) | سلسلة نماذج مستقلة، معززة بـ Groq |
| التعامل مع الفشل | تسجيل تحذير، فقدان الذاكرة | Fallback ثنائي الطبقات، بعمق 5 نماذج |
| Fallback النقل | إعادة محاولة نفس النموذج 3 مرات | Fallback متسلسل عبر نماذج مختلفة |
| Fallback منطق الأعمال | لا يوجد | التحقق من استدعاء الأداة + تبديل النموذج |
| حماية المهلة (Timeout) | لا يوجد (Cloudflare 524 يحظر الجلسة) | asyncio.wait_for(timeout=30) + Fallback |
| اقتطاع الجلسة | لا يوجد (السياق ينمو للأبد) | اقتطاع الرسائل القديمة بعد الدمج |
| البحث في السجل | لا يوجد | نافذة HISTORY.md متحركة، قابلة للبحث بـ grep |
| النماذج الداخلية | غير مدعومة | hidden=True للنماذج الخاصة بالنظام فقط |
| منع الحلقات | غير مطلوب (لا توجد سلاسل) | مجموعة visited تمنع حلقات A→B→A |
| دقة البوابة (Gateway) | افتراض تنسيق API واحد | بوابة لكل نموذج مع اكتشاف البروتوكول |
يمثل كل صف في هذا الجدول فشلاً في الإنتاج إما جربناه بأنفسنا أو لاحظناه في متتبعات المشكلات الخاصة بأطر العمل الأخرى. الـ Fallback ثنائي الطبقات، وكتالوج النماذج المخفية، ودقة البوابة لكل نموذج، والـ Fallback الناتج عن المهلة: لا يوجد أي من هذه في nanobot أو أي إطار عمل وكلاء مفتوح المصدر آخر فحصناه.
ما تعلمناه
"نجاح الطلب" ليس "نجاح المهمة". تعمل محركات إعادة المحاولة العامة على مستوى HTTP. لا يمكنها معرفة أن استجابة 200 مع JSON صالح هي في الواقع فشل لأن النموذج لم يستخدم الأداة التي طلبتها. تحتاج العمليات الحساسة للأعمال إلى معايير نجاح خاصة بها ومنطق Fallback خاص بها.
تفشل النماذج الصغيرة بشكل مختلف عن النماذج الكبيرة. النماذج الكبيرة (GPT-4.1، Claude Sonnet) تستدعي الأدوات دائماً تقريباً عند طلبها. النماذج الصغيرة على محركات الاستنتاج السريعة تولد أحياناً استجابات تبدو صالحة ولكنها تتجاهل مخطط الأداة تماماً. هذا ليس خطأ يمكنك إصلاحه بهندسة الـ prompts، بل هو فجوة في القدرات تتطلب معالجة معمارية.
اختبر ببيانات الإنتاج، وليس ببيانات اصطناعية. نجح اختبارنا الأولي بـ 6 رسائل اصطناعية على كل نموذج. بينما فشلت جلسة حقيقية مكونة من 60 رسالة مع سجل استدعاء أدوات وطوابع زمنية ولغات مختلطة على اثنين من نماذج Groq الثلاثة. يكشف تعقيد البيانات الحقيقية عن أوضاع فشل لن تكشفها أبداً بيانات الاختبار النظيفة.
هذا هو السبب أيضاً في أهمية دليل الـ Rate limiting لـ AI API هنا. لا يحتاج نظام الذاكرة فقط إلى "نموذج أفضل"، بل يحتاج إلى سياسة نقل، وفحص نجاح منطق الأعمال، وسلم Fallback لا ينهار تحت وطأة فشل المزود العادي.
تصف هذه المقالة نمط موثوقية من Hermes Agent. استخدم نمط الهندسة المعمارية هنا كمرجع لمتانة الذاكرة، وتصميم الـ Fallback، وتوجيه النماذج المدرك للبوابات (gateway-aware).
هل تحتاج إلى أكثر من 300 نموذج AI عبر مفتاح API واحد؟ يوفر tokenlab.sh وصولاً موحداً إلى OpenAI و Anthropic و Google و DeepSeek و Groq والمزيد.
