← Todo el material
Curso Intermedio 23 de agosto de 2026

Evals — Módulo 5: evaluar el camino, no solo la respuesta

Quinto módulo del Curso de Evals: un agente puede acertar la respuesta por el camino equivocado, y ese agente falla mañana. Qué es una trayectoria, cómo se mide su eficiencia, cómo se detectan bucles y pasos redundantes, las tres formas de compararla contra una referencia y cómo atribuir el fallo al paso exacto donde se rompió.

☰ Índice del contenido

En el módulo 4 dejamos un juez calibrado: un LLM que puntúa la salida final con una rúbrica que correlaciona razonablemente con lo que diría un humano. Ese juez mira una cosa y una sola: el texto que el agente entregó al final. Y funciona bien mientras el sistema sea un chatbot que responde en un turno. Cuando el sistema es un agente —algo que razona, llama herramientas, observa lo que devuelven y vuelve a decidir— ese juez se queda ciego frente a la mitad del sistema. Este módulo abre esa mitad.

La tesis del módulo

Un agente que llega a la respuesta correcta por el camino equivocado no es un agente que funciona: es un agente que todavía no falla. La respuesta final es el resultado de una lotería de N pasos; evaluar solo el resultado es evaluar la lotería, no el sistema.

Qué es una trayectoria

Una trayectoria (trajectory) es la secuencia completa de pasos que el agente ejecutó desde que recibió la tarea hasta que la dio por terminada. En el patrón ReAct que vimos en el módulo 5 de LangGraph, cada paso es una vuelta del ciclo:

  1. Razonar (thought): el modelo decide qué hacer a continuación.
  2. Actuar (action): llama a una herramienta con ciertos argumentos.
  3. Observar (observation): recibe el resultado de esa herramienta.
  4. Vuelve al paso 1, hasta que decide que ya puede responder.

La trayectoria es el registro de esas vueltas. Si el agente resolvió “¿cuánto vendimos en marzo comparado con febrero?” llamando primero a consultar_ventas(mes="2026-03"), después a consultar_ventas(mes="2026-02") y finalmente respondiendo, su trayectoria tiene dos pasos de herramienta y una respuesta.

En términos prácticos, una trayectoria es lo que ya está en tu sistema de trazas —LangSmith, Phoenix o el stream() de LangGraph— pero convertido en un objeto que se puede puntuar. La diferencia entre observabilidad y evaluación de trayectorias es exactamente esa: la observabilidad te deja mirar un caso; la evaluación te deja puntuar los doscientos y comparar la corrida de hoy con la de ayer.

Dos caminos al mismo resultado

Este diagrama es el módulo entero en una imagen. Las dos trayectorias responden bien la misma pregunta. Solo una de las dos es un sistema que puedes poner en producción.

     TRAYECTORIA SANA (2 tools + respuesta)

START ──► ┌──────────────────────────┐
          │ 1  buscar_cliente(rut)   │
          └────────────┬─────────────┘
                       ▼
          ┌──────────────────────────┐
          │ 2  ver_saldo(id=8812)    │
          └────────────┬─────────────┘
                       ▼
          ┌──────────────────────────┐
          │ 3  responder("$142.900") │ ──► END
          └──────────────────────────┘


  TRAYECTORIA PATOLÓGICA (6 tools + respuesta)

START ──► ┌──────────────────────────┐
          │ 1  ver_saldo(id=null)    │
          └────────────┬─────────────┘
                       ▼ error: id requerido
          ┌──────────────────────────┐
          │ 2  ver_saldo(id=null)    │ ◄──┐
          └────────────┬─────────────┘    │
                       ▼ mismo error      │ bucle
          ┌──────────────────────────┐    │  x3
          │ 3  ver_saldo(id=null)    │ ───┘
          └────────────┬─────────────┘
                       ▼
          ┌──────────────────────────┐
          │ 4  listar_clientes()     │ 40k tokens
          └────────────┬─────────────┘
                       ▼
          ┌──────────────────────────┐
          │ 5  buscar_cliente(rut)   │
          └────────────┬─────────────┘
                       ▼
          ┌──────────────────────────┐
          │ 6  ver_saldo(id=8812)    │
          └────────────┬─────────────┘
                       ▼
          ┌──────────────────────────┐
          │ 7  responder("$142.900") │ ──► END
          └──────────────────────────┘

  Mismo output. Un juez de salida final les pone 5/5 a ambas.

