From c5dd87ee3248929f5ee860e592a56e7dda60e8aa Mon Sep 17 00:00:00 2001 From: didar Date: Fri, 21 Aug 2026 21:00:34 +0500 Subject: [PATCH] feat: make ai_operator_settings config code the source of truth Add sync_ai_operator_config_from_code() which overwrites the DB-cached ai_operator_settings row from ai_operator_default_config() on startup of ai_orchestrator_service and ai_voice_runtime_service. Greeting and system prompt changes now go through git + deploy instead of manual psql/API edits to prod. Also adds a root README pointing to existing docs. Co-Authored-By: Claude Sonnet 5 --- README.md | 41 ++++++++++++++++++++++++ services/ai_orchestrator_service/app.py | 7 ++++ services/ai_voice_runtime_service/app.py | 10 +++++- services/shared/ai_operator_config.py | 10 ++++++ 4 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..c1aea5d --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# call-center + +Омниканальный контакт-центр: телефония (Asterisk), Telegram, WhatsApp, веб-чат, email — плюс встроенный голосовой ИИ-оператор (распознавание речи → LLM → синтез речи). Написан на Python (FastAPI), ~25 сервисов вокруг одной SQLAlchemy-схемы, локально работает на SQLite, в проде — на PostgreSQL. + +## Документация + +| Документ | Про что | +|---|---| +| [`docs/architecture/overview.md`](docs/architecture/overview.md) | Обзор микросервисов и их ролей | +| [`docs/architecture/voice-ai.md`](docs/architecture/voice-ai.md) | Голосовой ИИ-оператор подробно | +| [`docs/architecture/real-time-voice-service.md`](docs/architecture/real-time-voice-service.md) | Рантайм голосового моста (AudioSocket, TTS/ASR) | +| [`docs/architecture/event-schemas.md`](docs/architecture/event-schemas.md) | Схемы событий шины (RabbitMQ) | +| [`docs/runbooks/local-setup.md`](docs/runbooks/local-setup.md) | Как поднять проект локально (демо-режим и ручной запуск) | +| [`docs/runbooks/deployment.md`](docs/runbooks/deployment.md) | Как разворачивать (Docker Compose / Kubernetes) | +| [`longread.md`](longread.md) | Сквозной архитектурный ревью репозитория — что реально есть, где технический долг | + +## Быстрый старт + +Подробности — в [`docs/runbooks/local-setup.md`](docs/runbooks/local-setup.md). Коротко: + +```bash +python -m pip install -r requirements.txt +cp .env.example .env # по умолчанию SQLite, без Docker + +python scripts/migrate_core_db.py +uvicorn gateway.app:app --reload --port 8080 +``` + +Точки входа (после старта `gateway`): `/operator`, `/supervisor`, `/admin`, `/analyst` на `http://localhost:8080`. + +Демо-режим одной командой (поднимает весь стек + тестовые данные): `scripts/prepare_demo.ps1`. + +## Тесты + +```bash +pytest -q +``` + +## Частые задачи + +- **Поменять greeting/system prompt голосового бота Айнур** — правится в коде: `services/shared/ai_operator_config.py` → `ai_operator_default_config()`, коммит и деплой; сервис сам синкает значение в БД при старте. diff --git a/services/ai_orchestrator_service/app.py b/services/ai_orchestrator_service/app.py index dc988b3..fb91aa0 100644 --- a/services/ai_orchestrator_service/app.py +++ b/services/ai_orchestrator_service/app.py @@ -99,6 +99,7 @@ from services.shared.ai_operator_config import ( load_ai_operator_config, load_effective_ai_operator_config, save_ai_operator_config, + sync_ai_operator_config_from_code, ) from services.shared.voice_tts_config import load_voice_tts_config, save_voice_tts_config @@ -106,6 +107,12 @@ app = FastAPI(title="ai-orchestrator-service", version="1.0.0") init_sql_schema() +_sync_session = get_session() +try: + sync_ai_operator_config_from_code(_sync_session) +finally: + _sync_session.close() + logger = logging.getLogger(__name__) _AI_ANALYTICS_CHANNELS = {"telegram", "whatsapp"} diff --git a/services/ai_voice_runtime_service/app.py b/services/ai_voice_runtime_service/app.py index b9188d3..c7c1b90 100644 --- a/services/ai_voice_runtime_service/app.py +++ b/services/ai_voice_runtime_service/app.py @@ -19,7 +19,10 @@ from services.ai_voice_runtime_service.media_runtime import AudioSocketMediaRunt from services.ai_voice_runtime_service.providers.asr import build_asr_provider, build_streaming_asr_provider from services.ai_voice_runtime_service.runtime_tts_provider import RuntimeConfiguredTTSProvider from services.ai_orchestrator_service import operator_persona as persona -from services.shared.ai_operator_config import load_effective_ai_operator_config +from services.shared.ai_operator_config import ( + load_effective_ai_operator_config, + sync_ai_operator_config_from_code, +) from services.shared.core import Role, new_id, utc_now_iso from services.shared.db import get_session from services.shared.models import ( @@ -1547,6 +1550,11 @@ _MEDIA_RUNTIME = AudioSocketMediaRuntime( @asynccontextmanager async def _lifespan(_: FastAPI): + _sync_session = get_session() + try: + sync_ai_operator_config_from_code(_sync_session) + finally: + _sync_session.close() await _MEDIA_RUNTIME.start() try: yield diff --git a/services/shared/ai_operator_config.py b/services/shared/ai_operator_config.py index 5444a28..5159e10 100644 --- a/services/shared/ai_operator_config.py +++ b/services/shared/ai_operator_config.py @@ -82,3 +82,13 @@ def save_ai_operator_config( session.commit() session.refresh(row) return AIOperatorConfigOut(config=config, updated_at=row.updated_at, source="database") + + +def sync_ai_operator_config_from_code(session) -> AIOperatorConfigOut: + """Overwrite the DB-stored config with the code defaults. + + Called on service startup so `ai_operator_default_config()` is the + source of truth: edit it, deploy, and the DB row is overwritten to match. + Any manual edits made via the API/psql since the last deploy are lost. + """ + return save_ai_operator_config(session, ai_operator_default_config())