Zum Inhalt springen
EN DE

Challenge 6.1: Evalite Basics

Wie testest Du, ob Dein LLM-System gute Antworten gibt — manuell jedes Mal durchlesen? Was, wenn Du 50 Prompts aenderst und prüfen willst, ob die Qualität gestiegen oder gesunken ist?

.eval.ts Datei liefert Input, der durch data (Input + Expected), task (LLM Call) und scorers (Bewertung) fliesst und im Score Dashboard auf localhost:3006 landet — das Dreieck aus data, task und scorers ist der evalite()-Kern

evalite() nimmt drei Dinge: data (was rein geht und was rauskommen soll), task (die zu testende Funktion) und scorers (wie bewertet wird). Die Ergebnisse landen in einer lokalen SQLite-Datenbank und werden im Browser-Dashboard angezeigt.

Ohne Evals: Du aenderst einen Prompt und hoffst, dass die Antworten besser werden. Du liest manuell 5 Antworten durch, die sehen gut aus, also deployest Du. Zwei Wochen später merkst Du: Bei einer bestimmten Frage halluziniert das LLM jetzt. Keine Ahnung, seit wann.

Mit Evals: Du aenderst einen Prompt, startest evalite watch, und siehst sofort: Score von 0.82 auf 0.91 gestiegen. Oder: Score von 0.82 auf 0.65 gefallen — Regression entdeckt, bevor ein User es merkt. Messen, vergleichen, iterieren.

Evalite und die Autoevals-Bibliothek als Dev-Dependencies installieren:

Terminal-Fenster
pnpm add -D evalite vitest autoevals ai @ai-sdk/openai zod

Warum Vitest? Evalite baut auf Vitest auf — es ist eine Peer Dependency, nicht optional. Evalite nutzt Vitest’s Test Runner, Watch Mode und Reporter-Infrastruktur. Die .eval.ts Dateikonvention funktioniert analog zu .test.ts bei Vitest.

Dann ein Script in der package.json einrichten:

{
"scripts": {
"eval": "evalite",
"eval:dev": "evalite watch"
}
}

evalite watch startet den Watch-Modus — wie vitest --watch. Bei jeder Änderung an .eval.ts Dateien werden die Evals automatisch neu ausgefuehrt.

Evalite sucht nach Dateien mit der Endung .eval.ts — analog zu .test.ts bei Vitest:

src/
my-feature.ts ← Dein Code
my-feature.test.ts ← Unit Tests (Vitest)
my-feature.eval.ts ← Evals (Evalite)

Jede .eval.ts Datei enthaelt eine oder mehrere evalite() Aufrufe.

Die drei Bausteine — data, task, scorers:

import { evalite } from 'evalite';
import { Levenshtein } from 'autoevals'; // Levenshtein misst die Edit-Distanz — wie viele Zeichenaenderungen noetig sind, um vom Output zum Expected zu kommen
evalite('My First Eval', {
// 1. Test-Daten: Was geht rein, was soll rauskommen?
data: [
{ input: 'Hello', expected: 'Hello World!' },
],
// 2. Die zu testende Funktion — hier noch ohne LLM
task: async (input) => {
return input + ' World!';
},
// 3. Bewertung: Wie nah ist der Output am Expected?
scorers: [Levenshtein],
});

Ablauf:

  1. data liefert Test-Cases (Input + erwarteter Output)
  2. task wird pro Test-Case mit dem input ausgefuehrt
  3. scorers vergleichen den tatsaechlichen Output mit dem expected-Wert
  4. Ergebnisse werden gespeichert (standardmaessig in-memory; für persistente Speicherung konfigurierst Du SQLite via createSqliteStorage in einer evalite.config.ts)
  5. Dashboard zeigt Scores unter http://localhost:3006

Statt eines statischen Arrays kann data auch eine async Function sein — nuetzlich für dynamisches Laden:

evalite('Capital Cities', {
data: async () => [
{ input: 'What is the capital of France?', expected: 'Paris' },
{ input: 'What is the capital of Germany?', expected: 'Berlin' },
{ input: 'What is the capital of Japan?', expected: 'Tokyo' },
],
task: async (input) => {
// Hier kommt später Dein LLM-Call rein
return input;
},
scorers: [Levenshtein],
});

Wenn Du einen echten LLM-Call als task nutzt, wrappst Du das Modell mit traceAISDKModel. Damit erfasst Evalite alle LLM-Aufrufe (Tokens, Latenz, Kosten) im Dashboard:

