用途
Gemma Gem 是一个 Chrome 扩展,通过 WebGPU 完全在设备本地运行 Google 的 Gemma 4 模型。它向每个页面注入聊天浮层,并暴露一个可调用工具的智能体循环,能够读取页面、点击元素、填写表单、执行 JavaScript 以及截图,全程不向任何服务器发送数据。本技能用于构建和扩展 Gemma Gem。
架构概览
系统由三部分组成:
- Offscreen Document(offscreen/):通过 @huggingface/transformers 加载 ONNX 模型,运行智能体循环,流式输出 token。
- Service Worker(background/):路由消息,处理 take_screenshot 和 run_javascript。
- Content Script(content/):注入 Shadow DOM 聊天界面,执行 DOM 工具。
此外,agent/ 是一个零依赖模块,定义 ModelBackend 和 ToolExecutor 接口,可作为独立库提取。
安装与构建
前置条件:Node.js 18+、pnpm。
pnpm install
# 开发构建(启用日志、source maps)
pnpm build
# 生产构建(仅错误、压缩)
pnpm build:prod
加载扩展:
- 打开 chrome://extensions
- 启用开发者模式
- 点击“加载已解压的扩展程序”→ 选择 .output/chrome-mv3-dev/
模型在首次打开聊天时自动下载:
- onnx-community/gemma-4-E2B-it-ONNX — 约 500 MB(默认)
- onnx-community/gemma-4-E4B-it-ONNX — 约 1.5 GB
首次运行后模型会缓存在浏览器的 cache storage 中。
核心接口(agent/)
ModelBackend 接口定义 generate 方法,接收消息、工具定义和生成选项,返回 StreamChunk 异步生成器。ToolDefinition 包含 name、description 和 parameters(JSONSchema)。GenerateOptions 包含 maxNewTokens 和 thinking。
ToolExecutor 接口定义 execute 方法,接收工具名和参数,返回 Promise。
Agent Loop 的 runAgentLoop 函数接收用户消息、历史、模型、工具执行器、工具定义和最大迭代次数,返回 AgentEvent 异步生成器。循环中调用 model.generate,遇到 token 类型则产出 token 事件,遇到 tool_call 类型则执行工具并产出 tool_start 和 tool_result 事件,将结果追加到消息历史,遇到 done 则返回。
内置工具
| 工具 | 位置 | 说明 |
|---|---|---|
| read_page_content | Content script | 读取页面文本/HTML 或 CSS 选择器 |
| take_screenshot | Service worker | 将可见标签页截取为 PNG |
| click_element | Content script | 通过 CSS 选择器点击 |
| type_text | Content script | 通过 CSS 选择器向输入框输入文本 |
| scroll_page | Content script | 按像素滚动 |
| run_javascript | Service worker | 在页面上下文中执行 JS |
新增工具
工具定义分布在两处:定义(在 offscreen 智能体中)和执行器(在 content script 或 service worker 中)。
步骤 1 — 定义工具 schema:在 offscreen/tools/definitions.ts 中导出 ToolDefinition,包含 name、description 和 parameters。
步骤 2 — 注册到工具列表:在 offscreen/tools/index.ts 中导入定义并加入 ALL_TOOLS 数组。
步骤 3 — 在 content script 中实现执行:在 content/tools/executor.ts 的 executeContentTool 函数中通过 switch 处理工具名,例如 get_page_title 返回 document.title,read_page_content 根据 selector 返回元素文本或 body 文本,click_element 查找元素并点击,type_text 聚焦输入框、设置值并派发 input 和 change 事件。
步骤 4 — 处理 service worker 侧工具:在 background/tools.ts 的 executeSwTool 中处理 take_screenshot(调用 chrome.tabs.captureVisibleTab)和 run_javascript(调用 chrome.scripting.executeScript),其他工具返回 null 以转发到 content script。
消息路由模式
Service worker 充当消息总线,所有通信使用 chrome.runtime.sendMessage。消息类型包括 TOOL_CALL、TOOL_RESULT、TOKEN、AGENT_DONE、AGENT_ERROR。Offscreen 向 SW 发送 TOOL_CALL,SW 再通过 chrome.tabs.sendMessage 转发给 content script。
模型配置
在 offscreen/model.ts 中使用 @huggingface/transformers 的 pipeline 加载模型。MODEL_IDS 映射 E2B 和 E4B 到对应的 ONNX 模型 ID。loadModel 函数接收模型大小和进度回调,返回 TextGenerationPipeline,配置 dtype 为 q4f16,device 为 webgpu,并通过 progress_callback 报告进度。
设置与持久化
设置通过 chrome.storage.sync 存储。GemmaGemSettings 包含 modelSize(E2B 或 E4B)、thinking、maxIterations 和 disabledHosts。DEFAULT_SETTINGS 默认 modelSize 为 E2B,thinking 为 false,maxIterations 为 10,disabledHosts 为空数组。getSettings 合并默认值和已存储值,saveSettings 合并补丁后保存。disableOnCurrentSite 函数将当前主机名加入 disabledHosts。
Shadow DOM 聊天界面模式
Content script 注入 Shadow DOM 以隔离样式。injectChatOverlay 函数创建宿主 div,附加 closed 模式的 shadow root,注入样式和聊天容器,最后添加到 document.body。
调试
所有日志使用 [Gemma Gem] 前缀。开发构建记录 info/debug/warn,生产构建仅记录错误。
- Service worker 日志:chrome://extensions → Gemma Gem → “Inspect views: service worker”
- Offscreen document 日志(最有用:模型加载、提示词、工具调用):chrome://extensions → Gemma Gem → “Inspect views: offscreen.html”
- Content script 日志:任意页面 DevTools → Console(过滤 [Gemma Gem])
- 所有扩展上下文:chrome://inspect#other
在 offscreen document 日志中重点检查:模型下载进度、完整提示词构建、每轮 token 数、原始模型输出(工具调用解析前)、工具执行结果。
常见模式与注意事项
WebGPU 可用性检查:若 navigator.gpu 不存在则抛出错误,提示使用 Chrome 113+ 并启用硬件加速;请求适配器失败也抛出错误。
Offscreen document 生命周期:Chrome 可能挂起 offscreen document,发送消息前需先 ping。ensureOffscreen 函数检查 chrome.offscreen.hasDocument(),不存在则通过 chrome.offscreen.createDocument 创建,理由为 WORKERS,说明为“Run Gemma 4 inference via WebGPU”。
上下文窗口管理:Gemma 4 支持 128K token,但长上下文会拖慢推理。可通过 clear_context 按页清除历史,或限制存储轮数。trimHistory 函数在消息超过 MAX_HISTORY_TURNS * 2 时截取最后部分。
工具调用解析:Gemma 4 以结构化格式输出工具调用。自定义解析时需防范部分/流式 JSON,safeParseToolCall 函数在 JSON.parse 失败时返回 null。
DOM 工具的 CSS 选择器安全:safeQuerySelector 函数在 document.querySelector 抛出异常时返回 null,以应对模型给出的无效选择器。