Новая настройка instance_settings.contact_email (включён/адрес, с валидацией формата) — подставляется в заголовок Reply-To писем подтверждения регистрации, приглашений и саммари. Админ-эндпоинт POST /admin/settings/test-email отправляет проверочное письмо синхронно и возвращает внятный результат (успех либо текст ошибки транспорта), не раскрывая логин/пароль SMTP.
Workers — Celery Tasks
Асинхронная пост-обработка сеансов конференций: транскрибация аудиотреков, суммаризация, email-уведомления и рассылка .ics-приглашений. Работает через Celery + Redis, использует общее окружение backend (модели, репозитории, плагины).
Структура
workers/
├── celery_app.py Конфигурация Celery: broker, beat schedule, task_routes
├── db.py Утилиты для работы с БД в воркерах (async-сессии)
├── livekit_client.py Обёртка для LiveKit API (получение записанных треков)
├── tasks/
│ ├── __init__.py
│ ├── dispatch.py send_task_with_retry — безопасная постановка следующей
│ │ задачи пайплайна при временной недоступности брокера
│ ├── maintenance.py Периодические задачи (beat): очистка зависших сеансов/
│ │ конференций, восстановление зависших саммари/уведомлений
│ ├── pipeline.py run_pipeline — диспетчер: транскрибация треков + реконструкция фраз
│ ├── summarize.py summarize_session — суммаризация (map-reduce через LLM)
│ ├── notify.py notify_session — email с резюме сеанса
│ └── invitations.py send_invitations — рассылка .ics-приглашений
├── transcription/
│ ├── __init__.py
│ ├── phrases.py Реконструкция фраз из сегментов треков (чистая функция)
│ └── README.md Документация модуля
├── summarizer/
│ ├── __init__.py
│ ├── transcript.py Сборка транскрипта из фраз (чистая функция)
│ ├── prompts/
│ │ ├── summary_map_ru.txt Промпт map-шага map-reduce суммаризации
│ │ └── summary_reduce_ru.txt Промпт reduce-шага
│ └── eval/ Eval-корпус для сравнения качества по уровням AI
│ ├── run_tiers.py
│ ├── corpus/
│ └── results/
└── README.md Этот файл
Архитектура пайплайна
Celery + Redis для асинхронной пост-обработки сеансов.
Состав Celery-задач
| Задача | Входные данные | Выходные данные |
|---|---|---|
run_pipeline(session_id) |
session_id | session_audio_tracks.segments, phrases |
summarize_session(session_id) |
session_id | conference_sessions.summary_data |
notify_session(session_id) |
session_id | email с резюме отправлен, запись в email_deliveries |
send_invitations(conference_id, emails) |
conference_id, список email | .ics-приглашение отправлено |
State machine pipeline_status
recording
↓ (webhook room_finished → enqueue run_pipeline)
transcribing
↓ (транскрибация + реконструкция фраз успешны)
summarizing
↓ (summarize_session записывает summary_data; статус остаётся summarizing)
↓ (notify_session отправляет email)
notified
↓
SUCCESS: история сохранена в БД
или при ошибке на любом шаге:
failed
↓ Лог ошибки, повторный запуск вручную
Идемпотентность: каждый шаг пайплайна проверяет текущий pipeline_status:
- Если уже прошёл этот шаг → skip (guard от дублирования)
- Если failed → воркер логирует и пропускает
- Если recording/transcribing → может переходить по ступеням
Защита от потери постановки следующей задачи
Каждый шаг пайплайна ставит следующую задачу в очередь через
workers.tasks.dispatch.send_task_with_retry (несколько попыток при
временной недоступности Redis-брокера). Если все попытки исчерпаны, шаг не
откатывается и не проваливается — восстановление берёт на себя beat-задача
workers.tasks.maintenance:
recover_stuck_summaries— переставляетsummarize_sessionдля сеансов, зависших вpipeline_status='summarizing'безsummary_dataдольше порога.recover_stuck_notifications— переставляетnotify_sessionдля сеансов с готовымsummary_data, но без отправленного уведомления.
Оба идемпотентны: повторная постановка безопасна благодаря собственным guard'ам задач.
Транскрибация (workers/tasks/pipeline.py)
Задача run_pipeline(session_id) — диспетчер пайплайна пост-обработки сеанса.
- Проверка статуса сеанса (
pipeline_status) иconfig/plugins.yaml(enabled/disabled) - Ожидание завершения egress: retry-цикл с backoff (egress завершается позже room_finished)
- Переход в
pipeline_status='transcribing' - Транскрибация каждого трека (per-track идемпотентность):
- Загрузка плагина (
FasterWhisperCPU/FasterWhisperGPUили другого из конфига) transcriber.transcribe(audio_path, language='ru')- Сохранение сегментов в
session_audio_tracks.segments(JSONB) - Коммит после каждого трека (точка возобновления)
- Загрузка плагина (
- Реконструкция фраз (если хотя бы один трек успешен):
- Сборка сегментов по участникам (с учётом смещений треков)
build_phrases(segments_by_participant, track_offsets)— чистая функция- DELETE phrases WHERE session_id + INSERT новые фразы в одной транзакции
- Переход в
pipeline_status='summarizing'
- Обработка ошибок:
- Если все треки failed →
pipeline_status='failed' - Зависшие треки (исчерпаны retry) → помечаются
failed, продолжаем с остальными
- Если все треки failed →
Плагин FasterWhisperCPU/GPU (backend/core/plugins/faster_whisper.py):
- Встроенный Silero VAD (подавление молчания)
- Отбрасывание сегментов < 0.3 сек (галлюцинации Whisper)
- Ленивый импорт (не грузит зависимость в API-процесс)
- Синглтон модели на процесс воркера
Реконструкция фраз (workers/transcription/phrases.py, чистая функция):
- Слияние сегментов всех участников в таймлайн
- Группировка подряд идущих сегментов одного спикера → фраза
- Короткие вставки другого спикера (<1.5 сек) не рвут фразу
- Перекрытия речи → обе фразы
- Тишина не рвёт фразу
Типы данных:
segments_by_participant: dict[uuid.UUID, list[Segment]] # Выход faster-whisper
track_offsets: dict[uuid.UUID, float] # Смещения по времени
→ list[PhraseDraft] # Фразы (без БД)
Детали: workers/transcription/README.md.
Суммаризация (workers/tasks/summarize.py)
Задача summarize_session(session_id) — собрать транскрипт сеанса, разбить на чанки, создать резюме:
- Guard'ы: сеанс найден, не завершён, статус
summarizing, фраз достаточно - Собрать транскрипт: фразы →
build_transcript()→ строки[Имя MM:SS] текст - Инстанциирование плагина
Summarizer(из эффективной конфигурации уровня AI,backend/services/instance_settings.py::load_effective_config) - Вызвать
summarizer.summarize(transcript):chunk_transcript()— разбить на чанки (20 минут, max_chunk_tokens зависит от уровня)- Map: LLM по каждому чанку (
summary_map_ru.txt, max_tokens_map per-tier) - Reduce: объединить резюме (иерархически при переполнении,
summary_reduce_ru.txt, max_tokens_reduce per-tier)
session.summary_data = результат, commit- Статус ОСТАЁТСЯ
summarizing— готовность определяется парой:pipeline_status='summarizing'Иsummary_data IS NOT NULL; переход вnotified— задачаnotify_session
Уровни AI (см. ADR-004 docs/architecture/adr/004-ai-tier-matrix.md):
- min: Qwen3.5-4B, max_tokens_map=1024, max_tokens_reduce=1536, CPU llama.cpp
- medium: Qwen3.5-9B, max_tokens_map=1024, max_tokens_reduce=2048, CPU/GPU опционально
- max: Qwen3.5-35B-A3B (MoE), max_tokens_map=1536, max_tokens_reduce=2560, GPU llama.cpp обязателен
Надёжность:
- Retry + circuit breaker в HTTP-клиенте (3 попытки, экспоненциальный backoff, breaker после 5 ошибок)
- Celery-retry задачи (bind + acks_late)
- Идемпотентные guard'ы:
summary_data IS NOT NULL→ no-op
Конфигурация:
- config/plugins.yaml — дефолт
provider: "null", для пресета 3+ ="qwen_local" - backend/services/ai_tiers.py — матрица уровней min/medium/max
Установка модели: docs/deploy/llm-setup.md
Документация: docs/plugins/summarizer.md
Качество: docs/deploy/quality-tiers.md (параметры per-tier, eval-корпус)
Уведомления (workers/tasks/notify.py) и приглашения (workers/tasks/invitations.py)
notify_session(session_id) собирает получателей (владелец, участники по
summary_recipients), формирует письмо (backend/services/email_templates.py)
и отправляет через backend/services/email.py (backend console/smtp),
записывая доставку в email_deliveries (идемпотентность: уже отправленным
получателям письмо повторно не шлётся).
send_invitations(conference_id, emails) формирует .ics-приглашение
(backend/services/ics.py) и рассылает его приглашённым и организатору.
Промпты суммаризации
Русскоязычные промпты для Qwen3.5, единые для всех уровней AI:
summarizer/prompts/summary_map_ru.txt— map-шаг (суммаризация чанка)summarizer/prompts/summary_reduce_ru.txt— reduce-шаг (объединение сумм)
Качество наращивается размером модели (4B/9B/35B-A3B), а не правкой промптов. Per-tier параметры генерации (max_tokens, temperature) задаются в backend/services/ai_tiers.py (ADR-004).
Инфраструктура Celery
celery_app.py
from celery import Celery
from core.config import get_settings
settings = get_settings()
app = Celery("vidconf", broker=settings.redis_url, backend=None)
app.conf.task_routes = {
"workers.tasks.pipeline.run_pipeline": {"queue": "transcription"},
"workers.tasks.summarize.*": {"queue": "summarize"},
"workers.tasks.notify.*": {"queue": "notify"},
"workers.tasks.invitations.*": {"queue": "notify"},
}
app.conf.beat_schedule = {
"cleanup-conferences": {
"task": "workers.tasks.maintenance.cleanup_conferences",
"schedule": 60.0,
},
"recover-stuck-summaries": {
"task": "workers.tasks.maintenance.recover_stuck_summaries",
"schedule": 300.0,
},
"recover-stuck-notifications": {
"task": "workers.tasks.maintenance.recover_stuck_notifications",
"schedule": 300.0,
},
}
tasks/maintenance.py
cleanup_conferences:
Закрытие зависших сеансов и завершение просроченных плановых конференций:
- Открытые сеансы без активных участников дольше
IDLE_THRESHOLD(10 мин) — закрываются напрямую, статус родительской конференции —scheduled(закреплённая) илиended(незакреплённая) - Незакреплённые плановые конференции, чьё плановое окно истекло без единого сеанса — переводятся в
ended
recover_stuck_summaries / recover_stuck_notifications — уровень 2 защиты от потери постановки задачи при недоступности Redis-брокера (см. «Защита от потери постановки следующей задачи» выше).
Идемпотентность: каждый шаг проверяет текущее состояние перед действием, поэтому повторные запуски безопасны.
Запуск
Docker Compose:
docker compose -f deploy/docker-compose.yml up -d # Включает сервисы worker/worker-transcriber
docker compose -f deploy/docker-compose.yml logs -f worker
Локально:
cd backend
PYTHONPATH=/path/to/backend uv run celery -A workers.celery_app worker -B --loglevel=info
Раздельные очереди Celery
Воркеры слушают выделенные очереди для изоляции нагрузки (см.
app.conf.task_routes выше):
| Очередь | Задачи | Назначение |
|---|---|---|
transcription |
run_pipeline |
Интенсивная обработка аудио (CPU/GPU) |
summarize |
summarize_session |
Инференс LLM |
notify |
notify_session, send_invitations |
Email, низкий приоритет |
celery (default) |
остальные задачи (beat, maintenance) | Периодические + сервисные |
Подробнее о раздельных воркер-контейнерах и профилях compose — docs/deploy/scaling.md.
Метрики
Endpoint: GET /metrics (Prometheus, реализован в backend/api/metrics.py)
vidconf_http_request_duration_seconds— гистограмма латентности HTTP-запросов backend (по маршруту/методу/статусу)vidconf_pipeline_sessions— gauge числа сеансов в каждом статусеpipeline_status, пересчитывается при каждом scrapevidconf_celery_queue_depth— gauge длины очередей Celery в Redis (transcription/summarize/notify/celery),LLENпри каждом scrape
Визуализация: Grafana (compose-профиль monitoring, см. docs/deploy/monitoring.md)
Eval-корпус для качества
Директория: workers/summarizer/eval/
Назначение: верификация качества суммаризации на разных уровнях AI (min/medium/max).
Структура:
run_tiers.py— скрипт для запуска eval на всех уровняхcorpus/— тестовые транскриптыresults/— результаты прогонов по уровням
Использование:
cd workers
python summarizer/eval/run_tiers.py --level min # eval уровня min (Qwen3.5-4B)
python summarizer/eval/run_tiers.py --level medium
python summarizer/eval/run_tiers.py --level max
Методика и результаты прогонов — docs/deploy/quality-tiers.md.
Конфигурация
.env переменные
Полный справочник — docs/deploy/env.md. Ключевые для воркеров:
REDIS_URL=redis://redis:6379/0
DATABASE_URL=postgresql+asyncpg://vidconf:vidconf@postgres:5432/vidconf
PLUGINS_CONFIG_PATH=config/plugins.yaml
# Email
EMAIL_BACKEND=console # или smtp
SMTP_HOST=localhost
SMTP_PORT=587
# LiveKit (получение записанных аудиотреков)
LIVEKIT_API_KEY=devkey
LIVEKIT_API_SECRET=change-me-livekit-secret
LIVEKIT_PUBLIC_URL=ws://localhost:7880
# Обнаруженное железо (заполняет install.sh, используется для детекта уровней AI)
HW_CPUS=
HW_RAM_MB=
HW_GPU_NAME=
HW_VRAM_MB=
Docker Compose
# Запустить весь стек с воркерами
docker compose -f deploy/docker-compose.yml up -d
# Только воркер (при условии postgres/redis уже запущены)
docker compose -f deploy/docker-compose.yml up -d worker
# Логи
docker compose -f deploy/docker-compose.yml logs -f worker
# Статус
docker compose -f deploy/docker-compose.yml ps
Тестирование
cd backend # Workers используют окружение backend
uv run pytest tests/ -v
# С режимом Celery eager (синхронно, без реальной очереди)
CELERY_ALWAYS_EAGER=True uv run pytest tests/ -v
Ссылки
Backend & плагины
- Backend README:
backend/README.md— FastAPI, контракты плагинов - Плагины:
- Контракты:
docs/plugins/contracts.md - Транскрибатор:
docs/plugins/transcriber.md - Суммаризатор:
docs/plugins/summarizer.md
- Контракты:
- Уровни AI:
- Матрица уровней (min/medium/max):
backend/services/ai_tiers.py - ADR-004 (архитектурное решение):
docs/architecture/adr/004-ai-tier-matrix.md
- Матрица уровней (min/medium/max):
Модули воркеров
- Пайплайн:
workers/tasks/pipeline.py - Реконструкция фраз:
workers/transcription/phrases.py+workers/transcription/README.md - Суммаризация:
workers/tasks/summarize.py - Уведомления и рассылка:
workers/tasks/notify.py,workers/tasks/invitations.py
Развёртывание & мониторинг
- Инсталлятор:
docs/deploy/install.md(5 пресетов, автодетект) - Профили оборудования:
docs/deploy/hardware-profiles.md(требования ресурсов) - Качество (eval-корпус):
docs/deploy/quality-tiers.md - Мониторинг:
docs/deploy/monitoring.md(Prometheus + Grafana) - Масштабирование:
docs/deploy/scaling.md
Модели и миграции
- Схема БД:
docs/db/schema.md - Модели:
backend/models/audio_track.py,backend/models/phrase.py,backend/models/session.py
Архитектурные решения
- ADR-001:
docs/architecture/adr/001-dynamic-conferences-pivot.md(динамические конференции) - ADR-002:
docs/architecture/adr/002-phrase-attribution-session-participant.md(атрибуция фраз к участнику сеанса) - ADR-004:
docs/architecture/adr/004-ai-tier-matrix.md(матрица уровней AI) - Общая архитектура:
docs/architecture/README.md