Skip to content

Vaynerov Technologies

Wir entwickeln nicht einfach — wir zaubern jede Codezeile und jeden Pixel.

Alle ArtikelBaugeschichten

Ein KI-Architekt mit ehrlichen Preisen

Beschreiben Sie ein Produkt in einem Absatz, und Blueprint zeichnet ein Systemdiagramm, einen Dreiphasenplan, ein Team und einen Preis. Alles auf dieser Liste ist generiert – außer dem Einen, das die Leute am meisten interessiert, und genau das war der Entwurf.

Edward AmirainGründer, Vaynerov Technologies
Veröffentlicht 9 Min. Lesezeit
Auf dieser Seite
  1. Das Modell schlägt vor, die Engine bepreist
  2. Structured Output – und wo die Grenzen wirklich liegen
  3. Alles rund um das Modell
  4. Ein Demomodus, der nicht lügt
  5. Das Diagramm zeichnen, ganz ohne Canvas
  6. Das Angebot wird auf dem Server neu bepreist

Ein Sprachmodell, das Ihren Preis erfindet, ist kein Feature. Es ist eine Haftung in schöner Schrift. Jemand liest eine Zahl auf unserer Seite, macht einen Screenshot und steht zwei Wochen später da und erwartet, dass wir dazu stehen – zu Recht. Die Zahl stand auf unserer Seite.

Als ich also Vaynerov Project Blueprint gebaut habe – Idee einfügen, Architektur bekommen –, habe ich eine Regel aufgeschrieben, bevor ich die erste Zeile des Prompts schrieb, und alles andere an diesem Feature ist ihr nachgelagert: Das Modell bepreist niemals irgendetwas. Es schlägt nur Auswahlen vor, und selbst die werden bereinigt, bevor die Engine sie sieht.

Was folgt, ist eine Build-Geschichte über ein KI-Feature, bei dem das interessante Engineering fast vollständig um das Modell herum liegt.

8–12
Knoten über mindestens 4 Bahnen
3
Phasen, exakt – zweifach erzwungen
$150–250
Unit-Rate-Band aus dem Rechner
5 / 15 min
Generierungen pro IP
Die Linie, die ich nicht verschiebe. Alles links ist ein Vorschlag; alles rechts ist Arithmetik, die /pricing für dieselbe Auswahl produzieren würde.

Das Modell schlägt vor, die Engine bepreist

Der Rechner hinter /pricing ist älter als Blueprint – er kam mit der Website selbst und hat im Juni eine Regeltabelle bekommen. Er ist ungefähr so unglamourös, wie Software nur sein kann: Jede Plattform, jedes Feature, jede KI-Fähigkeit, jede Designstufe und jede Integrationsstufe trägt eine Basis-Unit-Zahl; die werden summiert; die Summe wird mit der Produktkomplexität multipliziert, dann mit einem QA-Multiplikator, dann mit einem Launch-Multiplikator; die angepasste Gesamtsumme trifft auf ein Unit-Rate-Band und wird zu einer Spanne in Dollar.

PlattformBasis-UnitsBasiswochen
Web-App508
iOS406
Android406
Backend / API305
Adminpanel254

Fünf der Standardregeln der Engine. Features werden genauso bepreist – Auth 8 Units, Zahlungen 15, Realtime 10, ein Admin-CMS 20; Retrieval-gestützte KI 25, Agenten 30.

Der Zeitplan ist dieselbe Art Arithmetik. Plattformen laufen annahmegemäß parallel, tragen also das Maximum ihrer Basiswochen bei statt die Summe; alles, was keine Plattform ist, rechnet sich mit fünfzehn Units pro Woche um; dann zwei Wochen Puffer, weil es immer zwei Wochen sind.

Blueprint ruft genau diese Engine auf, mit genau diesen Regeln, geladen aus derselben calculator_rules-Tabelle, die auch die Preisseite liest – und fehlt die Tabelle oder ist sie nicht erreichbar, fallen beide auf dieselben fest codierten Defaults zurück statt auf eine Schätzung. Was das Modell beisteuert, ist eine Auswahl: eine Plattform, ein Produkttyp, eine Liste von Features. Dann läuft sanitizeSelections, bevor die Bepreisung läuft. Unbekannte Bezeichner fliegen raus, statt wohlwollend zugeordnet zu werden. Pflichtfelder mit Einfachauswahl, die leer zurückkamen, werden aus Defaults gefüllt.

Der Teil, über den ich mich still freue: Die JSON-Schema-Enums, gegen die das Modell generiert, der im Prompt abgedruckte Optionskatalog und die Allowlist des Sanitizers sind alle aus demselben Regelobjekt abgeleitet. Fügt man dem Rechner ein Feature hinzu, taucht es in allen dreien auf. Sie können nie auseinanderlaufen, weil es nichts zu synchronisieren gibt.

Structured Output – und wo die Grenzen wirklich liegen

Blueprint nutzt Claude Opus 4.8 (claude-opus-4-8) mit Structured Output – ein JSON-Schema, das an die Output-Konfiguration der Anfrage gehängt wird, statt der älteren Gewohnheit, ein Fake-Tool zu definieren und dessen Argumente auszulesen. Die Antwort ist JSON, weil der Decoder auf JSON eingeschränkt war, nicht weil wir höflich gefragt und danach einen Parser mit try drumherum geschrieben haben.

Diese Einschränkung hat allerdings eine bestimmte Form, und es ist nicht die Form einer Validierungsbibliothek. Ein Schema, das die Generierung steuert, muss einfach bleiben: Unseres, von Hand geschrieben, setzt additionalProperties: false, listet jede Eigenschaft als erforderlich, drückt optionale Felder als anyOf: [{ type: "string" }, { type: "null" }] aus und trägt nirgends ein minItems, maxItems, minimum oder maximum.

{
  "type": "object",
  "additionalProperties": false,
  "required": ["id", "label", "lane", "tech", "note"],
  "properties": {
    "id": { "type": "string" },
    "label": { "type": "string" },
    "lane": { "enum": ["client", "edge", "services", "data", "infra"] },
    "tech": { "type": "array", "items": { "type": "string" } },
    "note": { "anyOf": [{ "type": "string" }, { "type": "null" }] }
  }
}

Ein Knoten des Graph-Schemas. Optionalität läuft über anyOf mit null, weil die Grammatik keinen Begriff für einen fehlenden Pflichtschlüssel hat; Anzahlen und Längen fehlen mit Absicht.

Die engen Grenzen leben eine Schicht tiefer, in zod, nach dem Parsen: drei bis vierzehn Knoten, höchstens zweiunddreißig Kanten, ein Phasen-Array der Länge exakt drei, drei bis sechs Teamrollen, zwei bis vier Risiken, eine Wochenzahl zwischen eins und vierzig und eine Idee zwischen zehn und tausend Zeichen. Das Schema garantiert also die Form und zod die Vernunft, und der Prompt fragt dazwischen nach dem ästhetischen Ziel – acht bis zwölf Knoten über mindestens vier Bahnen mit mindestens einem Stück Infrastruktur, acht bis sechzehn Kanten, Phasen von zwei bis zehn Wochen mit je zwei bis vier Ergebnissen. Das einzige Few-Shot-Beispiel im Prompt ist ein bewusst kleines System aus sechs Knoten, denn ein Beispiel ist ein Gravitationstrichter, und mir ist lieber, er zieht nach unten.

Der System-Prompt trägt außerdem unsere hauseigene Verbotsliste – die beiden Marketingwörter, die auf dieser Seite nie erlaubt sind, werden benannt und dem Modell untersagt. Das war nie der schwere Teil des Prompts, aber es ist der Teil, den ich zuerst prüfe.

Alles rund um das Modell

Die Anfrage ist mit Absicht cache-förmig gebaut. Prompt Caching ist ein Präfix-Abgleich, überlebt also nur, wenn die führenden Bytes jedes Mal identisch sind – deshalb sind System-Prompt, Optionskatalog und Few-Shot bytestabil und deterministisch sortiert, und alles Volatile (die Sprache des Besuchers, seine Idee) reitet auf dem User-Turn, wo es hingehört. Den Optionskatalog nichtdeterministisch zu sortieren hätte uns still bei jeder Anfrage den Cache gekostet, und nichts hätte kaputt ausgesehen.

