écrits/tutorial/2026/07
Tutorial4 juil. 2026·24 min

Chrome Built-in AI : créez des fonctionnalités IA on-device avec la Prompt API et Gemini Nano

Apprenez à utiliser la Prompt API intégrée de Chrome pour exécuter Gemini Nano directement dans le navigateur. Ce tutoriel pratique couvre la détection de fonctionnalités, les sessions, le streaming, la sortie JSON structurée, les prompts multimodaux et une stratégie de repli cloud hybride.

Pendant des années, ajouter de l'IA à une application web signifiait une seule chose : envoyer les données de l'utilisateur vers une API cloud, payer au token et espérer que la latence reste clémente. L'IA intégrée de Chrome change cette équation. Avec Gemini Nano livré directement dans le navigateur, la Prompt API vous permet d'exécuter l'inférence d'un modèle de langage entièrement sur l'appareil de l'utilisateur — sans clé API, sans coût par requête, sans qu'aucune donnée ne quitte la machine.

Dans ce tutoriel, vous allez construire un ensemble complet de fonctionnalités IA on-device : détection de disponibilité, gestion de sessions, réponses en streaming, sortie JSON structurée, et un patron hybride de qualité production qui bascule vers un modèle cloud quand le modèle local n'est pas disponible. Tout fonctionne en JavaScript pur, donc vous pouvez l'intégrer dans n'importe quel framework — Next.js, Nuxt, SvelteKit ou HTML vanilla.

Prérequis

Avant de commencer, assurez-vous d'avoir :

  • Chrome 138 ou plus récent sur desktop (Windows 10/11, macOS 13+, Linux ou ChromeOS). La Prompt API est sortie de l'origin trial et est disponible dans Chrome stable pour les extensions et, progressivement, pour les sites web.
  • Au moins 22 Go d'espace disque libre sur le volume qui contient votre profil Chrome — Gemini Nano est téléchargé une seule fois et partagé entre tous les sites.
  • Un GPU avec plus de 4 Go de VRAM (les GPU intégrés des machines récentes conviennent).
  • Des connaissances de base en promesses JavaScript et async/await.
  • Node.js 20+ si vous voulez lancer le projet de démonstration en local avec un serveur de développement.

La Prompt API fonctionne uniquement sur Chrome desktop pour l'instant. Le support mobile est sur la feuille de route, c'est exactement pourquoi le patron de repli hybride de l'étape 7 est essentiel pour les applications en production.

Ce que vous allez construire

Un petit widget de « notes intelligentes » qui fonctionne entièrement dans le navigateur :

  1. Détecte si Gemini Nano est disponible sur la machine du visiteur
  2. Télécharge le modèle avec une barre de progression à la première utilisation
  3. Résume et reformule les notes avec une sortie en streaming
  4. Extrait des tâches structurées depuis du texte libre en JSON validé
  5. Bascule vers un modèle cloud côté serveur quand l'IA on-device n'est pas disponible

Étape 1 : comprendre l'architecture de l'IA intégrée

Chrome expose plusieurs API spécialisées au-dessus de Gemini Nano — Summarizer, Translator, Language Detector, Writer et Rewriter — plus la Prompt API généraliste. Les API spécialisées sont plus simples et plus optimisées, mais la Prompt API est celle qui vous donne le contrôle conversationnel complet, les prompts système personnalisés et la sortie structurée. C'est elle que nous utilisons ici.

L'interface globale clé est LanguageModel. Le cycle de vie est toujours le même :

// 1. Vérifier la disponibilité
const availability = await LanguageModel.availability();
// "unavailable" | "downloadable" | "downloading" | "available"
 
// 2. Créer une session (déclenche le téléchargement si nécessaire)
const session = await LanguageModel.create();
 
// 3. Envoyer un prompt
const result = await session.prompt("Explique le cache HTTP en une phrase.");
console.log(result);
 
// 4. Libérer les ressources une fois terminé
session.destroy();

C'est tout le modèle mental. Le reste de ce tutoriel n'est qu'un raffinement de ces quatre appels.