La segunda trayectoria tiene tres problemas que el juez de salida final no ve, y los tres son bugs reales:

  • El bucle: repitió tres veces la misma llamada con el mismo argumento inválido. Con un max_iterations un poco más generoso, ese bucle es infinito y el caso se cae por timeout.
  • El paso caro: listar_clientes() sin filtro trajo cuarenta mil tokens al contexto. Ese caso cuesta veinte veces lo que debería y empuja al agente hacia la ventana de contexto.
  • La fragilidad: llegó al resultado correcto porque buscar_cliente estaba disponible y el modelo se acordó tarde. Mañana, con el contexto un poco más lleno o con el proveedor actualizando el modelo, no se acuerda.

Ese último punto es la razón profunda para evaluar trayectorias: la trayectoria patológica no es un caso que pasó, es un caso que va a fallar y que todavía no falló. Medirla es la única forma de arreglarlo antes.

Las métricas de trayectoria

Cinco métricas cubren casi todo lo que necesitas. Todas son deterministas —se calculan con aritmética sobre la traza, sin llamar a ningún LLM— y por lo tanto son baratas y corren en cada pull request, igual que las métricas de retrieval del módulo de evaluación de RAG.

1. Éxito de tarea (task success)

Éxito de tarea = fracción de casos donde el estado final cumple el criterio de éxito definido en el módulo 2.

Es la métrica principal y sigue siendo binaria. Todo lo demás en este módulo la condiciona, pero no la reemplaza: un agente eficientísimo que no resuelve la tarea vale cero.

2. Eficiencia del camino (path efficiency)

Eficiencia = pasos mínimos / pasos ejecutados, acotada a 1.0.

Si la tarea se resuelve en 2 llamadas a herramientas y el agente usó 6, la eficiencia es 2/6 = 0.33. Si usó 2, es 1.0. Los “pasos mínimos” salen de tu trayectoria de referencia, que escribiste tú a mano cuando armaste el caso.

Es la métrica que más rápido detecta una degradación silenciosa: un cambio de prompt que sube el éxito de 0.88 a 0.89 pero baja la eficiencia de 0.81 a 0.52 no es una mejora, es un agente que aprendió a compensar a fuerza de intentos.

3. Tasa de bucles (loop rate)

Tasa de bucles = fracción de casos donde la misma llamada (nombre + argumentos normalizados) se repite N o más veces sin que cambie la observación.

La condición “sin que cambie la observación” es la que separa el bucle real del reintento legítimo: llamar dos veces a consultar_estado(pedido=77) para ver si ya cambió es normal; llamarla tres veces recibiendo el mismo error no lo es. Con N=3 se pesca casi todo sin falsos positivos.

4. Pasos redundantes

Pasos redundantes = llamadas cuya observación no aportó información nueva que el agente usara después.

En la práctica se aproxima con dos reglas baratas: llamadas duplicadas exactas, y llamadas cuyo resultado nunca aparece citado ni referenciado en los pasos posteriores. Es la métrica que más se ensucia; úsala como señal de investigación, no como umbral que bloquea.

5. Costo por tarea

Costo por tarea = tokens de entrada × precio_in + tokens de salida × precio_out, sumado sobre todos los pasos.

Es la métrica que convence al que paga la cuenta, y la que hace visible el listar_clientes() del ejemplo. Repórtala siempre junto al éxito: un agente con 0.94 de éxito a $0.08 por tarea y otro con 0.91 a $0.006 son decisiones de negocio distintas, no un ganador y un perdedor.

MétricaDeterministaQué diagnosticaUmbral inicial razonable
Éxito de tareaDepende del criterioSi el sistema sirveLo que hayas definido en el módulo 2
Eficiencia del caminoSíPrompt vago, tools mal descritas> 0.70
Tasa de buclesSíManejo de errores inexistente< 0.02
Pasos redundantesSí (aproximada)Contexto que el agente no leeSeñal, no umbral
Costo por tareaSíTools que devuelven demasiadoPresupuesto del proyecto

Ojo: la eficiencia del camino no se optimiza sola, se optimiza contra el éxito. Un agente al que castigas por dar pasos aprende a rendirse antes. Siempre se reportan juntas y siempre se lee la eficiencia condicionada a los casos exitosos: la eficiencia de una trayectoria que falló no significa nada.

Comparar contra una trayectoria de referencia

Para las métricas anteriores basta con la traza. Pero muchas veces quieres algo más fuerte: ¿siguió el camino que yo esperaba? Ahí necesitas una trayectoria de referencia (reference trajectory), que es la secuencia de llamadas que un experto escribió como camino correcto para ese caso.

La pregunta operativa es cómo se comparan dos secuencias. Hay tres formas, y elegir mal es la causa más común de evals de trayectoria que nadie mira porque “siempre salen rojos”.

Coincidencia exacta (exact match)

La trayectoria observada tiene que ser idéntica a la de referencia: mismas herramientas, mismo orden, sin nada de más ni de menos.

referencia : [buscar_cliente, ver_saldo]
observada  : [buscar_cliente, ver_saldo]            ✓ pasa
observada  : [buscar_cliente, ver_perfil, ver_saldo] ✗ falla

Es la más estricta y casi siempre es demasiado estricta. Un agente que consultó un dato extra antes de responder no hizo nada malo; con exact match, ese caso es rojo. El resultado típico es una suite con 40% de aprobación que el equipo aprende a ignorar, que es peor que no tener suite.

Coincidencia como subsecuencia (in-order match)

La referencia tiene que aparecer en orden dentro de la observada, pero se permiten pasos extra intercalados.

referencia : [buscar_cliente, ver_saldo]
observada  : [buscar_cliente, ver_perfil, ver_saldo] ✓ pasa
observada  : [ver_saldo, buscar_cliente]             ✗ falla

Es el modo por defecto para la gran mayoría de los casos. Captura lo único que de verdad importa cuando hay dependencias —“tienes que buscar al cliente antes de pedir su saldo”— y no castiga la exploración razonable. Los pasos extra no se ignoran: se contabilizan en la eficiencia del camino, que es donde corresponde.

Coincidencia de conjunto (any-order match)

Solo importa que las herramientas de la referencia hayan sido llamadas, sin importar el orden ni lo que haya en medio.

referencia : {consultar_clima, consultar_vuelos}
observada  : [consultar_vuelos, consultar_clima]   ✓ pasa
observada  : [consultar_clima]                     ✗ falla

Es la correcta cuando los pasos son genuinamente independientes: consultar el clima y consultar los vuelos no tienen relación de dependencia, y exigir un orden es inventar un requisito que no existe.

ModoPermite pasos extraExige ordenCuándo usarloRiesgo
ExactoNoSíFlujos regulados con secuencia auditada (KYC, pagos)Falsos rojos; la suite se abandona
SubsecuenciaSíSíPor defecto. Hay dependencias reales entre pasosDeja pasar caminos ineficientes (mídelos aparte)
ConjuntoSíNoPasos independientes, consultas paralelasNo detecta inversiones de orden peligrosas

Cuidado: el modo de comparación se define por caso, no por suite. En un mismo golden dataset conviven un caso de reembolso que exige orden exacto (verificar identidad → verificar compra → emitir nota de crédito) y un caso de consulta que no exige nada. Un campo modo en cada caso del dataset es la diferencia entre un eval que la gente respeta y uno que la gente silencia.

Atribución del fallo al paso N

