Assertions
Assertions prüfen Agenten-Ausgaben, Tool-Aufrufe, Trigger-Payloads, das Verhalten untergeordneter Agenten und Abläufe mit menschlichen Freigaben.
Auf dieser Seite
Expression DSL
Testausdrücke verwenden denselben sicheren Evaluator wie Tool Conditions und Approval Rules.
Python-ähnliche Syntax: and, or, not; Vergleiche == != > < >= <=; Zugehörigkeit mit in und not in; Klammern zur Gruppierung; String-Literale in einfachen oder doppelten Anführungszeichen. Verschachtelte Objekte sind über Pfade mit Punktnotation wie context.user.role erreichbar. Ein einzelner Pfad wie context.active prüft, ob der Wert gesetzt und weder leer noch null oder false ist. Fehlt ein Feld, ist die Bedingung nicht erfüllt; es wird kein Fehler ausgelöst.
expected_result
outputoutput.id == 10 oder "hi" in output prüfen.errorNone.status"completed", "failed", "cancelled", "blocked" oder "awaiting_approval".context.<key>context enthält die Werte, die der Builder in build() gespeichert hat. Bei Tests ohne Builder ist es leer. Die Werte lassen sich mit der Agentenantwort vergleichen, zum Beispiel über output.id == context.row_uuid. Siehe Dynamische Payload Builder.expected_tool_calls
invocationsparams.<key>context.<key>params und invocations lässt sich damit prüfen, ob ein Tool die vom Builder erzeugte ID erhalten hat, zum Beispiel mit params.uuid == context.test_uuid.approval_decisions.params
params.<key>context.<key>and auf oberster Ebene werden zwei Dinge getrennt geprüft: params.* filtert einzelne Tool-Aufrufe nach ihren Argumenten; invocations prüft, wie viele Aufrufe diesen Filter erfüllen. Beide Prüfungen können auf context.* zugreifen. Enthält der Ausdruck nur Bedingungen für params.*, muss mindestens ein Aufruf passen. Das entspricht invocations >= 1. Mehrere Einträge für dasselbe Tool prüfen unterschiedliche Argumentwerte unabhängig voneinander. Als Tool-Name ist der lokale Funktionsname oder die vollständige Referenz mit Modulpfad zulässig.expected_result Beispiele
tests:
# Status prüfen (der häufigste Fall)
- name: completes_cleanly
payload: "ping"
expected_result: status == "completed"
# JSON Output über Attributzugriff
- name: returns_id_10
payload: '{"a": 4, "b": 6}'
expected_result: output.id == 10
# Substring-Match in einer Plain-Text-Antwort
- name: greets_user
payload: "hallo"
expected_result: '"hallo" in output'
# Numerischer Vergleich und boolesche Verknüpfung
- name: high_confidence_only
payload: "klassifiziere das"
expected_result: output.confidence >= 0.8 and output.label != "unknown"
# Negativfall: Ein Fehler ist das erwartete Ergebnis
- name: rejects_invalid_input
payload: '{"vendor": ""}'
expected_result: status == "failed" and "missing vendor" in errorexpected_tool_calls Beispiele
tests:
# Einfacher Name: Das Tool muss mindestens einmal aufgerufen werden
- name: uses_calculator
payload: '{"a": 4, "b": 6}'
expected_tool_calls:
- math.calculator.add
# Mapping-Form: Expression für invocations
- name: calls_add_at_least_five_times
payload: '{"sum_many": [1,2,3,4,5,6]}'
expected_tool_calls:
- math.calculator.add: invocations >= 5
# Genau einen Aufruf erzwingen
- name: calls_send_exactly_once
payload: "sende eine Zusammenfassung"
expected_tool_calls:
- notifications.send: invocations == 1
# Über params.* nach Call-Argumenten filtern: Prüft, ob der Agent
# wirklich die Operanden aus der Payload verwendet und keine erfunden hat.
# Fehlt invocations, gilt implizit "mindestens ein passender Call".
- name: calls_add_with_payload_args
payload: '{"a": 4, "b": 6}'
expected_tool_calls:
- math.calculator.add: params.a == 4 and params.b == 6
# Dasselbe Tool wiederholen, um jedes Argument-Set unabhängig zu prüfen.
# Jeder Eintrag ist eine eigene Assertion. Der Test besteht, wenn der Agent
# add(4, ...) und add(7, ...) jeweils einmal aufruft, unabhängig von der Reihenfolge.
- name: calls_add_for_each_pair
payload: "berechne 4+6 und 7+8 getrennt"
expected_tool_calls:
- math.calculator.add: invocations == 1 and params.a == 4
- math.calculator.add: invocations == 1 and params.a == 7
# Assertion zur relativen Reihenfolge: Diese Tools müssen im Trace
# in dieser Reihenfolge erscheinen. Dazwischen dürfen andere Tool Calls liegen.
- name: fetches_then_sends
payload: "suche die Bestellung und benachrichtige den Kunden"
expected_tool_call_order:
- orders.lookup
- notifications.send
# Params mit dem Builder Context abgleichen. Der Builder fügt eine Zeile ein,
# speichert ihre UUID in context["test_uuid"] und übergibt sie dem Agenten im Prompt.
# Die Assertion schlägt fehl, wenn der Agent eine andere als die vom Builder
# bereitgestellte Zeile abruft.
- name: fetches_the_row_we_just_inserted
builder: insert_then_query
expected_result: output.row.id == context.test_uuid
expected_tool_calls:
- db.fetch_row: params.uuid == context.test_uuid and invocations == 1
# Negative Assertion: Das Tool darf NICHT aufgerufen werden
- name: plain_chat_no_tools
payload: "sage hallo"
expected_no_tool_calls:
- math.calculator.add
- notifications.sendAssertions für ausgelöste Agenten
Wenn der getestete Agent trigger_agent aufruft, siehe trigger_agent, kann expected_child_agents den Input, Output, die Tool-Aufrufe und weitere Trigger des untergeordneten Agenten prüfen.
Die Prüfung lässt sich verschachteln: Jeder Eintrag verwendet den Namen des aufgerufenen Agenten als Schlüssel. Über eigene expected_child_agents lassen sich auch die Agenten prüfen, die dieser Agent wiederum aufruft.
tests:
# Der Dispatcher Agent ruft trigger_agent("summarizer", ...) mit
# wait_for_response=True auf. Dadurch stehen Output und Tool Calls
# für die Child-Agent Assertions zur Verfügung.
- name: dispatches_to_summarizer
payload: '{"text": "..."}'
expected_child_agents:
summarizer:
expected_payload: payload.text != ""
expected_result: output.summary != ""
expected_tool_calls:
- llm.complete: invocations >= 1
expected_no_tool_calls:
- email.send
# Trigger Payload mit dem Builder Context abgleichen, damit der Test fehlschlägt,
# wenn der Agent statt der erhaltenen Fixture ID eine falsche weitergibt.
# Funktioniert sowohl bei einem Dict (payload.field) als auch bei einem
# String (Substring über payload_raw) des Parent-Agenten.
- name: forwards_charge_id_unchanged
builder: create_charge_then_refund
builder_args:
amount_cents: 4200
expected_child_agents:
billing-refunder:
expected_payload: payload.charge_id == context.charge_id
# Rekursiv: Einen Grandchild prüfen, den summarizer seinerseits auslöst.
# Auf jeder Ebene wiederholt sich dieselbe Struktur: Agent-Namen als Keys,
# dieselben Assertion-Felder und eigene expected_child_agents.
- name: dispatches_summarizer_then_publisher
payload: '{"text": "..."}'
expected_child_agents:
summarizer:
expected_result: output.summary != ""
expected_child_agents:
publisher:
expected_tool_calls:
- kafka.publish: params.topic == "summaries"
# Bei Fire-and-Forget Triggers (wait_for_response=False) kann das Ergebnis
# nicht geprüft werden. Die Payload wird aber beim Call erfasst, deshalb
# gilt expected_payload weiterhin.
- name: fans_out_telemetry
payload: '{"event": "checkout"}'
expected_child_agents:
telemetry-writer:
expected_triggered: 1
expected_payload: payload.event == "checkout"Welche Prüfungen auf die Antwort warten müssen
wait_for_response=True:expected_result,expected_tool_calls,expected_tool_call_order,expected_no_tool_callsund verschachtelteexpected_child_agentsgelten vollständig.wait_for_response=False: Fire-and-Forget. Prüfe den Call und seine Payload mitexpected_triggeredundexpected_payload. Ergebnis-, Tool- und verschachtelte Assertions benötigenwait_for_response=True.
Trigger Payload prüfen
expected_payload verwendet dieselbe Syntax wie expected_result, prüft aber die übergebenen Eingabedaten. Mit payload.<key> lassen sich einzelne Felder prüfen, wenn der aufrufende Agent ein Dictionary oder einen JSON-String übergibt. Über payload_raw lässt sich prüfen, ob eine Texteingabe einen bestimmten Text enthält. context.<key> greift wie bei den anderen Assertions auf den Builder-Kontext zu. So prüft beispielsweise payload.charge_id == context.charge_id, ob die vom Builder erzeugte ID unverändert weitergegeben wurde. Diese Prüfung funktioniert auch bei Fire-and-Forget-Aufrufen, weil die Payload beim Aufruf erfasst wird.
trigger_agent_at wird in Tests als Fire-and-Forget behandelt. Der Test prüft den Namen des Agenten, die Anzahl der geplanten Aufrufe und die erfasste Payload. Er wartet nicht auf den geplanten Ausführungszeitpunkt.
Wie Aufrufe geprüft werden
- Jeder Aufruf wird einzeln geprüft. Für jeden Aufruf von
trigger_agentwerden die zugehörigen Tool-Aufrufe und die Aufrufe weiterer Agenten separat ausgewertet. - Ein passender Aufruf genügt. Wird derselbe untergeordnete Agent mehrfach aufgerufen, besteht die Assertion, sobald einer der Aufrufe, auf dessen Antwort gewartet wird, die festgelegten Bedingungen erfüllt.
- Der Builder-Kontext wird geteilt.
context.<key>inexpected_resultoderexpected_tool_callseines untergeordneten Agenten liest dasselbe Builder-Dictionary wie der Testfall auf oberster Ebene. Eine inbuild()gespeicherte Fixture-ID ist dadurch auf jeder Ebene erreichbar.
Approvals testen (HITL)
approval_decisions liefert approve-, reject- oder timeout-Antworten für passende ausstehende Freigaben. Der Test wird nach der ausgewählten Entscheidung fortgesetzt, sofern die Freigaberegel dies erlaubt.
tests:
- name: approves_the_exact_refund
builder: create_charge_then_refund
approval_decisions:
- tool: billing.refund
params: params.charge_id == context.charge_id
decision: approve
reason: Durch diesen Test genehmigt
expected_result: status == "completed"- Passende Freigabeanfrage auswählen.
toolenthält die vollständige Tool-Referenz. Das optionale Feldparamsschränkt ein, für welche Aufrufparameter die Entscheidung gilt. Der Ausdruck kann aufparamsund den Builder-Kontextcontextzugreifen sowietrue,falseundnullverwenden. Ohne dieses Feld gilt die Entscheidung unabhängig von den Parametern des Tools. - Ergebnis wählen.
decisionistapprove,rejectodertimeout;reasonist optional. Ablehnungen und Timeouts berücksichtigen die Einstellungon_rejectiondes Approvals. - Jeden Eintrag höchstens einmal anwenden. Pro Aufruf wird ein Entscheidungseintrag höchstens einmal verwendet.
- Vollständige Zuordnung verlangen. Mit
strict_approval_decisions: trueindefaultsoder einem Testfall schlägt der Test fehl, wenn eine Freigabeanfrage keine passende Entscheidung hat oder ein Entscheidungseintrag ungenutzt bleibt. Standardmäßig ist diese Prüfung mitfalsedeaktiviert. - Ohne Entscheidung auf Freigabe warten. Ist die strikte Prüfung deaktiviert und keine passende Entscheidung vorhanden, endet der Aufruf mit
status == "awaiting_approval".