Skip to content
3 min read · 564 words

WebLLM

WebLLM runs a real language model inside the browser tab, on the user's GPU, via MLC's WebGPU runtime. No server, no API key, no token leaving the machine at inference time. As a battery it is a thin extension of the OpenAI Chat Completions wire shape, which means — unlike the other two on-device batteries — it hands back structured tool calls. You don't parse text here; MLC's compiled grammar already did. That makes it the easiest on-device battery to reason about, and the Ask ADK agent in this site's own header is the proof it holds up in production: a 3B model, a browser tab, grounded answers with citations.

If you run a small model whose compiled grammar occasionally lets a call slip into content instead of tool_calls, the opt-in localToolCallParser recovers it — only when the engine returned no structured call (the engine always wins), the same fallback the other native-API batteries expose. See the shared contract.

ts
import { WebLLMChatCompletionsAdapter } from '@nhtio/adk/batteries/llm/webllm_chat_completions'

const executor = new WebLLMChatCompletionsAdapter({
  model: 'Llama-3.2-3B-Instruct-q4f32_1-MLC',
})

Verified models

WebLLM's prebuilt config advertises roughly a hundred MLC-compiled models. That list is a catalog of what's available, not a claim about what works in an agent loop — and we are not going to launder one into the other. Unlike the transformers.js and LiteRT-LM pages, these rows are not from the gated real-model matrix (it has no WebLLM entries) — they're from Ask ADK production + its eval:

ModelSourceWhat it's for / what bit
Llama-3.2-3B-Instruct-q4f32_1-MLCAsk ADK productionThe Ask ADK docs agent. Hard 4096-token context window — it's the model's compiled limit, and forcing a higher contextWindow silently corrupts output rather than erroring. Budget against 4096.
Qwen3.5-2BAsk ADK evalBake-off tie with Llama-3.2-3B (9/12 on the same eval, with identical failures — pipeline-bound, not model-bound). A viable swap.
(~100 others in the MLC catalog)catalog onlyAvailable to load; we didn't run them. "It's in the catalog" is not "it works."

The 4096 cap is the single most important fact on this page if you're building with the 3B. It is not a contextWindow you can raise — it's the window the model was compiled with, so the ADK's token budgeting has to treat it as a hard ceiling. The Ask ADK showcase walks through exactly how RAG-first budgeting sheds context to fit it.

The cross-runtime embedding trap

Our recommendation: don't mix embedding runtimes in one corpus

If you also use embeddings, think twice before indexing some vectors with a transformers.js embedder and others with the WebLLM/MLC build of "the same" model. We measured it: the same arctic-embed model produced vectors only ~0.77 cosine similar across the two runtimes — different enough that nearest-neighbor search returns garbage when they're mixed. Nothing in the kit stops you; it's your corpus. But it cost us a real migration, so unless you've measured cross-runtime compatibility for your model and retrieval threshold, pick one embedding runtime per corpus and stay there.

Media + errors

Image/audio/document map to Chat Completions content blocks (it's a wire-shape battery); video throws E_UNSUPPORTED_MEDIA_MODALITY unless unsupportedMediaPolicy degrades it. Errors are the typed E_WEBLLM_CHAT_COMPLETIONS_* family (context overflow, stream error, invalid tool-call args) plus E_INVALID_WEBLLM_CHAT_COMPLETIONS_OPTIONS. Full surface in Assembly → LLM batteries.