Skip to content

feat: 电脑端服务出错时可改用候补模型,本机模型可作候补,历史记录写明实际服务商 - #210

Merged
sunerpy merged 3 commits into
mainfrom
feat/fallback-by-error-class
Oct 31, 2026
Merged

sunerpy merged 3 commits into
mainfrom
feat/fallback-by-error-class

Conversation

@sunerpy

@sunerpy sunerpy commented Oct 31, 2026 •

Copy link
Copy Markdown
Owner

候补模型从「只在额度用完时切换」扩展为可选的「服务出错时也切换」,默认关闭(路线图 R11,计划 PR-7);同时承接原 PR-5 的「历史记录写明实际服务商与是否候补」。用户决定 D25、D26 见下文。手机端只能显示新的行状态,没有开关,行为不变。

用户能看到的变化

  • 设置 › 语音模型 / AI 模型 › 额度用完后改用其他模型,在电脑上新增开关「服务出错时也改用候补」,默认关闭;关闭「额度用完后改用其他模型」时这个开关不可用。打开后,以下情况也按顺序改用列表中的下一个模型:
    • 请求超时、网络错误、服务器出错(5xx)、服务繁忙(503)、请求过于频繁(429);
    • 本地识别时显卡、内存或模型出错。
    • 密钥无效、请求有误等错误不切换,照常报告。
  • 上限:出错的模型约 1 分钟内跳过(服务商说明了等待时间时按它的时间,最长一天),列表中显示「出错,暂时跳过 · <时间> 再试」,时间到后自动恢复;「重新检查」同时清除额度与出错记录。一次请求有 3 个模型出错后不再尝试,额度用完的模型不计在内(用户决定 D25);识别约 150 秒、润色约 60 秒后不再尝试,报告超时。开关关闭时与之前完全相同:只有额度用完才切换。
  • 本地模型可以作为候补:语音识别的候补列表可以添加已下载的本地模型(按名称列出)。所选的是云端服务时,出错或额度用完可以改用列表中的本地模型。
  • 选用本地识别时录音不会上传:当前选择的是本地模型时,列表中的云端服务显示「选用本地识别时,出错不会改用云端服务」,不参与切换;开启「服务出错时也改用候补」后,本地模型出错只改用列表中的本地模型。
  • 隐私说明:开关打开时,服务商列表下方的去向说明写明「额度用完或服务出错时改发给 …」。
  • 历史记录:记录详情的技术信息新增「识别服务商」与「润色服务商」一行,例如「Groq · 候补模型 · 所选模型出错」,或「候补模型 · 所选模型额度已用完」(用户决定 D26:服务商与原因单独一行,模型仍在原来的「识别模型」一行)。旧记录没有这些信息,不显示这一行。同步到手机的记录也带这些字段。
  • 导入音频或视频仍然不使用候补模型。
  • 已有的错误提示文字不变。

文档与截图

  • docs/dictation.md:新增 §3.14「服务出错时改用候补」(触发表、冷却、上限、本地候补、本地为主不上传、导入、历史字段、状态与界面),§3.5 三处指向它。
  • docs/acceptance.md 两行(行为、界面)。
  • 站点中英文:docs/site/{,zh/}recognition/cloud.md(「服务出错时也改用候补」一节、历史说明)、docs/site/{,zh/}recognition/local.md(出错时改用其他本地模型,本地为主不上传)、docs/site/{,zh/}dictation/history.md(详情里的服务商与候补原因)。站点文档同步检查通过。
  • 截图:重拍 docs/site/public/screens/local-{zh,en}-light.webp。这两张图背景里露出候补区块,区块多了开关一行。其他截图不受影响:历史页截图的示例记录没有服务商字段,AI 模型页截图拍不到候补区块。

