Mit einem live backend testen

Kurzfassung

  • Ein live-backend-Test führt die Kandidaten-component (oder das Modell-Artefakt oder die Graph-Änderung) auf derselben runtime aus, die die Produktion nutzt, mit einem darum gewickelten I/O-Harness. Der Kandidat läuft in der Produktions-Container-Umgebung, über produktionstypisierte Streams, ohne gemockten Transport und ohne separates Test-SDK.
  • Die kanonische Schleife ist manifestgetrieben: ppl lease run --test=<manifest> --runtime=<id> prägt eine lease, publiziert einen prerelease aus dem Arbeitsverzeichnis, lädt lokale Fixtures hoch, öffnet einen live-test-Event-Stream und rollt die lease am Ende zurück. Exit 0 bei ready, Exit 1 bei failed.
  • Die Harness-Form ist Input ⇒ CUT ⇒ Output mit input_<format>_http / output_<format>_http-components an den Kanten. Inputs sind HTTP-POST; Outputs sind WebSocket-Frames (JSON für serialisierbare Typen, Metadaten-dann-binär für Medien).
  • Eine lease ist der Aufräum-Kontrakt: das Test-backend, der prerelease, die temporären Fixtures und das deployment leben alle innerhalb der lease und werden gemeinsam entfernt, wenn die lease schließt. Die TTL garantiert den Abbau, selbst wenn der Test-Runner früh beendet.
  • Der handgebaute Pfad (backend create / add-vertex / connect / change-parameter / deploy) deckt die Fälle ab, die die Manifest-Grammatik nicht ausdrücken kann — einmalige Graphen, vorhandene Harness-Kombinationen, nicht standardmäßige Test-Runner.

Was ein live-backend-Test ist

Ein live-backend-Test führt die Kandidaten-component auf der runtime aus, auf der sie in der Produktion laufen wird, innerhalb eines echten backend-Graphen, mit einem darum gewickelten I/O-Harness, damit Fixtures hinein- und Ergebnisse herausfließen können. Der Kandidat sieht dieselbe containerisierte Umgebung, dieselben typisierten Streams und dieselben Serving-Service-Abhängigkeiten, die er in der Produktion sieht. Die Harness-components (input_<format>_http, output_<format>_http) sind selbst released components, sodass der Test-Pfad und der Produktions-Pfad derselbe Pfad sind.

Das vermeidet den üblichen Kompromiss zwischen schnellem Feedback und einer realistischen Umgebung. Ein gemocktes Modell gibt einen schnellen Test, der nichts über das echte Modell beweist; ein handlaufendes Modell in einem Notebook ist getreu, bietet aber keine Isolation, Reproduzierbarkeit oder CI-Geschichte; ein separates "Test-Modus"-SDK driftet mit der Zeit von der Produktion ab. Ein live-backend-Test behält die realistische Umgebung ohne all das.

Die lease ist es, die das als CI-Muster praktikabel macht. Ein echtes backend, das pro Test hochgefahren wird, würde sonst lecken — das backend, das deployment und die temporären Fixtures würden alle bestehen bleiben, und jeder CI-Lauf würde Waisen hinterlassen. Innerhalb einer lease ist der gesamte Apparat ein eigener Geltungsbereich, der gemeinsam entfernt wird, sodass der Test sowohl realistisch als auch sauber ist.

Siehe Leases und Der lease-Lebenszyklus für das Eigentumsmodell dahinter.

Die Harness-Form

fixture
   │
   ▼
input_<fmt>_http ──► CUT (component under test) ──► output_<fmt>_http
   (POST or WS)      (the candidate)                 (WS frames)

Der Harness gibt der Kandidaten-component eine echte Input-Quelle und einen echten Output-Sink, sodass der Test typisierte Streams genauso ausübt, wie es die Produktion tut. Die Input-component nimmt eine HTTP-Anfrage und emittiert einen typisierten Stream in den Input-Port der CUT. Die Output-component konsumiert den Output-Stream der CUT und emittiert ihn als WebSocket-Frame, den der Test lesen kann.

Der Harness sind zwei normale released components, als vertices auf einem normalen backend hinzugefügt — keine spezielle Infrastruktur. Du tauschst verschiedene Harness-components (ein Input Audio HTTP für eine Audio-CUT, ein Output Image HTTP für eine bildemittierende CUT) aus, indem du den vertex an jedem Ende änderst. Die Form bleibt gleich. Eine CUT ohne output_type — ein Sink wie log-message — ist immer noch live-testbar: sie produziert keinen harness-lesbaren Frame, also prüfst du stattdessen gegen ihre Container-Logs.

Zwei Wege, einen auszuführen

Es gibt zwei Pfade zum selben live-Test. Wähle danach, ob du ihn wiederholen wirst.

ManifestgetriebenDeklarativ und wiederholbar, gesteuert durch ppl lease run --test. Greife dazu für CI, Regressions-Suiten, Beweis-Schleifen und Versionsvergleiche. Detailliert in Pfad 1 unten.
Handgebautes backendDie rohen backend create / add-vertex / connect / deploy-Primitive. Greife dazu für einmalige Graphen und Harness-Kombinationen, die die Manifest-Grammatik nicht ausdrücken kann. Detailliert in Pfad 2 unten.

Pfad 1 — manifestgetrieben (ppl lease run --test=…)

Nutze diesen Pfad für alles, was du wiederholen willst: CI, Regressions-Suiten, Beweis-Schleifen, Versionsvergleiche.

Das Manifest beschreibt den Test deklarativ: was der Kandidat ist, welche Fixtures er sehen soll, welcher Graph ihn umwickelt und welche Outputs erfasst werden sollen. Die CLI liest das Manifest, publiziert den Kandidaten bei Bedarf, lädt lokale Fixtures hoch, öffnet den live-test-Event-Stream und emittiert Server-Events als NDJSON, bis der Test einen Terminalzustand erreicht.

ppl lease run --test=tests/live-test.yml --runtime=<runtime_id>

Die CLI tut fünf Dinge der Reihe nach, alle aus dem Manifest abgeleitet: parsen, optional den CUT-prerelease aus dem Arbeitsverzeichnis publizieren, alle lokalen-path-Fixtures vorab hochladen (und ihre Manifest-Einträge auf file_id umschreiben), den live-test-Stream öffnen und jedes Server-Event als eine NDJSON-Zeile auf stdout emittieren. Der Exit-Code ist 0 bei ready und 1 bei failed oder Transport-Schluss, was der Kontrakt ist, den ein CI-Schritt braucht.

--cut-version-id richtet den Test auf einen bestimmten prerelease aus, statt einen aus cwd zu publizieren. --label setzt das lease-Label (für späteres Bulk-Aufräumen verwendet). --ttl cappt die Lebensdauer der lease (der Server wendet einen Default plus einen harten Cap an). Die lease rollt immer zurück, wenn der Lauf endet; um ein deployment zur Inspektion am Laufen zu halten, nutze ppl lease test, das es auf der lease laufen lässt, bis du zurückrollst.

Das Manifest

Das Manifest ist eine deklarative Beschreibung des gesamten Laufs. Der vertices-Abschnitt benennt Harness-components an den Kanten und lässt den CUT-vertex unmarkiert — ein vertex ohne harness ist die CUT. Der edges-Abschnitt verdrahtet sie. Fixtures binden per Name und lösen sich auf genau eines von path / url / file_id auf. secrets binden workspace-secrets in vertex-Parameter. output files deklarieren, welche erzeugten Dateien die Plattform beim Abbau in den workspace speichern soll.

release:  auto: true                    # CLI publishes a prerelease from cwddeploy:  fixed_duration: 10m           # Pin deployment lifetime (Go duration)  lease_ttl: 30m                # Whole-lease TTL (server caps)fixtures:  - {name: dog, path: ./tests/dog.jpg}secrets:  - {vertex: cut, key: HF_TOKEN, workspace_secret: hf_token}graph:  vertices:    in:  {harness: "Input Image HTTP"}    cut: {params: {model_cfg: {type: String, value: '"fastvit_t8"'}}}    out: {harness: "Output JSON HTTP"}  edges:    - {from: in,  from_output: 0, to: cut, to_input: 0}    - {from: cut, from_output: 0, to: out, to_input: 0}output_files:  - {name: report, vertex: cut, key: report.json}

Der Event-Stream

Der Server meldet jeden Schritt der Test-Einrichtung über den live-test-Stream. Jedes Event ist ein JSON-Objekt mit einem type-Feld; die CLI gibt sie wortgetreu aus, eines pro Zeile.