Saber que un caso falló no arregla nada. Lo que arregla es saber en qué paso se rompió, y eso no se deduce de la respuesta final: se instrumenta.

La regla es que cada paso de la trayectoria guarde lo suficiente para reconstruirlo sin volver a ejecutar nada:

       ANATOMÍA DE UN PASO INSTRUMENTADO

  ┌──────────────────────────────────────────────┐
  │ indice        3                              │
  │ herramienta   ver_saldo                      │
  │ argumentos    {"id": null}                   │
  │ observacion   ERROR: id es obligatorio       │
  │ ok            false                          │
  │ tokens_in     1842                           │
  │ tokens_out    31                             │
  │ latencia_ms   410                            │
  └──────────────────────────────────────────────┘

  primer paso con ok=false  ──►  paso culpable = 3
  todo lo posterior         ──►  consecuencia, no causa

Con eso, la atribución es una regla simple y determinista: el paso culpable es el primer paso donde ok es falso o donde la trayectoria se desvía de la referencia. Todo lo que viene después es consecuencia. Es exactamente el mismo principio que “el primer error del compilador es el único que hay que leer”.

El valor de la atribución no es por caso: es agregado. Cuando cruzas cien casos fallidos por paso culpable y por herramienta, aparece la tabla que decide tu semana:

Paso culpableHerramientaCasosCausa dominante
1buscar_cliente3El usuario dio el RUT con puntos
2ver_saldo31Argumento id nulo: el agente no encadena
4emitir_nota6Timeout del servicio externo

Treinta y uno de cuarenta fallos en un solo paso, por una sola causa. Ese es el arreglo de la semana, y sin atribución habrías leído cuarenta trazas a mano para descubrirlo.

Código: representar y puntuar una trayectoria

Nada de esto necesita un framework. Son dataclasses y aritmética.

"""Evaluación de trayectorias de agentes: representación y puntuación.

Sin dependencias externas. La idea es que puedas pegar esto en tu
repo y adaptarlo al formato de traza que ya emite tu agente.
"""
from __future__ import annotations

import json
from collections import Counter
from dataclasses import dataclass, field
from statistics import mean


@dataclass
class Paso:
    """Un paso de la trayectoria: una llamada a herramienta y su resultado."""
    herramienta: str
    argumentos: dict
    observacion: str = ""
    ok: bool = True
    tokens_in: int = 0
    tokens_out: int = 0
    latencia_ms: int = 0

    def firma(self) -> str:
        """Identidad normalizada del paso: nombre + argumentos ordenados.

        Sirve para detectar repeticiones. `sort_keys` evita que
        {"a":1,"b":2} y {"b":2,"a":1} cuenten como llamadas distintas.
        """
        return f"{self.herramienta}({json.dumps(self.argumentos, sort_keys=True)})"


@dataclass
class Trayectoria:
    pasos: list[Paso] = field(default_factory=list)
    respuesta_final: str = ""
    exito: bool = False          # lo fija el criterio de éxito del caso

    @property
    def nombres(self) -> list[str]:
        return [p.herramienta for p in self.pasos]

    def costo_usd(self, precio_in: float, precio_out: float) -> float:
        """Precios en USD por 1M de tokens (como los publican los proveedores)."""
        t_in = sum(p.tokens_in for p in self.pasos)
        t_out = sum(p.tokens_out for p in self.pasos)
        return (t_in * precio_in + t_out * precio_out) / 1_000_000


# --- Comparación contra una trayectoria de referencia -------------------

def coincide_exacto(obs: list[str], ref: list[str]) -> bool:
    return obs == ref


def coincide_subsecuencia(obs: list[str], ref: list[str]) -> bool:
    """¿Aparece `ref` en orden dentro de `obs`, permitiendo pasos extra?

    Recorre `obs` una sola vez avanzando un puntero sobre `ref`.
    O(n). Si el puntero llega al final, la referencia está contenida.
    """
    i = 0
    for nombre in obs:
        if i < len(ref) and nombre == ref[i]:
            i += 1
    return i == len(ref)


def coincide_conjunto(obs: list[str], ref: list[str]) -> bool:
    """Ignora orden y repeticiones: solo exige que se hayan llamado todas."""
    return set(ref).issubset(set(obs))


COMPARADORES = {
    "exacto": coincide_exacto,
    "subsecuencia": coincide_subsecuencia,
    "conjunto": coincide_conjunto,
}


# --- Métricas de trayectoria -------------------------------------------

def detectar_bucle(t: Trayectoria, n: int = 3) -> bool:
    """Bucle = misma firma repetida n+ veces CON la misma observación.

    La condición sobre la observación es la que distingue el bucle
    patológico del polling legítimo (consultar un estado que cambia).
    """
    conteo = Counter((p.firma(), p.observacion) for p in t.pasos)
    return any(c >= n for c in conteo.values())


def pasos_redundantes(t: Trayectoria) -> int:
    """Llamadas duplicadas exactas: la primera cuenta, las demás sobran."""
    vistas: set[str] = set()
    redundantes = 0
    for p in t.pasos:
        if p.firma() in vistas:
            redundantes += 1
        vistas.add(p.firma())
    return redundantes


def eficiencia(t: Trayectoria, pasos_minimos: int) -> float:
    """pasos_minimos / pasos_ejecutados, acotado a 1.0.

    Se acota porque una trayectoria más corta que la referencia no es
    'mejor que perfecta': o el caso se resolvió por otro camino válido
    o el criterio de éxito está mal definido.
    """
    if not t.pasos:
        return 0.0
    return min(1.0, pasos_minimos / len(t.pasos))


def paso_culpable(t: Trayectoria, ref: list[str] | None = None) -> int | None:
    """Índice (1-indexado) del primer paso que se rompió o se desvió.

    Prioridad: (1) primer paso con ok=False; (2) si hay referencia y no
    coincide como subsecuencia, la primera posición donde se desvía.
    Devuelve None si la trayectoria es sana.
    """
    for i, p in enumerate(t.pasos, start=1):
        if not p.ok:
            return i
    if ref and not coincide_subsecuencia(t.nombres, ref):
        # Primer paso que no pertenece a la referencia; si todos
        # pertenecen (faltó uno), el culpable es el último ejecutado.
        for i, nombre in enumerate(t.nombres, start=1):
            if nombre not in ref:
                return i
        return len(t.pasos) or None
    return None


def evaluar_caso(t: Trayectoria, caso: dict) -> dict:
    """Puntúa una trayectoria contra la definición de un caso del golden set.

    El caso trae: trayectoria_referencia, modo de comparación y
    pasos_minimos (por defecto, el largo de la referencia).
    """
    ref = caso["trayectoria_referencia"]
    modo = caso.get("modo", "subsecuencia")
    minimos = caso.get("pasos_minimos", len(ref))
    comparar = COMPARADORES[modo]

    return {
        "id": caso["id"],
        "exito": t.exito,
        "camino_ok": comparar(t.nombres, ref),
        "eficiencia": round(eficiencia(t, minimos), 3),
        "bucle": detectar_bucle(t),
        "redundantes": pasos_redundantes(t),
        "pasos": len(t.pasos),
        "costo_usd": round(t.costo_usd(precio_in=3.0, precio_out=15.0), 5),
        "paso_culpable": paso_culpable(t, ref),
    }


def agregar(resultados: list[dict]) -> dict:
    """Agregado de la corrida. Ojo: la eficiencia se promedia SOLO sobre
    los casos exitosos —la eficiencia de un caso fallido no significa nada."""
    exitosos = [r for r in resultados if r["exito"]]
    return {
        "n": len(resultados),
        "exito_tarea": round(mean(r["exito"] for r in resultados), 3),
        "camino_ok": round(mean(r["camino_ok"] for r in resultados), 3),
        "eficiencia_media": (
            round(mean(r["eficiencia"] for r in exitosos), 3) if exitosos else 0.0
        ),
        "tasa_bucles": round(mean(r["bucle"] for r in resultados), 3),
        "costo_medio_usd": round(mean(r["costo_usd"] for r in resultados), 5),
        "fallos_por_paso": dict(
            Counter(r["paso_culpable"] for r in resultados if r["paso_culpable"])
        ),
    }

