Files
call-center/services/ai_orchestrator_service/voice_name_config.py
T
Yera AllandClaude Opus 4.6 6798320209 fix: remove dead code duplicates, add SQL LIMIT across all services
- Remove duplicate function definitions with hardcoded "AI-оператор" strings
  (ai_voice_runtime, ai_orchestrator, voice_name_config, voice.py)
- Remove unreachable dead code after return in ai_voice_runtime
- Add SQL LIMIT to 17 unbounded queries across 12 services to prevent OOM
- Move Python-side filtering to SQL WHERE in reporting_service
- Downgrade 19 logger.warning to logger.info for normal-flow events in media_runtime

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 12:04:26 +05:00

165 lines
6.1 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=(
"Здравствуйте, {name}. Коротко расскажите, пожалуйста, чем помочь."
),
confirmation_greeting_template=(
"Если я правильно расслышал, вас зовут {name}? И чем помочь?"
),
inline_followup_prompt="И ещё подскажите, как мне к вам обращаться?",
),
kz=VoiceNameCollectionLanguageTexts(
start_prompt="Сәлеметсіз бе. Атыңызды атайсыз ба?",
personalized_greeting_template=(
"Сәлеметсіз бе, {name}. Қысқаша айтыңызшы, не көмектесу керек."
),
confirmation_greeting_template=(
"Егер дұрыс естісем, атыңыз {name}? Қалай көмектесемін?"
),
inline_followup_prompt="Тағы бір нақтыласам, сізге қалай қараймын?",
),
),
)
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)