Transformers.js en extensiones Chrome bajo Manifest V3 | Keryc
Mientras reconstruías la arquitectura del asistente Gemma 4 para navegador, seguro te preguntaste: ¿dónde corro el modelo, cómo manejo el estado y qué pasa si el service worker se suspende? Esta guía técnica te explica la receta práctica para ejecutar inferencia local con Transformers.js dentro de una extensión Chrome bajo Manifest V3, usando la extensión publicada como mapa de implementación.
Arquitectura general
La división de responsabilidades es la base del proyecto. En public/manifest.json se definen tres puntos de entrada claros:
background.service_worker -> archivo compilado background.js (control y modelos)
side_panel.default_path -> sidebar.html (UI de chat persistente)
content_scripts[] -> content.js (puente con la página web)
La regla de diseño: deja la orquestación pesada en el background y mantiene la UI y los content scripts ligeros. Con eso ganas: una sola instancia de modelo por extensión, menos consumo de memoria y límites de seguridad bien respetados.
¿Quién hace qué?
Background (src/background/background.ts) es el plano de control: ciclo de vida de agentes, inicialización de modelos, ejecución de herramientas y almacenamiento compartido.
Side panel (src/sidebar/*) es la capa de interacción: entrada/salida de chat, streaming y controles.
Content script (src/content/content.ts) es el puente con la página: extracción de DOM y resaltados.
Mantener la historia de la conversación en el background evita cargas duplicadas de modelo y mantiene la UI reactiva.
Mensajería y contratos entre runtimes
Con runtimes separados, la mensajería es el esqueleto que los une. En el proyecto todo está tipado mediante enums compartidos.
Todas las inferencias corren en el background. Ventajas prácticas:
Un único host de modelos para todas las pestañas evita duplicación de memoria.
Artefactos y cache se guardan bajo la origin de la extensión chrome-extension://<extension-id>, no por sitio.
El UI solo renderiza y escucha eventos, no carga modelos.
Herramientas y llamadas de función desde el modelo
Con plantillas tipo Gemma-4, el modelo puede emitir bloques especiales para indicar llamadas a herramientas. Por ejemplo, la salida del modelo puede incluir un token de herramienta como:
Por eso hay una capa de normalización (webMcp) y un parser (extractToolCalls) que convierten la salida en ejecuciones deterministas de herramientas (por ejemplo get_open_tabs, open_url, ask_website, highlight_website_element).
Ciclo de vida de modelos y resiliencia MV3
Manifest V3 introduce un reto: los service workers pueden suspenderse y reiniciarse. Trata el runtime del modelo como recuperable.
CHECK_MODELS revisa cache y estima tamaños pendientes.
INITIALIZE_MODELS descarga/inicializa y emite DOWNLOAD_PROGRESS a la UI.
Después de la inicialización, se reutilizan instancias de larga vida: pipeline de generación y pipeline de embeddings.
Design pattern: recrea o re-inicializa el estado si el worker se levanta otra vez. Mantén inicialización idempotente.
Estado y almacenamiento local
Decidir dónde vive cada tipo de estado es crítico:
Conversación corta y orquestación: memoria en background (Agent.chatMessages).
Preferencias y settings: chrome.storage.local para persistencia.
Historial semántico y vectores: IndexedDB (VectorHistoryDB) para datos voluminosos.
Contenido extraído de páginas: caché en background (WebsiteContentManager) por URL.
Esta separación deja el estado volátil en memoria, las preferencias duraderas en storage y los datos grandes en una DB local.
Permisos y privacidad
Manifest solicita solo lo necesario: sidePanel, storage, scripting, tabs y host_permissions para http(s)://*/* en este caso. Explica al usuario que la inferencia corre localmente dentro de la extensión para reducir preocupaciones sobre datos salientes.
Consejo: pide permisos lo más estrechos posibles y documenta claramente qué se procesa localmente.
Build y despliegue
MV3 exige salidas predecibles por runtime:
Usa una build multi-entry en vite.config.ts y asegúrate de generar exactamente los artefactos que el manifest.json espera (sidebar.html, background.js, content.js).
Mantén el content script como un output auto-contenido para evitar problemas con carga de chunks en tiempo de ejecución.
Objetivo: un artefacto por punto de entrada, en la ruta exacta que declara el manifest.
Patrones y variaciones prácticas
El patrón principal funciona para varios escenarios:
Popup-first assistant: UI rápida en popup, background sigue orquestando.
Side-panel copilot: conversaciones largas en panel persistente con background manejando herramientas.
Per-tab agents: guarda un agent por tabId en background si necesitas contexto por pestaña.
UI híbrida: popup + side panel + options page comparten el mismo background.
Decide dónde vive el estado (global, por tabId o por sitio), coloca la inferencia en background y trata a UI/content como clientes ligeros.
Recomendaciones finales para desarrolladores
Mantén el background como la fuente de verdad para la conversación y el host de modelos.
Implementa parsers robustos para las tool calls del modelo; los outputs no son siempre perfectamente formateados.
Planifica re-inicialización de modelo por la naturaleza efímera del worker MV3.
Reduce permisos y explica el procesamiento local al usuario para mejorar confianza y facilitar la revisión en Chrome Web Store.
Este enfoque te deja con una arquitectura coherente: un host de modelos centralizado, UIs reactivas y content scripts enfocados en DOM. Si quieres reproducirlo, la implementación de referencia está publicada y lista para clonar y estudiar.