Étape 2 : une détection de fonctionnalités bien faite

Ne supposez jamais que l'API existe. Les navigateurs plus anciens, Chrome mobile, Firefox et Safari lèveront tous une erreur si vous touchez LanguageModel directement. Encapsulez la détection dans un helper :

// lib/ai-detect.js
export async function detectOnDeviceAI() {
  // L'interface elle-même peut ne pas exister
  if (!("LanguageModel" in self)) {
    return { supported: false, reason: "api-missing" };
  }
 
  const availability = await LanguageModel.availability();
 
  switch (availability) {
    case "available":
      return { supported: true, ready: true };
    case "downloadable":
    case "downloading":
      return { supported: true, ready: false, needsDownload: true };
    default:
      // "unavailable" : le matériel ou une politique bloque le modèle
      return { supported: false, reason: "hardware-or-policy" };
  }
}

La distinction entre les trois états positifs compte pour l'UX :

  • available — le modèle est sur le disque, les sessions se créent instantanément
  • downloadable — matériel compatible, mais l'utilisateur n'a pas encore déclenché le téléchargement
  • downloading — un autre onglet ou site a déjà commencé à récupérer le modèle

Appeler LanguageModel.create() quand la disponibilité est downloadable lance un téléchargement de plusieurs gigaoctets. Obtenez toujours un geste explicite de l'utilisateur (un clic sur un bouton) avant de le déclencher, et affichez la progression. Télécharger silencieusement des gigaoctets au chargement de la page est le moyen le plus rapide d'irriter les utilisateurs sur connexion limitée.

Étape 3 : créer une session avec progression du téléchargement

L'appel create() accepte un callback monitor qui expose les événements de progression du téléchargement. Reliez-le à une barre de progression :

// lib/ai-session.js
export async function createSession(onProgress) {
  const session = await LanguageModel.create({
    monitor(m) {
      m.addEventListener("downloadprogress", (e) => {
        // e.loaded est une fraction entre 0 et 1
        onProgress?.(Math.round(e.loaded * 100));
      });
    },
    initialPrompts: [
      {
        role: "system",
        content:
          "Tu es un assistant d'écriture concis intégré dans une application de notes. " +
          "Réponds en texte brut sans titres markdown.",
      },
    ],
  });
 
  return session;
}

Deux détails méritent attention :

Les prompts système vont dans initialPrompts. La première entrée avec le rôle system définit le comportement persistant pour toute la session. Vous pouvez aussi injecter des exemples few-shot en alternant les rôles user et assistant ensuite — le modèle les traite comme des tours de conversation précédents.

