feat: add explicit managed model download sources
Managed speech and OCR downloads previously used catalog-specific source URLs without a global selection. Platform Features now lets users choose ModelScope by default or Hugging Face, while Main validates and freezes that source for each download. Verified coverage remains explicit: downloads never mix artifacts or silently switch sources, and installed models plus ZIP imports stay source-independent. Release note: 可在“设置 → 平台功能 → 通用设置”中选择 ModelScope 或 Hugging Face 作为后续语音输入与 OCR 模型下载源;缺少完整已验证文件或下载失败时不会静默换源。
This commit is contained in:
@@ -29,6 +29,9 @@ GoodBuddy 文档按“文档类型 → 功能域”组织。新增文档应先
|
||||
## 跨功能文档
|
||||
|
||||
- [自动化平台架构](./architecture/automation-platform-architecture.md)
|
||||
- [平台功能页签与模型下载源设计](./architecture/model-download-source-design.md)
|
||||
- [本地文本向量模型与连接设计](./architecture/local-text-embedding-model-design.md)
|
||||
- [全双工实时语音交互设计](./architecture/full-duplex-voice-design.md)
|
||||
- [DeepSeek Harness Runtime 设计](./architecture/deepseek-harness-runtime-design.md)
|
||||
- [跨平台助手产品设计](./design/cross-platform-assistant-product-design.md)
|
||||
- [长期助手路线图](./roadmap/long-term-assistant-roadmap.md)
|
||||
|
||||
@@ -0,0 +1,944 @@
|
||||
# GoodBuddy 全双工实时语音交互设计
|
||||
|
||||
## 文档信息
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| 文档类型 | 跨功能技术与产品架构 |
|
||||
| 状态 | 设计中 |
|
||||
| 版本 | 0.1 |
|
||||
| 日期 | 2026-08-19 |
|
||||
| 适用产品 | GoodBuddy 桌面端 |
|
||||
| 目标平台 | Windows、macOS、Linux,x64 与 arm64 |
|
||||
| 相关基线 | [跨平台助手产品设计](../design/cross-platform-assistant-product-design.md)、[长期助手路线图](../roadmap/long-term-assistant-roadmap.md)、[统一界面设计系统](../../UI-DESIGN.md) |
|
||||
|
||||
本文定义 GoodBuddy 中类似自然通话的全双工实时语音能力,包括本地与云端语音引擎、
|
||||
音频平面、会话状态、打断语义、工具审批、数据留存、失败恢复、跨平台交付和验收指标。
|
||||
|
||||
本文所称“支持本地与云端”是指用户可以显式配置并选择不同语音引擎,不代表系统可以在
|
||||
它们之间自动切换。**GoodBuddy 不设计静默降级。**
|
||||
|
||||
---
|
||||
|
||||
## 1. 摘要与核心决策
|
||||
|
||||
1. 实时语音是独立的 `VoiceSession`,不把现有一次性语音听写改名后直接复用。
|
||||
2. 系统支持三种显式引擎:
|
||||
- 本地模块化全双工:本地流式 ASR、所选 Agent Runtime、本地流式 TTS。
|
||||
- 本地原生全双工:一个本地端到端语音模型同时听、想和说。
|
||||
- 云端原生全双工:通过供应商 Realtime/Live API 进行双向流式音频交互。
|
||||
3. 用户开始会话时冻结引擎、Provider、模型、版本、地域、数据位置、声音、能力和
|
||||
Turn Detection 配置。会话过程中不得静默替换。
|
||||
4. 同一目标内允许有界重试、网络抖动恢复和语义等价的内部执行优化;任何会改变
|
||||
Provider、模型、数据位置、成本、隐私、能力、质量或可感知延迟的替代路径都必须显式。
|
||||
5. 所选引擎不可用时,会话明确进入 `blocked` 或 `failed`,保留可恢复上下文,并提供
|
||||
“重试当前引擎”或“结束后选择其他引擎”。不自动切换本地/云端,不退回听写、纯文本或
|
||||
非全双工模式。
|
||||
6. Renderer 负责麦克风采集、回声消除、低延迟播放和即时打断;Main 负责凭据、会话
|
||||
控制、Provider Adapter、工具权限、持久化和资源回收。
|
||||
7. 音频帧不进入普通 `AgentEvent` 和聊天消息持久化通道。默认只保存最终文本、会话状态和
|
||||
有界诊断,不保存原始录音。
|
||||
8. 语音不能成为新的授权通道。Ask 继续只读,Execute 的工具调用继续经过现有审批控件。
|
||||
|
||||
---
|
||||
|
||||
## 2. 背景与当前基础
|
||||
|
||||
GoodBuddy 当前已经具备:
|
||||
|
||||
- Renderer 中的麦克风入口、录音状态和取消操作。
|
||||
- `getUserMedia` 的单声道采集、回声消除和噪声抑制。
|
||||
- 将完整录音重采样为 16 kHz PCM 的能力。
|
||||
- 基于 `sherpa-onnx` 的本地离线识别、模型下载、ZIP 迁移、选择和删除。
|
||||
- Main 中受信任发送者校验、Zod IPC 输入校验、超时、取消和应用关闭回收。
|
||||
- `AgentRuntime.run()` 的流式文本、工具事件、审批、取消和会话持久化。
|
||||
- Renderer 中的流式聊天时间线、全局通知和可访问的输入控件。
|
||||
|
||||
当前链路仍是:
|
||||
|
||||
```text
|
||||
点击麦克风
|
||||
→ 最多录音 20 秒
|
||||
→ 停止并一次性发送完整 PCM
|
||||
→ 本地离线转写
|
||||
→ 把文本插入输入框
|
||||
→ 用户再次确认发送
|
||||
```
|
||||
|
||||
该链路适合听写,但不具备:
|
||||
|
||||
- 连续流式识别和临时转写。
|
||||
- 同时采集与播放。
|
||||
- 自动轮次检测。
|
||||
- 助手语音输出。
|
||||
- 用户抢话和响应截断。
|
||||
- 音频队列、背压和时钟同步。
|
||||
- 实时语音 Provider 抽象。
|
||||
- 语音会话快照和诊断。
|
||||
|
||||
因此实时语音必须新增会话层,而不是在现有 `SpeechTranscriptionService` 后面简单追加 TTS。
|
||||
|
||||
---
|
||||
|
||||
## 3. 目标
|
||||
|
||||
### 3.1 用户目标
|
||||
|
||||
- 用户可以像通话一样持续说话,不需要每轮点击开始和停止。
|
||||
- 助手可以边生成边说,并显示与实际播放进度一致的文本。
|
||||
- 用户开口时可以自然打断,助手在很短时间内停止出声并开始听取新内容。
|
||||
- 用户始终知道当前使用本地还是云端、具体引擎是什么、音频或转写文本会去哪里。
|
||||
- 本地或云端引擎失败时,用户能看到准确状态并决定下一步,不被系统暗中换模型。
|
||||
- 语音对话继续拥有文本聊天中的项目、知识库、角色、Ask/Execute、工具审批和历史能力。
|
||||
|
||||
### 3.2 产品目标
|
||||
|
||||
- 在六个平台/架构目标上提供统一的上层会话契约。
|
||||
- 先以现有 `sherpa-onnx` 和 Agent Runtime 构建可跨平台交付的本地模块化引擎。
|
||||
- 允许云端 Provider 使用 WebRTC 或 WebSocket,但不把供应商协议泄漏到通用 UI。
|
||||
- 允许高性能设备安装本地原生全双工模型,但按真实能力检测决定是否可选。
|
||||
- 保持 Main-only 凭据、上下文隔离、沙箱、取消、超时、有界输出和关机回收。
|
||||
- 为延迟、打断、回声、音频中断、Provider 错误和成本提供可诊断指标。
|
||||
|
||||
---
|
||||
|
||||
## 4. 非目标
|
||||
|
||||
首期不包含:
|
||||
|
||||
- 唤醒词、后台常驻监听或应用退出后的麦克风采集。
|
||||
- 根据网络、负载、价格或“智能判断”自动选择语音引擎。
|
||||
- 在一个会话内自动从云端切到本地,或从本地切到云端。
|
||||
- 从原生全双工自动退到 ASR → LLM → TTS,或反向切换。
|
||||
- 在实时语音失败后自动改成一次性听写、纯文本发送或系统 TTS。
|
||||
- 默认保存、上传或训练用户原始音频。
|
||||
- 声音克隆、未成年人声音模仿、电话呼入或多人会议。
|
||||
- 使用口头“同意”替代工具审批按钮或键盘确认。
|
||||
- 绕过当前 Agent Runtime 和权限边界的 Provider 直连工具、MCP 或 Connector。
|
||||
- 保证所有本地原生语音模型都能在 CPU 或全部六个发布目标上运行。
|
||||
|
||||
---
|
||||
|
||||
## 5. 术语与全双工范围
|
||||
|
||||
| 术语 | 定义 |
|
||||
| --- | --- |
|
||||
| `VoiceEngineProfile` | 用户保存的语音引擎配置,包含类型、Provider、模型、地域、声音和能力 |
|
||||
| `VoiceSession` | 一次从用户显式开始到结束的连续实时语音会话 |
|
||||
| `VoiceTurn` | 用户输入和助手响应形成的一次可持久化对话轮次 |
|
||||
| 系统级全双工 | 麦克风在助手播放期间继续采集,用户可以随时打断 |
|
||||
| 原生模型全双工 | 同一个模型联合处理持续输入、轮次判断和持续语音输出 |
|
||||
| 模块化全双工 | ASR、Agent Runtime 和 TTS 分离,但系统保持同时听说与可打断 |
|
||||
| 临时文本 | 尚未确认的 ASR 或尚未实际播放的助手文本,不写入长期历史 |
|
||||
| 已提交文本 | 用户轮次已确认,或助手对应音频已实际播放的文本 |
|
||||
| Barge-in | 用户在助手说话时开口,触发立即静音、取消和上下文截断 |
|
||||
| 引擎快照 | 会话开始时冻结的完整、无凭据配置及能力声明 |
|
||||
|
||||
“模块化”不等同于“回退”。当用户明确选择模块化本地引擎时,它就是该会话的唯一正式
|
||||
执行路径。原生模型和模块化引擎之间没有隐式优先级。
|
||||
|
||||
---
|
||||
|
||||
## 6. 不静默降级产品契约
|
||||
|
||||
### 6.1 必须显式的变化
|
||||
|
||||
以下变化不得在活动会话中静默发生:
|
||||
|
||||
- 本地与云端之间切换。
|
||||
- Provider、Endpoint、地域或账号切换。
|
||||
- 模型 ID、模型版本、量化档位或语音角色切换。
|
||||
- 原生全双工与模块化全双工之间切换。
|
||||
- ASR、LLM 或 TTS 组件切换。
|
||||
- 从音频输入改成仅文本输入,或从语音输出改成仅文本输出。
|
||||
- 禁用原本声明可用的工具、知识库、角色或 Execute 能力后继续运行。
|
||||
- 把原始音频改为上传,或改变云端数据地域和保留策略。
|
||||
- 采用明显更慢、更低质量或成本不同的路径。
|
||||
|
||||
### 6.2 可自动进行的恢复
|
||||
|
||||
以下操作可以自动执行,但必须保持同一引擎快照:
|
||||
|
||||
- 同一连接内的丢包恢复、抖动缓冲和音频重排。
|
||||
- 同一 Provider、模型、地域和配置的有限重连。
|
||||
- 同一本地模型进程的有限重启。
|
||||
- 不改变语义、隐私、成本和已声明性能级别的算子或执行 Provider 优化。
|
||||
|
||||
恢复在用户可感知前完成时可不打断界面;持续超过 500 ms、导致音频停顿或创建新远端
|
||||
会话时,必须显示“正在重新连接当前引擎”。所有恢复都进入有界诊断记录。
|
||||
|
||||
### 6.3 失败后的用户决策
|
||||
|
||||
恢复预算耗尽后:
|
||||
|
||||
1. 停止采集上传和音频播放。
|
||||
2. 将临时文本标记为未提交,不伪装成完整轮次。
|
||||
3. 保存已提交文本和脱敏错误。
|
||||
4. 显示当前失败的引擎、影响和建议。
|
||||
5. 提供“重试当前引擎”和“结束语音会话”。
|
||||
6. 用户结束后可以显式选择其他引擎并开始新会话。
|
||||
|
||||
首期不提供自动 Failover 列表。未来即使允许用户预先配置替代引擎,也必须在切换前获得
|
||||
明确确认,并在会话中持续显示新的活动引擎。
|
||||
|
||||
### 6.4 产品级适用范围
|
||||
|
||||
本契约不仅适用于语音。GoodBuddy 中 Provider、模型、Runtime、数据处理位置、工作模式、
|
||||
权限范围和质量档位等影响隐私、成本或能力的用户选择,都不得被静默替换。
|
||||
|
||||
用户明确选择名为“自动”的策略时,系统可以在该策略事先声明的范围内选择,但实际结果和
|
||||
任何能力退化必须可见、可诊断,不能把空结果或不完整结果表示为正常成功。
|
||||
|
||||
---
|
||||
|
||||
## 7. 用户体验
|
||||
|
||||
### 7.1 入口
|
||||
|
||||
现有麦克风入口继续表示“语音输入/听写”,转写进入可编辑输入框,不自动发送。
|
||||
|
||||
实时语音使用独立的“开始语音对话”入口,避免用户误以为点击一次听写会开启持续监听。
|
||||
入口只在以下条件满足时可用:
|
||||
|
||||
- 已选择并验证一个全双工语音引擎。
|
||||
- 当前平台满足该引擎能力要求。
|
||||
- 麦克风权限可申请。
|
||||
- 当前 Conversation 没有冲突的活动请求。
|
||||
- 当前工作模式和引擎能力兼容。
|
||||
|
||||
### 7.2 会话界面
|
||||
|
||||
活动会话显示一个持续可见的语音控制区:
|
||||
|
||||
- 当前状态:准备中、正在听、用户说话、正在思考、助手说话、正在打断、等待审批、
|
||||
正在重连、失败。
|
||||
- 本地/云端徽标、引擎名称和数据去向。
|
||||
- 实时用户转写和与播放同步的助手文本。
|
||||
- 麦克风静音、结束会话和必要的设备入口。
|
||||
- 输入音量与助手播放状态,但不得只用颜色表达。
|
||||
- 云端会话的使用量或成本提示入口。
|
||||
|
||||
“结束语音会话”是活动状态下的唯一主操作。波形和头像动效遵守
|
||||
`prefers-reduced-motion`,关闭动效后仍使用文字和图标表达状态。
|
||||
|
||||
### 7.3 打断
|
||||
|
||||
助手说话期间检测到用户有效语音:
|
||||
|
||||
1. 在 Renderer 立即对当前音频执行 20–40 ms 淡出。
|
||||
2. 清空尚未播放的音频队列。
|
||||
3. 向 Main 发送包含播放位置的 `interrupt`。
|
||||
4. Main 取消当前 Agent/TTS 响应或向 Provider 发送截断事件。
|
||||
5. 尚未播放的助手文本保持临时状态并从会话上下文中移除。
|
||||
6. 输入状态切到用户说话,继续采集,不重新建立会话。
|
||||
|
||||
键盘点击“停止说话”与语音 Barge-in 使用相同取消和提交语义。
|
||||
|
||||
### 7.4 工具和审批
|
||||
|
||||
- Ask 模式继续在 Runtime 边界保持只读。
|
||||
- Execute 模式的工具调用进入现有 Approval Broker。
|
||||
- 等待审批时暂停新的助手音频,可播放一次确定性的短提示,例如“需要你确认一个操作”。
|
||||
- 工具参数、风险、范围和确认操作使用现有可访问审批控件。
|
||||
- 麦克风中的“同意”“确认”或相似内容只作为普通用户文本,不构成授权。
|
||||
- 用户拒绝或取消后,结果作为结构化工具事件返回当前引擎,不私自换模型继续。
|
||||
|
||||
### 7.5 设置结构
|
||||
|
||||
长期设置结构使用一级“语音”分类,并以 `PageTabs` 组织:
|
||||
|
||||
1. **实时对话**:语音引擎列表、默认引擎、能力、数据位置、地域、声音和真实连接测试。
|
||||
2. **语音输入**:现有本地 ASR 模型、一次性听写和麦克风设置。
|
||||
3. **语音输出**:本地 TTS 模型、声音、语速和试听。
|
||||
|
||||
当前“模型连接”中的“语音输入”可在迁移阶段保留,之后移动现有模型管理组件时必须保存
|
||||
已安装模型和选择,不创建第二份设置。
|
||||
|
||||
模型类型选择器当前已经包含四项,不增加第五个分段项来承载实时语音,以免违反
|
||||
`SegmentedControl` 的 2–4 项约束。
|
||||
|
||||
语音引擎卡片必须持续显示:
|
||||
|
||||
- 本地或云端。
|
||||
- Provider、模型和版本。
|
||||
- 支持的语言。
|
||||
- 系统级或原生模型全双工。
|
||||
- 是否支持工具、图像和当前 Ask/Execute 模式。
|
||||
- 所需硬件或云端地域。
|
||||
- 音频和文本的数据去向。
|
||||
- 安装、已验证、不可用或需要凭据状态。
|
||||
|
||||
实际生成能力只能通过一次真实、有界、由用户触发的会话测试确认。配置保存成功或只完成
|
||||
握手不能证明麦克风输入、语音输出和打断均可工作。
|
||||
|
||||
---
|
||||
|
||||
## 8. 总体架构
|
||||
|
||||
```text
|
||||
┌──────────────────────── Renderer ────────────────────────┐
|
||||
│ VoiceSession UI │
|
||||
│ getUserMedia → AudioWorklet Capture → Fast VAD │
|
||||
│ AudioWorklet Playback ← Jitter/Playback Queue │
|
||||
└────────────── control IPC ─────── media MessagePort ───────┘
|
||||
│
|
||||
┌────────────────────────── Main ────────────────────────────┐
|
||||
│ VoiceSessionController │
|
||||
│ ├─ Session snapshot and state │
|
||||
│ ├─ Turn coordinator and interruption │
|
||||
│ ├─ Tool/approval bridge │
|
||||
│ ├─ Transcript/message persistence │
|
||||
│ ├─ Credential and provider policy │
|
||||
│ └─ VoiceEngineAdapter │
|
||||
│ ├─ LocalModularAdapter │
|
||||
│ ├─ LocalNativeDuplexAdapter │
|
||||
│ └─ CloudRealtimeAdapter │
|
||||
└───────────────┬──────────────────────┬─────────────────────┘
|
||||
│ │
|
||||
Local managed sidecar Cloud Realtime API
|
||||
or bounded worker WebRTC / WebSocket
|
||||
```
|
||||
|
||||
### 8.1 Renderer 音频平面
|
||||
|
||||
Renderer 负责需要接近音频设备的低延迟操作:
|
||||
|
||||
- 在用户操作后调用 `getUserMedia`。
|
||||
- 请求单声道、回声消除、噪声抑制和受支持时的自动增益。
|
||||
- 使用 `AudioWorklet`,不继续扩展 `ScriptProcessorNode`。
|
||||
- 将音频切成 10–20 ms 有序帧,并按引擎格式重采样。
|
||||
- 执行快速本地 VAD,用于 Barge-in,不独立提交最终轮次。
|
||||
- 维护有界播放和抖动缓冲,记录实际播放采样位置。
|
||||
- 在打断、设备变化、休眠或窗口销毁时立即静音和释放资源。
|
||||
|
||||
Renderer 不持有长期 API Key、不创建本地模型目录、不决定工具权限,也不持久化原始音频。
|
||||
|
||||
### 8.2 Preload 与 IPC
|
||||
|
||||
控制面使用显式、类型化的 preload 方法:
|
||||
|
||||
- `voice.getSnapshot()`
|
||||
- `voice.startSession(input)`
|
||||
- `voice.stopSession(sessionId)`
|
||||
- `voice.setMuted(input)`
|
||||
- `voice.interrupt(input)`
|
||||
- `voice.respondApproval(...)` 继续复用现有审批接口
|
||||
- `voice.onEvent(listener)`
|
||||
|
||||
音频帧不使用逐帧 `ipcRenderer.invoke`、JSON 或 Base64。Main 通过
|
||||
`MessageChannelMain` 向可信主 Frame 传递专用 `MessagePort`,使用可转移
|
||||
`ArrayBuffer` 和严格的帧头。控制事件和媒体帧分别限速、限长和验证。
|
||||
|
||||
### 8.3 Main 控制面
|
||||
|
||||
`VoiceSessionController` 负责:
|
||||
|
||||
- 每个窗口最多一个活动语音会话。
|
||||
- 解析并冻结 `VoiceSessionSnapshot`。
|
||||
- 建立所选 Adapter,不执行自动 Adapter 选择。
|
||||
- 维护输入、输出和生命周期状态。
|
||||
- 将 Barge-in 传播到 Provider、Agent Runtime、TTS 和播放队列。
|
||||
- 桥接工具调用、审批、问题和取消。
|
||||
- 只提交已经确认或实际播放的文本。
|
||||
- 处理超时、重连预算、应用退出、系统休眠和窗口销毁。
|
||||
- 对错误和诊断执行脱敏与边界限制。
|
||||
|
||||
### 8.4 本地进程边界
|
||||
|
||||
轻量 ONNX 能力可以运行在受控 Worker。需要 Python、CUDA、Metal/MLX 或独立依赖树的
|
||||
原生模型运行在 GoodBuddy 管理的 Sidecar:
|
||||
|
||||
- 只绑定 loopback,不监听外部网卡。
|
||||
- 使用随机端口和每次启动的短期认证值。
|
||||
- 环境变量使用最小 allowlist。
|
||||
- 不继承云端 Provider 密钥。
|
||||
- 模型路径由 Main 从受管目录解析,不接受任意相对路径。
|
||||
- 启动、健康检查、并发、输出、内存、超时和进程树有界。
|
||||
- 应用退出时终止完整进程树。
|
||||
|
||||
Sidecar 不因本地模型启动失败而自行连接云端。
|
||||
|
||||
### 8.5 云端连接边界
|
||||
|
||||
供应商支持 WebRTC 时优先使用其媒体传输、编解码和抖动能力:
|
||||
|
||||
- Main 使用长期凭据创建受限、短时的会话描述或临时凭据。
|
||||
- Renderer 只接收当前会话需要的短期材料。
|
||||
- 工具和业务事件优先由 Main sideband 连接处理。
|
||||
- Provider 不支持 sideband 时,由 Main 拥有 WebSocket,并通过媒体 `MessagePort`
|
||||
与 Renderer 交换音频。
|
||||
|
||||
长期凭据永不进入 Renderer、日志、诊断或会话快照。云端 Profile 必须固定可信 Endpoint、
|
||||
地域和数据说明,不跟随重定向切换到未声明的主机。
|
||||
|
||||
---
|
||||
|
||||
## 9. 共享契约
|
||||
|
||||
建议新增 `src/shared/voice-contracts.ts`,核心结构如下:
|
||||
|
||||
```ts
|
||||
type VoiceEngineKind =
|
||||
| 'local-modular'
|
||||
| 'local-native-duplex'
|
||||
| 'cloud-native-duplex'
|
||||
|
||||
type VoiceComponentRef = {
|
||||
providerId: string
|
||||
modelId: string
|
||||
modelVersion?: string
|
||||
endpoint?: string
|
||||
region?: string
|
||||
accountRef?: string
|
||||
credentialRef?: string
|
||||
}
|
||||
|
||||
type VoiceEngineProfile = {
|
||||
id: string
|
||||
name: string
|
||||
kind: VoiceEngineKind
|
||||
locality: 'local' | 'cloud'
|
||||
voiceId: string
|
||||
components: {
|
||||
asr?: VoiceComponentRef
|
||||
tts?: VoiceComponentRef
|
||||
nativeDuplex?: VoiceComponentRef
|
||||
}
|
||||
dataPath: {
|
||||
audioDestination:
|
||||
| { kind: 'device' }
|
||||
| {
|
||||
kind: 'provider'
|
||||
providerId: string
|
||||
endpoint: string
|
||||
region?: string
|
||||
}
|
||||
transcriptDestination:
|
||||
| { kind: 'device' }
|
||||
| {
|
||||
kind: 'provider'
|
||||
providerId: string
|
||||
endpoint: string
|
||||
region?: string
|
||||
}
|
||||
retentionPolicyId?: string
|
||||
}
|
||||
capabilities: {
|
||||
nativeDuplex: boolean
|
||||
supportsTools: boolean
|
||||
supportsAsk: boolean
|
||||
supportsExecute: boolean
|
||||
inputLanguages: string[]
|
||||
outputLanguages: string[]
|
||||
}
|
||||
}
|
||||
|
||||
type VoiceRuntimeSnapshot = {
|
||||
selection: Exclude<AgentRuntimeSelection, { provider: 'auto' }>
|
||||
profileRevision?: string
|
||||
configurationDigest: string
|
||||
workspacePath: string
|
||||
}
|
||||
|
||||
type VoiceSessionSnapshot = {
|
||||
sessionId: string
|
||||
conversationId: string
|
||||
profile: VoiceEngineProfile
|
||||
profileRevision: string
|
||||
engineConfigurationDigest: string
|
||||
runtime?: VoiceRuntimeSnapshot
|
||||
workMode: 'ask' | 'execute'
|
||||
inputFormat: VoiceAudioFormat
|
||||
outputFormat: VoiceAudioFormat
|
||||
turnDetection: VoiceTurnDetectionConfig
|
||||
startedAt: string
|
||||
}
|
||||
```
|
||||
|
||||
`credentialRef` 和 `accountRef` 是不含凭据正文的稳定引用。Endpoint 写入 Profile 或快照前
|
||||
必须规范化并删除用户名、密码、查询参数和 Fragment;供应商部署路径仍应保留,以便检测
|
||||
Endpoint 是否发生变化。Profile 持久化时只引用 Main 加密设置,快照不包含长期或临时
|
||||
Token。
|
||||
|
||||
模块化引擎分别记录 ASR 和 TTS 组件,原生引擎记录 `nativeDuplex` 组件;不能用一个
|
||||
`modelId` 代表多组件链路。文本 Agent 使用独立 `VoiceRuntimeSnapshot`,记录已解析的明确
|
||||
Runtime、模型 Profile 修订、配置摘要和工作区。`dataPath` 分别说明原始音频和转写文本
|
||||
留在设备还是发送到哪个供应商。
|
||||
|
||||
### 9.1 Runtime Lease
|
||||
|
||||
语音会话不能在每轮请求时重新读取可变的全局 Runtime 设置。启动时必须:
|
||||
|
||||
1. 将 `auto` 解析为明确的 Runtime 和模型 Profile,并在会话界面显示实际结果。
|
||||
2. 根据已解析配置创建或取得一个不可变的 `VoiceRuntimeLease`。
|
||||
3. Lease 在整个 Voice Session 内引用同一个 Runtime 实例和配置摘要。
|
||||
4. 全局设置变化只为新请求和新 Voice Session 创建 Runtime,不替换活动 Lease。
|
||||
5. 用户删除或修改活动 Profile 时,界面说明“下次语音会话生效”;当前 Lease 继续运行。
|
||||
6. 固定实例无法继续时,当前语音会话明确失败,不能取得新的全局 Runtime 继续。
|
||||
|
||||
现有 `AgentRuntimeController` 的可变 `current` Slot 会在 `replace()` 后中断活动请求,因此
|
||||
不能直接作为长期 Voice Session Lease。实现前必须增加引用计数式 Pin/Lease,或由
|
||||
`SelectedRuntimeManager` 为会话持有独立 Runtime Slot;会话结束后再
|
||||
`releaseConversation()` 并释放 Lease。
|
||||
|
||||
### 9.2 事件
|
||||
|
||||
控制事件至少包括:
|
||||
|
||||
- `session-preparing`
|
||||
- `session-ready`
|
||||
- `session-reconnecting`
|
||||
- `input-speech-started`
|
||||
- `input-transcript-delta`
|
||||
- `input-transcript-committed`
|
||||
- `response-started`
|
||||
- `response-transcript-delta`
|
||||
- `response-audio-started`
|
||||
- `response-interrupted`
|
||||
- `response-completed`
|
||||
- `approval-required`
|
||||
- `tool-state`
|
||||
- `usage`
|
||||
- `error`
|
||||
- `session-ended`
|
||||
|
||||
音频帧使用独立二进制协议,包含:
|
||||
|
||||
- `sessionId`
|
||||
- `generationId`
|
||||
- `sequence`
|
||||
- `timestampSamples`
|
||||
- `sampleRate`
|
||||
- `channels`
|
||||
- `encoding`
|
||||
- `payload`
|
||||
|
||||
帧乱序、重复、跨会话或超过大小上限时直接拒绝,不尝试解释为其他格式。
|
||||
|
||||
---
|
||||
|
||||
## 10. 状态模型
|
||||
|
||||
全双工不能只用一个“正在听/正在说”枚举描述。会话使用三个正交状态:
|
||||
|
||||
```text
|
||||
Lifecycle:
|
||||
idle → preparing → active ↔ reconnecting → ended
|
||||
└──────────────→ failed
|
||||
|
||||
Input:
|
||||
muted ↔ listening ↔ speech
|
||||
|
||||
Output:
|
||||
idle → generating → playing → interrupting → idle
|
||||
```
|
||||
|
||||
用户可见状态由三个状态组合得出。合法示例:
|
||||
|
||||
- `input=listening + output=playing`:助手说话,同时继续监听。
|
||||
- `input=speech + output=interrupting`:用户抢话,助手正在停止。
|
||||
- `lifecycle=reconnecting + input=muted + output=idle`:当前引擎重连,停止上传。
|
||||
|
||||
`awaiting-approval` 是运行阻塞原因,不关闭会话;此时输入可以继续听取取消或补充文本,
|
||||
但不能把口头内容解释成授权。
|
||||
|
||||
---
|
||||
|
||||
## 11. Turn、文本与播放提交
|
||||
|
||||
### 11.1 用户输入
|
||||
|
||||
- 流式 ASR Delta 只用于界面。
|
||||
- Endpoint Detector 确认轮次后产生 committed transcript。
|
||||
- 空白、纯噪音和低置信度片段不创建用户消息。
|
||||
- 用户可在提交前通过键盘修正;修正结果而非原始猜测进入 Agent Runtime。
|
||||
|
||||
### 11.2 助手输出
|
||||
|
||||
模块化 TTS 可能落后于文本生成,因此助手文本分为:
|
||||
|
||||
- `generated`:模型已生成,尚未安排播放。
|
||||
- `queued`:已生成音频,尚未播放。
|
||||
- `played`:对应音频已从播放时钟确认输出。
|
||||
|
||||
助手消息需要区分“用户可见历史”和“下一轮模型上下文”:
|
||||
|
||||
- 所有已展示的有界文本和结构化内容都写入可见消息历史。
|
||||
- 可朗读文本记录 `generated`、`queued`、`played` 边界;中断后的消息标记为
|
||||
`interrupted`,并保留用户已经看见的内容及已播放边界。
|
||||
- 下一轮模型上下文只包含 `played` 可朗读文本,以及已经展示的 `visual-only` 内容。
|
||||
- 尚未播放的可朗读尾部即使曾临时显示,也不回送模型,并在历史中显示“未播完”状态。
|
||||
|
||||
代码块、表格、URL、引用和工具结果等不适合逐字朗读的内容使用 `visual-only` Block。它们
|
||||
一旦完整展示即可进入可见历史和下一轮上下文,不受语音播放边界裁切。这样既不会丢失用户
|
||||
已经看到的详细成果,也不会让模型误以为用户听到了被打断的语音尾部。
|
||||
|
||||
云端 Provider 支持会话截断时,Main 使用实际播放位置截断远端 Conversation Item;
|
||||
不支持时由 GoodBuddy 在下一轮上下文中只组装 `played` 和已展示的 `visual-only` 部分。
|
||||
现有消息契约与上下文组装器需要增加对应 Block 状态,不能用删掉完整助手消息来模拟截断。
|
||||
|
||||
### 11.3 文本转语音规划
|
||||
|
||||
模块化引擎从流式文本中产生可取消的短语块:
|
||||
|
||||
- 优先在中文标点、英文句界和自然从句边界提交。
|
||||
- 首个短语不等待完整回答,以降低首音频延迟。
|
||||
- URL、Markdown 标记、代码块、表格、引用编号和工具 JSON 不逐字符朗读。
|
||||
- 不能可靠口述的内容在界面展示,并使用确定性短提示说明“详细内容已显示在对话中”。
|
||||
- 不调用第二个未选择的模型生成“语音摘要”。
|
||||
|
||||
---
|
||||
|
||||
## 12. 引擎设计
|
||||
|
||||
### 12.1 本地模块化全双工
|
||||
|
||||
首个跨平台本地基线复用现有 `sherpa-onnx`:
|
||||
|
||||
```text
|
||||
AudioWorklet
|
||||
→ Silero/TEN VAD
|
||||
→ sherpa-onnx OnlineRecognizer
|
||||
→ selected AgentRuntime
|
||||
→ deterministic speech text planner
|
||||
→ sherpa-onnx TTS callback
|
||||
→ AudioWorklet playback
|
||||
```
|
||||
|
||||
现有 `sherpa-onnx` Node Addon 已提供在线识别、VAD、本地 TTS 和 TTS 音频回调。当前已安装
|
||||
的 SenseVoice、Paraformer 和 Whisper 目录主要用于离线识别;实时模式需要独立的在线
|
||||
模型目录和能力声明,不能把离线模型误标成流式模型。
|
||||
|
||||
“本地模块化”只保证音频采集、ASR 和 TTS 在本机。中间 Agent Runtime 是否本地取决于
|
||||
用户明确选择的模型连接:
|
||||
|
||||
- 连接到 loopback 本地模型时,完整链路可离线。
|
||||
- 连接到云端文本模型时,原始音频留在本地,但最终转写文本和 Agent 上下文会发送到
|
||||
该模型。界面必须明确显示这一数据路径。
|
||||
|
||||
不得因当前文本 Runtime 不可用而替换为另一模型连接。
|
||||
|
||||
### 12.2 本地原生全双工
|
||||
|
||||
本地原生 Adapter 面向 MiniCPM-o、Moshi/PersonaPlex、BayLing-Duplex 等能够持续接收并
|
||||
生成音频的模型。具体模型接入前必须逐个验证:
|
||||
|
||||
- 中文和目标语言质量。
|
||||
- 真正的持续输入、Barge-in 和 Backchannel,而不只是流式输出。
|
||||
- 首音频延迟和长期运行内存。
|
||||
- Windows、macOS、Linux 及 x64/arm64 Runtime 可用性。
|
||||
- NVIDIA CUDA、Apple Silicon 或 CPU 的真实硬件要求。
|
||||
- 工具调用、系统指令、上下文长度和取消支持。
|
||||
- 模型、声音、训练数据与商业分发许可。
|
||||
- 权重下载、ZIP 迁移、校验和、磁盘占用和卸载。
|
||||
|
||||
本地原生模型不作为六平台默认能力。只有能力检测和一次真实会话测试通过后才允许选择。
|
||||
缺少结构化工具能力的模型可以声明为 Ask-only;Execute 入口必须阻塞并说明原因,不能暗中
|
||||
调用另一个文本模型补齐工具。
|
||||
|
||||
### 12.3 云端原生全双工
|
||||
|
||||
云端 Adapter 可以面向 OpenAI Realtime、Gemini Live、Qwen Realtime、Azure Voice Live
|
||||
等正式配置。每个 Adapter 必须显式声明:
|
||||
|
||||
- WebRTC 或 WebSocket 传输。
|
||||
- 输入输出音频格式。
|
||||
- VAD、Semantic Turn Detection 和手动提交能力。
|
||||
- 响应取消、音频截断和实际播放对齐能力。
|
||||
- 输入与输出转写能力。
|
||||
- 工具调用和 sideband 控制能力。
|
||||
- 会话时长、上下文、速率限制和费用。
|
||||
- 可用地域、数据处理与保留说明。
|
||||
|
||||
Provider 配置不使用泛化“OpenAI compatible”推断 Realtime 能力。普通 Chat Completions
|
||||
Endpoint 不能因为 URL 相似就被标记为实时语音。
|
||||
|
||||
---
|
||||
|
||||
## 13. 音频处理
|
||||
|
||||
### 13.1 采集
|
||||
|
||||
- 浏览器设备通常以 44.1 或 48 kHz 采集,不能假定请求值就是实际值。
|
||||
- 使用 `MediaStreamTrack.getSettings()` 记录实际声道、采样率和回声消除状态。
|
||||
- AudioWorklet 以原始设备时钟采集,再按引擎要求转换为 16/24/48 kHz。
|
||||
- 默认单声道 Float32 内部格式,边界处转换为 PCM16、Opus 或 Provider 指定格式。
|
||||
- 每帧 10–20 ms,带序号和采样时间,不使用墙钟猜测播放位置。
|
||||
|
||||
### 13.2 回声与抢话
|
||||
|
||||
回声处理使用两层信号:
|
||||
|
||||
1. Chromium AEC/NS/AGC 处理后的麦克风流。
|
||||
2. GoodBuddy 已知的播放活动、播放能量和 VAD 结果。
|
||||
|
||||
只有满足最短语音持续时间、能量和回声相关性条件时才触发 Barge-in。阈值必须可测试,
|
||||
不能仅依赖一个 Provider 的 `speech_started` 事件。Provider 事件作为权威轮次信号之一,
|
||||
本地快速 VAD 负责先静音。
|
||||
|
||||
### 13.3 播放与背压
|
||||
|
||||
- 每个响应使用独立 `generationId`,旧响应帧不得进入新队列。
|
||||
- 播放队列按采样时钟排序,禁止无限积压。
|
||||
- 达到高水位时对上游施加背压;无法背压的 Provider 丢弃会话并报告协议错误,不能持续
|
||||
增长内存。
|
||||
- 音频缺口使用短静音或 Provider 编解码恢复,不重复上一段语音。
|
||||
- 切换输出设备、设备丢失或系统休眠时暂停提交时钟,避免把未播放文本标记为已听到。
|
||||
|
||||
---
|
||||
|
||||
## 14. 数据与持久化
|
||||
|
||||
### 14.1 默认保存
|
||||
|
||||
- Voice Session ID、Conversation ID 和时间。
|
||||
- 无凭据的引擎快照及其摘要哈希。
|
||||
- 最终用户文本、已展示的助手消息、`visual-only` Block、实际播放边界和中断状态。
|
||||
- 中断、失败、取消和完成状态。
|
||||
- 有界延迟、音频中断和用量指标。
|
||||
- 工具与审批事件继续进入现有任务和活动记录。
|
||||
|
||||
### 14.2 默认不保存
|
||||
|
||||
- 原始麦克风音频。
|
||||
- Provider 返回但尚未播放的音频。
|
||||
- 临时 ASR Delta。
|
||||
- 长期或临时 API Key、Cookie、会话 Token。
|
||||
- Provider 原始错误正文和可能包含用户内容的网络帧。
|
||||
- 回声参考信号、设备唯一标识和完整声学特征。
|
||||
|
||||
未来若提供录音留存,必须是独立、默认关闭的功能,说明保存位置、期限、大小、导出和删除,
|
||||
并与“改进模型”授权分离。
|
||||
|
||||
### 14.3 崩溃恢复
|
||||
|
||||
应用启动时将未结束的 Voice Session 标记为 `interrupted`。恢复文本 Conversation,
|
||||
但不自动重新打开麦克风、不自动连接 Provider,也不重播未完成音频。
|
||||
|
||||
---
|
||||
|
||||
## 15. 错误、重连与资源回收
|
||||
|
||||
| 场景 | 行为 |
|
||||
| --- | --- |
|
||||
| 麦克风权限拒绝 | 阻塞启动,保留引擎选择,提供系统权限说明 |
|
||||
| 输入/输出设备消失 | 立即静音或暂停,要求用户处理设备,不改用未选择设备 |
|
||||
| 本地模型缺失或损坏 | 阻塞启动,进入模型管理,不连接云端 |
|
||||
| 本地 Runtime 启动失败 | 在有界预算内重启同一 Runtime,之后明确失败 |
|
||||
| 云端认证或地域错误 | 明确失败,保留配置,不尝试其他 Provider/地域 |
|
||||
| 短暂断网 | 同一引擎有界重连,超过 500 ms 显示状态 |
|
||||
| Provider 限流或余额不足 | 结束生成并显示原因,不切本地模型 |
|
||||
| Agent Runtime 失败 | 终止当前轮次,允许重试同一 Runtime,不换连接 |
|
||||
| TTS 失败 | 当前轮次失败,不静默改成系统 TTS 或仅文本成功 |
|
||||
| 工具等待审批 | 暂停响应,保留会话;拒绝后把结果返回当前引擎 |
|
||||
| 应用退出/窗口销毁 | 取消请求、停止 Track、关闭 Port/PeerConnection、终止 Sidecar |
|
||||
| 系统休眠/锁屏 | 停止采集和上传;恢复后要求用户显式继续 |
|
||||
|
||||
每个会话必须有最大时长、最大连续无声时间、最大媒体队列、最大临时文本、最大重连次数和
|
||||
最大诊断大小。取消优先于重连和重试。
|
||||
|
||||
---
|
||||
|
||||
## 16. 安全与隐私
|
||||
|
||||
1. 只允许可信主窗口主 Frame 创建和控制 Voice Session。
|
||||
2. 麦克风权限只放行音频,不因实时语音放开视频。
|
||||
3. 任何音频采集都需要用户操作;活动期间持续显示应用内状态和系统麦克风指示。
|
||||
4. 云端会话在开始前显示 Provider、地域、发送内容和可能费用。
|
||||
5. API Key 只在 Main 的加密设置或受控环境变量中使用。
|
||||
6. 临时 Provider 凭据具有最短可行期限、最小能力和单会话作用域。
|
||||
7. Provider 工具调用必须回到 Main 的白名单、Schema、Ask/Execute 和审批边界。
|
||||
8. 本地 Sidecar 只监听 loopback,使用短期认证,不开放外部端口。
|
||||
9. 模型权重按受信任目录、固定来源、大小和 SHA-256 校验,导入 ZIP 防止路径穿越和压缩炸弹。
|
||||
10. 日志只记录状态、耗时、错误分类和匿名引擎 ID,不记录语音正文和音频。
|
||||
11. 窗口隐藏时若会话仍活动,托盘必须持续显示麦克风状态和停止入口;首期可以选择隐藏即
|
||||
暂停,但不能隐藏后无提示继续采集。
|
||||
12. Voice Session 不扩大项目、知识库、文件、浏览器或桌面控制范围。
|
||||
|
||||
---
|
||||
|
||||
## 17. 性能与质量指标
|
||||
|
||||
### 17.1 交互指标
|
||||
|
||||
| 指标 | 目标 |
|
||||
| --- | --- |
|
||||
| 用户开口到本地 VAD 检出 | P95 ≤ 100 ms |
|
||||
| Barge-in 检出到扬声器静音 | P95 ≤ 150 ms |
|
||||
| 播放队列常态深度 | 100–400 ms |
|
||||
| 用户轮次结束到临时文本稳定 | P50 ≤ 300 ms |
|
||||
| 用户轮次结束到首段助手音频 | 云端/原生引擎 P50 ≤ 800 ms;模块化引擎 P50 ≤ 1,200 ms |
|
||||
| 已提交文本与实际播放偏差 | ≤ 100 ms 或一个最小短语块 |
|
||||
| 连续 30 分钟会话 | 无未界定内存增长、重复播放或资源泄漏 |
|
||||
|
||||
本地指标必须注明测试硬件,不能把高端 GPU 结果宣传为 CPU 基线。未达到所选引擎声明的
|
||||
实时系数时,能力检测应标记为不满足实时要求,而不是静默切到更小模型。
|
||||
|
||||
### 17.2 质量指标
|
||||
|
||||
- 中文普通话、英文和中英混合词的 ASR 错误率。
|
||||
- 长停顿、语气词、短回答和自我修正的轮次准确率。
|
||||
- 扬声器回声、键盘声、音乐和旁人说话下的误打断率。
|
||||
- 真正用户抢话的漏检率和停止延迟。
|
||||
- TTS 首段延迟、断句、数字、日期、英文缩写和代码术语可懂度。
|
||||
- 中断后下一轮上下文不包含未播放内容。
|
||||
- Provider、模型、数据位置和能力从不发生未声明变化。
|
||||
|
||||
---
|
||||
|
||||
## 18. 测试策略
|
||||
|
||||
### 18.1 自动化
|
||||
|
||||
- Voice Contract Schema、大小边界和迁移测试。
|
||||
- 三组正交状态及非法状态组合测试。
|
||||
- 有序、乱序、重复、迟到和跨 Session 音频帧测试。
|
||||
- Barge-in 对播放、Provider、Agent、TTS 和持久化的取消传播测试。
|
||||
- 临时文本、已提交文本和播放位置对齐测试。
|
||||
- 同一引擎重连预算与超时测试。
|
||||
- “禁止静默降级”矩阵测试:任何 Adapter、Provider、模型、地域或 Runtime 变化都必须失败。
|
||||
- Ask 只读和 Execute 审批测试。
|
||||
- 窗口销毁、应用退出、休眠和设备丢失的资源释放测试。
|
||||
- 不持久化音频、临时 Token 和 Provider 原始正文的数据库测试。
|
||||
|
||||
### 18.2 模拟与声学测试
|
||||
|
||||
建立确定性 Fake Voice Engine,能够注入:
|
||||
|
||||
- 固定节奏的输入、文本和音频。
|
||||
- 网络抖动、丢包、重复和断开。
|
||||
- 超前文本、迟到音频和错误播放位置。
|
||||
- 用户抢话、回声、短噪音和长停顿。
|
||||
- 工具调用、审批、拒绝和取消。
|
||||
|
||||
真实声学测试使用预录双声道夹具,一路作为助手扬声器参考,一路作为用户麦克风输入。
|
||||
不能只通过静态单段 WAV 验证全双工。
|
||||
|
||||
### 18.3 手动与外部调用
|
||||
|
||||
- 六个发布目标分别验证麦克风权限、采集、播放、设备拔插和应用退出。
|
||||
- 本地模型在声明的最低硬件上完成 30 分钟稳定性和实时系数测试。
|
||||
- 云端 Provider 测试会产生外部调用和费用,只在明确授权的 gated 测试中运行。
|
||||
- 每个云端 Adapter 至少验证一次真实音频输入、真实音频输出、打断和工具审批。
|
||||
- 真实测试失败时不使用配置握手成功替代生成验证。
|
||||
|
||||
---
|
||||
|
||||
## 19. 跨平台交付
|
||||
|
||||
### 19.1 基线
|
||||
|
||||
- 本地模块化引擎作为 Windows、macOS、Linux x64/arm64 的统一功能基线。
|
||||
- 在线 ASR、VAD 和轻量 TTS 权重不内置,继续使用按需下载和 ZIP 离线迁移。
|
||||
- GoodBuddy 托管模型的下载遵守
|
||||
[平台功能页签与模型下载源设计](./model-download-source-design.md),使用用户显式选择的
|
||||
ModelScope 或 Hugging Face,失败时不切换来源。
|
||||
- 云端 Adapter 在六个平台复用同一契约,并分别验证 Electron WebRTC/WebSocket 行为。
|
||||
- 本地原生引擎按 Adapter 声明平台与硬件,不伪装成全平台能力。
|
||||
|
||||
### 19.2 硬件能力等级
|
||||
|
||||
| 等级 | 目标 |
|
||||
| --- | --- |
|
||||
| CPU 基线 | 本地模块化 ASR/TTS;文本 Runtime 可以本地或云端 |
|
||||
| Apple Silicon | 可增加 MLX/Metal 本地原生 Adapter,必须单独验证 |
|
||||
| NVIDIA GPU | 可增加 CUDA 本地原生 Adapter,按显存和驱动验证 |
|
||||
| 不满足要求 | 引擎卡片显示不可用与原因,不自动选择其他引擎 |
|
||||
|
||||
安装包继续保持轻量。大模型权重、CUDA Runtime 和独立 Python 环境不得无条件加入全部
|
||||
发布包。
|
||||
|
||||
---
|
||||
|
||||
## 20. 分阶段实施
|
||||
|
||||
### 阶段 0:契约与模拟器
|
||||
|
||||
- 新增 Voice Contracts、状态机和 Fake Voice Engine。
|
||||
- 建立禁止静默降级测试矩阵。
|
||||
- 建立会话快照、事件和诊断结构。
|
||||
|
||||
### 阶段 1:Renderer 音频平面
|
||||
|
||||
- AudioWorklet 采集与播放。
|
||||
- 媒体 `MessagePort`、背压和播放时钟。
|
||||
- 快速 VAD、回声关联、Barge-in 和设备生命周期。
|
||||
- 实时语音控制区和可访问状态。
|
||||
|
||||
### 阶段 2:本地模块化基线
|
||||
|
||||
- 在线 ASR 和 VAD 模型管理。
|
||||
- Agent Runtime 流式文本桥。
|
||||
- 本地 TTS 模型管理、短语规划、音频回调和取消。
|
||||
- 最终文本持久化与工具审批。
|
||||
- 六个平台/架构验证。
|
||||
|
||||
### 阶段 3:首个云端原生 Adapter
|
||||
|
||||
- Main-only 凭据和引擎 Profile。
|
||||
- WebRTC 或 WebSocket 会话。
|
||||
- 转写、音频、截断、用量和 Provider 错误。
|
||||
- sideband 工具与审批。
|
||||
- 真实有费用的 gated 验证。
|
||||
|
||||
### 阶段 4:本地原生全双工 Adapter
|
||||
|
||||
- 选择一个中文质量、许可和硬件要求已验证的模型。
|
||||
- 建立受管 Sidecar、能力检测和真实会话测试。
|
||||
- 验证原生 Barge-in、文本提交、工具能力和长期稳定性。
|
||||
|
||||
### 阶段 5:扩展与质量
|
||||
|
||||
- 增加经过验证的云端和本地 Adapter。
|
||||
- 输出设备选择和企业语音策略。
|
||||
- 声学基准、延迟仪表盘和成本诊断。
|
||||
- 评估是否允许用户预配置仍需确认的显式替代策略。
|
||||
|
||||
---
|
||||
|
||||
## 21. 首个垂直切片
|
||||
|
||||
首个可合并实现应使用 Fake Voice Engine,不立即绑定某个云端 Provider:
|
||||
|
||||
1. 用户显式开始会话。
|
||||
2. AudioWorklet 持续采集和播放模拟流。
|
||||
3. Fake Engine 产生临时转写、助手文本和音频。
|
||||
4. 用户开口触发 150 ms 内静音和响应取消。
|
||||
5. 持久化已提交用户文本、已展示助手内容、`visual-only` Block 和实际播放边界;下一轮
|
||||
模型上下文只使用已播放文本与完整显示的 `visual-only` Block。
|
||||
6. 模拟工具审批时暂停语音,拒绝口头授权。
|
||||
7. 注入 Adapter 失败后明确结束,不切换任何引擎。
|
||||
8. 关闭窗口后所有 Track、Port、计时器和模拟任务归零。
|
||||
|
||||
该切片先验证最难改变的会话、音频、提交和安全契约,再分别接入本地和云端实现。
|
||||
|
||||
---
|
||||
|
||||
## 22. 验收标准
|
||||
|
||||
- 用户可以明确选择本地模块化、本地原生或云端原生引擎,界面持续显示当前选择。
|
||||
- 会话快照冻结 Provider、模型、地域、声音、数据位置和能力。
|
||||
- 任何引擎、Provider、模型、地域、Runtime 或模式变化都不能在测试中静默发生。
|
||||
- 助手播放期间继续采集麦克风,用户可在 P95 150 ms 内打断。
|
||||
- 中断后未播放音频与文本不进入下一轮上下文。
|
||||
- Ask 和 Execute 在语音中与文本中使用同一权限和审批边界。
|
||||
- 语音口令不能批准工具。
|
||||
- 云端长期凭据不进入 Renderer,本地 Sidecar 不监听外部地址。
|
||||
- 默认数据库、日志和 Artifact 中没有原始音频。
|
||||
- 本地引擎失败不连接云端,云端引擎失败不启动本地模型。
|
||||
- 重连只针对同一引擎快照,并在可感知时显示状态。
|
||||
- 六个平台目标完成各自声明能力的真实采集、播放、取消和资源回收验证。
|
||||
- `npm test`、`npm run typecheck`、`npm run lint` 和生产构建全部通过。
|
||||
|
||||
---
|
||||
|
||||
## 23. 参考
|
||||
|
||||
- [sherpa-onnx](https://github.com/k2-fsa/sherpa-onnx):本地在线/离线 ASR、VAD 与 TTS。
|
||||
- [OpenAI Realtime](https://developers.openai.com/api/docs/guides/realtime):云端实时音频会话与 WebRTC/WebSocket。
|
||||
- [Gemini Live API](https://ai.google.dev/gemini-api/docs/live-api):云端双向实时音频与多模态会话。
|
||||
- [Qwen Realtime](https://help.aliyun.com/zh/model-studio/realtime):云端实时音视频输入与音频/文本输出。
|
||||
- [MiniCPM-o](https://github.com/OpenBMB/MiniCPM-V):本地端到端多模态与全双工候选。
|
||||
- [Moshi](https://github.com/kyutai-labs/moshi):本地原生全双工语音模型框架。
|
||||
- [PersonaPlex](https://github.com/NVIDIA/personaplex):本地可控角色与声音的全双工候选。
|
||||
- [AudioWorklet](https://developer.mozilla.org/en-US/docs/Web/API/AudioWorklet):Renderer 低延迟音频处理基础。
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,8 @@ GoodBuddy 需要用同一条可信文档解析链路服务以下场景:
|
||||
- 后续的合同审阅、表格分析、演示文稿理解和文档转换。
|
||||
|
||||
文档解析不是对话模型的附属功能。它是主进程管理的独立基础能力,设置入口为“设置中心 / 文档解析”。
|
||||
本地 OCR 的模型下载源以
|
||||
[平台功能页签与模型下载源设计](../../architecture/model-download-source-design.md)为准。
|
||||
|
||||
## 2. 当前基线
|
||||
|
||||
@@ -72,11 +74,12 @@ PDF 不是所有文档唯一的中间格式。解析应同时保留:
|
||||
OCR 模型区沿用语音模型管理模式:
|
||||
|
||||
- 应用不内置模型权重;
|
||||
- 用户按需从 ModelScope 下载,下载完成后离线使用;
|
||||
- 用户按需从全局选择的 ModelScope 或 Hugging Face 下载,默认 ModelScope,下载完成后
|
||||
离线使用;
|
||||
- 显示来源、语言、运行时、模型体积、安装与校验状态;
|
||||
- 联网设备可导出已安装模型 ZIP,离线或内网设备可直接导入;
|
||||
- 支持下载进度、取消、删除、ZIP 导入导出、打开模型仓库和受管目录;
|
||||
- “打开 ModelScope”直接显示在 OCR 模型卡片右上角,不使用手动导入折叠区;
|
||||
- “打开模型仓库”直接显示在 OCR 模型卡片右上角,并使用当前下载源对应仓库;
|
||||
- 模型操作即时生效,解析策略仍通过分类页头的“保存设置”提交。
|
||||
|
||||
### 4.1 第一阶段字段
|
||||
@@ -182,11 +185,14 @@ type ParsedDocument = {
|
||||
|
||||
### 7.2 下载与安装
|
||||
|
||||
Tiny、Small 和 Medium 模型均由 PaddlePaddle 官方 ModelScope 仓库提供。Small 是默认推荐档位;Medium 面向更高识别质量,但具有更高内存占用和延迟。每个档位的检测模型、识别模型与字符字典配置分别使用固定提交,并在应用内记录文件字节数和 SHA-256。
|
||||
Tiny、Small 和 Medium 模型使用 PaddlePaddle 发布的规范工件。GoodBuddy 为 ModelScope
|
||||
和 Hugging Face 分别维护固定下载 Target,默认使用 ModelScope。Small 是默认推荐档位;
|
||||
Medium 面向更高识别质量,但具有更高内存占用和延迟。每个档位的检测模型、识别模型与
|
||||
字符字典配置分别使用固定提交,并在应用内记录共同的文件字节数和 SHA-256。
|
||||
|
||||
下载流程:
|
||||
|
||||
1. 主进程从固定 ModelScope `resolve/<revision>/...` 地址读取文件;
|
||||
1. 主进程读取已保存的全局模型下载源,并解析该来源的固定 Target;
|
||||
2. 禁用凭据与缓存,限制重定向次数和单文件大小;
|
||||
3. 写入受管目录下的随机临时安装目录;
|
||||
4. 边下载边计算 SHA-256,并核对完整字节数;
|
||||
@@ -194,7 +200,9 @@ Tiny、Small 和 Medium 模型均由 PaddlePaddle 官方 ModelScope 仓库提供
|
||||
6. 原子重命名为正式模型目录;
|
||||
7. 失败、取消或退出时删除临时文件。
|
||||
|
||||
模型只在下载或用户显式打开仓库时访问网络。OCR 推理从受管目录读取已校验文件,不发起网络请求。
|
||||
单次任务只使用启动时冻结的一个来源。所选来源不可用或缺少任一必需文件时明确失败,
|
||||
不请求另一个来源。模型只在下载或用户显式打开仓库时访问网络。OCR 推理从受管目录读取
|
||||
已校验文件,不发起网络请求。
|
||||
|
||||
### 7.3 离线 ZIP 迁移
|
||||
|
||||
@@ -298,7 +306,8 @@ DOC、XLS、PPT 通过 `DocumentConversionProvider` 转换:
|
||||
- 新增文档解析设置分类和持久化契约;
|
||||
- 建立 `DocumentParsingService`,供聊天和知识库共用;
|
||||
- 将无文本 PDF 识别为可触发 OCR 的明确状态;
|
||||
- 接入 PP-OCRv6 Tiny、Small、Medium 的 ModelScope 下载、校验、ZIP 离线迁移、删除与 WASM Worker;
|
||||
- 接入 PP-OCRv6 Tiny、Small、Medium 的双来源下载、校验、ZIP 离线迁移、删除与
|
||||
WASM Worker;
|
||||
- 实现真实文件测试和六平台验证入口。
|
||||
|
||||
### 阶段二
|
||||
@@ -322,7 +331,8 @@ DOC、XLS、PPT 通过 `DocumentConversionProvider` 转换:
|
||||
- 模型文件损坏时拒绝加载并显示可恢复错误;
|
||||
- 未安装模型时扫描文档提示用户前往“文档解析”下载,文本型文档仍可原生解析;
|
||||
- 下载中可显示文件与总进度并允许取消,失败或取消后不留下已安装状态;
|
||||
- ModelScope 下载与 ZIP 导入均经过同一大小和 SHA-256 校验;
|
||||
- ModelScope、Hugging Face 下载与 ZIP 导入均经过同一大小和 SHA-256 校验;
|
||||
- 所选下载源失败或缺少模型时不会请求另一个来源;
|
||||
- 语音和 OCR 模型可在联网设备导出 ZIP,并在离线设备导入后完成真实推理;
|
||||
- 路径穿越、未知条目、错误模型 ID、篡改文件和超限 ZIP 均被拒绝;
|
||||
- 超页数、超时、取消和关闭不会留下运行任务;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
| 日期 | 2026-08-11 |
|
||||
| 适用产品 | GoodBuddy 桌面端 |
|
||||
| 实施范围 | 第一阶段:可用、可见、可诊断;第二阶段:可调、可优化、可维护 |
|
||||
| 相关设计 | [本地文本向量模型与连接设计](../../architecture/local-text-embedding-model-design.md) |
|
||||
|
||||
## 1. 背景
|
||||
|
||||
@@ -38,7 +39,8 @@ OpenAI 兼容向量模型、RRF 混合检索、知识图谱、任务状态和来
|
||||
3. 保留“模型按需检索”,并新增“每次先检索”模式。后者必须由 Main 进程
|
||||
预检索,不能只依赖提示词要求模型调用工具。
|
||||
4. 知识库新建后不默认启用全部已有知识库;对话中的范围继续由用户显式选择。
|
||||
5. 向量服务不可用时保留全文检索,但必须返回明确降级状态。
|
||||
5. 向量服务不可用时保留全文与中文检索,但必须返回明确降级状态。这是可见的检索通道
|
||||
降级,不得自动切换应用托管模型、Ollama、云端 Provider 或其他向量模型。
|
||||
6. 中文召回使用应用内可控的 CJK n-gram 索引,不新增远程服务依赖。
|
||||
7. 混合检索保留 RRF 候选融合,并增加本地确定性重排、可选的
|
||||
Cohere/Jina 兼容学习型重排、最低相关度和上下文预算。学习型重排失败时
|
||||
@@ -46,7 +48,9 @@ OpenAI 兼容向量模型、RRF 混合检索、知识图谱、任务状态和来
|
||||
8. 向量搜索取消 5,000 分块静默失效,使用有界内存的分页扫描。在没有稳定
|
||||
跨平台向量扩展前,接受本地 CPU 线性扫描,并持续显示性能诊断。
|
||||
9. 向量索引兼容性同时校验 Provider、Model、维度和 Provider Fingerprint。
|
||||
同名模型切换端点后,旧向量不能继续参与召回。
|
||||
同名模型切换端点后,旧向量不能继续参与召回。Fingerprint 的完整模型、编码与
|
||||
数据路径定义以[本地文本向量模型与连接设计](../../architecture/local-text-embedding-model-design.md)
|
||||
为准。
|
||||
10. 失败或取消的重建不能停用上一版已就绪索引。新索引只有完整校验成功后才
|
||||
原子替换当前服务版本。
|
||||
11. 分块设置属于知识库,修改后不会伪装为立即生效。用户需要显式重建索引。
|
||||
@@ -156,6 +160,11 @@ OpenAI 兼容向量模型、RRF 混合检索、知识图谱、任务状态和来
|
||||
“检索测试”是当前知识库的高频诊断操作,通过知识库标题区次操作打开独立
|
||||
工作台,不新增第五个一级页签。
|
||||
|
||||
全局向量模型仍在“设置 → 模型连接 → 向量模型”中配置。应用托管本地模型、
|
||||
用户自行安装的 Ollama/自托管服务和云端兼容服务的界面、数据路径及切换语义以
|
||||
[本地文本向量模型与连接设计](../../architecture/local-text-embedding-model-design.md)
|
||||
为准,知识库页面只显示当前模型、索引兼容性、覆盖率和重建操作。
|
||||
|
||||
对话输入区的知识范围弹层包含:
|
||||
|
||||
1. 已启用知识库多选。
|
||||
@@ -495,7 +504,7 @@ type KnowledgeRetrievalResponse = {
|
||||
|
||||
| 场景 | 行为 |
|
||||
| --- | --- |
|
||||
| 向量查询失败 | 继续全文和图谱检索,显示降级原因 |
|
||||
| 向量查询失败 | 继续已配置的全文、中文和图谱通道,显示降级原因,不切换向量 Provider 或模型 |
|
||||
| 部分文档无向量 | 使用可用文档,显示完成数和失败数 |
|
||||
| CJK 索引迁移失败 | 回滚迁移,不损坏旧 FTS |
|
||||
| 重排失败 | 回退 RRF 排序并显示诊断 |
|
||||
@@ -558,6 +567,7 @@ GoodBuddy 不上传私人检索查询或文档内容。本地诊断至少记录
|
||||
- “每次先检索”在 Runtime 启动前产生检索诊断和引用,即使模型未调用工具。
|
||||
- 未配置向量模型时,中文改写问题仍能通过 CJK 索引召回相关分块。
|
||||
- 向量查询失败时回答可继续,界面明确显示已降级。
|
||||
- 应用托管模型、Ollama 和云端向量连接之间不会自动切换;实际数据路径持续可见。
|
||||
- 10,000 个分块的向量测试能够返回正确 Top K,不出现固定上限空结果。
|
||||
- 同名模型切换端点后,不会读取 Fingerprint 不匹配的旧向量。
|
||||
- 重建失败时,上一版已就绪向量仍能继续召回。
|
||||
|
||||
Reference in New Issue
Block a user