AI Agents · Lección

Detección y ruptura de bucles infinitos

Límites de iteraciones máximas, detección de acciones repetidas y disyuntores para bucles.

Lección 3 de 413 pasos

Detección y ruptura de bucles infinitos es una lección gratuita de AI Agents en CoddyKit. Esta es la lección 3 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de AI Agents, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de AI Agents incluye 4 lecciones en total.

La amenaza de los bucles infinitos

Un agente atrapado en un bucle infinito consume tokens, bloquea recursos y no produce ninguna salida útil. En producción, esto se traduce directamente en dinero desperdiciado y frustración para los usuarios.

Tres mecanismos funcionan conjuntamente para evitar los bucles infinitos: límites máximos de iteraciones, detección de acciones repetidas y tiempos de espera.

Protección 1: Límite máximo de iteraciones

La protección más sencilla e importante es un límite estricto de pasos. Todo bucle de agente debe tener uno. Cuando se alcanza el límite, el agente devuelve su mejor respuesta actual o un mensaje de fallo explícito.

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
    }

Protección 2: Detección de acciones repetidas

Las acciones repetidas son el principal indicio de un bucle infinito. Realice un seguimiento de un historial de pares (tool_name, arguments). Si el mismo par aparece más de N veces, el agente está atascado: detenga el bucle e inyecte un mensaje de error.

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}

Seguimiento de acciones repetidas en una ventana

En lugar de realizar un seguimiento de los recuentos de todos los pasos, detecte repeticiones en una ventana deslizante que contenga los últimos N pasos. Esto permite detectar bucles que varían ligeramente, pero se repiten en un ciclo breve.

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

Protección 3: Tiempo de espera en tiempo real con signal.alarm

En sistemas Unix, signal.alarm() genera un SIGALRM después del número de segundos especificado. Esto proporciona un tiempo de espera estricto incluso si el bucle del agente está bloqueado en una llamada lenta a una herramienta.

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)

Tiempo de espera con threading.Timer (multiplataforma)

threading.Timer funciona en todas las plataformas, incluido Windows. Establezca un indicador cuando transcurra el tiempo de espera; el bucle del agente comprueba el indicador y sale si está activado.

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}

Tiempo de espera asíncrono con asyncio.wait_for

En arquitecturas de agentes asíncronos, use asyncio.wait_for(coroutine, timeout=N). Genera asyncio.TimeoutError si la corrutina no se completa dentro del número de segundos especificado.

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

Inyección de instrucciones de salida en el historial

Cuando se detecte un bucle, no se limite a detenerlo silenciosamente. Inyecte un mensaje del sistema en el historial de la conversación que explique lo sucedido y pida al agente que concluya. Esto le da al LLM la oportunidad de producir una respuesta final antes de que se interrumpa su ejecución.

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

Registro de la interrupción de un bucle

Registre siempre cuándo se activa una protección del bucle. Esto crea un registro de la frecuencia y los motivos por los que los agentes se quedan atascados, un dato muy valioso para mejorar las instrucciones y las implementaciones de las herramientas.

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

Combinación de las tres protecciones

Los agentes de producción deben combinar las tres protecciones: máximo de iteraciones (obligatorio), detección de repeticiones (detecta ciclos) y tiempo de espera (detecta llamadas bloqueadas a herramientas). Juntas hacen que el bucle sea prácticamente infalible.

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

Prueba de las protecciones del bucle con pruebas unitarias

Escriba pruebas unitarias específicas para las protecciones del bucle. Cree un agente simulado que llame siempre a la misma herramienta y compruebe que la protección lo detecta dentro del número de pasos esperado.

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

Comprobación de conocimientos: Detección de bucles infinitos

Compruebe sus conocimientos sobre las técnicas para detectar y detener bucles infinitos.

Resumen: Detección y detención de bucles infinitos

Ahora puede proteger sus agentes frente a los bucles infinitos con tres protecciones complementarias:

  • Máximo de iteraciones: límite estricto de pasos, obligatorio en todo bucle de agente
  • Detección de repeticiones: aplique un hash a los pares (tool, args) y detenga el bucle cuando aparezcan más de N veces
  • Tiempo de espera: signal.alarm() en Unix, threading.Timer para varias plataformas y asyncio.wait_for() para código asíncrono
  • Inyección de instrucciones de salida: dé al agente la oportunidad de concluir antes de detenerlo de forma forzosa
  • Registre siempre cuándo se activa una protección; son datos valiosos para mejorar las instrucciones
Gratis para empezar

Aprende AI Agents con un tutor de IA — gratis

Escribe y ejecuta código real en tu navegador, obtén ayuda instantánea de un tutor de IA disponible 24/7 y continúa donde lo dejaste en la web o en la aplicación.

Cursos
60
Lecciones
239

Preguntas frecuentes

¿La lección «Detección y ruptura de bucles infinitos» es gratis?

Sí — el texto completo de «Detección y ruptura de bucles infinitos» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de AI Agents, actualiza a CoddyKit PRO. El curso de AI Agents incluye 4 lecciones en total.

¿Qué aprenderé en «Detección y ruptura de bucles infinitos»?

Límites de iteraciones máximas, detección de acciones repetidas y disyuntores para bucles. Practicas AI Agents con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar AI Agents?

No se requiere experiencia previa. AI Agents en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 3 de 4.

¿Cuánto tiempo toma la lección «Detección y ruptura de bucles infinitos»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de AI Agents?

Sí. Cada lección de AI Agents incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Fallos habituales en los bucles de agentes
  2. Registro de trazas para los pasos del agente
  3. Detección y ruptura de bucles infinitos
  4. Técnicas de depuración paso a paso
← Volver a AI Agents