Zum Inhalt springen
EN DE

Challenge 9.3: Comparing Outputs

Wie findest Du heraus, welches Modell die beste Antwort gibt — ohne jede Antwort selbst zu lesen? Und was, wenn die “beste” Antwort von der Aufgabe abhaengt?

Overview: Same Prompt zu Model A, B, C (alle Prozess in Gruppe), alle zu Judge LLM, dann zu Best Output

Derselbe Prompt geht parallel an mehrere Modelle. Alle Ergebnisse werden gesammelt und von einem Judge-LLM bewertet. Das beste Ergebnis wird zurueckgegeben.

Ohne systematischen Vergleich: Du wahlst ein Modell nach Bauchgefuehl oder Marketing. “Claude ist besser” oder “GPT-4o ist schneller” — ohne Daten. Du weisst nicht, ob ein billigeres Modell für Deinen Use Case reichen würde.

Mit systematischem Vergleich: Du testest objektiv, welches Modell für Deine spezifische Aufgabe die beste Antwort liefert. Du triffst datengetriebene Modell-Entscheidungen. Du findest heraus, wo ein Flash-Modell ausreicht und wo Du ein Pro-Modell brauchst.

Der erste Schritt — denselben Prompt an mehrere Modelle senden und alle Ergebnisse sammeln:

