Transformers.js + Chrome Extension MV3: полный гайд по локальному AI | AiManual
AiManual Logo Ai / Manual.
26 Апр 2026 Гайд

Как встроить Transformers.js в Chrome Extension: архитектура MV3 и код

Подробный гайд по интеграции Transformers.js в Chrome Extension с Manifest V3. Код, архитектура, подводные камни и готовые решения для запуска AI-моделей прямо

Зачем вам локальный AI в расширении, если есть облака?

Сценарий: вы пишете Chrome-расширение для суммаризации страниц, авто-ответов на почте или офлайн-переводчика. Отправлять каждое нажатие на сервер? Дорого, медленно, а главное — приватность летит к чертям. Chrome Store уже завален расширениями, которые шлют ваш текст на неизвестные API. Пользователи (особенно корпоративные) требуют он-девайс.

С другой стороны — Manifest V3. Гугл убил background pages, ввел service_worker, ограничил eval и кучу всего. Старые расширения на chrome.extension.getBackgroundPage() теперь legacy. Но именно MV3 делает локальный AI жизнеспособным: вы получаете изолированный worker без доступа к DOM, который может грузить модели и считать f32 матрицы без риска для вкладок.

До 2025 года задача «запустить BERT в расширении» требовала костылей с WebAssembly и ручным умножением. Сейчас — просто npm install @xenova/transformers (уже v4 с WebGPU-ускорением). Но если просто воткнуть библиотеку в popup — убьете память и заблокируете UI. Нужна правильная архитектура.

Ключевая идея: Модель живет в service worker'e, общается с вкладками через chrome.runtime.sendMessage. Popup — только тонкий интерфейс. Инференс — фоновая задача с уведомлениями.

Разберем по шагам, как собрать расширение, которое запускает Gemma 4 (или Qwen 2.5) прямо в браузере, не отправляя данные в облако.

Шаг 1: Структура проекта и манифест MV3

Стандартный набор: manifest.json, src/background.js (service worker), src/popup/, src/content/ (если нужен доступ к DOM). Но для AI мы добавляем onnxruntime-web и transformers.js.

{
  "manifest_version": 3,
  "name": "On-device Summarizer",
  "version": "1.0",
  "permissions": ["storage", "activeTab", "scripting"],
  "host_permissions": [""],
  "background": {
    "service_worker": "src/background.js",
    "type": "module"
  },
  "action": {
    "default_popup": "src/popup/index.html"
  },
  "content_scripts": [{
    "matches": [""],
    "js": ["src/content.js"]
  }]
}

Важно: Service worker не имеет доступа к DOM, но может выполнять chrome.scripting.executeScript. Модель загружается в worker, что идеально — он может «спать» между вызовами, а при активации (по сообщению) просыпается.

Шаг 2: Подключение Transformers.js в Service Worker

Установка:

npm install @xenova/transformers onnxruntime-web

В background.js (ES module):

import { pipeline } from '@xenova/transformers';

let summarizer = null;

// Инициализация модели при старте (лениво)
async function getSummarizer() {
  if (!summarizer) {
    // Загружаем квантованную модель (q4) для экономии памяти
    summarizer = await pipeline('summarization', 'Xenova/distilbart-cnn-6-6', {
      quantized: true,  // int8 квантование
      device: 'wasm',   // или 'webgpu' если есть поддержка
    });
  }
  return summarizer;
}

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.type === 'SUMMARIZE') {
    // Асинхронный ответ в MV3: возвращаем true для sendResponse
    (async () => {
      try {
        const model = await getSummarizer();
        const result = await model(request.text, { max_length: 100 });
        sendResponse({ success: true, summary: result[0].summary_text });
      } catch (err) {
        sendResponse({ success: false, error: err.message });
      }
    })();
    return true;  // keep channel open
  }
});

Обратите внимание: мы используем quantized: true. Это снижает размер модели с ~1.4 ГБ до ~350 МБ. Для расширения — критично. Подробнее про квантование в нашем гайде по Transformers v5.

Шаг 3: Общение с Popup и Content Script

Popup — браузерное действие. Он не должен грузить модели. Только посылает запрос worker'у и показывает результат.

// popup.js
chrome.tabs.query({ active: true, currentWindow: true }, ([tab]) => {
  chrome.scripting.executeScript({
    target: { tabId: tab.id },
    func: () => document.body.innerText
  }, async ([result]) => {
    const text = result.result.slice(0, 5000); // лимит на вход
    const response = await chrome.runtime.sendMessage({ type: 'SUMMARIZE', text });
    document.getElementById('output').innerText = response.summary;
  });
});

Но есть нюанс: время жизни service worker. По спецификации MV3 worker может выгрузиться через 30 секунд бездействия. Если модель грузится 3 секунды — успеет. Но если инференс долгий (большая LLM), Chrome может убить worker. Решение: использовать chrome.storage.local для кеширования результата или chrome.runtime.connect() с портом, который держит worker активным.

