Aller au contenu
astorlm
Langue: Français
← Carte

Niveau 11

Observabilité et évaluations

Une trace montre ce qu'a fait l'agent sur une exécution. Une évaluation le note sur de nombreuses exécutions. Il vous faut les deux : l'évaluation vous dit que quelque chose a cassé, la trace vous dit pourquoi.
1/59 Plis du bandonéon :
  • user
  • assistant
  • tool_result
  • tool_result (erreur)
Cas 1 sur 3. Chaque trou est un cas d'évaluation : un prompt, et ce à quoi ressemble une bonne exécution. Par 3 : un fer, puis un putt.

EventBus

Le problème

Un agent n'est pas une fonction qu'on peut lire de haut en bas. Le modèle décide des étapes à l'exécution, et le même prompt peut prendre un autre chemin demain. Quand un utilisateur dit « il a réservé la mauvaise chose », vous devez savoir ce qu'il a fait, étape par étape. Et quand vous changez le prompt, un outil ou le modèle, vous devez savoir si vous l'avez amélioré ou dégradé.

C'est Blindspot, la boîte noire. Il a l'air de marcher, jusqu'au jour où il ne marche plus, et là personne ne peut dire ce qui s'est passé, ce que ça a coûté, ni depuis quand. Essayer quelques prompts à la main et dire « ça a l'air bien », c'est comme ça que Blindspot part en production.

La solution

L'observabilité, c'est enregistrer chaque exécution sous forme de trace : un arbre de spans, un par unité de travail, chacun avec sa durée et son coût. L'exécution est un span ; chaque tour, chaque appel au modèle et chaque outil aussi. Un span qui a mal tourné est marqué en erreur. Chaque boucle de ce site émet déjà les événements ; un tracer se contente d'écouter l'EventBus et de les chronométrer. Envoyez les spans à n'importe quel outil OpenTelemetry (le standard ouvert pour les traces) et vous obtenez la chronologie de l'animation.

Les évaluations (evals) sont des tests pour agents. Un dataset contient les cas : un prompt, et ce à quoi ressemble une bonne exécution. Vous exécutez chaque cas avec un agent neuf, et des scorers notent chaque exécution. La part de cas qui passent, c'est le chiffre à surveiller. Il existe quatre sortes de scorers, et une bonne évaluation les combine :

  • La réponse

    Comparer le texte final avec ce qu'attend le cas : à l'identique, par un mot qu'il doit contenir, ou par un motif.

    Peu coûteux et déterministe. Il ne voit que la fin : une bonne réponse obtenue par le mauvais chemin passe quand même.

  • La trajectoire

    Comparer les outils que l'agent a appelés, dans l'ordre, avec ceux qu'attend le cas.

    Il repère un mauvais chemin vers une bonne réponse, comme au trou 3. Trop strict, il fait échouer des exécutions qui ont pris une autre bonne route : autorisez des extras là où ils sont sans danger.

  • Votre propre vérification

    N'importe quelle fonction qui va d'une exécution à un score : des coups au par ou en dessous, un budget de tokens, un champ dans la sortie.

    Ce qui compte pour votre produit. Gardez-la déterministe quand c'est possible.

  • Un modèle comme juge

    Un autre modèle lit la question, la réponse et une grille, et donne une note.

    Pour les réponses sans texte juste unique : le ton, l'utilité, un résumé. Plus lent, ça coûte un appel, et il faut une grille précise et quelques contrôles face à des notes humaines.

Le trou 3 explique pourquoi il en faut plusieurs. La réponse disait « trou en 4 », et 4, c'est le par. Seul le scorer de trajectoire a vu le driver là où le cas attendait un fer. Et seule la trace a montré ce qui s'est passé là : un span rouge, une balle dans l'eau.

Les personnages

Les mêmes personnages que d'habitude, cette fois sur un parcours de golf.

Un trou un cas d'évaluation
Un prompt, et ce à quoi ressemble une bonne exécution : son par et les clubs à utiliser.
Le caddie le modèle
Il lit le trou et choisit un club. Il ne frappe jamais.
Les clubs les outils
drive, iron et putt. C'est Astor qui frappe.
Les lignes de vol la trace
Chaque vol reste dessiné sur le parcours, et chaque étape est un span sur la chronologie : appels au modèle en bleu, outils en orange, erreurs en rouge.
Le commissaire votre code d'évaluation
Il confie chaque cas à un agent neuf, et tamponne la carte quand c'est fini.
La carte de score le rapport
Une ligne par cas, une vérification par scorer, et le taux de réussite en bas.

Le code

Avec astorlm : attachTracer transforme les événements d'un agent en spans pour n'importe quel exportateur. runEval exécute un dataset avec un agent neuf par cas, applique vos scorers et renvoie un rapport avec le taux de réussite par scorer. Les deux vivent dans astorlm/experimental.

À partir de zéro : La boucle du niveau 2 avec un chronomètre autour de chaque appel au modèle et de chaque outil, et un for sur les cas avec une fonction par 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: { … } }

Points de vigilance

  • Lancez les évaluations à chaque changement. Un nouveau prompt, une description d'outil ou une version de modèle peut réparer un cas et en casser deux. Gardez le dataset dans le dépôt, exécutez-le en CI, et faites échouer le build quand le taux de réussite baisse.
  • Faites grandir le dataset avec de vrais échecs. Chaque bug signalé par un utilisateur devient un cas, avec sa trace comme preuve. Un dataset de cas faciles passe éternellement et ne prouve rien.
  • Les exécutions varient. Le même cas peut passer aujourd'hui et échouer demain. Exécutez les cas importants plusieurs fois et regardez le taux, pas un résultat isolé.
  • Les traces contiennent les données de vos utilisateurs. Les prompts, les entrées et les sorties des outils s'y retrouvent. Masquez ce qu'il faut (un hook, niveau 6), et traitez le stockage des traces comme une base de données contenant des données personnelles.
  • Le coût est aussi une métrique. Enregistrez les tokens par span. Un agent qui réussit tous les cas avec deux fois plus d'appels, c'est une régression que votre taux de réussite ne montrera pas.