Skip to content

Add ElevenLabs Scribe transcription adapter - #161

Open
RichJackson wants to merge 13 commits into
mainfrom
feature/11labs-integration
Open

RichJackson wants to merge 13 commits into
mainfrom
feature/11labs-integration

Conversation

@RichJackson

@RichJackson RichJackson commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

What

Adds ElevenLabs Scribe as a transcription service and switches preprod over to it.

ElevenLabs adapter

  • New synchronous adapter elevenlabs_stt (common/services/transcription_services/elevenlabs.py), registered with the transcription manager.
  • Calls the EU data residency stack (https://api.eu.residency.elevenlabs.io) by default. A residency key only works against the stack it was issued for, so ELEVENLABS_BASE_URL must match the key.
  • Retries rate limits (429) and server errors (5xx); auth and validation errors fail straight away.
  • If diarisation returns no speaker, words fall back to speaker_0 rather than being dropped.
  • New settings: ELEVENLABS_API_KEY, ELEVENLABS_BASE_URL, ELEVENLABS_STT_MODEL (default scribe_v2), ELEVENLABS_STT_TIMEOUT_SECONDS (default 1 hour). Because the adapter is synchronous, the timeout, not the service's 10-hour limit, caps how long a recording it can handle.

Shared retry and polling settings

  • The Azure and AWS adapters each hardcoded the same tenacity decorator and retry_count/retry_delay arguments. These now go through transcription_retry in retry.py and new TRANSCRIPTION_RETRY_* / TRANSCRIPTION_POLL_* settings. The defaults match the old hardcoded values.
  • check() no longer takes retry_count/retry_delay.
  • Status-code magic numbers (and their noqa: PLR2004s) are replaced with constants in common/http_status.py.

Adapter input checks

  • Synchronous adapters now raise TypeError if they're given a Recording, and async adapters if they're given a Path, instead of failing later with a less obvious error.

Infrastructure

  • ELEVENLABS_API_KEY added to the SSM env secrets (placeholder value).
  • TRANSCRIPTION_SERVICES is ["elevenlabs_stt"] in the preprod workspace only; every other workspace keeps azure_stt_synchronous + azure_stt_batch. Preprod has no Azure fallback.

Other

  • Adds the elevenlabs dependency. Most of the pyproject.toml diff is formatting (lists collapsed/expanded), not functional changes.

Tests

  • tests/test_elevenlabs_adapter.py: adapter unit tests (response mapping, retry behaviour, base URL, manager registration).
  • tests/test_transcription_retry.py, tests/test_transcription_adapter_inputs.py: shared retry and input-type checks.
  • tests/test_elevenlabs_adapter_live.py: transcribes a 60-second clip against the real API. Marked costs_money and requires_audio_data; needs an EU residency ELEVENLABS_API_KEY in .env.

Note - this is currently deployed to preprod

🤖 Generated with Claude Code

https://claude.ai/code/session_01JdgoAiwmGAx5qksBNtBVsw

RichJackson and others added 11 commits September 9, 2026 10:46
Synchronous adapter for the ElevenLabs Scribe speech-to-text API, registered with the transcription manager,
with settings for its API key, model and request timeout.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GSrMumb6qEQrBJdZ37NbfB
Adapters build their tenacity retry from transcription_retry and the TRANSCRIPTION_RETRY_* settings, and the
async adapters poll using the TRANSCRIPTION_POLL_* settings instead of per-call arguments. Shared HTTP status
codes move to common/http_status.py.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GSrMumb6qEQrBJdZ37NbfB
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GSrMumb6qEQrBJdZ37NbfB
The manager only passes a Recording to async adapters, so raise a TypeError before calling the API. This also
lets Pyright narrow the argument to a Path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GSrMumb6qEQrBJdZ37NbfB
The synchronous Azure adapter rejects anything but a local file and the async AWS and Azure batch adapters
anything but a Recording, before any client is created. This also lets Pyright narrow the Path | Recording
argument in each start().

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GSrMumb6qEQrBJdZ37NbfB
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XZQAgYmkw28HnNLZHgcwxS
…iption test

A data residency API key is rejected with a 401 by every stack but its own, including the
global API the SDK calls by default. ELEVENLABS_BASE_URL now defaults to the EU stack and
can be pointed elsewhere for a key issued on another one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XZQAgYmkw28HnNLZHgcwxS
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JdgoAiwmGAx5qksBNtBVsw
@RichJackson
RichJackson requested a review from a team as a code owner September 14, 2026 14:17

@GChrysostomou GChrysostomou left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work Rich!

LANGUAGE_CODE = "eng"


def _is_retryable(exception: BaseException) -> bool:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks very re-usable so maybe move to retry.py?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah no afraid not - it depends on ApiError from the elevenlabs SDK

@@ -0,0 +1,49 @@
"""Transcribe real audio with the live ElevenLabs Scribe API.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we can skip this if env is local development? If basically our ENVIRONMENT=local we don't have to check this to even further minimise costs if we are building tests lets say for a new feature and we do make test?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah great call out - I'll add a skip

RichJackson and others added 2 commits September 15, 2026 11:48
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread pyproject.toml
"ray[default]>=2.53.0,<3",
"azure-storage-blob>=12.26.0,<13",
"azure-identity>=1.23.1,<2",
"elevenlabs",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unbounded

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.

2 participants