Most coverage of Chrome's built-in AI focuses on the Prompt API and Gemini Nano — the general-purpose model that lets you write free-form prompts directly in the browser. What gets less attention is the second model Chrome ships: Gemma 197M, an ultra-efficient expert model announced at Google I/O 2026 that transparently powers a suite of task-specific APIs. These two models serve fundamentally different purposes, run from separate download budgets, and expose entirely different JavaScript interfaces. Understanding when to reach for each is the difference between building a flexible AI feature and building one that scales to every device without server costs (Google Chrome for Developers, 2026).
This article is a technical deep-dive into Gemma 197M: what makes its architecture efficient enough to run in a browser, which APIs it enables, and how to use all five of them with production-grade patterns. Every code example runs against the stable Chrome APIs as of Chrome 148, with availability checks and error handling included.
Two Models, Two Jobs
Chrome's on-device AI stack is not a single model doing everything. It separates general-purpose inference from task inference, for good reason: a model optimised to translate two sentences has no need to hold a general world model in its weights. Shipping a smaller specialist avoids the memory and latency cost of routing every task through the full Gemini Nano.
Google's public documentation at Google I/O 2026 positions Gemma 197M as the model powering the task APIs. It is worth noting that the Language Detector and Translator APIs predate the Gemma 197M announcement and historically ran on smaller dedicated language models downloaded as separate language packs. Whether those two APIs were silently migrated to Gemma 197M or continue to use separate models is not confirmed in Google's current published documentation. The Summarizer API is explicitly associated with Gemma 197M. For Language Detector and Translator, treat the model attribution as the current public framing, not a verified implementation detail.
- Free-form prompts with system instructions
- Multimodal input: text, image, audio
- Structured JSON output via
responseConstraint - Streaming token generation
- Session state across multiple turns
- Output languages:
en,es,ja
- Summarizer: tldr, key-points, teaser, headline
- Language Detector: confidence-ranked language detection
- Translator: on-device language pair translation
Architecture: Why 197M Active Parameters is Enough
The efficiency of Gemma 197M comes from two architectural innovations that originate in the Gemma 3n model family. Together they allow Chrome to run a model with far fewer active parameters than its raw weight count would suggest.
The 197M figure refers to active parameters during inference — the weights actually loaded and executed on the accelerator for a given task. The model's total parameter count, including PLE embeddings resident on CPU, is higher. This distinction matters when reasoning about latency and memory: what hits the GPU is 197M; what sits on disk and in CPU memory is more.
The Three Stable Task APIs
As of Chrome 138, three task-specific browser APIs are stable. They work without flags, origin trial tokens, or any server-side setup.
Summarizer API
The Summarizer is the flagship use of Gemma 197M. It accepts a long text and returns a summary in one of four types — tldr, key-points, teaser, headline — in either plain text or markdown, at three length settings. The API exposes inputQuota and measureInputUsage() to let you check token budget before submitting — critical for long documents that may exceed the context window.
async function summarize(text, { type = 'key-points', format = 'plain-text', length = 'medium', onProgress, } = {}) { if (!('Summarizer' in self)) throw new Error('Summarizer API not supported'); const availability = await Summarizer.availability(); if (availability === 'unavailable') throw new Error('Summarizer not available on this device'); const summarizer = await Summarizer.create({ type, format, length, outputLanguage: 'en', // required: 'en' | 'es' | 'ja' monitor(m) { m.addEventListener('downloadprogress', (e) => { const pct = e.total ? Math.round((e.loaded / e.total) * 100) : 0; onProgress?.(pct); }); }, }); // Check token budget — guard required: not exposed in all builds if (typeof summarizer.measureInputUsage === 'function') { try { const usage = await summarizer.measureInputUsage(text); if (usage > summarizer.inputQuota) { summarizer.destroy(); throw new Error(`Input too long: ${usage} tokens, quota is ${summarizer.inputQuota}`); } } catch (e) { if (e.message?.includes('too long')) throw e; // TypeError: not implemented in this build — skip quota check } } let result = ''; const stream = summarizer.summarizeStreaming(text); for await (const chunk of stream) { // defensive: older builds yield cumulative text; newer builds yield deltas if (chunk.startsWith(result)) result = chunk; else result += chunk; } summarizer.destroy(); return result; } // Usage const summary = await summarize(longArticleText, { type: 'key-points', format: 'markdown', length: 'short', onProgress: (pct) => console.log(`Model: ${pct}%`), });
The chunk semantics of summarizeStreaming() — and of writeStreaming() and rewriteStreaming() — differ between Chrome builds. Older builds yield cumulative text (each chunk contains all text produced so far); newer builds yield incremental deltas. The defensive pattern above covers both: if the new chunk begins with the accumulated buffer it is cumulative and replaces it; otherwise it is a delta and gets appended. Without this guard, cumulative builds produce doubled and garbled output when accumulated with +=.
Language Detector API
The Language Detector is useful anywhere you need to route user-generated content by language — before translation, before model selection, or to present UI in the detected language. It returns an array of candidates sorted by descending confidence, covering the most probable language down to edge cases. The model runs entirely on-device; no text is sent to a server.
async function detectLanguage(text) { if (!('LanguageDetector' in self)) return null; const availability = await LanguageDetector.availability(); if (availability === 'unavailable') return null; const detector = await LanguageDetector.create(); const results = await detector.detect(text); detector.destroy(); // [ { detectedLanguage: 'it', confidence: 0.97 }, // { detectedLanguage: 'la', confidence: 0.02 }, ... ] return { primary: results[0]?.detectedLanguage ?? 'en', confidence: results[0]?.confidence ?? 0, all: results, }; } // Route a support ticket to the right team by language async function routeTicket(ticketBody) { const { primary, confidence } = (await detectLanguage(ticketBody)) ?? {}; if (confidence > 0.85) { return ROUTING_MAP[primary] ?? 'default-queue'; } return 'default-queue'; // low-confidence fallback }
Note: the Language Detector and Translator APIs run on desktop only (Windows, macOS, Linux, ChromeOS). They are not available on Android or iOS Chrome versions as of Chrome 148 (Google Chrome for Developers, 2026).
Translator API
The Translator API translates text between language pairs on-device. Each language pair requires its own model download, so you should check availability per pair before attempting translation. The Translator.availability() method accepts sourceLanguage and targetLanguage and returns the readiness state for that specific combination.
async function translate(text, { from, to }) { if (!('Translator' in self)) throw new Error('Translator API not supported'); // Check pair-specific availability before creating the instance const availability = await Translator.availability({ sourceLanguage: from, targetLanguage: to, }); if (availability === 'unavailable') { throw new Error(`Translation pair ${from}→${to} not available on this device`); } const translator = await Translator.create({ sourceLanguage: from, targetLanguage: to, monitor(m) { m.addEventListener('downloadprogress', (e) => { console.log(`Language pair: ${Math.round(e.loaded / e.total * 100)}%`); }); }, }); const result = await translator.translate(text); translator.destroy(); return result; } // Translate user review to English for downstream processing const english = await translate('Produit excellent, livraison rapide', { from: 'fr', to: 'en', });
Advanced Pattern: Chaining APIs
The real power of having all three APIs in the same JavaScript context is composability. You can chain Language Detector → Translator → Summarizer to process multilingual documents entirely on the client side — no server, no API key, no data leaving the device. This pattern is particularly relevant for enterprise applications handling sensitive or regulated content.
async function summarizeAnyLanguage(text, { summaryType = 'key-points', summaryLength = 'short', targetLang = 'en', } = {}) { // Step 1: Detect source language const detection = await detectLanguage(text); const sourceLang = detection?.primary ?? targetLang; // Step 2: Translate only if needed let processText = text; if (sourceLang !== targetLang && detection?.confidence > 0.80) { try { processText = await translate(text, { from: sourceLang, to: targetLang }); } catch { // Pair not available — summarize in original language } } // Step 3: Summarize return summarize(processText, { type: summaryType, length: summaryLength }); } // Process a French contract brief into English key points const keyPoints = await summarizeAnyLanguage(frenchContractText, { summaryType: 'key-points', summaryLength: 'medium', targetLang: 'en', });
Gemma 197M vs Gemini Nano: Choosing the Right Tool
Both models are on-device, both are free of token costs, and both require the same ~22 GB of available disk space in the Chrome profile volume. The decision point is task complexity and flexibility.
| Dimension | Gemini Nano — Prompt API | Gemma 197M — Task APIs |
|---|---|---|
| Flexibility | Full prompt customisation, system instructions, multi-turn sessions | Predefined task types with configuration parameters |
| Input modalities | Text, image, audio | Text only |
| Output format | Free-form string or structured JSON via responseConstraint |
Task-specific (summary, translated text, generated content) |
| Output language constraint | en, es, ja only (must be specified) |
Task-determined (Translator outputs the target language) |
| Latency profile | Higher — full generative inference per prompt | Lower — narrower task = fewer effective compute paths |
| Token quota | Per-session context window, managed manually | inputQuota + measureInputUsage() before call |
| Best for | Entity extraction, classification, Q&A on page content, structured data from unstructured text, image description | Summarisation, translation, language routing, writing assistance, tone adjustment |
Production Checklist
.availability() returning 'available', 'downloadable', 'downloading', or 'unavailable'. On first run, expect 'downloadable' — Chrome will begin the model download when .create() is called. Never skip this check; on unsupported hardware (less than ~4 GB VRAM or old integrated GPUs) the API returns 'unavailable' and .create() will throw..create() on a new device triggers a ~2 GB model download. Use the monitor(m) callback to listen to downloadprogress events and surface a progress bar to the user. Without feedback, the 30–120 second wait appears as a hang. Subsequent calls on the same device are instant — the model is cached.await summarizer.measureInputUsage(text) and compare to summarizer.inputQuota before submitting. Exceeding the quota throws mid-stream. For long documents, consider splitting at paragraph boundaries and summarising in chunks, then summarising the summaries..destroy() in a finally block to release them immediately rather than waiting for garbage collection. Failing to do so in a multi-step pipeline accumulates resource pressure and degrades performance of subsequent calls.outputLanguage: 'en' (or 'es' / 'ja') to be explicitly set on both the availability() check and the create() call. Without it, Chrome logs a runtime warning and output quality degrades silently. The Translator API is exempt — its output language is determined by targetLanguage. The Language Detector API has no output language concept.Chrome 138+ on Windows 10/11, macOS 13+ (Ventura and later), Linux, or ChromeOS on Chromebook Plus devices. At least 22 GB of free disk space on the volume containing the Chrome profile is required for model storage. First-use download is approximately 2 GB and runs in the background; all subsequent calls use the cached model and are available offline (Google Chrome for Developers, 2026).
- Google Chrome for Developers. (2026). 15 updates from Google I/O 2026: Powering the agentic web with new capabilities, tools, and features in Chrome. developer.chrome.com/blog/chrome-at-io26
- Google Chrome for Developers. (2026). Summarize with built-in AI — Summarizer API. developer.chrome.com/docs/ai/summarizer-api
- Google Chrome for Developers. (2026). Language detection with built-in AI. developer.chrome.com/docs/ai/language-detection
- Google Chrome for Developers. (2026). Translation with built-in AI — Translator API. developer.chrome.com/docs/ai/translator-api
- Google Chrome for Developers. (2026). Built-in AI APIs. developer.chrome.com/docs/ai/built-in-apis
- Google Developers Blog. (2025). Announcing Gemma 3n preview: powerful, efficient, mobile-first AI. developers.googleblog.com — introducing-gemma-3n
- Google Developers Blog. (2025). Introducing Gemma 3n: The developer guide — Per-Layer Embedding and MatFormer architecture. developers.googleblog.com — introducing-gemma-3n-developer-guide
- MatFormer: Nested Transformer for Elastic Inference. arXiv:2310.07707. arxiv.org/abs/2310.07707
- Google Chrome for Developers. (2025). Enhancing Gemini Nano: delivering higher quality summaries with LoRA. developer.chrome.com/blog/improved-summaries-gemini-nano