Add configurable AI operator persona

This commit is contained in:
Magzhan Zhumabayev
2026-05-11 17:37:26 +05:00
parent 67a0255c0d
commit ce3ba270aa
11 changed files with 473 additions and 19 deletions
+79
View File
@@ -12,6 +12,7 @@ ai_module = importlib.import_module("services.ai_orchestrator_service.app")
ai_app = ai_module.app
voice_module = importlib.import_module("services.ai_orchestrator_service.voice")
voice_config_module = importlib.import_module("services.ai_orchestrator_service.voice_name_config")
ai_operator_config_module = importlib.import_module("services.shared.ai_operator_config")
voice_tts_config_module = importlib.import_module("services.shared.voice_tts_config")
from services.interaction_service.app import app as interaction_app
from services.shared.core import new_id, utc_now_iso
@@ -19,6 +20,7 @@ from services.shared.db import get_session
from services.shared.models import VoiceAIStartIn, VoiceAITurnIn
from services.shared.sql_models import (
AIJobRow,
AIOperatorSettingsRow,
AISessionRow,
AITurnRow,
AsteriskCallLinkRow,
@@ -90,6 +92,27 @@ def reset_voice_tts_settings():
session.close()
@pytest.fixture(autouse=True)
def reset_ai_operator_settings():
session = get_session()
try:
row = session.execute(select(AIOperatorSettingsRow)).scalar_one_or_none()
if row is not None:
session.delete(row)
session.commit()
finally:
session.close()
yield
session = get_session()
try:
row = session.execute(select(AIOperatorSettingsRow)).scalar_one_or_none()
if row is not None:
session.delete(row)
session.commit()
finally:
session.close()
def _u(value: str) -> str:
return value.encode("ascii").decode("unicode_escape")
@@ -2880,6 +2903,62 @@ def test_voice_tts_config_put_persists_custom_payload():
session.close()
def test_ai_operator_config_get_returns_ainur_defaults():
ai_client = TestClient(ai_app)
response = ai_client.get("/ai/operator/config", headers=admin_headers())
assert response.status_code == 200
payload = response.json()
assert payload["source"] == "defaults"
assert payload["updated_at"] is None
assert payload["config"]["agent_name"] == "Айнур"
assert "Айнур" in payload["config"]["base_system_prompt"]
assert "Айнур" in payload["config"]["identity_reply_ru"]
def test_ai_operator_config_put_persists_custom_prompt():
ai_client = TestClient(ai_app)
payload = ai_operator_config_module.ai_operator_default_config().model_dump()
payload["company_name"] = "Kazakhtelecom"
payload["base_system_prompt"] = "Ты {agent_name}, единый оператор {company_name}."
payload["identity_reply_ru"] = "Я {agent_name}, оператор {company_name}."
response = ai_client.put("/ai/operator/config", headers=admin_headers(), json=payload)
assert response.status_code == 200
saved = response.json()
assert saved["source"] == "database"
assert saved["config"]["company_name"] == "Kazakhtelecom"
assert saved["config"]["base_system_prompt"] == "Ты {agent_name}, единый оператор {company_name}."
session = get_session()
try:
row = session.execute(select(AIOperatorSettingsRow)).scalar_one()
assert "Kazakhtelecom" in row.config_json
finally:
session.close()
def test_identity_question_uses_same_ainur_reply_without_handoff():
operator_config = ai_operator_config_module.ai_operator_default_config()
decision = ai_module._decide_reply(
customer=None,
interaction=SimpleNamespace(),
thread=SimpleNamespace(),
messages=[SimpleNamespace(author_type="customer", text="Скажи мне, кто ты такой")],
kb_results=[],
language="ru",
operator_config=operator_config,
)
assert decision["intent"] == "identity_question"
assert "Айнур" in decision["reply_text"]
assert decision["needs_handoff"] is False
assert decision["_model"] == "operator_identity_policy"
def test_voice_start_disabled_hands_off_without_prompt():
session = get_session()
try: