Skip to content

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

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

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-jameshe-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

FileChange
assemblyai/async_client.pyNew. AsyncClient wraps an httpx.AsyncClient.
assemblyai/async_api.pyNew. Asyncio version of each transcript request function.
assemblyai/async_transcriber.pyNew. AsyncTranscriber and AsyncTranscript.
assemblyai/types.py1 line. LemurSource now accepts an AsyncTranscript.
assemblyai/__init__.pyExports AsyncTranscriber, AsyncTranscript, AsyncClient.
README.md, CLAUDE.mdAsyncio examples and reference notes.
tests/unit/test_async_transcriber.pyNew. 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.
importasyncioimportassemblyaiasaaiasyncdefmain():
asyncwithaai.AsyncTranscriber() astranscriber:
transcript=awaittranscriber.transcribe("./audio.mp3")
print(transcript.text)
sentences=awaittranscript.get_sentences()
asyncio.run(main())

he-jamesand 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>
Sign up for freeto 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

@he-james@jchang-assemblyai