feat(asr): 支持 OpenRouter 作为 ASR 提供商 (#582)#591
Conversation
OpenRouter 的 /audio/transcriptions 与 OpenAI 路径相同、也走 Bearer,但请求体是
application/json:{model, input_audio:{data:<base64 wav>, format:"wav"}},而非 OpenAI
官方的 multipart/form-data。直接当 whisper 兼容 provider 会被 OpenRouter 拒绝。
- whisper.rs: 新增 AsrRequestFormat{Multipart,OpenRouterJson},transcribe_chunk 按格式
分发;OpenRouterJson 走 JSON+标准 base64(带 padding),不带 multipart 专属的
prompt/response_format 字段。用 with_request_format builder 避免改 new() 签名(不动
既有 4 处构造点)。
- coordinator.rs: is_whisper_compatible_provider 注册 openrouter;新增
whisper_request_format;verbose_json 对 openrouter 保持关闭;base64 膨胀 ~33%,长
录音按 30s 切分(同 zhipu)。
- 前端: ASR_PRESETS / AsrPresetId / Overview 名称映射 + 5 份 i18n 加 openrouter。
- 测试: OpenRouterJson 发 JSON body 单测 + 注册/格式断言;既有 transcribe 测试不变。
默认 model 用报告人已验证的 openai/whisper-large-v3-turbo。
新增直接依赖 base64=0.22(此前已是传递依赖,未引入新 crate)。
|
@claude 请审核这个 PR:新增 OpenRouter 作为 ASR provider(请求体 JSON+base64)。重点看:(1) transcribe_chunk 的 match 分支里 wav 在两臂分别 move/borrow 是否有隐患;(2) OpenRouterJson 是否正确排除了 multipart 专属字段(prompt/response_format);(3) base64 标准编码(带 padding)是否符合 OpenRouter 要求;(4) verbose_json 对 openrouter 保持关闭是否正确;(5) 长录音 30s 切分是否合理。 |
|
Claude finished @appergb's task in 2m 51s —— View job PR 代码审核:OpenRouter ASR 支持
审核结果整体实现质量良好,核心路径的正确性可以确认。以下逐点分析。 (1) transcribe_chunk 中 wav 的 move/borrow结论:✅ 无隐患。
match 两臂互斥,Rust 借用检查器按臂独立分析所有权。
(2) OpenRouterJson 是否正确排除 multipart 专属字段结论:✅ 正确,且有两层保障。
serde_json::json!({
"model": self.model,
"input_audio": {
"data": base64::engine::general_purpose::STANDARD.encode(&wav),
"format": "wav",
},
})该 body 仅含
小建议:当前测试( (3) base64 标准编码(带 padding)结论:✅ 选型正确。
这是 JSON API 传输二进制数据的惯例选择;对比 30s WAV(16 kHz 16-bit mono)≈ 960 KB,base64 后 ≈ 1.28 MB,payload 尺寸合理。 (4) verbose_json 对 OpenRouter 保持关闭结论:✅ 正确,双重保障。 第一层(coordinator.rs 2515–2516 行): fn whisper_supports_verbose_json(provider_id: &str) -> bool {
matches!(provider_id, "whisper" | "groq") // openrouter 不在列
}第二层(whisper.rs 174–184 行): 响应解析走 (5) 长录音 30s 切分是否合理结论:✅ 合理但偏保守,后续可按实测放宽。
30s 切分的合理性:
可选后续优化:真机测试通过后,如 OpenRouter 文档确认 body 上限 ≥ 8 MB,可将切分改为 60s 以减少长录音的 HTTP 往返次数。 总体评价代码结构清晰,builder 模式(
其余挂起事项(真机长录音验证)与 PR 描述一致,是合理的 TODO。 |
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
User description
需求
Closes #582
OpenRouter 的
/audio/transcriptions与 OpenAI 路径相同、也走 Bearer 鉴权,但请求体格式不同:multipart/form-dataapplication/jsonfile= WAV 二进制input_audio.data= base64(wav)所以直接把 OpenRouter 当 whisper 兼容 provider 加进预设,multipart 请求会被拒。
改动(单一职责:接入 OpenRouter ASR)
后端
whisper.rs:新增AsrRequestFormat { Multipart, OpenRouterJson },transcribe_chunk按格式分发。OpenRouterJson发{model, input_audio:{data, format:"wav"}},用标准 base64(带 padding),且不带 multipart 专属的prompt/response_format字段(避免未知字段 4xx)。用with_request_formatbuilder,不改new()签名(既有 4 处构造点零改动)。coordinator.rs:is_whisper_compatible_provider注册openrouter;新增whisper_request_format;verbose_json对 openrouter 保持关闭(其 JSON 协议不吃response_format);base64 膨胀 ~33%,长录音按 30s 切分(同 zhipu)。前端
ASR_PRESETS/AsrPresetId/Overview名称映射 + 5 份 i18n 加openrouter,默认baseUrl=https://openrouter.ai/api/v1、model=openai/whisper-large-v3-turbo(报告人已验证的模型)。测试
whisper.rs:OpenRouterJson 发 JSON body(含input_audio+format:"wav"、非 multipart)的单测;既有 multipart transcribe 测试不变。coordinator.rs:注册/格式/verbose_json/chunk-limit 断言。验证
tsc --noEmit通过cargo check通过cargo test --lib(openrouter + transcribe 共 11 项)全过{text}) 与长录音切分上限依赖
新增直接依赖
base64 = "0.22"(此前已是传递依赖,未引入新 crate)。备注
OpenRouter 官方文档已确认请求/响应格式(响应顶层
text,与 OpenAI 一致),故复用现有json["text"]解析。@claude 请审核。
PR Type
Enhancement, Tests
Description
Add OpenRouter ASR provider with JSON+base64 format
Backend: new request format enum, chunk limit, base64 dependency
Frontend: preset, i18n, type definitions
Tests: verify JSON request and provider registration
Diagram Walkthrough
flowchart LR User["User selects OpenRouter"] --> Frontend["ASR_PRESETS + i18n"] Frontend --> Coordinator["coordinator.rs: set request format"] Coordinator --> WhisperBatch["WhisperBatchASR"] WhisperBatch --> OpenRouterJson["OpenRouterJson encoding"] OpenRouterJson --> JSON["application/json body"] JSON --> Audio["input_audio.data: base64(wav)"] Audio --> Response["{ text }"]File Walkthrough
6 files
Add AsrRequestFormat enum and OpenRouterJson encodingRegister openrouter as whisper compatible provider and set formatUse with_request_format for dictation sessionAdd openrouter to ASR name key mappingAdd OpenRouter preset with default model and baseUrlAdd openrouter to AsrPresetId union5 files
Add asrOpenrouter i18n keyAdd asrOpenrouter i18n keyAdd asrOpenrouter i18n keyAdd asrOpenrouter i18n keyAdd asrOpenrouter i18n key1 files
Add base64 dependency