Die Idee des Besuchers wird in <idea>-Tags gewickelt, mit der Anweisung, dass ihr Inhalt Daten sind und keine Anweisungen. Das ist kein gelöstes Problem, und ich behaupte nichts anderes. Es ist eine spürbare Reduktion der einfachen Angriffe, auf einer Fläche, deren schlimmstes Ergebnis ein albernes Diagramm ist – und auf der der Preis, also das Angriffsziel, vom Text aus überhaupt nicht erreichbar ist.

Die stärkste Verteidigung gegen Prompt Injection ist kein cleverer Prompt. Sie besteht darin, nichts Wertvolles durch den Prompt zu leiten.

Auf der Antwortseite prüfen wir stop_reason, bevor wir den Inhalt ansehen. Eine Verweigerung wird zum Code refused; das Anschlagen der 16,000-Token-Decke wird zu truncated; jede interne Fehlerklasse bildet auf einen kleinen client-sicheren Code ab, und roher Fehlertext verlässt den Server nie – eine Meldung des vorgelagerten Anbieters ist unser Infrastrukturdetail, nicht die Lektüre des Besuchers. Alles wird gestreamt, aber nur, um die Verbindung unter einer Zwei-Minuten-Maximallaufzeit am Leben zu halten; wir nehmen die finale Nachricht und validieren sie als Ganzes.

Dann sanitizeBlueprint, absichtlich nachsichtig, denn das Ziel ist ein darstellbarer Graph, kein perfekter. Es kürzt über der Vierzehn-Knoten-Decke, entfernt doppelte IDs, wirft Selbstschleifen und Kanten auf nicht existierende Knoten weg, beschneidet Phasen-Knotenverweise, die nicht mehr auflösen, und hängt verwaiste Knoten an eine deklarierte Phase, damit nichts frei schwebt. Null gibt es nur zurück, wenn das Ergebnis strukturell unbrauchbar ist. In v1 gibt es keine Retry-Schleife, eine Einschränkung, die ich gewählt und nicht entdeckt habe: Ein Retry würde die Latenz des schlimmsten Falls verdoppeln, um eine Ausgabe zu retten, die wir vorher nicht beschreiben konnten.

Der Transport sind Server-Sent Events, und die Route antwortet immer mit **HTTP 200 und text/event-stream** – Fehler kommen als error-Event im Stream an, sodass der Client genau ein Protokoll konsumiert und keinen zweiten Codepfad hat, der vergammeln kann. Vier Statusframes feuern an echten Meilensteinen statt auf einem Timer: queued, drafting, validating, pricing. Der SSE-Reader des Clients ist von Hand über fetch und einen ReadableStream gebaut und trennt an Leerzeilen, weil EventSource im Browser kein POST absetzen kann.

Ein Demomodus, der nicht lügt

Ist kein API-Key konfiguriert, funktioniert Blueprint trotzdem. Der Server meldet die Generierung als nicht verfügbar, der Client ruft die Route gar nicht erst auf, und vier handgeschriebene Referenz-Blueprints – ein SaaS-Produkt, ein Marktplatz, ein Support-Tool, eine IoT-Plattform – werden per Stichwort auf das gematcht, was Sie getippt haben. Der Fortschritt ist inszeniert, damit sich die Seite weiterhin anfühlt, als würde sie denken.

Und hier der Teil, auf den es ankommt: Die Demopreise sind echt. Sie sind nicht aus der Referenzdatei entnommen. Sie laufen durch calculateEstimate mit denselben Standardregeln, sodass die Zahlen, die ein Besucher bei abgeschaltetem Modell sieht, die Zahlen sind, die /pricing für dieselbe Konfiguration produzieren würde. Eine Demo, die falsches Geld nennt, ist eine Demo, die Besuchern beibringt, dass unser Geld falsch ist.

Die Missbrauchskontrollen sind billig und ein bisschen gemein. Weit links außerhalb des Viewports parkt ein Honeypot-Feld; ein Bot, der es ausfüllt, bekommt einen vollkommen überzeugenden Erfolg – ein Blueprint aus der Konserve, korrekte Preise, ein animiertes Diagramm – ohne dass wir einen einzigen Token ausgeben, und wenn er danach ein Angebot anfordert, erhält er ein gültig aussehendes Token, das mit 404 endet. Echter Traffic ist auf fünf Generierungen pro fünfzehn Minuten und Adresse begrenzt, gezählt von einer atomaren Fixed-Window-Funktion in Postgres, mit einer In-Memory-Map als Rückfallebene, wenn die Datenbank nicht erreichbar ist.

