Idee in 30 Sekunden
Tool-Mocking und Fault Injection ermöglichen es, API-Fehler kontrolliert nachzubauen und zu prüfen, wie ein Agent damit umgeht, ohne echtes Netzwerk und ohne nicht-deterministisches Rauschen.
Der Hauptwert: timeout, 5xx oder kaputte Antworten werden gezielt reproduziert, und retry, fallback sowie stop reason werden überprüft.
Problem
Ohne mocks und fault injection sieht das Team meist nur Happy-Path:
- Tool antwortet schnell;
- Antwort ist valide;
- Agent beendet den Run ohne Fehler.
In Production ist das selten. Tools können timeouts, partielle Ausfälle, leere Felder oder instabile latency liefern.
Ohne separate Fehlertests führt das oft zu:
- unvorhersehbaren Ausfällen in kritischen Szenarien;
- endlosen Wiederholungen von Aufrufen;
- teuren und verrauschten Incidents, die schwer reproduzierbar sind.
Wann einsetzen
Dieser Ansatz ist nötig, wenn ein Agent über externe Tools arbeitet:
- Zahlungs-API, CRM, Suche, Backend-Services;
- Tools mit retry/backoff;
- Szenarien, in denen korrekter
stop_reasonwichtig ist; - Szenarien mit fallback (zum Beispiel Backup-Tool oder sichere Antwort).
Wenn ein Tool-Fehler lokal modellierbar ist, ist das ein guter Kandidat für einen fault-injection-Test.
Umsetzung
In der Praxis gilt eine einfache Regel: ein Fehlertyp, ein Test, kontrollierte Bedingungen. Die Beispiele unten sind schematisch und nicht an ein konkretes Framework gebunden.
Wie es in einem Test funktioniert
Kurzer Testzyklus mit fault injection
- Test case - ein Verhalten zur Prüfung.
- Mock tool - Input/Output-Vertrag fixieren.
- Inject fault - konkreten Fehler einspielen (
timeout,5xx,bad_payload). - Run - konkreten Agenten-Schritt ausführen.
- Assertions - retry, fallback,
stop_reasonund Fehlerformat prüfen.
1. Vertrag des Mock-Tools fixieren
class FakePaymentsAPI:
def __init__(self, mode: str = "ok"):
self.mode = mode
def refund(self, order_id: str):
if self.mode == "ok":
return {"status": "approved", "order_id": order_id}
if self.mode == "timeout":
raise TimeoutError("payments_timeout")
if self.mode == "http_500":
raise RuntimeError("payments_500")
return {"status": None}
Der Mock sollte den echten Tool-Vertrag möglichst genau abbilden. Sonst erzeugen Tests eine falsche Sicherheit.
2. Fehler kontrolliert injizieren
def test_timeout_fault_is_injected():
payments = FakePaymentsAPI(mode="timeout")
agent = Agent(payments_api=payments)
result = agent.handle_refund("order-8472")
assert result.stop_reason in {"tool_error_handled", "fallback_used"}
Der Fehler muss explizit und wiederholbar sein: derselbe Test soll immer dasselbe Fehlerprofil reproduzieren.
3. Retry und fallback prüfen
def test_retry_then_fallback():
payments = FlakyPaymentsAPI(fail_times=2, then="timeout")
backup = FakeBackupTool()
agent = Agent(payments_api=payments, backup_tool=backup, max_retries=2)
result = agent.handle_refund("order-9001")
assert payments.calls == 2
assert result.selected_tool == "backup_tool"
assert result.stop_reason == "fallback_used"
Wichtig ist nicht nur, dass ein Fehler auftritt, sondern auch die Recovery-Policy danach.
Für retry-Flows sollte nicht nur die Anzahl Versuche geprüft werden, sondern auch die Bedingungen, unter denen das System stoppt und zu fallback oder fail wechselt.
Bei Tools mit Side Effects ist wichtig zu prüfen, dass retry keine doppelten Operationen erzeugt.
4. Fehlerstruktur fixieren
def test_error_envelope_is_stable():
payments = FakePaymentsAPI(mode="http_500")
agent = Agent(payments_api=payments)
result = agent.handle_refund("order-1122")
assert result.error["code"] == "tool_error"
assert result.error["tool"] == "payments_api"
assert result.error["retryable"] is True
Ein stabiles Fehlerformat vereinfacht Debugging, Alerts und Regression-Checks.
5. Solche Tests in CI ausführen
Diese Tests sollten in jedem PR über den Standard-pytest-Schritt in CI laufen, wenn Änderungen Tool-Logik, retries oder fallback-Regeln betreffen.
Typische Fehler
Mock passt nicht zum echten Vertrag
Test läuft durch, aber in Production fällt der Agent wegen anderer Feldstruktur oder anderem Fehlercode aus.
Typische Ursache: Mock liefert vereinfachte Payloads, die der realen API nicht entsprechen.
Nur Happy-Path prüfen
Tests enthalten nur "erfolgreiche Antwort", aber kein timeout, 5xx und keine invalide Payload.
Typische Ursache: keine verpflichtende Liste von Fehlerprofilen pro kritischem Tool.
Zufällige fault injection
Derselbe Test besteht mal und fällt mal.
Typische Ursache: zufällige Fehler ohne festen Seed oder instabile Timeouts.
Keine Prüfung von stop_reason und error shape
Das Team prüft nur finalen Antworttext, während Recovery-Logik ungetestet bleibt.
Typische Ursache: fehlende strukturelle Assertions für stop_reason, error.code, selected_tool.
Keine Prüfung von Side Effects bei retry
Der Wiederholungsaufruf behandelt den Fehler formal, erzeugt aber doppelte Operation oder doppelten Schreibvorgang.
Typische Ursache: Tests prüfen nur stop_reason und fallback, aber nicht die Idempotenz der Tool-Schicht.
Unit- und Integrationsprüfungen werden gemischt
Der Test heißt unit, ruft aber eine reale API auf.
Typische Ursache: keine klare Grenze zwischen lokalen Tests (mocks/fault injection) und Integrationsschicht.
Kurzfassung
- Tool-Mocking und fault injection prüfen, wie der Agent Tool-Fehler verarbeitet.
- Ein Fehlertyp wird durch einen eigenen deterministischen Test abgedeckt.
- Prüft nicht nur Text, sondern retry, fallback,
stop_reasonund Fehlerformat. - Kritische Fault-Tests sollten in jedem PR laufen.
FAQ
Q: Kann man Fehler ohne echte API testen?
A: Ja. Auf Unit-Ebene ist das Standard: fakes und mocks liefern ein stabiles, reproduzierbares Signal.
Q: Was ist wichtiger: retry oder fallback?
A: Beides. Retry deckt kurze Ausfälle ab, fallback schützt Szenarien bei längerer Nichtverfügbarkeit des Haupt-Tools.
Q: Wie viele Fehlerprofile sollte ein Tool mindestens haben?
A: Mindestens drei: timeout, Serverfehler (5xx) und invalide Payload.
Q: Ersetzt das eval harness und regression?
A: Nein. Diese Tests decken lokales Verhalten der Tool-Schicht ab. Systemverhalten auf vollständigen Szenarien prüfen eval harness und regression.
Was als Nächstes
Bindet fault-Cases in Eval Harness ein und fixiert sie in Golden Datasets. Für Änderungen zwischen Versionen ergänzt Regression Testing, und Incidents analysiert ihr mit Replay and Debugging.