Repository navigation
feat(dictation): add the Dictation API with the live upload as its only connection - #247
Merged
Merged
Conversation
Adds the Dictation API (dictation.assemblyai.com) as a first-class product
alongside the sync API, in assemblyai/dictation/v1/ mirroring sync/v1/.
Public surface:
aai.DictationTranscriber(*, client=None, config=None, max_workers=None,
api_key=None) -> transcribe(), transcribe_async(), warm(), close(),
context manager, config property
aai.AsyncDictationTranscriber(*, client=None, config=None, api_key=None)
-> await transcribe(), await warm(), await aclose(), async context
manager
aai.DictationConfig(sample_rate, channels, language_codes,
keyterms_prompt, llm_instruction) — rejects unknown fields
aai.DictationResponse(text, words, confidence, llm_response, llm_error,
audio_duration_ms, session_id, request_time_ms, sync_time_ms) plus
final_text, which prefers the LLM rewrite
aai.DictationWord(text, confidence)
aai.DictationError(status_code, error_code, retry_after)
The request posts multipart/form-data with the config part ahead of the
audio part, authenticates with the raw API key, and sends no model routing
header — the server picks the model. Endpoints are /v1/transcribe and
/v1/warm under settings.dictation_base_url, with a 300s
settings.dictation_http_timeout.
Audio-input handling both single-request products share moves to
assemblyai/_audio.py: reading path/bytes/file-object input, choosing the
multipart Content-Type, and serializing the config part. sync/v1/_base.py
keeps the same names and signatures over it, and the sync API's wire
behavior is unchanged — every container still posts as WAV. Dictation maps
the common container extensions to their own Content-Types.
Error-envelope parsing is shared too, so dictation raises DictationError
from the same RFC 9457, {error, error_code}, and {detail} bodies.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Every dictation entry point now posts to POST /v1/transcribe/live, the one route the Dictation API markets, with the config part ahead of the audio and the body chunked so the request starts before the audio exists. The buffered transcribe()/transcribe_async() and the /v1/transcribe constant are removed; a test pins that nothing requests the buffered route. Public surface, mirroring the sync API's live work (#241/#244/#245): DictationTranscriber.transcribe_live(data, config=None) — an iterable of chunks, a binary file object read as it fills, raw bytes, or a local path (complete audio goes over the same connection as a single chunk; there is no transcribe() to steer it to) DictationTranscriber.open_live(config=None) -> DictationLiveSession — push-style: write()/close()/result()/abort(), context manager; the request runs on the transcriber's worker pool from the moment the session opens AsyncDictationTranscriber.transcribe_live() / open_live() -> AsyncDictationLiveSession — same, as a task on the running loop warm() unchanged. AudioSource / AsyncAudioSource replace the dictation AudioInput export; sessions are exported from assemblyai.dictation and assemblyai.dictation.v1. Plumbing: the streaming multipart encoder and chunk iterators live in a new product-neutral assemblyai/_multipart.py (sync's copy on #241 should import it once that lands); _audio.resolve_format() decides filename and Content-Type without reading bytes. settings.dictation_http_timeout stays 300 s and is documented as per-socket-operation, as every httpx timeout is. Verified against prod with the quickstart harness: path, threaded session and asyncio session all return the same transcript as the buffered route did, with ~0.5 s from last byte to result on a paced 8.7 s clip. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…loop tick The asyncio open_live() test asserted the transport had been reached after a single sleep(0), but transcribe_live() first resolves the source in a worker thread, so on a slower runner the task is still awaiting that when the assertion runs. Poll for the signal with a bounded wait. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The httpx 0.22/0.24 tox envs pull an older pytest-httpx that has no is_reusable and does not read an async request's stream, so a body read after the call hits a spent stream. Register one response per request instead, and have the asyncio client-level tests inspect what reached the transport rather than re-read the recorded body; the framing itself is still asserted through MockTransport. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ell/dictation-sdk Keeps the shared _audio helpers for sync's _resolve_audio and adds master's resolve_format() as a wrapper over _audio.resolve_format so stream_filename() keeps its helper; merges the typing imports in sync/v1/api.py; keeps both sync_live_http_timeout and the dictation settings. Version to 1.7.0 on top of master's 1.6.0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The merge left two identical copies of the streaming multipart encoder: sync/v1/_multipart.py from #241 and the product-neutral assemblyai/_multipart.py the dictation client uses. Sync now imports the shared one, including _Aborted, and its own copy is removed. No behaviour change; the sync live tests import _STREAM_READ_SIZE from the shared path. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ccampbell-aai
force-pushed
the
ccampbell/dictation-sdk
branch
from
September 10, 2026 19:34
375ecd4 to
3b8ff35
Compare
Master is at 1.4.0 (the sync live stack squash-merged with only #241's bump surviving), so 1.7.0 skipped two versions. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ccampbell-aai
marked this pull request as ready for review
September 10, 2026 21:21
…Examples section Two rows in Choosing a transcriber, a Dictation Examples section that mirrors the sync one (open_live() from a microphone, transcribe_live() from an iterator/file object/bytes/path, llm_instruction and final_text, config, warm(), asyncio, errors), and the dictation settings in the defaults block. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
bgotthold-aai
approved these changes
Sep 10, 2026
aurpsis-aai
approved these changes
Sep 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds the Dictation API (
dictation.assemblyai.com) to the SDK asassemblyai.dictation.v1, with top-level shortcuts underaai.*.What's new
A dictation client for short spoken notes. Audio is uploaded as it is spoken over a single connection to
POST /v1/transcribe/live, the service transcribes each speech segment as it lands, and the result comes back as one transcript with an optional LLM rewrite. There is no job id and no polling.Clients
DictationTranscriber(*, client=None, config=None, max_workers=None, api_key=None)open_live(config=None) -> DictationLiveSession— push-style session for callback-driven audio. The request starts on a worker thread the moment the session opens.write()is thread-safe and never blocks;close()ends the audio;result()waits for the transcript;abort()drops the request. As a context manager it closes on a clean exit and aborts if the block raised.transcribe_live(data, config=None)— pull-style. Takes an iterable of chunks, a binary file object read as it fills, raw bytes, or a local path. Complete audio is sent as a single chunk over the same connection.warm()opens the connection ahead of time;close()shuts the worker pool; context manager;configproperty.AsyncDictationTranscriber(*, client=None, config=None, api_key=None)— the asyncio counterpart.transcribe_live()also accepts async iterables;open_live() -> AsyncDictationLiveSessionruns the request as a task on the running loop, withwrite()/close()as plain functions andresult()/abort()as coroutines.warm(),aclose(), async context manager.Models
DictationConfig—sample_rate,channels(both required only for raw PCM; WAV carries them in its header),language_codes,keyterms_prompt(≤2048 chars total),llm_instruction(≤2048 chars). Unknown fields are rejected.DictationResponse—text,words(DictationWord:text,confidence),confidence,llm_response,llm_error,audio_duration_ms,session_id,request_time_ms,sync_time_ms, plusfinal_text, which returns the LLM rewrite when there is one and the raw transcript otherwise. Unknown response fields are ignored.DictationError—status_code,error_code(snake_cased problem-details title, e.g.audio_too_large),retry_afteron 429/503. Raised for any non-200, including rejections the server sends while the upload is still in flight.AudioSource/AsyncAudioSource— the accepted input types.Wire behaviour
multipart/form-datawith chunked transfer encoding, theconfigpart always ahead of theaudiopart (an empty object when nothing is set).audio/pcmfor.pcm/.rawor whensample_rate/channelsare set,audio/wavby default, and the true container type for other known extensions.X-AAI-Modelheader; the service picks the model. URLs are not accepted.Settings
dictation_base_url(defaulthttps://dictation.assemblyai.com, overridable viaASSEMBLYAI_DICTATION_BASE_URL).dictation_http_timeout(default 300 s, per socket operation like every httpx timeout).Shared helpers
assemblyai/_multipart.py— the streaming multipart encoder and chunk iterators, used by both the dictation and sync live clients.assemblyai/_audio.py— shared audio-input reading, format resolution and config serialization for the single-request products.Docs
open_live(), iterator/file/bytes/path viatranscribe_live(), LLM rewrite, config,warm(), asyncio, errors), and the dictation settings in the defaults block.Validation
transcribe_live(path), a threadedopen_live()session paced at real time, and an asyncio session all return the expected transcript withllm_responsepopulated.Version bumps to 1.5.0. Draft until the Dictation API is public.
🤖 Generated with Claude Code