[唤醒应答与快速端点]:完成唤醒后我在播报和本地VAD端点优化,包含缓冲清理、配置项和测试覆盖

This commit is contained in:
mkbk
2026-06-17 20:51:14 +08:00
parent 860c2909fc
commit 44f708a3dc
12 changed files with 224 additions and 7 deletions
+3 -2
View File
@@ -80,7 +80,7 @@ def make_runtime(texts: list[str], context: ConversationContext | None = None) -
tts = SineTtsProvider()
reporter = RecordingReporter()
runtime = LiveVoiceRuntime(
config=AppConfig(llm_api_key="secret", speech_provider="cloud"),
config=AppConfig(llm_api_key="secret", speech_provider="cloud", post_playback_drain_ms=0),
transport=transport,
wakeword=KeywordWakeWordProvider(),
vad_recorder=VadRecorder(EnergyVadProvider(), min_duration_ms=40, end_silence_ms=40),
@@ -101,8 +101,9 @@ class LiveRuntimeTests(unittest.TestCase):
self.assertEqual(summary.completed_turns, 2)
self.assertEqual(len(stt.calls), 2)
self.assertEqual(len(llm.calls), 2)
self.assertEqual(len(transport.played_segments), 2)
self.assertEqual(len(transport.played_segments), 4)
self.assertEqual(reporter.transcripts, ["第一问", "第二问"])
self.assertIn("应答中:我在", reporter.statuses)
self.assertIn("恢复待机:可继续唤醒", reporter.statuses[-1])
def test_temporary_context_is_sent_to_second_llm_call(self) -> None:
+13
View File
@@ -72,6 +72,14 @@ class ModelsConfigTests(unittest.TestCase):
self.assertEqual(config.wake_provider, "local_kws")
self.assertEqual(config.wake_kws_threshold, 0.25)
self.assertEqual(config.wake_kws_score, 1.0)
self.assertEqual(config.wake_ack_text, "我在")
self.assertEqual(config.post_playback_drain_ms, 250)
self.assertEqual(config.vad_provider, "local")
self.assertEqual(config.vad_threshold, 0.5)
self.assertEqual(config.vad_min_duration_ms, 250)
self.assertEqual(config.vad_end_silence_ms, 350)
self.assertEqual(config.vad_no_speech_timeout_ms, 5000)
self.assertEqual(config.vad_max_recording_ms, 12000)
self.assertEqual(config.speech_provider, "cloud")
self.assertEqual(config.asr_model, "mimo-v2.5-asr")
self.assertEqual(config.tts_model, "mimo-v2.5-tts")
@@ -90,6 +98,11 @@ class ModelsConfigTests(unittest.TestCase):
errors = config.validate_basic()
self.assertTrue(any("OWNER_WAKE_PROVIDER" in error.message for error in errors))
def test_vad_provider_must_be_local_or_energy(self) -> None:
config = AppConfig(vad_provider="invalid")
errors = config.validate_basic()
self.assertTrue(any("OWNER_VAD_PROVIDER" in error.message for error in errors))
def test_missing_dotenv_uses_non_secret_defaults(self) -> None:
config = AppConfig.from_dotenv("/tmp/owner-voice-pet-missing.env")
self.assertEqual(config.llm_base_url, "https://token-plan-cn.xiaomimimo.com/v1")
+14
View File
@@ -42,6 +42,12 @@ class TransportTests(unittest.TestCase):
self.assertFalse(result.played)
self.assertEqual(result.error.code, ErrorCode.AUDIO_OUTPUT_DEVICE_MISSING)
def test_memory_transport_flushes_input_frames(self) -> None:
transport = MemoryAudioTransport([frame(1, 0), frame(2, 20)])
transport.start_input()
self.assertEqual(transport.flush_input(), 2)
self.assertEqual(transport.read_frames(10), [])
def test_file_replay_roundtrip(self) -> None:
frames = [frame(1, 0, {"wake": True}), frame(2, 20, {"speech": True})]
with tempfile.TemporaryDirectory() as tmp:
@@ -76,6 +82,14 @@ class TransportTests(unittest.TestCase):
self.assertEqual(frames[0].pcm, b"\x01\x00\x02\x00")
self.assertEqual(frames[0].sample_rate, 16000)
def test_sounddevice_transport_flushes_queued_input(self) -> None:
fake = FakeSoundDevice()
transport = SoundDeviceAudioTransport(sounddevice_module=fake)
transport.start_input(sample_rate=16000, channels=1)
self.assertEqual(transport.flush_input(), 1)
self.assertEqual(transport.read_frames(0), [])
transport.stop()
def test_sounddevice_transport_plays_pcm_to_raw_output_stream(self) -> None:
fake = FakeSoundDevice()
transport = SoundDeviceAudioTransport(sounddevice_module=fake)