deploy / deploy (push) Successful in 30s
Centralizes fixed control intents and adds a data-driven intent_code field on kb_articles so many phrasings of the same FAQ question resolve to one stable code (e.g. VOUCHER_ACTIVATION) instead of a free-form, unvalidated string the LLM invented on the fly. - services/shared/intents.py: CONTROL_INTENTS + normalize_intent() - kb_articles.intent_code column (ORM + dev/sqlite runtime compat + migrations/sql/0034_* for postgres/sqlite) - kb_service CRUD exposes intent_code - orchestrator surfaces intent_code to the LLM and validates its intent output against control intents + the KB codes shown that turn - voice.py: _voice_early_intent_bucket renamed to _voice_ack_topic_bucket to stop it being conflated with the canonical FAQ intent
40 lines
1.4 KiB
Python
40 lines
1.4 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Iterable
|
|
|
|
# Fixed, code-level conversational signals — not FAQ topics. These strings are
|
|
# already used as intent literals across ai_orchestrator_service/app.py,
|
|
# voice.py, and asserted directly in tests; centralized here rather than
|
|
# renamed so every call site validates against the same set.
|
|
CONTROL_INTENTS: frozenset[str] = frozenset(
|
|
{
|
|
"identity_question",
|
|
"handoff_request",
|
|
"sensitive_request",
|
|
"resolution_confirmed",
|
|
"clarification",
|
|
"kb_answer",
|
|
"unknown",
|
|
}
|
|
)
|
|
|
|
UNKNOWN_INTENT = "unknown"
|
|
|
|
|
|
def normalize_intent(raw: str | None, *, known_topic_codes: Iterable[str] = ()) -> str:
|
|
"""Validate a model-produced intent against control intents and KB topic codes.
|
|
|
|
`known_topic_codes` are the `intent_code` values of the KB articles actually
|
|
shown to the model for this turn — anything else the model invents collapses
|
|
to UNKNOWN_INTENT rather than being trusted verbatim.
|
|
"""
|
|
candidate = str(raw or "").strip()
|
|
if not candidate:
|
|
return UNKNOWN_INTENT
|
|
if candidate in CONTROL_INTENTS:
|
|
return candidate
|
|
normalized_topic_codes = {str(code or "").strip().upper() for code in known_topic_codes if str(code or "").strip()}
|
|
if candidate.upper() in normalized_topic_codes:
|
|
return candidate.upper()
|
|
return UNKNOWN_INTENT
|