Su agente ReAct está desperdiciando el 90% de sus reintentos: aquí le mostramos cómo detenerlo

Para quién es: ingenieros de aprendizaje automático y creadores de inteligencia artificial que ejecutan agentes LLM en producción, especialmente sistemas estilo ReAct que utilizan LangChain, LangGraph, AutoGen o bucles de herramientas personalizados. Si es nuevo en ReAct, es un patrón de indicaciones en el que un LLM alterna entre pasos de Pensamiento, Acción y Observación para resolver tareas utilizando herramientas.

están quemando la mayor parte de su presupuesto de reintentos en errores que nunca podrán tener éxito.

En una prueba comparativa de 200 tareas, el 90,8 % de los reintentos se desperdiciaron, no porque el modelo fuera incorrecto, sino porque el sistema seguía reintentando herramientas que no existían. No es “improbable que tenga éxito”. Garantizado para fallar.

No encontré esto ajustando las indicaciones. Lo encontré instrumentando cada reintento, clasificando cada error y rastreando exactamente a dónde se fue el presupuesto. La causa principal resultó ser una única suposición arquitectónica: dejar que el modelo elija el nombre de la herramienta en tiempo de ejecución.

Esto es lo que hace que esto sea particularmente peligroso. Es casi seguro que su panel de seguimiento no lo muestra. En este momento probablemente muestra:

Tasa de éxito: buena Latencia: aceptable Reintentos: dentro de los límites

Lo que no muestra: cuántos de esos reintentos fueron imposibles desde el primer intento. De esa brecha trata este artículo.

Nota de simulación: todos los resultados provienen de una simulación determinista que utiliza parámetros calibrados, no llamadas API en vivo. La tasa de alucinaciones (28%) es una estimación conservadora para las alucinaciones de llamadas de herramientas en agentes estilo ReAct derivada del análisis del modo de falla en los puntos de referencia de clase GPT-4 publicados (Yao et al., 2023; Shinn et al., 2023); no es una cifra reportada directamente de esos artículos. Las conclusiones estructurales se consideran propiedades arquitectónicas; Los porcentajes exactos variarán en la producción. Las limitaciones completas se analizan al final. Reproduzca cada número usted mismo: python app.py –seed 42.

Repositorio de GitHub: https://github.com/Emmimal/react-retry-waste-analysis

En producción, esto significa que se paga por los reintentos que no pueden tener éxito y se priva a los que sí pueden.

Izquierda: el enrutamiento de herramientas basado en cadenas pasa la salida del modelo directamente a TOOLS.get(): un nombre alucinado devuelve Ninguno, quema el presupuesto de reintentos a través de un contador global sin taxonomía de errores y falla silenciosamente. Derecha: el enrutamiento determinista resuelve los nombres de las herramientas de un dictado de Python en el momento del plan, clasifica los errores antes de volver a intentarlo y hace que las alucinaciones en la capa de enrutamiento sean estructuralmente imposibles. Imagen del autor.

TL;DR

El 90,8% de los reintentos se desperdiciaron en errores que nunca pudieron tener éxito. Causa raíz: dejar que el modelo elija los nombres de las herramientas en tiempo de ejecución (TOOLS.get(tool_name)). Las indicaciones no solucionan el problema: un nombre de herramienta alucinado es un error permanente. Ningún reintento puede hacer que una clave faltante aparezca en un diccionario.

Tres soluciones estructurales eliminan el problema: clasificar los errores antes de volver a intentarlo, utilizar disyuntores por herramienta y trasladar el enrutamiento de herramientas al código. Resultado: 0 % de reintentos desperdiciados, variación de pasos 3 veces menor, ejecución predecible.

La ley en la que se basa este artículo

Ante los datos, el principio, expresado una vez, sin rodeos:

Reintentar sólo tiene sentido para errores que pueden cambiar. El nombre de una herramienta alucinada no puede cambiar. Por lo tanto, volver a intentarlo es un desperdicio garantizado.

Este no es un argumento de probabilidad. No se trata de que "las alucinaciones sean lo suficientemente raras como para ignorarlas". Es una propiedad lógica: TOOLS.get("web_browser") devuelve Ninguno en el primer intento, en el segundo y en todos los intentos posteriores. La herramienta no existe. El contador de reintentos no lo sabe. De todos modos, quema un espacio presupuestario.

