0Pricing
AI Agents · Leçon

Détecter et interrompre les boucles infinies

Protections contre le dépassement du nombre maximal d’itérations, détection des actions répétées et disjoncteurs de boucle.

Détecter et interrompre les boucles infinies est une leçon AI Agents gratuite sur CoddyKit. Ceci est la leçon 3 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage AI Agents, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours AI Agents comprend 4 leçons au total.

La menace des boucles infinies

Un agent pris dans une boucle infinie consomme des jetons, mobilise des ressources et ne produit aucune sortie utile. En production, cela se traduit directement par de l’argent gaspillé et la frustration des utilisateurs.

Trois mécanismes fonctionnent ensemble pour empêcher les boucles infinies : les limites du nombre maximal d’itérations, la détection des actions répétées et les délais d’attente.

Garde-fou 1 : limite du nombre maximal d’itérations

Le garde-fou le plus simple et le plus important est une limite stricte d’étapes. Toute boucle d’agent doit en avoir une. Lorsque la limite est atteinte, l’agent renvoie soit sa meilleure réponse actuelle, soit un message d’échec explicite.

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
    }

Garde-fou 2 : détecter les actions répétées

Les actions répétées sont la caractéristique principale d’une boucle infinie. Conservez un historique des paires (nom de l’outil, arguments). Si la même paire apparaît plus de N fois, l’agent est bloqué : interrompez la boucle et injectez un message d’erreur.

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}

Suivre les actions répétées dans une fenêtre

Plutôt que de suivre les compteurs cumulés, détectez les répétitions dans une fenêtre glissante correspondant aux N dernières étapes. Cela permet de repérer les boucles qui varient légèrement, mais suivent un cycle sur une courte période.

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

Garde-fou 3 : délai d’attente en temps réel avec signal.alarm

Sur les systèmes Unix, signal.alarm() déclenche une SIGALRM après un nombre de secondes défini. Cela fournit un délai d’attente strict, même si la boucle de l’agent est bloquée dans un appel d’outil lent.

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)

Délai d’attente avec threading.Timer (multiplateforme)

threading.Timer fonctionne sur toutes les plateformes, y compris Windows. Définissez un indicateur après l’expiration du délai : la boucle de l’agent vérifie cet indicateur et se termine s’il est activé.

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}

Délai d’attente asynchrone avec asyncio.wait_for

Dans les architectures d’agents asynchrones, utilisez asyncio.wait_for(coroutine, timeout=N). Cette fonction déclenche asyncio.TimeoutError si la coroutine ne se termine pas dans le délai défini en secondes.

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))

Injecter des instructions d’échappement dans l’historique

Lorsqu’une boucle est détectée, ne l’interrompez pas simplement en silence. Injectez dans l’historique de la conversation un message système qui explique ce qui s’est passé et demande à l’agent de conclure. Le LLM a ainsi la possibilité de produire une réponse finale avant son interruption.

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'])

Journaliser l’interruption d’une boucle

Journalisez toujours le déclenchement d’un garde-fou de boucle. Vous obtenez ainsi un relevé de la fréquence et des raisons pour lesquelles les agents se bloquent, des données extrêmement utiles pour améliorer vos invites et vos implémentations d’outils.

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"}')

Combiner les trois garde-fous

Les agents de production doivent combiner les trois garde-fous : nombre maximal d’itérations (obligatoire), détection des répétitions (pour repérer les cycles) et délai d’attente (pour repérer les appels d’outils bloquants). Ensemble, ils rendent la boucle extrêmement robuste.

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'}

Tester les garde-fous de boucle avec des tests unitaires

Écrivez des tests unitaires dédiés à vos garde-fous de boucle. Créez un agent simulé qui appelle toujours le même outil et vérifiez que le garde-fou le détecte dans le nombre d’étapes prévu.

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')

Vérification des connaissances : détecter les boucles infinies

Testez votre compréhension des techniques de détection et d’interruption des boucles infinies.

Bilan : détecter et interrompre les boucles infinies

Vous pouvez désormais protéger vos agents contre les boucles infinies grâce à trois garde-fous complémentaires :

  • Nombre maximal d’itérations : limite stricte d’étapes, obligatoire dans toute boucle d’agent
  • Détection des répétitions : hacher les paires (outil, arguments) et interrompre la boucle lorsqu’elles apparaissent plus de N fois
  • Délai d’attente : signal.alarm() sous Unix, threading.Timer sur toutes les plateformes, asyncio.wait_for() pour l’asynchrone
  • Injection d’une échappatoire : donner à l’agent la possibilité de conclure avant l’arrêt forcé
  • Journalisez toujours le déclenchement d’un garde-fou : ces données sont précieuses pour améliorer les invites

Questions Fréquemment Posées

La leçon « Détecter et interrompre les boucles infinies » est-elle gratuite ?

Oui — le texte complet de « Détecter et interrompre les boucles infinies » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours AI Agents, passe à CoddyKit PRO. Le cours AI Agents comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « Détecter et interrompre les boucles infinies » ?

Protections contre le dépassement du nombre maximal d’itérations, détection des actions répétées et disjoncteurs de boucle. Tu pratiques AI Agents avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer AI Agents ?

Aucune expérience préalable n'est requise. AI Agents sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 3 sur 4.

Combien de temps prend la leçon « Détecter et interrompre les boucles infinies » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon AI Agents ?

Oui. Chaque leçon AI Agents inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. Échecs courants des boucles d’agents
  2. Journalisation des traces des étapes d’un agent
  3. Détecter et interrompre les boucles infinies
  4. Techniques de débogage pas à pas
← Retour à AI Agents