وكيل AI واحد دخل في حلقة tool calls، نده search_database 213 مرة في 12 دقيقة، وحرق 47 دولار من فاتورة Claude قبل ما الـ Kubernetes job يطفي نفسه على الـ timeout. السطور الجاية فيها 4 طبقات دفاع شغّالة على prod، وكود يعيد إنتاج المشكلة في 30 سطر.
المشكلة باختصار
الوكلاء اللي بيستخدموا tool use (Anthropic، OpenAI Assistants، LangChain agents) بيدخلوا في loop داخلي: النموذج بيقرر ينده أداة، ياخد الرد، يقرر ينده أداة تانية، وهكذا لحد ما يجاوب. لو الـ logic بتاع التوقف ضعيف، الوكيل بينده نفس الأداة بنفس المعطيات 50 و100 و200 مرة. مفيش exception، مفيش error — بس الفاتورة بتطلع.
المثال البسيط: الطبّاخ اللي بينده النادل كل دقيقة
تخيّل طبّاخ بيحضّر طلب. كل ما يخلّص خطوة بيقول للنادل "اجبلي ملح"، يجيبه، بعدين "اجبلي ملح" تاني، تالت، رابع. النادل مش هيوقفه — مش شغلته يفهم إذا الطلب اتنفّذ ولا لأ. المطعم هيقفل والطبّاخ لسه بيطلب ملح.
ده بالظبط اللي بيحصل مع وكيل AI. النموذج "بينسى" إنه ندى الأداة دي قبل كده، أو رد الأداة ما ساعدوش يكمّل، فبيكرر النداء بنفس المعطيات.
التعريف العلمي: ReAct Loop وعلاماته
الـ pattern الشائع اسمه ReAct (Reasoning + Acting)، اقترحه Yao et al في ورقة 2022. الفكرة: النموذج بيتبادل بين Thought → Action → Observation لحد ما يوصل لإجابة نهائية. الحلقة بتبقى مرضية (pathological) لما يحصل واحد من 3 أنماط:
- Identical repetition: نفس الأداة بنفس الـ arguments مرتين متتاليتين على الأقل.
- Argument cycling: A→B→A→B→A بدون تقدّم في الـ context.
- No-progress drift: 8+ نداءات بدون تغيّر في الـ
state hashاللي بيمثّل ما تعلّمه الوكيل.
الافتراض هنا: الـ orchestrator بيدير الحلقة (مش النموذج)، وبتقدر تشوف كل tool_use و tool_result قبل ما تبعتهم تاني للنموذج. ده بينطبق على Anthropic Messages API، LangGraph، و CrewAI.
إعادة إنتاج المشكلة في 30 سطر
الكود ده وكيل بسيط بيدوّر على بيانات عميل. الأداة بترجّع not_found فالنموذج بيكرر النداء بنفس الـ ID لحد ما الـ context تتملّى:
import anthropic
client = anthropic.Anthropic()
messages = [{"role": "user", "content": "هات بيانات العميل رقم 9921"}]
tools = [{
"name": "search_database",
"description": "ابحث عن عميل بالـ ID",
"input_schema": {
"type": "object",
"properties": {"customer_id": {"type": "string"}},
"required": ["customer_id"]
}
}]
while True:
resp = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=messages,
)
if resp.stop_reason == "end_turn":
break
tool_call = next(b for b in resp.content if b.type == "tool_use")
messages.append({"role": "assistant", "content": resp.content})
messages.append({"role": "user", "content": [{
"type": "tool_result",
"tool_use_id": tool_call.id,
"content": "not_found"
}]})على Claude Sonnet 4.6 الكود ده وصل لـ 47 iteration في تجربتي قبل ما الـ context يضرب الحد، باستهلاك ~140 ألف input token تراكمي. التكلفة التقديرية: ~0.42$ لطلب واحد. ضرب في 100 طلب يومي عمل في حادثة prod حقيقية مرّت عليّ = 42$ يوميًا بدون فايدة.
الدفاعات الأربع: من الأرخص للأذكى
كل طبقة منهم بتمسك حالة مختلفة. شغّلهم كلهم — مش بدائل، طبقات.
1. حد أقصى للنداءات (max_iterations)
أرخص دفاع، أوّل دفاع. متوسّط طلب صحيح بياخد 3 إلى 8 نداءات. حدّ 15 بيمسك 99% من الحالات الشاذة بدون ما يقطع طلب شرعي.
MAX_ITERATIONS = 15
for i in range(MAX_ITERATIONS):
resp = client.messages.create(...)
if resp.stop_reason == "end_turn":
return resp
raise AgentLoopError(f"تجاوز الحد {MAX_ITERATIONS}")2. كشف التكرار الحرفي
اعمل hash من tool_name + json.dumps(arguments, sort_keys=True). لو الـ hash اتكرر مرتين متتاليتين، اقطع. ده بيمسك 70% من حالات الـ loops.
import hashlib, json
last_hash = None
def call_signature(tool_name, args):
payload = json.dumps({"t": tool_name, "a": args}, sort_keys=True)
return hashlib.sha256(payload.encode()).hexdigest()
sig = call_signature(tool_call.name, tool_call.input)
if sig == last_hash:
raise AgentLoopError("نداء متطابق مرّتين متتاليتين")
last_hash = sig3. حد تكلفة لكل طلب (cost guard)
الـ max_iterations مش كافي لو فيه نداء واحد بيرجّع 50KB من الـ tool_result. اقفل عند سقف توكن تراكمي.
MAX_INPUT_TOKENS = 60000
total_input = 0
for i in range(MAX_ITERATIONS):
resp = client.messages.create(...)
total_input += resp.usage.input_tokens
if total_input > MAX_INPUT_TOKENS:
raise AgentBudgetError(f"تجاوز ميزانية التوكن: {total_input}")4. State hash لاكتشاف عدم التقدّم
الدفاعات الأولى بتفشل لو الوكيل بيبدّل بين أداتين بدون تقدّم. اعمل hash من مجموعة الـ observations المختلفة اللي شافها الوكيل. لو الـ hash ما تغيّرش لـ 5 iterations متتالية، اقطع.
observations = set()
no_progress_streak = 0
prev_state = None
for i in range(MAX_ITERATIONS):
resp = client.messages.create(...)
obs = extract_tool_result_text(resp)
observations.add(hashlib.sha256(obs.encode()).hexdigest())
state = hashlib.sha256(
"|".join(sorted(observations)).encode()
).hexdigest()
no_progress_streak = no_progress_streak + 1 if state == prev_state else 0
if no_progress_streak >= 5:
raise AgentStuckError("الوكيل ما تعلّمش جديد لـ 5 iterations")
prev_state = stateالـ trade-offs بصراحة
- max_iterations منخفض جدًا: لو حطيت 5 بدل 15، هتقطع 4% من الطلبات الشرعية اللي محتاجة استكشاف فعلي.
- cost guard: بيكسب أمان مالي، بيخسر إن طلبات ضخمة شرعية (مثلاً تحليل ملف 200KB) ممكن تترفض. الحل: tier للأنواع المختلفة من الطلبات.
- state hash: بيمسك الـ subtle loops، بيكلّفك ~3ms لكل iteration للـ hashing — لا شيء عمليًا.
- الافتراض إنك بتدير الحلقة بنفسك. لو بتستخدم Claude Managed Agents أو OpenAI Assistants v2، الـ orchestration مغلقة وحلولك بتبقى من خلال tool design + system prompt بس.
متى لا تستخدم هذه الدفاعات
وكلاء التحقيقات الطويلة (research agents زي اللي في Anthropic Computer Use أو Browserbase) ممكن طبيعي ياخدوا 40-80 نداء صحيح. لو طبّقت max_iterations=15 هتكسر استخدامًا شرعيًا. الحل في الحالة دي: ارفع الـ max_iterations لـ 100، لكن خلّي state hash و cost guard شغّالين بنفس القيم. هما اللي هيمسكوا الـ pathology، مش العداد.
زيّ كده: مهام مفتوحة (open-ended) فيها متعدد الأهداف. الوكيل ساعتها بيبدّل بين سياقات شرعيًا، فدفاع التكرار الحرفي بيدّيك false positive. اشتغل بـ scoping أوضح في الـ system prompt قبل ما تضيف دفاع.
الخطوة التالية
افتح كود الوكيل بتاعك دلوقتي، حط max_iterations=15 في الـ for loop الموجود، وأضف الـ last_hash check من الفقرة الثانية. ده دفاع 80% من الفاتورة في 8 سطور. باقي الطبقات ابنيها لما تشوف أول incident فعلي في الـ logs.
المصادر
- Yao et al., "ReAct: Synergizing Reasoning and Acting in Language Models", arXiv:2210.03629, 2022.
- Anthropic Messages API — Tool Use documentation: docs.anthropic.com/en/docs/build-with-claude/tool-use
- Anthropic — "Building effective agents", anthropic.com/research/building-effective-agents (December 2024).
- LangGraph documentation on agent loop control: langchain-ai.github.io/langgraph
- أرقام التكلفة بناءً على Claude Sonnet 4.6 pricing (3$/MTok input, 15$/MTok output) كما هو منشور على anthropic.com/pricing.