اكتشاف الحلقات اللانهائية وكسرها
حواجز الحد الأقصى للتكرارات، واكتشاف الإجراءات المتكررة، وقواطع دائرة الحلقات
اكتشاف الحلقات اللانهائية وكسرها درس مجاني في AI Agents على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في AI Agents، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة AI Agents 4 دروس في المجموع.
تهديد الحلقة اللانهائية
يستهلك الوكيل العالق في حلقة لانهائية الرموز المميزة، ويحجز الموارد، ولا ينتج مخرجات مفيدة. وفي بيئة الإنتاج، يترجم ذلك مباشرةً إلى أموال مهدرة وإحباط للمستخدمين.
تعمل ثلاث آليات معًا لمنع الحلقات اللانهائية: حدود التكرار القصوى، واكتشاف الإجراءات المتكررة، والمهلات الزمنية.
آلية الحماية 1: الحد الأقصى للتكرارات
أبسط آلية حماية وأهمها هي وضع حد صارم للخطوات. ويجب أن تحتوي كل حلقة وكيل على هذا الحد. عند الوصول إليه، يُرجع الوكيل إما أفضل إجابة حالية لديه أو رسالة فشل صريحة.
MAX_ITERATIONS = 20
def run_agent(query: str) -> dict:
history = []
for step in range(1, MAX_ITERATIONS + 1):
action = decide_action(query, history)
if action['type'] == 'final_answer':
return {'status': 'ok', 'answer': action['answer'], 'steps': step}
result = execute_tool(action['tool'], action['args'])
history.append({'step': step, 'tool': action['tool'], 'result': result})
# Hard stop — max iterations reached
return {
'status': 'max_iterations_reached',
'answer': None,
'steps': MAX_ITERATIONS
}آلية الحماية 2: اكتشاف الإجراءات المتكررة
تُعد الإجراءات المتكررة العلامة المميزة للحلقة اللانهائية. تتبعوا سجلًا لأزواج (اسم الأداة، الوسيطات). إذا ظهر الزوج نفسه أكثر من N مرة، يكون الوكيل عالقًا؛ فأوقفوا الحلقة وأدخلوا رسالة خطأ.
import hashlib
import json
def action_hash(tool_name: str, args: dict) -> str:
payload = json.dumps({'tool': tool_name, 'args': args}, sort_keys=True)
return hashlib.md5(payload.encode()).hexdigest()
def run_agent_with_repeat_detection(query: str) -> dict:
history = []
action_counts = {}
for step in range(1, 21):
action = decide_action(query, history)
if action['type'] == 'final_answer':
return {'status': 'ok', 'answer': action['answer']}
key = action_hash(action['tool'], action['args'])
action_counts[key] = action_counts.get(key, 0) + 1
if action_counts[key] > 2: # seen this exact action more than twice
history.append({
'role': 'system',
'content': f'You have called {action["tool"]} with the same arguments {action_counts[key]} times. '
f'This approach is not working. Try a completely different strategy or state what you know so far.'
})
continue
result = execute_tool(action['tool'], action['args'])
history.append({'tool': action['tool'], 'result': result})
return {'status': 'loop_detected', 'answer': None}تتبع الإجراءات المتكررة ضمن نافذة
بدلًا من تتبع أعداد التكرار طوال مدة التشغيل، اكتشفوا التكرار ضمن نافذة منزلقة لآخر N خطوات. يلتقط ذلك الحلقات التي تختلف قليلًا لكنها تدور ضمن دورة قصيرة.
from collections import deque
def is_cycling(recent_actions: deque, window: int = 6) -> bool:
if len(recent_actions) < window:
return False
# Check if the last window/2 actions repeat the first window/2
half = window // 2
first_half = list(recent_actions)[:half]
second_half = list(recent_actions)[half:window]
return first_half == second_half
# In the agent loop:
# recent_actions = deque(maxlen=6)
# recent_actions.append(action_hash(tool, args))
# if is_cycling(recent_actions):
# print('Cycling detected — breaking loop')
# break
recent = deque(['a', 'b', 'a', 'b'], maxlen=6)
print(is_cycling(recent, window=4)) # True — cycling detectedآلية الحماية 3: مهلة زمنية فعلية باستخدام signal.alarm
في أنظمة Unix، ترفع signal.alarm() إشارة SIGALRM بعد عدد محدد من الثواني. ويوفر ذلك مهلة زمنية صارمة حتى إذا كانت حلقة الوكيل عالقة داخل استدعاء بطيء لأداة.
import signal
class AgentTimeout(Exception):
pass
def timeout_handler(signum, frame):
raise AgentTimeout('Agent exceeded time limit')
def run_agent_with_signal_timeout(query: str, timeout_seconds: int = 60) -> dict:
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(timeout_seconds) # set the alarm
try:
result = run_core_agent_loop(query)
signal.alarm(0) # cancel the alarm on success
return result
except AgentTimeout:
signal.alarm(0)
return {'status': 'timeout', 'answer': None}
except Exception as e:
signal.alarm(0)
raise
# Note: signal.alarm is Unix-only (Linux/Mac)المهلة الزمنية باستخدام threading.Timer (متعدد المنصات)
تعمل threading.Timer على جميع المنصات، بما في ذلك Windows. اضبطوا علامة بعد انقضاء مدة المهلة؛ وتتحقق حلقة الوكيل من العلامة وتخرج إذا كانت مضبوطة.
import threading
def run_agent_with_timer_timeout(query: str, timeout_seconds: int = 60) -> dict:
timed_out = threading.Event()
def set_timeout():
timed_out.set()
timer = threading.Timer(timeout_seconds, set_timeout)
timer.start()
history = []
try:
for step in range(1, 21):
if timed_out.is_set():
return {'status': 'timeout', 'answer': None, 'steps': step}
action = decide_action(query, history)
if action['type'] == 'final_answer':
return {'status': 'ok', 'answer': action['answer']}
result = execute_tool(action['tool'], action['args'])
history.append({'tool': action['tool'], 'result': result})
finally:
timer.cancel() # always cancel if done before timeout
return {'status': 'max_steps', 'answer': None}المهلة غير المتزامنة باستخدام asyncio.wait_for
في بنى الوكلاء غير المتزامنة، استخدموا asyncio.wait_for(coroutine, timeout=N). فهي ترفع asyncio.TimeoutError إذا لم تكتمل coroutine خلال عدد الثواني المحدد.
import asyncio
async def run_async_agent(query: str) -> dict:
history = []
for step in range(1, 21):
action = await async_decide_action(query, history)
if action['type'] == 'final_answer':
return {'status': 'ok', 'answer': action['answer']}
result = await async_execute_tool(action['tool'], action['args'])
history.append({'tool': action['tool'], 'result': result})
return {'status': 'max_steps', 'answer': None}
async def run_with_timeout(query: str, timeout: float = 60.0) -> dict:
try:
return await asyncio.wait_for(run_async_agent(query), timeout=timeout)
except asyncio.TimeoutError:
return {'status': 'timeout', 'answer': None}
# asyncio.run(run_with_timeout('What is Python?', timeout=30.0))إدخال تعليمات الخروج في السجل
عند اكتشاف حلقة، لا تكتفوا بإيقافها بصمت. أدخلوا رسالة نظام في سجل المحادثة تشرح ما حدث وتطلب من الوكيل إنهاء المهمة. ويمنح ذلك LLM فرصة لإنتاج إجابة نهائية قبل قطعه.
def inject_loop_escape(history: list, step: int, reason: str):
message = (
f'[SYSTEM] You have been running for {step} steps. Reason: {reason}. '
f'You MUST now provide a FINAL_ANSWER based on what you have found so far, '
f'even if the information is incomplete. Do not call any more tools.'
)
history.append({'role': 'system', 'content': message})
# In the agent loop, when approaching the limit:
# if step >= MAX_ITERATIONS - 2:
# inject_loop_escape(history, step, 'approaching max iteration limit')
# Or when a repeat is detected:
# if action_counts[key] > 2:
# inject_loop_escape(history, step, 'repeated action detected')
if __name__ == '__main__':
demo_history = []
inject_loop_escape(demo_history, step=18, reason='approaching max iteration limit')
print(demo_history[-1]['content'])
تسجيل وقت كسر الحلقة
سجلوا دائمًا وقت تفعيل إحدى آليات حماية الحلقة. ينشئ ذلك سجلًا يوضح مدى تكرار تعثر الوكلاء وسببه، وهي بيانات لا تقدر بثمن لتحسين التعليمات البرمجية وعمليات تنفيذ الأدوات.
import logging
logger = logging.getLogger('agent_guard')
def check_and_break_loop(step: int, action_counts: dict, current_key: str) -> bool:
count = action_counts.get(current_key, 0)
if count > 2:
logger.warning(
f'Infinite loop detected at step {step}. '
f'Action hash {current_key[:8]} seen {count} times. '
f'Breaking loop.'
)
return True # signal to break
if step >= 18: # approaching limit
logger.warning(
f'Approaching max iterations at step {step}. '
f'Injecting escape prompt.'
)
return False
if __name__ == '__main__':
import sys
logging.basicConfig(level=logging.WARNING, format='%(message)s', stream=sys.stdout)
demo_counts = {'search:{"q": "weather"}': 3}
check_and_break_loop(step=10, action_counts=demo_counts, current_key='search:{"q": "weather"}')
دمج آليات الحماية الثلاث
ينبغي للوكلاء المخصصين للإنتاج دمج آليات الحماية الثلاث جميعها: الحد الأقصى للتكرارات (إلزامي)، واكتشاف التكرار (لالتقاط الدورات)، والمهلة الزمنية (لالتقاط استدعاءات الأدوات الحاجزة). وتجعل هذه الآليات الحلقة منيعة عند جمعها معًا.
import threading
import hashlib
import json
def run_production_agent(query: str) -> dict:
MAX_STEPS = 20
TIMEOUT_SEC = 120
timed_out = threading.Event()
timer = threading.Timer(TIMEOUT_SEC, timed_out.set)
timer.start()
history = []
action_counts = {}
try:
for step in range(1, MAX_STEPS + 1):
if timed_out.is_set():
return {'status': 'timeout'}
action = decide_action(query, history)
if action['type'] == 'final_answer':
return {'status': 'ok', 'answer': action['answer']}
key = hashlib.md5(json.dumps(action, sort_keys=True).encode()).hexdigest()
action_counts[key] = action_counts.get(key, 0) + 1
if action_counts[key] > 2:
inject_loop_escape(history, step, 'repeat detected')
continue
result = execute_tool(action['tool'], action['args'])
history.append({'tool': action['tool'], 'result': result})
finally:
timer.cancel()
return {'status': 'max_steps'}اختبار آليات حماية الحلقة باختبارات الوحدة
اكتبوا اختبارات وحدة مخصصة لآليات حماية الحلقة. أنشئوا وكيلًا وهميًا يستدعي الأداة نفسها دائمًا، وتحققوا من أن آلية الحماية تكتشف ذلك خلال عدد الخطوات المتوقع.
from unittest.mock import MagicMock
def test_repeat_detection_breaks_loop():
# Create a mock that always returns the same action
always_same_action = MagicMock(return_value={
'type': 'tool',
'tool': 'search_web',
'args': {'query': 'same query'}
})
always_success = MagicMock(return_value='some result')
result = run_agent_with_repeat_detection(
query='test',
decide_action=always_same_action,
execute_tool=always_success
)
# Should stop due to loop detection, not run all 20 steps
assert result['status'] in ('loop_detected', 'max_iterations_reached')
# Should not have run all 20 steps (loop should be detected by step 6-7)
print('Loop guard test passed')اختبار المعرفة: اكتشاف الحلقات اللانهائية
اختبروا فهمكم لتقنيات اكتشاف الحلقات اللانهائية وكسرها.
مراجعة: اكتشاف الحلقات اللانهائية وكسرها
يمكنكم الآن حماية وكلائكم من الحلقات اللانهائية باستخدام ثلاث آليات حماية متكاملة:
- الحد الأقصى للتكرارات: حد صارم للخطوات، وهو إلزامي في كل حلقة وكيل
- اكتشاف التكرار: احسبوا تجزئة أزواج (الأداة، الوسيطات) وأوقفوا الحلقة عند ظهورها أكثر من N مرة
- المهلة الزمنية: استخدموا
signal.alarm()في Unix، وthreading.Timerعبر المنصات، وasyncio.wait_for()للعمليات غير المتزامنة - إدخال تعليمات الخروج: امنحوا الوكيل فرصة لإنهاء المهمة قبل الإيقاف القسري
- سجلوا دائمًا وقت تفعيل إحدى آليات الحماية؛ فهذه بيانات قيّمة لتحسين التعليمات
الأسئلة الشائعة
هل درس «اكتشاف الحلقات اللانهائية وكسرها» مجاني؟
نعم — نص درس «اكتشاف الحلقات اللانهائية وكسرها» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة AI Agents، انتقل إلى CoddyKit PRO. تتضمن دورة AI Agents 4 دروس في المجموع.
ماذا ستتعلم في «اكتشاف الحلقات اللانهائية وكسرها»؟
حواجز الحد الأقصى للتكرارات، واكتشاف الإجراءات المتكررة، وقواطع دائرة الحلقات تتمرن على AI Agents مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ AI Agents؟
لا تُشترط خبرة سابقة. AI Agents على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.
كم من الوقت يستغرق درس «اكتشاف الحلقات اللانهائية وكسرها»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس AI Agents هذا؟
نعم. كل درس في AI Agents يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- أخطاء حلقات الوكلاء الشائعة
- تسجيل التتبعات لخطوات الوكيل
- اكتشاف الحلقات اللانهائية وكسرها
- تقنيات تصحيح الأخطاء خطوة بخطوة