import { generateText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
import { openai } from '@ai-sdk/openai';
import { google } from '@ai-sdk/google';
const prompt = 'Erklaere den Unterschied zwischen REST und GraphQL in 3 Saetzen.';
const models = [
{ name: 'Claude Sonnet', model: anthropic('claude-sonnet-4-5-20250514') },
{ name: 'GPT-4o', model: openai('gpt-4o') },
{ name: 'Gemini Flash', model: google('gemini-2.5-flash') },
];
// Alle Modelle parallel aufrufen
const results = await Promise.all(
models.map(async ({ name, model }) => {
const start = Date.now();
const result = await generateText({ model, prompt });
return {
name,
text: result.text,
tokens: result.usage.totalTokens,
durationMs: Date.now() - start,
};
}),
);
// Ergebnisse ausgeben
for (const r of results) {
console.log(`\n--- ${r.name} (${r.tokens} Tokens, ${r.durationMs}ms) ---`);
console.log(r.text);
}

Promise.all sendet alle Requests gleichzeitig. Die Gesamtdauer ist die des langsamsten Modells, nicht die Summe aller. Du bekommst für jeden Call Text, Token-Verbrauch und Dauer.

Bevor Du ein LLM als Judge einsetzt, kannst Du einfache Metriken automatisch vergleichen:

function compareBasicMetrics(
results: Array<{ name: string; text: string; tokens: number; durationMs: number }>
) {
console.log('\n=== Vergleich ===\n');
console.log('| Modell | Zeichen | Tokens | Dauer | Tokens/Sec |');
console.log('|--------|---------|--------|-------|------------|');
for (const r of results) {
const tokensPerSec = Math.round(r.tokens / (r.durationMs / 1000));
console.log(
`| ${r.name} | ${r.text.length} | ${r.tokens} | ${r.durationMs}ms | ${tokensPerSec} |`,
);
}
// Kuerzeste Antwort (oft praeziser)
const shortest = results.reduce((a, b) => (a.text.length < b.text.length ? a : b));
console.log(`\nKuerzeste Antwort: ${shortest.name}`);
// Schnellste Antwort
const fastest = results.reduce((a, b) => (a.durationMs < b.durationMs ? a : b));
console.log(`Schnellste Antwort: ${fastest.name}`);
// Guenstigste Antwort
const cheapest = results.reduce((a, b) => (a.tokens < b.tokens ? a : b));
console.log(`Guenstigste Antwort: ${cheapest.name}`);
}
compareBasicMetrics(results);

Einfache Metriken — Laenge, Geschwindigkeit, Token-Verbrauch — geben einen ersten Überblick. Aber sie sagen nichts über die Qualität der Antwort.

Für Qualitaetsbewertung: Ein starkes LLM bewertet die Antworten der anderen. Das Muster kennst Du aus Level 6.3:

import { generateText, Output } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
import { z } from 'zod';
const JudgmentSchema = z.object({
rankings: z.array(z.object({
model: z.string(),
score: z.number().min(1).max(10),
reasoning: z.string(),
})),
winner: z.string(),
summary: z.string(),
});
async function judgeOutputs(
prompt: string,
results: Array<{ name: string; text: string }>,
) {
const formattedOutputs = results
.map((r, i) => `<output model="${r.name}">\n${r.text}\n</output>`)
.join('\n\n');
const judgment = await generateText({
model: anthropic('claude-sonnet-4-5-20250514'), // ← Starkes Modell als Judge
system: `Du bist ein objektiver Qualitaets-Reviewer.
Bewerte die folgenden LLM-Outputs nach:
1. Korrektheit — Sind die Fakten richtig?
2. Praezision — Ist die Antwort auf den Punkt?
3. Verstaendlichkeit — Ist die Erklärung klar?
Vergib Scores von 1-10 und begruende Deine Bewertung.`,
prompt: `<task>${prompt}</task>\n\n${formattedOutputs}`,
output: Output.object({ schema: JudgmentSchema }),
});
return judgment.output;
}
// Nutzung
const judgment = await judgeOutputs(prompt, results);
console.log('\n=== LLM-as-a-Judge ===\n');
console.log(`Gewinner: ${judgment.winner}`);
console.log(`Zusammenfassung: ${judgment.summary}`);
for (const r of judgment.rankings) {
console.log(`\n${r.model}: ${r.score}/10`);
console.log(` ${r.reasoning}`);
}

Der Judge bekommt die Originalaufgabe und alle Outputs. Durch das Zod Schema ist die Bewertung strukturiert — Score, Begruendung und Gewinner. Wichtig: Der Judge sollte ein starkes Modell sein, idealerweise nicht eines der bewerteten Modelle.

Hinweis: In diesem Beispiel nutzen wir Sonnet als Judge und als eines der bewerteten Modelle — in Production wuerdest Du ein staerkeres Modell (z.B. Opus) als Judge einsetzen, das nicht selbst bewertet wird.

Für wiederholbare Vergleiche nutzt Du ein Eval-Framework wie Evalite aus Level 6:

import { evalite } from 'evalite';
import { generateText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
import { openai } from '@ai-sdk/openai';
import { google } from '@ai-sdk/google';
// Eval für jedes Modell separat ausführen
const modelsToCompare = [
{ name: 'claude-sonnet', model: anthropic('claude-sonnet-4-5-20250514') },
{ name: 'gpt-4o', model: openai('gpt-4o') },
{ name: 'gemini-flash', model: google('gemini-2.5-flash') },
];
for (const { name, model } of modelsToCompare) {
evalite(`compare-${name}`, {
data: async () => [
{ input: 'Erklaere Promises in 2 Saetzen.', expected: 'Promise' },
{ input: 'Was ist der Unterschied zwischen let und const?', expected: 'const' },
{ input: 'Erklaere async/await.', expected: 'await' },
],
task: async (input) => {
const result = await generateText({ model, prompt: input });
return result.text;
},
scorers: [
// Pruefe ob der erwartete Begriff vorkommt
(output, expected) => {
const contains = output.toLowerCase().includes(expected.toLowerCase());
return { score: contains ? 1 : 0, name: 'contains-keyword' };
},
// Pruefe Praezision (kuerzere Antworten = hoeher)
(output) => {
const score = Math.max(0, 1 - output.length / 2000);
return { score, name: 'conciseness' };
},
],
});
}

Mit Evalite bekommst Du reproduzierbare Ergebnisse über mehrere Testfaelle hinweg. Du kannst Modelle systematisch auf Deinen spezifischen Use Case benchmarken — nicht nach allgemeinen Benchmarks, sondern nach Deinen Anforderungen.

Aufgabe: Rufe 3 Modelle parallel auf, vergleiche die Ergebnisse nach Laenge und einem einfachen Keyword-Scorer.

Erstelle compare-outputs.ts und fuehre aus mit npx tsx compare-outputs.ts.

import { generateText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
import { openai } from '@ai-sdk/openai';
import { google } from '@ai-sdk/google';
const prompt = 'Erklaere den Unterschied zwischen var, let und const in JavaScript in maximal 3 Saetzen.';
const expectedKeywords = ['var', 'let', 'const', 'scope', 'block'];
// TODO 1: Definiere ein Array mit 3 Modellen (name + model)
// TODO 2: Rufe alle 3 parallel auf mit Promise.all
// TODO 3: Vergleiche die Ergebnisse:
// - Wie viele expectedKeywords kommen in jeder Antwort vor?
// - Wie lang ist jede Antwort (Zeichen)?
// - Wie viele Tokens hat jede Antwort verbraucht?
// TODO 4: Bestimme den "Gewinner" basierend auf:
// - Meiste Keywords = hoechste Qualität
// - Bei Gleichstand: kuerzere Antwort gewinnt

Checkliste:

  • 3 Modelle parallel aufgerufen mit Promise.all
  • Keyword-Count für jede Antwort berechnet
  • Laenge und Token-Verbrauch verglichen
  • Gewinner bestimmt und begruendet
  • Ergebnisse uebersichtlich formatiert ausgegeben
Lösung anzeigen
import { generateText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
import { openai } from '@ai-sdk/openai';
import { google } from '@ai-sdk/google';
const prompt =
'Erklaere den Unterschied zwischen var, let und const in JavaScript in maximal 3 Saetzen.';
const expectedKeywords = ['var', 'let', 'const', 'scope', 'block'];
const models = [
{ name: 'Claude Sonnet', model: anthropic('claude-sonnet-4-5-20250514') },
{ name: 'GPT-4o', model: openai('gpt-4o') },
{ name: 'Gemini Flash', model: google('gemini-2.5-flash') },
];
// Parallel aufrufen
const results = await Promise.all(
models.map(async ({ name, model }) => {
const start = Date.now();
const result = await generateText({ model, prompt });
const text = result.text;
// Keyword-Scoring
const keywordCount = expectedKeywords.filter(kw =>
text.toLowerCase().includes(kw.toLowerCase()),
).length;
return {
name,
text,
tokens: result.usage.totalTokens,
durationMs: Date.now() - start,
keywordCount,
charCount: text.length,
};
}),
);
// Ergebnisse ausgeben
console.log('=== Vergleich ===\n');
console.log('| Modell | Keywords | Zeichen | Tokens | Dauer |');
console.log('|--------|----------|---------|--------|-------|');
for (const r of results) {
console.log(
`| ${r.name} | ${r.keywordCount}/${expectedKeywords.length} | ${r.charCount} | ${r.tokens} | ${r.durationMs}ms |`,
);
}
// Gewinner bestimmen
const sorted = [...results].sort((a, b) => {
if (b.keywordCount !== a.keywordCount) return b.keywordCount - a.keywordCount;
return a.charCount - b.charCount; // Bei Gleichstand: kuerzere Antwort gewinnt
});
console.log(`\nGewinner: ${sorted[0].name} (${sorted[0].keywordCount} Keywords, ${sorted[0].charCount} Zeichen)`);
// Antworten anzeigen
for (const r of results) {
console.log(`\n--- ${r.name} ---`);
console.log(r.text);
}

Erklärung: Promise.all sendet alle Requests parallel — die Gesamtdauer ist die des langsamsten Modells. Der Keyword-Scorer zaehlt, wie viele erwartete Begriffe in der Antwort vorkommen. Bei Gleichstand gewinnt die kuerzere Antwort, weil Praezision in der Aufgabe gefordert war (“maximal 3 Saetze”).

Erwarteter Output (ungefaehr):
=== Vergleich ===
| Modell | Keywords | Zeichen | Tokens | Dauer |
|--------|----------|---------|--------|-------|
| Claude Sonnet | 5/5 | 312 | 198 | 1.2s |
| GPT-4o | 4/5 | 287 | 176 | 0.9s |
| Gemini Flash | 5/5 | 256 | 143 | 0.6s |
Gewinner: Gemini Flash (5 Keywords, 256 Zeichen)
Combine: Prompt zu Parallel Calls, dann zu Result A, B, C, alle zu Evalite Scorer, dann zu Vergleichs-Report

Uebung: Kombiniere Comparing Outputs mit dem Eval-Framework aus Level 6. Statt eines einmaligen Vergleichs:

  1. Definiere 5 Test-Prompts für Deinen Use Case (z.B. TypeScript-Erklärungen)
  2. Definiere 2 Scorer — einen Keyword-Scorer und einen Laengen-Scorer
  3. Laufe alle 3 Modelle über alle 5 Prompts mit Evalite
  4. Vergleiche die Ergebnisse — welches Modell gewinnt im Durchschnitt?

Das Ergebnis ist ein datengetriebener Report, der Dir sagt: “Für TypeScript-Erklärungen ist Modell X am besten.” Das ist die Grundlage für den Model Router aus Challenge 9.2.

Optional Stretch Goal: Implementiere den LLM-as-a-Judge als Evalite-Scorer. Nutze ein starkes Modell das die Outputs auf einer Skala von 1-10 bewertet und eine Begruendung liefert.

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