Ihr Test am Montag war erfolgreich. Gleiche Eingabe, gleicher Code, temperature=0. Am Dienstag schlug er fehl, obwohl Sie nichts geändert hatten. Die Assertion prüfte auf eine exakte Zeichenkette, und das Modell formulierte dieselbe Antwort nur anders. Der Test ist rot, der Agent funktioniert, und Sie debuggen die Testsuite statt des Produkts.
Das ist der Preis für Tests von LLM-Ausgaben. Selbst bei temperature=0 erhalten Sie über mehrere Durchläufe hinweg nicht zuverlässig byte-identische Antworten. Dieser Leitfaden zeigt, wie Sie Assertions schreiben, die bei variierender Formulierung stabil bleiben. Er vertieft Fehlermodus drei aus unserem Leitfaden dazu, warum KI-Agenten in der Produktion scheitern.
Warum temperature=0 nicht Determinismus bedeutet
Die Temperatur steuert das Sampling des nächsten Tokens. Bei 0 wählt das Modell jeweils das wahrscheinlichste Token. Das wirkt reproduzierbar, ist es aber nicht zwingend.
Die Ursachen liegen unterhalb des Modells:
- Gleitkomma-Arithmetik auf GPUs ist nicht assoziativ.
- Unterschiedliche Reihenfolgen bei Berechnungen können minimale Rundungsabweichungen erzeugen.
- Diese Abweichungen können die Token-Rangfolge verändern.
- Anbieter bündeln Requests unterschiedlich, wechseln Hardware, aktualisieren Inferenzbibliotheken oder routen in andere Regionen.
Eine lange vLLM-Diskussion erklärt, warum selbst ein fester Seed mit temperature=0 keine bitgenaue Reproduzierbarkeit garantiert.
Determinismus ist eine Eigenschaft des gesamten Serving-Stacks, nicht nur ein Flag in Ihrer Modellanfrage.
Behandeln Sie identische Ausgabe daher nicht als Vertrag. Testen Sie stattdessen, ob die Antwort fachlich und strukturell korrekt ist.
Warum exakte String-Assertions Ihre Suite unzuverlässig machen
Die klassische Falle:
assert.equal(response, "Ihre Gesamtsumme beträgt $42.00.");
Dieser Test schlägt fehl, obwohl die Antwort korrekt sein kann:
Ihr Bestellwert liegt bei $42.00.
Ein Test, der bei korrektem Verhalten fehlschlägt, ist gefährlicher als kein Test:
- Das Team erlebt Fehlalarme.
- Tests werden wiederholt ausgeführt, bis sie zufällig grün sind.
- Echte Regressionen gehen im Rauschen unter.
- Das Vertrauen in die gesamte Testsuite sinkt.
Wir haben bereits beschrieben, was unzuverlässige Tests verursacht und warum sie sich ausbreiten. Nicht-deterministische Modellantworten sind eine besonders schnelle Quelle für Flakiness.
Die Lösung ist nicht, noch mehr Text einzufrieren. Drehen Sie die Richtung um: Testen Sie den Vertrag, nicht die Formulierung.
Auf Struktur und Bedeutung prüfen, nicht auf exakten Text
Ein Support-Agent kann eine Rückerstattung auf viele Arten bestätigen. Jede gültige Antwort sollte aber dieselben fachlichen Fakten enthalten:
- Betrag der Rückerstattung
- Bestell-ID
- Status
- erwartete Datentypen und Formate
Fragen Sie nicht:
Hat das Modell diesen Satz exakt so geschrieben?
Fragen Sie stattdessen:
Enthält die Antwort die erwartete Struktur, gültige Werte und zulässige Grenzen?
Damit überstehen Ihre Tests Umformulierungen, erkennen aber weiterhin echte Fehler wie fehlende Felder, falsche Datentypen, ungültige Payloads oder Werte außerhalb erlaubter Bereiche.
Die Antwort mit einem JSON Schema validieren
Wenn Ihr Agent strukturierte Daten zurückgibt, definieren Sie ein JSON Schema und validieren Sie jede Antwort dagegen.
Beispiel für eine Rückerstattungsantwort:
{
"type": "object",
"required": ["order_id", "status", "amount"],
"properties": {
"order_id": {
"type": "string",
"pattern": "^ORD-[0-9]+$"
},
"status": {
"type": "string",
"enum": ["refunded", "pending", "denied"]
},
"amount": {
"type": "number",
"minimum": 0
}
},
"additionalProperties": false
}
Mit JavaScript und Ajv könnte die Assertion so aussehen:
import Ajv from "ajv";
import assert from "node:assert/strict";
const ajv = new Ajv();
const validate = ajv.compile(refundSchema);
const valid = validate(agentResponse);
assert.equal(valid, true, JSON.stringify(validate.errors));
Das Schema prüft:
- erforderliche Felder,
- Datentypen,
- zulässige Enum-Werte,
- ID-Formate,
- unerwartete Zusatzfelder.
Es prüft nicht, ob der Freitext exakt gleich formuliert ist. Genau das macht es robust.
Laden Sie Ihr Antwortschema in Apidog und validieren Sie Live-Antworten dagegen. Bei einer Abweichung sehen Sie das konkrete fehlerhafte Feld statt eines langen String-Diffs.
Tool-Aufrufe auf Ziel, Methode und Payload prüfen
Wenn ein Agent ein Tool aufruft, testen Sie den Tool-Aufruf selbst, nicht den Satz, der ihn ausgelöst hat.
Prüfen Sie mindestens drei Dinge:
- Wurde das richtige Tool gewählt?
- Wurden HTTP-Methode und Ziel korrekt gesetzt?
- Entspricht die Payload dem Tool-Schema?
Beispiel für einen Buchungsagenten:
assert.equal(toolCall.method, "POST");
assert.equal(toolCall.path, "/reservations");
assert.equal(Number.isInteger(toolCall.body.guests), true);
assert.match(toolCall.body.date, /^\d{4}-\d{2}-\d{2}$/);
Noch robuster wird es mit einem Schema:
{
"type": "object",
"required": ["guests", "date"],
"properties": {
"guests": {
"type": "integer",
"minimum": 1
},
"date": {
"type": "string",
"format": "date"
}
},
"additionalProperties": false
}
Validieren Sie dabei auch, dass keine erfundenen Felder in der Payload landen. Die End-to-End-Methode zum Testen der API-Aufrufe eines Agenten behandelt das Erfassen und Prüfen solcher Tool-Schemas.
Numerische Bereiche statt exakter Werte verwenden
Für Zahlen, die ein Modell erzeugt oder weitergibt, sind Bereichsprüfungen meist besser als Gleichheit.
Ein Warenkorb-Agent berechnet beispielsweise eine Gesamtsumme. Sie kennen vielleicht nicht jeden exakten Wert über alle Steuerregeln und Versandoptionen hinweg. Sie kennen aber fachliche Grenzen:
assert.ok(response.total >= 0);
assert.ok(response.total <= cartSubtotal + maxShipping + maxTax);
Damit erkennen Sie relevante Fehler:
- negative Gesamtsummen,
- unrealistisch hohe Werte,
-
0bei einem gefüllten Warenkorb, - Werte außerhalb des Geschäftsrahmens.
Bereichsprüfungen eignen sich auch für:
- Konfidenzwerte,
- Artikelanzahlen,
- Token-Verbrauch,
- Latenzbudgets,
- Rabatte,
- abgeleitete Kennzahlen.
Wählen Sie die breiteste Grenze, die bei einem echten Fehler noch zuverlässig fehlschlägt.
Erforderliche Schlüssel prüfen und verbotene Felder ausschließen
Zwei einfache Assertions liefern besonders viel Wert:
- Felder, auf die Ihr Code angewiesen ist, müssen vorhanden und nicht
nullsein. - Felder, die niemals sichtbar sein dürfen, müssen fehlen.
Beispiel:
assert.ok(response.resolution);
assert.equal(response.resolution, null, false);
assert.equal("internal_notes" in response, false);
assert.equal("raw_prompt" in response, false);
Für eine klarere Prüfung:
assert.ok(Object.hasOwn(response, "resolution"));
assert.notEqual(response.resolution, null);
assert.ok(!Object.hasOwn(response, "internal_notes"));
assert.ok(!Object.hasOwn(response, "raw_prompt"));
Diese Prüfungen sind unabhängig von der Formulierung. Gleichzeitig schützen sie gegen Datenschutzlecks, bei denen ein Modell versehentlich interne Notizen oder Prompt-Inhalte in eine Kundenausgabe übernimmt.
Freitext mit semantischen und Schwellenwertprüfungen testen
Manchmal ist die Antwort bewusst Prosa. Dann können Sie trotzdem stabile Eigenschaften prüfen.
Beispiel:
assert.ok(responseText.includes(orderId));
assert.ok(responseText.length <= 500);
assert.ok(!blockedPhrases.some((phrase) => responseText.includes(phrase)));
Typische Freitext-Assertions:
- Enthält die Antwort die übergebene Bestellnummer?
- Bleibt sie unter einem Längenlimit?
- Enthält sie keine gesperrten Phrasen?
- Enthält sie erwartete Fakten oder Links?
- Vermeidet sie interne Daten?
Wenn Sie die semantische Nähe zu einer Referenzantwort prüfen müssen, verwenden Sie einen Embedding-Ähnlichkeitsscore mit einem Schwellenwert:
assert.ok(similarityScore >= 0.85);
Behandeln Sie das als grobes Tor, nicht als Beweis für sachliche Korrektheit. Eine semantisch ähnliche Antwort kann immer noch einen subtilen Faktenfehler enthalten. Kombinieren Sie solche Prüfungen daher mit Schema-, Feld- und Bereichs-Assertions.
Bereichs-Snapshots statt Text-Snapshots verwenden
Snapshot-Tests bleiben nützlich, wenn Sie nur stabile Teile einfrieren.
Speichern Sie beispielsweise:
- Schlüsselsatz,
- Typen,
- erlaubte Enum-Werte,
- Wertebereiche,
- erwartete Objektform.
Speichern Sie nicht den vollständigen Freitext als unveränderlichen Blob.
Ein sinnvoller Snapshot beschreibt eher:
{
"requiredKeys": ["order_id", "status", "amount"],
"statusEnum": ["refunded", "pending", "denied"],
"amountRange": {
"min": 0,
"max": 10000
}
}
Dann bricht der Test bei einer strukturellen Änderung, die geprüft werden muss — nicht bei einem Synonym.
Zustand und Gedächtnis erschweren Tests zusätzlich
Bisher galt: eine Anfrage hinein, eine Antwort heraus. Zustandsbehaftete Agenten funktionieren anders. Sie tragen Erinnerungen über mehrere Runden mit.
Die Antwort kann davon abhängen:
- welche Dokumente abgerufen wurden,
- welche Daten in früheren Runden gespeichert wurden,
- in welcher Reihenfolge Tool-Aufrufe liefen,
- wie frühere Gespräche zusammengefasst wurden.
Zwei Durchläufe derselben Konversation können deshalb auseinanderlaufen, selbst wenn die letzte Nutzereingabe identisch ist. Unser Erklärer dazu, wie das Gedächtnis von KI-Agenten funktioniert, zeigt, wo dieser Zustand lebt und wie er aufgebaut wird.
Machen Sie zustandsbehaftete Tests mit zwei Gewohnheiten beherrschbar.
1. Testzustand vor jedem Test zurücksetzen
Initialisieren Sie den Speicher vor jedem Test auf einen bekannten Zustand:
beforeEach(async () => {
await agentMemory.clear();
await agentMemory.seed({
userId: "test-user-1",
preferences: [],
reservations: []
});
});
So testen Sie nicht gleichzeitig unterschiedliche Modellausgaben und unterschiedliche Ausgangszustände.
2. Pfadunabhängige Invarianten prüfen
Prüfen Sie Eigenschaften, die unabhängig vom Gesprächspfad gelten müssen:
assert.ok(account.balance >= 0);
assert.equal(reservations.length, 1);
Beispiele für Invarianten:
- Ein Guthaben darf nie negativ werden.
- Eine abgeschlossene Flugbuchung erzeugt genau eine Reservierung.
- Eine Bestellung darf nicht zweimal belastet werden.
- Ein Ticket muss am Ende einen gültigen Status besitzen.
Solche Assertions überleben zustandsbehaftete, nicht-deterministische Abläufe deutlich besser als exakte Gesprächs-Snapshots.
Abhängigkeiten mocken, damit Tests wiederholbar bleiben
Sie können zuverlässige Tests nicht allein auf Live-APIs Dritter aufbauen. Externe APIs können:
- rate-limiten,
- Daten verändern,
- temporär ausfallen,
- unterschiedliche Antworten liefern.
Damit fügen sie neben dem Modell eine zweite Zufallsquelle hinzu.
Mocken Sie daher die Abhängigkeiten, die nicht Gegenstand des aktuellen Tests sind:
const paymentApiMock = {
charge: async () => ({
id: "pay_test_123",
status: "approved",
amount: 42.0
})
};
const searchApiMock = {
search: async () => [
{ id: "doc-1", title: "Rückerstattungsrichtlinie" },
{ id: "doc-2", title: "Bestellstatus" },
{ id: "doc-3", title: "Versandinformationen" }
]
};
Damit liefern Zahlungs- und Such-API bei jedem Testlauf dieselben kontrollierten Antworten. Sie können außerdem gezielt Randfälle erzwingen:
const declinedPaymentMock = {
charge: async () => ({
id: "pay_test_456",
status: "declined",
reason: "insufficient_funds"
})
};
Dann prüfen Sie, ob der Agent korrekt reagiert:
assert.equal(agentResponse.status, "payment_failed");
assert.ok(agentResponse.message);
Richten Sie Apidog auf die Abhängigkeiten Ihres Agenten aus, um Mocks mit stabilen, kontrollierbaren Response-Bodies einzurichten. Kombinieren Sie diese Mocks mit Schema-Assertions. Das gehört zur breiteren Praxis des Agentic AI Testing, in der Mocking und Assertions zusammenarbeiten.
Wo Apidog passt — und wo nicht
Ordnen Sie das Tool korrekt ein: Apidog ist eine Plattform für API-Design, API-Tests und Mocking.
Es ist nicht:
- ein Agent-Framework,
- ein Modell-Host,
- eine Agent-Laufzeitumgebung,
- eine Orchestrierungsplattform,
- eine Evaluations- oder Observability-Plattform.
Apidog baut Ihren Agenten nicht, führt ihn nicht aus und bewertet nicht seine Argumentation.
Es passt an die API-Grenze, an der Ihr Agent kommuniziert:
- Antwortverträge testen: Validieren Sie API-Antworten des Agenten gegen Schemas, Bereiche, erforderliche und verbotene Schlüssel.
- Abhängigkeiten mocken: Stellen Sie reproduzierbare Antworten für externe APIs bereit.
Das ist die Schnittstelle, die Apidog abdeckt: Verträge über Requests und Responses — nicht das Modell, das sie erzeugt.
Testen Sie den Vertrag, nicht die Formulierung
Nicht-Determinismus ist kein Fehler, den Sie mit temperature=0 wegkonfigurieren können. Er ist eine Eigenschaft von LLM-Serving in der Praxis.
Ersetzen Sie fragile Assertions wie diese:
assert.equal(responseText, expectedText);
durch Assertions auf stabile Eigenschaften:
assert.ok(validateSchema(response));
assert.ok(response.total >= 0);
assert.ok(response.total <= maxAllowedTotal);
assert.ok(Object.hasOwn(response, "order_id"));
assert.ok(!Object.hasOwn(response, "internal_notes"));
Teams mit zuverlässigen Agenten testen, was konstant bleibt:
- Schema,
- Struktur,
- Datentypen,
- Wertebereiche,
- erforderliche Felder,
- verbotene Felder,
- Tool-Call-Payloads,
- pfadunabhängige Invarianten.
Die Formulierung darf variieren. Ihre Testsuite bleibt dadurch grün, wenn sich nur Text bewegt — und wird rot, wenn wirklich etwas kaputtgeht.
Nehmen Sie diese Woche eine unzuverlässige String-Assertion aus Ihrer Suite. Schreiben Sie sie als Schema- und Bereichsprüfung um. Verwenden Sie Apidog, um Agent-Antworten gegen Verträge zu validieren und Abhängigkeiten für wiederholbare Tests zu mocken.
Top comments (0)