Essay

Five things to do with the language model already in your Chrome

Chrome can run Gemini Nano on your own machine. Here is what it is good at, where it quietly goes wrong, and live examples you can run on this page.

Somewhere on your laptop, if you use desktop Chrome, there may be a language model you never asked for. Chrome can download Gemini Nano, Google’s smallest Gemini model, and expose it to web pages through a handful of JavaScript APIs. No API key, no server, no invoice at the end of the month. The model runs on your own GPU or CPU, and your text never leaves the machine.

That sounds like either the future of web apps or a very elaborate way to make laptop fans louder. After spending an evening poking at it, my answer is: a bit of both. This post walks through five practical ways to use it, with live examples you can run on this page. Every output below is produced by the model in your browser when you press the button. If your browser doesn’t have the API, the example says so instead of pretending.

First, the obvious question. Does your browser have it?

Interactive example: Which built-in AI APIs this browser exposes. It needs JavaScript and runs entirely in your browser.

When I ran this in Chrome 153 on a MacBook, the Prompt API (LanguageModel), the Summarizer and the language detector were ready. The translator was downloadable, and the Writer, Rewriter and Proofreader APIs were not exposed at all. Your table will probably look different. These APIs have moved through origin trials at different speeds, and availability depends on your Chrome version, flags and hardware. Google’s documentation asks for about 22 GB of free disk space and either a GPU with more than 4 GB of memory or a CPU machine with 16 GB of RAM. Chrome downloads the model once and shares it between every site that uses it. Mine arrived in the time it took to write three React components.

1. Just prompt it #

The Prompt API is the general-purpose one. You create a session, send text and get text back, streamed if you like:

const session = await LanguageModel.create({
  expectedInputs: [{ type: 'text', languages: ['en'] }],
  expectedOutputs: [{ type: 'text', languages: ['en'] }],
});
for await (const chunk of session.promptStreaming('Explain a KV cache in three sentences.')) {
  output.textContent += chunk;
}

Try it:

Interactive example: Prompt Gemini Nano and watch it stream. It needs JavaScript and runs entirely in your browser.

On my machine, the first chunk arrived after about 1.3 seconds and the full answer took 2.9 seconds. That is fine for a feature that runs after a click, and too slow for anything that runs on every keystroke.

Then I read the answer. Gemini Nano explained that a KV cache is “a really fast lookup table” that uses “the input (keys) to quickly retrieve corresponding results (values)”. It is confident and fluent, and wrong in a way that would get past most readers. The keys and values in a transformer’s KV cache are attention vectors, not a hash map from inputs to outputs. If you want the real explanation, the LLM serving lab has one, with numbers.

This is the most important thing to understand about Nano. It is a small model. It writes well and knows much less than it sounds like it knows. Treat it like a fast, eager intern: great at reshaping text you give it, and not someone to ask for facts.

Two practical details. A session keeps the conversation, so a follow-up question sees the earlier turns. Ask it “shorter, please” after the first answer. The session also has a budget. Chrome reported a context window of 9,216 tokens, and session.contextUsage tells you how much you have used. That fills up quickly if you paste in a long document, so treat the context window as a design constraint from the start.

2. Summarize without writing a prompt #

The Summarizer API is a thin, opinionated wrapper around the same model. You don’t write a prompt. You pick a type (key-points, tldr, teaser or headline) and a length, and Chrome does the prompting.

Interactive example: Summarize text with the Summarizer API. It needs JavaScript and runs entirely in your browser.

The sample text is a small postmortem I wrote for this purpose. It deliberately includes numbers and a trade-off. Here’s what Nano gave me:

  • Headline: “Nightly Batch Job Redesigned, Runtime Drops to 40 Minutes.” Honestly, better than mine.
  • TL;DR: It claimed “a 90% reduction in runtime”. Four hours to forty minutes is an 83% reduction. Close enough for a headline, wrong enough for a report.
  • Key points: It said costs went up “due to the workers’ per-minute billing and the introduction of a dead-letter queue.” The text never says the dead-letter queue costs anything. The model merged two nearby facts into one cause.

None of these mistakes are dramatic. That’s what makes them dangerous. Summaries are exactly where people stop reading carefully. My rule of thumb: on-device summaries are good for triage, such as deciding what to read, tagging or previewing a link. Don’t use them where a wrong number ends up in a decision.

The summaries took 1.3 to 2.7 seconds each. A server model would be faster and more accurate. What you get here is zero cost per call and privacy. Your users’ drafts never go anywhere, which for some products is the whole point.

