Un architecte IA aux tarifs honnêtes
Décrivez un produit en un paragraphe et Blueprint vous dessine un schéma de système, un plan en trois phases, une équipe et un prix. Tout dans cette liste est généré, sauf la seule chose qui intéresse vraiment les gens — et c'était tout le principe.
Sur cette page
Un modèle de langage qui invente votre prix n'est pas une fonctionnalité. C'est un engagement juridique en belle police de caractères. Quelqu'un lit un chiffre sur notre site, en fait une capture d'écran, et se présente deux semaines plus tard en attendant que nous l'honorions — et il aurait raison. Le chiffre était sur notre page.
Alors quand j'ai construit Vaynerov Project Blueprint — collez une idée, obtenez une architecture — j'ai écrit une règle avant la moindre ligne de prompt, et tout le reste de la fonctionnalité en découle : le modèle ne fixe jamais aucun prix. Il ne fait que proposer des sélections, et même celles-ci sont assainies avant que le moteur ne les voie.
Ce qui suit est le récit de construction d'une fonctionnalité IA dont l'ingénierie intéressante se trouve presque entièrement autour du modèle.
- 8–12
- nœuds répartis sur au moins 4 couloirs
- 3
- phases, exactement — imposé deux fois
- $150–250
- fourchette de taux unitaire, du calculateur
- 5 / 15 min
- générations par adresse IP
Le modèle propose ; le moteur chiffre
Le calculateur derrière /pricing est plus vieux que Blueprint — il est arrivé avec le site lui-même et s'est doté d'une table de règles en juin. Il est à peu près aussi peu glamour qu'un logiciel puisse l'être : chaque plateforme, fonctionnalité, capacité IA, niveau de design et palier d'intégration porte un nombre d'unités de base ; ces nombres s'additionnent ; la somme est multipliée par la complexité du produit, puis par un multiplicateur de QA, puis par un multiplicateur de lancement ; le total ajusté rencontre une fourchette de taux unitaire et devient une fourchette en dollars.
| Plateforme | Unités de base | Semaines de référence |
|---|---|---|
| Application web | 50 | 8 |
| iOS | 40 | 6 |
| Android | 40 | 6 |
| Backend / API | 30 | 5 |
| Panneau d'administration | 25 | 4 |
Cinq des règles par défaut du moteur. Les fonctionnalités se chiffrent de la même façon — authentification 8 unités, paiements 15, temps réel 10, un CMS d'administration 20 ; IA à génération augmentée par récupération 25, agents 30.
Le délai relève de la même arithmétique. Les plateformes sont supposées avancer en parallèle : elles contribuent donc le maximum de leurs semaines de référence plutôt que leur somme ; tout ce qui n'est pas une plateforme se convertit à quinze unités par semaine ; puis deux semaines de marge, parce qu'il y a toujours deux semaines.
Blueprint appelle exactement ce moteur, avec exactement ces règles, chargées depuis la même table calculator_rules que lit la page de tarifs — et si la table manque ou reste injoignable, les deux se replient sur les mêmes valeurs par défaut codées en dur, pas sur une supposition. Ce que le modèle apporte, c'est une sélection : une plateforme, un type de produit, une liste de fonctionnalités. Puis sanitizeSelections s'exécute avant le chiffrage. Les identifiants inconnus sont écartés plutôt qu'interprétés avec bienveillance. Les champs obligatoires à choix unique revenus vides sont remplis par des valeurs par défaut.
Ce dont je suis discrètement satisfait : les énumérations du schéma JSON contre lequel le modèle génère, le catalogue d'options imprimé dans le prompt et la liste blanche de l'assainisseur dérivent tous du même objet de règles. Ajoutez une fonctionnalité au calculateur et elle apparaît dans les trois. Ils ne peuvent jamais diverger, puisqu'il n'y a rien à maintenir en phase.
Sortie structurée, et où vivent réellement les bornes
Blueprint utilise Claude Opus 4.8 (claude-opus-4-8) en sortie structurée — un schéma JSON attaché à la configuration de sortie de la requête, plutôt que la vieille habitude consistant à définir un faux outil et à en lire les arguments. La réponse est du JSON parce que le décodeur a été contraint de produire du JSON, pas parce que nous l'avons demandé gentiment avant d'écrire un parseur entouré d'un try.
Cette contrainte a toutefois une forme, et ce n'est pas celle d'une bibliothèque de validation. Un schéma qui pilote la génération doit rester simple : le nôtre, écrit à la main, pose additionalProperties: false et déclare chaque propriété comme obligatoire, exprime les champs facultatifs sous la forme anyOf: [{ type: "string" }, { type: "null" }], et ne porte aucun minItems, maxItems, minimum ni maximum, nulle part.
{
"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" }] }
}
}Un nœud du schéma de graphe. L'optionalité passe par anyOf-avec-null parce que la grammaire n'a aucune notion de clé obligatoire absente ; les comptes et les longueurs sont délibérément absents.
Les bornes serrées vivent une couche plus bas, dans zod, après l'analyse : de trois à quatorze nœuds, trente-deux arêtes au maximum, un tableau de phases de longueur exactement trois, de trois à six rôles dans l'équipe, de deux à quatre risques, un nombre de semaines entre un et quarante, et une idée entre dix et mille caractères. Le schéma garantit donc la forme et zod garantit la raison, tandis que le prompt demande, entre les deux, la cible esthétique : de huit à douze nœuds répartis sur au moins quatre couloirs avec au moins une brique d'infrastructure, de huit à seize arêtes, des phases de deux à dix semaines portant chacune de deux à quatre livrables. L'unique exemple few-shot du prompt est un système délibérément petit, à six nœuds, parce qu'un exemple est un puits de gravité et que je préfère qu'il tire vers le bas.
Le prompt système porte aussi notre liste d'interdits maison — les deux mots marketing qui ne sont jamais autorisés sur ce site sont nommés et interdits au modèle. Cela n'a jamais été la partie difficile du prompt, mais c'est la partie que je vérifie en premier.
Tout ce qui entoure le modèle
La requête est mise en forme pour le cache, exprès. La mise en cache de prompt fonctionne par correspondance de préfixe : elle ne survit que si les octets de tête sont identiques à chaque fois — le prompt système, le catalogue d'options et le few-shot sont donc stables au bit près et ordonnés de façon déterministe, et tout ce qui varie (la langue du visiteur, son idée) voyage dans le tour utilisateur, là où c'est sa place. Trier le catalogue d'options de façon non déterministe nous aurait discrètement coûté le cache à chaque requête, et rien n'aurait eu l'air cassé.
L'idée du visiteur est encadrée par des balises <idea> accompagnées d'une instruction précisant que leur contenu est une donnée, pas une instruction. Ce n'est pas un problème résolu et je ne prétendrai pas le contraire. C'est une réduction significative des attaques faciles, sur une surface où le pire résultat est un schéma ridicule, et où le prix — la chose qui vaudrait la peine d'être attaquée — n'est pas atteignable depuis le texte.
La meilleure défense contre l'injection de prompt n'est pas un prompt astucieux. C'est de ne faire passer aucune valeur importante par le prompt.
Côté réponse, nous examinons stop_reason avant de regarder le contenu. Un refus devient un code refused ; atteindre le plafond de 16 000 tokens devient truncated ; chaque classe d'erreur interne se ramène à un petit code sans risque pour le client, et le texte d'erreur brut ne quitte jamais le serveur — le message d'un fournisseur en amont est un détail de notre infrastructure, pas de la lecture pour le visiteur. Tout est diffusé en flux, mais uniquement pour garder la connexion vivante sous une durée maximale de deux minutes ; nous prenons le message final et le validons d'un bloc.
Puis sanitizeBlueprint, indulgent par conception, car l'objectif est un graphe affichable et non un graphe parfait. Il coupe au-delà du plafond de quatorze nœuds, déduplique les identifiants, supprime les boucles sur soi et les arêtes pointant vers des nœuds inexistants, élague les références de phase qui ne se résolvent plus, et rattache les nœuds orphelins à une phase déclarée pour que rien ne flotte. Il ne renvoie null que lorsque le résultat est structurellement inutilisable. Il n'y a pas de boucle de nouvelle tentative en v1, une limitation que j'ai choisie plutôt que découverte : réessayer doublerait la latence du pire cas pour sauver une sortie que nous ne saurions pas décrire à l'avance.
Le transport, ce sont des Server-Sent Events, et la route répond toujours en **HTTP 200 avec text/event-stream** — les échecs arrivent sous forme d'événement error à l'intérieur du flux, de sorte que le client ne consomme qu'un seul protocole dans tous les cas et n'a pas de second chemin de code à laisser pourrir. Quatre trames d'état se déclenchent à de vrais jalons plutôt que sur une minuterie : queued, drafting, validating, pricing. Le lecteur SSE du client est écrit à la main par-dessus fetch et un ReadableStream, avec découpage sur les lignes vides, parce que l'EventSource du navigateur ne sait pas émettre de POST.
Un mode démo qui ne ment pas
Sans clé d'API configurée, Blueprint fonctionne quand même. Le serveur signale la génération comme indisponible, le client n'appelle jamais la route, et quatre blueprints de référence écrits à la main — un produit SaaS, une place de marché, un outil de support, une plateforme IoT — sont mis en correspondance par mots-clés avec ce que vous avez tapé. La progression est mise en scène pour que la page ait quand même l'air de réfléchir.
Voici la partie qui compte : les prix de la démo sont réels. Ils ne sont pas échantillonnés depuis le fichier de référence. Ils passent par calculateEstimate avec les mêmes règles par défaut, si bien que les chiffres vus par un visiteur avec le modèle éteint sont ceux que /pricing produirait pour la même configuration. Une démo qui affiche de la fausse monnaie est une démo qui apprend aux visiteurs que notre monnaie est fausse.
Les protections anti-abus sont bon marché et un peu vaches. Un champ honeypot est garé loin au-delà du bord gauche de la fenêtre ; un robot qui le remplit obtient un succès parfaitement convaincant — un blueprint prêt à l'emploi, un chiffrage correct, un schéma animé — sans que nous dépensions le moindre token, et s'il enchaîne sur une demande de devis, il reçoit un jeton d'apparence valide qui répond 404. Le trafic réel est limité à cinq générations par quart d'heure et par adresse, comptées par une fonction atomique à fenêtre fixe dans Postgres, avec une table en mémoire comme repli quand la base est injoignable.
Dessiner le schéma sans le moindre canvas
La sortie de Blueprint est un schéma de système, et après une longue saison de WebGL j'ai pris plaisir à construire celui-ci en SVG simple. Pas de canvas, pas de three.js, pas de passe de mesure. La mise en page est un module pur, sans React et sans accès au DOM : cinq couloirs fixes — client, edge, services, données, infrastructure — des nœuds de 148 sur 54 unités, des colonnes de 176 de large, un minimum de quatre colonnes.
Ces trois constantes sont une preuve, pas une préférence. La largeur de colonne dépasse la largeur d'un nœud, les nœuds sont triés par phase puis par ordre d'origine et répartis uniformément sur les colonnes disponibles : deux nœuds ne peuvent donc pas se chevaucher, quel que soit leur nombre jusqu'au plafond. Il n'y a pas de passe de détection de collisions, parce que les collisions sont impossibles. Chaque bug de mise en page que j'ai livré dans ma vie était dans une passe de collisions.
Les arêtes sont des Béziers cubiques. Les liaisons dans un même couloir s'arquent au-dessus de leur couloir sur 85 % de la hauteur d'un nœud, pour ne jamais traverser les boîtes intermédiaires ; les liaisons inter-couloirs tirent d'au moins 26 unités, proportionnellement à la distance verticale parcourue. Les trois natures d'arête se distinguent par leur pointillé plutôt que par leur couleur — synchrone en trait plein, asynchrone en tirets longs, flux de données en pointillé fin — avec une légende, parce que la couleur seule n'est pas une information que tout le monde reçoit.
Quelques décisions plus petites que je referais. L'opacité porte du sens : un nœud filtré par phase reste à 0,14, un non-voisin du nœud survolé à 0,22, un nœud hors de la phase survolée à 0,30 — trois paliers de pas maintenant. Les pastilles de technologie voient leur largeur budgétée arithmétiquement d'après le nombre de caractères, plafonnées à deux pastilles et tronquées à treize caractères, parce que le SVG n'a pas de retour à la ligne et qu'un texte qui déborde d'une boîte en SVG continue tout simplement sa route. Les nœuds sont de vrais boutons pour le clavier, avec Entrée et Espace, et les impulsions qui voyagent le long des arêtes sont purement et simplement désactivées quand le visiteur a demandé une réduction des animations — la barre de progression échange de même son balayage indéterminé contre un remplissage proportionnel statique.
Le devis est rechiffré, sur le serveur
Quand un visiteur aime ce qu'il voit et demande un vrai devis, le navigateur envoie les sélections. Il envoie aussi les chiffres, et le serveur les ignore complètement. Honeypot, puis validation de schéma, puis limite de débit, puis réassainissement des sélections, puis rechargement des règles, puis nouvelle exécution de l'estimation — côté serveur, de zéro — et seul ce résultat-là est écrit dans l'enregistrement du devis. Le commentaire au-dessus tient en une ligne et fait le travail : ne jamais faire confiance aux chiffres du client.
Ce qui ramène la fonctionnalité à son point de départ. Le schéma que Blueprint vous remet est une proposition — l'esquisse d'une forme raisonnable que pourrait prendre votre produit, et une esquisse a le droit d'être discutée. C'est à cela que sert la première conversation avec un studio. Le prix, lui, n'est pas une esquisse. Il est sorti du même moteur, de la même table de règles, que tous les autres chiffres de ce site, et ce sera encore ce chiffre-là quand vous en ferez une capture d'écran.