Todo el problema surge de este desajuste. La solución también.

La línea única que agota silenciosamente su presupuesto de reintentos

Aparece en casi todos los tutoriales de ReAct. Probablemente lo hayas escrito:

tool_fn = TOOLS.get(tool_name) # ◄─ LA LÍNEA si tool_fn es Ninguno: # No hay taxonomía de errores aquí. # TOOL_NOT_FOUND parece idéntico a una falla transitoria de la red. # El contador de reintentos global consume presupuesto en una herramienta # que nunca existirá y lo registra como un "fallo".

Esta es la línea. Todo lo demás en este artículo se deriva de ello.

Cuando un LLM alucina el nombre de una herramienta (web_browser, sql_query, python_repl) TOOLS.get() devuelve Ninguno. El agente sabe que la herramienta no existe. El contador de reintentos global no. Trata a TOOL_NOT_FOUND de manera idéntica a TRANSIENT: mismo espacio de presupuesto, misma lógica de reintento, mismo retroceso.

La cascada: cada alucinación consume espacios de reintento que podrían haber solucionado un fracaso real. Cuando llega un tiempo de espera de red genuino dos pasos después, no queda nada. La tarea falla: se registra como agotamiento de reintentos genéricos, sin rastro de un nombre de herramienta alucinado como la causa principal.

Si sus registros contienen reintentos en TOOL_NOT_FOUND, ya tiene este problema. La única pregunta es qué fracción de su presupuesto está consumiendo. En este punto de referencia, la respuesta fue del 90,8%.

La configuración de referencia

Dos agentes, 200 tareas, los mismos parámetros simulados, las mismas herramientas, las mismas tasas de fracaso, con una diferencia estructural.

Nota comparativa: este punto de referencia compara una línea base ingenua de ReAct con un flujo de trabajo con las tres correcciones aplicadas. Las correcciones 1 (taxonomía de errores) y 2 (disyuntores por herramienta) se aplican de forma independiente a un agente ReAct sin cambiar su arquitectura. La solución 3 (enrutamiento determinista de la herramienta) es el diferenciador estructural: es lo que hace que la alucinación en la capa de enrutamiento sea imposible. La brecha mostrada es acumulativa; Tenga esto en cuenta al leer los números.

Agente ReAct: Pensamiento estándar → Acción → Bucle de observación. Contador de reintentos global único (MAX_REACT_RETRIES = 6, MAX_REACT_STEPS = 10). Sin taxonomía de errores. El nombre de la herramienta proviene de la salida de LLM en tiempo de ejecución. Cada nombre de herramienta alucinado quema exactamente 3 espacios de reintento (HALLUCINATION_RETRY_BURN = 3); esta constante genera directamente la cifra de desperdicio del 90,8 % y se analiza con más detalle en Limitaciones.

Flujo de trabajo controlado: ejecución determinista del plan donde el enrutamiento de herramientas es una búsqueda de dictado de Python resuelta en el momento del plan. Taxonomía de errores aplicada en el punto de falla. Disyuntores por herramienta (se dispara después de 3 fallos consecutivos, sonda de recuperación después de 5 segundos simulados, se cierra después de 2 éxitos de sonda). Reintentar la lógica con ámbito de clase de error.

Parámetros de simulación:

ParámetroValorNotasSemilla42Semilla aleatoria globalTareas200Por experimento Tasa de alucinaciones28%Estimación conservadora de los puntos de referencia publicados Tasa de detección de bucles18%Aplicado a pasos con una duración del historial > 2HALLUCINATION_RETRY_BURN3Ranuras de reintento quemadas por alucinaciónMAX_REACT_RETRIES6Presupuesto de reintento globalMAX_REACT_STEPS10Límite de paso por tareaCosto del token proxy$3/1 millón de tokensEstimación de rango medio para modelos de clase GPT-4Tasas de sensibilidad5%, 15%, 28%Tasas de alucinaciones para barrido

