跳到正文
原文
Hugging Face Blog·· 2026-04-23精选AI 评分62

如何在 Chrome 扩展中使用 Transformers.js

How to Use Transformers.js in a Chrome Extension

AI 摘要

Hugging Face 发布教程,讲解如何在 Manifest V3 约束下用 Transformers.js 在 Chrome 扩展中运行本地 AI 功能,并以官方发布的 Gemma 4 浏览器助手扩展为参考实现。

正文 · AI 翻译

我们最近发布了一个由 Gemma 4 E2B 驱动的 Transformers.js 演示浏览器扩展,帮助用户浏览网页。

在构建它的过程中,我们遇到了几个关于 Manifest V3 运行时、模型加载和消息传递的实用观察,值得分享。

适用对象

本指南面向希望在 Manifest V3 约束下使用 Transformers.js 在 Chrome 扩展中运行本地 AI 功能的开发者。

读完后,你将拥有与本项目相同的架构:托管模型的后台 service worker、侧边栏聊天 UI,以及用于页面级操作的内容脚本。

我们将构建什么

在本指南中,我们将重建 Transformers.js Gemma 4 Browser Assistant 的核心架构,以已发布的扩展为参考,以开源代码库为实现地图。

1) Chrome 扩展架构(MV3)

在深入之前,先快速说明范围:我不会深入讲解 React UI 层或 Vite 构建配置。这里的重点是高层架构决策:每个 Chrome 运行时中运行什么,以及这些部分如何编排。

如果你对 Manifest V3 还不熟悉,请先阅读这篇简短概述: 什么是 Manifest V3?。

1.1 运行时上下文和入口点

在 MV3 中,你的架构从 public/manifest.json 开始。本项目定义了三个入口点:

后台 service worker 还处理 chrome.action.onClicked,为活动标签页打开侧边栏。 需要了解的相关入口点:可以使用 action.default_popup 定义弹出窗口,它非常适合快速操作。本项目使用侧边栏进行持久聊天,但编排模式是相同的。

1.2 什么在哪里运行

关键的设计决策是将繁重的编排保留在后台,并保持 UI/页面逻辑轻量。

  • 后台(src/background/background.ts)是控制平面:代理生命周期、模型初始化、工具执行,以及特征提取等共享服务。
  • 侧边栏(src/sidebar/*)是交互层:聊天输入/输出、流式更新和设置控件。
  • 内容脚本(src/content/content.ts)是页面桥梁:DOM 提取和突出显示操作。

这种划分的一个实际结果是,对话历史也存在于后台(Agent.chatMessages):UI 发送诸如 AGENT_GENERATE_TEXT 的事件,后台追加消息、运行推理,然后向侧边栏发出 MESSAGES_UPDATE。

这种拆分避免了重复加载模型,保持 UI 响应,并尊重 Chrome 围绕 DOM 访问的安全边界。

1.3 消息传递契约

一旦运行时分离,消息传递就成为骨干。在本项目中,所有消息都通过 src/shared/types.ts 中的枚举进行类型化。

  • Side panel -> background (BackgroundTasks):
    • CHECK_MODELS,INITIALIZE_MODELS
    • AGENT_INITIALIZE,AGENT_GENERATE_TEXT,AGENT_GET_MESSAGES,AGENT_CLEAR
    • EXTRACT_FEATURES
  • Background -> side panel (BackgroundMessages):
    • DOWNLOAD_PROGRESS,MESSAGES_UPDATE
  • Background -> content (ContentTasks):
    • EXTRACT_PAGE_DATA,HIGHLIGHT_ELEMENTS,CLEAR_HIGHLIGHTS

编排规则很简单:后台是唯一的协调者;侧边栏和内容脚本是专门的工作者,负责请求操作并渲染结果。

典型的请求流程:

  1. 侧边栏发送 AGENT_GENERATE_TEXT。
  2. 后台追加到 Agent.chatMessages 并运行模型/工具步骤。
  3. 后台发出 MESSAGES_UPDATE。
  4. 侧边栏根据更新后的消息列表重新渲染。

2) Transformers.js 集成细节

2.1 模型与职责

在 src/shared/constants.ts 中,此扩展使用两种模型角色:

这种拆分是有意为之:Gemma 4 负责推理/工具决策,而 MiniLM 生成向量嵌入,用于 ask_website 和 find_history 中的语义相似度搜索。

2.2 推理运行位置

所有推理都在后台运行(src/background/background.ts):

  • 通过 pipeline("text-generation", ...) 进行文本生成,并借助我们新的 DynamicCache 类启用一致的 KV 缓存
  • 通过 pipeline("feature-extraction", ...) 进行嵌入,外加向量归一化

这样为所有标签页/会话提供单一模型宿主,避免重复内存占用,并保持侧边栏 UI 响应迅速。由于模型从后台 service worker 加载,产物缓存在扩展源(chrome-extension://<extension-id>)下,而不是按网站源分别缓存,从而为整个扩展安装提供一份共享缓存。

MV3 生命周期注意事项:service worker 可能被挂起并重启,因此模型运行时状态应视为可恢复,并在需要时重新初始化。

2.3 下载与缓存生命周期

模型生命周期是明确的:

权限与隐私是架构的一部分,而不是最后才勾选的复选框。在本项目中,public/manifest.json 请求 sidePanel、storage、scripting 和 tabs,外加用于 http(s)://*/* 的 host_permissions:

  • sidePanel:打开和控制侧边栏 UX 所必需。
  • storage:跨会话持久化工具/设置状态所必需。
  • tabs + scripting:感知标签页的工具和页面级操作所必需。
  • 在 http(s)://*/* 上的 host_permissions:必需,因为内容提取/高亮设计为可在任意网站上工作。

为什么保持权限范围狭窄:权限决定用户信任和 Chrome 应用商店审核风险。只请求你的功能实际需要的内容,并明确说明推理在扩展运行时本地运行,以便用户了解其数据在何处处理。

3) 智能体与工具执行循环

3.1 工具调用基础(为什么存在这一层)

在执行循环之前,有必要了解模型工具调用如何工作(这是任何智能体工作流的基础)。你传入消息以及工具 schema(name、description 和 parameters),Transformers.js 会使用模型的聊天模板根据这些输入格式化实际提示词。由于聊天模板因模型而异,确切的工具调用格式取决于你使用的模型。使用 Gemma-4 风格的模板时,当模型决定调用某个工具,它会发出一个特殊的工具调用 token 块。

import { pipeline } from "@huggingface/transformers";

const generator = await pipeline(
  "text-generation",
  "onnx-community/gemma-4-E2B-it-ONNX",
  {
    dtype: "q4f16",
    device: "webgpu",
  },
);

const messages = [{ role: "user", content: "What's the weather in Bern?" }];

const output = await generator(messages, {
  max_new_tokens: 128,
  do_sample: false,
  tools: [
    {
      type: "function",
      function: {
        name: "getWeather",
        description: "Get the weather in a location",
        parameters: {
          type: "object",
          properties: {
            location: {
              type: "string",
              description: "The location to get the weather for",
            },
          },
          required: ["location"],
        },
      },
    },
  ],
});

在生成时,模型可能输出类似以下内容:

<|tool_call>call:getWeather{location:<|"|>Bern<|"|>}<tool_call|>

这正是本项目设有归一化层(webMcp)和解析器(extractToolCalls)的原因:模型输出必须转换为确定性的工具执行。

3.2 本项目中的工具接口

src/background/agent/webMcp.tsx 将扩展工具归一化为模型友好的形状:

  • name、description、inputSchema、execute

示例工具包括 get_open_tabs、go_to_tab、open_url、close_tab、find_history、ask_website 和 highlight_website_element。

3.3 循环设计(Agent.runAgent)

这里的核心设计选择是将内部模型消息与面向 UI 的聊天消息分离:

  • 内部模型记录(messages):用于 generator(...) 中消息的 system/user/tool/assistant 轮次。
  • UI 记录(chatMessages):用户看到的内容,包括流式输出的助手文本以及工具执行元数据(tools)和性能指标。

执行流程:

  1. 将用户输入添加到 chatMessages,创建占位助手消息,并流式输出 token。
  2. 使用 extractToolCalls.ts 解析流式/最终模型输出为 { message, toolCalls }。
  3. 保持用户可见的助手消息为纯文本,同时工具调用在后台执行。
  4. 将工具结果追加到助手工具元数据,并将结果作为下一轮提示反馈回去。
  5. 重复直到没有剩余工具调用,然后最终确定助手内容 + 指标。

这样既保持用户沟通简洁,又在后台保留确定性的工具循环。

4) 数据边界与持久化

状态放置是另一个在 MV3 中非常重要的架构决策。在此实现中,状态按生命周期和访问模式拆分:

  • 对话状态:后台内存(Agent.chatMessages),用于快速的逐轮编排。
  • 工具偏好:chrome.storage.local,使设置跨会话持久化。
  • 语义历史向量:IndexedDB(VectorHistoryDB),用于更大的本地检索数据。
  • 提取的页面内容:后台缓存(WebsiteContentManager),以活动 URL 为键。

如第 1.2 节所述,将对话历史保留在后台可在 UI 更新之间提供单一规范状态。这样可将短生命周期状态保留在内存中,将持久设置保留在扩展存储中,将大量检索数据保留在本地数据库中。

5) 构建与打包说明

你不需要复杂的构建设置,但 MV3 确实要求每个运行时都有可预测的输出。

目标很简单:每个 Chrome 入口点一个产物,放在 public/manifest.json 期望的确切位置。

最终要点

解锁整个项目的架构选择是清晰的关注点分离:后台负责编排和模型执行,UI 界面保持轻量,内容脚本处理页面访问。

此项目使用侧边栏,但同样的方法也适用于其他设置:

  • 弹窗优先助手:使用 action.default_popup 进行快速交互,由后台负责对话状态和模型执行。
  • 侧边栏副驾驶:在持久面板中保持长时间运行的对话,同时后台处理工具循环和缓存。
  • 每标签页代理:当每个标签页应有自己的上下文时,在后台为每个 tabId 保留一个代理状态。
  • 混合 UI(弹窗 + 侧边栏 + 选项页):所有 UI 入口点都与同一个后台协调器通信,并复用相同的消息契约。

实用规则很简单:决定状态存放位置(global、tabId 或站点作用域),将该状态和模型推理保留在后台(基本上作为后台服务),并让 UI/内容运行时充当专注的客户端。

来源:Hugging Face Blog · huggingface.co

00