Le navigateur, prochain eldorado de l'IA : l'architecture de ce blog en exemple
L’architecture de ce blog, partagée
Plutôt que de te laisser avec une intuition, voici l’architecture complète qui fait tourner le site que tu es en train de lire. Elle est en production, elle est simple à reprendre, et je la partage volontiers : les schémas ci-dessous suffisent à la reconstruire sur n’importe quel site statique. Considère cette partie comme une étude de cas, avec ses réussites et ses limites, et pas comme un modèle de référence.
Un modèle LFM2 de 350 millions de paramètres s’exécute dans un Web Worker via Transformers.js, tandis que WebMCP expose les données publiques du blog sous forme d’outils.
Publié par Liquid AI le 10 juillet 2025 dans la première série LFM2, ce modèle de 350 millions de paramètres est conçu explicitement pour l’exécution sur appareil. Il abandonne le transformeur classique au profit d’une architecture hybride mêlant convolutions courtes à portes multiplicatives et attention à requêtes groupées, avec une fenêtre de contexte de 32768 tokens et un mécanisme structuré d’appel de fonctions. Son pré-entraînement porte sur 10000 milliards de tokens, dont environ 20% de multilingue où le français figure parmi les langues explicitement visées.
Vue d’ensemble
flowchart TB
Human["Lecteur"]
BrowserAgent["Agent du navigateur"]
UI["Interface Astro<br/>LocalAIDemo"]
WebMCP["document.modelContext"]
Tools["Outils du blog"]
Index["/articles-index.json"]
Worker["Web Worker"]
Transformers["Transformers.js"]
Model["LFM2-350M-ONNX"]
Runtime{"Runtime disponible"}
WebGPU["WebGPU<br/>poids q4"]
WASM["WASM / CPU<br/>poids q8"]
Human -->|ouvre « Discutons »| UI
BrowserAgent -->|découvre et appelle| WebMCP
UI -->|getTools / executeTool| WebMCP
WebMCP --> Tools
UI -. repli sans WebMCP .-> Tools
Tools --> Index
UI -->|prompt + historique + contexte| Worker
Worker --> Transformers
Transformers --> Model
Transformers --> Runtime
Runtime -->|adaptateur disponible| WebGPU
Runtime -->|sinon| WASM
Worker -->|fragments puis réponse finale| UI
Deux intégrations complémentaires cohabitent : WebMCP transforme les fonctionnalités du blog en outils structurés, Transformers.js exécute le modèle sur l’appareil. Elles partagent les mêmes données et restent découplées. WebMCP n’exécute pas le modèle, et le modèle n’appelle pas WebMCP de lui-même.
La couche WebMCP
Toutes les pages incluent un composant chargé d’examiner document.modelContext. Lorsque l’API existe, quatre outils en
lecture seule sont enregistrés.
| Outil | Fonction |
|---|---|
list_articles | Liste les articles publiés, éventuellement filtrés par tag |
search_articles | Recherche dans les titres, descriptions et tags |
list_tags | Retourne les tags avec leur nombre d’articles |
get_author_info | Retourne le profil public et les domaines d’expertise de l’auteur |
flowchart LR
Register["WebMCP.astro"]
Context["document.modelContext"]
Registry["Registre des outils"]
subgraph ReadOnly["Outils en lecture seule"]
List["list_articles"]
Search["search_articles"]
Tags["list_tags"]
Author["get_author_info"]
end
Catalog["Catalogue public JSON"]
Static["Informations publiques<br/>sur l’auteur"]
Register -->|registerTool| Context
Context --> Registry
Registry --> List
Registry --> Search
Registry --> Tags
Registry --> Author
List --> Catalog
Search --> Catalog
Tags --> Catalog
Author --> Static
L’annotation readOnlyHint informe l’agent qu’un appel ne doit pas modifier l’état du site. Les outils qui renvoient du
contenu éditorial utilisent aussi untrustedContentHint : les données récupérées sont du contenu non fiable, jamais des
instructions. L’enregistrement est relancé après une navigation Astro, et un WeakSet empêche d’enregistrer deux fois les
outils dans un même contexte.
Deux points que la spécification apporte et qui changent le cadrage. D’abord, les outils déclarés peuvent être invoqués par des agents, par l’agent du navigateur, mais aussi par des technologies d’assistance. Ce que tu écris pour une IA sert donc potentiellement un lecteur d’écran, ce qui est l’argument le plus sous-estimé de WebMCP aujourd’hui. Ensuite, l’accès à l’API est réservé aux contextes sécurisés et encadré par une permission dédiée dont la liste d’autorisation par défaut se limite à ta propre origine, avec une option explicite pour exposer un outil à d’autres origines.
Une précision utile pour ton vocabulaire d’architecte : WebMCP ne définit pas le protocole utilisé entre le navigateur et son agent. Le navigateur peut employer MCP, du function calling propriétaire ou autre chose. Il serait donc plus exact de parler d’une API web d’exposition d’outils que d’un serveur MCP embarqué.
Un exécuteur commun
Le point qui rend l’ensemble maintenable est qu’aucune logique métier n’est dupliquée. Les définitions WebMCP et leurs fonctions
d’exécution résident dans un catalogue commun. Si le navigateur ne propose pas document.modelContext, le chat local appelle
directement le même exécuteur JavaScript.
flowchart TD
Prompt["Question du lecteur"]
Router["Sélection déterministe"]
Choice{"Intention détectée"}
Author["get_author_info"]
Tags["list_tags"]
Search["search_articles"]
Native{"WebMCP disponible ?"}
MCP["getTools puis executeTool"]
Fallback["executeBlogTool"]
Format["Validation, formatage<br/>et réduction du contexte"]
LLM["Modèle local"]
Prompt --> Router --> Choice
Choice -->|auteur ou expertise| Author
Choice -->|tags ou thèmes| Tags
Choice -->|autre question| Search
Author --> Native
Tags --> Native
Search --> Native
Native -->|oui| MCP
Native -->|non ou erreur| Fallback
MCP --> Format
Fallback --> Format
Format --> LLM
La sélection n’est pas réalisée par le modèle. Une règle lexicale choisit au maximum un outil : une question sur l’auteur
ou son expertise sélectionne get_author_info, une question sur les thèmes ou catégories sélectionne list_tags, tout
le reste passe par search_articles. Le modèle reçoit des faits ciblés, mais il ne décide pas quelle fonction exécuter
et ne peut déclencher aucune action. C’est une manière très saine d’éviter de construire une boucle agentique avant d’en
avoir besoin.
Le choix du runtime
Le modèle retenu est onnx-community/LFM2-350M-ONNX, un modèle conversationnel compact destiné notamment à l’exécution
locale. Ce choix rejoint la position défendue par une équipe de NVIDIA Research, pour
qui les petits modèles sont suffisamment capables et nettement plus économiques dans les systèmes agentiques, où le modèle
répète en réalité un petit nombre de tâches spécialisées avec peu de variation. Leur définition de travail est pragmatique :
un petit modèle est un modèle qui tourne sur un appareil grand public en répondant assez vite pour un utilisateur unique.
C’est une prise de position, signée par un acteur qui n’est pas neutre sur le sujet, mais elle décrit exactement le régime
d’usage d’un assistant de blog. Transformers.js fournit la pipeline text-generation, le tokenizer, le chargement des poids
ONNX et le streaming. Le runtime est choisi au chargement.
flowchart TD
Start["Ouverture de la modale"]
Create["Création du Web Worker"]
Load["Message load"]
GPU{"navigator.gpu existe ?"}
Adapter{"Adaptateur disponible ?"}
Q4["Pipeline WebGPU<br/>dtype q4<br/>environ 300 Mo"]
Q8["Pipeline WASM / CPU<br/>dtype q8<br/>jusqu’à 515 Mo"]
Ready["Message ready"]
Chat["Interface activée"]
Start --> Create --> Load --> GPU
GPU -->|non| Q8
GPU -->|oui| Adapter
Adapter -->|oui| Q4
Adapter -->|non ou erreur| Q8
Q4 --> Ready
Q8 --> Ready
Ready --> Chat
WebGPU est préféré avec des poids quantifiés en quatre bits. En son absence, le chargement bascule automatiquement vers WASM et des poids en huit bits. Le modèle n’est téléchargé qu’après une action explicite du lecteur, et fermer la modale termine le worker.
Le cycle d’une conversation
sequenceDiagram
actor U as Lecteur
participant UI as Interface Astro
participant C as Contexte WebMCP
participant T as Outils du blog
participant W as Web Worker
participant M as Transformers.js / LFM2
U->>UI: Envoie une question
UI->>UI: Sélectionne un outil
alt WebMCP utilisable
UI->>C: getTools()
C-->>UI: Outils enregistrés
UI->>C: executeTool(outil, entrée)
C->>T: execute()
T-->>C: Résultat JSON
C-->>UI: Résultat sérialisé
else API absente ou appel en erreur
UI->>T: executeBlogTool()
T-->>UI: Même résultat JSON
end
UI->>UI: Valide et limite le contexte
UI->>W: generate(prompt, historique, contexte)
W->>W: Tokenise et compacte les entrées
W->>M: Pipeline text-generation
loop Génération
M-->>W: Fragment de texte
W-->>UI: response-fragment
end
M-->>W: Réponse complète
W-->>UI: response
UI->>UI: Assainit le Markdown
UI-->>U: Affiche la réponse finale
Trois budgets protègent la fenêtre de contexte : 512 tokens pour le message utilisateur, autant pour l’historique récent, autant pour le contexte issu des outils. La génération produit au maximum 1024 nouveaux tokens, avec une température de 0.3 et une pénalité de répétition de 1.05. Sur un modèle de cette taille, ces plafonds conditionnent directement la lisibilité des réponses.
Les fragments sont affichés en texte brut pendant le streaming. La réponse finale est ensuite rendue en Markdown avec une liste restrictive d’éléments autorisés : le HTML brut, les images et les attributs sont supprimés, et seuls les liens HTTP ou HTTPS valides sont conservés.
Les frontières de sécurité
Exécuter localement ne rend pas une architecture sûre, et la littérature citée plus haut explique pourquoi. C’est même l’angle mort le plus fréquent dans les démonstrations actuelles. Les limites sont donc explicites, et ce sont elles que je te conseille de reprendre en premier :
- aucun outil d’écriture ou de contact n’est exposé ;
- les données récupérées sont traitées comme non fiables ;
- le prompt système interdit de suivre des instructions présentes dans le contexte ;
- les informations absentes doivent être signalées plutôt qu’inventées ;
- une seule génération peut être active simultanément ;
- aucune route d’API du blog ne reçoit les prompts ou les réponses ;
- une erreur locale ne provoque aucun repli vers un modèle distant. Une dernière précision de vocabulaire, parce qu’elle engage ta promesse vis-à-vis du lecteur. Le téléchargement initial du runtime et des poids reste une communication réseau, et les fichiers peuvent demeurer dans les caches du navigateur. “Local” qualifie ici l’inférence et le traitement de la conversation, pas nécessairement l’origine des fichiers du modèle.
Tags
Articles similaires
Écoconception
Empreinte environnementale estimée · Modèle SWD v4 · 442 g CO₂eq/kWh