Typen
TL;DR
- Ein Typ ist der Vertrag, den die Plattform verwendet, um zu entscheiden, ob die Ausgabe einer Komponente die Eingabe einer anderen Komponente speisen kann. Typen werden zur Edit-Zeit, zur Deploy-Zeit und an jeder Stelle geprüft, an der ein Wert durch das System fließt.
- Typen kommen in drei Kategorien: atomare Typen (
Int32,Double,String,Bool, …), zusammengesetzte Typen, die aus anderen Typen aufgebaut sind (Listen, Tupel, Records, Optionale, Summen), und benannte Typen aus der Registry (Image,Tensor,AudioFrame,BoundingBox, …), die typisierte binäre Payloads mit ihren Metadaten tragen. - Groß-/Kleinschreibung von Bezeichnern ist signifikant. Kleingeschriebene Bezeichner (
t,a,frame) sind generische Typvariablen; großgeschriebene Bezeichner (Image,Double,BoundingBox) sind atomare oder benannte Typen. Die Plattform unifiziert Generics zur Edit-Zeit gegen die konkreten daran verbundenen Typen. - Es gibt keine implizite Konvertierung. Ein
Stringkann nicht zu einemImagewerden. Stimmen zwei Slots nicht überein, lehnt die Plattform die Verbindung ab und meldet den vorgelagerten Typ, den nachgelagerten Typ und wo die Unifikation scheitert. Die Lösung ist eine Konverter-Komponente. - Parameterwerte sind Pipelang-Literale, nicht JSON — Strings sind doppelt zitiert, Tupel verwenden runde Klammern, Listen verwenden eckige Klammern, Records verwenden geschweifte Klammern. Das Literal muss zum deklarierten Typ des Parameters passen; Abweichungen werden beim change-parameter-Aufruf abgelehnt.
Was ein Typ auf dieser Plattform ist
Ein Typ ist die deklarierte Form eines Wertes, der sich zwischen Komponenten bewegt: der Vertrag, der besagt, was ein Ausgabe-Slot produziert und was ein Eingabe-Slot akzeptiert. KI-Workflows komponieren Komponenten aus verschiedenen Teams, Modell-Frameworks und Plattform-Generationen in dasselbe Backend, daher braucht jeder Wire eine geteilte Beschreibung der Daten, die ihn überqueren — rohe Bytes versus ein strukturierter Tensor, ein vorab skaliertes Bild versus eines, das der Konsument skalieren muss, eine Bounding-Box-Liste in Pixelkoordinaten versus normalisiert. Der Typ ist diese Beschreibung.
Jede Komponente deklariert den Typ jedes Eingabe-Slots, jedes Ausgabe-Slots, jedes Konfigurationsparameters und jedes Datei-Slots. Jedes Backend verdrahtet diese Slots mit dem erzwungenen typisierten Vertrag. Die Typinferenz der Plattform läuft zur Edit-Zeit über den gesamten Graph und lehnt jede Mutation ab, die einen Vertrag verletzen würde — im Moment, in dem die Mutation versucht wird, vor dem Deploy.
Komponentenautoren schreiben den Vertrag; alle nachgelagert — Graphautoren, künftige Maintainer, KI-Agenten, die die Plattform von der CLI aus steuern — lesen ihn aus dem Manifest, ohne den Quellcode der Komponente zu lesen. Das Manifest ist die Spezifikation.
Mentales Modell
Ein Pipelang-Typ ist die Formsprache für jeden Wert, über den die Plattform Schlüsse zieht. Drei Kategorien decken alles ab:
atomic composite named
───────── ──────────────── ─────────
Int32, UInt32 [Image] Image
Int64, UInt64 (Double, String) AudioFrame
Float, Double {x: Double, y: Double} Tensor
Bool, Char Image | AudioFrame BoundingBox
String, Bytes [BoundingBox] Polygon<Double>
Atomare Typen sind die sprachebenen Skalare: zehn numerische und Wert-Typen, die dem entsprechen, was die meisten Typsysteme kennen. Bytes ist die einzige bewusste Ausnahme — ein opaker Binär-Blob, sparsam verwendet, wenn kein reicherer Typ passt.
Zusammengesetzte Typen werden aus anderen Typen mit Konstruktoren aufgebaut. Listen [t], Tupel (a, b, c), Records {x: A, y: B}, Summentypen a | b. Ein zusammengesetzter Typ kann jeden anderen Typ umhüllen, einschließlich eines weiteren zusammengesetzten Typs — [[Image]] ist eine Liste von Listen von Bildern.
Benannte Typen leben in der Typ-Registry der Plattform. Image ist kein Tensor aus Bytes; es ist der benannte Typ, auf den sich jede bildverarbeitende Komponente einigt, mit Form, Kanalreihenfolge und Metadaten, auf die sich nachgelagerte Konsumenten verlassen können, ohne sie erneut abzuleiten. Dasselbe für AudioFrame (Abtastrate, Kanalanzahl, Encoding), Tensor (Form, dtype), BoundingBox (Koordinaten, Frame-Referenz). Die benannte Registry ist die Brücke zwischen dem typisierten Vertrag und der Domänensemantik.
Siehe Benannte Typen für die Registry hinter Image, Tensor und den Rest.
Generics verwenden kleingeschriebene Bezeichner
Verwende diesen Bezugsrahmen, wann immer du dich fragst „ist t ein Platzhalter oder ein echter Typ".
Groß-/Kleinschreibung von Bezeichnern ist in Pipelang signifikant. Ein Typ, der mit einem kleingeschriebenen Anfangsbuchstaben geschrieben ist (t, a, frame), ist eine generische Typvariable: ein Platzhalter, den die Plattform gegen den konkreten Typ unifiziert, mit dem er letztlich verbunden wird. Ein Typ mit einem großgeschriebenen Anfangsbuchstaben (Image, Double, BoundingBox) ist ein konkretes Primitiv oder ein benannter Typ — kein Platzhalter.
Die Schreibweisenregel macht Komponenten polymorph. Ein als [t] → [t] deklarierter Durchreich-Puffer funktioniert auf [Image], [AudioFrame], [Tensor] oder jedem anderen Elementtyp — die Plattform unifiziert t konsistent über die Deklaration der Komponente hinweg. Der Puffer-Autor schreibt die Komponente einmal; der Graphautor verwendet sie überall wieder.
Die Schreibweise wählt die Bedeutung. [T] (großgeschrieben) deklariert einen Slot, der einen benannten Typ namens T akzeptiert, der wahrscheinlich nicht in der Registry existiert — die Plattform lehnt ihn mit „unknown named type" ab. [t] deklariert einen generischen Slot. Die Regel: kleingeschrieben = Variable; großgeschrieben = konkret.
Die Prüfung läuft zur Edit-Zeit
Verwende diesen Bezugsrahmen, wann immer du dich fragst, warum die Plattform einen connect-Aufruf abgelehnt hat.
Wenn ein Backend zwei Vertices verdrahtet, läuft die Typinferenz der Plattform über die vorgeschlagene Kante — und über den gesamten Graph — und entscheidet, ob die Verbindung schlüssig ist. Ein erfolgreicher Wire bedeutet, dass der Ausgabetyp der Quelle mit dem Eingabetyp des Ziels unifiziert, unter Berücksichtigung aller generischen Variablen, die konsistent instanziiert werden müssen. Ein fehlgeschlagener Wire bedeutet, dass die Plattform die Operation ablehnt und den vorgelagerten Typ, den nachgelagerten Typ und die Stelle meldet, an der die Unifikation scheitert. Die Operation wird nicht aufgezeichnet; der Graph bleibt gültig.
Die Prüfung deckt jede Stelle ab, an der Typen erscheinen. Das Verbinden einer Image-Ausgabe in eine [BoundingBox]-Eingabe wird abgelehnt. Das Setzen eines Parameters auf ein Literal, das nicht zum deklarierten Typ passt, wird bei change-parameter abgelehnt. Das Binden einer Datei, deren file_type: nicht zum konsumierenden Slot passt, wird bei add-file abgelehnt. Das Ändern des gepinnten Release eines Vertex auf eine neuere Version, deren I/O-Typen nicht mit der nachgelagerten Verdrahtung unifizieren, wird vor dem Deploy markiert. Der Graph, den die Plattform deployt, ist konstruktionsbedingt dieselbe Form, die die Plattform validiert hat.
Es gibt keine implizite Konvertierung. Um einen String-Wert in einen Image-Slot zu speisen, platziere eine Konverter-Komponente dazwischen; die Plattform überbrückt die Lücke nicht stillschweigend. Edit-Zeit-Prüfung verlagert Typabweichungen weg vom Laufzeitpfad, wo stillschweigende Konvertierung in codebasierten Kompositionssystemen sie sonst zutage fördert.
Offene Typen und wie sie sich auflösen
Verwende diesen Bezugsrahmen, wann immer du dich fragst, warum eine generische Komponente oder ein oneof[…]-Slot „unentschieden" bleiben darf, während du baust.
Edit-Zeit-Prüfung erzwingt nicht, dass jeder Typ konkret ist — das ermöglicht generische und polymorphe Komponenten. Ein als [t] → [t] deklarierter Puffer hält t offen, bis ein konkreter Stream daran verdrahtet wird; ein oneof[Image, DepthImage]-Slot bleibt ein unaufgelöster Bound, bis die umgebenden Verbindungen ihn auf eine Variante zwingen. Die Plattform unifiziert diese offenen Typen gegen die konkreten Typen, mit denen sie sich verbinden, während der Graph aufgebaut wird. Ein getaggter Bound oneof t[…] geht weiter — er korreliert und pinnt jedes Vorkommen mit demselben Tag auf dieselbe Variante, sodass eine Wahl den Rest fixiert.
Refinements engen ein, statt zu öffnen. Int32<0..=255>, String<"a" | "b" | "c">, Int64<%8> und String<email> heften ein Prädikat an eine bereits konkrete Basis, und sie reisen mit dem Typ durch die Inferenz als Teil seiner Identität — Int64 und Int64<%8> sind verschiedene Typen. Weil die Basis eines Refinements stets konkret ist, hält ein Refinement einen Typ nie unaufgelöst; es schränkt nur die Werte ein, die der Typ zulässt. (Das Ändern oder Entfernen des Refinements eines Parameters typisiert den Parameter neu, und die Plattform löscht jeden gespeicherten Wert, den der neue Typ nicht mehr akzeptieren würde.)
Deploy erfordert jeden Typ konkret
Verwende diesen Bezugsrahmen, wann immer du dich fragst, was sich zwischen dem Editieren eines Backends und dem Deployen ändert.
Die Edit-Zeit-Prüfung ist nachsichtig gegenüber offenen Typen; Deploy nicht. Das Deployen eines Backends führt strikte Inferenz über den gesamten Graph aus, und jeder Wire und jeder Konfigurationsparameter muss sich auf einen voll konkreten Typ auflösen. Alles noch Offene — eine übrig gebliebene Typvariable, eine Pack-Variable, ein unaufgelöster oneof[…]-Bound — lässt den Deploy scheitern, anstatt einen Container mit unbestimmtem Vertrag zu starten. Ein unaufgelöster Bound wird als einen konkreten Pin benötigend gemeldet.
Das ist der Lohn dafür, den Graph überhaupt zu typisieren: ein Backend kann die Laufzeit nicht mit einer mehrdeutigen Form auf irgendeinem Wire erreichen. Bis zur Deploy-Zeit ist jedes Generic gebunden und jedes oneof durch die umgebenden Verbindungen auf eine einzige Variante eingeengt worden. Wurde ein Slot nie eingeschränkt — eine an nichts Konkretes verdrahtete generische Komponente, ein begrenzter Slot, den der Graph nie pinnt — besteht die Lösung darin, die Verdrahtung vor dem Deployen zu vervollständigen. Das System, das läuft, ist genau dasjenige, das strikte Inferenz vollständig aufgelöst hat.
Pipelang-Literale sind nicht JSON
Verwende diesen Bezugsrahmen beim ersten Mal, wenn du nach change-parameter greifst und die Plattform ablehnt, was wie gültiges JSON aussieht.
Parameterwerte sind Pipelang-Literale, nicht JSON. Die Grammatik ist klein, aber spezifisch: Strings sind doppelt zitiert, Tupel verwenden runde Klammern mit positionalen Elementen, Listen verwenden eckige Klammern, Records verwenden geschweifte Klammern mit benannten Feldern, Booleans sind kleingeschrieben, Gleitkommazahlen müssen einen Dezimalpunkt enthalten (1.0, nicht 1), und das leere Tupel () ist der kanonische „kein Wert" / Unit.
Ein paar Literalformen, die man sich merken sollte:
- Strings:
"google/vit-base-patch16-224". Stets doppelt zitiert. - Zahlen:
42(Ganzzahl),0.5(Gleitkomma — beachte den Dezimalpunkt),0x2a(hex),-1. - Booleans:
true,false. Kleingeschrieben. - Tupel:
(0.0, 0.0, 0.0). Einelementige Tupel brauchen ein nachgestelltes Komma:(42,). - Listen:
[0.1, 0.2, 0.3],[(8, 0.1), (4, 0.3)]. Nachgestelltes Komma ist optional. - Records:
{x: 1.0, y: 2.0}. Benannte Felder, optionales nachgestelltes Komma.
Shell-Aufrufe sollten das gesamte Literal einfach zitieren, damit die Shell runde Klammern, eckige Klammern oder Anführungszeichen nicht auffrisst, bevor Pipelang parst:
ppl backend change-parameter <bid> --vertex 3 --name threshold \ --type Double --value '0.5'ppl backend change-parameter <bid> --vertex 3 --name class_thresholds \ --type '[(UInt64, Double)]' --value '[(8, 0.1), (4, 0.3)]'
Die vollständige Grammatik — jede Literalform, die JSON-vs-Pipelang-Unterschiede, die Regeln der Registry benannter Typen, die Generics-Einschränkungen — liegt in der Typsyntax-Referenz.
Wann man nach welcher Form greift
Verwende diesen Abschnitt als Schnellführer zu den Typkategorien.
Atomare Typen sind richtig für die offensichtlichen Fälle — Zahlen, Strings, Booleans, opake Bytes. Verwende sie als Konfigurationsparameter, als skalare Ausgaben, als Elemente innerhalb reicherer zusammengesetzter Typen.
Records sind richtig, wenn die Daten strukturell sind (keine opaken Bytes, keine Medien-Metadaten) und kein benannter Katalogtyp passt. Ein {x: Double, y: Double, depth: Double} für einen 3D-Punkt ist klarer als drei separate Parameter. Records sind inline definiert; sie existieren als der Typ, den du geschrieben hast, statt als benannter Eintrag in der Registry.
Listen und Tupel sind richtig, wenn der Wert eine Sammlung ist. Listen sind homogen ([BoundingBox] ist eine Liste von Bounding-Boxen); Tupel sind heterogen positional ((Image, [Double], String) trägt drei Werte verschiedener Typen). Greife zu Tupeln, wenn die Positionen bedeutsam sind; greife zu Records, wenn die Positionen Namen brauchen.
Summentypen (a | b) sind richtig, wenn ein Slot wirklich zwei nicht verwandte Formen behandelt — ein Medien-Demultiplexer, der entweder Image oder AudioFrame emittiert, ein multimodales Modell, dessen Ausgabe von seiner Eingabe abhängt. Summentypen sollten die bewusste Wahl sein, nicht ein Weg, um die Definition eines reicheren benannten Typs zu umgehen.
Benannte Typen sind richtig für alles Domänenspezifische, das Metadaten mit Bytes mitführt — Bilder mit Form und Kanalreihenfolge, Audio mit Abtastrate, Tensoren mit Form und dtype. Die benannte Registry ist, was Komponenten aus verschiedenen Teams ermöglicht, sich darauf zu einigen, was ein Image ist, ohne den Vertrag jedes Mal neu abzuleiten.
Generics (t, a) sind richtig für formerhaltende Komponenten — Puffer, Sampler, Gates, Splitter, Transformationen, die auf jedem Elementtyp arbeiten. Dieselbe Komponente wird über Image, AudioFrame, Tensor und jeden anderen Typ wiederverwendet, den der Graph durch sie verdrahtet.
Wo das hineinpasst
Das Typsystem ist der Vertrag, auf dem der Rest der Plattform aufbaut. Backends verlassen sich darauf, schlechte Verdrahtung zur Edit-Zeit abzulehnen. Komponenten-Releases verlassen sich darauf, um Unveränderlichkeit bedeutsam zu machen — der typisierte Vertrag ist das öffentliche Gesicht der Version. Deployments verlassen sich darauf, um zu wissen, was Container beim Start erwarten. Proof-Loops verlassen sich darauf, um zu wissen, welche Form eine Ausgabe haben sollte. Anwendungen verlassen sich darauf, um UI-Bedürfnisse an Endpunktformen zu binden.
Untypisierte Streams mit ad-hoc Validierung pro Komponente sind die übliche Alternative, und sie schieben Verdrahtungsabweichungen in die Laufzeit. Typisierung verlagert die Prüfung auf die Edit-Zeit: Autoren deklarieren den Vertrag einmal, und der Rest des Systems komponiert dagegen.
Verwandt
- Komponenten — wo der typisierte Vertrag deklariert wird.
- Backends — wo typisierte Kanten verbinden und wo Mutationen validiert werden.
- Benannte Typen — die Registry hinter
Image,Tensor,AudioFrame. - Transformationen — typisierte inline eingefügte atomare Typen zwischen Vertices.
- Typsyntax-Referenz — vollständige Literalgrammatik.
- Quickstart — Routenwähler für deine erste Aufgabe.