fix(asr): Whisper/OpenRouter 动态超时 + 全局兜底 15→30s

- COORDINATOR_GLOBAL_TIMEOUT_SECS 从 15 改为 30,为云端 batch ASR 留网络预算
- 新增 whisper_transcribe_timeout(audio_secs):max(30, ceil(audio×0.5)+20)
- WhisperBatchASR 新增 buffer_duration_ms() 供 coordinator 计算动态超时
- dictation.rs Whisper 路径从固定 15s 切到动态超时,超时日志包含音频时长

Closes #721
This commit is contained in:
Chris233 committed 2026-06-21 20:48:38 +08:00
1 parent 323f44d450
commit 3ec8c5ef0c
3 files changed
+32 -7

No files matched your search

@@ -87,6 +87,12 @@ impl WhisperBatchASR {
/// Whisper transcriptions endpoint.
///
/// 失败时**保留** PCM buffer,让上层有机会重试或在历史中至少留一个失败记录;
/// 当前缓冲音频时长(毫秒)。Coordinator 在 transcribe() 调用前读取,
/// 用于计算 Whisper / OpenRouter 的动态超时。不消费缓冲。
pub fn buffer_duration_ms(&self) -> u64 {
pcm_duration_ms(&self.buffer.lock())
}
/// 之前的实现一进函数就 `mem::take` 把 buffer 清空,凭证错或网络中断都会
/// 让用户的录音直接消失。
pub async fn transcribe(&self) -> Result<RawTranscript> {
+14 -3
View File
@@ -2897,9 +2897,9 @@ const CAPSULE_AUTO_HIDE_DELAY_MS: u64 = 2000;
const POST_SESSION_COOLDOWN_MS: u64 = 600;
/// Coordinator 全局超时保护:防止 ASR await_final_result() 永远挂起。
/// 设置为 15 秒(比 ASR 的 12 秒 FINAL_RESULT_TIMEOUT 稍长),
/// 只在 ASR 超时机制失效时作为最后的防线触发。
const COORDINATOR_GLOBAL_TIMEOUT_SECS: u64 = 15;
/// 设置为 30 秒,为云端 batch ASR(OpenRouter Whisper 等)提供足够的
/// 网络超时预算;只在 ASR 自身超时机制失效时作为最后的防线触发。
const COORDINATOR_GLOBAL_TIMEOUT_SECS: u64 = 30;
#[cfg(target_os = "windows")]
fn foundry_audio_transcribe_timeout_duration() -> std::time::Duration {
@@ -2917,6 +2917,17 @@ fn local_qwen_transcribe_timeout(audio_secs: f64) -> std::time::Duration {
std::time::Duration::from_secs(secs)
}
/// Whisper / OpenRouter 云端 batch ASR 的动态转写超时。OpenRouter 按 30s
/// 分片,每片是一次 HTTP round-trip;网络抖动、排队、base64 body 都会
/// 拉长耗时。公式 max(30, ceil(audio_s × 0.5) + 20):30s 是全局兜底;
/// 长录音按音频长度的 0.5 倍 + 20s 余量,覆盖多分片串行请求 + 网络波动。
fn whisper_transcribe_timeout(audio_secs: f64) -> std::time::Duration {
let secs = ((audio_secs * 0.5).ceil() as u64)
.saturating_add(20)
.max(COORDINATOR_GLOBAL_TIMEOUT_SECS);
std::time::Duration::from_secs(secs)
}
/// sherpa-onnx offline batch 暂与 Foundry 同档;后续按 Windows 真机 CPU/模型
/// 实测结果再调整。
#[cfg(target_os = "windows")]
@@ -1691,8 +1691,15 @@ pub(super) async fn end_session(inner: &Arc<Inner>) -> Result<(), String> {
}
ActiveAsr::Whisper(w) => {
debug_assert!(uses_global_timeout);
// Whisper 也添加类似的超时保护
let timeout_duration = std::time::Duration::from_secs(COORDINATOR_GLOBAL_TIMEOUT_SECS);
// Whisper / OpenRouter 动态超时:音频越长、分片越多,给更多
// HTTP round-trip 预算。公式见 `whisper_transcribe_timeout`。
let audio_secs = (w.buffer_duration_ms() as f64) / 1000.0;
let timeout_duration = whisper_transcribe_timeout(audio_secs);
log::info!(
"[coord] Whisper transcribe: audio={:.2}s timeout={}s",
audio_secs,
timeout_duration.as_secs()
);
match tokio::time::timeout(timeout_duration, w.transcribe()).await {
Ok(Ok(r)) => r,
Ok(Err(e)) => {
@@ -1712,8 +1719,9 @@ pub(super) async fn end_session(inner: &Arc<Inner>) -> Result<(), String> {
}
Err(_) => {
log::error!(
"[coord] whisper 全局超时 {} 秒",
COORDINATOR_GLOBAL_TIMEOUT_SECS
"[coord] Whisper 动态超时 {}s(音频 {:.2}s)",
timeout_duration.as_secs(),
audio_secs
);
emit_capsule(
inner,