Skip to content

feat(sync): push-style live sessions via open_live() - #244

Merged
ccampbell-aai merged 1 commit into
aurpsis/sync-chunked-uploadfrom
ccampbell/sync-live-session
Sep 10, 2026
Merged

ccampbell-aai merged 1 commit into
aurpsis/sync-chunked-uploadfrom
ccampbell/sync-live-session

Conversation

@ccampbell-aai

@ccampbell-aai ccampbell-aai commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #241, which now carries the transcribe_live() rename and README examples. This PR adds only the push-style session on top.

Why

transcribe_live() takes an iterator, but most live audio arrives in a callback: sounddevice, PyAudio, WebRTC tracks, telephony media streams, websocket frame handlers. Every such caller had to build the same bridge before getting a transcript: a queue, a stop flag, a drain generator and, on the sync side, a thread to run the blocking call in. That bridge is the same thirty-odd lines in every application, and none of it is the application's job.

config = aai.SyncTranscriptionConfig(sample_rate=16000, channels=1)

with aai.SyncTranscriber() as transcriber:
    with transcriber.open_live(config) as session:
        with sd.RawInputStream(samplerate=16000, channels=1, dtype="int16",
                               callback=lambda data, *_: session.write(bytes(data))):
            input("Recording, press Enter to stop... ")
    print(session.result().text)

What

  • SyncTranscriber.open_live(config) -> LiveSession; AsyncSyncTranscriber.open_live(config) -> AsyncLiveSession. Both exported from assemblyai.sync.v1.
  • The request starts the moment the session opens (on the transcriber's existing worker pool, or as a task on the running loop), so the connection and config part go out while the speaker is still talking.
  • write(chunk) queues audio and never blocks. Thread-safe on the sync side; on the async side it is a plain function so a callback can call it, and must run on the loop thread (loop.call_soon_threadsafe(session.write, chunk) from elsewhere). Chunk types are validated at write() so a str surfaces immediately rather than in result().
  • close() ends the audio; result() waits for the transcript (closing first if needed); abort() drops the request. Context-manager exit closes on success and aborts if the block raised.
  • Both are thin layers over transcribe_live(): no new transport code, same errors, same 120 s cap and silence-abort semantics. The iterator form stays for sources that already are iterators or file objects.

Abort works by having the producer raise a private exception, which propagates out of httpx and drops the connection; abort() swallows it. After close() the upload is already complete, so an abort then waits out the in-flight response (documented).

README now leads the live examples with the session (sounddevice, and a websocket handler for asyncio) and keeps transcribe_live() for iterator and file-object sources.

Validation

  • 528 unit tests pass (19 new); the sync modules also pass on the httpx 0.22 floor. Ruff and mypy clean.
  • Verified live against prod: sync and async sessions fed from a sounddevice capture callback return transcripts identical to transcribe() on the same PCM, with the post-speech wait cut to roughly a third; a mid-upload abort() returns immediately and drops the request.

No version bump: this stacks on #241 and inherits its version, so it carries the skip-version-bump label. The test matrix and version-bump workflows only trigger for PRs into master, so only lint runs here until the base moves.

🤖 Generated with Claude Code

@ccampbell-aai
ccampbell-aai force-pushed the ccampbell/sync-live-session branch from 862d166 to 7fcb51e Compare September 9, 2026 21:17
@ccampbell-aai ccampbell-aai changed the title feat(sync): transcribe_live() rename + push-style open_live() sessions feat(sync): push-style live sessions via open_live() Sep 9, 2026
@ccampbell-aai
ccampbell-aai marked this pull request as ready for review September 9, 2026 21:23
transcribe_live() takes an iterator, but most live audio arrives in a
callback: sounddevice, PyAudio, WebRTC tracks, telephony media streams,
websocket frame handlers. Every such caller had to build the same bridge
(a queue, a stop flag, a drain generator and, on the sync side, a thread
to run the blocking call in) before getting a transcript.

SyncTranscriber.open_live() returns a LiveSession and
AsyncSyncTranscriber.open_live() an AsyncLiveSession. The request starts
the moment the session opens, on the transcriber's worker pool or as a
loop task; write() queues audio without blocking (thread-safe on the sync
side), close() ends it, result() waits for the transcript, and abort()
drops the request. Context-manager exit closes on success and aborts if
the block raised. Chunk types are validated at write() so a str shows up
immediately rather than in result().

Both sessions are thin layers over transcribe_live(): no new transport
code, same errors, same 120 s cap and silence-abort semantics. The
iterator form stays for sources that already are iterators or file
objects.

README leads the live examples with the session now (sounddevice, and a
websocket handler for asyncio), and keeps transcribe_live() for
iterator and file-object sources. 19 tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@ccampbell-aai
ccampbell-aai force-pushed the ccampbell/sync-live-session branch from 780dd73 to 6df7cbf Compare September 10, 2026 18:23
@ccampbell-aai ccampbell-aai added the skip-version-bump PR intentionally ships no release; bypasses the Version Bump Check label Sep 10, 2026
@ccampbell-aai
ccampbell-aai merged commit c67cda5 into master Sep 10, 2026
8 of 9 checks passed
ccampbell-aai added a commit that referenced this pull request Sep 10, 2026
…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>
ccampbell-aai added a commit that referenced this pull request Sep 10, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-version-bump PR intentionally ships no release; bypasses the Version Bump Check

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants