Saltar al contenido
astorlm
Idioma: Español
← Mapa

Nivel 11

Observabilidad y evaluaciones

Una traza muestra qué hizo el agente en una ejecución. Una evaluación lo califica en muchas ejecuciones. Necesitas las dos: la evaluación te dice que algo se rompió, la traza te dice por qué.
1/59 Pliegues del bandoneón:
  • user
  • assistant
  • tool_result
  • tool_result (error)
Caso 1 de 3. Cada hoyo es un caso de evaluación: un prompt, y cómo se ve una buena ejecución. Par 3: un hierro, y después un putt.

EventBus

El problema

Un agente no es una función que puedas leer de arriba abajo. El modelo decide los pasos en tiempo de ejecución, y el mismo prompt puede tomar otro camino mañana. Cuando un usuario dice “reservó lo que no era”, necesitas saber qué hizo, paso a paso. Y cuando cambias el prompt, una herramienta o el modelo, necesitas saber si lo mejoraste o lo empeoraste.

Ese es Blindspot, la caja negra. Parece funcionar, hasta que deja de hacerlo, y entonces nadie puede decir qué pasó, cuánto costó ni desde cuándo. Probar unos cuantos prompts a mano y decir “se ve bien” es la forma en que Blindspot llega a producción.

La solución

Observabilidad significa registrar cada ejecución como una traza: un árbol de spans, uno por cada unidad de trabajo, cada uno con cuánto tardó y cuánto costó. La ejecución es un span; también lo es cada turno, cada llamada al modelo y cada herramienta. Un span que salió mal se marca como error. Cada bucle de este sitio ya emite los eventos; un tracer solo escucha el EventBus y les toma el tiempo. Envía los spans a cualquier herramienta de OpenTelemetry (el estándar abierto para trazas) y obtienes la línea de tiempo de la animación.

Las evaluaciones (evals) son tests para agentes. Un dataset guarda los casos: un prompt, y cómo se ve una buena ejecución. Corres cada caso con un agente nuevo, y los scorers califican cada ejecución. La proporción de casos que pasan es tu número a vigilar. Hay cuatro tipos de scorer, y una buena evaluación los mezcla:

  • La respuesta

    Comparar el texto final con lo que espera el caso: exacto, por una palabra que debe contener o por un patrón.

    Barato y determinista. Solo ve el final: una respuesta correcta a la que se llegó por el camino equivocado igual pasa.

  • La trayectoria

    Comparar las herramientas que llamó el agente, en orden, con las que espera el caso.

    Detecta un mal camino hacia una buena respuesta, como el hoyo 3. Si es demasiado estricto, falla ejecuciones que tomaron otra ruta válida: permite extras donde no hacen daño.

  • Tu propia verificación

    Cualquier función que vaya de una ejecución a un puntaje: golpes en par o por debajo, un presupuesto de tokens, un campo en la salida.

    Lo que le importe a tu producto. Mantenla determinista cuando puedas.

  • Un modelo como juez

    Otro modelo lee la pregunta, la respuesta y una rúbrica, y pone una nota.

    Para respuestas sin un único texto correcto: tono, utilidad, un resumen. Es más lento, cuesta una llamada y necesita una rúbrica precisa y algunos controles contra notas humanas.

El hoyo 3 es la razón por la que quieres más de uno. La respuesta decía “embocada en 4”, y 4 es el par. Solo el scorer de trayectoria vio el driver donde el caso esperaba un hierro. Y solo la traza mostró qué pasó ahí: un span rojo, una bola en el agua.

El elenco

El mismo elenco de siempre, esta vez en una cancha de golf.

Un hoyo un caso de evaluación
Un prompt, y cómo se ve una buena ejecución: su par y los palos a usar.
El caddie el modelo
Lee el hoyo y elige un palo. Nunca golpea.
Los palos las herramientas
drive, iron y putt. Astor es quien golpea.
Las líneas de vuelo la traza
Cada vuelo queda dibujado en la cancha, y cada paso es un span en la línea de tiempo: llamadas al modelo en azul, herramientas en naranja, errores en rojo.
El comisario tu código de evaluación
Le pasa cada caso a un agente nuevo, y sella la tarjeta cuando termina.
La tarjeta el informe
Una fila por caso, una verificación por scorer, y la tasa de aprobación al pie.

El código

Con astorlm: attachTracer convierte los eventos de un agente en spans para cualquier exportador. runEval corre un dataset con un agente nuevo por caso, aplica tus scorers y devuelve un informe con la tasa de aprobación por scorer. Los dos viven en astorlm/experimental.

