fix: let voice early-plan turns answer from the FAQ knowledge base
deploy / deploy (push) Successful in 32s
deploy / deploy (push) Successful in 32s
The speculative "early plan" turn (computed on partial ASR, before the caller finishes talking) can win the race and get spoken as the actual reply, but it unconditionally skipped KB search and answered common questions (schedule/address/price/status/problem) with a hardcoded clarifying question even when the FAQ already had the answer. KB search is a cheap in-memory lexical scan over a DB-cached row set, so it fits the early-plan latency budget unlike a real LLM call. Now early-plan runs it and, on a match, answers from the KB snippet (intent resolved via normalize_intent) instead of guessing a generic clarifying question; with no match it falls back to the prior behavior unchanged. operator_request is unaffected.
This commit is contained in:
@@ -1589,6 +1589,79 @@ def test_voice_v2_streaming_duplex_early_plan_returns_domain_followup_without_ll
|
||||
assert decision["metadata"]["early_intent"] == "schedule"
|
||||
|
||||
|
||||
def test_voice_v2_streaming_duplex_early_plan_answers_from_kb_without_llm(monkeypatch):
|
||||
monkeypatch.setenv("AI_VOICE_POLICY_MODE", "v2_streaming_duplex")
|
||||
|
||||
def _unexpected_llm(messages, **kwargs):
|
||||
raise AssertionError(f"LLM should not be called for early plan: {messages!r}")
|
||||
|
||||
monkeypatch.setattr(ai_module, "_request_structured_model_decision", _unexpected_llm)
|
||||
|
||||
kb_article = SimpleNamespace(
|
||||
article_id="kba_early_schedule",
|
||||
title="График работы филиалов",
|
||||
body="Филиалы работают с понедельника по пятницу с 9:00 до 18:00.",
|
||||
intent_code="BRANCH_SCHEDULE",
|
||||
)
|
||||
decision = voice_module._voice_decision(
|
||||
language="ru",
|
||||
customer=None,
|
||||
interaction=SimpleNamespace(interaction_id="int_voice_early_schedule_kb", status="new", queue_id="que_voice", subject="unknown"),
|
||||
transcript_text="Мне надо узнать график работы",
|
||||
transcript_window=[],
|
||||
kb_results=[kb_article],
|
||||
disclosure_required=False,
|
||||
request_metadata={
|
||||
"voice_v2_enabled": True,
|
||||
"reply_phase": "early_plan",
|
||||
"response_plan_id": "rsp_early_schedule_kb",
|
||||
"early_intent": "schedule",
|
||||
},
|
||||
)
|
||||
|
||||
assert decision["model"] == "voice_early_plan_kb"
|
||||
assert decision["intent"] == "BRANCH_SCHEDULE"
|
||||
assert decision["kb_refs"] == ["kba_early_schedule"]
|
||||
assert decision["needs_handoff"] is False
|
||||
assert "9:00" in decision["reply_text"] or "9" in decision["reply_text"]
|
||||
assert decision["metadata"]["reply_phase"] == "early_plan"
|
||||
|
||||
|
||||
def test_voice_v2_streaming_duplex_early_plan_operator_request_skips_kb(monkeypatch):
|
||||
monkeypatch.setenv("AI_VOICE_POLICY_MODE", "v2_streaming_duplex")
|
||||
|
||||
def _unexpected_llm(messages, **kwargs):
|
||||
raise AssertionError(f"LLM should not be called for early plan: {messages!r}")
|
||||
|
||||
monkeypatch.setattr(ai_module, "_request_structured_model_decision", _unexpected_llm)
|
||||
|
||||
kb_article = SimpleNamespace(
|
||||
article_id="kba_should_not_be_used",
|
||||
title="Unrelated article",
|
||||
body="Should not be referenced for an operator handoff.",
|
||||
intent_code="SOMETHING_ELSE",
|
||||
)
|
||||
decision = voice_module._voice_decision(
|
||||
language="ru",
|
||||
customer=None,
|
||||
interaction=SimpleNamespace(interaction_id="int_voice_early_operator", status="new", queue_id="que_voice", subject="unknown"),
|
||||
transcript_text="Соедините меня с оператором",
|
||||
transcript_window=[],
|
||||
kb_results=[kb_article],
|
||||
disclosure_required=False,
|
||||
request_metadata={
|
||||
"voice_v2_enabled": True,
|
||||
"reply_phase": "early_plan",
|
||||
"response_plan_id": "rsp_early_operator",
|
||||
"early_intent": "operator_request",
|
||||
},
|
||||
)
|
||||
|
||||
assert decision["intent"] == "handoff_request"
|
||||
assert decision["needs_handoff"] is True
|
||||
assert decision["kb_refs"] == []
|
||||
|
||||
|
||||
def test_voice_decision_hearing_check_keeps_active_topic_without_llm(monkeypatch):
|
||||
def _unexpected_llm(messages, **kwargs):
|
||||
raise AssertionError(f"LLM should not be called for hearing check: {messages!r}")
|
||||
|
||||
Reference in New Issue
Block a user