typeBedeutung
lease_createdServer hat eine lease für diesen Lauf geprägt.
fixture_fetchedEines pro URL-Modus-Fixture.
backend_createdServer hat eine backend-id zugewiesen.
vertex_addedEines pro vertex; trägt die serververgebene vertex_id.
edge_connectedEines pro Kante.
param_setEines pro gesetztem vertex-Parameter.
file_boundEines pro vertex-Dateibindung.
deploy_starteddeployment-id geprägt.
containerEines pro hochgefahrenem Container.
readyTerminaler Erfolg. CLI beendet mit 0.
failedTerminaler Fehler. Payload {stage, error, debug_bundle?}. CLI beendet mit 1.

Der Stream deckt die Test-Einrichtung ab; der Fixture-rein / Ergebnis-raus-Austausch findet nach ready gegen die geforwardeten endpoints statt. Bilde deine Assertions zurück auf die serververgebene vertex_id ab, die jedes vertex_added-Event trägt.

Pfad 2 — handgebautes backend

Nutze diesen Pfad, wenn die Manifest-Grammatik den Test nicht beschreiben kann: einmalige Graphen, Harness-Kombinationen, die das Schema nicht abdeckt, oder Treiber, die in einem Runner geschrieben sind, den der Manifest-Flow nicht einbettet.

Der handgebaute Pfad nutzt dieselben CLI-Primitive, die die Plattform intern nutzt: ein backend erstellen, die Input- / CUT- / Output-vertices hinzufügen, ihre Ports verbinden, alle Parameter binden, die die CUT braucht, und deployen.

PID=$(ppl backend create --name "live test $(date +%s)" | jq -r .data.backend_id)# component versions returns a bare JSON array (no {count, items} envelope):INPUT_V=$(ppl component versions <input_component_id>  | jq -r '.[] | select(.tags[]? == "latest") | .id')CUT_V=$(ppl component versions <cut_component_id>      | jq -r '.[] | select(.tags[]? == "latest") | .id')OUTPUT_V=$(ppl component versions <output_component_id> | jq -r '.[] | select(.tags[]? == "latest") | .id')# add-vertex has no vertex-id flag — the server assigns it and returns# it in .data.vertex_id. Capture each id for the connect calls.IN=$(ppl  backend add-vertex $PID --version $INPUT_V  --alias in  | jq -r .data.vertex_id)CUT=$(ppl backend add-vertex $PID --version $CUT_V    --alias cut | jq -r .data.vertex_id)OUT=$(ppl backend add-vertex $PID --version $OUTPUT_V --alias out | jq -r .data.vertex_id)ppl backend connect $PID --from-vertex $IN  --from-output 0 --to-vertex $CUT --to-input 0ppl backend connect $PID --from-vertex $CUT --from-output 0 --to-vertex $OUT --to-input 0ppl backend change-parameter $PID --vertex $CUT --name model_cfg \    --type String --value '"fastvit_t8"'ppl backend deploy --runtime <runtime_id> --backend $PID

Diese Verben hängen sich nicht an eine lease — nur der manifestgetriebene lease run-Pfad stempelt das backend, den prerelease, die Fixtures und das deployment als einen lease-eigenen Geltungsbereich, der gemeinsam zurückrollt. Die handgebaute Sequenz oben ist also die Form für einmalige Erkundung, bei der du die Ressourcen von Hand verwirfst (ppl backend undeploy --backend $PID, dann ppl backend delete $PID). Für eine wiederholbare CI-Schleife mit automatischem Aufräumen nutze Pfad 1.

Den Harness wählen

Der Harness auf jeder Seite muss zum I/O-Typ der CUT passen:

CUT-Input-TypHarness-ingress
ImageInput Image HTTP (Output 0 = Image)
AudioFrameInput Audio HTTP
TensorInput NumPy HTTP
alles SerialisierbareInput JSON HTTP (Output 0 = t)
CUT-Output-TypHarness-egress
ImageOutput Image HTTP
Polygon<Double>Output JSON HTTP
[BoundingBox]Output JSON HTTP
alles GenerischeOutput JSON HTTP

Output JSON HTTP ist der Default-egress: es akzeptiert jede backend-typisierte Nachricht und serialisiert sie auf dem WebSocket zu JSON, was die am einfachsten in Tests zu prüfende Form ist. Nutze Output Image HTTP (Metadaten-dann-binär-Frame-Protokoll), wenn die CUT ein Image emittiert und du die rohen Bytes willst.

Entdecke Harness-components per Query — das schlanke list-Limit beträgt 20 Datensätze, und --query sucht darüber hinaus:

ppl component list --query=Inputppl component list --query=Output

Den Test steuern

Nach dem deploy hole die endpoint-URLs mit ppl forward $PID. Jede Zeile trägt den endpoint_name (das, was die Harness-component in ihrem http:-Block deklariert hat), die vertex_id, die url und das token. Behandle die URL und das token als Bearer-Anmeldedaten; reiche sie als Umgebungsvariablen an deinen Test-Treiber weiter, statt sie zu loggen.

Die Output-URL ist HTTP, aber Tests verbinden sich als WebSocket damit (https://wss://). Die Input-URL ist einfacher HTTP-POST. Öffne den Output-WebSocket vor dem Posten des Inputs — sonst kann die Antwort eintreffen, während der Reader noch verbindet, und der Test verpasst sie. Für Output JSON HTTP trägt ein Textframe pro backend-Nachricht den JSON-Body; für Output Image HTTP folgt auf einen Textframe {"type": "metadata", "metadata": {…}} (Schlüssel über metadata_keys konfigurierbar) ein Binärframe mit den Bild-Bytes.

Timing-Realitäten

Ein frisches backend braucht 30 Sekunden bis 4 Minuten zum deployen — Image-Pull aus der lokalen Registry der runtime, Container-Start, Typinferenz-Lauf, endpoint-Registrierung. Kalte runtimes sitzen am oberen Ende dieser Spanne. Wenn die CUT ein Modell träge lädt (HuggingFace-Pull, ONNX-Initialisierung, Triton-Modell-Laden), kann die erste Anfrage nach dem deploy zusätzliche 10–30 Sekunden dauern. Test-Treiber sollten Per-Frame-Read-Timeouts für die erste Anfrage auf ≥30 Sekunden setzen und Kaltstart-Zeit auf Harness-Ebene einplanen, nicht pro Test.

Diese Zahlen prägen das Test-Design, weil jedes frische backend sie erneut zahlt — sie amortisieren sich nicht über backends hinweg. Eine CI-Suite, die sie respektiert, fährt ein backend hoch, läuft viele Fixtures dagegen und baut es ab, statt ein neues backend pro Fixture hochzufahren. Der Manifest-Pfad unterstützt das direkt: eine lease hält ein deployment für den ganzen Lauf.

Wenn der Test fehlschlägt

Fehler fallen in eine kleine Menge von Formen:

  • Typ-Mismatch an einer connect-Kante — die Plattform verweigerte den Graphen vor dem deploy. Der Fehler benennt die Kante und den Konflikt; behebe die Verbindung (oft eine fehlende transformation zwischen nicht passenden, aber kompatiblen Typen).
  • endpoint löst auf, aber POST liefert 404 — das backend deployt noch. Polle, bis Container running sind, bevor du Input sendest.
  • WebSocket-Reads laufen ab — die CUT ist im Container abgestürzt. Lies ihre Logs (ppl deployment logs --container <container_id>).
  • WebSocket verbindet, aber keine Frames treffen ein — die CUT läuft, scheitert aber still (keine Exception, kein Emit). component-Level-Logging ist der nächste Schritt.
  • deploy lehnt ab mit "no nodes available" — der runtime fehlt GPU- oder RAM-Spielraum für den Kandidaten. Wähle eine andere runtime oder warte.

Für breitere Muster siehe Häufige Fehler.

Wo dies hineinpasst

Ein live-backend-Test ist der Beweis-Schritt zwischen Bauen und Ausliefern. Er beantwortet, ob die Kandidaten-component, das Modell-Artefakt oder die Graph-Revision sich korrekt auf der runtime verhält, auf der sie in der Produktion laufen wird. Die runtime ist identisch zur Produktion und die lease garantiert das Aufräumen, sodass das Ergebnis sowohl getreu als auch wiederholbar ist — der Nachweis, den eine promote-Entscheidung braucht.

Der Manifest-Pfad macht aus dieser Schleife einen CI-Schritt statt einer handgesteuerten Sequenz. Der handgebaute Pfad deckt Tests ab, die nicht in die Manifest-Grammatik passen. Beide produzieren denselben Beweis; sie unterscheiden sich nur darin, wie du den Test beschreibst.

Verwandt

War diese Seite hilfreich?