Esta constante es el factor mecánico directo de la cifra de desperdicio del 90,8%. Con un valor de 1, se queman menos espacios por evento; el recuento desperdiciado del flujo de trabajo permanece en 0 independientemente. Ejecute la verificación de sensibilidad usted mismo: modifique esta constante y observe que el flujo de trabajo siempre desperdicia cero reintentos.

La simulación utiliza tres herramientas (buscar, calcular y resumir) con tasas de falla realistas por herramienta. El costo de la herramienta se rastrea en 200 tokens por paso de LLM.

Todos los números de este artículo se reproducen exactamente en python app.py –seed 42.

Lo que encontró el punto de referencia

La tasa de éxito oculta el verdadero problema

ReAct tuvo éxito en 179/200 tareas (89,5%). El flujo de trabajo se realizó correctamente en 200/200 (100,0%).

Gráficos de barras que comparan ReAct con el flujo de trabajo determinista que muestran la tasa de éxito y los eventos de alucinaciones en 200 tareas, destacando una mayor confiabilidad y cero alucinaciones en el flujo de trabajo.
La comparación entre ReAct y el flujo de trabajo determinista muestra tasas de éxito similares, pero una diferencia crítica en los eventos de alucinaciones, donde ReAct registra 155 alucinaciones mientras que el flujo de trabajo las elimina por completo, lo que expone una brecha de confiabilidad oculta en el diseño del agente. Imagen por autor

La brecha del 10,5% es real. Pero la tasa de éxito es una métrica de aprobado/reprobado: no dice nada sobre qué tan cerca del borde llegó una carrera de pase, o cuánto se quemó para llegar allí. El número más informativo es lo que sucedió dentro de esas 179 ejecuciones "exitosas" de ReAct. Específicamente: ¿a dónde se fue el presupuesto de reintento?

El presupuesto de reintento

Gráfico de barras apiladas que muestra el uso del presupuesto de reintentos en ReAct frente al flujo de trabajo, destacando el 90,8 % de los reintentos desperdiciados en ReAct en comparación con cero reintentos desperdiciados en el flujo de trabajo determinista.
Los agentes de ReAct desperdician la mayor parte del presupuesto de reintentos en errores que no se pueden reintentar, mientras que el flujo de trabajo garantiza que cada reintento apunte a fallas recuperables, lo que revela una gran ineficiencia en la lógica de reintento del agente estándar. Imagen por autor
MetricReActWorkflowTotal de reintentos51380Útil (errores reintentables)4780Desperdiciado (errores no reintentables)4660Tasa de desperdicio90,8%0,0%Promedio de reintentos/tarea2.560.40

466 de 513 reintentos (90,8%) apuntaron a errores que no pueden tener éxito por definición. El flujo de trabajo disparó 80 reintentos. Todos y cada uno fueron útiles. La brecha es de 6,4 veces en el total de reintentos y de 466 a 0 en los desperdiciados. Esa no es una diferencia de rendimiento. Es estructural.

Una nota sobre la mecánica: HALLUCINATION_RETRY_BURN = 3 significa que cada nombre de herramienta alucinado quema exactamente 3 espacios de reintento en la simulación de ReAct. La cifra del 90,8% es sensible a esta constante: con un valor de 1, se desperdician menos reintentos por evento de alucinación. Pero la propiedad estructural se mantiene en todos los valores: el flujo de trabajo no desperdicia ningún reintento, porque los errores que no se pueden reintentar se clasifican y se omiten antes de que se consuma cualquier espacio. Ejecute la verificación de sensibilidad usted mismo: modifique HALLUCINATION_RETRY_BURN y observe que el recuento de desperdicio del flujo de trabajo permanece en 0.

Por qué 19 de 21 fallas de ReAct tuvieron causas fundamentales idénticas

Motivo del fallo% de ejecuciones de falloshallucined_tool_exhausted_retries1990.5%tool_error_exhausted_retries:rate_limited14.8%tool_error_exhausted_retries:dependency_down14.8%

19 de 21 fallas: nombre de herramienta alucinado, presupuesto de reintento global agotado, tarea muerta. No fallas de red. No límites de tarifas. Las cuerdas alucinadas volvieron a intentarlo hasta que no quedó nada. El flujo de trabajo no tuvo fallas en 200 tareas.

