Python-Fallstricke

Die Python-component-API von Pipelogic ist klein, aber die Plattform hat Meinungen dazu, wie sich process() verhalten darf. Die Muster auf dieser Seite sind diejenigen, die sauber kompilieren und ausgeliefert werden, dann aber zur Laufzeit scheitern — meist, weil die Rückgabeform, der stateful-Kontrakt oder das Import-Layout nicht dem entspricht, was die runtime erwartet.

Lies dies einmal, bevor du eine component debuggst, die sauber baut, aber keinen Output produziert.

Dinge, die andere Autoren zuerst erwischt haben, sortiert danach, wo die Falle liegt.

Konfiguration

Pinne keine Pakete, die die runtime base bereits mitliefert

Das runtime-base-Image liefert numpy, pyyaml, protobuf, pika und pipelogic. Eines davon in requirements.txt zu pinnen verhält sich nicht still daneben — der requirements.txt-Validator des build systems lehnt den Upload mit einem Feldfehler ab, der dich auffordert, das Pin zu entfernen. Wenn du wirklich eine andere Version brauchst, wähle ein schwereres build_system, statt erneut zu pinnen.

# WRONG — rejected at validation time
numpy==2.0.0

# Right — the runtime base supplies numpy

Pinne jede Abhängigkeit exakt

==1.2.3, niemals >=, ~=, ein Wildcard wie ==1.2.*, eine URL/VCS-Referenz oder gar kein Spezifizierer — der Validator lehnt jede davon ab. Lockere Pins machen Builds nicht-reproduzierbar und ziehen beim Rebuild stillschweigend neue CVEs herein.

Wähle das richtige build_system

build_system ist ein Pflichtfeld ohne Default. Sein Wert wählt das runtime-base-Image, auf dem deine component gebaut wird; eine Bibliothek zu importieren, die diese base nicht mitliefert (z. B. torch unter einer reinen CPU-base), scheitert zur Laufzeit. Werte, die im component registry vorkommen:

build_systemTypische Verwendung
2Schlanke Python-runtime
2-mlGängige ML-runtime
2-opencv4.11OpenCV-lastige components
2-cuda12.6 / 2-cuda12.8-onnxrtgpu1.22CUDA / ONNX Runtime GPU
2-cuda12.8-torch2.8-onnxrtgpu1.22PyTorch + ONNX Runtime
2-cuda12.8-torch2.8-ultralyticsUltralytics-Stack
2-cuda12.8-torch2.8-onnxrtgpu1.22-roboflowRoboflow-Inferenz
customEigenes base-Image mitbringen

Passe den Wert an die Bibliotheken an, die du importierst. Siehe concepts/build-systems für die vollständige Matrix.

Typen

Numpy-Zugriff aus Bytes / List: wisse, welcher Aufruf kopiert

import numpy as nparr = pipe_bytes.unsafe_numpy(dtype=np.uint8, shape=(h, w, 3))  # zero-copy, READ-ONLY viewarr = np.asarray(pipe_bytes)                                    # copy-on-writearr = pipe_bytes.safe_numpy()                                   # copy-on-writearr = np.array(pipe_bytes, copy=True)                           # always an independent copy

unsafe_numpy() liefert einen nur-lesbaren View ohne Kopie, der nur gültig ist, solange das Quellobjekt Bytes/List im Scope ist — verwahre ihn nicht über den Aufruf hinaus und schreibe nicht durch ihn hindurch. np.asarray(...) und .safe_numpy() sind copy-on-write: sicher zu halten, solange sie im Scope sind, und Mutationen wirken in den Puffer zurück, bis der erste Schreibvorgang eine private Kopie erzwingt. Verwende np.array(..., copy=True), wenn du einen Wert brauchst, der vollständig unabhängig von der Eingabe ist.

Default-int ist Int64, Default-float ist Double

from pipelogic.types import ListL = List([1, 2, 3])              # element type: Int64L = List([1.0, 2.0])             # element type: DoubleL = List([1, 2, 3], '[Int32]')   # explicit Int32

Konstruiere keine dicts für typisierte Werte

Die High-Level-wrapper existieren aus gutem Grund — sie erledigen die named-type registration und das Feld-Layout für dich:

# WRONG — manual dict construction breaks when type fields driftreturn {"width": w, "height": h, "data": arr.tobytes(), "format": ...}# Right — let the wrapper do itreturn Image(arr, color_space=ColorSpace.BGR)

Das Gleiche gilt für BoundingBox, Tensor, AudioFrame, Mask, Landmark usw. Bevorzuge die wrapper in pipelogic.cv und pipelogic.types — sie erledigen das Feld-Layout und die named-type registration für dich. Steige nur dann auf rohe dicts ab, wenn du einen Grund hast, den die wrapper nicht abdecken.

Components

Virtual input ist Opt-in über den Parameternamen

Um virtual-input-Nachrichten zu empfangen, benenne den Parameter virtual_input in deiner component-Funktion. Die runtime erkennt den Namen und schaltet die component in den virtual-input-Modus:

def from_vin(virtual_input):    return virtual_input[0]run(from_vin)

Das Zurückgeben eines Tuples aus einer 1-Output-component verpackt es als einen Output

Wenn component.yml einen output_type deklariert und deine Funktion (a, b) zurückgibt, serialisiert die runtime das Tuple als deinen einen Output — sie teilt es nicht automatisch auf. Um mehrere Outputs zurückzugeben, deklariere sie in component.yml:

worker:  output_types:    - BoundingBox    - Image

dann gib (boxes, image) in der deklarierten Reihenfolge zurück.

