Los loops de razonamiento en agentes de IA ocurren cuando un agente llama a la misma herramienta repetidamente sin hacer progreso, convencido de que un intento más producirá la respuesta perfecta. El agente desperdicia tokens, tiempo y dinero sin entregar un resultado. Este post muestra cómo detectar y bloquear llamadas repetidas, validado con una demo donde herramientas ambiguas causaron 14 llamadas vs estados SUCCESS claros que se detuvieron en 2.
Esta demo usa Strands Agents. Los patrones (debounce hooks, estados claros de herramientas y límites de llamadas) son independientes del framework y aplican a cualquier agente que soporte hooks de ciclo de vida, incluyendo LangGraph, AutoGen y CrewAI.
Código funcional: github.com/aws-samples/sample-why-agents-fail
Serie: Por Qué Fallan los Agentes de IA
- Desbordamiento de Ventana de Contexto — Patrón de Puntero de Memoria para datos grandes
- Herramientas MCP Que Nunca Responden — Patrón asíncrono para APIs externas lentas
- Loops de Razonamiento en Agentes de IA (este post) — Detectar y bloquear llamadas repetidas a herramientas
El Problema: Agentes Que Piensan Demasiado
Los loops de razonamiento en agentes de IA ocurren cuando un agente llama a la misma herramienta repetidamente sin hacer progreso, desperdiciando tokens y tiempo sin entregar un resultado. Los agentes de IA no solo fallan dando respuestas incorrectas; fallan al nunca terminar. Las investigaciones muestran que los agentes quedan atrapados en loops de razonamiento donde llaman a la misma herramienta repetidamente, convencidos de que "un paso más" producirá la respuesta perfecta.
The Decoder (Jan 2025) encontró que incluso con poder de cómputo ilimitado, pensar demasiado lleva a decisiones pobres. La comprensión incompleta del mundo causa errores compuestos. Cada paso de razonamiento adicional empeora las cosas, no las mejora.
Particula (Jul 2025) (observación comunitaria) documentó un caso extremo: un agente ejecutó 847 pasos de razonamiento a $47 por minuto y nunca entregó una respuesta final. Siguió refinando lógica, cuestionando conclusiones y solicitando más datos en un ciclo sin fin.
CodiesHub (Dec 2025) (observación comunitaria) identifica las causas raíz:
- Objetivos poco claros — el agente no sabe cuándo está completa la tarea
- Retroalimentación ambigua de herramientas — las herramientas no devuelven estados claros de éxito/fallo
- Sin criterios de parada — sin límites duros en iteraciones o tiempo
Por Qué Ocurren los Loops: Retroalimentación Ambigua de Herramientas
La retroalimentación ambigua de herramientas ocurre cuando las herramientas devuelven resultados parciales o sugieren "puede haber más datos disponibles" sin estados terminales claros, causando que los agentes reintenten la misma llamada. Las herramientas que devuelven resultados parciales o sugieren "puede haber más datos disponibles" hacen que los agentes reintenten:
@tool
def search_flights(origin: str, destination: str, max_price: float) -> str:
"""Busca vuelos bajo un precio máximo."""
prices = [random.randint(200, 800) for _ in range(3)]
matching = [p for p in prices if p <= max_price]
# El problema: "Puede haber más resultados disponibles" señala al LLM que reintente
# El agente interpreta esto como "Debo buscar de nuevo para encontrar una mejor oferta"
return (
f"Se encontraron {len(matching)} vuelos bajo ${max_price} "
f"(de {len(prices)} verificados). "
"Nota: Puede haber más resultados disponibles. Los precios cambian frecuentemente."
)
Esa "Nota: Puede haber más resultados disponibles" dispara el loop. El agente lo ve y piensa: "Tal vez si busco de nuevo, encontraré una mejor oferta." Reintenta con los mismos parámetros, obtiene resultados similares, y el ciclo continúa.
Solución 1: Debounce Hook con Strands
Los Strands Hooks interceptan el ciclo de vida del agente en cualquier punto. Un Debounce Hook usa BeforeToolCallEvent para detectar llamadas duplicadas antes de que se ejecuten:
from strands.hooks import HookProvider, BeforeToolCallEvent, BeforeInvocationEvent
class DebounceHook(HookProvider):
def __init__(self, window_size=3):
self.call_history = [] # Rastrea pares (tool_name, input)
self.window_size = window_size # Tamaño de ventana deslizante para detección de duplicados
self.blocked_count = 0
def register_hooks(self, registry):
# BeforeInvocationEvent se dispara una vez al inicio de cada llamada agent.invoke()
registry.add_callback(BeforeInvocationEvent, self.reset)
# BeforeToolCallEvent se dispara antes de cada ejecución de herramienta — aquí interceptamos
registry.add_callback(BeforeToolCallEvent, self.check_duplicate)
def reset(self, event):
# Limpia el historial al inicio de cada invocación para que los límites no se mezclen entre llamadas
self.call_history = []
def check_duplicate(self, event):
# Construye una huella digital del nombre de herramienta + entradas exactas
key = (event.tool_use["name"], str(event.tool_use["input"]))
recent = self.call_history[-self.window_size:]
if recent.count(key) >= 2:
# cancel_tool es una API nativa de Strands que bloquea la ejecución y devuelve este mensaje al LLM
event.cancel_tool = "BLOCKED: Llamada duplicada detectada"
self.blocked_count += 1
return
self.call_history.append(key)
agent = Agent(tools=[search_flights], hooks=[DebounceHook()])
El hook rastrea las últimas 3 llamadas a herramientas. Si la misma herramienta con los mismos parámetros aparece dos veces, el tercer intento se bloquea vía event.cancel_tool, una API nativa de Strands que bloquea la ejecución de herramientas y devuelve un mensaje de error al LLM.
Solución 2: Estados SUCCESS/FAILED Claros
Las herramientas que devuelven estados terminales explícitos ayudan a los agentes a saber cuándo detenerse:
@tool
def book_hotel(hotel: str, guest: str, nights: int) -> str:
"""Reserva una habitación de hotel. Devuelve SUCCESS o FAILED claro.
Returns:
SUCCESS: Reserva confirmada con ID
FAILED: Reserva fallida con razón
"""
if random.random() > 0.15:
conf = f"HT{random.randint(10000, 99999)}"
price = random.randint(150, 350)
return f"SUCCESS: Reserva {conf} confirmada — {guest} en {hotel}, {nights} noches, ${price * nights} total"
return f"FAILED: {hotel} completamente reservado"
Cuando el agente recibe "SUCCESS: Reserva HT79265 confirmada", sabe que la tarea está hecha. Sin ambigüedad, sin llamadas extra.
Solución 3: Límites Duros con LimitToolCounts
CodiesHub recomienda: "Iteraciones, tokens, tiempo, gasto son no negociables." Strands proporciona LimitToolCounts en el Hooks Cookbook, un hook que limita llamadas a herramientas por invocación:
from strands.hooks import HookProvider, BeforeToolCallEvent, BeforeInvocationEvent
from threading import Lock
class LimitToolCounts(HookProvider):
"""Limita llamadas a herramientas por invocación. Del Strands Hooks Cookbook."""
def __init__(self, max_tool_counts: dict[str, int]):
# Presupuestos de llamadas por herramienta: {"search_flights": 2} significa máximo 2 búsquedas por invocación
self.max_tool_counts = max_tool_counts
self.tool_counts = {}
self._lock = Lock() # Thread-safe para llamadas concurrentes a herramientas en escenarios Swarm
def register_hooks(self, registry):
registry.add_callback(BeforeInvocationEvent, self.reset_counts)
registry.add_callback(BeforeToolCallEvent, self.intercept_tool)
def reset_counts(self, event):
# Reinicia por invocación para que los límites apliquen por tarea, no por vida del agente
with self._lock:
self.tool_counts = {}
def intercept_tool(self, event):
tool_name = event.tool_use["name"]
with self._lock:
max_count = self.max_tool_counts.get(tool_name)
count = self.tool_counts.get(tool_name, 0) + 1
self.tool_counts[tool_name] = count
if max_count and count > max_count:
# Techo duro: bloquea la llamada y dice al LLM explícitamente que se detenga
event.cancel_tool = f"Límite de herramienta '{tool_name}' alcanzado. NO LLAMAR MÁS."
# Aplica un límite duro de 2 búsquedas de vuelos por tarea de reserva — previene costos desbocados
limit_hook = LimitToolCounts(max_tool_counts={"search_flights": 2})
agent = Agent(tools=[search_flights], hooks=[limit_hook])
Incluso si el agente quiere buscar 10 veces, está limitado a 2. Techo duro, costos predecibles.
Resultados de la Demo
Probamos con un agente de reserva de viajes que busca vuelos y hoteles:
| Escenario | Llamadas a Herramientas | Tiempo | Resultado |
|---|---|---|---|
| Retroalimentación Ambigua | 14 | 21s | El agente reintentó orgánicamente — "los precios pueden cambiar" causó loops |
| DebounceHook | 12 | 15s | Redujo reintentos pero alguna variación en parámetros |
| Estados SUCCESS Claros | 2 | 4s | El agente se detuvo inmediatamente después de SUCCESS |
| LimitToolCounts | 6 (2 bloqueadas) | 6s | Techo duro aplicado — sin desborde |
El contraste es dramático: 14 llamadas con herramientas ambiguas vs 2 llamadas con estados SUCCESS claros. Eso es una diferencia de 7x causada puramente por el diseño de retroalimentación de herramientas.
Cuándo Usar Cada Solución
DebounceHook — previene llamadas duplicadas con parámetros idénticos. Úsalo cuando las herramientas son idempotentes y reintentar con la misma entrada es desperdicio.
Estados SUCCESS/FAILED claros — la solución más simple. Diseña herramientas para devolver estados terminales explícitos. El agente sabe cuándo detenerse.
LimitToolCounts — techo duro en llamadas a herramientas por invocación. Úsalo en producción para prevenir costos desbocados independientemente del diseño de herramientas. Del Strands Hooks Cookbook.
Los tres juntos — defensa en profundidad. Estados claros previenen la mayoría de loops, debounce atrapa duplicados, y límites duros garantizan ejecución acotada.
Pruébalo Tú Mismo
Necesitas Python 3.9+, uv, y una clave API de OpenAI.
git clone https://github.com/aws-samples/sample-why-agents-fail
cd sample-why-agents-fail/stop-ai-agents-wasting-tokens/03-reasoning-loops-demo
uv venv && uv pip install -r requirements.txt
export OPENAI_API_KEY="tu-clave-aquí"
uv run python test_reasoning_loops.py # Ejecuta los 4 escenarios
O abre test_reasoning_loops.ipynb en Jupyter, JupyterLab, VS Code, o tu entorno de notebook preferido.
Conclusiones Clave
- La retroalimentación ambigua de herramientas causa loops orgánicos — "puede haber más resultados disponibles" hace que los agentes reintenten
- 14 llamadas vs 2 llamadas — estados SUCCESS claros reducen llamadas en 7x en nuestra demo
-
Los hooks interceptan antes de la ejecución —
BeforeToolCallEvent.cancel_toolbloquea la llamada antes de que la herramienta se ejecute. ElDebounceHookson ~30 líneas de código - Los límites duros son obligatorios — cada agente necesita topes en iteraciones, tiempo y gasto
- Se documentaron 847 pasos a $47/min (Particula, observación comunitaria) — agentes sin límites queman dinero sin entregar respuestas
Preguntas Frecuentes
¿Por qué los agentes de IA repiten la misma llamada a herramienta?
Los agentes repiten llamadas a herramientas cuando las respuestas de herramientas contienen retroalimentación ambigua como "puede haber más resultados disponibles" o "los precios cambian frecuentemente." El LLM interpreta estas señales como una razón para reintentar, esperando resultados diferentes o mejores. Sin estados terminales claros (SUCCESS/FAILED), el agente no tiene forma de saber que la tarea está completa.
¿Qué es un DebounceHook y cómo previene loops de razonamiento?
Un DebounceHook rastrea llamadas recientes a herramientas en una ventana deslizante. Cuando la misma herramienta se llama con parámetros idénticos más que un umbral establecido (típicamente 2 veces dentro de una ventana de 3), el hook bloquea la llamada usando event.cancel_tool antes de que la herramienta se ejecute. El LLM recibe un mensaje "BLOCKED: Llamada duplicada" y debe intentar un enfoque diferente. En Strands Agents, esto son aproximadamente 30 líneas de código usando la API de HookProvider.
¿Cómo reducen los estados SUCCESS/FAILED claros las llamadas a herramientas?
Cuando una herramienta devuelve "SUCCESS: Reserva HT79265 confirmada," el LLM reconoce que la tarea está completa y deja de llamar a esa herramienta. Respuestas ambiguas como "Se encontraron 2 vuelos, puede haber más disponibles" carecen de esta señal, causando que el agente reintente. En nuestra demo, estados claros redujeron las llamadas a herramientas de 14 a 2, una mejora de 7x.
Referencias
Investigación
- Language models can overthink — The Decoder, Jan 2025
- How many reasoning steps do AI agents need — Particula (observación comunitaria), Jul 2025
- How to Prevent Infinite Loops and Spiraling Costs — CodiesHub (observación comunitaria), Dec 2025
Implementación
- Strands Hooks — Lifecycle event interception and tool cancellation
Gracias!


Top comments (0)