做一个在浏览器里跑自动化工作流的扩展,其中一类节点是"用AI总结、分类或抽取这段文本"。要让这一步不需要API密钥、也不把页面内容发到任何地方,模型就必须跑在扩展内部。
![]()
这件事拆下来是:三套运行时、两个Web Worker、一次CSP意外,以及一条给没有WebGPU的机器准备的兜底路径。重点不在概念,在管道怎么接。
为什么要把模型塞进扩展里
大多数"AI自动化"步骤实际干的事很朴素:总结一个页面、打一个情感标签、从HTML里抠出五个字段。这类活不需要数据中心里的前沿模型。一个1–4B参数的模型本地跑就够用,免费,而且文本始终不离开这台机器。
当某一步真的需要深度推理时,用户可以把那一个节点切到OpenAI、Anthropic、Google或本地的Ollama。目标是让本地成为默认,而不是唯一选项。
三套运行时,各管一摊
没有一个"在浏览器里跑模型"的库能覆盖全部场景,所以扩展里装了三套运行时。另外还有一个节点,在浏览器暴露Chrome内置AI时调用它。一个"本地模型"页面会列出每套运行时下已安装的模型,并允许下载或删除,用户不必关心某个模型跑在哪个引擎上。
推理必须离开主线程。扩展的界面是一个Svelte应用,挂在侧边栏和标签页里。一个2GB的模型如果在那个线程上编译,画布会直接卡死。所以第一条规则很简单:每套运行时都跑在专属的worker里。
整体布局是:两个worker干重活,界面只负责发请求和画结果。能上显卡就上显卡,上不了就用WASM——慢一些,但任何机器都能跑。
聊天走WebLLM worker
下载、编译、生成全部发生在worker里,侧边栏因此保持响应。WebLLM的worker侧代码相当干净,三行就够:引入WebWorkerMLCEngineHandler,实例化,把onmessage接到handler上。
麻烦在于:为了让本地模型和云端模型插进同一套agent代码,这里用了LangChain的ChatWebLLM包装器。它在构造函数里自己建了一个主线程引擎,没有办法从外部传一个进去。但WebWorkerMLCEngine实现了同一个MLCEngineInterface,所以可以自己建worker引擎再替换进去。
代码里最关键的是那段兜底:worker路径失败时(没有WebGPU、下载出错),先terminate掉worker避免线程被孤立,再回退到主线程引擎初始化,而不是让整个节点崩掉。
任务走第二个worker
transformers.js和TensorFlow.js共用一个worker。它按模型id缓存pipeline,所以工作流第二次运行时不会重新加载任何东西,同时把下载进度流式回传给界面。
任务pipeline只有在适配器存在、且该任务在"已知可在WebGPU上正常工作"的名单里时才走WebGPU。有些pipeline在WASM上没事、在WebGPU上不稳定,所以这份白名单是按任务划分的,不是全局开关。WebGPU的探测只做一次并记住结果,避免反复请求适配器。
MV3的两条坑,第一条花掉一个下午
默认情况下,transformers.js里的ONNX Runtime Web会从CDN加载它的WASM加载脚本和二进制文件。而Manifest V3的扩展页面带着这样的CSP:script-src 'self' 'wasm-unsafe-eval'; object-src 'self'。
在MV3里你没法往script-src里加远程主机。于是这个import被拦下,本地模型一个都跑不起来,而报错信息并不会把原因指得很明白。
修法是反过来:把ONNX Runtime的资源自己打包进扩展(一个小Vite插件在构建时把它们复制到/assets/ort/),并在第一次调用pipeline之前,把运行时的wasmPaths指向同源的那份拷贝。另外,'wasm-unsafe-eval'是允许WebAssembly编译的前提,manifest的CSP里没有它,这一整套都不成立。
存储是用户的,就得让用户看见
模型很大,用户需要看到自己磁盘上装了什么、并且能删掉。transformers.js的模型存在Cache API里,这部分好办。WebLLM更麻烦:它不暴露单个模型的字节大小。本地模型页面退而用navigator.storage.estimate()拿总量,只在运行时自己上报时才显示单模型体积。这是诚实的近似,不是编出来的数字。
小模型擅长什么
在真实工作流里跑几个月后,分界线很清楚。小的本地模型擅长狭窄、定义明确的任务。需要帮忙的也有:工具调用——小模型没有工具调用API,只能靠提示词加解析。不擅长的则是长链条多步推理,以及任何答错代价很高的事,这时候就把节点切到更大的模型。
诚实的成本在第一次运行:要付一次下载,从分类器的几十MB到聊天模型的几个GB。之后就有缓存,启动很快。最简单的端到端工作流是:读当前页面,用本地模型总结,展示结果。模型下载一次,之后离线可用。
如果要在自己的扩展里做这件事,短版本就一句:本地为默认,但不限于本地。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.