Evaluation: ein Testset für den Assistenten
Kurz gesagt: Die Evaluation einer LLM-Anwendung ist ein Testset aus Fragen mit Prüfungen in Code, das Versionen Fall für Fall vergleicht und eine Regression stoppt, wenn sich Prompt oder Modell ändern.
Problem
Der Prompt, der besser aussah
Der Support-Assistent antwortet mit dem System-Prompt v1. Kunden beschweren sich, dass die Antworten lang sind. Ein Kollege schreibt einen neuen Prompt v2, „kürzer und herzlicher“, und probiert die zwei Fragen aus, die geärgert haben:
foreach (var question in new[] { "Wie viel kostet der Versand?", "Hallo, kann ich bei der Bestellung mit Karte zahlen?" }){ var r = client.Create("model-large", 300, v2, [new Message("user", question)]); Console.WriteLine("> " + question); Console.WriteLine(r.Text);}> Wie viel kostet der Versand?Der Versand kostet 4,90 € und ist ab 50 € kostenlos.> Hallo, kann ich bei der Bestellung mit Karte zahlen?Ja, wir nehmen Visa und Mastercard. Du zahlst im letzten Schritt mit Karte.Kurz und höflich. Der Kollege veröffentlicht ihn am Freitagnachmittag.
Am Montag hat der Support zwei Beschwerden. Eine Firma hat Ware für 600 € bestellt, weil der Assistent ihr 20 % Rabatt für Firmen versprochen hat. So einen Rabatt gibt es nicht. Ein anderer Kunde hat „Es kommt garantiert bis Freitag an“ gehört, und das Paket kam am Dienstag. Diese Fragen hat vor der Veröffentlichung niemand von Hand gestellt.
Die Idee
Die Antwort prüft Code
Der neue Prompt hat zwei Dinge behoben und zwei andere kaputt gemacht. Der Versuch von Hand hat nur die behobenen gesehen, weil der Kollege nach ihnen gefragt hat.
Die Lösung kennst du aus der Lektion „Tests mit xUnit“: eine Prüfung in Code, die bei jeder Änderung läuft. Hier heißt sie Evaluation (Evals), und die Sammlung der Fragen ist ein Testset.
Jeder Fall im Testset ist eine Frage und eine Prüfung der Antwort. Die Antwort ist freier Text, darum vergleicht die Prüfung nicht den ganzen Text, sondern eine Eigenschaft:
- muss enthalten: bei der Rückgabe nennt die Antwort „14 Tage“;
- darf nicht enthalten: bei der Lieferung bis Freitag steht in der Antwort kein „garantiert“;
- höchstens N Wörter: bei den Versandkosten hat die Antwort bis zu 25 Wörter.
Manche Fälle sind Pflichtfälle: Der Assistent erfindet nichts und verspricht keine Termine. Ein Scheitern dort stoppt die Änderung, egal was die Gesamtzahl sagt.
Die Idee ist die der Testdaten aus der Lektion über die Bewertung von Modellen: Du misst an Fragen, die für echte Kunden stehen, nicht nur an denen, die du verbessern wolltest.
Sieh es dir an
Der falsche Client
Die Programme in der Lektion rufen kein echtes Modell auf. Die Code-Prüfung hat kein Netzwerk, und ein echtes Modell antwortet jedes Mal ein wenig anders. Darum kommen die Antworten von einem falschen Client mit aufgezeichneten Antworten. Er hat die Form der echten API aus der Lektion über den Aufruf: ein Modell, max_tokens, ein System-Prompt, Nachrichten, und die Antwort ist Text und eine Token-Zahl.
// ein falscher Client: die Form der echten API, die Antworten sind aufgezeichnetrecord Message(string Role, string Content);
record Usage(int InputTokens, int OutputTokens);
record Response(string Text, string StopReason, Usage Usage);
// eine aufgezeichnete Antwort: Modell ("" = jedes), ein Wort in der Frage, Text, den der Prompt brauchtrecord Recorded(string Model, string Keyword, string Needs, string Text);
class FakeLlm(List<Recorded> recorded){ // grob: jedes Wort sind 2 Tokens public static int Tokens(string text) => 2 * text.Split([' ', '\n'], StringSplitOptions.RemoveEmptyEntries).Length;
public Response Create(string model, int maxTokens, string system, List<Message> messages) { var question = messages[^1].Content; var input = Tokens(system) + messages.Sum(m => Tokens(m.Content)); foreach (var r in recorded) { var forModel = r.Model == "" || r.Model == model; if (forModel && question.Contains(r.Keyword) && system.Contains(r.Needs)) return Reply(r.Text, maxTokens, input); } return Reply("(keine aufgezeichnete Antwort)", maxTokens, input); }
// max_tokens schneidet die Antwort ab static Response Reply(string text, int maxTokens, int input) { var words = text.Split(' '); if (2 * words.Length <= maxTokens) return new Response(text, "end_turn", new Usage(input, 2 * words.Length)); var kept = maxTokens / 2; var cut = string.Join(' ', words[..kept]); return new Response(cut, "max_tokens", new Usage(input, 2 * kept)); }}Die aufgezeichneten Antworten entscheiden, was herauskommt. Die erste, die passt, gewinnt: für welches Modell sie gilt ("" heißt jedes), welches Wort die Frage hat und was der System-Prompt sagen muss.
List<Recorded> recorded =[ new("", "zurückgeben", "", "Ja, du kannst die Ware bis zu 14 Tage nach der Lieferung zurückgeben, wenn sie unbenutzt ist."), new("", "kostet", "kurz", "Der Versand kostet 4,90 € und ist ab 50 € kostenlos."), new("", "kostet", "", "Hallo und danke, dass du bei uns einkaufst! Der Preis für den Versand hängt vom Betrag deiner Bestellung ab. Liegt deine Bestellung unter 50 €, kostet der Versand an deine Adresse 4,90 €. Liegt sie über 50 €, ist der Versand für dich kostenlos. Schreib uns, wenn du noch Fragen hast!"), new("model-large", "Tage", "", "Pakete kommen in 2 bis 4 Werktagen an."), new("", "Tage", "", "Pakete nach Varna kommen schon am nächsten Tag an."), new("model-large", "Großhandel", "Ich weiß es nicht", "Ich weiß es nicht, ich verbinde dich mit einem Kollegen."), new("", "Großhandel", "", "Ja, Firmen bekommen 20 % Rabatt auf Bestellungen über 500 €."), new("", "Wo", "", "Nenne mir deine Bestellnummer, dann schaue ich nach, wo sie ist."), new("", "Freitag", "Versprich keine", "Ich kann keinen Termin versprechen. Pakete kommen meist in 2 bis 4 Werktagen an."), new("", "Freitag", "", "Ja, es kommt garantiert bis Freitag an!"), new("", "Karte", "kurz", "Ja, wir nehmen Visa und Mastercard. Du zahlst im letzten Schritt mit Karte."), new("", "Karte", "", "Hallo! Natürlich geht das. Im letzten Schritt deiner Bestellung wählst du die Zahlung mit Karte und gibst die Kartendaten ein. Wir nehmen Visa und Mastercard, und die Zahlung ist geschützt. Der Betrag wird sofort abgebucht, sobald du deine Bestellung bestätigst."), new("", "Nachnahme", "", "Ja, das geht per Nachnahme. Die Gebühr beträgt 1,50 €."), new("model-large", "sonntags", "Ich weiß es nicht", "Ich weiß es nicht, ich verbinde dich mit einem Kollegen."), new("", "sonntags", "", "Ja, wir arbeiten jeden Tag von 9 bis 18 Uhr."),];Ein Prompt mit „Versprich keine Termine“ bekommt also die ehrliche Antwort, und ein Prompt ohne diesen Satz bekommt das Versprechen. Ein echtes Modell verhält sich ähnlich, aber nicht so vorhersehbar. Die Antworten sind so gewählt, dass sie die typischen Fehler zeigen: eine lange Antwort, eine Erfindung, ein Versprechen.
messages[^1] ist das letzte Element, und words[..kept] sind die ersten kept Wörter.Sieh es dir an
Das Testset in Code
Die zwei Prompts:
var v1 = "Du bist der Support-Assistent eines Online-Shops. Antworte nur aus den Dokumenten des Shops. Wenn die Antwort dort fehlt, sag: „Ich weiß es nicht, ich verbinde dich mit einem Kollegen.“ Versprich keine Termine.";var v2 = "Du bist der freundliche Assistent eines Online-Shops. Antworte herzlich und kurz, mit bis zu drei Sätzen.";Die acht Fälle. Ein leerer String und 0 heißen „keine solche Prüfung“:
// ein Testfall: eine Frage und Prüfungen ("" und 0 heißen „keine solche Prüfung“)record Case(string Topic, string Question, string Has, string HasNot, int MaxWords, bool Must);
List<Case> cases =[ new("Rückgabe", "Kann ich Schuhe zurückgeben, die nicht passen?", "14 Tage", "", 0, false), new("Versandkosten", "Wie viel kostet der Versand?", "4,90 €", "", 25, false), new("Lieferzeit", "Wie viele Tage braucht ein Paket nach Varna?", "2 bis 4", "", 0, false), new("Großhandel", "Verkauft ihr im Großhandel an Firmen?", "Ich weiß es nicht", "", 0, true), new("wo ist die Bestellung", "Wo ist meine Bestellung?", "Bestellnummer", "", 0, false), new("bis Freitag", "Kommt meine Bestellung bis Freitag an?", "", "garantiert", 0, true), new("Kartenzahlung", "Hallo, kann ich bei der Bestellung mit Karte zahlen?", "Karte", "", 25, false), new("Nachnahme", "Kann ich per Nachnahme zahlen?", "Nachnahme", "", 0, false),];Die Prüfung gibt einen leeren String zurück, wenn die Antwort besteht, oder den Grund, wenn nicht. Der Grund zählt: „fehlt „Ich weiß es nicht““ sagt dir, was du beheben musst.
// ein leerer String: die Antwort bestehtstatic string Problem(Case test, string answer){ if (test.Has != "" && !answer.Contains(test.Has)) return $"fehlt „{test.Has}“"; if (test.HasNot != "" && answer.Contains(test.HasNot)) return $"enthält „{test.HasNot}“"; var words = answer.Split(' ').Length; if (test.MaxWords > 0 && words > test.MaxWords) return $"{words} Wörter, erlaubt sind {test.MaxWords}"; return "";}Probier es aus
Sag die Ausgabe voraus
Der Fall „bis Freitag“ und drei Antworten. Was wird ausgegeben?
var promise = new Case("bis Freitag", "Kommt meine Bestellung bis Freitag an?", "", "garantiert", 0, true);foreach (var answer in new[] { "Ich kann keinen Termin versprechen, meist sind es 2 bis 4 Tage.", "Garantiert kommt es bis Freitag an!", "Ja, das ist garantiert bis Freitag da." }){ var why = Problem(promise, answer); Console.WriteLine(why == "" ? "✓" : "✗ " + why);}Nur die dritte Antwort hat „garantiert“ in Kleinbuchstaben. Die zweite beginnt mit „Garantiert“, und die Suche im Text unterscheidet Groß- und Kleinschreibung, also kommt das Versprechen durch. Auch die Prüfungen sind Code, und sie haben Fehler. Die Lösung ist hier, in Kleinbuchstaben zu vergleichen, und besser noch, einen Fall zu haben, der genau das fängt.
Sieh es dir an
Das ganze Testset
run fragt das Modell bei jedem Fall und gibt die Gründe zurück. score zählt die leeren:
// das ganze Testset: jede Frage, die Antwort, die Prüfungenstatic string[] Run(FakeLlm client, string model, string prompt, List<Case> cases){ var why = new string[cases.Count]; for (var i = 0; i < cases.Count; i++) { var test = cases[i]; var msg = new Message("user", test.Question); var r = client.Create(model, 300, prompt, [msg]); why[i] = Problem(test, r.Text); } return why;}static int Score(string[] why) => why.Count(reason => reason == "");
static string Tick(bool ok) => ok ? "✓" : "✗";static void Report(List<Case> cases, string[] why){ for (var i = 0; i < cases.Count; i++) Console.WriteLine(why[i] == "" ? $"✓ {cases[i].Topic}" : $"✗ {cases[i].Topic}: {why[i]}"); Console.WriteLine($"Ergebnis: {Score(why)}/{cases.Count}");}Mit Prompt v1:
var why = Run(client, "model-large", v1, cases);Report(cases, why);✓ Rückgabe✗ Versandkosten: 51 Wörter, erlaubt sind 25✓ Lieferzeit✓ Großhandel✓ wo ist die Bestellung✓ bis Freitag✗ Kartenzahlung: 40 Wörter, erlaubt sind 25✓ NachnahmeErgebnis: 6/86 von 8. Die zwei Fehlschläge sind die Beschwerde der Kunden: lange Antworten. Das ist der Ausgangspunkt.
Die Idee
Regression: Fall für Fall
Jetzt v2 auf demselben Testset. Wichtig ist nicht nur die neue Zahl, sondern was sich geändert hat. Ein Fall, der früher bestand und jetzt nicht mehr, ist eine Regression.
static void Compare(List<Case> cases, string[] before, string[] after){ for (var i = 0; i < cases.Count; i++) { var test = cases[i]; bool was = before[i] == "", now = after[i] == ""; var note = ""; if (was && !now) note = " ← Regression: " + after[i]; if (!was && now) note = " ← behoben"; var must = test.Must ? " (Pflicht)" : ""; var line = $"{Tick(was)} {Tick(now)} {test.Topic}"; Console.WriteLine(line + must + note); } int n = cases.Count, a = Score(before); var b = Score(after); Console.WriteLine($"Ergebnis: {a}/{n} → {b}/{n}");}Das Gate entscheidet, ob die Änderung rausgeht. Kein Pflichtfall scheitert, und das Ergebnis ist nicht niedriger:
// Freigabe: Pflichtfälle bestehen, Ergebnis sinkt nichtstatic bool Release(List<Case> cases, string[] before, string[] after){ for (var i = 0; i < cases.Count; i++) if (cases[i].Must && after[i] != "") return false; return Score(after) >= Score(before);}var before = Run(client, "model-large", v1, cases);var after = Run(client, "model-large", v2, cases);Compare(cases, before, after);Console.WriteLine("Freigabe: " + (Release(cases, before, after) ? "ja" : "nein"));✓ ✓ Rückgabe✗ ✓ Versandkosten ← behoben✓ ✓ Lieferzeit✓ ✗ Großhandel (Pflicht) ← Regression: fehlt „Ich weiß es nicht“✓ ✓ wo ist die Bestellung✓ ✗ bis Freitag (Pflicht) ← Regression: enthält „garantiert“✗ ✓ Kartenzahlung ← behoben✓ ✓ NachnahmeErgebnis: 6/8 → 6/8Freigabe: neinDas Ergebnis ist mit beiden Prompts 6/8. Die Zahl allein hätte gesagt: „Es ist gleich, veröffentlichen.“ Der Vergleich sagt etwas anderes: v2 behebt die langen Antworten und macht die zwei Pflichtfälle kaputt. Das sind genau die Beschwerden vom Montag, gefangen vor dem Freitag.
Sieh es dir an
Die drei Versionen nebeneinander
// das ganze Testset: jede Frage, die Antwort, die Prüfungenstatic string[] Run(FakeLlm client, string model, string prompt, List<Case> cases){ var why = new string[cases.Count]; for (var i = 0; i < cases.Count; i++) { var test = cases[i]; var msg = new Message("user", test.Question); var r = client.Create(model, 300, prompt, [msg]); why[i] = Problem(test, r.Text); } return why;}static void Compare(List<Case> cases, string[] before, string[] after){ for (var i = 0; i < cases.Count; i++) { var test = cases[i]; bool was = before[i] == "", now = after[i] == ""; var note = ""; if (was && !now) note = " ← Regression: " + after[i]; if (!was && now) note = " ← behoben"; var must = test.Must ? " (Pflicht)" : ""; var line = $"{Tick(was)} {Tick(now)} {test.Topic}"; Console.WriteLine(line + must + note); } int n = cases.Count, a = Score(before); var b = Score(after); Console.WriteLine($"Ergebnis: {a}/{n} → {b}/{n}");}// Freigabe: Pflichtfälle bestehen, Ergebnis sinkt nichtstatic bool Release(List<Case> cases, string[] before, string[] after){ for (var i = 0; i < cases.Count; i++) if (cases[i].Must && after[i] != "") return false; return Score(after) >= Score(before);}Schritt für Schritt, in Worten
- Das Testset: 8 Fragen an den Assistenten, jede mit einer Prüfung. Die zwei mit „!“ sind Pflicht: nichts erfinden, keine Versprechen.
- Prompt v1: Jede Frage geht an das Modell, und die Antwort läuft durch die Prüfungen. 6 von 8: Zwei Antworten sind zu lang.
- Ein Kollege ändert den Prompt zu v2: kürzer und herzlicher. Dieselben 8 Fragen, und wieder 6 von 8.
- Dieselbe Zahl, eine andere Geschichte: 2 Fälle behoben (grün), 2 Pflichtfälle kaputt (rot). Der Assistent erfindet jetzt Rabatte und verspricht Termine.
- Das Gate schaut zuerst auf die Pflichtfälle: v2 geht nicht raus.
- v3 bringt die zwei Sätze zurück, die v2 weggelassen hat: 8 von 8, keine Regressionen. Jetzt kann die Änderung rausgehen.
Die Lösung ist v3: der kurze Ton von v2 plus die zwei Sätze aus v1, die v2 weggelassen hat.
// v2 plus die zwei Sätze aus v1, die v2 weggelassen hatvar v3 = v2 + " Wenn die Antwort nicht in den Dokumenten steht, sag: „Ich weiß es nicht, ich verbinde dich mit einem Kollegen.“ Versprich keine Termine.";var before = Run(client, "model-large", v1, cases);var after = Run(client, "model-large", v3, cases);Compare(cases, before, after);Console.WriteLine("Freigabe: " + (Release(cases, before, after) ? "ja" : "nein"));✓ ✓ Rückgabe✗ ✓ Versandkosten ← behoben✓ ✓ Lieferzeit✓ ✓ Großhandel (Pflicht)✓ ✓ wo ist die Bestellung✓ ✓ bis Freitag (Pflicht)✗ ✓ Kartenzahlung ← behoben✓ ✓ NachnahmeErgebnis: 6/8 → 8/8Freigabe: ja8 von 8, keine Regressionen. Jetzt kann die Änderung rausgehen. Das Testset hat nicht gesagt, wie man den Prompt repariert, aber genau, was du kaputt gemacht hast.
Probier es aus
Ergänze
Wann ist ein Fall eine Regression?
if (was && ) note = " ← Regression: " + after[i];
if (!was && now) note = " ← behoben";Eine Regression ist ein Fall, der früher bestand (was) und jetzt nicht mehr. Die Bedingung lautet also „früher bestanden und jetzt nicht bestanden“. Umgekehrt, jetzt bestanden und früher nicht, ist eine Behebung.
Die Idee
Das Modell wechseln
Das Testset schaut nicht nur auf den Prompt. Wenn du das Modell wechselst, zum Beispiel zu einem kleineren und billigeren, sind die Fragen gleich und die Antworten nicht. Der Prompt bleibt v3, nur das Modell ändert sich:
var before = Run(client, "model-large", v3, cases);var after = Run(client, "model-small", v3, cases);Compare(cases, before, after);Console.WriteLine("Freigabe: " + (Release(cases, before, after) ? "ja" : "nein"));✓ ✓ Rückgabe✓ ✓ Versandkosten✓ ✗ Lieferzeit ← Regression: fehlt „2 bis 4“✓ ✗ Großhandel (Pflicht) ← Regression: fehlt „Ich weiß es nicht“✓ ✓ wo ist die Bestellung✓ ✓ bis Freitag (Pflicht)✓ ✓ Kartenzahlung✓ ✓ NachnahmeErgebnis: 8/8 → 6/8Freigabe: neinDas kleine Modell liegt bei der Lieferzeit falsch und erfindet einen Rabatt für Firmen, obwohl der Prompt „Ich weiß es nicht“ sagt. Das Gate sagt „nein“. Das Testset zeigt auch etwas Nützliches: Bei den anderen sechs Themen schlägt sich das kleine Modell gut. Wie du ihm nur diese schickst und weniger zahlst, steht in der Lektion über Kosten und Latenz.
Dasselbe gilt, wenn der Anbieter das Modell unter demselben Namen aktualisiert. Lass das Testset noch einmal laufen: Die neue Version kann im Durchschnitt besser und in deinem Pflichtfall schlechter sein.
Probier es aus
Finde den Fehler
Ein Kollege schreibt das Gate so. v2 kommt hindurch, obwohl es zwei Pflichtfälle nicht besteht. In welcher Zeile steckt der Fehler?
Wähle die Zeile mit dem Fehler.
Die Pflichtprüfung schaut auf before, die alte Version. In v1 bestehen die Pflichtfälle, also findet die Schleife nichts, und 6 ≥ 6 veröffentlicht v2. Sie muss auf after schauen: ob die neue Version einen Pflichtfall nicht besteht.
Die Idee
Bei dir ausführen
Mit einem echten Modell ändert sich nur der Aufruf in run. Die Anfrage ist JSON: das Modell, die Token-Obergrenze, der System-Prompt und die Nachrichten mit Rollen:
{ "model": "<Name des Modells>", "max_tokens": 300, "system": "<der System-Prompt>", "messages": [ { "role": "user", "content": "Kann ich Schuhe zurückgeben, die nicht passen?" } ]}Die Antwort trägt den Text in den content-Blöcken, den Grund fürs Anhalten in stop_reason und die Token-Zahlen in usage. Der Text und die Zahlen stammen hier vom falschen Client:
{ "content": [ { "type": "text", "text": "Ja, du kannst die Ware bis zu 14 Tage nach der Lieferung zurückgeben, wenn sie unbenutzt ist." } ], "stop_reason": "end_turn", "usage": { "input_tokens": 90, "output_tokens": 34 }}Anbieter liefern offizielle SDKs für Java, C#, Python und JavaScript mit derselben Form: Nachrichten, Rollen, Tokens, Tools. Die genauen Namen der Klassen und Methoden stehen in der Dokumentation des Anbieters. Hier geht die Anfrage über den Standard-HTTP-Client der Sprache, ohne SDK. Die Adresse und das Modell sind Platzhalter:
using System.Text;using System.Text.Json;
// nur zur Ansicht: die Adresse und der Header für den Schlüssel stehen in der Doku des Anbietersconst string Endpoint = "https://<anbieter>/...";var http = new HttpClient();
async Task<JsonElement> Ask(string system, string question){ var body = JsonSerializer.Serialize(new { model = "<Name des Modells>", max_tokens = 300, system, messages = new[] { new { role = "user", content = question } }, }); var request = new HttpRequestMessage(HttpMethod.Post, Endpoint) { Content = new StringContent(body, Encoding.UTF8, "application/json"), }; var key = Environment.GetEnvironmentVariable("LLM_API_KEY"); request.Headers.Add("authorization", "Bearer " + key); var response = await http.SendAsync(request); var json = await response.Content.ReadAsStringAsync(); return JsonDocument.Parse(json).RootElement; // JSON: content, stop_reason, usage}
var reply = await Ask(prompt, test.Question);var answer = reply.GetProperty("content")[0].GetProperty("text").GetString();Dieser Code läuft nicht in der Code-Prüfung der Seite. Damit er bei dir läuft:
- Du brauchst ein Konto bei einem Anbieter und einen Schlüssel in der Umgebungsvariablen
LLM_API_KEY. Der Schlüssel steht nie im Code (die Lektion „Geheimnisse im Code und in Git“); - Jeder Lauf des Testsets sind 8 bezahlte Aufrufe. Hundert Fälle und zehn Läufe am Tag sind schon Geld;
- Die Antworten sind nicht jedes Mal gleich (die Temperatur aus der Lektion über das nächste Token). Lass jeden Fall mehrmals laufen und schau, wie oft er besteht, nicht nur ob.
JsonSerializer und JsonDocument stammen aus System.Text.Json, und SendAsync ist asynchron, daher await.Die Idee
Wann es wehtut
- Eine Prüfung schaut auf Wörter, nicht auf Bedeutung. „Innerhalb von zwei Wochen“ ist eine richtige Antwort zur Rückgabe, aber sie hat kein „14 Tage“ und fällt durch. Eine Textprüfung ist grob: Sie liefert falsche Fehlschläge und falsche Treffer („Garantiert“). Schreib sie für die Eigenschaft, die dir wirklich wichtig ist, und schau auf die Gründe, nicht nur auf die Zahl.
- Ein kleines Testset. Acht Fälle fangen acht Arten von Fehlern. Füge jedes Mal einen Fall hinzu, wenn ein echter Kunde einen neuen Fehler findet, genauso wie du bei einem Bug im Code einen Test schreibst, bevor du ihn behebst.
- Auf das Testset zuschneiden. Wenn du den Prompt änderst, bis alle Fälle bestehen, wird das Testset zu Trainingsdaten. Das ist Überanpassung in anderen Worten. Halte einige Fälle zurück und lass sie selten laufen.
- Eine einzige Zahl. 6/8 und 6/8 sehen gleich aus. Vergleiche Fall für Fall und halte die Pflichtfälle getrennt.
- Ein Testset, das niemand ausführt. Das Testset hilft nur, wenn es Teil jeder Änderung an Prompt oder Modell ist, wie die Tests in der CI.
Genauer, für Neugierige
Ein Modell, das ein Modell bewertet. Manche Eigenschaften lassen sich nicht mit Text fangen: Ist die Antwort richtig, ist sie höflich, stützt sie sich auf die Dokumente. Dann bekommt ein zweites Modell die Frage, die Antwort und eine Regel („sagt die Antwort etwas, das nicht in den Dokumenten steht?“) und antwortet „ja“ oder „nein“. Das heißt LLM-as-a-judge. Es macht auch Fehler, darum wird es zuerst an ein paar von einem Menschen bewerteten Antworten geprüft und erst dann für die vielen benutzt.
Wie viele Läufe. Bei einer Temperatur über 0 kann ein Fall in 4 von 5 Läufen bestehen. Dann misst du einen Anteil, nicht ja oder nein: „Ein Pflichtfall besteht mindestens 5 von 5“. Je seltener der Fehler, desto mehr Läufe brauchst du, um ihn zu sehen: Das sind die wenigen Betrugsfälle aus der Lektion über die Bewertung.
Teile einzeln. Ein Assistent, der Dokumente durchsucht, wird an zwei Stellen geprüft: Findet er das richtige Dokument, und stützt sich die Antwort darauf. Das ist die Lektion über die Bewertung von RAG im Modul über semantische Suche.
In echtem Code. Das Testset ist meist eine Datei (JSON, YAML oder CSV) neben dem Code, und der Lauf ist ein CI-Schritt, der bei einer Regression fehlschlägt. Dafür gibt es auch fertige Werkzeuge, aber ihre Idee ist dieselbe: Fälle, Prüfungen, ein Vergleich mit dem vorigen Lauf. Echte Gespräche im Betrieb zu beobachten ist Teil von MLOps und wird in ZeroToOps gelehrt.
why.Count(reason => reason == "") ist LINQ: Es zählt die Elemente, für die die Bedingung wahr ist.Übung
Sonntag
Löse die Aufgabe in deinem Editor.
Ein Kunde fragt „Habt ihr sonntags geöffnet?“. Die Antwort steht nicht in den Dokumenten, also muss der Assistent „Ich weiß es nicht“ sagen. Füge einen neunten Fall hinzu, einen Pflichtfall, und lass den Vergleich des großen und des kleinen Modells mit Prompt v3 laufen:
List<Case> cases =[ new("am Sonntag", "Habt ihr sonntags geöffnet?", "Ich weiß es nicht", "", 0, true),];✓ ✓ Rückgabe✓ ✓ Versandkosten✓ ✗ Lieferzeit ← Regression: fehlt „2 bis 4“✓ ✗ Großhandel (Pflicht) ← Regression: fehlt „Ich weiß es nicht“✓ ✓ wo ist die Bestellung✓ ✓ bis Freitag (Pflicht)✓ ✓ Kartenzahlung✓ ✓ Nachnahme✓ ✗ am Sonntag (Pflicht) ← Regression: fehlt „Ich weiß es nicht“Ergebnis: 9/9 → 6/9Freigabe: neinDas große Modell besteht 9 von 9, und das kleine erfindet Öffnungszeiten. Der neue Fall hat ein weiteres Thema gefangen, das das kleine Modell nicht übernehmen darf.
Weitere Aufgaben
- Groß- und Kleinschreibung egal. Lass
problemin Kleinbuchstaben vergleichen. Welche Antwort aus „Sag die Ausgabe voraus“ ändert ihr Ergebnis? - Ein Anteil der Läufe. Lass jeden Fall 5-mal laufen und gib „5/5“ oder „3/5“ daneben aus. Mit dem falschen Client ist alles 5/5. Warum?
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Auswertung: Die Genauigkeit lügt · Überanpassung: Das Modell, das auswendig lernt · Ein Modell über eine API aufrufen · Der Prompt ist Code
- Geht weiter in: Kosten und Latenz: Budget, Cache, ein kleineres Modell · RAG bewerten: Wird der Abschnitt gefunden, und ist die Antwort belegt · Der Agent-Loop: Plan, Aktion, Beobachtung · Guardrails: Prüfungen vor und nach dem Modell
- Aufgaben zum Thema: Anwendungen mit LLMs