Tu AI Agent Puede Devolver "Éxito" y Haber Fracasado por Completo — Ningún Test Unitario lo Verá Jamás
Tu agent llama a la API. Recibe un HTTP 200. Ejecuta la tool call. Todo parece perfecto.
*Y la tarea ha fallado por completo. *
El agent escribió en el registro equivocado. La acción no tuvo el efecto esperado sobre el estado del sistema. El outcome en el mundo real no coincidió con lo que el output sugería.
Y ningún test unitario, ningún try-catch, ningún framework de testing tradicional va a detectarlo.
Este es el punto ciego estructural del desarrollo de agents en 2026: la mayoría se despliegan sin evaluación estructurada, y sus errores son silenciosos, sistémicos e invisibles. La sabiduría convencional dice que el problema es el modelo — "cambia de modelo, afina el prompt, añade más herramientas". Es exactamente el mismo error categorial que comete el 80% de los SaaS al estructurar sus planes a ciegas: optimizar la cifra en vez de instrumentar el dato.
No necesitas un mejor modelo. Necesitas un harness que clasifique los fallos antes de que puedas medirlos, y que los mida antes de que puedas mejorarlos.
La jerarquía es clasificar → medir → optimizar. Y casi nadie hace los dos primeros pasos.
---
El Punto Ciego Estructural de los Tests Unitarios
Un test unitario verifica que el código devuelve el output correcto. Un agent acierta o falla a nivel de outcome en el mundo real.
Esas dos cosas viven en planos distintos, y ahí reside el problema.
Mira este ejemplo. Tu agent tiene una tool que registra clientes potenciales en un CRM:
```python
test_unitario.py — PASA, pero la tarea real FALLA
def test_registra_cliente():
resultado = agent.run(
herramienta="registrar_prospecto",
input={"nombre": "María", "email": "maria@empresa.com"},
crm="conversoria"
)
El output del código parece correcto
assert resultado.http_status == 200
assert resultado.log == "registro_ok"
✅ El test unitario PASA
```
Ahora mira lo que pasa en producción real:
```python
produccion.py — el outcome NO es el que esperabas
resultado = agent.run(
herramienta="registrar_prospecto",
input={"nombre": "María", "email": "maria@empresa.com"},
crm="conversoria"
)
HTTP 200 ✅ | log = "registro_ok" ✅
PERO el agent escribió en "prospectos_backup"
en vez de la tabla "prospectos_activos"
consulta = "SELECT COUNT(*) FROM prospectos_activos WHERE email='maria@empresa.com'"
❌ 0 filas — la tarea fracasó a nivel de resultado.
```
El test unitario pasó porque verifica el output del código. El agent falló porque el outcome no ocurrió en el mundo.
*Esa brecha entre output y outcome es exactamente el espacio que ningún framework de testing tradicional cubre. *
Y los agents que construyes hoy fallan ahí constantemente. No excepcionalmente — constantemente.
---
Por Qué el Try-Catch es un Error Categorial
Aquí está el giro contraintuitivo que cambia todo lo que sabes sobre errores en agents:
El try-catch modela el fallo como flujo de control excepcional. Pero la clase de fallo dominante en agents no es excepcional.
El sistema completa la ejecución. No lanza nada. No se interrumpe. Y el error vive en el resultado.
```python
❌ Enfoque equivocado: try-catch alrededor de todo
def ejecutar_agent(tarea):
try:
resultado = agent.ejecutar(tarea) # ~2 min de ejecución
return resultado # Nunca lanza — siempre "completa"
except Exception as e:
return manejar_error(e) # ESTE CÓDIGO NUNCA SE EJECUTA
```
El agent nunca lanza una excepción. Devuelve un objeto de resultado "exitoso" que contiene la catástrofe completa sin que nadie se entere.
La recuperación de errores en agents no es un try-catch genérico. Es una máquina de estados por fase — planificar → ejecutar → generar — con un clasificador de errores explícito que decide cómo y cuándo recuperarse:
```python
✅ Enfoque correcto: clasificador + máquina de estados por fase
class FaseAgent:
PLANIFICAR = "planificar"
EJECUTAR = "ejecutar"
GENERAR = "generar"
def clasificar_fallo(contexto) -> Bucket:
"""Clasifica una ejecución en uno de los 4 buckets del harness."""
if contexto["state_ok"] and not contexto["outcome_ok"]:
return Bucket.BUCLE_OCULTO # El agent "completó" pero el resultado no ocurrió
if contexto["tool_seleccionada"] != contexto["tool_requerida"]:
return Bucket.HERRAMIENTA_ERRONEA
if contexto["razonamiento_no_verificado"]:
return Bucket.ALUCINACION_ACCION
return Bucket.CORRECTO
def recuperar(fase_actual, bucket):
if bucket == Bucket.HERRAMIENTA_ERRONEA:
return replanificar(fase_actual) # Replanificar, no reintentar ciegamente
if bucket == Bucket.BUCLE_OCULTO:
return escalar_a_humano(fase_actual) # Escalar — reintentar no servirá
if bucket == Bucket.ALUCINACION_ACCION:
return verificar_estado_y_retry(fase_actual)
return abortar(fase_actual)
```
La decisión de recuperar — reintentar, replanificar, escalar, abortar — pertenece a la taxonomía de evaluación, no al manejador de excepciones.
Esa distinción es la que separa un harness real de un script con try/except.
---
El Framework: El Harness de 4 Buckets para Evaluación de AI Agents
No hay atajo. Este es el proceso completo que convierte tu agent de "creo que funciona" a "medido y justificable".
Paso 1 — Diseña la taxonomía de fallos específica de tu dominio
Los buckets no se copian. Se derivan de los señales observables de tu propio agent.
En un agent de código, el bucket dominante podrías ser selección de herramienta incorrecta. En un agent de soporte, probablemente sea política alucinada. Si copias una taxonomía genérica, obtienes buckets que no clasifican nada de tu realidad.
Define cada bucket en términos de evidencia concreta en tus logs. No en términos abstractos.
| Bucket | Señal observable en tus logs |
|---|---|
| ✅ Correcto | Outcome verificado = esperado |
| 🔧 Herramienta errónea | Tool call ≠ tool requerida para la subtarea |
| 🔁 Bucle oculto | Estado no cambió pese a inputs válidos |
| 🌀 Alucinación de acción | Razón de la acción no verificable en el contexto |
Paso 2 — Construye un suite de tareas doradas
30 a 50 tareas representativas por tipo de tarea. Cada una con un outcome esperado verificable y criterios de aprobación explícitos.
El golden set es el suelo del harness. Sin él, no hay nada que medir.
Paso 3 — Instrumenta el agent
Logs estructurados por fase — plan, tool calls, outputs — que hagan cada ejecución reproducible y clasificable a posteriori.
Sin esto, los buckets no tienen datos que clasificar:
```python
instrumentacion.py — consumo por tarea
def registrar_ejecucion(fase: str, tarea_id: str, tipo_tarea: str, info: dict):
"""Logging estructurado por fase — el proxy no monetario del coste."""
log_estructurado({
"tarea_id": tarea_id,
"tipo_tarea": tipo_tarea,
"fase": fase, # planificar / ejecutar / generar
"tokens_usados": info.get("tokens", 0),
"tool_calls": info.get("tool_calls", 0),
"pasos": info.get("pasos", 0),
"latencia_ms": info.get("latencia_ms", 0),
"modelo": info.get("modelo"),
})
```
Paso 4 — Ejecuta el harness sobre cada candidato
Cada vez que cambias de modelo, de prompt o de versión de herramientas, ejecutas el harness completo. Calculas precisión, fiabilidad y consumo de recursos por tipo de tarea, comparando contra el baseline.
Paso 5 — Convierte el harness en un gate de despliegue
Ejecución en cada cambio. Tracking de métricas en el tiempo. Rollback automático ante regresiones:
```python
gate_despliegue.py — bloquea el deploy ante regresiones
def gate_de_despliegue(resultados_nuevos, baseline):
precision = resultados_nuevos["precision_total"]
fiabilidad = resultados_nuevos["fiabilidad_total"]
if precision < baseline["precision"] * 0.95:
abortar_deploy(f"Precisión degradada: {precision:.1%} vs baseline {baseline['precision']:.1%}")
if fiabilidad < baseline["fiabilidad"] * 0.95:
abortar_deploy(f"Fiabilidad degradada: {fiabilidad:.1%} vs baseline {baseline['fiabilidad']:.1%}")
if resultados_nuevos["tokens_por_tarea"] > baseline["tokens_por_tarea"] * 1.3:
abortar_deploy("Consumo de recursos disparado — revisa la nueva versión")
activar_deploy()
```
Esto es lo que convierte "creo que funciona" en una afirmación medida.
---
"¿La evaluación con LLM no es subjetiva?"
Te escucho. "¿Quién decide que una ejecución falló?"
La respuesta es un enfoque híbrido que no depende de un juez único:
1. Comprobaciones deterministas de outcome: aserciones sobre el estado final, verificación de tool calls.
2. LLM-as-judge calibrado contra etiquetas humanas en un subset del golden set.
Los 4 buckets son una capa de clasificación, no un juez único. Las reglas deterministas cazan el 80% de los fallos silenciosos (el ejemplo del registro equivocado). El LLM-as-judge cubre el resto, y se calibra contra personas reales — no contra otro LLM.
---
"¿No es overkill para un prototipo?"
El harness arranca con un smoke suite de 10 tareas y los 4 buckets. El coste es incremental.
Se amortiza en la primera regresión silenciosa que detecta antes de llegar a producción. Y para un agent de prototipo, la pregunta relevante no es "¿puede hacer esto?" sino "¿puedo justificar que haga esto en producción?"
Un agent sin harness no puede justificarse. No puede mejorarse con evidencia. No puede defenderse contra regresiones.
---
La Lección que se Transfiere Completa
El paralelismo con el pricing es el argumento profundo de todo esto. El 80% de los SaaS estructura sus planes a ciegas — optimizan la cifra en vez de instrumentar el uso. Los equipos de agents optimizan el prompt en vez de instrumentar los outcomes.
Ambos fracasos son epistemológicos, no numéricos. Ambos comparten la misma jerarquía rota: optimizar antes de medir, medir antes de clasificar.
Primero se mide. Luego se optimiza. Siempre en ese orden.
Lo que te llevas
Los tests unitarios detectan crashes y violaciones de contrato, no fallos silenciosos de outcome — son complementarios al harness, no sustitutos.
El try-catch es un error categorial: el fallo dominante en agents no es excepcional, vive en el resultado.
Los 4 buckets convierten los logs ruidosos en una señal agregable por tipo de tarea, comparable entre candidatos y trackeable en el tiempo.
Medir consumo por tarea (tokens, tool calls, latencia) añade la tercera dimensión: no basta con que sea correcto, tiene que ser eficiente y fiable en cada tipo de tarea.
El harness es el único mecanismo que hace seguros los rollbacks y las actualizaciones de modelo.
El futuro de los agents no lo deciden los modelos más grandes ni los prompts más largos. Lo deciden los equipos que instrumentan sus outcomes, miden por tipo de tarea y convierten cada regresión silenciosa en una señal accionable.
El harness no es un gasto de ingeniería. Es la diferencia entre un agent que demuestra valor y un agent que finge tenerlo.
Lee el artículo completo en brianmenagomez.com
Más sobre mis servicios en brianmenagomez.com
Herramientas: Conversor IAE CNAE · Gestorias cerca de ti · Calculadora IRPF