Les sessions sont à état. Chaque appel à prompt() s'ajoute à la fenêtre de contexte de la session. Pour un widget de notes, c'est ce que vous voulez ; pour des opérations sans état, clonez plutôt une session fraîche depuis une session de base (l'étape 6 couvre ce point).

Étape 4 : prompter avec une sortie en streaming

Une réponse complète peut prendre plusieurs secondes sur du matériel modeste. Le streaming fait la différence entre une application qui semble cassée et une qui semble vivante. Utilisez promptStreaming(), qui retourne un ReadableStream :

// lib/ai-actions.js
export async function summarizeStreaming(session, noteText, onChunk) {
  const stream = session.promptStreaming(
    `Résume la note suivante en 2 phrases maximum :\n\n${noteText}`
  );
 
  let fullText = "";
  for await (const chunk of stream) {
    fullText += chunk;
    onChunk(fullText); // mise à jour incrémentale de l'UI
  }
  return fullText;
}

Le brancher au DOM tient en une ligne dans n'importe quel framework. Exemple vanilla :

const output = document.querySelector("#summary");
summarizeBtn.addEventListener("click", async () => {
  output.textContent = "";
  await summarizeStreaming(session, noteArea.value, (text) => {
    output.textContent = text;
  });
});

Si l'utilisateur quitte la page ou clique sur annuler, interrompez le flux avec un AbortController — passez le signal dans l'objet d'options :

const controller = new AbortController();
const stream = session.promptStreaming(promptText, {
  signal: controller.signal,
});
// plus tard, à l'annulation :
controller.abort();

Étape 5 : sortie JSON structurée avec un schéma

La fonctionnalité décisive pour les vraies applications : la Prompt API accepte un JSON Schema comme contrainte de réponse, et la sortie du modèle est garantie conforme. Voici comment extraire des tâches depuis des notes en texte libre sans gymnastique de regex :

const todoSchema = {
  type: "object",
  properties: {
    todos: {
      type: "array",
      items: {
        type: "object",
        properties: {
          task: { type: "string" },
          priority: { type: "string", enum: ["high", "medium", "low"] },
          dueMention: { type: "string" },
        },
        required: ["task", "priority"],
      },
    },
  },
  required: ["todos"],
};
 
export async function extractTodos(session, noteText) {
  const raw = await session.prompt(
    `Extrais les actions à faire de cette note :\n\n${noteText}`,
    { responseConstraint: todoSchema }
  );
  return JSON.parse(raw).todos;
}

Comme la contrainte est appliquée au niveau de l'échantillonnage, JSON.parse ne lèvera pas d'erreur sur une sortie malformée — une amélioration spectaculaire de fiabilité par rapport au prompting « réponds en JSON s'il te plaît », et quelque chose que même beaucoup d'API cloud ratent encore sous charge.

Gardez des schémas peu profonds. Les schémas profondément imbriqués ralentissent sensiblement l'échantillonnage contraint on-device. Deux niveaux d'imbrication, comme ci-dessus, est le bon compromis.

Étape 6 : gérer le contexte avec le clonage de sessions et les quotas

La fenêtre de contexte de Gemini Nano est petite comparée aux modèles frontière du cloud. L'objet session expose une comptabilité d'usage pour réagir avant de heurter le mur :

console.log(session.inputUsage);  // tokens consommés jusqu'ici
console.log(session.inputQuota);  // total de tokens disponibles
 
const remaining = session.inputQuota - session.inputUsage;
if (remaining < 1000) {
  // Le contexte est presque plein — repartir de zéro
}

Pour les opérations sans état (résume ceci, reformule cela), évitez de polluer une seule session longue durée. Créez une session de base une fois — en payant le coût du prompt système une seule fois — et clonez-la par opération :

const baseSession = await createSession();
 
async function runIsolated(promptText) {
  const clone = await baseSession.clone();
  try {
    return await clone.prompt(promptText);
  } finally {
    clone.destroy();
  }
}

clone() copie les prompts initiaux mais pas la conversation accumulée, donnant à chaque opération un contexte propre et peu coûteux. Détruisez toujours les clones avec destroy() — les sessions on-device retiennent de la mémoire GPU.

Étape 7 : le patron de repli hybride

La réalité en production : une part significative de vos utilisateurs sera sur mobile, sur Firefox, ou sur des machines qui ne passent pas la barre matérielle. La bonne architecture traite l'IA on-device comme une amélioration progressive au-dessus d'une route serveur.

// lib/smart-ai.js
import { detectOnDeviceAI } from "./ai-detect.js";
 
let session = null;
 
export async function smartPrompt(promptText) {
  const status = await detectOnDeviceAI();
 
  if (status.supported && status.ready) {
    session ??= await LanguageModel.create();
    return {
      source: "on-device",
      text: await session.prompt(promptText),
    };
  }
 
  // Repli : route serveur relayant un modèle cloud
  const res = await fetch("/api/ai", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ prompt: promptText }),
  });
  const data = await res.json();
  return { source: "cloud", text: data.text };
}

Et le route handler Next.js correspondant utilisant l'API Claude comme niveau cloud :

// app/api/ai/route.ts
import Anthropic from "@anthropic-ai/sdk";
 
const anthropic = new Anthropic(); // lit ANTHROPIC_API_KEY depuis l'environnement
 
export async function POST(req: Request) {
  const { prompt } = await req.json();
 
  const message = await anthropic.messages.create({
    model: "claude-haiku-4-5-20251001",
    max_tokens: 512,
    messages: [{ role: "user", content: prompt }],
  });
 
  const text = message.content
    .filter((block) => block.type === "text")
    .map((block) => block.text)
    .join("");
 
  return Response.json({ text });
}

