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 <noreply@anthropic.com>
This commit is contained in:
@@ -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()`, коммит и деплой; сервис сам синкает значение в БД при старте.
|
||||||
@@ -99,6 +99,7 @@ from services.shared.ai_operator_config import (
|
|||||||
load_ai_operator_config,
|
load_ai_operator_config,
|
||||||
load_effective_ai_operator_config,
|
load_effective_ai_operator_config,
|
||||||
save_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
|
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()
|
init_sql_schema()
|
||||||
|
|
||||||
|
_sync_session = get_session()
|
||||||
|
try:
|
||||||
|
sync_ai_operator_config_from_code(_sync_session)
|
||||||
|
finally:
|
||||||
|
_sync_session.close()
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
_AI_ANALYTICS_CHANNELS = {"telegram", "whatsapp"}
|
_AI_ANALYTICS_CHANNELS = {"telegram", "whatsapp"}
|
||||||
|
|||||||
@@ -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.providers.asr import build_asr_provider, build_streaming_asr_provider
|
||||||
from services.ai_voice_runtime_service.runtime_tts_provider import RuntimeConfiguredTTSProvider
|
from services.ai_voice_runtime_service.runtime_tts_provider import RuntimeConfiguredTTSProvider
|
||||||
from services.ai_orchestrator_service import operator_persona as persona
|
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.core import Role, new_id, utc_now_iso
|
||||||
from services.shared.db import get_session
|
from services.shared.db import get_session
|
||||||
from services.shared.models import (
|
from services.shared.models import (
|
||||||
@@ -1547,6 +1550,11 @@ _MEDIA_RUNTIME = AudioSocketMediaRuntime(
|
|||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def _lifespan(_: FastAPI):
|
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()
|
await _MEDIA_RUNTIME.start()
|
||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
|
|||||||
@@ -82,3 +82,13 @@ def save_ai_operator_config(
|
|||||||
session.commit()
|
session.commit()
|
||||||
session.refresh(row)
|
session.refresh(row)
|
||||||
return AIOperatorConfigOut(config=config, updated_at=row.updated_at, source="database")
|
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())
|
||||||
|
|||||||
Reference in New Issue
Block a user