662 lines
26 KiB
Markdown
662 lines
26 KiB
Markdown
# Audio Pipeline
|
||
|
||
Пайплайн обработки аудиозаписей звонков: от появления файла на диске до классификации, анализа по промптам и публикации итогового результата в очередь `final`.
|
||
|
||
Система построена на **файловом триггере** + **RabbitMQ** + **PostgreSQL** + внешних API (Nexara STT, Yandex LLM).
|
||
|
||
---
|
||
|
||
## Содержание
|
||
|
||
1. [Общая схема](#общая-схема)
|
||
2. [Структура проекта](#структура-проекта)
|
||
3. [Инфраструктура](#инфраструктура)
|
||
4. [Этапы обработки](#этапы-обработки)
|
||
5. [Файловое хранилище](#файловое-хранилище)
|
||
6. [RabbitMQ](#rabbitmq)
|
||
7. [PostgreSQL](#postgresql)
|
||
8. [Воркеры](#воркеры)
|
||
9. [Форматы сообщений](#форматы-сообщений)
|
||
10. [Промпты для анализа](#промпты-для-анализа)
|
||
11. [Агрегация и очередь final](#агрегация-и-очередь-final)
|
||
12. [Конфигурация (.env)](#конфигурация-env)
|
||
13. [Запуск и управление](#запуск-и-управление)
|
||
14. [Логирование](#логирование)
|
||
15. [Обработка ошибок](#обработка-ошибок)
|
||
16. [Переключение промптов на API](#переключение-промптов-на-api)
|
||
17. [Что не реализовано](#что-не-реализовано)
|
||
|
||
---
|
||
|
||
## Общая схема
|
||
|
||
```
|
||
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
|
||
│ incoming/ │────▶│ watcher │────▶│ RabbitMQ │
|
||
│ (файлы) │ │ (сканер) │ │ audio.new │
|
||
└─────────────┘ └──────────────┘ └──────┬──────┘
|
||
│
|
||
┌──────────────┐ ▼
|
||
│ processing/ │ ┌─────────────┐
|
||
│ (аудио) │◀────│ transcribe │── Nexara API (STT)
|
||
└──────────────┘ └──────┬──────┘
|
||
│
|
||
fanout transcription_done
|
||
┌───────────┴───────────┐
|
||
▼ ▼
|
||
┌─────────────┐ ┌─────────────┐
|
||
│ analyse │ │ tagging │
|
||
│ Yandex LLM │ │ Yandex LLM │
|
||
└──────┬──────┘ └──────┬──────┘
|
||
│ │
|
||
└───────────┬───────────┘
|
||
▼
|
||
┌─────────────┐
|
||
│ PostgreSQL │
|
||
│ results │
|
||
└──────┬──────┘
|
||
│
|
||
оба готовы ───────┤
|
||
▼
|
||
┌─────────────┐
|
||
│ final │ (RabbitMQ)
|
||
│ + удаление │
|
||
│ файла │
|
||
└─────────────┘
|
||
```
|
||
|
||
**Ключевые принципы:**
|
||
|
||
- Каждый этап — отдельный Docker-сервис (воркер).
|
||
- Связь между этапами — через RabbitMQ (асинхронно).
|
||
- Промежуточные и итоговые результаты LLM — в PostgreSQL.
|
||
- `analyse` и `tagging` работают **параллельно** после транскрипции.
|
||
- В `final` публикует тот воркер, который завершился **последним**.
|
||
|
||
---
|
||
|
||
## Структура проекта
|
||
|
||
```
|
||
audio-pipeline/
|
||
├── docker-compose.yml # инфраструктура и все сервисы
|
||
├── .env # конфигурация (не в git)
|
||
├── db/
|
||
│ └── init.sql # схема PostgreSQL
|
||
├── storage/ # аудиофайлы на хосте (монтируется в контейнеры)
|
||
│ ├── incoming/ # сюда кладут новые файлы
|
||
│ ├── processing/ # файлы в обработке
|
||
│ └── failed/ # файлы при критических ошибках watcher
|
||
├── watcher/ # сканер файловой системы
|
||
│ ├── cmd/watcher/main.go
|
||
│ └── internal/
|
||
│ ├── config/
|
||
│ ├── scanner/
|
||
│ └── publisher/
|
||
└── workers/
|
||
├── transcribe/ # STT + загрузка промптов + fanout
|
||
│ ├── cmd/transcribe/main.go
|
||
│ ├── configs/prompts.json
|
||
│ └── internal/
|
||
│ ├── consumer/
|
||
│ ├── nexara/
|
||
│ ├── prompts/
|
||
│ └── models/
|
||
├── analyse/ # анализ по промптам (Yandex LLM)
|
||
│ └── cmd/analyse/main.go
|
||
└── tagging/ # классификация диалога (Yandex LLM)
|
||
└── cmd/tagging/main.go
|
||
```
|
||
|
||
---
|
||
|
||
## Инфраструктура
|
||
|
||
| Сервис | Контейнер | Порты | Назначение |
|
||
|-------------|-------------|----------------|-------------------------------|
|
||
| `rabbit` | rabbit | 5672, 15672 | RabbitMQ + Management UI |
|
||
| `postgres` | postgres | 5432 | Хранение результатов |
|
||
| `watcher` | watcher | — | Мониторинг `incoming/` |
|
||
| `transcribe`| transcribe | — | Транскрипция + fanout |
|
||
| `analyse` | analyse | — | Анализ по промптам |
|
||
| `tagging` | tagging | — | Классификация диалога |
|
||
|
||
**RabbitMQ UI:** http://localhost:15672 (логин/пароль из `.env`)
|
||
|
||
---
|
||
|
||
## Этапы обработки
|
||
|
||
### 1. Watcher — обнаружение файла
|
||
|
||
**Триггер:** появление аудиофайла в `{STORAGE_ROOT}/incoming/`.
|
||
|
||
**Алгоритм:**
|
||
|
||
1. Каждые `POLL_INTERVAL` (по умолчанию 5 с) сканирует `incoming/`.
|
||
2. Пропускает скрытые файлы (`.`) и временные (`.tmp`).
|
||
3. Проверяет расширение: `.mp3`, `.wav`, `.m4a`, `.ogg`, `.flac`, `.webm`.
|
||
4. Ждёт стабилизации размера файла (`STABLE_WINDOW` / `STABLE_CHECKS`) — защита от незавершённой загрузки.
|
||
5. Генерирует ULID `task_id`.
|
||
6. Атомарно переименовывает: `incoming/name.wav` → `processing/{task_id}.wav`.
|
||
7. Публикует задачу в RabbitMQ (`audio_pipeline` / `audio.new`).
|
||
|
||
**При ошибке публикации:** файл возвращается в `incoming/`. Если откат невозможен — перемещается в `failed/`.
|
||
|
||
### 2. Transcribe — транскрипция
|
||
|
||
**Вход:** очередь `transcribe.tasks`.
|
||
|
||
**Алгоритм:**
|
||
|
||
1. Читает аудиофайл по `file_path` из сообщения.
|
||
2. Отправляет в **Nexara API** (Speech-to-Text).
|
||
3. Загружает промпты (`prompts.json` или HTTP API).
|
||
4. Формирует `TranscriptionResult` и публикует в fanout-exchange `transcription_done`.
|
||
5. Сообщение доставляется **одновременно** в очереди `analyse` и `tagging`.
|
||
|
||
### 3. Tagging — классификация диалога
|
||
|
||
**Вход:** очередь `tagging`.
|
||
|
||
**Алгоритм:**
|
||
|
||
1. Получает транскрипцию из сообщения.
|
||
2. Отправляет **один** запрос в **Yandex LLM** с промптом классификации (L1/L2/L3, risk_level и т.д.).
|
||
3. Сохраняет результат в `results.tagging` (PostgreSQL).
|
||
4. Если `results.analysis` уже заполнен — публикует в `final` и удаляет файл.
|
||
|
||
### 4. Analyse — анализ по промптам
|
||
|
||
**Вход:** очередь `analyse`.
|
||
|
||
**Алгоритм:**
|
||
|
||
1. Получает транскрипцию и массив `prompts` из сообщения.
|
||
2. Для **каждого** промпта — отдельный запрос в **Yandex LLM** (сейчас 3 промпта: behavioral, client_data, cargo_data).
|
||
3. Сохраняет результат в `results.analysis`, метаданные — в `results.metadata`.
|
||
4. Если `results.tagging` уже заполнен — публикует в `final` и удаляет файл.
|
||
|
||
### 5. Final — итоговое сообщение
|
||
|
||
**Выход:** очередь `final` (default exchange, routing key = `final`).
|
||
|
||
Публикуется **полный JSON** из таблицы `results`: транскрипция, analysis, tagging, metadata, статус, timestamps.
|
||
|
||
После успешной публикации аудиофайл удаляется из `processing/`.
|
||
|
||
> **Consumer для очереди `final` пока не реализован** — сообщения накапливаются в очереди до подключения обработчика.
|
||
|
||
---
|
||
|
||
## Файловое хранилище
|
||
|
||
Путь на хосте задаётся `STORAGE_ROOT` (по умолчанию `./storage`).
|
||
|
||
| Директория | Назначение |
|
||
|---------------|-------------------------------------------------|
|
||
| `incoming/` | Новые файлы для обработки |
|
||
| `processing/` | Файлы в работе (после claim watcher) |
|
||
| `failed/` | Файлы при невосстановимых ошибках watcher |
|
||
|
||
**Жизненный цикл файла:**
|
||
|
||
```
|
||
incoming/recording.wav
|
||
→ processing/01KTN....wav (watcher)
|
||
→ остаётся до завершения (transcribe читает по пути)
|
||
→ удаляется (после публикации в final)
|
||
```
|
||
|
||
Внутри контейнеров путь: `/data/storage/...` (volume mount).
|
||
|
||
---
|
||
|
||
## RabbitMQ
|
||
|
||
### Топология
|
||
|
||
```
|
||
exchange: audio_pipeline (direct)
|
||
└── queue: transcribe.tasks ← routing key: audio.new
|
||
|
||
exchange: transcription_done (fanout)
|
||
├── queue: analyse
|
||
└── queue: tagging
|
||
|
||
queue: final (default exchange, без binding)
|
||
```
|
||
|
||
### Очереди
|
||
|
||
| Очередь | Producer | Consumer | Описание |
|
||
|--------------------|------------|------------|-----------------------------|
|
||
| `transcribe.tasks` | watcher | transcribe | Новые аудиозадачи |
|
||
| `analyse` | transcribe | analyse | Результат транскрипции |
|
||
| `tagging` | transcribe | tagging | Результат транскрипции |
|
||
| `final` | analyse/tagging | — | Итоговый результат |
|
||
|
||
### Dead Letter
|
||
|
||
Очередь `transcribe.tasks` настроена с DLX (`dlx`). Сообщения с `Nack(requeue=false)` уходят в dead-letter. Отдельная DLQ-очередь может потребовать дополнительной настройки.
|
||
|
||
---
|
||
|
||
## PostgreSQL
|
||
|
||
### Таблица `results`
|
||
|
||
```sql
|
||
CREATE TABLE results (
|
||
task_id TEXT PRIMARY KEY,
|
||
filename TEXT,
|
||
transcription TEXT,
|
||
analysis JSONB, -- результат analyse (Yandex LLM)
|
||
tagging JSONB, -- результат tagging (Yandex LLM)
|
||
metadata JSONB, -- file_path, segments, prompts, language, transcribed_at
|
||
status TEXT DEFAULT 'pending', -- pending | done
|
||
created_at TIMESTAMPTZ,
|
||
updated_at TIMESTAMPTZ
|
||
);
|
||
```
|
||
|
||
### Кто что пишет
|
||
|
||
| Поле | Пишет | Когда |
|
||
|-----------------|----------|--------------------------------|
|
||
| `tagging` | tagging | После классификации |
|
||
| `analysis` | analyse | После анализа |
|
||
| `filename` | оба | При сохранении своей части |
|
||
| `transcription` | analyse | При сохранении analysis |
|
||
| `metadata` | analyse | file_path, segments, prompts… |
|
||
| `status` | оба | `done` когда оба поля заполнены|
|
||
|
||
### Просмотр данных
|
||
|
||
```bash
|
||
docker exec -it postgres psql -U pipeline -d pipeline
|
||
```
|
||
|
||
```sql
|
||
SELECT task_id, filename, status, updated_at FROM results ORDER BY updated_at DESC;
|
||
|
||
SELECT task_id, jsonb_pretty(analysis), jsonb_pretty(tagging)
|
||
FROM results WHERE task_id = 'ВАШ_TASK_ID';
|
||
```
|
||
|
||
---
|
||
|
||
## Воркеры
|
||
|
||
### Watcher
|
||
|
||
- **Язык:** Go
|
||
- **Зависимости:** RabbitMQ
|
||
- **Volume:** `${STORAGE_ROOT}:/data/storage`
|
||
- **Не использует:** Postgres, Yandex, Nexara
|
||
|
||
### Transcribe
|
||
|
||
- **Язык:** Go
|
||
- **API:** Nexara (STT)
|
||
- **Промпты:** static file или HTTP
|
||
- **Volume:** storage + `configs/prompts.json`
|
||
|
||
### Tagging
|
||
|
||
- **Язык:** Go
|
||
- **API:** Yandex Cloud LLM (`YANDEX_API_KEY`, `YANDEX_MODEL`)
|
||
- **При старте:** тестовый запрос к Yandex API (проверка подключения)
|
||
- **Volume:** storage (для удаления файлов) + `.env` (hot-reload токена)
|
||
|
||
### Analyse
|
||
|
||
- **Язык:** Go
|
||
- **API:** Yandex Cloud LLM
|
||
- **Вызовов LLM на задачу:** по числу промптов (сейчас 3)
|
||
- **Без повторов:** один вызов на промпт, при ошибке — сообщение отбрасывается
|
||
- **Volume:** storage + `.env`
|
||
|
||
---
|
||
|
||
## Форматы сообщений
|
||
|
||
### 1. Watcher → Transcribe
|
||
|
||
**Exchange:** `audio_pipeline` (direct)
|
||
**Routing key:** `audio.new`
|
||
**Queue:** `transcribe.tasks`
|
||
|
||
```json
|
||
{
|
||
"task_id": "01KTNVA3EKW8CY2QDAYKKVZ40W",
|
||
"file_path": "/data/storage/processing/01KTNVA3EKW8CY2QDAYKKVZ40W.wav",
|
||
"filename": "1.wav",
|
||
"size": 1234567,
|
||
"created_at": 1780914907
|
||
}
|
||
```
|
||
|
||
### 2. Transcribe → Analyse + Tagging
|
||
|
||
**Exchange:** `transcription_done` (fanout)
|
||
**Queues:** `analyse`, `tagging` (одинаковое тело)
|
||
|
||
```json
|
||
{
|
||
"task_id": "01KTNVA3EKW8CY2QDAYKKVZ40W",
|
||
"filename": "1.wav",
|
||
"file_path": "/data/storage/processing/01KTNVA3EKW8CY2QDAYKKVZ40W.wav",
|
||
"transcription": "полный текст транскрипции...",
|
||
"language": "ru",
|
||
"segments": [
|
||
{"start": 0.0, "end": 27.96, "text": "..."}
|
||
],
|
||
"prompts": [
|
||
{
|
||
"id": 1,
|
||
"id_section": 1,
|
||
"name": "behavioral",
|
||
"prompt": "Ты — строгий классификатор звонков...",
|
||
"dt_create": "2026-06-09T09:00:00"
|
||
}
|
||
],
|
||
"transcribed_at": 1780914907
|
||
}
|
||
```
|
||
|
||
### 3. Final — итоговое сообщение
|
||
|
||
**Queue:** `final`
|
||
|
||
```json
|
||
{
|
||
"task_id": "01KTNVA3EKW8CY2QDAYKKVZ40W",
|
||
"filename": "1.wav",
|
||
"transcription": "полный текст...",
|
||
"analysis": {
|
||
"behavioral": {
|
||
"greeting": {"value": true, "evidence": "Здравствуйте", "confidence": 0.95},
|
||
"initiative": {"value": true, "evidence": "...", "confidence": 0.8},
|
||
"questions_check": {"value": false, "evidence": null, "confidence": 0.0},
|
||
"closing": {"value": true, "evidence": "всего доброго", "confidence": 0.9}
|
||
},
|
||
"client_data": { "...": "..." },
|
||
"cargo_data": { "...": "..." }
|
||
},
|
||
"tagging": {
|
||
"L1": "tracking",
|
||
"L2": "location_request",
|
||
"L3": "delay",
|
||
"risk_level": "medium",
|
||
"has_action_items": true,
|
||
"has_deadline": false
|
||
},
|
||
"file_path": "/data/storage/processing/01KTNVA3EKW8CY2QDAYKKVZ40W.wav",
|
||
"language": "ru",
|
||
"segments": [...],
|
||
"prompts": [...],
|
||
"transcribed_at": 1780914907,
|
||
"status": "done",
|
||
"created_at": "2026-06-09T09:00:00Z",
|
||
"updated_at": "2026-06-09T09:05:00Z"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Промпты для анализа
|
||
|
||
### Источник
|
||
|
||
Файл: `workers/transcribe/configs/prompts.json`
|
||
Или HTTP API (см. [переключение на API](#переключение-промптов-на-api)).
|
||
|
||
Transcribe загружает промпты и **вкладывает их в сообщение** для analyse. Analyse не знает, откуда они пришли.
|
||
|
||
### Текущие промпты (3 штуки)
|
||
|
||
| name | Назначение |
|
||
|----------------|---------------------------------------------------------|
|
||
| `behavioral` | Приветствие, инициативность, вопросы, прощание |
|
||
| `client_data` | Первый раз, город, тип клиента, контакты, источник |
|
||
| `cargo_data` | Характер груза, параметры, стоимость |
|
||
|
||
Каждый промпт — **полный текст инструкции** с форматом JSON-ответа. Analyse отправляет:
|
||
|
||
```
|
||
<текст промпта из конфига>
|
||
|
||
=== ТРАНСКРИПЦИЯ ===
|
||
"""
|
||
<текст звонка>
|
||
"""
|
||
```
|
||
|
||
Ответ LLM сохраняется целиком под ключом `name` промпта в `analysis`.
|
||
|
||
### Tagging — отдельный промпт
|
||
|
||
Tagging **не использует** `prompts.json`. У него встроенный промпт классификации логистических диалогов (L1/L2/L3, risk_level, has_action_items, has_deadline).
|
||
|
||
---
|
||
|
||
## Агрегация и очередь final
|
||
|
||
Оба воркера (`analyse`, `tagging`) пишут в одну строку `results` по `task_id`.
|
||
|
||
**Атомарная проверка готовности** (SQL):
|
||
|
||
```sql
|
||
UPDATE results SET ...
|
||
RETURNING (analysis IS NOT NULL AND tagging IS NOT NULL)
|
||
```
|
||
|
||
- Если `RETURNING = false` — воркер ждёт второго.
|
||
- Если `RETURNING = true` — этот воркер:
|
||
1. Читает полную строку из БД
|
||
2. Публикует JSON в очередь `final`
|
||
3. Удаляет файл из `processing/` (только пути с `/processing/`)
|
||
|
||
---
|
||
|
||
## Конфигурация (.env)
|
||
|
||
Пример ключевых переменных:
|
||
|
||
```env
|
||
# Storage
|
||
STORAGE_ROOT=./storage
|
||
|
||
# Watcher
|
||
POLL_INTERVAL=5s
|
||
STABLE_WINDOW=2s
|
||
STABLE_CHECKS=3
|
||
|
||
# RabbitMQ
|
||
RABBITMQ_URL=amqp://admin:secret123@rabbit:5672/
|
||
RABBITMQ_EXCHANGE=audio_pipeline
|
||
RABBITMQ_ROUTING_KEY=audio.new
|
||
|
||
# Transcribe
|
||
INPUT_QUEUE=transcribe.tasks
|
||
OUTPUT_EXCHANGE=transcription_done
|
||
ANALYSE_QUEUE=analyse
|
||
TAGGING_QUEUE=tagging
|
||
FINAL_QUEUE=final
|
||
PREFETCH=1
|
||
|
||
# Nexara (STT)
|
||
NEXARA_BASE_URL=https://api.nexara.ru
|
||
NEXARA_API_KEY=...
|
||
NEXARA_MODEL=whisper-1
|
||
NEXARA_TIMEOUT=10m
|
||
|
||
# Prompts
|
||
PROMPTS_SOURCE=static
|
||
PROMPTS_FILE=/app/configs/prompts.json
|
||
PROMPTS_SECTION=1
|
||
|
||
# Postgres
|
||
POSTGRES_USER=pipeline
|
||
POSTGRES_PASSWORD=pipeline_secret
|
||
POSTGRES_DB=pipeline
|
||
DATABASE_URL=postgres://pipeline:pipeline_secret@postgres:5432/pipeline?sslmode=disable
|
||
|
||
# Yandex LLM (tagging + analyse)
|
||
YANDEX_API_KEY=t1....
|
||
YANDEX_MODEL=gpt://folder_id/model_name
|
||
YANDEX_API_URL=https://ai.api.cloud.yandex.net/v1/chat/completions
|
||
```
|
||
|
||
### Hot-reload токена Yandex
|
||
|
||
Воркеры `tagging` и `analyse` монтируют `.env` как `/config/.env` и перечитывают при **каждом старте** контейнера:
|
||
|
||
```bash
|
||
docker compose restart tagging analyse
|
||
```
|
||
|
||
> `docker compose restart` не пересоздаёт контейнер, но перезапускает процесс, который читает свежий `.env`.
|
||
|
||
---
|
||
|
||
## Запуск и управление
|
||
|
||
### Первый запуск
|
||
|
||
```bash
|
||
cd audio-pipeline
|
||
docker compose up -d --build
|
||
```
|
||
|
||
### Полный сброс (очереди + БД)
|
||
|
||
```bash
|
||
docker compose down -v
|
||
docker compose up -d --build
|
||
```
|
||
|
||
### Пересборка отдельных воркеров
|
||
|
||
```bash
|
||
docker compose up -d --build transcribe analyse tagging
|
||
```
|
||
|
||
### Обработка нового файла
|
||
|
||
```bash
|
||
cp recording.wav storage/incoming/
|
||
```
|
||
|
||
### Проверка статуса
|
||
|
||
```bash
|
||
docker compose ps
|
||
docker compose logs -f watcher transcribe analyse tagging
|
||
```
|
||
|
||
### RabbitMQ — просмотр очереди final
|
||
|
||
UI: http://localhost:15672 → Queues → `final`
|
||
|
||
---
|
||
|
||
## Логирование
|
||
|
||
Все воркеры пишут **структурированные JSON-логи** в stdout.
|
||
|
||
### Ключевые события
|
||
|
||
| Событие | Воркер | Описание |
|
||
|--------------------------|-----------|---------------------------------------|
|
||
| `claimed file` | watcher | Файл взят в обработку |
|
||
| `transcribed` | transcribe| STT завершён |
|
||
| `llm call ok` | analyse/tagging | Вызов Yandex API |
|
||
| `task complete` | analyse/tagging | Оба результата готовы |
|
||
| `published final` | analyse/tagging | Сообщение в final |
|
||
| `processing file deleted` | analyse/tagging | Файл удалён из processing |
|
||
| `yandex api check ok` | analyse/tagging | Проверка API при старте |
|
||
|
||
### Поиск в логах
|
||
|
||
```bash
|
||
# все LLM-вызовы
|
||
docker compose logs analyse 2>&1 | grep '"llm call'
|
||
|
||
# завершённые задачи
|
||
docker compose logs 2>&1 | grep '"task complete"'
|
||
|
||
# ошибки
|
||
docker compose logs 2>&1 | grep '"level":"WARN"'
|
||
```
|
||
|
||
---
|
||
|
||
## Обработка ошибок
|
||
|
||
| Ситуация | Поведение |
|
||
|----------------------------------|------------------------------------------------|
|
||
| Битое JSON в очереди | `Nack(requeue=false)` — в DLQ |
|
||
| Ошибка Nexara STT | `Nack(requeue=false)` — в DLQ |
|
||
| Ошибка Yandex LLM | `Nack(requeue=false)` — сообщение отбрасывается |
|
||
| Ошибка сохранения в Postgres | `Nack(requeue=false)` — отбрасывается |
|
||
| Redelivered сообщение | Пропускается (без повторного вызова LLM) |
|
||
| Ошибка публикации в final | Файл **не** удаляется |
|
||
| Yandex API недоступен при старте | Воркер не запускается, контейнер рестартит |
|
||
|
||
**Политика без повторов:** каждый промпт / классификация — ровно один вызов LLM. Повторные доставки RabbitMQ игнорируются.
|
||
|
||
---
|
||
|
||
## Переключение промптов на API
|
||
|
||
Уже реализовано в transcribe. Достаточно изменить `.env`:
|
||
|
||
```env
|
||
PROMPTS_SOURCE=http
|
||
PROMPTS_BASE_URL=https://your-api.example.com
|
||
PROMPTS_API_KEY=your_token
|
||
PROMPTS_SECTION=1
|
||
```
|
||
|
||
**Запрос:** `GET {PROMPTS_BASE_URL}/metrics/?id_section=1`
|
||
|
||
**Ожидаемый ответ** — массив в том же формате, что `prompts.json`:
|
||
|
||
```json
|
||
[
|
||
{
|
||
"id": 1,
|
||
"id_section": 1,
|
||
"name": "behavioral",
|
||
"prompt": "полный текст промпта...",
|
||
"dt_create": "2026-06-09T09:00:00"
|
||
}
|
||
]
|
||
```
|
||
|
||
Analyse менять не нужно — промпты приходят в сообщении RabbitMQ.
|
||
|
||
---
|
||
|
||
## Что не реализовано
|
||
|
||
- **Consumer очереди `final`** — нет воркера, который читает итоговые сообщения
|
||
- **DLQ-очередь** `transcribe.tasks.failed` — exchange `dlx` объявлен, но отдельная очередь может не быть привязана
|
||
- **Повторная обработка** при ошибках LLM — намеренно отключена
|
||
- **Архивация** удалённых файлов — файлы удаляются без бэкапа
|
||
|
||
---
|
||
|
||
## Быстрый troubleshooting
|
||
|
||
| Проблема | Решение |
|
||
|---------------------------------------|--------------------------------------------------------|
|
||
| Файл не обрабатывается | Проверить `incoming/`, логи watcher |
|
||
| TLS timeout к Yandex из контейнера | MTU Docker, VPN, `docker compose restart` |
|
||
| Старый Yandex токен | Обновить `.env`, `docker compose restart tagging analyse` |
|
||
| `context canceled` в final | Исправлено — пересобрать analyse/tagging |
|
||
| `metadata` column does not exist | `ALTER TABLE results ADD COLUMN IF NOT EXISTS metadata JSONB;` |
|
||
| Файл остаётся в processing | Проверить, дошли ли оба воркера до `published final` |
|