💡
Для долгих задач (генерация текста) рекомендую разбивать на чанки или использовать стриминг через ReadableStream. В Transformers.js v4 это поддерживается.

Шаг 4: Оптимизация — размер, память, WebGPU

На апрель 2026 года WebGPU доступен в Chrome на 80% устройств (требуется Vulkan/Metal). Если ваша целевая аудитория — энтузиасты с RTX, смело включайте device: 'webgpu'. Это ускорит инференс в 3-5 раз. Но для расширения, которое должно работать на любом ноутбуке, лучше оставить 'wasm' как fallback.

Размер бандла: onnxruntime-web весит ~12 MB. Если добавить модель — еще 300+ MB. Используйте CDN: загружайте модель по URL, а не пакуйте в расширение. В manifest укажите web_accessible_resources для доступа к локальным файлам, но модели лучше тянуть из Hugging Face Hub (кеш через Cache API).

// background.js
const modelId = 'Xenova/distilbart-cnn-6-6';
const quantized = true;
const revision = 'main';
const model = await pipeline('summarization', modelId, { quantized, revision });

Библиотека сама скачает модель при первом запуске и закеширует в IndexedDB. Повторный запуск — мгновенный.

Для более продвинутых сценариев (локальный агент, как в On-device браузерный агент на Qwen), понадобится управление сессиями и контекстным окном. Там же описан stepwise planning.

Типичные грабли и как их обойти

Грабли 1: Service worker не просыпается для загрузки модели

Если пользователь открывает popup первый раз, worker еще не загружен. chrome.runtime.sendMessage может упасть. Решение: в popup слушать событие chrome.runtime.onInstalled или chrome.runtime.onStartup? Нет, в MV3 worker стартует при первом сообщении. Лучше сделать так: popup вызывает chrome.runtime.sendMessage с флагом type: 'WAKE', worker в ответ отдает статус готовности. Если не готов — ждать 200ms и повторять.

// popup
function sendWithRetry(msg, retries = 5) {
  return new Promise((resolve, reject) => {
    const attempt = (n) => {
      chrome.runtime.sendMessage(msg, (resp) => {
        if (chrome.runtime.lastError) {
          if (n > 0) setTimeout(() => attempt(n-1), 200);
          else reject(chrome.runtime.lastError);
        } else resolve(resp);
      });
    };
    attempt(retries);
  });
}

Грабли 2: Память — лимит 512 MB на worker

Chrome может убить worker, если он жрет больше 512 МБ. Модели вроде Gemma 4 2B занимают ~1.5 ГБ в fp16, но в int4 — ~400 МБ. Используйте квантованные версии. Если модель всё ещё большая — выгружайте её после использования: model.dispose() и очищайте кэш.

Грабли 3: Content script не имеет доступа к модели

Не пытайтесь импортировать @xenova/transformers в content script — он выполняется в изолированном мире, без доступа к window и с ограниченным WebAssembly. Только communication через chrome.runtime.

Как НЕ надо делать (худшие практики)

Встречал расширения, где модель загружается в popup при каждом клике. Popup закрылся — модель выгрузилась. Следующий клик — снова загрузка 10 секунд. Пользователи в ярости. Никогда так не делайте.

Второй антипаттерн — скачивать модели без квантования. Расширение раздувается до 2 ГБ, Chrome Store отказывается публиковать (лимит 500 MB). Используйте modelId: 'Xenova/distilbart-cnn-6-6' с флагом quantized: true — это даст модель размером ~350 МБ.

FAQ: быстрые ответы

ВопросОтвет
Какую модель выбрать для суммаризации?Xenova/distilbart-cnn-6-6 — легкая, 350 МБ. Для русского языка Xenova/rugpt3-small-summarization.
Можно ли запустить Gemma 4 в расширении?Да, через Transformers.js v4 с device: 'webgpu'. Gemma 4 2B в int4 занимает ~1.2 ГБ, но Chrome может выгрузить worker. Рекомендую использовать chrome.runtime.connect с портом, чтобы держать его живым.
Поддерживает ли Transformers.js v5? (2026)Transformers v5 (Python) — да, но Transformers.js пока на v4. В планах миграция на ONNX Runtime 2.0.
Что делать, если модель не загружается из-за CORS?Используйте web_accessible_resources в manifest или проксируйте через background fetch.

Финальный код и тестирование

Полный код расширения я выложил в репозиторий (ссылка в профиле). Но главное — понимать архитектуру: model lives in worker, UI — dumb. Если вы освоите этот паттерн, сможете добавить в расширение распознавание изображений (через pipeline('image-classification')), генерацию текста (через pipeline('text-generation', model)) и даже стриминг речи (Kitten TTS — см. гайд).

Напоследок: не гонитесь за самой тяжелой моделью. Для расширения часто хватает DistilBERT или TinyLlama. Локальный AI — это про скорость и приватность, а не про супер-интеллект. Сделайте так, чтобы модель грузилась за 2 секунды и не жрала батарею — и пользователи скажут спасибо.

Подписаться на канал