[持续对话判定]:完成恢复待机延迟修复,包含本地规则收敛和分类器跳过回归测试
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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=["第一问", "第一问"]))
|
||||
|
||||
Reference in New Issue
Block a user