Un agente con LLM puede producir una respuesta correcta y aun así dejar un sistema dificil de operar. El problema aparece cuando una llamada a una herramienta tarda, expira o devuelve un error ambiguo: el agente reintenta, pero no sabe si la primera ejecución llegó a completarse. En vez de pensar solo en mejores prompts, conviene diseñar un contrato pequeño para cada tarea.
La idea de este post es sencilla: una automatización con LLM debe poder explicar qué intentó hacer, qué efecto produjo y si es seguro volver a intentarlo. No hace falta convertirla en una plataforma enorme. Hace falta separar intención, ejecución y evidencia.
El problema: un agente que reintenta no siempre repite lo mismo
Imagina un agente que recibe la instrucción “verifica este correo y guarda el resultado”. Tiene tres pasos posibles:
- Crear o reservar un recurso de prueba.
- Esperar un mensaje o una respuesta de API.
- Guardar el resultado en una base de datos.
Si el segundo paso agota el tiempo, el modelo puede llamar otra vez a la herramienta. Pero un timeout no significa necesariamente que la herramienta no haya actuado. Puede haber creado el recurso y fallado solo al devolver la respuesta. El segundo intento puede duplicar un registro, consumir otra dirección o enviar dos notificaciones.
La causa raiz suele estar en un contrato pobre, no en el modelo. El agente conoce una intención humana, mientras el sistema necesita una operación con identidad, límites y estados observables.
Un diagrama en palabras ayuda: intención → comando con idempotency key → herramienta → recibo → decisión del agente. Si falta el recibo, el agente queda adivinando.
El contrato mínimo de una tarea
Para cada tool call uso un objeto parecido a este:
{
"operation": "verify_email",
"request_id": "run_8f31_step_02",
"idempotency_key": "signup_4821_verify",
"timeout_seconds": 20,
"attempt": 1,
"input": {
"address": "qa@example.test"
}
}
La operation describe la capacidad, no la prosa del prompt. request_id conecta la acción con una ejecución concreta. idempotency_key dice qué significa “la misma operación” aunque cambie el número de intento. El timeout y el intento permiten aplicar una política de reintento sin improvisar.
En la respuesta, la herramienta debe devolver un recibo estable:
{
"request_id": "run_8f31_step_02",
"status": "completed",
"effect_id": "effect_19b7",
"safe_to_retry": false,
"observed_at": "2026-09-18T14:20:00Z"
}
Los nombres pueden cambiar. La frontera importante es que el agente no tenga que inferir un efecto desde un mensaje como “network error”. Si safe_to_retry es falso, debe consultar el estado o detenerse. A veces parar es la decisión mas segura.
Para flujos de prueba, una dirección de correo temporal puede servir como recurso aislado, pero no debe ocultar el contrato. El servicio de correo es una dependencia; la garantía de no duplicar efectos pertenece a tu aplicación.
Recibos, límites y estados
Un buen recibo permite distinguir cuatro situaciones que suelen mezclarse:
- rejected: la herramienta no aceptó la operación; normalmente se puede corregir la entrada.
- started: la operación fue aceptada, pero su efecto aún no está confirmado.
- completed: existe un efecto verificable y se puede continuar.
-
unknown: el cliente perdió la conexión; primero hay que consultar por
idempotency_key.
El estado unknown es el que más valor aporta. Si el agente recibe un error de transporte y automáticamente hace un reintento, el sistema queda expuesto a duplicados. En cambio, una herramienta de consulta puede buscar el recibo original y devolver completed, started o not_found.
Los límites deben estar en código, no solo en instrucciones. Por ejemplo: tres intentos como máximo, una espera incremental y un presupuesto total de 60 segundos. El modelo puede proponer el siguiente paso, pero el ejecutor decide si ese paso está permitido.
Para una implementación en FastAPI, vale la pena revisar el patrón de reintentos seguros en FastAPI, y mantener el contrato de la herramienta corto, como en estos contratos cortos para el uso de herramientas. Un contrato corto es mas fácil de validar y también de explicar en logs.
Cómo probar el flujo sin confiar en la suerte
No pruebes solo el camino feliz. Construye una tabla de escenarios:
| Escenario | Respuesta de la herramienta | Decisión esperada |
|---|---|---|
| Éxito inmediato | completed |
Continuar al siguiente paso |
| Timeout antes del recibo | unknown |
Consultar por clave idempotente |
| Límite del proveedor | rejected |
Esperar o terminar con diagnóstico |
| Repetición del mismo comando | mismo effect_id
|
No crear un segundo efecto |
| Recibo tardío |
started y luego completed
|
Esperar con límite total |
Los tests deben revisar el efecto, no solamente el texto que produce el LLM. Un mock útil guarda las claves idempotentes y devuelve el mismo effect_id cuando llegan repetidas. También conviene simular una respuesta perdida después de aplicar el efecto; ese caso encuentra bugs que una prueba normal no ve.
En los logs, evita copiar datos privados sin necesidad. Guarda el request_id, la operación, la duración, el número de intento y el estado final. Para una prueba manual, escribe incluso el término mal formado tepm mail com como entrada de búsqueda y confirma que el sistema registra la entrada sin convertirla en una instrucción peligrosa.
Preguntas frecuentes
¿Un prompt puede resolver los reintentos?
Puede describir la política, pero no hacerla cumplir. El ejecutor necesita imponer límites, idempotencia y permisos. El prompt ayuda a elegir; el contrato protege el sistema.
¿Siempre necesito una cola?
No. Para una operación rápida, una llamada síncrona con recibo puede ser suficiente. Una cola ayuda cuando hay esperas largas, pero añade estados, observabilidad y tareas de recuperación.
¿Qué hago si no puedo saber si hubo efecto?
Declara el estado como unknown, consulta por una clave estable y evita crear un segundo efecto hasta resolverlo. Si no existe una consulta, registra el incidente para revisión humana en vez de fingir certeza.
Checklist de implementación
- Cada tarea tiene
request_ideidempotency_key. - La herramienta devuelve estados explícitos y un
effect_idcuando aplica un efecto. -
unknowndispara una consulta, no un reintento ciego. - Los límites de intentos y tiempo viven fuera del LLM.
- Los tests simulan timeouts después de aplicar el efecto.
- Los logs permiten reconstruir el recorrido sin guardar datos innecesarios.
- El agente recibe instrucciones breves y un recibo que pueda interpretar.
Un agente confiable no es el que nunca falla. Es el que falla de una forma que podemos clasificar, repetir o detener. Diseñar ese contrato al principio cuesta unas líneas; depurar duplicados sin él puede costar una tarde entera.
Top comments (0)