Ein Prompt wurde geändert, das KI-Modell gewechselt oder ein Tool refaktoriert. Ist etwas kaputtgegangen? Bei klassischem Code liefert eine Testsuite in Sekunden eine Antwort. Bei KI-Agenten veröffentlichen viele Teams eine Änderung und beobachten anschließend das Dashboard. Da derselbe Input unterschiedliche Ergebnisse erzeugen kann, reicht eine einzelne Beobachtung nicht aus.
Heute stellen wir Connic Tests vor, ein Testframework für das Verhalten von Agenten. Es berücksichtigt, dass identische Eingaben zu unterschiedlichen Ergebnissen führen können. Assertions prüfen den Trace und die abschließende Antwort. Während des Deployments entscheidet die Testsuite, ob die neue Version die festgelegten Anforderungen erfüllt und veröffentlicht werden kann.
Warum Agenten klassische Testframeworks überfordern
Pytest, Jest und JUnit nehmen an, dass derselbe Input immer denselben Output erzeugt. Diese Annahme ist die Grundlage klassischer Assertions. Agenten widerlegen sie. Derselbe Prompt kann andere Tokens liefern, unterschiedliche Tool-Pfade nehmen oder einen sporadischen Fehler zeigen, der beim Retry verschwindet. Testsuiten, die deterministische Outputs voraussetzen, werden entweder nutzlos und lassen jeden fehlerhaften Build passieren oder unbrauchbar und blockieren jeden korrekten.
Diese Fehlerbilder übersehen gewöhnliche Testframeworks:
Wir haben Connic Tests für diese Probleme entwickelt.
Assertions für echte Agenten-Runs erkennen Regressionen, bevor sie den Produktivbetrieb erreichen.
Kostenlos startenDer 30-Sekunden-Überblick
Eine Testsuite ist eine YAML-Datei unter tests/<agent-name>.yaml. Dasselbe flache Layout wie bei middleware/, ohne Scaffolding oder Boilerplate. Jede Datei deklariert einen oder mehrere Testfälle für den gleichnamigen Agenten.
version: "1.0"
agent: stress-tester # optional, defaults to the filename stem
defaults:
runs: 10 # invoke the agent 10 times per case
success_threshold: 90 # 9 out of 10 must pass
timeout_s: 60
tests:
- name: returns_id_10
payload: '{"a": 4, "b": 6}'
expected_result: output.id == 10
expected_tool_calls:
- math.calculator.add # called at least once
- math.calculator.add: invocations >= 5 # ...or with an expression
expected_no_tool_calls:
- email.send # must NOT be calledDieser Test für den Produktivbetrieb definiert zehn Aufrufe, eine erforderliche Erfolgsquote von 90 %, eine Output-Assertion, zwei positive Erwartungen an Tool-Calls und einen verbotenen Nebeneffekt. Zusätzliche Fixtures, Runner oder Integrationscode sind nicht nötig.
Schwankende Ergebnisse mit wiederholten Testläufen prüfen
Der erste Unterschied bei Connic Tests sind runs und success_threshold. Gemeinsam legen sie fest, wie oft ein Testfall ausgeführt wird und welcher Anteil der Ausführungen bestehen muss.
runs: 1) oder Regressionstest (runs: 50, success_threshold: 95) dienen. Die Anzahl wird pro Testfall gewählt: Einfache Prüfungen bleiben günstig, kritische Fälle erhalten mehr wiederholte Runs. Jeder Aufruf wird mit Agenten-Run ID und Pass/Fail erfasst. Schlägt ein Case bei drei von 50 Runs fehl, führen Links direkt zu diesen drei Traces. Keine Suche in Logs nötig.
Assertions passend zum tatsächlichen Verhalten von Agenten
Ein String-Equality-Check auf den finalen Output des Agenten ist fast nie die richtige Assertion. Mit Connic Tests bieten wir vier Formen von Assertions. Jede ist ein Ausdruck statt eines fest codierten Vergleichsoperators.
output, error und status evaluiert. Mögliche Ausdrücke sind output.total > 0, output.status == "refunded" oder ein anderer sicherer Ausdruck. JSON-Nutzdaten werden automatisch geparst.math.calculator.add: invocations >= 5 prüft die Anzahl der Aufrufe. So werden stille Regressionen sichtbar, bei denen ein Agent ein benötigtes Tool nicht mehr verwendet.expected_result fehlt, muss der Run trotzdem den Status completed erreichen. Jeder Case erhält damit kostenlos den grundlegenden Check, ob er abgestürzt ist.assertEquals und assertContains begrenzen die möglichen Fragen, Expressions nicht. Alles, was sich über das Ausgabeobjekt berechnen lässt, etwa numerische Vergleiche, Längenprüfungen, verschachtelte Schlüssel oder Mengenzugehörigkeit, ist bereits eine gültige Assertion. Es ist dieselbe Engine wie bei unseren Trace-Filtern und wird sicher serverseitig evaluiert.Multimodale Fixtures ohne zusätzliches Setup
Viele aktuelle Agenten lesen PDFs, parsen Rechnungen, erkennen Belege per OCR oder beschreiben Bilder. Die zugehörigen Tests sollten das ebenfalls tun. Eine Binärdatei wird unter tests/files/ abgelegt und über ihren Namen referenziert. Der Runner codiert sie als Base64 und übergibt dem Agenten multimodale Nutzdaten.
tests:
- name: extract_invoice_total
payload: "Extract the total amount from this invoice."
files:
- invoice_a.pdf
- invoice_b.pdf
runs: 5
success_threshold: 100
expected_result: output.total > 0 and output.currency == "EUR"Fünf Runs, die jeweils beide PDFs zusammen mit dem Prompt erhalten, und die strikte Erwartung, dass jeder eine positive Summe in Euro zurückgibt. Dabei laufen echte KI-Modell-Aufrufe mit echten Fixtures, ohne Mocking oder Stubs.
Dynamische Builder für Tests mit Zustand
Manche Tests können nicht statisch sein. Ein Test für einen Agenten für Erstattungen muss zuerst eine echte Zahlung erstellen. Ein Updater-Test muss einen echten Datensatz anlegen. Dynamische Builder sind kleine Python-Module unter tests/builders/, die zur Laufzeit die Testdaten erzeugen und die Fixture danach wieder entfernen.
import stripe
def build(context, builder_args, test_name, payload, files):
"""Create a real Stripe charge, then ask the agent to refund it."""
charge = stripe.Charge.create(
amount=builder_args["amount_cents"],
currency="usd",
source="tok_visa",
)
# stash state for cleanup() to read back
context["charge_id"] = charge.id
return f"Please refund charge {charge.id} for the customer."
def cleanup(run, context, builder_args):
"""Runs after the agent finishes (pass OR fail)."""
charge_id = context.get("charge_id")
if charge_id:
# ensure the fixture is gone even if the agent forgot
try:
stripe.Refund.create(charge=charge_id)
except stripe.error.InvalidRequestError:
pass # already refunded by the agent under test
return None # returning False would mark the case failedtests:
- name: refunds_a_real_charge
builder: create_charge_then_refund
builder_args:
amount_cents: 4200
expected_result: output.status == "refunded"
expected_tool_calls:
- stripe.refund.create
expected_no_tool_calls:
- email.sendDer Builder erstellt den Testzustand, der Agent arbeitet damit, die Assertion prüft das Verhalten und der Cleanup-Hook entfernt die Fixture, selbst wenn der Test fehlgeschlagen ist. Builder laufen in derselben Sandbox wie der Agent und sehen deshalb dasselbe Environment, dieselben Secrets und dieselben Tools.
False von cleanup() zurückgegeben, schlägt der Case zusätzlich fehl. Das ist hilfreich, wenn der Teardown selbst einen Bug zeigt, etwa wenn der Agent eine behauptete Erstattung nicht wirklich ausgeführt hat. Jeder andere Rückgabewert gilt als Erfolg.Nebeneffekte mocken, Reasoning beibehalten
Builder testen echten Zustand. Mocks gehen in die andere Richtung: Sie testen das Reasoning des Agenten, ohne ausgewählten eigenen Code wirklich auszuführen. Kein Datensatz wird geschrieben, keine E-Mail gesendet und keine Zahlung bei einem Drittanbieter ausgelöst. Unter tests/mocks/ liegt dafür ein Python-Modul, auf das ein Testfall über das Feld mocks verweist.
from connic import GuardrailResult
# Replace one custom file tool result.
def mock_data_customer_add_customer(tool_name, params, context):
return {"id": "cust_test_1", "name": params.get("name")}
# Replace middleware and one exact before-hook phase.
def middleware_before(content, context):
return content
def mock_data_customer_add_customer_hook_before(tool_name, params, context):
return params
# Replace only this custom input guardrail.
def guardrail_input_domain_check(content, context):
return GuardrailResult(passed=True)tests:
- name: adds_a_customer_without_touching_the_db
payload: '{"name": "Ada"}'
mocks: customer_mocks
strict_mocks: true
strict_hook_mocks: true
strict_middleware_mocks: true
strict_guardrail_mocks: true
# The add_customer call is served by the mock but still recorded,
# so the assertion holds while nothing ever reaches a real datastore.
expected_tool_calls:
- data.customer.add_customer: params.name == "Ada"
expected_result: output.id == "cust_test_1"Tool-Ersetzungen verwenden eine Hierarchie von der exakten Funktion (mock_data_customer_add_customer) über das Modul (mock_data_customer) und den Namespace (mock_data) bis zum Catch-all mock. Dasselbe Modul kann middleware_before und middleware_after, hierarchische Hook-Phasen mit _hook_before und _hook_after sowie Custom Guardrails ersetzen. Eine passende Funktion ersetzt die Phase; fehlt sie, läuft standardmäßig der echte Code. Integrierte Guardrails, vordefinierte Tools und Implementierungen von api: laufen immer wirklich.
expected_tool_calls und expected_no_tool_calls weiterhin, welches Tool der Agent verwenden wollte, während der konfigurierte Rückgabewert als Ergebnis dient.strict_mocks: true pro Datei oder Testfall schlägt die Ausführung fehl, bevor ein nicht gemocktes eigenes Datei-Tool läuft. Diese Vorgabe gilt nur für Tools. Unabhängig davon lassen sich strict_hook_mocks, strict_middleware_mocks oder strict_guardrail_mocks aktivieren, damit ein konfigurierter Lifecycle-Abschnitt ohne passenden Mock fehlschlägt, bevor der echte Code ausgeführt wird. Alle drei sind standardmäßig false; fehlende Phasen und integrierte Guardrails sind ausgenommen.Tests als verbindliche Deployment-Prüfung
Tests, die nur auf dem Laptop eines Entwicklers laufen, vermitteln Sicherheit, sind aber keine Kontrolle. Connic Tests ist direkt mit der Deployment-Pipeline verbunden:
Bei jedem regulären Deployment erkennt Connic die Testsuiten im Projekt, erweitert jeden Case entsprechend der Anzahl runs und führt sie in einem isolierten Runner aus. Verfehlt ein Case seinen Schwellenwert, stoppt das Deployment vor dem Produktivbetrieb. Der Befehl connic deploy --skip-tests ist ein bewusster CLI-Ausweg für Hotfixes; Git Auto-Deployments können das Gate nicht umgehen.

connic test führt dasselbe Framework im aktuellen Environment aus und streamt Ergebnisse in das Terminal.Muster aus der Praxis
expected_tool_calls: warehouse.query: invocations >= 1. Künftige KI-Modell-Wechsel, die den Fehler wieder einführen, lassen das Deployment automatisch fehlschlagen.expected_no_tool_calls: email.send. Eine Regression mit einer vermeintlich hilfreichen Bestätigungs-E-Mail wird beim Deployment statt durch eine Kundenbeschwerde erkannt.Einordnung in die übrige Plattform
Connic Tests prüft vor der Veröffentlichung, ob ein Agent die festgelegten Anforderungen erfüllt. Im laufenden Betrieb ergänzen zwei weitere Funktionen die Qualitätsprüfung:
| System | Wann es läuft | Welche Frage es beantwortet |
|---|---|---|
| Connic Tests | Während des Deployments | Erfüllt dieser Build die definierten Anforderungen? |
| LLM Judges | Nach jedem oder ausgewählten Run im Produktivbetrieb | Wie entwickelt sich die Qualität im Vergleich zu den definierten Bewertungskriterien? |
| A/B-Testing | Über zwei Live-Varianten hinweg | Welche Version funktioniert bei echtem Traffic besser? |
Tests erkennen Regressionen vor der Veröffentlichung. Judges bewerten die veröffentlichten Ausführungen. A/B-Testing entscheidet zwischen zwei Versionen, die beide bestanden haben. Zusammen liefern sie Rückmeldungen vor und nach der Veröffentlichung, auf deren Grundlage das Team den Agenten weiterentwickeln kann.
Erste Schritte
Tests lassen sich in fünf Minuten zu einem bestehenden Agenten hinzufügen:
- 1.Im Projektstamm ein Verzeichnis
tests/erstellen, direkt nebenagents/ - 2.
tests/<agent-name>.yamlum ein oder zwei Testfälle ergänzen. Für schnelles Feedback mitruns: 1beginnen - 3.
connic testlokal ausführen und die gestreamten Testfälle beobachten - 4.Die Änderungen pushen und bereitstellen. Dieselbe Suite schützt jetzt den Produktivbetrieb. Die Anzahl
runsfür besonders wichtige Testfälle erhöhen
Die vollständige Schema-Referenz mit jedem Feld, Standardwert und jeder Ausdrucksbindung steht in der Testing-Dokumentation. Für den Einstieg in Connic lässt sich mit dem Quickstart-Leitfaden ein erster Agent bereitstellen. Anschließend schützt ein Test vor Regressionen, bevor Kunden sie bemerken.