Das Diagramm zeichnen, ganz ohne Canvas

Blueprints Ausgabe ist ein Systemdiagramm, und nach einer langen WebGL-Saison hat es Spaß gemacht, dieses hier in schlichtem SVG zu bauen. Kein Canvas, kein three.js, kein Messdurchgang. Das Layout ist ein reines Modul ohne React und ohne DOM-Zugriff: fünf feste Bahnen – Client, Edge, Services, Daten, Infrastruktur –, Knoten von 148 mal 54 Einheiten, Spalten von 176 Breite, mindestens vier Spalten.

Diese drei Konstanten sind ein Beweis, keine Vorliebe. Die Spaltenbreite übersteigt die Knotenbreite, Knoten werden nach Phase und dann nach ursprünglicher Reihenfolge sortiert und gleichmäßig über die verfügbaren Spalten verteilt, sodass sich zwei Knoten bei keiner Knotenzahl bis zur Obergrenze überlappen können – es gibt keinen Kollisionsdurchgang, weil Kollisionen unmöglich sind. Jeder Layout-Bug, den ich je ausgeliefert habe, saß in einem Kollisionsdurchgang.

Kanten sind kubische Bézierkurven. Verbindungen innerhalb einer Bahn wölben sich um 85 % der Knotenhöhe über ihre Bahn, damit sie nie durch die Kästen dazwischen laufen; Verbindungen über Bahnen hinweg ziehen um mindestens 26 Einheiten, skaliert mit der zurückgelegten vertikalen Distanz. Die drei Kantenarten unterscheiden sich per Strichmuster statt per Farbe – synchron durchgezogen, asynchron langgestrichelt, Datenfluss fein gepunktet – mit Legende, denn Farbe allein ist nicht für alle eine Information.

Fünf Bahnen, strichcodierte Kanten, eine harte Überlappungsgarantie aus der Spaltenarithmetik. Die Bahnfarbe ist Dekoration; das Strichmuster ist die Information.

Ein paar kleinere Entscheidungen, die ich wieder so treffen würde. Opazität trägt Bedeutung: Ein durch die Phase herausgefilterter Knoten liegt bei 0.14, ein Nicht-Nachbar des überfahrenen Knotens bei 0.22, ein Knoten außerhalb der überfahrenen Phase bei 0.30 – drei Stufen von gerade nicht. Technologie-Chips werden rechnerisch aus der Zeichenzahl budgetiert, auf zwei Chips gedeckelt und bei dreizehn Zeichen gekürzt, denn SVG kennt keinen Umbruch, und Text, der in SVG aus einem Kasten läuft, läuft einfach weiter. Knoten sind für die Tastatur echte Buttons, mit Enter und Leertaste, und die Pulse, die entlang der Kanten laufen, sind komplett abgeschaltet, wenn der Besucher reduzierte Bewegung wünscht – der Fortschrittsbalken tauscht dann ebenso seinen unbestimmten Scan gegen eine statische anteilige Füllung.

Das Angebot wird auf dem Server neu bepreist

Wenn ein Besucher mag, was er sieht, und ein echtes Angebot anfordert, schickt der Browser die Auswahlen. Er schickt auch die Zahlen, und der Server ignoriert sie vollständig. Honeypot, dann Schemavalidierung, dann das Rate-Limit, dann die Auswahlen erneut bereinigen, dann die Regeln erneut holen, dann die Schätzung erneut ausführen – serverseitig, von vorn – und nur dieses Ergebnis wird in den Angebotsdatensatz geschrieben. Der Kommentar darüber ist vier Wörter lang und erledigt die Sache: never trust client numbers.

Womit das Feature wieder dort ist, wo es angefangen hat. Das Diagramm, das Blueprint Ihnen aushändigt, ist ein Vorschlag – eine Skizze einer vernünftigen Form, die Ihr Produkt annehmen könnte, und einer Skizze darf man widersprechen. Genau dafür ist das erste Gespräch mit einem Studio da. Der Preis ist keine Skizze. Er kam aus derselben Engine, aus derselben Regeltabelle wie jede andere Zahl auf dieser Seite, und er wird diese Zahl noch sein, wenn Sie den Screenshot machen.