fix(bailian): 修复 DashScope ASR 部分语句重复输出的问题

两个根因:
1. end_time 判断用 is_some() 而非 > 0:API 对 interim 结果设 end_time:0,
   导致所有中间结果被当成 final 推入 segments
2. 累积文本拼接:同一句 API 多次发送("你"→"你好"→"你好吗"),
   每次作为新 segment push,join 后变成重复拼接

修复:
- end_time 改用 > 0 判断真正的句子结束
- 引入 sentence_id → BTreeMap 按序存储,同一 sentence_id 后到覆盖前到

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
aeoformandClaude Opus 4.7 committed 2026-05-22 20:49:11 +08:00
1 parent 7635df5df5
commit 54f07d85e9
1 file changed
+19 -5
+19 -5
View File
@@ -5,6 +5,7 @@
//! matches OpenLess' recorder output directly. The Qwen OpenAI Realtime line is
//! a different protocol and is intentionally left for a follow-up provider.
use std::collections::BTreeMap;
use std::sync::Arc;
use std::time::{Duration, Instant};
@@ -94,7 +95,9 @@ struct SyncState {
start: Option<Instant>,
final_tx: Option<oneshot::Sender<Result<RawTranscript, BailianASRError>>>,
send_tx: Option<mpsc::UnboundedSender<SendItem>>,
final_segments: Vec<String>,
/// sentence_id → text,按 sentence_id 排序拼接得到最终文本。
/// 同一 sentence_id 的后到结果覆盖前一个,消除累积文本导致的重复。
final_segments: BTreeMap<i64, String>,
last_result_text: String,
}
@@ -367,11 +370,22 @@ impl BailianRealtimeASR {
if trimmed.is_empty() {
return;
}
let is_sentence_final = sentence.get("end_time").is_some();
// end_time 为 0 或缺失时是 interim 结果;仅正数才是真正完成的句子。
let end_time = sentence
.get("end_time")
.and_then(Value::as_i64)
.unwrap_or(0);
let is_sentence_final = end_time > 0;
let sentence_id = sentence
.get("sentence_id")
.and_then(Value::as_i64)
.unwrap_or(0);
let mut st = self.state.lock();
st.last_result_text = trimmed.to_string();
if is_sentence_final && st.final_segments.last().map(|s| s.as_str()) != Some(trimmed) {
st.final_segments.push(trimmed.to_string());
if is_sentence_final && sentence_id > 0 {
// 同一 sentence_id 后到覆盖前到:API 对同一句话的累积更新
// ("你"→"你好"→"你好吗")只保留最终版本。
st.final_segments.insert(sentence_id, trimmed.to_string());
}
}
@@ -386,7 +400,7 @@ impl BailianRealtimeASR {
let text = if st.final_segments.is_empty() {
st.last_result_text.clone()
} else {
st.final_segments.join("")
st.final_segments.values().cloned().collect::<Vec<_>>().join("")
};
let duration_ms = if st.bytes_received > 0 {
st.bytes_received / BYTES_PER_MS