Stateful components müssen ein dict zurückgeben

Eine component wird stateful, wenn du initial_state= an run übergibst. Von da an erhält die Funktion ein state-Argument und muss beide Schlüssel zurückgeben:

def stateful(x, state):    return {"output": x * 2, "state": state + 1}    # required keysrun(stateful, initial_state=0)

Das Weglassen von state aus dem zurückgegebenen dict wirft ValueError("state must be provided when stateful is True").

Virtual-output-components müssen eine virtual_output-Liste zurückgeben

Aktiviere den virtual output, indem du use_virtual_output=True an run übergibst. Die Funktion gibt dann ein dict zurück, dessen virtual_output-Schlüssel eine Liste (möglicherweise leer) ist; jedes Element wird auf dem virtual-output stream vor den normalen Outputs emittiert:

def with_virtual_out(x):    return {"output": x, "virtual_output": [item1, item2]}run(with_virtual_out, use_virtual_output=True)

Das Weglassen des virtual_output-Schlüssels wirft.

CV-wrapper

Stereo-Audio wird nicht automatisch zu mono gemischt

AudioFrame.numpy() liefert die ursprüngliche (samples, channels)-float32-Form zurück. Viele Audiomodelle erwarten mono — rufe .mono() explizit auf:

audio_arr = audio.mono()              # 1-D float32, channels averaged

.mono() bleibt float32; wenn ein Modell int16-PCM will, verwende audio.to_int16(mono=True).

Der color space eines Image wird erzwungen — nicht lügen

Image(arr, color_space=ColorSpace.RGB) kündigt RGB auf der Leitung an. Nachgelagerte Consumer, die einen bestimmten color space erwarten (z. B. eine Image.to_gray()-Kette), berechnen die richtige Konvertierung anhand des deklarierten color space. Wenn du BGR-Daten als RGB gelabelt übergibst, ist jede nachgelagerte Konvertierung falsch.

image.numpy() liefert den aktuellen color space

Wenn die Eingabe BGR war, ist image.numpy() BGR. Wenn du einen bestimmten Raum willst, rufe to_bgr()/to_rgb()/to_gray() auf — sie liefern immer den richtigen Raum (und kopieren, wenn eine Konvertierung nötig ist).

Image.resize((h, w)) nimmt zuerst Höhe, dann Breite

OpenCV ist das Gegenteil — cv2.resize(img, (w, h)). Der wrapper von Pipelogic nimmt (height, width), um den numpy-shape-Konventionen zu entsprechen.

Datei- und Modellpfade

find_model_file benötigt genau einen Treffer

find_model_file durchsucht ein Verzeichnis rekursiv nach .pt-, .onnx-, .safetensors- oder .bin-Dateien (Override mit extensions=). Null Treffer wirft FileNotFoundError; mehr als einer wirft ValueError. Das ist Absicht — es zwingt dich, explizit zu sein, welche Gewichtsdatei „das Modell" ist. Zeige bei Checkpoints aus mehreren Dateien auf ein übergeordnetes Verzeichnis und schreibe deinen eigenen Loader.

ensure_local_dir berücksichtigt den Offline-Modus

ensure_local_dir gibt einen lokalen Verzeichnispfad unverändert zurück und führt andernfalls einen Snapshot-Download des Repos durch. Setze HF_HUB_OFFLINE=1 (oder TRANSFORMERS_OFFLINE=1) in der Container-Umgebung, und der Download wechselt zu local_files_only — er bedient den zur Build-Zeit vorab geholten Cache und greift nie auf das Netzwerk zu.

Init und Startup

Importiere pipelogic nicht lazy

pipelogic verbindet sich zur Import-Zeit mit der laufenden backend. Wenn du den Import verzögerst, kann sich deine component nicht an ihre streams anhängen. Setze from pipelogic.worker import run ganz oben in main.py.

Mutable Konfiguration

Mutable Parameter aktualisieren sich nur, wenn du config.sync() drainst

In component.yml als mutable deklarierte Parameter ändern die config-Attribute nicht von allein. Du musst config.sync() einmal pro Tick aufrufen — es drainst die Queue ausstehender Updates, wendet die neuen Werte auf config an und liefert die Menge der geänderten Schlüssel zurück. Eine component, die config.threshold liest, aber nie config.sync() aufruft, sieht weiterhin den Wert, mit dem sie gestartet ist.

from pipelogic.worker import config, rundef process(x):    changed = config.sync()          # drain once per tick    if "threshold" in changed:        ...                          # react to the new value    return x

Übergib die geänderte Schlüsselmenge selbst an HotSwapModel.apply

HotSwapModel lädt eine model backend neu, wenn sich ein beobachteter config-Schlüssel ändert, ruft aber absichtlich nie config.sync() selbst auf — so können mehrere Consumer in einer component auf denselben Update-Batch reagieren. Du drainst config.sync() und übergibst dessen Ergebnis:

def process(x):    swapper.apply(config.sync())     # you drain, HotSwapModel reacts    return swapper.backend.run(x)

Wenn der Rebuild (oder der Vorab-Validierungs-Download für eine neue Modell-ID) fehlschlägt, bleibt die vorherige backend live und eine Warnung wird geloggt — die component bedient weiter mit dem letzten als gut bekannten Modell.

Verwandt

  • Quickstart — sauberer Start ohne Fallstricke.
  • Typen — vollständiger Type-Katalog.
  • Python-component — Referenz zum Python-component-Einstiegspunkt.
  • Config — Lesen und Synchronisieren der component config.
  • Inferenz-Helfer — Modell-Laden und HotSwapModel.

War diese Seite hilfreich?