feat: распознавание голоса пациента (STT через OpenRouter + mock) #122

Merged
Eleboba merged 2 commits from feat/116-stt into dev 2026-10-31 11:37:06 +03:00
Owner

Что меняется

Серверная часть голосового ввода (слайд 11 «Записать → Проверить»). POST /api/v1/me/{token}/stt принимает WAV 16 кГц mono и возвращает распознанный текст. Ручка только расшифровывает: пациент проверяет текст и отправляет его обычным сообщением, поэтому safety, scrub() и шлюз LLM срабатывают как для набранного текста. Аудио не сохраняется и не логируется.

  • app/llm/stt.py: протокол SpeechToText, OpenRouterSTT (input_audio, google/gemini-2.5-flash, прокси и повторы по образцу vobot), MockSTT (офлайн).
  • STT_PROVIDER=auto|off|mock|openrouter (auto = OpenRouter при наличии ключа, иначе mock), STT_MODEL, лимит 60 с.
  • /patient/capabilities → voice_input.
  • Тексты промпта — в config/texts/stt.yaml; ADR-014: OpenRouter только на синтетике, для пилота SaluteSpeech (#121).

Closes #116

Зона

B (+ app/config.py, .env.example — контрактные, добавлены только новые поля)

Как проверить

uv run pytest tests/test_llm_stt.py tests/test_api_stt.py (OpenRouter замокан через httpx.MockTransport). Вживую: curl -H 'Content-Type: audio/wav' --data-binary @v.wav localhost:8000/api/v1/me/<token>/stt.

Чек-лист

  • rebase на origin/dev
  • make lint + make test зелёные (1078 passed)
  • лог в issue
  • нет ПДн в логах (длительность/провайдер/время/длина текста), нет секретов
  • живой прогон с новым ключом OpenRouter — старый ключ в .env отвечает 401

🤖 Generated with Claude Code

## Что меняется Серверная часть голосового ввода (слайд 11 «Записать → Проверить»). `POST /api/v1/me/{token}/stt` принимает WAV 16 кГц mono и возвращает распознанный текст. Ручка **только расшифровывает**: пациент проверяет текст и отправляет его обычным сообщением, поэтому safety, `scrub()` и шлюз LLM срабатывают как для набранного текста. Аудио не сохраняется и не логируется. - `app/llm/stt.py`: протокол `SpeechToText`, `OpenRouterSTT` (`input_audio`, `google/gemini-2.5-flash`, прокси и повторы по образцу vobot), `MockSTT` (офлайн). - `STT_PROVIDER=auto|off|mock|openrouter` (`auto` = OpenRouter при наличии ключа, иначе mock), `STT_MODEL`, лимит 60 с. - `/patient/capabilities` → `voice_input`. - Тексты промпта — в `config/texts/stt.yaml`; ADR-014: OpenRouter только на синтетике, для пилота SaluteSpeech (#121). Closes #116 ## Зона B (+ `app/config.py`, `.env.example` — контрактные, добавлены только новые поля) ## Как проверить `uv run pytest tests/test_llm_stt.py tests/test_api_stt.py` (OpenRouter замокан через `httpx.MockTransport`). Вживую: `curl -H 'Content-Type: audio/wav' --data-binary @v.wav localhost:8000/api/v1/me/<token>/stt`. ## Чек-лист - [x] rebase на origin/dev - [x] make lint + make test зелёные (1078 passed) - [x] лог в issue - [x] нет ПДн в логах (длительность/провайдер/время/длина текста), нет секретов - [ ] живой прогон с новым ключом OpenRouter — старый ключ в `.env` отвечает 401 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Eleboba added 1 commit 2026-10-31 11:22:15 +03:00
POST /api/v1/me/{token}/stt takes a 16 kHz mono WAV and only transcribes:
the patient checks the text and sends it as an ordinary message, so safety,
scrub() and the LLM gateway still apply. Audio is neither stored nor logged.
OpenRouter STT is synthetic-data only (ADR-014); SaluteSpeech for the pilot is #121.

Refs #116

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Eleboba added 1 commit 2026-10-31 11:29:44 +03:00
Live check on synthetic speech (espeak) via OpenRouter gemini-2.5-flash:
3–5 s per clip, silence → no speech.

Refs #116

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Eleboba merged commit 4eb506bf65 into dev 2026-10-31 11:37:06 +03:00
Sign in to join this conversation.