验证(head c59bab8520fd6426b520752f35bb2d136ddabfd7)

  • make verify(干净 HEAD):commit=c59bab8520fd6426b520752f35bb2d136ddabfd7 dirty_paths=0 gates=all,21 个门禁全部通过(overall_exit=0)。Rust 过滤后行覆盖 92.82%;前端行覆盖 shared 95.42%、ui 97.89%、desktop 97.57%、mobile 97.69%、mobile-rn 97.31%(36 个测试套件、278 个测试)。
  • Windows 真机 scripts/windows-remote.sh gate test(日志开头 commit c59bab852):本 PR 涉及的测试全部通过,包括 voltip-core lib 566 个、tests/fallback.rs 4 个、tests/long.rs 15 个、tests/import.rs 11 个、voltip-cloud 27 个、voltip-asr-local 45 个、contract.rs 13 个。失败的 2 个是 main 上就有、与本 PR 无关的 voltip-relay socket.rs gone-silent 和 voltip-transport link.rs probe。
  • 托管 CI:手动运行的 ci.yml run 41190735559(head c59bab85)全部成功,其中 macos-aarch64 · build + native run、macos-x64 · build + native run 和 CI Success 都通过。PR 本身的检查见下方。
  • 变基到 main fd31c75e(feat: 电脑端词典新增学习建议,一键把 AI 润色改正的写法加入词典或忽略,规则页提示有问题的规则 #208、feat: 电脑端新增自动备份,每周、更新安装前和替换导入前各备份一份,每台电脑保留自己最近 5 份 #209)时没有冲突。核对过:COMMANDS、UiState、UiEvent、ALL_EVENT_TAGS 本 PR 没有改;HistoryEntry 与 history/revs.rs 的 shallow() 只有本 PR 改;设置里 feat: 电脑端新增自动备份,每周、更新安装前和替换导入前各备份一份,每台电脑保留自己最近 5 份 #209 的 backup 字段与本 PR 的 on_outage 都在;docs/dictation.md 的 §3.14、§4.6.1、§16.10、§28、§29、§31 都在;站点 dictation/history.md 保留 feat: 电脑端新增自动备份,每周、更新安装前和替换导入前各备份一份,每台电脑保留自己最近 5 份 #209 的「自动备份」小节。变基后重新生成 IPC 夹具,没有变化。
  • goal gate:变基前第 1 轮 FAIL,1 个阻塞项(超大历史条目同步到手机时,有界投影丢掉新增的服务商与候补字段),修复并补回归测试后第 2 轮 PASS。变基没有冲突,所以没有再跑。
  • 变基前做过的验证(变基没有改动这些代码):
    • 先写测试、确认失败:Rust 的新测试在实现前都因为功能缺失失败,修 gate 意见时也先让测试失败再改。导入不走候补的两个回归测试,在去掉导入关闭候补的代码后都失败。前端测试是和实现一起写的;把实现文件换回基线后,9 个新测试都因为功能缺失失败。
    • 满载压测:32 核占满,voltip-core 的候补、冷却与截止点测试,tests/long.rs 两个分段候补测试,tests/fallback.rs 全部,tests/import.rs 两个导入测试,各 200 次,0 失败。
    • Linux 开发版真入口(pnpm tauri dev -- --bin voltip-desktop,Xvfb,独立数据目录,内存密钥库,两个只监听 127.0.0.1 的假 OpenAI 兼容服务,本地模型 paraformer-zh 与一个故意损坏的 qwen3-asr-0.6b):
      • 开关关闭时 503 不切换;
      • 打开后 503 改用候补,历史记 outage;
      • 冷却期内跳过出错的服务,一分钟后恢复;
      • 401 不切换;
      • 不回应的服务在客户端超时后改用候补;
      • 两个服务都不可用时改用本地模型;
      • 选用本地模型(损坏)时改用 paraformer-zh,两个假服务都是 0 次请求;
      • 中文界面与历史详情显示正确。
      • 没有调用生产或内置服务,没有用户密钥。

需要在用户电脑上做的验证

  1. Windows 与 macOS:打开「服务出错时也改用候补」,让一家真实的云端服务出错(例如断网,或把自定义接口指向一个返回 5xx 的地址),确认改用下一个模型,历史详情写明「识别服务商 … · 候补模型 · 所选模型出错」;把密钥改错,确认不切换。
  2. 带 GPU 的电脑:本地模型在显卡上出错(Vulkan 设备错误或显存不足)时,改用列表中的其他本地模型,录音不上传。这里只在 CPU 上用损坏的模型文件验证了「模型出错」。
  3. Windows、macOS 默认窗口大小:「语音模型」「AI 模型」页候补区块的新开关和说明不被截断;历史详情的服务商一行完整显示。

未决项

  • 无需用户决定的未决项。D25(尝试次数只计出错的模型)、D26(历史详情单独一行服务商)已由用户确认保留。
  • 基线已有、与本 PR 无关:超大历史条目同步到手机时,有界投影本来就清空 asr_note 与 refine_failure,没有改。

风险与回滚

  • 多花请求费用、音频发往另一家服务:只在用户打开开关、并且列表里列了那家服务时发生。冷却、每次请求最多 3 个出错的模型和总时长上限限制了额外请求。选用本地识别时,录音永远不会因为故障上传。
  • 错误类型更细:云端与本地客户端的超时、网络、服务器错误现在带类型返回,消息原文与之前相同,界面提示不变。开关关闭时,切换规则与之前完全相同(只看额度用完)。
  • 设置与历史:新增字段都是可选的(serde(default),空值不写),旧设置和旧历史照常读取;旧版本读到新字段会忽略。降级时有一处例外:候补列表里加了本地模型的话,旧版本照常听写,但在删掉这一项之前,保存语音模型设置和「换用其他模型重新识别」会被旧版本的检查拒绝(旧版本不允许本地模型作候补)。
  • 回滚:revert 这个 squash 提交即可。已写进历史的服务商字段留在记录里,旧版本忽略它们。

- 候补模型新增「服务出错时也改用候补」,默认关闭:请求超时、网络错误、服务器出错(5xx、503)、请求过于频繁(429),以及本机识别的显卡、内存或模型出错时,按顺序改用下一个模型;密钥无效、请求有误、取消不切换
- 出错的模型约 1 分钟内跳过(429 按服务给的等待时间,最长一天),之后自动恢复;每次请求最多尝试 3 个出错的模型,识别一次最长 150 秒、润色 60 秒
- 已安装的本机整段模型可以加入语音模型的候补列表;选用本机识别时,列表里的云端服务不参与,出错也不会把音频上传到云端
- 历史记录写明实际识别、润色的服务商,是否由候补模型完成以及原因(额度用完或服务出错),详情页显示
- 云端识别与润色客户端的超时、网络、服务器错误带类型传到核心,提示文字不变;导入音频或视频仍不走候补链
- 手机读取新的状态字段(出错跳过、本机识别不改用云端),不提供开关
- 文档 §3.14,站点云端识别、本机识别与历史记录页
@codecov

codecov Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.78431% with 41 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
packages/shared/src/fallback.ts 37.50% 10 Missing ⚠️
packages/shared/src/mock-backend.ts 33.33% 8 Missing ⚠️
crates/voltip-core/src/engines.rs 97.46% 6 Missing ⚠️
...ckages/ui/src/features/engines/FallbackSection.tsx 85.71% 1 Missing and 4 partials ⚠️
crates/voltip-core/src/dictation/fakes.rs 90.47% 4 Missing ⚠️
packages/shared/src/providers.ts 91.42% 0 Missing and 3 partials ⚠️
crates/voltip-core/src/dictation/engine.rs 99.16% 2 Missing ⚠️
crates/voltip-asr-local/src/sherpa.rs 0.00% 1 Missing ⚠️
crates/voltip-asr-local/src/transcriber.rs 96.15% 1 Missing ⚠️
...ackages/ui/src/features/engines/ServiceSummary.tsx 85.71% 0 Missing and 1 partial ⚠️

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #210      +/-   ##
==========================================
+ Coverage   93.43%   93.49%   +0.05%     
==========================================
  Files         452      452              
  Lines       71984    72987    +1003     
  Branches     4557     4594      +37     
==========================================
+ Hits        67257    68237     +980     
- Misses       4099     4118      +19     
- Partials      628      632       +4     
Flag Coverage Δ
rust 93.74% <98.79%> (+0.09%) ⬆️
web 92.37% <75.22%> (-0.13%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
apps/desktop/src/pages/history/HistoryDetail.tsx 86.48% <100.00%> (+0.10%) ⬆️
...desktop/src/pages/settings/engines/EnginesPane.tsx 100.00% <ø> (ø)
crates/voltip-asr-local/src/streaming.rs 98.00% <100.00%> (ø)
crates/voltip-cloud/src/dashscope.rs 96.96% <100.00%> (+0.08%) ⬆️
crates/voltip-cloud/src/lib.rs 96.56% <100.00%> (+0.56%) ⬆️
crates/voltip-core/src/dictation/fallback.rs 99.48% <100.00%> (+0.47%) ⬆️
crates/voltip-core/src/dictation/long.rs 98.76% <100.00%> (ø)
crates/voltip-core/src/dictation/mod.rs 100.00% <100.00%> (ø)
crates/voltip-core/src/dictation/ports.rs 90.14% <100.00%> (+1.12%) ⬆️
crates/voltip-core/src/dictation/redecode.rs 97.90% <100.00%> (-0.16%) ⬇️
... and 21 more

... and 6 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sunerpy
sunerpy merged commit 90537f0 into main Oct 31, 2026
31 checks passed
@sunerpy
sunerpy deleted the feat/fallback-by-error-class branch October 31, 2026 11:09
@github-actions github-actions Bot mentioned this pull request Oct 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant