لماذا يستمر وكيل الذكاء الاصطناعي (AI Agent) الخاص بك في فقدان ذاكرته (وكيف قمنا بإصلاح ذلك)
لقد أجرى وكيل الذكاء الاصطناعي الخاص بك للتو محادثة استمرت 30 دقيقة مع مستخدم. ناقشوا متطلبات المشروع، وشاركوا التفضيلات، واتخذوا القرارات. ثم يكتب المستخدم /new لبدء جلسة جديدة.
يحاول الوكيل دمج تلك المحادثة في الذاكرة طويلة المدى. تفشل مكالمة LLM. تجاوز حد المعدل (Rate limit). انتهت المهلة (Timeout). أو يعيد النموذج نصاً بدلاً من استدعاء الأداة المطلوبة.
لقد ذهبت الذاكرة. ثلاثون دقيقة من السياق، تبخرت.
يحدث هذا بمعدل أكبر مما تعتقد. لقد تتبعنا ذلك عبر نسخ LemonClaw الخاصة بنا: كان لعملية دمج الذاكرة معدل فشل يقارب 15% على أي نموذج منفرد. بالنسبة لميزة يُفترض أنها بنية تحتية غير مرئية، فإن هذا أمر غير مقبول.
كيف تتعامل الأطر الأخرى مع هذا (إنهم لا يفعلون)
تتعامل معظم أطر عمل وكلاء الذكاء الاصطناعي مع دمج الذاكرة كمكالمة LLM بسيطة. إذا نجحت، فبها ونعمت. وإذا لم تنجح، فستضيع الذاكرة.
يستخدم OpenClaw، إطار عمل الوكلاء مفتوح المصدر الأكثر شعبية، نفس النموذج للدمج كما هو الحال في المحادثة. مكالمة Claude Sonnet تكلف 0.003 دولار وتستغرق أكثر من 8 ثوانٍ، فقط لتلخيص دردشة لن يراها المستخدم أبداً. عندما تفشل تلك المكالمة (حد المعدل، انتهاء المهلة، خطأ في النموذج)، يقوم إطار العمل بتسجيل تحذير ويستمر. لقد ضاع سياق المستخدم.
nanobot، إطار عمل مشهور آخر، لديه نفس البنية. نموذج واحد، محاولة واحدة، لا يوجد نظام بديل (fallback). وظيفة الدمج لا تحتوي حتى على مهلة زمنية. يؤدي بطء الاستجابة من المصدر (أخطاء Cloudflare 524 شائعة) إلى حظر الجلسة بأكملها حتى ينقطع الاتصال.
لا يفصل أي من الإطارين عملية الدمج عن النموذج الرئيسي. لا يمتلك أي منهما منطقاً بديلاً لعمليات الذاكرة. ولا يميز أي منهما بين "فشل استدعاء 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). تفشل بعض النماذج، وخاصة الصغيرة منها التي تعمل على محركات استدلال سريعة، أحياناً في إنشاء استدعاءات وظائف صالحة بناءً على مطالبات (prompts) معقدة. تعيد API رمز 200 مع وجود ServiceUnavailableError ملفوف داخل MidStreamFallbackError. يرى منطق إعادة المحاولة استثناءً، فيعيد محاولة نفس النموذج، ويحصل على نفس الخطأ.
وضع الفشل 2: النموذج "ينجح" ولكنه لا يستدعي الأداة. يعيد LLM استجابة صالحة تماماً. HTTP 200. لا توجد أخطاء. ولكن بدلاً من استدعاء save_memory ببيانات منظمة، يكتب ملخصاً نصياً عادياً. يعتبر محرك إعادة المحاولة الخاص بك هذا نجاحاً. تتحقق وظيفة الدمج من استدعاءات الأدوات، فلا تجد شيئاً، وتستسلم.
وضع الفشل الثاني هو الأكثر خطورة. طبقة النقل تعتقد أن كل شيء نجح. طبقة الأعمال (business layer) تعرف أنه لم ينجح. لن يؤدي أي قدر من إعادات المحاولة على مستوى HTTP إلى إصلاح نموذج لا يفهم مخطط الأداة (tool schema) الخاص بك.
بنية البدائل ثنائية الطبقة (Dual-Layer 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 chain) بين كلتا الطبقتين، ويتم تعريفها مرة واحدة في كتالوج مركزي.
هذا نهج مختلف جذرياً عن إعادة محاولة نفس النموذج. عندما يفشل نموذج في استدعاء أداة، نادراً ما يفيد إعادة محاولته بنفس المطالبة. الانتقال إلى نموذج مختلف بأوزان مختلفة وسلوك استدعاء أدوات مختلف هو الحل.
كتالوج النماذج: المصدر الوحيد للحقيقة
يحتوي كل نموذج في كتالوجنا على حقل 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 الذي يواجهه المستخدم بينما تظل مشاركة في سلاسل البدائل. يرى المستخدمون 16 نموذجاً يمكنهم التبديل بينها. يستخدم النظام 19. النماذج الثلاثة المخفية موجودة حصرياً للمهام الخلفية مثل دمج الذاكرة، حيث تكون السرعة والتكلفة أكثر أهمية من جودة المحادثة.
هذا الكتالوج هو المصدر الوحيد للحقيقة لجميع عمليات توجيه النماذج. إضافة نموذج جديد إلى سلسلة البدائل يعني إضافة سطر واحد فقط. لا توجد ملفات تهيئة للمزامنة، ولا متغيرات بيئة للتحديث، ولا نصوص نشر (deployment scripts) للتعديل.
طبقة النقل: بدائل متسلسلة مع الكشف عن الحلقات المفرغة
يقوم محرك إعادة المحاولة بالسير في سلسلة البدائل باستخدام مجموعة "تمت زيارتها" (visited set) لمنع الحلقات اللانهائية:
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 نقطة نهاية أخرى. يقوم محرك البدائل بتحديد البوابة الصحيحة لكل نموذج في السلسلة، مما يمنع عدم تطابق البروتوكولات مثل إرسال طلبات Anthropic إلى نقطة نهاية OpenAI.
هذا تفصيل تتجاهله معظم أطر العمل تماماً. إنهم يفترضون أن جميع النماذج تتحدث نفس البروتوكول. في بيئة الإنتاج، مع 19 نموذجاً عبر 4 تنسيقات API مختلفة، ينهار هذا الافتراض على الفور.
طبقة الأعمال: التحقق من استدعاء الأداة
تضيف وظيفة الدمج حلقة بدائل خاصة بها في الأعلى:
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) أيضاً إلى تشغيل البدائل. إذا استغرق النموذج أكثر من 30 ثانية (شائع مع أخطاء Cloudflare 524 في المصادر البطيئة)، فإن الوظيفة تلتقط TimeoutError وتحاول تجربة النموذج التالي بدلاً من حظر جلسة المستخدم إلى أجل غير مسمى.
لماذا Groq لعملية الدمج
دمج الذاكرة هو مهمة خلفية. لا يرى المستخدم الإخراج. هم فقط بحاجة إليه ليعمل. وهذا يجعله مرشحاً مثالياً للنماذج السريعة والرخيصة.
تستخدم معظم أطر العمل نفس النموذج الباهظ لكل شيء. إذا كنت تقوم بتشغيل Claude Sonnet للمحادثة، فأنت تقوم أيضاً بتشغيل Claude Sonnet لدمج الذاكرة. هذا يكلف 3 دولارات لكل مليون رمز مدخل ويستغرق أكثر من 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 لديها موثوقية أقل في استدعاء الأدوات مع المطالبات المعقدة. لهذا السبب بالضبط توجد البدائل ثنائية الطبقة. عندما يفشل llama-3.3-70b في استدعاء الأداة، يتولى qwen3-32b المهمة. إذا فشل ذلك أيضاً، يحاول llama-4-scout. إذا فشلت جميع نماذج Groq الثلاثة، يتعامل gpt-4.1-mini مع الأمر بموثوقية تقارب 100% في استدعاء الأدوات.
في بيئة الإنتاج، نرى النموذج الأساسي ينجح في حوالي 85% من الوقت. تصل السلسلة إلى gpt-4.1-mini في أقل من 2% من عمليات الدمج. معدل الفشل الإجمالي: صفر فعلياً.
نتائج الإنتاج
لقد قمنا بنشر هذا في نسختين من LemonClaw واختبرناه بمحادثات Telegram حقيقية.
النشر الأول (بدائل أحادية الطبقة فقط):
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."
التقطت طبقة النقل الفشل الأول واستخدمت البديل. لكن qwen3-32b أعاد نصاً دون استدعاء الأداة. لم تتمكن البدائل أحادية الطبقة من التعامل مع هذا. هذا هو السيناريو الدقيق الذي كان سيؤدي بصمت إلى فقدان الذاكرة في أي إطار عمل آخر.
النشر الثاني (بدائل ثنائية الطبقة):
Memory consolidation (archive_all): 60 messages
model=llama-3.3-70b-versatile → success
Memory consolidation done: 60 messages remaining
نفس النموذج، نفس حجم الرسائل. هذه المرة نجح من المحاولة الأولى. الطبيعة المتقطعة لفشل استدعاء الأدوات هي بالضبط سبب احتياجك لسلسلة بدائل بدلاً من نموذج احتياطي واحد.
عندما يفشل النموذج الأساسي، تلتقطه السلسلة:
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
تمت تجربة أربعة نماذج، وحُفظت الذاكرة. يرى المستخدم "بدأت جلسة جديدة." وليس لديه أدنى فكرة عن حدوث أي من هذا.
الفجوة المعمارية
نظام الذاكرة في LemonClaw مقابل البدائل، ميزة تلو الأخرى:
| القدرة | إطار عمل وكيل AI تقليدي | LemonClaw |
|---|---|---|
| نموذج الدمج | نفس نموذج المحادثة (غالي، بطيء) | سلسلة نماذج مستقلة، معجلة بـ Groq |
| التعامل مع الفشل | تسجيل تحذير، فقدان الذاكرة | بدائل ثنائية الطبقة، بعمق 5 نماذج |
| بدائل النقل | إعادة محاولة نفس النموذج 3 مرات | بدائل متسلسلة عبر نماذج مختلفة |
| بدائل منطق الأعمال | لا يوجد | التحقق من استدعاء الأداة + تبديل النموذج |
| حماية المهلة | لا يوجد (Cloudflare 524 يحظر الجلسة) | asyncio.wait_for(timeout=30) + بدائل |
| اقتطاع الجلسة | لا يوجد (السياق ينمو للأبد) | اقتطاع الرسائل القديمة بعد الدمج |
| البحث في السجل | لا يوجد | نافذة HISTORY.md متحركة، قابلة للبحث بـ grep |
| النماذج الداخلية | غير مدعومة | hidden=True للنماذج الخاصة بالنظام فقط |
| منع الحلقات | غير مطلوب (لا توجد سلاسل) | مجموعة visited تمنع حلقات A→B→A |
| دقة البوابة | افتراض تنسيق API واحد | بوابة لكل نموذج مع الكشف عن البروتوكول |
يمثل كل صف في هذا الجدول فشلاً في بيئة الإنتاج إما جربناه بأنفسنا أو لاحظناه في متتبعات مشكلات أطر العمل الأخرى. البدائل ثنائية الطبقة، كتالوج النماذج المخفية، دقة البوابة لكل نموذج، البدائل الناتجة عن انتهاء المهلة: لا يوجد أي من هذه في OpenClaw أو nanobot أو أي إطار عمل وكلاء مفتوح المصدر آخر قمنا بفحصه.
ماذا تعلمنا
"نجاح الطلب" ليس "نجاح المهمة". تعمل محركات إعادة المحاولة العامة على مستوى HTTP. لا يمكنهم معرفة أن استجابة 200 مع JSON صالح هي في الواقع فشل لأن النموذج لم يستخدم الأداة التي طلبتها. تحتاج العمليات الحيوية للأعمال إلى معايير نجاح خاصة بها ومنطق بدائل خاص بها.
النماذج الصغيرة تفشل بشكل مختلف عن النماذج الكبيرة. النماذج الكبيرة (GPT-4.1، Claude Sonnet) تستدعي الأدوات دائماً تقريباً عند طلبها. النماذج الصغيرة في محركات الاستدلال السريع تنشئ أحياناً استجابات تبدو صالحة ولكنها تتجاهل مخطط الأداة تماماً. هذا ليس خطأ يمكنك إصلاحه بهندسة المطالبات. إنها فجوة في القدرات تتطلب تخفيفاً معمارياً.
الاختبار ببيانات الإنتاج، وليس البيانات الاصطناعية. نجح اختبارنا الأولي المكون من 6 رسائل اصطناعية في كل نموذج. فشلت جلسة الـ 60 رسالة الحقيقية مع سجل استدعاءات الأدوات والطوابع الزمنية واللغات المختلطة في اثنين من نماذج Groq الثلاثة. يكشف تعقيد البيانات الحقيقية عن أوضاع فشل لن تظهرها بيانات الاختبار النظيفة أبداً.
LemonClaw هو إطار عمل وكلاء ذكاء اصطناعي مفتوح المصدر مزود بتوجيه مدمج متعدد النماذج، وذاكرة مستمرة، وأكثر من 10 تكاملات مع منصات الدردشة. يتوفر نظام البدائل ثنائية الطبقة بالكامل الموصوف هنا في الإصدار مفتوح المصدر. قم بتشغيله على خادمك الخاص: github.com/hedging8563/lemonclaw
هل تحتاج إلى أكثر من 300 نموذج ذكاء اصطناعي عبر مفتاح API واحد؟ توفر lemondata.cc وصولاً موحداً إلى OpenAI و Anthropic و Google و DeepSeek و Groq والمزيد.