Y así se ve corriéndolo sobre las dos trayectorias del diagrama:

if __name__ == "__main__":
    caso = {
        "id": "saldo_001",
        "trayectoria_referencia": ["buscar_cliente", "ver_saldo"],
        "modo": "subsecuencia",
        "pasos_minimos": 2,
    }

    sana = Trayectoria(
        pasos=[
            Paso("buscar_cliente", {"rut": "12345678-9"}, "id=8812",
                 tokens_in=900, tokens_out=40),
            Paso("ver_saldo", {"id": 8812}, "142900",
                 tokens_in=1000, tokens_out=30),
        ],
        respuesta_final="El saldo es $142.900",
        exito=True,
    )

    patologica = Trayectoria(
        pasos=[
            *[Paso("ver_saldo", {"id": None}, "ERROR: id es obligatorio",
                   ok=False, tokens_in=1800, tokens_out=30)] * 3,
            Paso("listar_clientes", {}, "[... 40k tokens ...]",
                 tokens_in=42000, tokens_out=25),
            Paso("buscar_cliente", {"rut": "12345678-9"}, "id=8812",
                 tokens_in=44000, tokens_out=40),
            Paso("ver_saldo", {"id": 8812}, "142900",
                 tokens_in=45000, tokens_out=30),
        ],
        respuesta_final="El saldo es $142.900",
        exito=True,
    )

    for t in (sana, patologica):
        print(evaluar_caso(t, caso))

# {'id': 'saldo_001', 'exito': True, 'camino_ok': True, 'eficiencia': 1.0,
#  'bucle': False, 'redundantes': 0, 'pasos': 2, 'costo_usd': 0.00675,
#  'paso_culpable': None}
#
# {'id': 'saldo_001', 'exito': True, 'camino_ok': True, 'eficiencia': 0.333,
#  'bucle': True, 'redundantes': 2, 'pasos': 6, 'costo_usd': 0.41197,
#  'paso_culpable': 1}

Léelo con calma, porque es el punto del módulo. Las dos trayectorias tienen exito: True y camino_ok: True —el modo subsecuencia acepta los pasos extra—. Un eval de salida final las declara idénticas. Pero la segunda tiene eficiencia 0.333 en vez de 1.0, un bucle detectado, dos pasos redundantes y un costo de $0.412 contra $0.0068: 61 veces más cara por la misma respuesta. Ese factor 61 es invisible sin evaluar el camino.

Ojo: los precios del ejemplo (3.0 y 15.0 USD por millón de tokens de entrada y salida) son solo placeholders para que la aritmética se pueda seguir. Pon los de tu proveedor y tu modelo, y ponlos en una constante versionada: cuando cambien, quieres que tu histórico de costos siga siendo comparable o al menos que se note el corte.

Cómo se instrumenta esto en la práctica

No hace falta reescribir el agente. Hay tres puntos de enganche, de menos a más invasivo:

EngancheCómoCuándo conviene
Callbacks del frameworkstream(mode="updates") en LangGraph, callbacks en LangChainYa usas un framework; es lo más barato
Trazas OpenTelemetryCada tool es un span; la trayectoria es el árbol de spansYa tienes observabilidad montada en producción
Wrapper de toolsUn decorador que registra nombre, args, resultado y tokensAgente artesanal, sin framework

El wrapper es el que menos gente conoce y el que más rinde cuando el agente es propio:

"""Wrapper mínimo para capturar la trayectoria sin tocar la lógica."""
import functools
import time


