Revert "fix: let voice early-plan turns answer from the FAQ knowledge base"
deploy / deploy (push) Successful in 31s

This reverts commit 8ced7a59e3.
This commit is contained in:
2026-08-31 00:54:38 +05:00
parent 1dc37d2764
commit 9fdaa9472f
2 changed files with 17 additions and 139 deletions
+17 -66
View File
@@ -19,7 +19,6 @@ from services.shared.ai_context_summary import (
)
from services.shared.core import new_id, utc_now_iso
from services.shared.db import get_session
from services.shared.intents import normalize_intent
from services.shared.models import VoiceAIStartIn, VoiceAIStartOut, VoiceAITurnDecisionOut, VoiceAITurnIn, VoiceStartResult
from services.shared.sql_models import (
AISessionRow,
@@ -1535,7 +1534,6 @@ def _voice_early_plan(
transcript_text: str,
context_summary: str | dict[str, Any] | None = None,
request_metadata: dict[str, Any] | None = None,
kb_results: list[Any] = (),
) -> dict[str, Any]:
v2_metadata = _voice_v2_metadata(transcript_text, request_metadata)
payload = request_metadata if isinstance(request_metadata, dict) else {}
@@ -1578,56 +1576,10 @@ def _voice_early_plan(
"reply_phase": "early_plan",
},
}
if early_intent == "operator_request":
return {
"language": language,
"intent": "handoff_request",
"reply_text": _voice_compact_reply_text(_voice_handoff_reply(language), language=language),
"confidence": 0.62,
"needs_handoff": True,
"handoff_reason": "Запрос требует участия живого оператора.",
"case_action": "keep_open",
"kb_refs": [],
"summary_text": "Early domain plan is prepared.",
"model": "voice_early_plan_domain",
"latency_ms": 1,
"metadata": {
**v2_metadata,
"reply_phase": "early_plan",
"early_intent": early_intent,
},
}
if early_intent in {"schedule", "address", "price", "status", "problem"} and kb_results:
# A cheap lexical KB lookup fits the early-plan latency budget (no LLM
# round-trip), unlike the properly grounded/paraphrased "final" decision.
# Answering from the KB here beats guessing a generic clarifying question
# when the FAQ already has the answer — see the plan doc for why this
# branch exists at all (the early reply can win the race and get spoken
# before the final, LLM-grounded decision is ready).
article = kb_results[0]
snippet = _app()._article_snippet(article, limit=220)
reply_text = f"Қысқаша айтайын: {snippet}" if language == "kz" else f"Коротко подскажу: {snippet}"
topic_code = getattr(article, "intent_code", None)
return {
"language": language,
"intent": normalize_intent(topic_code or "kb_answer", known_topic_codes=[topic_code] if topic_code else []),
"reply_text": _voice_compact_reply_text(reply_text, language=language),
"confidence": 0.7,
"needs_handoff": False,
"handoff_reason": None,
"case_action": "keep_open",
"kb_refs": [article.article_id],
"summary_text": "AI ответил по базе ЧЗВ на предварительной стадии.",
"model": "voice_early_plan_kb",
"latency_ms": 1,
"metadata": {
**v2_metadata,
"reply_phase": "early_plan",
"early_intent": early_intent,
},
}
if early_intent in {"schedule", "address", "price", "status", "problem"}:
if early_intent in {"schedule", "address", "price", "status", "problem", "operator_request"}:
reply_text = _voice_summary_slot_prompt(language, context_summary) or _voice_topic_prompt(language, [transcript_text])
if early_intent == "operator_request":
reply_text = _voice_handoff_reply(language)
if not reply_text and language == "kz":
if early_intent == "schedule":
reply_text = "Qai filialdyn, mekenjaidyn nemese qalanyng jumys uaqyty qyzyqtyratynyn aitnyz."
@@ -1653,11 +1605,15 @@ def _voice_early_plan(
if reply_text:
return {
"language": language,
"intent": "clarification",
"intent": "handoff_request" if early_intent == "operator_request" else "clarification",
"reply_text": _voice_compact_reply_text(reply_text, language=language),
"confidence": 0.62,
"needs_handoff": False,
"handoff_reason": None,
"needs_handoff": early_intent == "operator_request",
"handoff_reason": (
"Запрос требует участия живого оператора."
if early_intent == "operator_request"
else None
),
"case_action": "keep_open",
"kb_refs": [],
"summary_text": "Early domain plan is prepared.",
@@ -1880,7 +1836,6 @@ def _voice_decision(
transcript_text=transcript_text,
context_summary=context_summary,
request_metadata=request_metadata,
kb_results=kb_results,
)
if persona.is_identity_request(normalized):
@@ -2582,17 +2537,13 @@ def turn_voice_session(session_id: str, payload: VoiceAITurnIn) -> VoiceAITurnDe
)
ai_session.context_summary_json = dump_context_summary(user_context_summary)
ai_session.context_summary_updated_at = now
# Unlike name extraction/context-summary writes above (skipped for
# early_plan_only since they're stateful and heavier), KB search is a
# cheap in-memory lexical scan over a DB-cached row set (see
# _load_kb_rows_cached) and comfortably fits the early-plan latency
# budget, so it always runs — this lets _voice_early_plan answer from
# the FAQ instead of guessing a generic clarifying question.
kb_results = app._kb_search(
session,
payload.transcript_text,
language=ai_session.language,
)
kb_results = []
if not early_plan_only:
kb_results = app._kb_search(
session,
payload.transcript_text,
language=ai_session.language,
)
disclosure_required = voice_session.disclosure_played_at is None
decision = _voice_decision(
language=ai_session.language or "ru",