Su panel de tasa de éxito nunca mostrará esto. El motivo del error está enterrado dentro del ciclo de reintento sin ninguna taxonomía para extraerlo. Esa es la ceguera del tablero que promete el título, y es peor de lo que parece, porque significa que no tienes señal cuando las cosas se están degradando, solo cuando ya han fallado.

La taxonomía del error: de “desconocido” a completamente clasificado

La solución fundamental es clasificar los errores en el momento en que se generan. Se pueden reintentar tres categorías; tres no lo son:

# Reintentable: puede tener éxito en un intento posterior RETRYABLE = {TRANSIENT, RATE_LIMITED, DEPENDENCY_DOWN} # No reintentable: reintentar el presupuesto de desperdicios por definición NON_RETRYABLE = {INVALID_INPUT, TOOL_NOT_FOUND, BUDGET_EXCEEDED}

Cuando cada error conlleva una clase, la decisión de reintento se convierte en una línea:

si no, exc.is_retryable(): log(RETRY_SKIPPED) # pausa consumida de presupuesto cero

La taxonomía completa de la ejecución de 200 tareas:

Gráfico de barras horizontales que muestra la distribución de la taxonomía de errores para ReAct y los agentes de flujo de trabajo, destacando el predominio de los errores de alucinación en ReAct y el manejo de disyuntores en el flujo de trabajo.
La taxonomía de errores expone el modo de falla raíz en los agentes ReAct, dominado por errores de alucinación, mientras que el flujo de trabajo los reemplaza con eventos de disyuntor controlados para un mejor manejo de fallas. Imagen por autor
Tipo de errorReActWorkflowhallucination1550rate_limited2422dependency_down1623loop_detected80transient726circuit_open049invalid_input10

El evento dominante de ReAct es la alucinación: 155 eventos, todos ellos no reintentables y todos con un presupuesto agotador. El evento dominante del flujo de trabajo es circuito_open: 49 fallos rápidos que nunca afectaron a un servicio ascendente. El flujo de trabajo no registró eventos de alucinaciones porque nunca le pide al modelo que produzca una cadena de nombre de herramienta.

No puedes alucinar una clave en un dictado que nunca le pides al modelo que produzca.

Esta es una garantía arquitectónica dentro del diseño de simulación. En un sistema real donde el LLM contribuye a la generación del plan, aún podrían ocurrir alucinaciones antes del enrutamiento de la herramienta. La garantía se mantiene precisamente cuando el enrutamiento es completamente determinista y la salida del modelo se limita a la estructura del plan, no a las cadenas de nombres de herramientas.

Los ocho eventos loop_detected en ReAct provienen de una tasa de bucle del 18% aplicada cuando len(history) > 2: el modelo "decide pensar más" en lugar de actuar, consumiendo un paso sin llamar a una herramienta. El flujo de trabajo no tiene equivalente porque no otorga al modelo autoridad de selección de pasos.

Previsibilidad del paso: la inestabilidad oculta σ revela

Histograma que compara la distribución de pasos de ReAct y los agentes de flujo de trabajo, mostrando una mayor variación y pasos de ejecución impredecibles en ReAct versus pasos estrechamente agrupados en el flujo de trabajo.
La distribución de pasos revela una inestabilidad oculta en los agentes ReAct, donde una gran variación conduce a una ejecución impredecible, mientras que el flujo de trabajo mantiene un recuento de pasos consistente y controlado. Imagen por autor
MetricReActWorkflowPromedio de pasos/tarea2.882.69Desv. estándar (σ)1.360.46

Los medios son casi idénticos. Las distribuciones no lo son. La desviación estándar es 3 veces mayor para ReAct.

El flujo de trabajo σ se mantiene en 0,46 en todas las tasas de alucinaciones analizadas, no por coincidencia, sino porque la estructura del plan es fija. El tipo de tarea (matemáticas, resumen, búsqueda) determina el recuento de pasos en el momento del plan. La tirada de alucinación no afecta el recuento de pasos cuando el enrutamiento de la herramienta nunca pasa por la salida del modelo.

