docs: recreate documentation structure with updated specs
This commit is contained in:
@@ -1,91 +0,0 @@
|
||||
# API Matrix by Stage
|
||||
|
||||
## Stage 1
|
||||
- auth-service: `/auth/login`, `/auth/me`, `/users`
|
||||
- audit-service: `/audit/events`
|
||||
- all services: `/health`
|
||||
|
||||
## Stage 2
|
||||
- customer-service: `/customers`
|
||||
- interaction-service: `/interactions`
|
||||
- routing-service: `/queues`
|
||||
- voice-adapter-service: `/integrations/voice/events`
|
||||
- telegram-adapter-service: `/integrations/telegram/webhook`
|
||||
|
||||
## Stage 3
|
||||
- kb-service: `/knowledge/*`
|
||||
- reporting-service: `/reports/kpi`, `/reports/export`
|
||||
- supervisor-service: `/supervisor/realtime`
|
||||
|
||||
## Stage 4
|
||||
- recording-service: `/recordings`, `/recordings/*`
|
||||
|
||||
## Stage 5
|
||||
- ivr-service: `/ivr/flows`, `/ivr/flows/*`, `/ivr/sessions`, `/ivr/sessions/*`
|
||||
- routing-service: `/queues/{queue_id}/route` with optional `ivr_session_id`
|
||||
|
||||
## Stage 6
|
||||
- reporting-service: `/reports/kpi` with optional `channel`
|
||||
- reporting-service: `/reports/coverage`
|
||||
- reporting-service: `/reports/export` with optional `queue_id` and `channel`
|
||||
|
||||
## Stage 7
|
||||
- no new business REST routes
|
||||
- ops CLI:
|
||||
- `scripts/load_test.py` with profile-driven mixed workload
|
||||
- `scripts/track7_check.py` for scale/hardening evidence validation
|
||||
|
||||
## Stage 8
|
||||
- no new business REST routes
|
||||
- event-bus-service (ops only): `/bus/outbox`, `/bus/outbox/*`
|
||||
- ops CLI:
|
||||
- `scripts/event_bus_smoke.py`
|
||||
- `scripts/track8_check.py`
|
||||
|
||||
## Stage 9
|
||||
- no new business REST routes
|
||||
- recording-service: `/recordings/import-upload` (internal bridge upload)
|
||||
- asterisk-bridge-service (ops only): `/asterisk/status`, `/asterisk/events`, `/asterisk/events/*`
|
||||
- bridge-to-platform auth modes:
|
||||
- `legacy_headers` (QA baseline)
|
||||
- `bearer_first` / `bearer` (Track 9.1 hardening)
|
||||
- ops CLI:
|
||||
- `scripts/track9_preflight.py`
|
||||
- `scripts/asterisk_lab_smoke.py`
|
||||
- `scripts/track9_check.py`
|
||||
- `scripts/track9_collect_evidence.py`
|
||||
- `scripts/track9_2_cutover.ps1` (controlled Helm cutover automation)
|
||||
|
||||
## Stage 11
|
||||
- no new business REST routes
|
||||
- asterisk-bridge-service call-control routes:
|
||||
- `/asterisk/live-calls`
|
||||
- `/asterisk/live-calls/{call_id}/claim`
|
||||
- `/asterisk/live-calls/{call_id}/hangup`
|
||||
- `/asterisk/live-calls/{call_id}/blind-transfer`
|
||||
- `/asterisk/live-calls/{call_id}/actions`
|
||||
- operator shell uses these routes for live call handling with external softphone media
|
||||
|
||||
## Stage 12
|
||||
- no new business REST routes
|
||||
- additive operator UX route:
|
||||
- `/asterisk/recent-calls`
|
||||
- operator shell uses this route for the short-lived recent-calls bucket
|
||||
|
||||
## Stage 14
|
||||
- no new business REST routes
|
||||
- additive browser softphone config route:
|
||||
- `/asterisk/browser-softphone/config`
|
||||
- operator shell may use direct browser media over Asterisk WSS in QA
|
||||
|
||||
## Stage 15
|
||||
- telegram-adapter-service real-bot webhook:
|
||||
- `/integrations/telegram/bot/webhook`
|
||||
- telegram-adapter-service operator thread workspace:
|
||||
- `/integrations/telegram/threads`
|
||||
- `/integrations/telegram/threads/{thread_id}`
|
||||
- `/integrations/telegram/threads/{thread_id}/messages`
|
||||
- `/integrations/telegram/threads/{thread_id}/claim`
|
||||
- `/integrations/telegram/threads/{thread_id}/close`
|
||||
- `/integrations/telegram/threads/{thread_id}/escalate`
|
||||
- operator shell gets a separate `Telegram` page as the canonical surface for this channel
|
||||
@@ -1,59 +0,0 @@
|
||||
# Asterisk UserEvent Contract
|
||||
|
||||
Track 9 uses dialplan-generated `UserEvent` frames over AMI as the stable bridge contract
|
||||
between Asterisk and the platform.
|
||||
|
||||
The bridge listens only for `UserEvent` names starting with `MVPCC`.
|
||||
|
||||
## `MVPCCCallStarted`
|
||||
|
||||
Required fields:
|
||||
|
||||
- `CallID`
|
||||
- `LinkedID`
|
||||
- `CallerNumber`
|
||||
- `CallerName`
|
||||
- `QueueCode`
|
||||
- `Extension`
|
||||
- `Context`
|
||||
- `Direction`
|
||||
|
||||
Example:
|
||||
|
||||
```text
|
||||
Event: UserEvent
|
||||
UserEvent: MVPCCCallStarted
|
||||
CallID: 1740912000.12
|
||||
LinkedID: 1740912000.12
|
||||
CallerNumber: 1001
|
||||
CallerName: Lab Caller
|
||||
QueueCode: voice_lab
|
||||
Extension: 7000
|
||||
Context: from-softphones
|
||||
Direction: inbound
|
||||
```
|
||||
|
||||
## `MVPCCCallEnded`
|
||||
|
||||
Required fields:
|
||||
|
||||
- `CallID`
|
||||
- `LinkedID`
|
||||
- `HangupCause`
|
||||
- `DurationSeconds`
|
||||
|
||||
## `MVPCCRecordingReady`
|
||||
|
||||
Required fields:
|
||||
|
||||
- `CallID`
|
||||
- `LinkedID`
|
||||
- `RemotePath`
|
||||
- `FileName`
|
||||
- `MimeType`
|
||||
- `DurationSeconds`
|
||||
|
||||
Notes:
|
||||
|
||||
- `RemotePath` is the absolute path on the Asterisk Linux VM.
|
||||
- The platform downloads the file over SFTP, then uploads it into `recording-service`.
|
||||
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "call.recording.ready.json",
|
||||
"title": "call.recording.ready",
|
||||
"type": "object",
|
||||
"required": ["event_type", "call_id", "payload"],
|
||||
"properties": {
|
||||
"event_type": {
|
||||
"const": "recording.ready"
|
||||
},
|
||||
"call_id": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"interaction_id": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"payload": {
|
||||
"type": "object",
|
||||
"required": ["source_path"],
|
||||
"properties": {
|
||||
"source_path": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"file_name": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"mime_type": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"duration_seconds": {
|
||||
"type": ["integer", "null"],
|
||||
"minimum": 0
|
||||
},
|
||||
"recorded_at": {
|
||||
"type": ["string", "null"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://mvp-cc.local/docs/architecture/event-envelope.json",
|
||||
"title": "EventEnvelope",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"event_id",
|
||||
"event_type",
|
||||
"event_version",
|
||||
"occurred_at",
|
||||
"producer",
|
||||
"entity_type",
|
||||
"entity_id",
|
||||
"routing_key",
|
||||
"payload"
|
||||
],
|
||||
"properties": {
|
||||
"event_id": {"type": "string", "minLength": 1},
|
||||
"event_type": {"type": "string", "minLength": 1},
|
||||
"event_version": {"type": "integer", "minimum": 1},
|
||||
"occurred_at": {"type": "string", "format": "date-time"},
|
||||
"producer": {"type": "string", "minLength": 1},
|
||||
"entity_type": {"type": "string", "minLength": 1},
|
||||
"entity_id": {"type": "string", "minLength": 1},
|
||||
"correlation_id": {"type": ["string", "null"]},
|
||||
"routing_key": {"type": "string", "minLength": 1},
|
||||
"payload": {"type": "object"}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
@@ -1,37 +1,41 @@
|
||||
# Event Schema Index
|
||||
# Контракты Событий (Event Schemas)
|
||||
|
||||
- `interaction.created.json`
|
||||
- `interaction.assigned.json`
|
||||
- `interaction.escalated.json`
|
||||
- `interaction.closed.json`
|
||||
- `agent.state.changed.json`
|
||||
- `call.recording.ready.json`
|
||||
- `call.connected.json`
|
||||
- `call.transferred.json`
|
||||
- `ivr.completed.json`
|
||||
Платформа контакт-центра использует событийно-ориентированную (Event-Driven) архитектуру поверх брокера сообщений **RabbitMQ** (с использованием паттерна Transactional Outbox). Все события (сообщения) следуют подходу "Contract-First" и описаны JSON-схемами.
|
||||
|
||||
Each file is a JSON Schema (draft 2020-12) and can be used as a validation contract between services.
|
||||
Исходники всех актуальных JSON-схем хранятся в директории `contracts/events/`.
|
||||
|
||||
Track 8 introduces a canonical transport envelope for bus-delivered events:
|
||||
## Список доменных событий (Domain Events)
|
||||
|
||||
- `docs/architecture/event-envelope.json`
|
||||
### Жизненный цикл звоноков (Telephony & Media Events)
|
||||
Шлюз телефонии (Asterisk Bridge Service) инфраструктура генерирует события о состоянии вызовов в реальном времени.
|
||||
|
||||
Payload-specific schemas under `contracts/events` remain the authoritative contracts for the
|
||||
domain payload inside that envelope.
|
||||
- `call.connected.json` — Выстреливает, когда звонок успешно сопряжен между абонентом и конечной точкой (оператором или ИИ-помощником).
|
||||
- `call.transferred.json` — Срабатывает при физическом переключении звонка (например, слепой трансфер от оператора к оператору, или Handoff от ИИ к живой очереди).
|
||||
- `call.recording.ready.json` — Ивент о том, что аудиозапись звонка завершена, обработана и скопирована в хранилище (управляется `recording-service`).
|
||||
- `ivr.completed.json` — Генерируется сервисом `ivr-service`, когда абонент завершил обход голосового меню (нажал необходимые DTMF цифры) и готов к маршрутизации.
|
||||
|
||||
Track 4 now provides the concrete schema file for the recording import placeholder:
|
||||
### Жизненный цикл обращений (Interaction Events)
|
||||
Отвечают за высокоуровневую бизнес-логику тикетов и многоканальных чатов. В основном генерируются в `interaction-service`.
|
||||
|
||||
- `docs/architecture/call.recording.ready.json`
|
||||
- `interaction.created.json` — В систему поступило новое обращение (клиент позвонил, написал в Telegram/Webchat или Email). Содержит метаданные канала.
|
||||
- `interaction.assigned.json` — Обращение захвачено живым агентом (или назначено routing-движком).
|
||||
- `interaction.escalated.json` — Агент или ИИ эскалировал тикет на уровень выше (например, на вторую линию поддержки - L2, или пометил тикет как критичный).
|
||||
- `interaction.closed.json` — Диалог или звонок успешно завершен, подведены итоги работы.
|
||||
|
||||
Track 5 adds the concrete schema for IVR completion:
|
||||
### Состояния операторов (Agent State Events)
|
||||
- `agent.state.changed.json` — Транслирует изменения статуса сотрудника ("Готов", "Перерыв", "В разговоре"). Используется в `supervisor-service` для отрисовки Dashboard в реальном времени, а также потребляется `routing-service` для распределения звонков по свободным операторам.
|
||||
|
||||
- `docs/architecture/ivr.completed.json`
|
||||
## Структура Конверта (Event Envelope)
|
||||
Любое доменное сообщение оборачивается в стандартизированный JSON-конверт для успешного трансфера через шину `event-bus-service`:
|
||||
|
||||
Track 9 adds the dialplan-to-platform `UserEvent` contract reference:
|
||||
|
||||
- `docs/architecture/asterisk-user-events.md`
|
||||
|
||||
Track 11 adds additive voice lifecycle event types produced by call-control flow:
|
||||
|
||||
- `call.connected` (operator connected to customer)
|
||||
- `call.transferred` (blind transfer requested by operator/admin)
|
||||
```json
|
||||
{
|
||||
"event_id": "8f39b1a0-54f3-...",
|
||||
"event_type": "call.connected",
|
||||
"timestamp": "2026-04-06T12:00:00Z",
|
||||
"source_service": "asterisk-bridge-service",
|
||||
"payload": {
|
||||
// Внутреннее содержимое строго по одной из JSON-схем, описанных выше
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://mvp-cc.local/contracts/events/ivr.completed.json",
|
||||
"title": "ivr.completed",
|
||||
"type": "object",
|
||||
"required": ["event_type", "call_id", "payload"],
|
||||
"properties": {
|
||||
"event_type": {
|
||||
"const": "ivr.completed"
|
||||
},
|
||||
"call_id": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"interaction_id": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"payload": {
|
||||
"type": "object",
|
||||
"required": ["session_id", "flow_id", "outcome_code", "resolved_queue_id", "digits", "terminal_node_id"],
|
||||
"properties": {
|
||||
"session_id": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"flow_id": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"outcome_code": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"resolved_queue_id": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"digits": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]$"
|
||||
}
|
||||
},
|
||||
"terminal_node_id": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
@@ -1,25 +1,53 @@
|
||||
# Architecture Overview
|
||||
# Обзор Архитектуры Контакт-Центра (MVP)
|
||||
|
||||
## Services
|
||||
- api-gateway
|
||||
- auth-service
|
||||
- audit-service
|
||||
- customer-service
|
||||
- interaction-service
|
||||
- routing-service
|
||||
- voice-adapter-service
|
||||
- telegram-adapter-service
|
||||
- kb-service
|
||||
- reporting-service
|
||||
- supervisor-service
|
||||
Контакт-центр спроектирован на базе микросервисной архитектуры с использованием событийно-ориентированного подхода (Event-Driven Architecture). Описываемая платформа поддерживает омниканальные коммуникации (Голос, Telegram, Email, Webchat, WhatsApp) с глубокой интеграцией AI-операторов (Voice AI V1, Telegram AI) и полноценной системой маршрутизации.
|
||||
|
||||
## Data and event strategy
|
||||
- All services use SQL storage via `DATABASE_URL`
|
||||
- Default local mode: SQLite (`.data/mvp_cc.db`)
|
||||
- Target mode: PostgreSQL (supported in `deployment/docker-compose.yml`)
|
||||
- Contract-first API and JSON event schema artifacts
|
||||
- Event contracts are fixed and ready for message bus integration (RabbitMQ/Kafka) in next phase
|
||||
## 1. Основные компоненты (Микросервисы)
|
||||
|
||||
## Deployment
|
||||
- Local: Docker Compose
|
||||
- On-prem: Kubernetes manifests and Helm chart
|
||||
Архитектура состоит из множества независимых сервисов, написанных на Python (FastAPI):
|
||||
|
||||
### Базовые и инфраструктурные сервисы
|
||||
- **`api-gateway`** — Единая точка входа для клиентских интерфейсов (UI оператора/супервизора/админа).
|
||||
- **`auth-service`** — Сервис аутентификации и RBAC. Поддерживает OIDC (Keycloak) и авторизацию по `APP_TOKEN_SECRET`.
|
||||
- **`audit-service`** — Служба записи логов аудита системных действий (подключена к шине событий).
|
||||
- **`event-bus-service`** — Асинхронная шина событий. Реализует паттерн Transactional Outbox/Inbox поверх брокера **RabbitMQ** для надежной доставки сообщений между сервисами.
|
||||
|
||||
### Клиентоцентричные сервисы и роутинг
|
||||
- **`customer-service`** — Хранит профили клиентов. Управляет связыванием номеров телефонов и Telegram/Webchat-аккаунтов в единый профиль (`customer_external_identities`).
|
||||
- **`interaction-service`** — Канонический источник истины об "обращениях" (Interactions). Хранит таймлайн общения клиента и статусы тикетов.
|
||||
- **`routing-service`** — Умная маршрутизация: оценка занятости сотрудников, скилл-бейзд роутинг, распределение по очередям.
|
||||
|
||||
### Телефония и Медиа каналы (Adapters)
|
||||
- **`asterisk-bridge-service`** — Главный мост с Asterisk телефонией (перехват AMI-событий с префиксом `MVPCC`). Отслеживает звонки, трансферы и командует Asterisk'ом.
|
||||
- **`ivr-service`** — Сервис голосового меню. Настраивает IVR потоки (flows) через интерфейс администратора и обрабатывает DTMF клики.
|
||||
- **`recording-service`** — Сервис управления записями звонков. Позволяет скачивать (SFTP) и локально управлять аудиофайлами звонков.
|
||||
- **`voice-adapter-service`** — Сохраняет события жизненного цикла звонков в БД.
|
||||
- **`telegram-adapter-service`** / **`email_adapter_service`** / **`webchat_adapter_service`** / **`whatsapp_adapter_service`** — Адаптеры интеграции с внешними цифровыми каналами (мессенджерами и веб-чатами).
|
||||
|
||||
### Интеллект и База Знаний (AI & KB)
|
||||
- **`kb-service`** — Локальная База Знаний. Содержит категории, статьи и обеспечивает быстрый поиск.
|
||||
- **`ai_orchestrator_service`** — Детерминированный мозг ИИ. Принимает решения о генерации ответа или передаче диалога (Handoff) оператору на основе контекста и бизнес-правил.
|
||||
- **`ai_voice_runtime_service`** — Реал-тайм прослойка аудио-моста (Speech-to-Text / Text-to-Speech) для голосового робота. Обрабатывает перебивания (barge-in) в реальном времени.
|
||||
|
||||
### Аналитика, Отчетность и Надзор
|
||||
- **`reporting-service`** — Агрегация KPI (Service Level, ASA, AHT, Abandon Rate, FCR) в разрезе каналов и агентов.
|
||||
- **`supervisor-service`** — "Живой" мониторинг контакт-центра (статусы агентов, метрики, прослушка записей).
|
||||
|
||||
## 2. Пользовательские Интерфейсы (UI Shells)
|
||||
|
||||
Проект предоставляет несколько разделенных рабочих сред:
|
||||
- `/operator` — Единое окно оператора (Обработка звонков: «Взять в работу», «Трансфер», «Сброс», переписка Telegram/Webchat). ИИ-сводки по звонкам.
|
||||
- `/supervisor` — Окно супервизора (Мониторинг очередей, статусов агентов, поиск и прослушивание записей звонков из `recording-service`).
|
||||
- `/admin` — Администрирование (Назначение ролей, управление очередями, конфигурация IVR деревьев, превью маршрутизации).
|
||||
- `/analyst` — Выгрузки и детальная панель исторических KPI и отчетов.
|
||||
|
||||
## 3. Стратегия Данных и Развертывание
|
||||
|
||||
1. **Хранение данных:** Каждый микросервис спроектирован так, чтобы иметь изолированную логическую базу.
|
||||
- По умолчанию используется локальный **SQLite**.
|
||||
- Для K8s Scale/Продакшена используется **PostgreSQL** DB Pooling (настраивается через переменные `DB_POOL_SIZE`, `DB_MAX_OVERFLOW`).
|
||||
- Исходным механизмом миграций является `Alembic` (скрипт `scripts/migrate_core_db.py`).
|
||||
2. **Инфраструктура / Развертывание:**
|
||||
- Разработка ведется через локальные Docker Compose конфигурации (`docker-compose.yml`, `docker-compose.server.yml`). Готов набор PS/Bash скриптов в директории `scripts/` (например, `scripts/prepare_demo.ps1`).
|
||||
- Для серверов и Enterprise масштабов написаны Kubernetes Helm Chart (`deployment/helm`) и K8s манифесты.
|
||||
- Шина **RabbitMQ** используется как канонический брокер сообщений для `event_outbox`/`event_inbox` логики обмена.
|
||||
|
||||
@@ -1,744 +0,0 @@
|
||||
# Voice AI V1 Technical Design
|
||||
|
||||
Status: `Proposed`
|
||||
|
||||
## 1. Goal
|
||||
|
||||
Add an AI-first voice operator on top of the accepted Asterisk voice baseline without replacing the current telephony stack.
|
||||
|
||||
Voice AI V1 must:
|
||||
|
||||
- answer selected inbound calls before a human joins;
|
||||
- reuse the existing `interaction`, `customer`, `KB`, `queue`, and operator UI model;
|
||||
- keep Asterisk as the system of record for call lifecycle, transfer, recording, SIP/WebRTC, and queue ownership;
|
||||
- store business state in platform tables, not only inside model context;
|
||||
- support deterministic AI-to-human handoff into the existing human queue.
|
||||
|
||||
## 2. Non-goals
|
||||
|
||||
Voice AI V1 does not:
|
||||
|
||||
- replace Asterisk, AMI, dialplan, queueing, SIP/WebRTC, or recording;
|
||||
- introduce a separate AI telephony platform or second source of truth for calls;
|
||||
- push full CRM history, full call transcript, or raw recordings into every model turn;
|
||||
- introduce a multi-agent swarm;
|
||||
- add attended transfer, conference, or full human-to-AI return in the same live call.
|
||||
|
||||
`return-to-ai` after human takeover is intentionally deferred to V2. It requires a second telephony redirect and fresh media re-attachment, which is riskier than AI-first plus human handoff.
|
||||
|
||||
## 3. Existing Baseline We Build On
|
||||
|
||||
The current repo already has the pieces needed for Voice AI V1:
|
||||
|
||||
- `asterisk_bridge_service` owns AMI ingestion, live call tracking, claim/hangup/transfer, and recording import.
|
||||
- `voice_adapter_service` stores telephony lifecycle events.
|
||||
- `interaction_service` owns the canonical interaction lifecycle.
|
||||
- `customer_service` plus `customer_external_identities` already provide cross-channel identity linking.
|
||||
- `ai_orchestrator_service` already implements the Telegram AI pattern:
|
||||
- deterministic orchestration;
|
||||
- explicit AI disclosure;
|
||||
- `ai_sessions` and `ai_turns`;
|
||||
- AI-to-human handoff;
|
||||
- source-of-truth separation between channel service and AI service.
|
||||
- `/operator` already has a hybrid Telegram AI UX with AI badges and AI summary cards.
|
||||
|
||||
Voice AI V1 should copy the Telegram AI service boundary pattern, not the Telegram channel model itself.
|
||||
|
||||
## 4. Target Architecture
|
||||
|
||||
### 4.1 Service responsibilities
|
||||
|
||||
| Service | Role in Voice AI V1 |
|
||||
| --- | --- |
|
||||
| `asterisk_bridge_service` | Keeps telephony truth, selects AI path for eligible calls, starts/stops AI runtime sessions, performs AI-to-human redirect, exposes operator-facing voice AI summary. |
|
||||
| `ai_voice_runtime_service` | New realtime service. Owns media session state, ASR/TTS streaming, turn detection, barge-in, runtime latency budget, and handoff requests. |
|
||||
| `ai_orchestrator_service` | Reused and extended. Owns deterministic voice decisioning, filtered context assembly, KB/tool usage, safe business policies, and AI summary generation. |
|
||||
| `interaction_service` | Remains the owner of interaction status and timeline; gains an internal timeline append endpoint for additive AI events. |
|
||||
| `voice_adapter_service` | Stays the owner of telephony event persistence only. It does not become the AI runtime. |
|
||||
| `ui/operator` | Reuses the current live call and popup flow; adds voice AI badges and handoff summary, but no separate `AI telephony` workspace. |
|
||||
|
||||
### 4.2 High-level component view
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["Asterisk<br/>queue, redirect, recording, SIP/WebRTC"] --> B["asterisk_bridge_service<br/>call truth + control plane"]
|
||||
B --> C["ai_voice_runtime_service<br/>media bridge + ASR/TTS + barge-in"]
|
||||
C --> D["ai_orchestrator_service<br/>policy + tools + summaries"]
|
||||
D --> E["customer_service / shared DB<br/>customer profile + memory"]
|
||||
D --> F["interaction_service<br/>interaction status + timeline"]
|
||||
D --> G["kb_service<br/>KB lookup"]
|
||||
B --> H["operator UI<br/>live call popup + voice AI summary"]
|
||||
```
|
||||
|
||||
### 4.3 Source-of-truth rule
|
||||
|
||||
- Telephony truth stays in `AsteriskCallLinkRow`, AMI events, queue redirect, and recording flow.
|
||||
- AI truth stays in `voice_ai_sessions`, `ai_sessions`, `ai_turns`, and transcript rows.
|
||||
- Interaction truth stays in `Interaction` plus `InteractionTimeline`.
|
||||
- Customer truth stays in `Customer` plus `CustomerExternalIdentity`.
|
||||
|
||||
No single prompt becomes the long-term state container.
|
||||
|
||||
## 5. Call Flow
|
||||
|
||||
### 5.1 AI-first inbound voice flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant A as Asterisk
|
||||
participant B as asterisk_bridge_service
|
||||
participant R as ai_voice_runtime_service
|
||||
participant O as ai_orchestrator_service
|
||||
participant I as interaction_service
|
||||
|
||||
A->>B: MVPCCCallStarted
|
||||
B->>I: create / reuse interaction
|
||||
B->>B: create or update AsteriskCallLinkRow
|
||||
B->>R: POST /internal/voice-ai/sessions
|
||||
R->>O: POST /ai/voice/sessions/{id}/start
|
||||
R->>A: play AI disclosure + greeting
|
||||
A->>R: caller audio stream
|
||||
R->>O: POST /ai/voice/sessions/{id}/turns
|
||||
O-->>R: reply plan or handoff decision
|
||||
R->>A: TTS playback
|
||||
```
|
||||
|
||||
Detailed steps:
|
||||
|
||||
1. `asterisk_bridge_service` receives the existing `MVPCCCallStarted` event.
|
||||
2. It keeps the current behavior of creating or resolving `interaction_id` and `AsteriskCallLinkRow`.
|
||||
3. It evaluates whether this queue or IVR outcome should go to AI-first mode.
|
||||
4. If not AI-eligible, the current human flow stays unchanged.
|
||||
5. If AI-eligible:
|
||||
- create `voice_ai_session`;
|
||||
- create or link generic `ai_session` with `channel="voice"`;
|
||||
- mark the call row as AI-owned;
|
||||
- start a runtime control session in `ai_voice_runtime_service`;
|
||||
- attach the customer leg to the AI media bridge.
|
||||
6. `ai_voice_runtime_service` plays a short disclosure and greeting.
|
||||
7. Finalized caller utterances are sent to `ai_orchestrator_service`.
|
||||
8. The orchestrator loads filtered business context, runs deterministic policy and KB/tool lookup, and returns a structured decision.
|
||||
9. The runtime either:
|
||||
- plays TTS back to the caller; or
|
||||
- requests handoff to a human queue.
|
||||
|
||||
### 5.2 AI-to-human handoff
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant R as ai_voice_runtime_service
|
||||
participant O as ai_orchestrator_service
|
||||
participant B as asterisk_bridge_service
|
||||
participant A as Asterisk
|
||||
participant U as Operator UI
|
||||
|
||||
R->>O: POST voice turn
|
||||
O-->>R: needs_handoff=true + reason + summary
|
||||
R->>B: POST /internal/voice-ai/calls/{call_id}/handoff
|
||||
B->>B: mark ai_state=handoff_required
|
||||
B->>A: redirect caller leg to existing human queue
|
||||
A->>U: normal operator incoming call path
|
||||
U->>B: GET /asterisk/live-calls/{call_id}/ai-summary
|
||||
B-->>U: AI handoff summary
|
||||
```
|
||||
|
||||
Handoff rules:
|
||||
|
||||
- handoff happens only through the existing Asterisk queue path;
|
||||
- the AI runtime never calls an operator directly and never becomes a second queueing owner;
|
||||
- `interaction_id` stays the same through AI and human phases;
|
||||
- the operator receives a concise AI summary before or during claim.
|
||||
|
||||
### 5.3 Call end
|
||||
|
||||
- `MVPCCCallEnded` and `MVPCCRecordingReady` continue to come from Asterisk through the existing bridge.
|
||||
- The bridge informs the runtime session that telephony ended.
|
||||
- The runtime closes `voice_ai_session`.
|
||||
- The orchestrator writes the final AI summary and marks `ai_session` closed.
|
||||
- The interaction timeline gets final AI events if they were not written yet.
|
||||
|
||||
## 6. Queue Selection and AI Eligibility
|
||||
|
||||
Voice AI V1 should start with config-driven AI eligibility, not a new admin UI.
|
||||
|
||||
Recommended V1 config:
|
||||
|
||||
`AI_VOICE_QUEUE_CONFIG_JSON`
|
||||
|
||||
Example shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"voice_lab": {
|
||||
"mode": "ai_first",
|
||||
"agent_profile": "voice_support",
|
||||
"handoff_queue_code": "voice_lab",
|
||||
"language": "ru"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Why config-first:
|
||||
|
||||
- it avoids touching the accepted routing UI and queue schema in the first step;
|
||||
- it keeps rollout reversible per queue;
|
||||
- it matches the current repo style where telephony behavior is still largely env-driven.
|
||||
|
||||
The longer-term path can move this policy into routing/admin later.
|
||||
|
||||
## 7. Data Model
|
||||
|
||||
## 7.1 Reused tables
|
||||
|
||||
These tables stay authoritative and should be reused:
|
||||
|
||||
- `interactions`
|
||||
- `interaction_timelines`
|
||||
- `customer_external_identities`
|
||||
- `asterisk_call_links`
|
||||
- `voice_events`
|
||||
- `call_recordings`
|
||||
- `ai_sessions`
|
||||
- `ai_turns`
|
||||
|
||||
Voice customer linking should reuse `customer_external_identities` with:
|
||||
|
||||
- `channel = "voice"`
|
||||
- `external_subject = normalized caller number`
|
||||
|
||||
No new identity table is needed.
|
||||
|
||||
## 7.2 New table: `voice_ai_sessions`
|
||||
|
||||
This table stores runtime state that does not fit cleanly into generic `ai_sessions`.
|
||||
|
||||
Suggested columns:
|
||||
|
||||
| Column | Type | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `id` | integer pk | Internal row id |
|
||||
| `session_id` | string unique | Public runtime session id |
|
||||
| `call_id` | string unique index | Current Asterisk customer-leg call id |
|
||||
| `linked_id` | string index | Asterisk linked id for recovery if call id changes |
|
||||
| `interaction_id` | string index | Shared interaction |
|
||||
| `customer_id` | string index nullable | Linked customer |
|
||||
| `queue_id` | string index | Original platform queue |
|
||||
| `ai_session_id` | string index | Generic AI session link |
|
||||
| `agent_profile` | string index | Voice policy profile |
|
||||
| `status` | string index | `queued`, `greeting`, `listening`, `thinking`, `speaking`, `handoff_requested`, `human_owned`, `completed`, `error` |
|
||||
| `language` | string index nullable | Current active language |
|
||||
| `asr_provider` | string nullable | Selected ASR backend |
|
||||
| `tts_provider` | string nullable | Selected TTS backend |
|
||||
| `handoff_reason` | text nullable | Last handoff reason |
|
||||
| `handoff_target_queue_id` | string nullable | Human target queue |
|
||||
| `disclosure_played_at` | string nullable | When AI disclosure was first spoken |
|
||||
| `last_user_utterance_at` | string nullable | Latest finalized caller speech |
|
||||
| `last_ai_reply_at` | string nullable | Latest completed AI reply |
|
||||
| `started_at` | string index | Session start |
|
||||
| `updated_at` | string index | Last state update |
|
||||
| `ended_at` | string index nullable | Session end |
|
||||
|
||||
## 7.3 New table: `voice_transcript_segments`
|
||||
|
||||
This table stores finalized voice transcript units separately from generic AI turns.
|
||||
|
||||
Suggested columns:
|
||||
|
||||
| Column | Type | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `id` | integer pk | Internal row id |
|
||||
| `segment_id` | string unique | Public segment id |
|
||||
| `session_id` | string index | `voice_ai_sessions.session_id` |
|
||||
| `call_id` | string index | Voice call correlation |
|
||||
| `interaction_id` | string index | Interaction correlation |
|
||||
| `speaker` | string index | `caller`, `assistant`, `system`, `operator` |
|
||||
| `source_type` | string index | `asr`, `tts`, `handoff_summary`, `system` |
|
||||
| `sequence_no` | integer index | Ordered segment number |
|
||||
| `text` | text | Final transcript text |
|
||||
| `confidence` | number nullable | ASR confidence when applicable |
|
||||
| `is_final` | boolean | Finalized transcript only in V1, but keep the flag for future partials |
|
||||
| `barge_in_interrupted` | boolean | Whether the assistant segment was interrupted |
|
||||
| `payload_json` | text | Provider metadata, timestamps, tool refs |
|
||||
| `created_at` | string index | Write time |
|
||||
|
||||
V1 should store finalized transcript segments only. Partial ASR events can stay in memory inside the runtime.
|
||||
|
||||
## 7.4 Additive columns on `asterisk_call_links`
|
||||
|
||||
This mirrors the Telegram pattern where the channel source-of-truth row also exposes AI status.
|
||||
|
||||
Add:
|
||||
|
||||
- `voice_session_id VARCHAR(64) NULL`
|
||||
- `ai_session_id VARCHAR(64) NULL`
|
||||
- `ai_state VARCHAR(32) NULL`
|
||||
- `ai_handoff_reason TEXT NULL`
|
||||
- `ai_last_model_at VARCHAR(64) NULL`
|
||||
|
||||
Suggested `ai_state` values:
|
||||
|
||||
- `queued`
|
||||
- `greeting`
|
||||
- `listening`
|
||||
- `thinking`
|
||||
- `active`
|
||||
- `handoff_required`
|
||||
- `human_owned`
|
||||
- `closed`
|
||||
- `error`
|
||||
|
||||
This lets `/asterisk/live-calls` and `/asterisk/recent-calls` drive UI chips directly without extra joins on every poll.
|
||||
|
||||
## 7.5 Additive column on `ai_sessions`
|
||||
|
||||
Add:
|
||||
|
||||
- `call_id VARCHAR(128) NULL`
|
||||
|
||||
Reason:
|
||||
|
||||
- Telegram already uses `thread_id`;
|
||||
- voice needs a direct telephony key for fast lookup and summary generation;
|
||||
- this keeps `ai_sessions` truly cross-channel instead of Telegram-shaped.
|
||||
|
||||
## 7.6 Reuse of `ai_turns`
|
||||
|
||||
Reuse `ai_turns` for:
|
||||
|
||||
- finalized user turn seen by the orchestrator;
|
||||
- model reply plan;
|
||||
- tool invocation results;
|
||||
- summary turn written during handoff or closure.
|
||||
|
||||
Do not reuse `ai_jobs` for voice V1. Telegram jobs are thread-triggered and synchronous voice turn processing does not need that queue model.
|
||||
|
||||
## 8. API Design
|
||||
|
||||
## 8.1 `asterisk_bridge_service` -> `ai_voice_runtime_service`
|
||||
|
||||
New internal control-plane endpoints:
|
||||
|
||||
### `POST /internal/voice-ai/sessions`
|
||||
|
||||
Starts a runtime session for an already accepted telephony call.
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
{
|
||||
"call_id": "1740912000.12",
|
||||
"linked_id": "1740912000.12",
|
||||
"interaction_id": "int_...",
|
||||
"queue_id": "que_...",
|
||||
"caller_number": "+7701...",
|
||||
"caller_name": "Lab Caller",
|
||||
"agent_profile": "voice_support",
|
||||
"language_hint": "ru",
|
||||
"handoff_queue_id": "que_...",
|
||||
"metadata": {
|
||||
"queue_code": "voice_lab",
|
||||
"direction": "inbound"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"voice_session_id": "avs_...",
|
||||
"ai_session_id": "ais_...",
|
||||
"status": "queued"
|
||||
}
|
||||
```
|
||||
|
||||
### `POST /internal/voice-ai/sessions/{session_id}/telephony-events`
|
||||
|
||||
Bridge notifies runtime about:
|
||||
|
||||
- `call.connected`
|
||||
- `call.ended`
|
||||
- `recording.ready`
|
||||
- `operator.connected`
|
||||
|
||||
This keeps telephony truth in the bridge while runtime stays current.
|
||||
|
||||
## 8.2 `ai_voice_runtime_service` -> `ai_orchestrator_service`
|
||||
|
||||
### `POST /ai/voice/sessions/{session_id}/start`
|
||||
|
||||
Creates or reopens the generic AI session and returns greeting policy:
|
||||
|
||||
```json
|
||||
{
|
||||
"voice_session_id": "avs_...",
|
||||
"call_id": "1740912000.12",
|
||||
"interaction_id": "int_...",
|
||||
"customer_id": "cus_...",
|
||||
"language_hint": "ru",
|
||||
"agent_profile": "voice_support"
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "ais_...",
|
||||
"language": "ru",
|
||||
"greeting_text": "Здравствуйте. Я AI-оператор компании...",
|
||||
"disclosure_required": true
|
||||
}
|
||||
```
|
||||
|
||||
### `POST /ai/voice/sessions/{session_id}/turns`
|
||||
|
||||
Main deterministic decision endpoint.
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
{
|
||||
"voice_session_id": "avs_...",
|
||||
"call_id": "1740912000.12",
|
||||
"interaction_id": "int_...",
|
||||
"transcript_text": "Хочу узнать статус заявки",
|
||||
"language": "ru",
|
||||
"sequence_no": 3,
|
||||
"barge_in": false,
|
||||
"metadata": {
|
||||
"turn_duration_ms": 4200
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"language": "ru",
|
||||
"intent": "status_check",
|
||||
"reply_text": "Я AI-оператор компании. Проверяю данные по обращению...",
|
||||
"confidence": 0.83,
|
||||
"needs_handoff": false,
|
||||
"handoff_reason": null,
|
||||
"case_action": "keep_open",
|
||||
"kb_refs": ["art_..."],
|
||||
"summary_text": "Клиент уточняет статус заявки.",
|
||||
"model": "gpt-4o-mini",
|
||||
"latency_ms": 780
|
||||
}
|
||||
```
|
||||
|
||||
`case_action` should stay aligned with the existing Telegram pattern:
|
||||
|
||||
- `none`
|
||||
- `keep_open`
|
||||
- `close`
|
||||
- `escalate`
|
||||
|
||||
### `POST /ai/voice/sessions/{session_id}/close`
|
||||
|
||||
Finalizes AI state and writes the terminal summary.
|
||||
|
||||
## 8.3 `ai_voice_runtime_service` -> `asterisk_bridge_service`
|
||||
|
||||
### `POST /internal/voice-ai/calls/{call_id}/handoff`
|
||||
|
||||
Requests redirect of the current customer leg into the existing human queue.
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
{
|
||||
"voice_session_id": "avs_...",
|
||||
"ai_session_id": "ais_...",
|
||||
"interaction_id": "int_...",
|
||||
"target_queue_id": "que_...",
|
||||
"reason": "Нужен человек для чувствительного запроса.",
|
||||
"summary": {
|
||||
"customer_request_text": "Клиент просит изменить договор",
|
||||
"ai_outcome_text": "AI собрал контекст и не выполнял чувствительное действие",
|
||||
"recommended_next_step": "Проверить договор и продолжить вручную"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Behavior:
|
||||
|
||||
- bridge validates the call is still active;
|
||||
- bridge updates `ai_state` to `handoff_required`;
|
||||
- bridge appends timeline `ai.handoff_requested`;
|
||||
- bridge redirects the customer leg into the configured human queue;
|
||||
- when the normal operator-connected flow happens, the same call row becomes `human_owned`.
|
||||
|
||||
This endpoint is internal-only and trusted for service actors such as `svc:ai-voice-runtime`.
|
||||
|
||||
## 8.4 `interaction_service`
|
||||
|
||||
Add one internal endpoint:
|
||||
|
||||
### `POST /interactions/{interaction_id}/timeline`
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "ai.reply_generated",
|
||||
"metadata": {
|
||||
"call_id": "1740912000.12",
|
||||
"voice_session_id": "avs_...",
|
||||
"ai_session_id": "ais_..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Why add this now:
|
||||
|
||||
- Voice AI should not write cross-service timeline rows by reaching into another service's DB contract ad hoc;
|
||||
- the same endpoint can later be reused by Telegram AI without changing its current behavior immediately;
|
||||
- it makes the AI event contract explicit.
|
||||
|
||||
Required Voice AI timeline actions:
|
||||
|
||||
- `ai.session_started`
|
||||
- `ai.reply_generated`
|
||||
- `ai.handoff_requested`
|
||||
- `ai.handoff_completed`
|
||||
- `ai.error`
|
||||
|
||||
Existing interaction endpoints remain reused as-is:
|
||||
|
||||
- `PATCH /interactions/{id}/status`
|
||||
- `POST /interactions/{id}/escalate`
|
||||
- `PATCH /interactions/{id}/assign`
|
||||
|
||||
## 8.5 Operator-facing read APIs
|
||||
|
||||
Extend `asterisk_bridge_service` output:
|
||||
|
||||
### `GET /asterisk/live-calls`
|
||||
|
||||
Add to each row:
|
||||
|
||||
- `voice_session_id`
|
||||
- `ai_session_id`
|
||||
- `ai_state`
|
||||
- `ai_handoff_reason`
|
||||
- `ai_last_model_at`
|
||||
|
||||
### `GET /asterisk/recent-calls`
|
||||
|
||||
Expose the same additive AI fields.
|
||||
|
||||
### `GET /asterisk/live-calls/{call_id}/ai-summary`
|
||||
|
||||
Return a summary shape intentionally aligned with Telegram:
|
||||
|
||||
```json
|
||||
{
|
||||
"call_id": "1740912000.12",
|
||||
"session_id": "ais_...",
|
||||
"voice_session_id": "avs_...",
|
||||
"status_label": "AI передал звонок оператору",
|
||||
"status_tone": "handoff",
|
||||
"customer_request_text": "Клиент хочет узнать статус обращения и изменить способ оплаты",
|
||||
"ai_outcome_text": "AI собрал контекст и объяснил рамки, но не выполнил чувствительное действие",
|
||||
"handoff_reason": "Запрос требует человека и проверки вручную",
|
||||
"recommended_next_step": "Проверить карточку обращения и продолжить звонок вручную",
|
||||
"generated_at": "2026-03-09T12:34:56Z"
|
||||
}
|
||||
```
|
||||
|
||||
This should be produced from `voice_ai_sessions`, `ai_turns`, and the latest transcript segments.
|
||||
|
||||
## 9. Context Filtering Rules
|
||||
|
||||
Voice AI must not send the whole call history into the model each turn.
|
||||
|
||||
For every voice turn, `ai_orchestrator_service` should assemble a filtered context from:
|
||||
|
||||
- customer profile:
|
||||
- `customer_id`
|
||||
- display name
|
||||
- preferred phone
|
||||
- tags
|
||||
- customer memory:
|
||||
- recent resolved issues
|
||||
- notable preferences
|
||||
- interaction state:
|
||||
- `interaction_id`
|
||||
- status
|
||||
- queue
|
||||
- last relevant timeline events
|
||||
- voice session state:
|
||||
- language
|
||||
- disclosure already played or not
|
||||
- previous handoff flag
|
||||
- current turn number
|
||||
- recent transcript window:
|
||||
- last `6-10` finalized segments, not the entire transcript
|
||||
- KB:
|
||||
- top `3` matched articles or fewer
|
||||
- business policy:
|
||||
- allowed actions
|
||||
- mandatory disclosure
|
||||
- sensitive-topic escalation rules
|
||||
|
||||
This matches the Telegram AI design principle already present in the repo.
|
||||
|
||||
## 10. Runtime Behavior
|
||||
|
||||
## 10.1 ASR/TTS abstraction
|
||||
|
||||
`ai_voice_runtime_service` should expose provider interfaces and start with one configured provider per environment.
|
||||
|
||||
Recommended internal modules:
|
||||
|
||||
- `providers/asr.py`
|
||||
- `providers/tts.py`
|
||||
- `session_manager.py`
|
||||
- `media_bridge.py`
|
||||
- `barge_in.py`
|
||||
|
||||
V1 supports one active ASR provider and one active TTS provider, behind interfaces. Multi-provider fallback is not required in the first version.
|
||||
|
||||
## 10.2 Barge-in
|
||||
|
||||
Barge-in is a V1 requirement because voice UX breaks if the caller cannot interrupt TTS.
|
||||
|
||||
Required behavior:
|
||||
|
||||
- while TTS is playing, incoming speech activity stops or fades out current playback;
|
||||
- interrupted assistant output is marked with `barge_in_interrupted=true` in transcript;
|
||||
- only finalized caller speech creates a new orchestrator turn;
|
||||
- if interruption happens repeatedly or ASR confidence is poor, handoff rules may trigger.
|
||||
|
||||
## 10.3 Latency budget
|
||||
|
||||
Target budget for one AI turn:
|
||||
|
||||
- end-of-utterance to finalized ASR text: `<= 600 ms`
|
||||
- orchestrator decision: `<= 900 ms`
|
||||
- first TTS audio chunk: `<= 500 ms`
|
||||
- total pause before AI speech starts: `<= 2.0 s`
|
||||
|
||||
If the runtime cannot stay within the budget repeatedly, it should prefer human handoff over a degraded long-silence experience.
|
||||
|
||||
## 11. Operator UI Integration
|
||||
|
||||
Voice AI V1 should integrate into the current operator shell, not create a second voice console.
|
||||
|
||||
### 11.1 Existing surfaces to extend
|
||||
|
||||
- browser call popup overlay;
|
||||
- `Voice debug` live/recent calls list;
|
||||
- unified customer history on `/operator`.
|
||||
|
||||
### 11.2 Required UI changes
|
||||
|
||||
1. Extend live call rows with an AI chip using the same language as Telegram:
|
||||
- `AI active`
|
||||
- `Ждёт человека`
|
||||
- `AI error`
|
||||
2. When a transferred AI-owned call reaches the operator popup:
|
||||
- load `GET /asterisk/live-calls/{call_id}/ai-summary`;
|
||||
- show a compact `Сводка AI` card above call actions;
|
||||
- keep existing `Принять в работу`, `Передать`, `Завершить` controls unchanged.
|
||||
3. Add AI metadata to customer history:
|
||||
- AI session started
|
||||
- AI handoff requested
|
||||
- AI handoff completed
|
||||
4. Do not add a separate full transcript workspace in V1.
|
||||
|
||||
### 11.3 UI behavior intentionally deferred
|
||||
|
||||
Deferred to V2:
|
||||
|
||||
- operator button `Вернуть AI` for live voice calls;
|
||||
- inline live transcript for operators during the call;
|
||||
- supervisor transcript explorer for full recordings plus transcripts.
|
||||
|
||||
## 12. Deployment and Config
|
||||
|
||||
## 12.1 New service in `docker-compose.server.yml`
|
||||
|
||||
Add:
|
||||
|
||||
- `ai-voice-runtime-service`
|
||||
|
||||
New shared env:
|
||||
|
||||
- `AI_VOICE_RUNTIME_SERVICE_URL`
|
||||
- `AI_VOICE_ENABLED`
|
||||
- `AI_VOICE_QUEUE_CONFIG_JSON`
|
||||
- `AI_VOICE_ASR_PROVIDER`
|
||||
- `AI_VOICE_TTS_PROVIDER`
|
||||
- `AI_VOICE_TTS_CACHE_ENABLED`
|
||||
- `AI_VOICE_TTS_CACHE_DIR`
|
||||
- `AI_VOICE_MAX_CONTEXT_SEGMENTS`
|
||||
- `AI_VOICE_HANDOFF_TIMEOUT_SECONDS`
|
||||
|
||||
Service auth:
|
||||
|
||||
- add trusted subject `svc:ai-voice-runtime` where internal bridge endpoints require it.
|
||||
|
||||
## 12.2 Asterisk-side change
|
||||
|
||||
The accepted human voice path stays intact.
|
||||
|
||||
Voice AI adds one new AI media bridge path in dialplan only for AI-selected queues. Preferred V1 implementation is:
|
||||
|
||||
- Asterisk dialplan redirects the customer leg into an external media bridge context dedicated to AI.
|
||||
|
||||
Exact low-level primitive should be validated in the lab:
|
||||
|
||||
- preferred: `AudioSocket` or equivalent bidirectional audio bridge;
|
||||
- fallback: narrowly scoped external-media/ARI only for AI queues.
|
||||
|
||||
The choice must keep Asterisk as the telephony owner.
|
||||
|
||||
## 13. Rollout Order
|
||||
|
||||
Recommended implementation order:
|
||||
|
||||
1. schema changes only:
|
||||
- `voice_ai_sessions`
|
||||
- `voice_transcript_segments`
|
||||
- additive AI columns on `asterisk_call_links`
|
||||
- additive `call_id` on `ai_sessions`
|
||||
2. control plane only:
|
||||
- bridge starts and closes empty runtime sessions behind `AI_VOICE_ENABLED=0`
|
||||
3. lab media path:
|
||||
- one AI-enabled lab queue
|
||||
- disclosure + greeting + ASR/TTS echo flow
|
||||
4. orchestrator integration:
|
||||
- KB lookup
|
||||
- filtered context
|
||||
- handoff decisioning
|
||||
5. operator summary:
|
||||
- popup card
|
||||
- voice debug AI chips
|
||||
6. staged pilot rollout per queue.
|
||||
|
||||
## 14. Key Risks and Open Questions
|
||||
|
||||
1. `call_id` stability during AI-to-human redirect must be validated in the Asterisk lab.
|
||||
If redirect creates a new call id, bridge recovery must switch to `linked_id` first and only then reuse `interaction_id`.
|
||||
2. The exact Asterisk media primitive must be confirmed before implementation.
|
||||
The design assumes a bidirectional bridge is available without replacing the accepted telephony baseline.
|
||||
3. Provider latency must be measured in the target environment before enabling AI-first for production queues.
|
||||
4. Sensitive actions should stay read-only in V1.
|
||||
Voice AI should use KB, customer lookup, and safe interaction updates, but not execute risky external business actions directly.
|
||||
|
||||
## 15. Summary
|
||||
|
||||
Voice AI V1 should be implemented as an additive AI layer over the accepted voice baseline:
|
||||
|
||||
- `asterisk_bridge_service` remains telephony truth and handoff executor;
|
||||
- `ai_voice_runtime_service` is the new realtime media layer;
|
||||
- `ai_orchestrator_service` is reused for deterministic business decisioning;
|
||||
- `interaction_service` remains the owner of the canonical AI timeline;
|
||||
- operator UI gets AI badges and AI handoff summary, not a new telephony product.
|
||||
|
||||
This keeps the current live voice contour intact while adding the same AI-first and human-handoff architecture that already works in Telegram.
|
||||
@@ -0,0 +1,39 @@
|
||||
# Архитектура Голосового ИИ (Voice AI V1)
|
||||
|
||||
Этот документ описывает техническое устройство и жизненный цикл обработки входящих голосовых звонков с помощью ИИ-оператора. ИИ интегрирован поверх базовой телефонии Asterisk и не заменяет ее полностью, выступая как первая линия поддержки (L1).
|
||||
|
||||
## 1. Ключевые принципы
|
||||
|
||||
1. **AI-first**: Звонок сначала направляется ИИ, если очередь настроена соответствующим образом.
|
||||
2. **Перехват звонка (Handoff)**: Исходный звонок детерминированно переключается на человека-оператора, если ИИ не может решить проблему или если клиент явно просит человека.
|
||||
3. **Единый источник истины**: Asterisk остается мастер-системой для SIP-потока, записи звонков и маршрутизации.
|
||||
4. **Безопасность (Read-Only)**: В версии V1 ИИ работает исключительно на чтение и выдачу справочной информации. Апдейт критичных финансовых данных в базе строго запрещен для ИИ и требует переключения на человека.
|
||||
|
||||
## 2. Архитектура обработки звонка
|
||||
|
||||
Процесс обработки звонка ИИ разделен на два основных микросервиса:
|
||||
|
||||
- **`ai_voice_runtime_service` (Аудио-прослойка):** Осуществляет прием аудио-потока. Работает в Real-time. Слушает аудио (через ASR модель) и отправляет синтезированную речь обратно клиенту (через TTS модель). Также отлавливает попытки клиента перебить робота (Barge-in), мгновенно прерывая воспроизведение.
|
||||
- **`ai_orchestrator_service` (Мозг ИИ):** Не работает со звуком. Получает только готовый текстовый транскрипт (реплику). Принимает решение:
|
||||
1. Найти знания в `kb-service` (RAG).
|
||||
2. Сгенерировать текстовый ответ для клиента (который будет озвучен через TTS).
|
||||
3. Если запрос слишком сложный — отправить команду "Переключить на живого человека".
|
||||
|
||||
## 3. Флоу входящего звонка (Step-by-step)
|
||||
|
||||
1. Абонент звонит на платформу.
|
||||
2. **`asterisk-bridge-service`** улавливает событие и решает направить звонок в `voice_lab`.
|
||||
3. Поднимаются две параллельные сессии: `voice_ai_session` (отслеживание аудио) и `ai_session` (бизнес-логика).
|
||||
4. Абонент подключается к медиа-интерфейсу `ai_voice_runtime_service`. Проигрывается дисклеймер: "Здравствуйте, я голосовой помощник...".
|
||||
5. Абонент озвучивает свой вопрос.
|
||||
6. ASR распознает вопрос и отправляет текст в **`ai_orchestrator_service`**.
|
||||
7. Оркестратор смотрит историю клиента, ищет ответ в базе знаний, генерирует ответ и возвращает команду в runtime "Скажи это".
|
||||
8. Если оркестратор понимает, что нужна помощь:
|
||||
- Отправляет сигнал `needs_handoff` с детальной "Сводкой ИИ" для оператора.
|
||||
- Звонок перехватывается, и абонент слушает музыку ожидания в очереди к живым людям.
|
||||
|
||||
## 4. Интеграция с Operator UI
|
||||
|
||||
Когда звонок поступает к оператору после ИИ, оператор не видит отдельный ИИ-интерфейс. Он видит стандартную карточку звонка. Но внутри этой карточки динамически выводится:
|
||||
- **AI Summary (Сводка):** Что клиент сказал, что ИИ попытался сделать и почему перевел звонок на человека.
|
||||
- **Таймлайн:** В историю обращений клиента логгируется, что "ИИ запрашивал перевод на специалиста".
|
||||
Reference in New Issue
Block a user