Files
call-center/services/ai_orchestrator_service/voice_name_config.py
T

169 lines
6.2 KiB
Python

from __future__ import annotations
import json
from typing import Any
from sqlalchemy import select
from services.shared.core import utc_now_iso
from services.shared.models import (
VoiceNameCollectionConfig,
VoiceNameCollectionConfigOut,
VoiceNameCollectionLanguageTexts,
VoiceNameCollectionTextsConfig,
)
from services.shared.sql_models import VoiceNameCollectionSettingsRow
VOICE_NAME_COLLECTION_SETTINGS_KEY = "global"
def voice_name_collection_default_config() -> VoiceNameCollectionConfig:
return VoiceNameCollectionConfig(
enabled=True,
texts=VoiceNameCollectionTextsConfig(
ru=VoiceNameCollectionLanguageTexts(
start_prompt="Здравствуйте. Назовите, пожалуйста, ваше имя.",
personalized_greeting_template=(
"Я AI-оператор компании. Здравствуйте, {name}. "
"Коротко расскажите, с чем помочь, и я сразу начну разбираться."
),
confirmation_greeting_template=(
"Я AI-оператор компании. Если я правильно расслышал, вас зовут {name}? "
"И чем помочь?"
),
inline_followup_prompt="И ещё подскажите, как мне к вам обращаться?",
),
kz=VoiceNameCollectionLanguageTexts(
start_prompt="Сәлеметсіз бе. Атыңызды атаңызшы.",
personalized_greeting_template=(
"Men kompaniyanyn AI operatoriymyn. Salemetsiz be, {name}. "
"Suragynyzdy aitanyz, men birden komektesuge tyrisamyn."
),
confirmation_greeting_template=(
"Men kompaniyanyn AI operatoriymyn. Durys estisem, atynyz {name} pa? "
"Qalai komektesemin?"
),
inline_followup_prompt="Tagy bir naqtylasam, sizge qalai qaratamyn?",
),
),
)
def _settings_row(session) -> VoiceNameCollectionSettingsRow | None:
return session.execute(
select(VoiceNameCollectionSettingsRow).where(
VoiceNameCollectionSettingsRow.settings_key == VOICE_NAME_COLLECTION_SETTINGS_KEY
)
).scalar_one_or_none()
def _normalize_config_payload(raw: Any) -> VoiceNameCollectionConfig:
if isinstance(raw, VoiceNameCollectionConfig):
return raw
if not isinstance(raw, dict):
raise ValueError("Voice name collection config payload must be an object")
return VoiceNameCollectionConfig.model_validate(raw)
def load_voice_name_collection_config(session) -> VoiceNameCollectionConfigOut:
default_config = voice_name_collection_default_config()
row = _settings_row(session)
if row is None:
return VoiceNameCollectionConfigOut(config=default_config, updated_at=None, source="defaults")
try:
parsed = json.loads(row.config_json or "{}")
config = _normalize_config_payload(parsed)
return VoiceNameCollectionConfigOut(config=config, updated_at=row.updated_at, source="database")
except Exception:
return VoiceNameCollectionConfigOut(config=default_config, updated_at=row.updated_at, source="defaults")
def load_effective_voice_name_collection_config(session) -> VoiceNameCollectionConfig:
return load_voice_name_collection_config(session).config
def save_voice_name_collection_config(
session,
config_payload: VoiceNameCollectionConfig | dict[str, Any],
) -> VoiceNameCollectionConfigOut:
config = _normalize_config_payload(config_payload)
row = _settings_row(session)
now = utc_now_iso()
if row is None:
row = VoiceNameCollectionSettingsRow(
settings_key=VOICE_NAME_COLLECTION_SETTINGS_KEY,
config_json="{}",
updated_at=now,
)
session.add(row)
row.config_json = json.dumps(config.model_dump(), ensure_ascii=False)
row.updated_at = now
session.commit()
session.refresh(row)
return VoiceNameCollectionConfigOut(config=config, updated_at=row.updated_at, source="database")
def voice_name_collection_texts(
config: VoiceNameCollectionConfig,
language: str,
) -> VoiceNameCollectionLanguageTexts:
if str(language or "").strip().lower() == "kz":
return config.texts.kz
return config.texts.ru
def voice_name_collection_start_prompt(config: VoiceNameCollectionConfig, language: str) -> str:
return voice_name_collection_texts(config, language).start_prompt
def voice_name_collection_personalized_greeting(
config: VoiceNameCollectionConfig,
language: str,
name: str,
) -> str:
return voice_name_collection_texts(config, language).personalized_greeting_template.format(name=name)
def voice_name_collection_confirmation_greeting(
config: VoiceNameCollectionConfig,
language: str,
name: str,
) -> str:
return voice_name_collection_texts(config, language).confirmation_greeting_template.format(name=name)
def voice_name_collection_inline_followup(config: VoiceNameCollectionConfig, language: str) -> str:
return voice_name_collection_texts(config, language).inline_followup_prompt
def voice_name_collection_followup_markers(
config: VoiceNameCollectionConfig,
language: str,
) -> tuple[str, ...]:
texts = voice_name_collection_texts(config, language)
markers = [texts.inline_followup_prompt]
for template in (texts.confirmation_greeting_template, texts.personalized_greeting_template):
for chunk in str(template or "").split("{name}"):
normalized = chunk.strip(" ,.!?;:-")
if len(normalized) >= 5:
markers.append(normalized)
markers.extend(
[
"как мне к вам обращаться",
"как к вам обращаться",
"вас зовут",
"atyngyz",
"qalai qaratamyn",
]
)
unique_markers: list[str] = []
seen: set[str] = set()
for marker in markers:
key = marker.strip()
if not key or key in seen:
continue
seen.add(key)
unique_markers.append(key)
return tuple(unique_markers)