Add configurable AI operator persona
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user