Los agentes LLM pueden ayudar a investigar por qué falla una prueba de email, pero hay una diferencia importante entre asistir una ejecución y controlar una ejecución. Si el modelo puede hacer cualquier cosa, el resultado será dificil de reproducir: una corrida pasa, la siguiente usa otro buzón, y el equipo termina discutiendo con una explicación bonita en lugar de revisar evidencia.
La solución práctica es tratar al agente como un componente dentro de un sistema con límites. El modelo interpreta señales y propone el siguiente paso. Las herramientas, en cambio, aplican operaciones pequeñas, validan estados y dejan un recibo. Esta separación hace que la automatización sea menos espectacular, pero bastante más confiable.
El problema: un agente no es un oráculo
En un flujo de signup hay varios estados posibles: formulario enviado, mensaje solicitado, mensaje recibido, enlace consumido y usuario activado. Un agente puede leer un log y decir que todo parece correcto, pero "parece" no es un estado del sistema.
El primer diseño que conviene dibujar es sencillo:
CI -> API de signup -> buzón de prueba -> extractor de enlace
\-> eventos y recibos ----------------------> agente LLM
El agente recibe el contexto de la ejecución, no acceso ilimitado a la base de datos ni a la consola de producción. Su trabajo es responder preguntas acotadas:
- ¿Qué transición no ocurrió?
- ¿Qué evidencia falta?
- ¿Qué comprobación segura se puede repetir?
En mis diseños de automatización, esta frontera evita que el LLM convierta una suposición en una acción. Si el modelo responde antes de tiempo, el test queda medio engañoso, aunque el resumen final suene convincente.
El contrato mínimo de una herramienta
Cada herramienta expuesta al agente debería tener una entrada pequeña y una salida verificable. Por ejemplo, en vez de entregar una función genérica como run_any_sql, se pueden ofrecer operaciones como estas:
{
"name": "get_verification_attempt",
"input": {"run_id": "ci-1842", "user_id": "u-92"},
"output": {
"status": "message_received",
"message_id": "m-771",
"observed_at": "2026-10-03T14:00:12Z"
}
}
El contrato debe declarar también los fallos: not_found, expired, rate_limited o invalid_run. Sin estos valores, el agente tiende a rellenar huecos con lenguaje natural. Eso sirve para una conversación, no para una prueba que debe fallar cuando falta un mensaje.
Una regla útil es que toda acción mutante necesite un run_id, un motivo y una clave de idempotencia. Leer el mensaje puede ser seguro; reenviar un mensaje o marcar un token como consumido no lo es. La herramienta debe decidir primero si la operación es válida y solo despues ejecutarla.
Arquitectura con estados observables
Para pruebas de email, separaría cuatro capas:
- Fixture: crea una dirección aislada y la asocia con la ejecución.
- Aplicación: solicita y procesa el email de verificación.
- Recolector: obtiene mensajes, timestamps y códigos de respuesta.
- Agente: explica el fallo y propone una comprobación permitida.
El buzón no debería ser la única fuente de verdad. También hay que guardar un recibo del intento: requested_at, delivered_at, consumed_at, correlation_id y resultado de cada paso. Así el modelo puede detectar que el email llegó tarde, en vez de afirmar que nunca se envió.
Para validar la parte de entrega, conviene separar el transporte de la interfaz que lee el mensaje. Un equipo puede aplicar reenvíos limpios para emails de signup sin dejar que el agente cambie la configuración del servicio. La herramienta devuelve hechos; el modelo los resume.
También debe existir una política de expiración. Las fixtures viejas contaminan los diagnósticos y hacen que una prueba verde no diga mucho. No hace falta meter una cola enorme para esto; una tarea de limpieza con un límite claro suele ser mas facil de operar.
Un runner pequeño para CI
Un runner inicial puede ser deliberadamente aburrido:
def verify_signup(run_id, email_client, app_client):
address = email_client.create_fixture(run_id)
attempt = app_client.request_verification(address)
message = email_client.wait_for_message(
address,
timeout_seconds=60,
correlation_id=attempt.correlation_id,
)
assert message is not None, "verification email was not delivered"
result = app_client.consume_link(message.verification_url)
assert result.status == "activated"
return {
"run_id": run_id,
"message_id": message.id,
"final_status": result.status,
}
El LLM puede entrar después de este paso, usando el recibo para clasificar el fallo. Por ejemplo, podría distinguir entre delivery_timeout, wrong_template y replay_rejected. No debería decidir que una aserción fallida es aceptable porque el texto del email "se ve bien".
En el panel de diagnóstico, una interfaz de rendimiento realmente utilizable también importa: mostrar el estado, la edad del fixture y el último evento ayuda más que un párrafo largo generado por IA. El resumen puede ir debajo, como contexto adicional.
Puntos de control antes de automatizar
Antes de conectar un agente al pipeline, comprobaría estas condiciones:
- Cada ejecución tiene un buzón y un
run_idúnicos. - Las herramientas devuelven estados enumerados y timestamps.
- Las acciones mutantes son idempotentes o requieren confirmación.
- Los logs no contienen el token completo ni datos innecesarios.
- Un fallo del agente deja la prueba en estado fallido, no en verde.
- La limpieza tiene un límite de tiempo y un responsable.
En documentación interna aparecen nombres ambiguos como dummy e mail. No es un problema por sí mismo, pero sí una señal para definir mejor el vocabulario: fixture, dirección, mensaje y token no son intercambiables.
La idea central es simple: el LLM aporta interpretación, no autoridad implícita. Con contratos pequeños, estados observables y recibos por ejecución, la automatización de pruebas de email puede ganar velocidad sin perder trazabilidad. El resultado deberia ser una investigación más rápida y, sobre todo, un fallo que otro ingeniero pueda repetir.
Top comments (0)