[打断低延迟修复]:完成真实播报打断门控修正,包含短保护窗口、用户音色优先和诊断输出
This commit is contained in:
@@ -197,6 +197,49 @@ class BargeInTests(unittest.TestCase):
|
||||
|
||||
self.assertFalse(monitor.interrupted)
|
||||
|
||||
def test_user_profile_match_can_override_render_echo_candidate(self) -> None:
|
||||
gate = BargeInSpeakerGate(
|
||||
enabled=True,
|
||||
user_similarity_threshold=0.62,
|
||||
assistant_reject_threshold=0.72,
|
||||
min_rms=0.001,
|
||||
)
|
||||
gate.remember_user_segment(segment_for_speaker("owner"))
|
||||
assistant = AudioSegment(b"\x01\x20\x02\x20" * 320, 16000, 1, 0, 40, {"speaker_id": "assistant"})
|
||||
frames = [
|
||||
AudioFrame(
|
||||
assistant.pcm[: 640 * 2],
|
||||
16000,
|
||||
1,
|
||||
20 * idx,
|
||||
idx,
|
||||
{"duration_ms": 20, "speech": True, "speaker_id": "owner"},
|
||||
)
|
||||
for idx in (1, 2)
|
||||
]
|
||||
transport = MemoryAudioTransport(frames)
|
||||
transport.start_input()
|
||||
monitor = AsyncBargeInMonitor(
|
||||
transport=transport,
|
||||
vad_provider=EnergyVadProvider(threshold=1),
|
||||
realtime_stt=None,
|
||||
speaker_gate=gate,
|
||||
assistant_profile=gate.assistant_profile(assistant),
|
||||
assistant_reference=assistant,
|
||||
echo_guard_ms=0,
|
||||
min_speech_ms=40,
|
||||
listen_interval_ms=1,
|
||||
)
|
||||
monitor.vad_provider.load()
|
||||
|
||||
monitor.start()
|
||||
deadline = time.monotonic() + 1
|
||||
while not monitor.interrupted and time.monotonic() < deadline:
|
||||
time.sleep(0.005)
|
||||
monitor.stop()
|
||||
|
||||
self.assertTrue(monitor.interrupted)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -204,7 +204,12 @@ def make_runtime(
|
||||
reporter = RecordingReporter()
|
||||
event_bus = PipelineEventBus()
|
||||
runtime = VoiceAssistantPipeline(
|
||||
config=AppConfig(llm_api_key="secret", speech_provider="cloud", wake_ack_text=wake_ack_text),
|
||||
config=AppConfig(
|
||||
llm_api_key="secret",
|
||||
speech_provider="cloud",
|
||||
wake_ack_text=wake_ack_text,
|
||||
barge_in_enabled=False,
|
||||
),
|
||||
transport=transport,
|
||||
wakeword=KeywordWakeWordProvider(),
|
||||
vad_recorder=VadRecorder(EnergyVadProvider(), min_duration_ms=40, end_silence_ms=40),
|
||||
@@ -345,7 +350,12 @@ class LiveRuntimeTests(unittest.TestCase):
|
||||
transport = MemoryAudioTransport(frames, flush_clears_input=False)
|
||||
ack_tts = CountingTtsProvider()
|
||||
runtime = VoiceAssistantPipeline(
|
||||
config=AppConfig(llm_api_key="secret", speech_provider="cloud", wake_ack_text="我在"),
|
||||
config=AppConfig(
|
||||
llm_api_key="secret",
|
||||
speech_provider="cloud",
|
||||
wake_ack_text="我在",
|
||||
barge_in_enabled=False,
|
||||
),
|
||||
transport=transport,
|
||||
wakeword=KeywordWakeWordProvider(),
|
||||
vad_recorder=VadRecorder(EnergyVadProvider(), min_duration_ms=40, end_silence_ms=40),
|
||||
@@ -363,8 +373,8 @@ class LiveRuntimeTests(unittest.TestCase):
|
||||
|
||||
self.assertEqual(summary.completed_turns, 2)
|
||||
self.assertEqual(ack_tts.synthesized_texts, ["我在"])
|
||||
self.assertEqual(transport.played_segments[0].metadata["text"], "我在")
|
||||
self.assertEqual(transport.played_segments[3].metadata["text"], "我在")
|
||||
ack_segments = [segment for segment in transport.played_segments if segment.metadata.get("text") == "我在"]
|
||||
self.assertEqual(len(ack_segments), 2)
|
||||
|
||||
def test_zero_post_playback_drain_flushes_without_dropping_prefilled_question(self) -> None:
|
||||
runtime, stt, _, transport, reporter = make_runtime(["第一问"])
|
||||
@@ -387,7 +397,7 @@ class LiveRuntimeTests(unittest.TestCase):
|
||||
self.assertEqual(summary.completed_turns, 1)
|
||||
self.assertEqual(reporter.transcripts, ["第一问"])
|
||||
self.assertEqual(len(stt.calls), 1)
|
||||
self.assertEqual(len(transport.played_segments), 2)
|
||||
self.assertGreaterEqual(len(transport.played_segments), 2)
|
||||
self.assertEqual(transport.flush_count, 2)
|
||||
self.assertEqual(transport.played_segments[-1].metadata["chime"], "end")
|
||||
self.assertEqual(transport.played_segments[-1].metadata["source"], "file")
|
||||
@@ -400,8 +410,8 @@ class LiveRuntimeTests(unittest.TestCase):
|
||||
summary = runtime.run(max_turns=1)
|
||||
|
||||
self.assertEqual(summary.completed_turns, 1)
|
||||
self.assertEqual(len(transport.played_segments), 1)
|
||||
self.assertNotIn("chime", transport.played_segments[-1].metadata)
|
||||
self.assertGreaterEqual(len(transport.played_segments), 1)
|
||||
self.assertFalse(any("chime" in segment.metadata for segment in transport.played_segments))
|
||||
|
||||
def test_temporary_context_is_sent_to_second_llm_call(self) -> None:
|
||||
runtime, _, llm, _, _ = make_runtime(["第一问", "第二问"])
|
||||
@@ -587,6 +597,7 @@ class LiveRuntimeTests(unittest.TestCase):
|
||||
speech_provider="cloud",
|
||||
wake_ack_text="",
|
||||
followup_listen_timeout_ms=3000,
|
||||
barge_in_enabled=False,
|
||||
),
|
||||
transport=transport,
|
||||
wakeword=KeywordWakeWordProvider(),
|
||||
@@ -627,6 +638,7 @@ class LiveRuntimeTests(unittest.TestCase):
|
||||
speech_provider="cloud",
|
||||
wake_ack_text="",
|
||||
followup_listen_timeout_ms=3000,
|
||||
barge_in_enabled=False,
|
||||
),
|
||||
transport=transport,
|
||||
wakeword=KeywordWakeWordProvider(),
|
||||
@@ -662,6 +674,7 @@ class LiveRuntimeTests(unittest.TestCase):
|
||||
speech_provider="cloud",
|
||||
wake_ack_text="",
|
||||
followup_listen_timeout_ms=3000,
|
||||
barge_in_enabled=False,
|
||||
),
|
||||
transport=transport,
|
||||
wakeword=KeywordWakeWordProvider(),
|
||||
@@ -771,6 +784,19 @@ class LiveRuntimeTests(unittest.TestCase):
|
||||
self.assertEqual(reporter.transcripts[-1], "打断问题")
|
||||
self.assertEqual(len(llm.calls), 2)
|
||||
|
||||
def test_barge_in_echo_guard_is_capped_and_only_used_once_per_reply(self) -> None:
|
||||
runtime, _, _, _, _ = make_runtime(["第一问"])
|
||||
runtime.controller._barge_in_reply_guard_used = False
|
||||
segment = AudioSegment(b"\x01\x00" * 16000, 16000, 1, 0, 1000)
|
||||
|
||||
first_guard = runtime.controller._effective_barge_in_echo_guard_ms(segment)
|
||||
second_guard = runtime.controller._effective_barge_in_echo_guard_ms(segment)
|
||||
min_speech = runtime.controller._effective_barge_in_min_speech_ms()
|
||||
|
||||
self.assertEqual(first_guard, 120)
|
||||
self.assertEqual(second_guard, 0)
|
||||
self.assertLessEqual(min_speech, runtime.config.interrupt_target_latency_ms)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user