Skip to content

feat(transcriber): add AsyncTranscriber for asyncio callers - #224

Closed
he-james wants to merge 2 commits into
jhe/share-request-helpersfrom
jhe/asyncio-transcriber
Closed

feat(transcriber): add AsyncTranscriber for asyncio callers#224
he-james wants to merge 2 commits into
jhe/share-request-helpersfrom
jhe/asyncio-transcriber

Conversation

@he-james

@he-james he-james commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Add aai.AsyncTranscriber, the asyncio version of aai.Transcriber. Every method that calls the API is a coroutine, so many transcriptions run concurrently on one thread. Addresses #80.

Transcriber.transcribe_async returns a concurrent.futures.Future. That future is not awaitable, and .result() blocks the event loop. An asyncio caller must therefore use a thread pool, and the hardware limits the thread count.

Updates

File Change
assemblyai/async_client.py New. AsyncClient wraps an httpx.AsyncClient.
assemblyai/async_api.py New. Asyncio version of each transcript request function.
assemblyai/async_transcriber.py New. AsyncTranscriber and AsyncTranscript.
assemblyai/types.py 1 line. LemurSource now accepts an AsyncTranscript.
assemblyai/__init__.py Exports AsyncTranscriber, AsyncTranscript, AsyncClient.
README.md, CLAUDE.md Asyncio examples and reference notes.
tests/unit/test_async_transcriber.py New. 38 tests.

New functionality

  • AsyncTranscriber: transcribe, submit, transcribe_group, submit_group, get_by_id, delete_by_id, list_transcripts, upload_file, aclose, and the async context manager protocol.
  • AsyncTranscript: the same fields as Transcript. Only the methods that call the API are coroutines.
  • AsyncClient: an httpx.AsyncClient pool. Share one pool between several transcribers.
import asyncio
import assemblyai as aai

async def main():
    async with aai.AsyncTranscriber() as transcriber:
        transcript = await transcriber.transcribe("./audio.mp3")
        print(transcript.text)
        sentences = await transcript.get_sentences()

asyncio.run(main())

he-james and others added 2 commits August 10, 2026 17:57
Closes #80.

`Transcriber.transcribe_async` returns a `concurrent.futures.Future`. That
future is not awaitable, and `.result()` blocks the event loop. An asyncio
caller must therefore use a thread pool. The hardware limits the thread count.

`AsyncTranscriber` provides the same API as `Transcriber`, with coroutines. It
takes the same `TranscriptionConfig`. Its result has the same fields. It needs
one thread.

- `AsyncClient` wraps an `httpx.AsyncClient`. There is no process-wide default
  instance. An `httpx.AsyncClient` pool belongs to the event loop that first
  used it. A global pool therefore fails on a second `asyncio.run()`.
- `async_api` provides an asyncio version of each transcript request function
  in `api`.
- `AsyncTranscript` has the same fields as `Transcript`. Only the methods that
  call the API are coroutines.
- The transcriber provides `get_by_id` and `delete_by_id`, because the async
  transcript needs the transcriber's pool.
- An upload sends a path or a file object in chunks. A worker thread reads each
  chunk. The request sets `Content-Length` when the size is known.
- `transcribe_group` and `submit_group` keep the input order. Both limit
  concurrent work to `max_concurrency`. Both report every error. The sync group
  methods discard errors when `return_failures` is not set.
- LeMUR remains synchronous only. `LemurSource` now accepts an `AsyncTranscript`.

Tests: 38 new unit tests in `tests/unit/test_async_transcriber.py`. They pass
under the pydantic v1-compat path and under pydantic v2. `pytest tests/unit`
gives 420 passed, 3 failed. The 3 failures need `pyaudio` and also fail on
master.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The `py311-httpx0.22` and `py311-httpx0.24` tox envs pin httpx below 0.25. pip
then resolves pytest-httpx back to 0.20 or 0.24, because the current
pytest-httpx requires httpx 0.28. Nine tests used APIs that those versions do
not have. The SDK code is correct on every pinned httpx version.

Two causes, two fixes:

- `add_response(is_reusable=True)` does not exist before pytest-httpx 0.31, and
  `add_callback` runs the callback synchronously before 0.25. An async callback
  therefore returns a coroutine as the response. The 5 group tests now stub
  `async_api.create_transcript` instead. The group methods only orchestrate
  `submit`, so a stub at the transport boundary tests the order, the
  concurrency limit, and the error collection.
- `request.read()` returns an empty body for an async-iterator request under
  pytest-httpx 0.20. The 4 upload tests now assert the request headers only.
  5 new tests call `_upload_request` directly and assert the streamed bytes.
  One new test covers a pipe, which cannot report a size.

Tests: 43 tests in `tests/unit/test_async_transcriber.py`, up from 38. The full
suite gives 425 passed, 3 failed on httpx 0.22, 0.24, and 0.28, and under both
pydantic paths. The 3 failures need `pyaudio` and also fail on master.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@he-james he-james closed this Aug 11, 2026
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