Skip to content

feat: public /v1/jobs API for Telemost transcribe service#2

Open
Savin99 wants to merge 4 commits into
mainfrom
claude/beautiful-varahamihira-551832
Open

feat: public /v1/jobs API for Telemost transcribe service#2
Savin99 wants to merge 4 commits into
mainfrom
claude/beautiful-varahamihira-551832

Conversation

@Savin99

@Savin99 Savin99 commented Apr 17, 2026

Copy link
Copy Markdown
Owner

Summary

  • Публичный контракт /v1/jobs (POST/GET/DELETE) на bot-service — идемпотентный по metadata.session_id, с Bearer-auth (401), concurrency-лимитом (429), mapping'ом внутренних статусов в queued → connecting → recording → transcribing → refining → done/error/cancelled.
  • GET /audio/{id}.wav под тем же Bearer'ом + hourly retention-cleanup; после истечения — 410 audio_expired, метаданные в GET /v1/jobs/{id} остаются.
  • Transcriber принимает speakers_hint / auto_enroll_unknown / initial_prompt; возвращает enrolled_voiceprints ({person_id, voice_bank_id}) + speaker_roles. Авто-энроллмент срабатывает ровно на «1 неопознанный кластер + 1 unenrolled подсказка».
  • voice_bank_ids.json — sidecar-реестр opaque id ↔ display_name с атомарной записью и транслитерацией (Илья Савин → ilya-savin-<uuid8>). VoiceBank остаётся адресованным по имени, tg-bot/CLI не ломаются.
  • Легаси /join, /status, /leave, /transcripts, /meetings, speaker-review/* не тронуты — tg-bot по-прежнему ходит через X-API-Key; на них теперь дополнительно принимается Bearer.

Divergence from the spec

  • result.audio_url отдаёт .wav, а не .mp3 (без ffmpeg-транскода) — согласовано.
  • options.diarize=false сейчас силентно игнорируется (всегда диаризуем). options.llm_refine=false влияет только на показ статуса refining, сам LLM-refinement управляется env-переменными сервиса. Клиент всегда шлёт true/true, так что функционально не заметно.
  • Drive-загрузка на /v1 — best-effort: если падает, транскрипт всё равно едет в done и возвращается через JSON; transcript_url остаётся NULL.

Test plan

  • python -m unittest discover bot-service/tests — 19 passed
  • python -m unittest discover transcriber-service/tests — 49 passed
  • python -m unittest discover tg-bot/tests — 10 passed (регрессий нет)
  • Ручной e2e: поднять stack (docker compose), отправить POST /v1/jobs с живым Telemost URL, прокликать через curl до done, проверить result.audio_url и enrolled_voiceprints.
  • Повторный запуск с voice_bank_id из прошлой сессии и enrolled: true — имя должно проставиться сразу.
  • DELETE /v1/jobs/{id} во время recording → через ~10с GET отдаёт cancelled.

🤖 Generated with Claude Code

Ilya and others added 4 commits April 17, 2026 15:04
Добавляет nullable-колонки для идемпотентности, payload'а спикеров, retention
аудио и прогресса. Миграция делается тем же idempotent `ADD COLUMN` паттерном,
что и предыдущие, плюс индекс по `session_id`. Статус-Literal расширяется
новыми промежуточными значениями (`connecting`, `refining`, `cancelled`) —
они нужны публичному state machine и не меняются для легаси `/join`-пути.

Co-Authored-By: Claude <claude@anthropic.com>
- `POST /transcribe` принимает `speakers_hint`, `auto_enroll_unknown`,
  `initial_prompt` и возвращает `enrolled_voiceprints` + `speaker_roles`.
- `identify_speakers` получает опциональный `allowed_names`, чтобы на
  конкретной встрече подбирать только голоса, заявленные клиентом.
- Авто-энроллмент срабатывает ровно на «1 неопознанный кластер + 1
  unenrolled-подсказка». В остальных конфигурациях — ничего не пишем.
- `voice_bank_ids.json` — sidecar-реестр id ↔ имя с атомарной записью и
  транслитерацией (`Илья Савин → ilya-savin-<uuid8>`). VoiceBank остаётся
  адресованным по display_name, поэтому tg-bot/CLI не ломаются.
- initial_prompt доходит до WhisperX через `transcribe_kwargs`.
- Тесты: отдельный test_voice_bank_registry и 4 новых сценария в
  test_transcribe_pipeline (initial_prompt, allowed_names, auto-enroll
  success/skip).

Co-Authored-By: Claude <claude@anthropic.com>
- `POST /v1/jobs` с payload'ом из спеки (source/metadata/speakers/options).
  Идемпотентность по `metadata.session_id` → 409 с тем же job_id.
  Concurrency-лимит (`MAX_CONCURRENT_JOBS`, default 4) → 429 + retry_after_sec.
- `GET /v1/jobs/{id}` отдаёт маппинг внутренних статусов в публичные
  `queued/connecting/recording/transcribing/refining/done/error/cancelled`,
  с `progress` на ходу и `result` в терминальном состоянии. `speaker_role`
  вычисляется по speakers-payload + enrolled_voiceprints.
- `DELETE /v1/jobs/{id}` через тот же `_stop_session`, но пишет `cancelled`
  (через новый флаг `cancel_terminal_status` в active_sessions), а не `error`.
- `GET /audio/{id}.wav` отдаёт запись поверх `PUBLIC_BASE_URL`. После истечения
  `audio_retention_expires_at` возвращается 410 `audio_expired`; метаданные
  при этом живут в `GET /v1/jobs/{id}`.
- Авторизация: `require_api_key` теперь path-aware — строгий Bearer с 401
  для `/v1` и `/audio`, совместимость с X-API-Key на легаси-роутах
  сохранена (Bearer на них тоже принимается).
- Новый `_bot_workflow_v1` идёт через `connecting → recording → transcribing
  → refining → done`, не зависит от легаси `_bot_workflow`. Drive-загрузка
  — best-effort: упала — логируем и идём в done без `transcript_url`.
- Hourly `_audio_retention_cleanup_loop` удаляет протухшие файлы.
- Тесты: 10 новых сценариев (happy-path с speakers_hint/audio_url/roles,
  drive-fail, retention 410, 401/404/409/429/cancelled).

Co-Authored-By: Claude <claude@anthropic.com>
PUBLIC_BASE_URL попадает в `result.audio_url`, остальные три — retention
аудио, лимит параллельных джоб и интервал cleanup-петли. Defaults выставлены
так, чтобы на чистом .env сервис стартовал без правок.

Co-Authored-By: Claude <claude@anthropic.com>
Savin99 pushed a commit that referenced this pull request Apr 21, 2026
- Кнопки теперь именованы 🎙 #1 / 🔗 #1, #2, … — видно какая кнопка
  к какой строке списка относится (раньше 10 одинаковых «Голоса» /
  «Drive» сливались в полосу).
- Для встреч без длительности и без осмысленного имени показываем
  первые 16 символов meeting_id в <code> — хоть какая-то разница
  между строками, иначе пять раз подряд «📅 18.04 14:37 · ⏱ ?».

Co-Authored-By: Claude <claude@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant