Skip to content

feat(sync): make the live connection the only one the client opens - #245

Merged
ccampbell-aai merged 3 commits into
ccampbell/sync-live-sessionfrom
aurpsis/sync-live-only
Sep 10, 2026
Merged

ccampbell-aai merged 3 commits into
ccampbell/sync-live-sessionfrom
aurpsis/sync-live-only

Conversation

@aurpsis-aai

@aurpsis-aai aurpsis-aai commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #244 (ccampbell/sync-live-session), which is itself stacked on #241. This PR adds only the consolidation on top.

What

Every entry point — transcribe(), transcribe_async(), transcribe_live(), open_live() — now posts to /v1/transcribe/live. There is one connection in this client.

Audio that is already complete is a stream whose bytes happen to all be ready, so it goes out over that connection as a single chunk rather than through a separate buffered request. What that buys is one request shape to reason about: one framing, one set of errors, one timeout — and a complete clip reaches the service exactly the way a live capture does, with each speech segment transcribed as it lands instead of after the upload finishes.

The endpoint constant becomes ENDPOINT_TRANSCRIBE_LIVE = "/v1/transcribe/live", following the rename in DeepLearning #20044 (merged and live in production).

The public surface is unchanged

api.transcribe() keeps its exact signature and delegates to transcribe_live(), so the legacy assemblyai.sync_api shim still re-exports it and test_old_sync_api_module_surface_is_preserved still passes. ENDPOINT_TRANSCRIBE stays exported for the same reason, with a comment noting nothing here requests it.

I went this way deliberately rather than deleting the buffered function: that test pins the old flat module's surface as a compatibility contract, and breaking it would be a major-version change for no gain — the goal is one connection, not a smaller import surface.

Note this is a client-side consolidation only. Sync still serves both /v1/transcribe and /v1/transcribe/live, and both stay.

One observable change

An empty config used to mean no config part at all. The live endpoint requires one ahead of the audio, so {} now goes out instead. Anyone inspecting the wire will always see a config part. Two tests asserting the old absence are updated to assert the presence and the ordering.

Complete-audio calls also move from sync_http_timeout to sync_live_http_timeout, whose per-operation shape is the right one for a streamed body. sync_http_timeout still backs the warm-up.

Compressed formats are not a concern here: this client has always been WAV/PCM only (_resolve_audio picks between audio/wav and audio/pcm), which is exactly what the live endpoint accepts.

Validation

  • 531 unit tests pass (528 before, 3 new), re-run against the current base after each rebase. Verified the baseline first: the base branch is 528/528 on the same interpreter, so every failure I saw along the way was mine to fix.
  • New tests pin the invariant: transcribe() and its async twin hit the live URL with no Content-Length and config-before-audio, and one test drives all four entry points and asserts the set of requested URLs is exactly {/v1/transcribe/live} — so a buffered request reappearing anywhere fails.
  • ruff check and ruff format --check clean on assemblyai/; mypy reports zero errors in sync/v1 (the errors elsewhere are pre-existing and identical to the base).

No version bump

The stack lands on master as one merge, so the version is set once at the top rather than per PR — this branch leaves __version__ alone. 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

@aurpsis-aai
aurpsis-aai marked this pull request as ready for review September 10, 2026 18:08
@ccampbell-aai ccampbell-aai added the skip-version-bump PR intentionally ships no release; bypasses the Version Bump Check label Sep 10, 2026
@aurpsis-aai
aurpsis-aai force-pushed the aurpsis/sync-live-only branch from 5c7517c to bd90f46 Compare September 10, 2026 18:19
@aurpsis-aai
aurpsis-aai added this pull request to stack #248 September 10, 2026 18:19
@aurpsis-aai
aurpsis-aai force-pushed the aurpsis/sync-live-only branch from bd90f46 to 076f059 Compare September 10, 2026 18:23
Every entry point — `transcribe()`, `transcribe_async()`,
`transcribe_live()`, `open_live()` — now posts to `/v1/transcribe/live`.
Audio that is already complete is a stream whose bytes happen to all be
ready, so it goes out over the same connection as a single chunk instead of
through a second, buffered request path.