En producción, una σ alta significa: latencia impredecible (no se pueden comprometer los SLA), costo simbólico impredecible (las previsiones presupuestarias son inexactas) y carga ráfaga invisible (un grupo incorrecto de tareas de larga duración llega sin previo aviso). La previsibilidad es una propiedad de producción. La tasa de éxito no lo mide. σ lo hace.

Las tres soluciones estructurales

Solución 1: clasifique los errores antes de decidir si volver a intentarlo

La solución fundamental es clasificar los errores en el momento en que se generan. Se pueden reintentar tres categorías; tres no lo son:

def call_tool_with_retry(tool_name, args, logger, ledger, step, max_retries=2, fallback=None): para intento en el rango(max_retries + 1): intente: devolver call_tool_with_circuit_breaker(tool_name, args, …) excepto AgentError como exc: si no exc.is_retryable(): # No reintentable: RETRY_SKIPPED — presupuesto cero consumido logger.log(RETRY_SKIPPED, error_kind=exc.kind.value) break # ← esta línea reduce el desperdicio a 0 si el intento < max_retries: ledger.add_retry(wasted=False) backoff = min(0.1 * (2 ** intento) + jitter, 2.0) logger.log(RETRY, intento=intento, backoff=backoff) si respaldo: devuelve ToolResult(tool_name, respaldo, 0.0, is_fallback=True) generar last_error

RETRY_SKIPPED es el evento de auditoría que demuestra que la taxonomía está funcionando. Busque en sus registros de producción para ver exactamente qué errores no reintentables se detectaron en qué paso, en qué tarea, sin consumir presupuesto. ReAct no puede emitir este evento; no tiene una taxonomía que saltarse.

Esta solución se aplica a un agente ReAct hoy sin cambiar su arquitectura de enrutamiento de herramientas. Si ejecuta LangChain o AutoGen, puede agregar clasificación de errores a su capa de herramientas y limitar su decorador de reintento a TransientToolError sin tocar nada más. No eliminará por completo el desperdicio provocado por las alucinaciones (para eso se requiere la solución 3), pero evita que INVALID_INPUT y otros errores permanentes quemen los reintentos en intentos que tampoco pueden tener éxito.

Solución 2: disyuntores por herramienta en lugar de un contador global

Un contador de reintentos global trata todas las herramientas como un único dominio de error. Cuando una herramienta se degrada, agota el presupuesto para todas las demás herramientas. Los disyuntores por herramienta contienen fallas localmente:

# Cada herramienta obtiene su propia instancia de disyuntor # CERRADO → las llamadas pasan normalmente # ABIERTO → las llamadas fallan inmediatamente, no hay acceso ascendente, no se consume presupuesto # MEDIO ABIERTO → una llamada de sonda; si tiene éxito, el circuito se cierra clase CircuitBreaker: Failure_threshold: int = 3 # se dispara después de 3 fallas consecutivas recovery_timeout: float = 5.0 # segundos simulados antes de que se permita la sonda Success_threshold: int = 2 # éxitos de la sonda necesarios para cerrar

El punto de referencia registró 49 eventos CIRCUIT_OPEN para el flujo de trabajo; cada uno de ellos fue una llamada que falló rápidamente sin tocar un servicio ascendente degradado y sin consumir el presupuesto de reintentos. ReAct registró cero porque no tiene estado por herramienta. Utiliza una herramienta degradada hasta que se agota el presupuesto global.

Al igual que la solución 1, esto se aplica de forma independiente a un agente ReAct. Los disyuntores por herramienta envuelven la capa de llamada de herramienta independientemente de cómo se seleccionó la herramienta. Los valores de umbral deberán ajustarse a su carga de trabajo.

Solución 3: enrutamiento de herramientas determinista (el diferenciador estructural)

Esta es la solución que elimina el problema de las alucinaciones en la capa de enrutamiento. Las soluciones 1 y 2 reducen el daño de las alucinaciones; Fix 3 los hace estructuralmente imposibles donde se aplica.

# ReAct: el nombre de la herramienta proviene de la salida de LLM, puede ser cualquier cadena nombre_herramienta = llm_response.tool_name # "web_browser", "sql_query", … tool_fn = TOOLS.get(nombre_herramienta) # Ninguno si tiene alucinaciones → quema de presupuesto # Flujo de trabajo: nombre de la herramienta resuelto desde el plan al inicio de la tarea, siempre válido STEP_TO_TOOL = { StepKind.SEARCH: "search", StepKind.CALCULATE: "calcular", StepKind.SUMMARISE: "resumir", } nombre_herramienta = STEP_TO_TOOL[step.kind] # KeyError es imposible; la alucinación es imposible

Utilice el LLM para razonar: qué pasos se necesitan, en qué orden y con qué argumentos. Utilice Python para el enrutamiento de herramientas. El modelo aporta una estructura de plan (tipos de pasos), no cadenas de nombres de herramientas.

Vale la pena nombrar honestamente la compensación: el enrutamiento determinista requiere que la estructura de su tarea se asigne a un conjunto finito de tipos de pasos. Para los agentes abiertos que necesitan componer dinámicamente secuencias de herramientas novedosas en un registro grande, esto limita la flexibilidad. Para los sistemas con estructuras de tareas predecibles (la mayoría de las implementaciones de producción), las ganancias en confiabilidad y previsibilidad son sustanciales.

Resumen antes/después:

DimensiónAntes (ReAct ingenuo) Después (las tres correcciones) Compensación Reintentos desperdiciados 90,8% 0,0% Ninguno Eventos de alucinación 1550 Pierde el descubrimiento dinámico de herramientas Paso σ 1.360.46 Pierde composición abierta Aislamiento del circuito Ninguno (global) Por herramienta Agrega trabajo de ajuste de umbral Auditabilidad Ninguno Taxonomía completa Agrega gastos generales de registro

El análisis de sensibilidad: el resultado del 5% es el alarmante

Gráfico de tres paneles que muestra el análisis de sensibilidad entre diferentes tasas de alucinaciones para la tasa de éxito, la tasa de reintentos inútiles y la desviación estándar del paso.
Análisis de sensibilidad entre las tasas de alucinaciones (5%, 15%, 28%). El flujo de trabajo mantiene un 0 % de reintentos desperdiciados y un σ = 0,46 estable en cada velocidad, mientras que los reintentos desperdiciados de ReAct aumentan drásticamente con las alucinaciones. Imagen del autor.
Tasa de alucinacionesReAct desperdiciado %Flujo de trabajo desperdiciado %ReAct σWorkflow σReAct éxito5%54.7%0.0%1.280.46100.0%15%81.4%0.0%1.420.4698.0%28%90.8%0.0%1.360.4689.5%

La fila del 5% merece especial atención. ReAct muestra un 100% de éxito: su monitoreo informa un agente en buen estado. Pero el 54,7% de los reintentos todavía se desperdician. El presupuesto se está agotando silenciosamente.

Esta es la ceguera del tablero hecha con precisión. Cuando llega un grupo de fallas real (un aumento en el límite de velocidad, un servicio degradado, una breve interrupción), menos de la mitad de la capacidad de reintento diseñada está disponible para manejarlo. No verás venir esto. Tu tasa de éxito fue del 100% hasta el momento en que dejó de serlo.

El flujo de trabajo desperdicia el 0 % de los reintentos en cada velocidad probada. El σ se mantiene en 0,46 independientemente de la frecuencia de las alucinaciones. Estas no son mejoras que dependen de la velocidad: son propiedades de la arquitectura.

Latencia: lo que revela el CDF y que los promedios ocultan

Función de distribución acumulativa de latencia que compara ReAct y agentes de flujo de trabajo, mostrando una latencia P95 similar a pesar de una latencia promedio más alta en el flujo de trabajo.
La distribución de la latencia muestra que, a pesar de una latencia promedio más alta, el flujo de trabajo coincide con ReAct en P95, lo que demuestra que las mejoras en la confiabilidad no se producen a costa del rendimiento de cola. Imagen por autor
MetricReActWorkflowLatencia promedio (ms)43.474.8Latencia P95 (ms)143.3146.2Tokens totales115.000107.400Costo estimado ($)$0,3450$0,3222

El flujo de trabajo parece más lento en promedio porque las ejecuciones fallidas de ReAct salen temprano; parecen rápidas porque fallaron rápido, no porque se completaron de manera eficiente. En P95, la métrica que importa para los compromisos de SLA, la latencia es efectivamente idéntica: 143,3 ms frente a 146,2 ms.