Desde cero: El bucle del nivel 2 con un cronómetro alrededor de cada llamada al modelo y de cada herramienta, y un for sobre los casos con una función por scorer.

import { OpenAIProvider, createLocalAgent } from 'astorlm'
import { attachTracer, createInMemoryExporter } from 'astorlm/experimental/tracing'
import { contains, runEval, toolTrajectory, type EvalCase, type Scorer } from 'astorlm/experimental/evals'

// Any OpenAI-compatible endpoint: OpenAI, Ollama, LM Studio, vLLM, a proxy…
const LLM = { baseURL: 'http://localhost:11434/v1', apiKey: 'YOUR_API_KEY' } // local servers usually ignore the key

const newGolfer = () =>
  createLocalAgent({
    provider: new OpenAIProvider({ ...LLM, model: 'your-model' }), // e.g. 'llama3.1', 'gpt-4o-mini'
    tools: [drive, iron, putt], // your code: each swing moves the ball and says where it landed
    maxTurns: 10,
  })

// 1. SEE one run: a tracer turns the agent's events into spans (run → turn → model call / tool).
const golfer = await newGolfer()
const exporter = createInMemoryExporter() // in production: an OpenTelemetry exporter instead
attachTracer(golfer, { exporter })
await golfer.run('Hole 3: par 4, 330 m to the pin. Water crosses the fairway at 200 m.')
for (const span of exporter.spans) {
  console.log(span.name, span.endTime! - span.startTime, 'ms', span.status) // e.g. "tool drive 320 ms error"
}

// 2. GRADE many runs: a dataset of cases, and scorers that decide pass or fail.
const dataset: EvalCase[] = [
  { id: 'hole-1', input: 'Hole 1: par 3, 150 m to the pin. Play it out and report your score.', expected: { par: 3, clubs: ['iron', 'putt'] } },
  { id: 'hole-2', input: 'Hole 2: par 4, 360 m to the pin. Play it out and report your score.', expected: { par: 4, clubs: ['drive', 'iron', 'putt'] } },
  {
    id: 'hole-3',
    input: 'Hole 3: par 4, 330 m to the pin. Water crosses the fairway at 200 m. Play it out and report your score.',
    expected: { par: 4, clubs: ['iron', 'iron', 'putt'] }, // lay up short of the water
  },
]
type Expected = { par: number; clubs: string[] }

// Each case expects its own clubs, so wrap the built-in trajectory scorer.
const path: Scorer = {
  name: 'trajectory',
  score: (result) => toolTrajectory((result.case.expected as Expected).clubs, { mode: 'ordered-subset' }).score(result),
}
// Your own scorer: any function from a run to a 0..1 score.
const par: Scorer = {
  name: 'par',
  score: (result) => {
    const strokes = Number(/in (\d+)/.exec(result.output)?.[1] ?? Infinity)
    const passed = strokes <= (result.case.expected as Expected).par
    return { scorer: 'par', score: passed ? 1 : 0, passed, details: `${strokes} strokes` }
  },
}

const report = await runEval({
  dataset,
  createAgent: () => newGolfer(), // a fresh agent per case: no shared history
  scorers: [contains('Holed out'), path, par], // add llmJudge({ provider, rubric }) for fuzzy answers
})

console.log(report.summary) // { total: 3, passed: 2, passRate: 0.67, byScorer: { … } }

Qué vigilar

  • Corre las evaluaciones en cada cambio. Un prompt nuevo, una descripción de herramienta o una versión de modelo pueden arreglar un caso y romper dos. Mantén el dataset en el repo, córrelo en CI y haz fallar el build cuando baja la tasa de aprobación.
  • Haz crecer el dataset con fallas reales. Cada bug que reporta un usuario se convierte en un caso, con su traza como evidencia. Un dataset solo de casos fáciles pasa para siempre y no prueba nada.
  • Las ejecuciones varían. El mismo caso puede pasar hoy y fallar mañana. Corre los casos importantes varias veces y mira la tasa, no un solo resultado.
  • Las trazas guardan datos de tus usuarios. Los prompts y las entradas y salidas de herramientas terminan en ellas. Oculta lo que debas (un hook, nivel 6), y trata el almacén de trazas como una base de datos con datos personales.
  • El costo también es una métrica. Registra tokens por span. Un agente que pasa todos los casos con el doble de llamadas es una regresión que tu tasa de aprobación no va a mostrar.