Appearance
Chrome 内置 AI (Gemini Nano) 深度解析与流式协议 Bug 排查复盘
归类:前端 / 浏览器原生能力 / 产研工具
发生时间:2026-06-24
状态:✅ 已沉淀
一、问题背景:内置 AI 流式渲染为空的神秘 Bug
在开发浏览器开发者工具箱 DevHelper 的 AI 侧边栏常驻聊天卡片时,我们遇到了一个极其诡异的现象:
- 自定义 API (如 OpenAI/火山引擎等) 模式:对话的回复完美渲染,且随着流式打字效果流畅吐出。
- Chrome 内置 AI 模式:输入问题并发送后,小机器人头像右边瞬间生成了一个白色回复气泡。但随着 AI 输出结束,气泡里的文字全部消失了,最终页面上只留下了一个被撑开的、里面完全是空白的超高白色长方形卡片。
- 错误捕获状态:控制台没有任何 JS 异常抛出,接口也显示请求顺利完成,但内容渲染结果却为空。
二、排查过程与根因分析
通过对通信层(ai-service.js)以及 UI 渲染层(js/tools/index.js)进行端到端的协议比对,我们锁定了流式交付契约的底层差异:
- 自定义 API 模式: 在
callOpenAI中,我们自己实现并维护了一个全量拼接缓冲区。每次触发流式回调,传给外层 UI 的onChunk回调的参数,都是自开头至今为止的所有拼接文本(全量文本):javascriptif (delta) { fullText += delta; onChunk(fullText); // 始终传递 0 ~ 当前位置的所有文字 } - Chrome 内置 AI 模式: 在底层的
callBuiltinAI中,我们原先直接将内置模型迭代器吐出的chunk参数原封不动地抛给了外层 UI 渲染回调:javascriptfor await (const chunk of stream) { result = chunk; onChunk(chunk); // 直接抛出原始数据块 } - 版本契约冲突:
- 在 Chrome 旧版本的一些实验性阶段中,
promptStreaming的迭代器吐出的chunk恰好是当前已生成的“全量累计文本”。 - 但在 Chrome 最新的 W3C 标准 Prompt API 提案中,为了提供高性能的 Token 级交付,流式迭代器吐出的
chunk被统一规范为「纯粹的当前增量 Token」(即只含新吐出的这一个字/词,不含历史累积)。 - 当我们的 UI 渲染端接收到增量 Token 时,在每次触发更新时都粗暴地用
innerHTML进行了整体覆盖:javascriptbubbleContent.innerHTML = renderInlineMarkdown(chunk); - 这就导致了“烂尾覆盖”:第一次迭代收到首字“你”,气泡渲染“你”;第二次迭代收到“好”,气泡渲染“好”(“你”被抹掉);在流式迭代结束的最后一秒,模型通常会发送一个空的结束符、回车或空包。这直接用最后一帧的空字符彻底覆盖了前面生成的所有字,最终在屏幕上留下了一个被多个空换行符撑开的、没有任何字的空白大气泡。
- 在 Chrome 旧版本的一些实验性阶段中,
三、优雅的「自适应拼接兼容」解决方案
为了抹平内置 AI 在不同 Chrome 浏览器版本中的实现差异,同时对 UI 层提供 100% 稳定的全量交付契约,我们在底层 callBuiltinAI 循环内实现了一套极具兼容性的 智能拼接算法:
javascript
try {
if (onChunk && typeof session.promptStreaming === 'function') {
const stream = session.promptStreaming(prompt);
let fullText = '';
for await (const chunk of stream) {
// 智能兼容:自动检测本次返回的是全量还是增量
if (fullText && chunk.startsWith(fullText)) {
// 如果新 chunk 以旧 fullText 开头,说明该版本为「全量累计模式」,直接替换
fullText = chunk;
} else {
// 否则说明该版本为最新规范的「增量 Token 模式」,进行字符累加
fullText += chunk;
}
onChunk(fullText); // 始终为 UI 提供全量渲染接口,消除协议不对等
}
if (typeof session.destroy === 'function') session.destroy();
return fullText;
}
// 非流式兜底处理
const result = await session.prompt(prompt);
if (typeof session.destroy === 'function') session.destroy();
return result;
} catch (e) {
// 容错与自动销毁会话
if (typeof session?.destroy === 'function') session.destroy();
throw new Error(`本地 AI 会话执行失败:${e.message}`);
}该算法的引入彻底治愈了“空白气泡”顽疾,使双模渲染表现完全对等。
四、Chrome 内置 AI (Gemini Nano) 深度知识梳理
为了方便后续复盘与技术调研,在此对 Chrome 原生内置 AI(Prompt API)做体系化的知识梳理:
1. 核心模型
- 搭载的模型为 Google Gemini Nano。它是 Gemini 大模型家族中专为移动设备及个人 PC 等端侧环境定制、低功耗、本地运行的轻量级小语言模型 (SLM)。
2. 与常规云端 LLM (如 GPT-4, Gemini Pro) 的对比
| 特性维度 | Chrome 内置 AI (Gemini Nano) | 常规云端 LLM |
|---|---|---|
| 运行位置 | 100% 用户本地物理设备 (CPU / GPU / NPU) | 云端超大规模 GPU 数据中心 |
| 网络依赖 | 完全离线运行 (首下需联网下载模型组件) | 必须全程保持高速网络连接 |
| 隐私安全性 | 绝对安全。数据决不外传,完美适合敏感商业代码、企业内网日志等机密处理。 | 必须将数据传输至第三方云服务器,有数据隐私泄露和合规风险。 |
| 生成速度与延迟 | 无网络延迟;但单 token 生成吞吐量取决于用户机器的本地硬件算力。 | 存在网络传输握手延迟和排队等待;但生成算力极大。 |
| 模型参数规模 | 极小 (约 1.8B / 3.2B 两个版本)。 | 巨大 (数十亿、数千亿到万亿参数级别)。 |
| 复杂推理能力 | 中等。擅长格式化、翻译、摘要、简单代码解读;超长逻辑与复杂推理容易出现幻觉。 | 极强。支持大规模上下文关联及深度跨学科逻辑分析。 |
3. 运行环境与使用范围限制
- 是否可以在普通网页中使用:可以。只要用户在 Chrome 中开启了对应 flags 选项,Prompt API 会作为全局原生 Web API 暴露在
window.ai下。在普通的本地http://localhost或者是安全的https://网页前端中即可直接调用。 - 是否能限制在扩展插件中使用:虽然普通网页可以调用,但由于该 API 处于 W3C 的实验起步阶段,普通页面的权限可能会受同源策略和沙箱隔离阻碍。Chrome 扩展插件 (Extension)(如 Options, Side Panel, Background Scripts)是目前最推荐、权限体系最稳固的生产环境载体。
4. 本地存储路径与文件大小
- 实际大小:Gemini Nano 模型在本地解压后的大小约为 1.5GB 至 2.0GB。
- 本地存储路径:
- macOS:
~/Library/Application Support/Google/Chrome/OptimizationGuideOnDeviceModel/ - Windows:
C:\Users\<username>\AppData\Local\Google\Chrome\User Data\OptimizationGuideOnDeviceModel\ - 可以在 Chrome 浏览器中输入并跳转
chrome://components页面,找到Optimization Guide On Device Model组件,点击“检查更新”来强制查看或下载它。
- macOS:
5. 功能边界与联网搜索能力
- 能否联网搜索:不能。Gemini Nano 是一个 100% 本地孤立运行的神经网络,它不具备任何联网能力,亦不包含任何外接工具(Tool Use / Agents)。
- 如何实现“联网”或“本地技能”:必须通过调用它的外层环境(即我们的插件代码)代为搜集外部上下文。例如:由 Chrome 插件的
activeTab去获取网页 DOM 文本,或通过插件发起外部fetch联网搜索,提取信息后组装为带有上下文 (Context) 的 Prompt 灌入本地的window.ai接口,以 RAG 的形式完成高级业务支持。