import { evalite } from 'evalite';
import { traceAISDKModel } from 'evalite/ai-sdk';
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { Levenshtein } from 'autoevals';
evalite('Capital Cities', {
data: async () => [
{ input: 'What is the capital of France?', expected: 'Paris' },
{ input: 'What is the capital of Germany?', expected: 'Berlin' },
],
task: async (input) => {
const result = await generateText({
model: traceAISDKModel(openai('gpt-4o-mini')), // ← Tracing!
system: 'Answer concisely. No periods.',
prompt: input,
});
return result.text;
},
scorers: [Levenshtein],
});

traceAISDKModel ist ein Wrapper, der das AI SDK Modell um Evalite-Tracing erweitert. Im Dashboard siehst Du dann nicht nur den Score, sondern auch Token-Verbrauch und Latenz pro Test-Case.

Starte die Evals mit pnpm eval:dev und oeffne http://localhost:3006:

  • Übersicht: Alle Evals mit Durchschnitts-Score
  • Detail-Ansicht: Jeder Test-Case mit Input, Output, Expected und Score
  • Verlauf: Scores über die Zeit — siehst Du Verbesserungen oder Regressionen
  • Traces: Bei traceAISDKModel siehst Du die LLM-Aufrufe mit Token-Verbrauch

Aufgabe: Richte Evalite ein und schreibe eine einfache Eval mit dem Levenshtein Scorer.

Erstelle die Datei hello.eval.ts und fuehre sie mit pnpm eval:dev aus. Die Ergebnisse siehst Du im Dashboard unter http://localhost:3006.

hello.eval.ts
import { evalite } from 'evalite';
import { Levenshtein } from 'autoevals';
// TODO 1: Erstelle eine evalite() mit dem Namen 'Greeting Eval'
// TODO 2: Definiere data mit 3 Test-Cases:
// - input: 'Hi' → expected: 'Hi! How can I help?'
// - input: 'Hello' → expected: 'Hello! How can I help?'
// - input: 'Hey' → expected: 'Hey! How can I help?'
// TODO 3: Implementiere eine task-Funktion die den Input nimmt
// und ' How can I help?' anhaengt (mit Ausrufezeichen nach dem Input)
// TODO 4: Nutze Levenshtein als Scorer

Checkliste:

  • evalite und Levenshtein importiert
  • data mit 3 Test-Cases definiert
  • task gibt den Input mit angehaengtem Text zurück
  • Levenshtein als Scorer gesetzt
  • pnpm eval:dev zeigt Ergebnisse im Dashboard
Lösung anzeigen
hello.eval.ts
import { evalite } from 'evalite';
import { Levenshtein } from 'autoevals';
evalite('Greeting Eval', {
data: [
{ input: 'Hi', expected: 'Hi! How can I help?' },
{ input: 'Hello', expected: 'Hello! How can I help?' },
{ input: 'Hey', expected: 'Hey! How can I help?' },
],
task: async (input) => {
return `${input}! How can I help?`;
},
scorers: [Levenshtein],
});

Erwarteter Output: Alle drei Test-Cases sollten Score 1.0 erreichen — die task-Funktion produziert exakt den erwarteten Output. Im Dashboard unter localhost:3006 siehst Du “Greeting Eval” mit Durchschnitts-Score 1.0.

Erklärung: Der Levenshtein Scorer misst die Aehnlichkeit zwischen dem tatsaechlichen Output und dem erwarteten Output. Score 1.0 bedeutet identisch, Score 0.0 bedeutet komplett verschieden. In diesem Beispiel sollte jeder Test-Case einen Score von 1.0 erreichen, weil die task-Funktion exakt den erwarteten Output produziert.

prompt und system fliessen in generateText() (Level 1.3), das result.text liefert, der zusammen mit expected durch den Levenshtein Scorer zu einem Score 0-1 fuehrt

Uebung: Nutze generateText aus Level 1.3 als task-Funktion in einer Evalite-Eval. Teste, ob ein LLM Hauptstaedte korrekt benennt.

  1. Erstelle eine .eval.ts Datei mit 5 Hauptstadt-Fragen
  2. Nutze generateText mit traceAISDKModel als task
  3. System Prompt: 'Answer with only the city name. No periods, no extra text.'
  4. Scorer: Levenshtein
  5. Starte pnpm eval:dev und pruefe die Scores im Dashboard

Frage zum Nachdenken: Warum ist Levenshtein hier nicht der ideale Scorer? Was passiert, wenn das LLM “Paris, France” statt “Paris” antwortet?

Part of AI Learning — free courses from prompt to production. Jan on LinkedIn