Ce patron vous donne le meilleur des deux mondes : une inférence gratuite, privée et à faible latence pour les clients capables, et une couverture universelle pour tous les autres. Affichez le champ source dans votre UI — un petit badge « traité sur votre appareil » est un vrai signal de confiance, surtout pour les audiences sensibles à la vie privée.

Étape 8 : prompts multimodaux (images et audio)

Les versions récentes de Chrome étendent la Prompt API pour accepter des entrées image et audio. Déclarez les types d'entrée attendus à la création de la session, puis passez des parties de contenu :

const session = await LanguageModel.create({
  expectedInputs: [{ type: "image" }],
});
 
const fileInput = document.querySelector("#screenshot");
const file = fileInput.files[0];
 
const description = await session.prompt([
  {
    role: "user",
    content: [
      { type: "text", value: "Décris cette capture d'écran pour un texte alternatif." },
      { type: "image", value: file },
    ],
  },
]);

La compréhension d'images on-device débloque des fonctionnalités auparavant impensables pour des raisons de confidentialité — génération automatique de textes alternatifs pour les photos des utilisateurs, classification de captures d'écran, lecture de reçus — sans qu'un seul octet ne quitte la machine.

Tester votre implémentation

Vérifiez chaque couche indépendamment :

  1. États de disponibilité. Dans chrome://flags, le modèle on-device peut être activé ou désactivé ; testez votre UI contre unavailable, downloadable et available. Testez aussi dans Firefox pour confirmer que le chemin de repli s'active.
  2. UX de téléchargement. Effacez le modèle via chrome://on-device-internals, rechargez et confirmez que votre barre de progression s'affiche pendant la récupération.
  3. Sortie structurée. Donnez à l'extracteur de tâches des notes volontairement désordonnées (« acheter du lait peut-être ?? URGENT : appeler Sami vendredi ») et vérifiez que le tableau parsé correspond au schéma.
  4. Comportement des quotas. Bouclez un long prompt jusqu'à ce que inputUsage approche inputQuota et confirmez que votre logique de réinitialisation de session se déclenche.

Dépannage

LanguageModel.availability() retourne unavailable sur du matériel capable. Consultez chrome://on-device-internals pour la vraie raison — le plus souvent un espace disque insuffisant (l'exigence de 22 Go est appliquée avec une marge de sécurité) ou une politique d'entreprise désactivant les fonctionnalités d'IA générative.

Le premier create() reste bloqué indéfiniment. Le téléchargement ne progresse que lorsque Chrome considère le réseau comme non limité. Sur les partages de connexion, Chrome peut le différer silencieusement. Affichez un indice « en attente du Wi-Fi » si availability() reste sur downloading.

La qualité de sortie semble inférieure aux modèles cloud. Elle l'est — Gemini Nano est un petit modèle. Gardez des tâches étroites (résumer, reformuler, extraire, classifier) et donnez-lui des exemples few-shot via initialPrompts. Réservez la génération ouverte à votre niveau cloud.

QuotaExceededError lors d'un prompt. Un seul prompt a dépassé la fenêtre de contexte. Découpez les entrées longues, ou routez les requêtes surdimensionnées directement vers le repli cloud.

Prochaines étapes

Conclusion

La Prompt API intégrée de Chrome transforme le navigateur en runtime d'IA. Vous avez appris à détecter la disponibilité sans casser les navigateurs non compatibles, à télécharger Gemini Nano avec une UX respectueuse, à streamer les réponses, à imposer une sortie JSON structurée avec des schémas, à gérer les quotas de contexte avec le clonage de sessions, à traiter des images et — surtout — à envelopper le tout dans un patron hybride qui se dégrade élégamment vers un modèle cloud. L'IA on-device ne remplace pas l'inférence cloud ; c'est un nouveau niveau gratuit et privé dans votre stack. Les applications qui semblent magiques en 2026 sont celles qui utilisent les deux.