我如何构建一个在本地使用 WebGPU 运行 Llama、DeepSeek 和 Mistral 的 Chrome 扩展(无需 Ollama,无服务器)
Source: Dev.to
为什么这个项目?
到目前为止,我们只在 GitHub 仓库或独立站点上看到基于 WebGPU 的 LLM 演示。这是第一个 Chrome 扩展,让喜欢在浏览器中“安装即用”的用户也能获得同样的体验——无需开发环境设置、无需 API 密钥、无需服务器。
我的动机
- 隐私顾虑 – 云 AI 服务会把每个提示发送到远程服务器。我想要一个留在本机的方案。
- “本地 AI” 的复杂性 – 像 Ollama 这样的工具很棒,但它们需要终端命令、模型下载,而且通常需要一台允许的工作笔记本。我需要一个我的非技术朋友(甚至是我妈妈)也能使用的方案。
- 成本 – $20 / 月对于偶尔使用(语法修正、文档摘要、偶尔的编码帮助)来说会累积。我想要一个免费、私密的替代方案。
我构建的
一个在浏览器内部运行 LLM 推理的 Chrome 扩展——无需服务器、无需 Ollama、无需 Docker、无需繁琐。只需安装即可聊天。
三大主要优势
| 优势 | 含义 |
|---|---|
| 隐私 | 消息和模型权重永不离开浏览器。 |
| 成本 | 下载一次模型后,推理免费(无需 API 调用)。 |
| 离线 | 缓存后,模型可在飞机上、地铁里或任何没有互联网的地方使用。 |
权衡: 只能使用较小的量化模型(例如 Llama‑Quant、SmolLM、Phi、DeepSeek‑R1 蒸馏版)才可行。对于日常写作、摘要和代码辅助,它们已经足够。
高层架构
Next.js front‑end
│
└─ useChat (Vercel AI SDK)
│
└─ BrowserAIChatTransport ← custom transport
│
├─ selects provider & model (Zustand store)
├─ obtains language model (ModelManager)
├─ (optional) wraps reasoning models with extractReasoningMiddleware
└─ calls streamText → UIMessageStream
- UI:相同的代码既可以作为普通网页应用运行,也可以作为 Chrome 侧边栏扩展(静态导出)运行。
- 后端:这里的“后端”是通过 WebGPU 使用你的 GPU,而不是 Node 服务器。
- 传输层:
useChat只关心能够接收消息并返回流的传输层,因此 UI 对底层提供者保持透明。
提供者支持
| 提供者 | 描述 | 最佳使用场景 |
|---|---|---|
| WebLLM (MLC) | 基于 WebGPU,支持更大的模型(Llama 3.2、Qwen、DeepSeek R1)。 | 在性能尚可的 GPU 上实现快速推理。 |
| Transformers.js | 通过 WASM 在 CPU 上运行,体积更小。 | 适用于 SmolLM 等轻量模型。 |
| Browser AI (Prompt API) | Chrome 内置的 Gemini Nano。 | 无需下载,开箱即用。 |
所有提供者都实现相同的 LanguageModelV3 接口。ModelManager:
- 实例化正确的适配器。
- 缓存模型实例(以便切换标签页时不会重新下载)。
- 为 UI 发出进度回调。
模型 ID 存放在单一的 models 模块中,经过低显存过滤并标记为 “supports reasoning” 或 “supports vision”。这使得传输层和 UI 都能了解每个模型的能力。
模型下载与初始化
下载权重文件可能有数千兆字节,空白页面会导致糟糕的用户体验。我创建了一个 useModelInitialization Hook,它:
- 检查缓存(
availability() === "available")。 - 如果缺失,触发一次最小化的
streamText调用以开始下载。 - 将进度更新通过管道传递给 UI。
进度可以来自两个来源:
- 模型管理器的回调。
streamText的data-modelDownloadProgress事件。
这两个流会合并为一个进度条,以提供流畅的体验。
处理推理模型
像 DeepSeek R1 这样的模型会在最终答案之前输出一个 … 块。我想在 UI 中展示这种“思考过程”。
- AI SDK 的
extractReasoningMiddleware会解析这些标签。 - 在 UI 端,会检查每个消息片段:
- 如果是推理 → 渲染一个 “ 组件(可折叠)。
- 否则 → 渲染普通文本。
因此,同一条流可以呈现两种不同的显示方式。
代码片段
传输实现(简化版)
// BrowserAIChatTransport.ts
const baseModel = modelManager.getModel(provider, modelId);
const model = isReasoningModel(modelId)
? wrapLanguageModel({
model: baseModel,
middleware: extractReasoningMiddleware({
tagName: "think",
startWithReasoning: true,
}),
})
: baseModel;
const result = streamText({
model,
messages: modelMessages,
...streamOptions, // only pass options the provider actually supports
});
return result.toUIMessageStream();
选项处理
一个坑点: 并非所有模型都支持每个选项(例如
topP、presencePenalty)。传输层只会转发 (a) 当前提供者支持的 并且 (b) 已显式设置的选项。我是吃了大亏才明白的。
要点
- WebGPU 使得在浏览器中进行 LLM 推理变得实用,适用于中等规模的模型。
- 单一传输抽象 让相同的 UI 能够与多个后端通信,而无需重复代码。
- 进度处理 对于在下载大型模型文件时提供良好的用户体验至关重要。
- 推理中间件 提供了一种简洁的方式来展示模型的内部思考过程。
- Chrome 扩展 可以作为私有离线 AI 工具的便捷分发渠道。
接下来是什么?
- 添加对视觉模型的支持(例如 OCR、图像字幕)。
- 尝试量化技巧,以将更大的模型压缩到低显存设备上。
- 完善移动 Chrome 的 UI/UX(侧边面板 vs. 全屏)。
欢迎在 noaibills.app 试用此扩展,如遇到任何问题请提交 issue!
概览
- 静态导出 – Next.js 使用
output: "export"构建,并将所有内容放入extension/ui/。侧边面板加载ui/index.html。 - CSP 问题 – Chrome 扩展不允许内联脚本。构建后脚本会提取 HTML 中的所有内联 “,将其保存为独立文件,并重写 HTML 以引用这些文件。
- WASM 加载 –
transformers.js需要 ONNX Runtime 的 WASM 文件,这些文件无法在扩展中从 CDN 获取。构建脚本会将它们复制到extension/transformers/,并在web_accessible_resources中声明。
结果: 一个代码库,一个构建流程。
- 开发在
localhost:3000运行。 - 生产环境构建为 Chrome 扩展。
持久化对话
我希望聊天能够在关闭标签页和浏览器重启后仍然保留,于是我使用了 Dexie(一个轻量级的 IndexedDB 包装器)并定义了一个简单的模式:
| 字段 | 类型 |
|---|---|
id | string(对话 ID) |
title | string |
model | string |
provider | string |
createdAt | Date |
messages | array of message objects |
当用户从历史记录中选择一个对话时,应用会 rehydrates(重新加载)所有内容——包括所使用的模型——从而让用户能够准确地从上次离开的地方继续。
从旧存储迁移
旧版本将数据存储在 localStorage 中。首次加载时,应用会:
- 检测到旧版数据。
- 批量插入到 IndexedDB 中。
- 删除旧的
localStorage条目。
聊天记录不会丢失。
架构要点
- 单传输模式 – 添加新提供者只需“接线适配器,添加模型 ID”。用户界面保持不变。
- 浏览器限制 – CSP、WASM 加载和存储配额都可以通过合适的构建脚本解决;只需为它们分配时间。
- 进度反馈 – 如果用户看到进度条,他们会等待 2 GB 的下载。空白屏幕会导致放弃。
用例定位
- 本地 AI 足以应对大多数日常任务(草稿、摘要、快速编码问题)。
- 它 不是 替代像 GPT‑4 这样的 大型云模型,但本地运行的 3 B 参数模型可以处理约 80 % 的常规文本工作。
目标受众
- 拥有严格数据隐私政策、阻止使用云 AI 且无法安装 Ollama 或 LMStudio 等桌面工具的组织。
- 需要快速草稿、语法检查或基本推理、且 不需要 API 费用或互联网依赖的团队。
对于需要实时知识或深度推理的任务,云模型仍是更好的选择。
建设者要点
如果你正在构建类似的东西,下面的模式应该可以推广到任何浏览器内运行时:
- 静态导出,并在构建后修复 CSP 和 WASM。
- 模型管理器 + 单传输 抽象,用于提供者。
- IndexedDB(通过 Dexie)用于持久化对话存储以及从旧格式迁移。
试试看吧:noaibills.app
如有疑问或反馈,欢迎随时联系——我很期待收到你的来信!