def instrumentar(registro: list[Paso]):
    """Decorador de fábrica: envuelve una tool y anota cada llamada.

    `registro` es la lista donde se van acumulando los pasos de ESTA
    ejecución. En concurrencia usa un contextvar en vez de una lista suelta.
    """
    def decorador(fn):
        @functools.wraps(fn)   # conserva __name__ y __doc__: el LLM lee la docstring
        def envuelta(**kwargs):
            t0 = time.perf_counter()
            try:
                resultado = fn(**kwargs)
                ok = True
            except Exception as e:              # el error TAMBIÉN es un paso
                resultado, ok = f"ERROR: {e}", False
            registro.append(Paso(
                herramienta=fn.__name__,
                argumentos=kwargs,
                observacion=str(resultado)[:500],   # trunca: no guardes 40k
                ok=ok,
                latencia_ms=int((time.perf_counter() - t0) * 1000),
            ))
            return resultado
        return envuelta
    return decorador

Dos detalles que se pagan solos: functools.wraps conserva la docstring, que es literalmente lo que el modelo lee para decidir si usa la tool (volveremos sobre esto en el módulo 6), y capturar la excepción como paso con ok=False en vez de dejarla propagar es lo que hace posible medir la recuperación tras un error.

Errores comunes

  • Evaluar solo la salida final en un sistema agéntico. Es el error madre del módulo. La respuesta correcta por el camino equivocado es una bomba de tiempo: aprobó hoy y va a reprobar cuando cambie el contexto, el modelo o el orden de las tools.
  • Usar coincidencia exacta por defecto. Genera una suite con 40% de aprobación donde nadie distingue el rojo real del rojo de siempre. Subsecuencia por defecto, exacto solo donde la secuencia sea un requisito auditado.
  • Escribir la trayectoria de referencia mirando lo que hizo el agente. Es el equivalente a escribir el examen después de ver las respuestas. La referencia la define quien sabe cuál es el camino correcto, antes de correr nada.
  • Promediar la eficiencia sobre todos los casos. Un caso que falló en el paso 1 tiene eficiencia altísima y contamina el promedio hacia arriba. La eficiencia se lee condicionada al éxito, siempre.
  • Optimizar el número de pasos como si fuera el objetivo. Presiona a un agente por dar menos pasos y aprenderá a responder sin verificar. Los pasos son un costo, no un mal; el objetivo es el éxito al costo mínimo, no el costo mínimo.
  • No guardar los argumentos de cada llamada. Sin argumentos no hay firma, sin firma no hay detección de bucles ni de redundancia, y la traza se vuelve una lista de nombres de función que no diagnostica nada. Guardar los argumentos (anonimizados si traen datos personales) es el requisito mínimo.
  • Guardar la observación completa. Cuarenta mil tokens por paso × doscientos casos × cada corrida es una factura de almacenamiento y un JSON que no abre. Trunca a unos cientos de caracteres y guarda aparte el hash de lo completo si necesitas auditoría.

La frase para llevarse

“La respuesta final te dice si el agente acertó hoy. La trayectoria te dice si va a acertar mañana.”

Qué viene

Módulo 6: evaluar el uso de herramientas, que es donde los agentes se rompen de verdad. Vamos a desarmar los cinco fallos que hay que medir por separado —elegir la tool equivocada, pasarle argumentos mal tipados, inventarse tools que no existen, no saber qué hacer cuando la tool devuelve un error, y romper el orden de las dependencias— cada uno con su métrica propia. Y vamos a montar un harness con las tools mockeadas que inyecta errores a propósito para medir la recuperación, porque un agente que nunca vio fallar una herramienta no es un agente probado. Vas a descubrir, además, que buena parte de los fallos de selección no se arreglan cambiando de modelo: se arreglan reescribiendo la descripción de la tool.

Material relacionado

EvalsAgentesTrayectoriasObservabilidadMétricas

Preguntas sobre este curso

Regístrate gratis para hacer preguntas y guardar tu progreso.

✉ Entérate de lo nuevo

Recibe un aviso cuando publique nuevos cursos, workshops o investigaciones. Sin spam.