3. Get JSON you can actually parse #

This is my favourite feature. Anyone who has built an LLM feature knows this moment: you ask for JSON, and the model answers “Sure! Here’s the JSON:” followed by a Markdown code fence. The Prompt API has a direct fix. Pass a JSON Schema as responseConstraint, and Chrome constrains decoding so the output must match it.

const json = await session.prompt(message, { responseConstraint: schema });
const data = JSON.parse(json); // doesn't throw

The example below pulls meeting details and action items out of a messy chat message. Untick the checkbox to ask for the same thing politely, in words, without the constraint.

Interactive example: Extract JSON with a response constraint. It needs JavaScript and runs entirely in your browser.

I ran both versions twice. Without the constraint, Nano wrapped its JSON in a Markdown code fence both times, so JSON.parse fails every time. That is technically JSON, in the same way a letter in an envelope is technically a letter. With the constraint, both runs produced valid JSON with every required key.

A schema guarantees the shape, not the content, though. In all four runs, Nano missed that Priya is bringing the Q3 numbers. It extracted two action items and dropped the third. In the constrained runs it also gave Ken’s lunch booking a due date of “Thursday the 9th”, which the message never states. The schema made sure a due field could exist, and the model filled it with a plausible guess. The practical lesson is to make fields optional unless the text always contains them. A required field invites the model to make something up.

Constrained decoding costs a little latency. It took about 3.9 seconds against 3.1 to 3.5 without the constraint. I’d still take the version that parses every time.

4. Describe an image #

The Prompt API also accepts images when you ask for them in expectedInputs. That opens up a genuinely useful feature: suggesting alt text while someone uploads an image.

Interactive example: Describe an image with multimodal input. It needs JavaScript and runs entirely in your browser.

Each image took about one second and used around 300 tokens of the context window. The descriptions were good, with the same flavour of small mistakes. The park photo came back as “People stroll along a paved path shaded by large trees”, which is correct. On the desk photo, Nano listed a “mouse”, but the photo shows a trackpad. The object detection lab calls the same trackpad a “laptop”. Two models, two confident wrong answers. Apparently trackpads are the dark matter of computer vision.

For alt text, “suggest, then let a human edit” is the right shape anyway. A slightly wrong first draft that someone corrects is much better than the empty alt="" most images get today.

5. Use it as the brain of a small agent #

Nano doesn’t have the tool-calling API that server models have, but constrained JSON gets you surprisingly close. Ask for {"thought": ..., "action": ..., "input": ...} in every reply, run the action in your own code, and feed the result back. That is the classic ReAct loop.

The agent trace lab does exactly this. It lets you switch between Claude and on-device Gemini Nano on the same question, with the same tools, and compare the traces. I expected Nano to fall over. It didn’t, at least not dramatically. Asked how much KV cache Llama 3.1 8B needs for a 32K-token conversation, it searched, read the right passage, called the calculator and answered, in four steps and about eight seconds.

Then I read the trace. It multiplied 128 KiB by 32,000 rather than 32,768, which gives about 3.9 GiB, and then reported “4 GiB” anyway, with a unit conversion in its reasoning that didn’t quite add up. It also skipped the citations the system prompt asked for. The final answer happens to be right, and the steps that produced it are sloppy. That is why I like trace views: the answer alone would have convinced me.

When I’d actually use it #

After an evening with it, here’s my honest scorecard.

Good fits: features that reshape text the user already provided, such as summaries for triage, extracting fields into forms, suggesting alt text, tagging and classification, rewriting drafts. Privacy-sensitive text that shouldn’t go to a server. Features that need to work offline. Anything where the cost per call would otherwise kill the idea.

Poor fits: anything that needs facts the model is supposed to know, long documents (9K tokens goes quickly), long chains of reasoning, and anything that has to work for every visitor. Safari and Firefox users get nothing, and even Chrome users may be on hardware that can’t run it.

That last point shapes the design. Build these as progressive enhancements. The feature should work without Nano, perhaps with a server fallback or with no AI at all, and get better when it’s available. Check availability() before showing a button, and tell users when they will trigger a multi-gigabyte download. The examples on this page all follow that rule, which is why you may be reading a polite “not available in this browser” instead of a demo. Sorry, Safari users. I still think it’s the right call.

A free, private language model in the browser is a genuinely new building block. Just treat it like a very fast intern who never checks their work, and design the product so someone does.

Related articles