[持续对话判定]:完成恢复待机延迟修复,包含本地规则收敛和分类器跳过回归测试

This commit is contained in:
mkbk
2026-06-18 13:51:35 +08:00
parent 86a429f018
commit ac72738fc8
3 changed files with 71 additions and 15 deletions
+21 -15
View File
@@ -35,28 +35,27 @@ class ContinuationDecisionProvider(Protocol):
class RuleContinuationDecisionProvider:
_continue_patterns = (
"你想",
"你要",
"你需要",
"需要我",
"要不要",
"是否需要",
"可以告诉我",
"告诉我",
"请告诉我",
"你希望",
"你更想",
"哪一个",
"哪一部分",
"哪种",
"哪个",
"请选择",
"选一个",
"请补充",
"需要补充",
"补充一下",
"继续吗",
)
_ambiguous_continue_patterns = (
"我还可以",
"我可以继续",
"还可以继续",
"可以继续",
"继续展开",
"继续讲",
"继续聊",
"两个方向",
"三个方向",
"几个方向",
"几个部分",
)
_standby_patterns = (
"这是",
"已经",
@@ -70,6 +69,11 @@ class RuleContinuationDecisionProvider:
"抱歉",
"出错",
"失败",
"先这样",
"到这里",
"随时叫我",
"有需要再叫我",
"需要时再叫我",
)
def decide(
@@ -88,7 +92,9 @@ class RuleContinuationDecisionProvider:
return ContinuationDecision("standby", 0.82, "assistant appears to finish the answer", "rule")
if len(text) <= 18 and not text.endswith(("?", "", "", "")):
return ContinuationDecision("standby", 0.72, "short non-question reply", "rule")
return ContinuationDecision("unknown", 0.0, "rule uncertain", "rule")
if any(pattern in text for pattern in self._ambiguous_continue_patterns):
return ContinuationDecision("unknown", 0.0, "ambiguous continuation offer", "rule")
return ContinuationDecision("standby", 0.78, "assistant did not ask for immediate input", "rule")
class LlmContinuationDecisionProvider:
+18
View File
@@ -58,6 +58,24 @@ class ContinuationDecisionTests(unittest.TestCase):
self.assertEqual(decision.action, "standby")
self.assertEqual(len(llm.calls), 1)
def test_hybrid_generic_completed_reply_does_not_call_llm_classifier(self) -> None:
llm = FakeClassifierLlm('{"action":"continue","confidence":0.99,"reason":"不应调用"}')
provider = HybridContinuationDecisionProvider(
RuleContinuationDecisionProvider(),
LlmContinuationDecisionProvider(llm, threshold=0.65),
threshold=0.65,
)
decision = provider.decide(
user_text="没有呢",
assistant_text="明白了,我先保持待机。有需要再叫我就行。",
history=[],
)
self.assertEqual(decision.action, "standby")
self.assertEqual(decision.provider, "rule")
self.assertEqual(llm.calls, [])
def test_hybrid_high_confidence_llm_can_continue(self) -> None:
llm = FakeClassifierLlm('{"action":"continue","confidence":0.91,"reason":"等待用户选择"}')
provider = HybridContinuationDecisionProvider(
+32
View File
@@ -594,6 +594,38 @@ class LiveRuntimeTests(unittest.TestCase):
self.assertEqual(event_types[-1], STANDBY_RESUMED)
self.assertEqual(len(llm.calls), 1)
def test_completed_reply_returns_to_standby_without_cloud_classifier_delay(self) -> None:
frames = [wake_frame(0, 0)]
frames.extend(segment_frames(1, 20, partials=["没有呢", "没有呢"]))
transport = MemoryAudioTransport(frames, flush_clears_input=False)
llm = QueueLlmProvider([["明白了,我先保持待机。有需要再叫我就行。"]])
runtime = VoiceAssistantPipeline(
config=AppConfig(
llm_api_key="secret",
speech_provider="cloud",
wake_ack_text="",
followup_listen_timeout_ms=3000,
),
transport=transport,
wakeword=KeywordWakeWordProvider(),
vad_recorder=VadRecorder(EnergyVadProvider(), min_duration_ms=40, end_silence_ms=40),
stt=QueueSttProvider(["没有呢"]),
realtime_stt=MetadataSttProvider(),
llm=llm,
tts=SineTtsProvider(),
context=ConversationContext(),
reporter=RecordingReporter(),
event_bus=PipelineEventBus(),
)
summary = runtime.run(max_turns=1)
event_types = [event.type for event in runtime.event_bus.events]
self.assertEqual(summary.completed_turns, 1)
self.assertNotIn(FOLLOWUP_LISTENING, event_types)
self.assertEqual(event_types[-1], STANDBY_RESUMED)
self.assertEqual(len(llm.calls), 1)
def test_barge_in_interrupts_playback_and_keeps_only_spoken_context(self) -> None:
first_question_frames = [wake_frame(0, 0)]
first_question_frames.extend(segment_frames(1, 20, partials=["第一问", "第一问"]))