feat: canonical intent taxonomy for AI operator (kb_answer -> intent_code)
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
This commit is contained in:
2026-08-31 00:17:51 +05:00
parent 3826f5704a
commit d2438b6954
14 changed files with 260 additions and 8 deletions
+8
View File
@@ -583,6 +583,7 @@ def _apply_runtime_schema_compatibility() -> None:
if "kb_articles" in table_names:
columns = _table_columns(inspector, "kb_articles")
_add_column_if_missing(conn, columns, "kb_articles", "article_group_id", "VARCHAR(64)")
_add_column_if_missing(conn, columns, "kb_articles", "intent_code", "VARCHAR(64)")
_add_column_if_missing(conn, columns, "kb_articles", "language", "VARCHAR(8) DEFAULT 'ru'")
if "language" in columns:
conn.execute(
@@ -619,6 +620,13 @@ def _apply_runtime_schema_compatibility() -> None:
"ON kb_articles(language)"
)
)
if "idx_kb_articles_intent_code" not in indexes:
conn.execute(
text(
"CREATE INDEX IF NOT EXISTS idx_kb_articles_intent_code "
"ON kb_articles(intent_code)"
)
)
if "sales_automation_tasks" in table_names:
columns = _table_columns(inspector, "sales_automation_tasks")