What that buys is one request shape to reason about: one framing, one set of
errors, one timeout, and a complete clip that reaches the service exactly the
way a live capture does — segments transcribed as they land rather than after
the upload finishes.

The public surface is unchanged. `api.transcribe()` keeps its signature and
delegates, so the legacy `assemblyai.sync_api` shim still re-exports it, and
`ENDPOINT_TRANSCRIBE` stays for the same reason with a note that nothing here
requests it. The endpoint constant is renamed to `ENDPOINT_TRANSCRIBE_LIVE`
and points at `/v1/transcribe/live` — the path is also served at
`/v1/transcribe/stream`, which it shipped under.

One observable change: an empty config used to mean no `config` part at all.
The live endpoint requires one ahead of the audio, so `{}` now goes out
instead. Two tests asserting the old absence are updated to assert the
presence and the ordering.

Complete-audio calls now take `sync_live_http_timeout`, whose per-operation
shape fits a streamed body.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V6AVhXhzQoBBgDoVrPBUap
@aurpsis-aai
aurpsis-aai force-pushed the aurpsis/sync-live-only branch from 076f059 to 0811caa Compare September 10, 2026 18:25
ccampbell-aai and others added 2 commits September 10, 2026 13:00
The buffered transcribe() now streams its request body through the live
path, and two tests failed only on the httpx 0.22/0.24 floor envs:

- test_sync_async.py read the recorded request with the synchronous
  request.read(), which asserts the stream is a sync Iterable. A streamed
  async body is not, and the older pytest_httpx does not consume it. Read
  the body in an async mock callback instead, which caches it on the
  recorded request so .read() works on every supported httpx.
- test_the_buffered_endpoint_is_never_requested used
  add_response(is_reusable=True); is_reusable is not in the pytest_httpx
  release pinned for those floors. Queue one response per submission.

Test-only; no product change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The async-callback form of the previous fix does not work on the oldest
supported pytest_httpx: it assigns `is_stream_consumed` to whatever the
callback returned, so a coroutine raises `AttributeError` before the
response is ever used. Reproduced on the httpx 0.22 floor and in CI.

Read the body inside an `httpx.MockTransport` handler instead, which
consumes the request stream the way a real transport does and works on
every supported httpx. This is the approach `test_sync_live.py` already
documents for its own body-shape assertions.

`test_the_buffered_endpoint_is_never_requested` queues one response per
submission for the same reason as before, sized to the four entry points
it drives.

Test-only; no product change. 531 pass on the modern stack and on the
httpx 0.22 floor.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V6AVhXhzQoBBgDoVrPBUap
@aurpsis-aai

Copy link
Copy Markdown
Contributor Author

Heads-up @ccampbell-aai — I pushed 55240d7 on top of your floor fix rather than replacing it, because the async-callback form doesn't survive the oldest pytest_httpx.

On pytest-httpx 0.20 (the pin for the py311-httpx0.22 env), _httpx_mock.py:184 does response.is_stream_consumed = False on whatever the callback returned. An async callback returns a coroutine, so it raises AttributeError: 'coroutine' object has no attribute 'is_stream_consumed' before the response is used. Reproduced both locally and in CI on your commit — run 34517828459, 5 async tests plus 2 "mocked but not requested" from the response queue count.

Mine reads the body inside an httpx.MockTransport handler instead, which consumes the request stream the way a real transport does and needs nothing version-specific — the approach test_sync_live.py's module docstring already recommends for body-shape assertions.

Verified 531 pass on the modern stack and 531 on the httpx 0.22 floor via sdk-test-oldhttpx. Your is_reusable diagnosis was right and is kept, just expressed as a loop sized to the four entry points the test drives.

Happy to take a different shape if you prefer — the constraint is just that it can't rely on pytest_httpx consuming an async body.

@ccampbell-aai
ccampbell-aai merged commit cacceca into master Sep 10, 2026
8 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