No está intercambiando latencia de cola por confiabilidad. En la cola, la simulación muestra que puedes tener ambos. El costo del token favorece el flujo de trabajo en un 6,6%, porque no quema pasos de LLM en bucles de reintento de alucinaciones que no producen resultados útiles.

Tres preguntas de diagnóstico para su sistema ahora mismo

Antes de leer la guía de implementación, responda estas tres preguntas sobre su agente actual:

1. Cuando el nombre de una herramienta del modelo no coincide con ninguna herramienta registrada, ¿su sistema vuelve a intentarlo? En caso afirmativo, el presupuesto se está agotando debido a errores que no se pueden reintentar en este momento.

2. ¿Su contador de reintentos es global o por herramienta? Un contador global permite que una herramienta degradada agote el presupuesto de todas las demás.

3. ¿Puedes buscar en tus registros RETRY_SKIPPED o un evento equivalente? De lo contrario, su sistema no tiene una taxonomía de errores ni un seguimiento de auditoría para el presupuesto desperdiciado.

Si respondió “sí/global/no” a estas tres, la solución 1 y la solución 2 son el camino más rápido hacia la recuperación y se pueden aplicar sin cambiar la arquitectura de su agente.

Implementando esto en su pila hoy

Estas tres correcciones se pueden aplicar de forma incremental a cualquier marco: LangChain, LangGraph, AutoGen o un bucle de herramientas personalizado.

Paso 1: agregar clasificación de errores (30 minutos). Defina dos clases de excepción en su capa de herramientas: una para errores reintentables (TransientToolError), otra para permanentes (ToolNotFoundError, InvalidInputError). Eleva la clase apropiada en el momento en que se detecta el error.

Paso 2: el alcance vuelve a intentar encontrar la clase de error (15 minutos). Si usa tenacidad, cambie retry_if_exception por retry_if_exception_type(TransientToolError). Si usa un bucle personalizado, agregue if not exc.is_retryable(): break antes del incremento del reintento.

Paso 3: mueva el enrutamiento de herramientas a un dictado (1 hora). Si tiene una estructura de tareas fija, defínala como una enumeración StepKind y resuelva los nombres de las herramientas desde dict[StepKind, str] en el momento del plan. Opcional si su caso de uso requiere una composición de herramientas abierta, pero elimina por completo el desperdicio presupuestario provocado por alucinaciones donde se puede aplicar.

Así es como se ve la vulnerabilidad en LangChain y cómo solucionarla:

Patrón vulnerable:

from langchain.agents import AgentExecutor, create_react_agent # Si el modelo genera "web_search" en lugar de "search", # AgentExecutor volverá a intentar el paso antes de fallar: # consume presupuesto en un error que no puede tener éxito. ejecutor = AgentExecutor( agente=create_react_agent(llm, herramientas, indicador), herramientas=herramientas, max_iterations=10 ) executor.invoke({"entrada": tarea})

Patrón fijo: taxonomía de errores + enrutamiento determinista:

from tenacity import retry, stop_after_attempt, retry_if_exception_type clase ToolNotFoundError(Exception): pasar # clase no reintentable TransientToolError(Exception): pasar # reintentable # Enrutamiento de herramientas en Python: el modelo genera el tipo de paso, no el nombre de la herramienta TOOL_REGISTRY = {"search": search_fn, "calculate": calc_fn} def call_tool(name: str, args: str): fn = TOOL_REGISTRY.get(nombre) si fn es Ninguno: generar ToolNotFoundError(f"'{nombre}' no registrado") # nunca reintentar: devolver fn(args) excepto RateLimitError como e: generar TransientToolError(str(e)) # reintentar con backoff @retry( stop=stop_after_attempt(3), reintentar=reintentar_if_excepción_tipo(TransientToolError) ) def run_step(nombre_herramienta: cadena, argumentos: cadena): devuelve call_tool(nombre_herramienta, argumentos)

Nota de producción: la llamada eval() en tool_calculate del punto de referencia está presente solo con fines de simulación. Nunca use eval() en una herramienta de producción; es una vulnerabilidad de inyección de código. Reemplácelo con un analizador de expresiones seguro como simpleeval o una biblioteca matemática especialmente diseñada.

Limitaciones de referencia

La tasa de alucinaciones es un parámetro, no una medida. La cifra del 28% es una estimación conservadora derivada del análisis del modo de falla en Yao et al. (2023) y Shinn et al. (2023): no es una cifra reportada directamente por ninguno de los artículos. Un modelo bien indicado con un esquema de herramientas limpio y un registro de herramientas pequeño y bien nombrado puede alucinar nombres de herramientas con mucha menos frecuencia. Ejecute el punto de referencia a su tasa observada real.

HALLUCINATION_RETRY_BURN es una constante de simulación que determina el porcentaje de desperdicio. Con un valor de 1, se desperdician menos reintentos por evento de alucinación; la cifra del 90,8% sería inferior. La conclusión estructural (el flujo de trabajo desperdicia un 0% en todos los valores) se mantiene independientemente. Ejecute python app.py –seed 42 con valores modificados de 1 y 2 para verificar.

El recuento cero de alucinaciones del flujo de trabajo es una propiedad del diseño de simulación. El enrutamiento de herramientas nunca pasa por la salida LLM en este punto de referencia. En un sistema real donde el LLM contribuye a la generación del plan, podrían ocurrir alucinaciones antes de la ruta.

Tres herramientas es un entorno simplificado. Los agentes de producción suelen gestionar docenas de herramientas con modos de fallo heterogéneos. La taxonomía y los patrones de disyuntores se escalan bien; Los valores de umbral deberán ajustarse a su carga de trabajo.

Se simulan cifras de latencia. La casi equivalencia del P95 es el hallazgo relevante para la producción. Los valores absolutos de milisegundos no deben informar la planificación de la capacidad. Las comparaciones de latencia promedio se ven confundidas por fallas de salida temprana en ReAct y la contabilidad LLM por paso en el flujo de trabajo; use P95 para cualquier razonamiento de latencia.

Métricas completas

Los resultados completos por métrica para las 200 tareas (seed=42, hallucination_rate=28%) están disponibles en `experiment_results.json` en el repositorio de GitHub. Ejecute `python app.py -seed 42 -export-json` para regenerarlos localmente.

Referencias

Yao, S., Zhao, J., Yu, D., Du, N., Shafran, I., Narasimhan, K. y Cao, Y. (2023). ReAct: sinergizar el razonamiento y la actuación en modelos lingüísticos. ICLR 2023. https://arxiv.org/abs/2210.03629 Shinn, N., Cassano, F., Gopinath, A., Narasimhan, K. y Yao, S. (2023). Reflexión: Agentes del lenguaje con aprendizaje por refuerzo verbal. NeurIPS 2023. https://arxiv.org/abs/2303.11366 Fowler, M. (2014). Disyuntor. martinfowler.com. https://martinfowler.com/bliki/CircuitBreaker.html Nygard, MT (2018). ¡Libéralo! Diseñar e implementar software listo para producción (2ª ed.). Estantería pragmática. Sculley, D., et al. (2015). Deuda técnica oculta en los sistemas de aprendizaje automático. NeurIPS 2015. https://papers.nips.cc/paper/2015/hash/86df7dcfd896fcaf2674f757a2463eba-Abstract.html

Divulgación

Metodología de simulación. Todos los resultados se producen mediante una simulación determinista (python app.py –seed 42), no llamadas API en vivo. La tasa de alucinaciones del 28% es un parámetro calibrado derivado del análisis del modo de falla en los puntos de referencia publicados, no una cifra medida directamente a partir de resultados de modelos en vivo.

Sin conflictos de intereses. El autor no tiene ninguna relación financiera con ninguna herramienta, marco, proveedor de modelos o empresa mencionada en este artículo. Ningún producto está respaldado ni patrocinado.

Obra original. Este artículo, su diseño de referencia y su código son trabajos originales del autor. Las referencias se utilizan únicamente para atribuir los hallazgos publicados que informaron la calibración y el diseño.

GitHub: https://github.com/Emmimal/react-retry-waste-analysis

python app.py –seed 42: resultados completos y las seis cifras. python app.py –replay 7: ejecución detallada de una sola tarea, paso a paso.