Testing eines LLM-Chatbots in einem MCP-System
(Wenn Sie Videoinhalte bevorzugen, sehen Sie sich bitte die kurze Videozusammenfassung dieses Artikels unten an.)
Wichtige Erkenntnisse
- Determinismus gilt nicht mehr: Das Testen von LLM-Chatbots verschiebt sich von exakten Gleichheitsprüfungen hin zu probabilistischen, semantischen Validierungen, bei denen mehrere korrekte Antworten auf dieselbe Eingabe existieren können.
- Die Architektur bestimmt die Testkomplexität: MCP-Orchestrierung, RAG-Pipelines, Tool-Aufrufe und Streaming-Antworten erzeugen mehrere potenzielle Fehlerquellen, wodurch die Ursachenanalyse zwangsläufig mehrschichtig wird.
- Die Validierung muss mehrdimensional sein: Die Kombination aus Must-have-, Must-not- und semantischen Ähnlichkeitsprüfungen ist entscheidend, um Flexibilität und Kontrolle auszubalancieren und Halluzinationsrisiken zu reduzieren.
- Testergebnisse sind kontext- und konfigurationsabhängig: Modellversion, Prompt-Design, Inference-Parameter und Gesprächshistorie beeinflussen die Ergebnisse, weshalb kontinuierliches Tuning und iterative Anpassung der Tests erforderlich sind.
Einführung: Der Paradigmenwechsel in der Qualitätssicherung für LLM-basierte Systeme
Das Testen eines LLM-Chatbots innerhalb eines MCP-basierten Systems unterscheidet sich grundlegend vom Testen klassischer Software. Traditionelle Systeme sind deterministisch: Derselbe Input führt stets zum selben Output. Bei einer typischen REST-API liefert eine Anfrage entweder die erwartete JSON-Payload oder eben nicht. Die Assertions sind dabei klar und eindeutig.
Ein Chatbot, der auf einem großen Sprachmodell basiert, verhält sich anders. Das Testen von Outputs eines Large Language Models (LLM) erfordert einen fundamentalen Paradigmenwechsel. Die Annahmen, die Softwaretests seit Jahrzehnten geprägt haben — Determinismus, exakte Reproduzierbarkeit und binäre Zustandsvalidierung — brechen im Kontext generativer KI zusammen.
Um die Komplexität des Testens solcher Anwendungen zu verstehen, müssen wir zunächst die zugrunde liegende Architektur betrachten, erklären, warum klassische Assertions versagen, und die einzigartigen, kontextabhängigen Fallstricke und Besonderheiten analysieren.
Überblick über die Systemarchitektur
Ein moderner LLM-basierter Chatbot ist im Kern ein komplexes, mehrschichtiges verteiltes System, in dem jede Komponente neue Variablen in die Testgleichung einbringt.
Wenn ein Benutzer einen Prompt absendet, durchläuft dieser mehrere kritische serverseitige Komponenten, bevor eine Antwort erzeugt wird. Zunächst wird die Eingabe häufig von einem Orchestrator oder Reasoning-Engine verarbeitet. In Enterprise-Umgebungen wie unserer kommt hier typischerweise das Model Context Protocol (MCP) zum Einsatz. MCP ermöglicht es dem LLM, sicher mit externen Datenquellen und internen Tools zu interagieren, ohne Integrationen fest zu verdrahten.
Parallel dazu nutzt das System ein Retrieval-Augmented-Generation-Muster. Bevor das LLM eine Antwort generiert, wird die Benutzeranfrage eingebettet und an eine Vektordatenbank gesendet, um semantisch relevante Kontexte abzurufen. Dieser abgerufene Kontext wird zusammen mit Systemanweisungen und dem Chatverlauf dynamisch in einen versteckten Meta-Prompt eingefügt. Erst dann wird die Nutzlast an die Inference-Engine (die Model-Serving-Schicht) gesendet. Schließlich generiert das LLM Token sequenziell, die über eine persistente Verbindung, etwa WebSockets mittels SignalR, an den Client gestreamt werden.
Herausforderung
Diese architektonischen Entscheidungen wirken sich direkt auf die Testbarkeit aus. Wenn man den „Chatbot“ testet, testet man gleichzeitig die Retrieval-Mechanismen, die Orchestrierungsschicht und das generative Modell selbst. Deshalb haben Fehler in einer solchen Umgebung selten nur eine einzige Ursache.
Wenn der Chatbot eine falsche Antwort liefert, kann die Ursache unter anderem sein:
- die Retrieval-Komponente hat irrelevante Dokumente zurückgegeben
- der Prompt ist für die jeweiligen Use Cases nicht optimal optimiert
- das richtige Dokument wurde zwar gefunden, aber vom Modell ignoriert
- das Modell hat Informationen erfunden, die nicht im Kontext vorhanden waren
- der Chatbot hat kein Tool aufgerufen, um eine bestimmte Aktion auszuführen oder die benötigten Daten abzurufen
- das Tool hat einen Fehler zurückgegeben, der nicht an das Modell weitergegeben wurde
- das Kontextfenster hat relevante Informationen abgeschnitten
Der Chatbot arbeitet zudem innerhalb einer Konversation. Eine Antwort kann von vorherigen Gesprächsverläufen, abgerufenen Dokumenten, System-Prompts und Tool-Ausgaben abhängen. Das Testen eines einzelnen Prompts in Isolation reproduziert daher nicht immer das Verhalten, das in realen Gesprächen auftritt.
Der geschäftliche Kontext erhöht den Druck zusätzlich. In diesem System erscheint der Chatbot auf der Website eines Unternehmens und beantwortet Fragen potenzieller Kunden zur Erfahrung und zu Projekten des Unternehmens. Wenn der Bot Projekte erfindet oder eine Anfrage falsch versteht, geht der Schaden über bloß falsche Informationen hinaus. Er kann aktiv eine erfolgreiche Lead-Bearbeitung simulieren, indem er bestätigt, dass eine Kontaktanfrage oder ein Formular an das Vertriebsteam gesendet wurde, obwohl tatsächlich kein nachgelagerter Prozess ausgelöst wurde. Das Ergebnis ist ein unterbrochener Conversion-Flow: Der Nutzer glaubt, dass eine Übergabe an einen menschlichen Agenten stattgefunden hat, während kein Lead erfasst wird, keine Benachrichtigung ausgelöst wird und kein Follow-up jemals erfolgt!
Aus diesem Grund erforderte das Testing eine Kombination aus klassischen QA-Techniken und Bewertungsmethoden, die speziell für LLM-Systeme entwickelt wurden.
Grundlegende Unterschiede beim Testen von LLM-Output vs. deterministischen Systemen
CKlassisches Softwaretesten basiert auf Determinismus: Gegeben Zustand A und Input B erwartet man, dass das System Output C zurückliefert. Wenn stattdessen D zurückkommt, wird ein Bug gemeldet.
LLMs sind inhärent probabilistisch. Sie berechnen eine Wahrscheinlichkeitsverteilung über die nächsten möglichen Tokens in einer Sequenz. Dadurch können identische Eingaben unterschiedliche Ausgaben erzeugen. Diese Nicht-Deterministik zerstört klassische Regression-Testing-Workflows. Wenn man eine exakte Gleichheitsprüfung schreibt, die erwartet, dass der Bot sagt: „Die Anwendung ist eine webbasierte SaaS-Plattform“, und der Bot antwortet stattdessen: „Die Software ist eine online bereitgestellte Plattform im SaaS-Modell“, der deterministische Test schlägt fehl.
Hier entsteht das Problem der semantischen Korrektheit. Die Ausgabe eines LLM kann grammatikalisch anders sein, andere Begriffe verwenden und völlig unterschiedlich strukturiert sein, und dennoch zu 100 % faktisch korrekt und gültig bleiben.
Aus diesem Grund brechen traditionelle Bug-Klassifikation und Reproduzierbarkeits-Workflows zusammen. Ein QA Engineer kann schwer ein „Steps to reproduce“-Ticket für eine LLM-Halluzination erstellen, da dieselben Schritte fünf Minuten später bereits eine perfekte Antwort liefern können.
Konfigurationsabhängige Natur des Systemoutputs
Selbst wenn man fortgeschrittene semantische Tests einsetzt, müssen QA-Teams ein Minenfeld konfigurationsabhängiger Variablen navigieren, das Test-Suites besonders fragil macht.
Zunächst ist die Testgültigkeit eng an spezifische Modellversionen gekoppelt. Unterschiedliche Modelle haben jeweils ihre eigenen Besonderheiten. Eine Test-Suite wird dadurch zu einer Momentaufnahme des erwarteten Verhaltens für ein bestimmtes Modell zu einem bestimmten Zeitpunkt.
Zweitens wirken Inferenz-Parameter wie Temperature (die die Zufälligkeit steuert) und Top-P (die die Vielfalt des Vokabulars kontrolliert) als versteckte Testvariablen. Eine Suite, die bei Temperature 0.2 noch relativ stabil ist, kann bei Temperature 0.7 deutlich weniger deterministisch werden.
Darüber hinaus sind diese Tests extrem empfindlich gegenüber Systemkonfigurationen. Kleine Anpassungen am System-Prompt – selbst scheinbar harmlose Formulierungsänderungen – können die nachgelagerten Outputs drastisch verändern.
Dies führt zu einer dauerhaften Herausforderung: der Unterscheidung zwischen Systemregressionen und erwarteter Varianz. Wenn ein Test fehlschlägt, muss das Team klären, ob das System tatsächlich kaputt ist (z. B. die RAG-Datenbank ist ausgefallen) oder ob das Modell lediglich eine statistisch unwahrscheinliche, aber dennoch akzeptable Antwortvariation erzeugt hat, die der semantische Evaluator nicht korrekt abgedeckt hat.
Schließlich führen Multi-Turn-Konversationen zu einer starken Zustandsverschmutzung. Da das Modell auf dem Gesprächsverlauf basiert, kann eine ungenaue Antwort in Turn eins den Kontext des LLM für Turn drei verfälschen. Das Testen von Multi-Turn-Flows erfordert daher das Isolieren des Zustands, das sorgfältige Management des Konversationskontexts und eine kontinuierliche Re-Validierung der gesamten Suite, während sich das System weiterentwickelt.
Damit erfasst ein Test nur ein eingeschränktes Beobachtungsfenster: eine einzelne Verhaltensschnittebene, erzeugt durch eine bestimmte Modellversion, Decoding-Konfiguration, System-Prompt, Eingabeprompt sowie Retrieval- und Konversationszustand. Sie stellt eine einzelne Trajektorie durch einen wesentlich größeren probabilistischen Raum möglicher Ausgaben dar.
Funktionales Testing
Zunächst wurde eine Liste von Use Cases erstellt. Das funktionale Testing begann mit den wichtigsten Benutzerszenarien, die auf der Website erwartet werden.
Typische Fragen waren unter anderem:
- Erfahrung in bestimmten Branchen
- verwendete Technologien für Backend– oder Frontend-Entwicklung
- Beispiele früherer Projekte
- grobe Projektschätzungen
- Kontaktaufnahme mit dem Vertriebsteam
Besucher fragen in der Regel nach der Erfahrung des Unternehmens, den Technologien und bisherigen Projekten. Einige Gespräche führen auch zu Kontaktanfragen.
Später wurde diese Liste zu Testfällen erweitert. Jeder Testfall ist strukturell wie ein klassischer Test aufgebaut, enthält jedoch spezifische Elemente, die für KI-basierte Systeme charakteristisch sind. Es gibt Abschnitte, die beschreiben, was zwingend enthalten sein muss, was in der Antwort angemessen ist und was unter keinen Umständen enthalten sein darf.
Eine Nutzerfrage wie „Haben Sie bereits Healthcare-Plattformen entwickelt?“ sollte eine Antwort liefern, die auf Portfolio-Daten aus der Wissensdatenbank basiert. Die Antwort sollte dabei reale Projekte erwähnen, falls solche existieren, und das Erfinden von Kunden vermeiden.
Hier folgt die Geschichte, wie wir ein maßgeschneidertes, durchgängiges Python-basiertes Test-Harness entwickelt haben, das für die End-to-End-Validierung von Streaming-Chatbot-Antworten konzipiert ist.
Die Herausforderung: WebSockets und nicht-deterministische Ausgaben
Der Chatbot streamt Tokens sequenziell über SignalR via WebSockets. Wir konnten nicht einfach einen HTTP-POST ausführen und eine JSON-Antwort auslesen. Daher haben wir ein modulares Python-Framework entwickelt, das in einen SignalR-Client, eine Evaluierungs-Engine und einen schlanken Test-Runner aufgeteilt ist.
Es wurde unter Berücksichtigung des „Separation of Concerns“-Prinzips entworfen: Testdaten (JSON-basierte Testfälle und Validierungsregeln) sind von der Transportschicht (SignalR/WebSockets), der Interpretationslogik (NLP-Analyse) und dem Ausführungs-Runner entkoppelt.
Erstellung des SignalR-Clients
Der erste Schritt bestand darin, die Kommunikation herzustellen. Da unser Chatbot über SignalR arbeitet, haben wir uns für die leichtgewichtige Python-Bibliothek websocket-client entschieden, anstatt schwere Browser-Automation-Tools wie Playwright oder Selenium einzubinden, da unser Ziel war, die API-/Backend-Logik direkt zu testen (Integration/E2E-Ebene ohne UI-Overhead).
SignalR hat dabei seine eigenen Besonderheiten. Es erfordert einen spezifischen JSON-Handshake ({“protocol”: “json”, “version”: 1}) und hängt am Ende jeder Payload ein sehr spezifisches Terminierungszeichen (\x1e) an.
Unser Client-Skript stellt die WebSocket-Verbindung her, verwaltet den Handshake und geht anschließend in eine while True-Listening-Loop. Da das LLM seine Antwort in kleinen Daten-Chunks streamt, parst der Client eingehende ReceiveMessage-Events, konkateniert die Text-Chunks und wartet, bis er vom Server ein isComplete: True-Flag erhält. An diesem Punkt schließt er den Socket sauber und übergibt den vollständigen String an unseren Evaluator.
Drei-Schichten-Validierungsstrategie
Nachdem wir den vollständigen Textstring vom Chatbot erhalten hatten, mussten wir entscheiden, ob er „korrekt“ ist. Wir haben ein dreistufiges Quality-Gate implementiert:
Auch wenn LLMs ihre Formulierungen variieren, gibt es oft harte geschäftliche Anforderungen daran, was zwingend erwähnt werden muss. Mithilfe eines JSON-basierten Testdaten-Ansatzes definieren wir must_have-Arrays. Um Flakiness zu vermeiden, haben wir eine Synonym-Engine gebaut.
Zum Beispiel: Wenn der Test verlangt, dass der Bot erwähnt, dass die Anwendung „web-based“ ist, mappen unsere Testdaten „web-based“ auf [“SaaS”, “online platform”, “web application”, “AJAX-based”]. Wenn der Bot einen dieser Begriffe verwendet, gilt die Assertion als bestanden.
Ebenso wichtig wie das, was der Bot sagt, ist das, was er nicht sagen darf. KI-Modelle sind anfällig für Halluzinationen. Wenn ein User nach einer Legacy-Accounting-Web-App fragt, sollte der Bot keine Features erfinden. Wir übergeben dem Framework ein must_not-Array mit Begriffen wie „mobile app“, „blockchain“ oder „AI analytics“. Wenn solche Begriffe erkannt werden, schlägt der Test sofort fehl.
Dieser Mechanismus bildet eine grundlegende Validierungsschicht. In den meisten Fällen liefert er stabile und vorhersagbare Ergebnisse, da er auf expliziten lexikalischen Einschränkungen basiert.
Diese Stabilität ist jedoch weiterhin oberflächlich. Zum Beispiel bedeutet das Fehlen eines Begriffs nicht automatisch Korrektheit. Wir mussten die Test-Suite mehrfach ausführen, um instabile Ergebnisse zu identifizieren, und das must_have-Set iterativ erweitern, bis die Ergebnisse ein zuverlässiges Niveau für die Interpretation erreichten.
Die schwächste Komponente in diesem Setup ist der must_not-Block selbst. Er geht davon aus, dass unerwünschtes Verhalten vollständig enumeriert werden kann. In der Praxis ist das unmöglich.
Gleichzeitig muss berücksichtigt werden, dass selbst wenn alle Keywords vorhanden sind, die Satzstruktur vollständig falsch sein kann.
Um dieses Problem zu lösen, haben wir sentence-transformers integriert, basierend auf torch und scikit-learn. Wir verwenden das Modell all-MiniLM-L6-v2 — ein schnelles, leichtgewichtiges NLP-Modell, ideal zur Berechnung von Sentence-Embeddings.
Beim Testlauf nehmen wir die generierte Antwort des Bots und eine vordefinierte expected_answer aus unseren JSON-Testfällen (im Grunde direkt aus der Datenquelle). Beide Texte werden in hochdimensionale Vektor-Embeddings umgewandelt, und anschließend berechnen wir die Cosine Similarity. Wenn der Wert unter 0.70 fällt (70 %, ein empirisch bestimmter Wert nach mehreren Iterationen), schlägt der Test fehl. Dadurch kann der Bot völlig unterschiedliche Satzstrukturen und Wortwahl verwenden und dennoch bestehen, solange die grundlegende semantische Bedeutung erhalten bleibt.
Ein Test gilt nur dann als bestanden, wenn alle drei Ebenen erfüllt sind.
Entkopplung von Logik und Daten: Die JSON-Testfallstruktur
Eine der wichtigsten architektonischen Entscheidungen, die wir früh getroffen haben, war die strikte Trennung der Testausführungslogik von den Testdaten und Validierungsregeln. Anstatt Test-Szenarien direkt in Python-Skripte zu hardcoden, haben wir alles in eine strukturierte JSON-Datei ausgelagert.
Dadurch entstand eine saubere Separation of Concerns: Der Python-Runner übernimmt das Wie (Transport und Interpretation), während die JSON-Datei das Was definiert (die Eingaben und die Quality Gates).
Jeder Testfall ist ein in sich geschlossenes JSON-Objekt, das als umfassender Vertrag für eine spezifische Chat-Interaktion dient.
Vorteile und Skalierbarkeit dieses Ansatzes:
- Zero-Code-Onboarding: Der wichtigste Vorteil ist die Zugänglichkeit. Business-Analysten, Product Manager oder Junior-QA-Engineers können Testfälle schreiben, ändern und prüfen, ohne WebSockets, Python oder Sentence Transformers verstehen zu müssen. Sie passen einfach die JSON-Datei an.
- Horizontale Skalierbarkeit ohne Grenzen: Da der Runner über ein standardisiertes JSON-Array iteriert, erfordert das Skalieren der Test-Suite von 10 auf 10.000 Testfälle keinerlei architektonische Änderungen am zugrunde liegenden Python-Code.
- Versionskontrolle-freundlich: JSON-Dateien lassen sich in Git sehr gut diffen. Wir können exakt nachverfolgen, wann ein Synonym hinzugefügt wurde oder wann ein expected_answer aktualisiert wurde, um ein neues Produktfeature abzubilden.
Test-Runner und Reporting
Wir haben einen eigenen CLI-Runner entwickelt, der die test_cases.json-Datei parst und die Test-Suite ausführt.
Zur Unterstützung beim Debugging haben wir colorama sowie reguläre Ausdrücke verwendet, um HTML-Tags zu entfernen und erkannte Keywords sowie Synonyme direkt in der Terminal-Ausgabe dynamisch in hellgrün hervorzuheben. Dadurch können QA-Engineers auf einen Blick visuell nachvollziehen, warum ein Test bestanden oder fehlgeschlagen ist.
Abschließend werden Ausführungsmetriken (Test-ID, Pass/Fail-Status und Antwortdauer in Sekunden) kontinuierlich in eine Ergebnis-Logdatei geschrieben, wodurch wir Latenzzeiten und Regressionsmetriken über die Zeit hinweg nachverfolgen können.
Ergebnisse
Das Testen KI-gestützter Systeme erfordert ein Denken jenseits traditioneller binärer Assertions.
Der Einsatz dieses maßgeschneiderten Frameworks hat die Art und Weise, wie unser Team an AI-Qualitätssicherung herangeht, grundlegend verändert. Wir haben uns von der aufwendigen manuellen Testarbeit entfernt, die viele frühe KI-Projekte belastet, und sie durch eine stärker deterministische, datengetriebene Pipeline ersetzt.
Durch die Kombination aus strenger Keyword-Validierung und semantischer Bewertung haben wir ein Sicherheitsnetz geschaffen, das sowohl flexibel als auch rigoros ist. Dies bildet die Grundlage für die Erhebung harter Metriken wie Latenz, Similarity-Scores und Halluzinations-Erkennungsraten.
Was kommt als Nächstes?
Während die aktuelle Architektur Single-Turn-Anfragen sehr gut verarbeitet, liegt die nächste Herausforderung in zustandsbehafteten Multi-Turn-Konversationen. Wir können das Framework so weiterentwickeln, dass es mit langen kontextuellen Zuständen arbeitet und bewertet, wie gut sich der Bot an Fakten erinnert, die drei oder vier Nachrichten zuvor etabliert wurden. Darüber hinaus prüfen wir die Integration von dynamischen „LLM-as-a-Judge“-Mechanismen, bei denen ein zweites Modell als finaler Schiedsrichter für Chatbot-Antworten fungiert.
Das System kann außerdem um Load- und Concurrency-Testing erweitert werden. Durch das Parallelisieren der Test-Suite über mehrere unabhängige Chat-Sessions hinweg können wir reale Nutzungsmuster simulieren und das Systemverhalten unter gleichzeitigen Anfragen evaluieren. Dadurch lassen sich Performance-Charakteristika wie Antwortlatenz, Durchsatz und Stabilität messen.
Das Testen von KI erfordert es, die Komfortzone absoluter Deterministik zu verlassen. Indem man Frameworks baut, die ebenso intelligent und anpassungsfähig sind wie die Systeme, die sie evaluieren, kann QA aufhören, hinterherzulaufen, und beginnt, die Entwicklung zuverlässiger KI-Produkte aktiv mitzugestalten.
Verwendete Technologien: Python, WebSockets, SignalR, PyTorch, Sentence Transformers (NLP), Scikit-learn, JSON, Regex.

Das Testen von LLM-basierten Systemen erfordert mehr als klassische QA-Ansätze. Eine strukturierte Validierungsstrategie hilft dabei, Halluzinationen zu erkennen und die Zuverlässigkeit von Antworten in produktiven KI-Anwendungen zu verbessern.
Siarhei Nestsiarenka, Chief QA


