feat(e2e): record and replay streamed provider responses chunk-for-chunk - #38136
Conversation
The record/replay harness stored a streamed provider response as one buffered body, so a replayed stream arrived coalesced and the /v1/messages streaming test could not be edge-wired. Keep each SSE transfer chunk in the bundle in the order the provider sent it (a new streamed response shape at BUNDLE_FORMAT_VERSION 4) so replay reproduces the provider's split points, the recorded usage chunk keeps its position, and a mid-stream upstream error replays as the same mid-stream error rather than a clean body. Resolves LIT-5742
Greptile SummaryThe PR extends the E2E provider-edge fixture format to preserve streamed SSE transfer chunks and replay upstream truncation.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| tests/e2e/e2e_http.py | Adds lazy upstream transfer-chunk iteration and represents mid-stream transport failures as terminal truncation steps. |
| tests/e2e/fixture_bundle.py | Introduces the version-4 discriminated streamed-response format and enforces manifest compatibility during loading. |
| tests/e2e/provider_edge.py | Records and replays SSE responses chunk by chunk; the previous delivery-order issue is fixed, while the terminator concern was clarified as outside the capture-completeness contract. |
| tests/e2e/llm_translation/test_messages_e2e.py | Edge-wires the Anthropic streaming test and verifies incremental deltas, usage ordering, and clean completion. |
| tests/e2e/test_provider_edge.py | Adds transfer-level coverage for streamed recording, replay, truncation, downstream closure, and ordinary chunked JSON buffering. |
| tests/e2e/test_fixture_bundle.py | Covers streamed-response serialization and rejection of foreign bundle versions. |
Reviews (3): Last reviewed commit: "fix(e2e): record a streamed chunk only a..." | Re-trigger Greptile
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
A downstream disconnect mid-relay was recording the chunk whose write never landed, so replay would hand back a byte the record run never delivered. Append each chunk after its yield returns, and label the truncation from the generator close, so the recording holds exactly what the proxy received.
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit b2f7216. Configure here.
tin-berri
left a comment
There was a problem hiding this comment.
Approving. chunk_size=None is the whole trick and the docstring says so — one piece per wire chunk is what makes a recording reproduce the provider's split points instead of re-slicing them, and a recording that coalesces the body can't tell you anything about streaming behaviour. Good closure on LIT-5742.
Things I liked:
StreamTruncationas a terminal step rather than a raised exception. A stream that delivered six chunks and then died is a different thing from a request that never streamed, and modelling it as data is what preserves that difference into the bundle.- The
truncatedreason carrying which side ended it (upstream:/downstream:), even though replay treats both identically. That's for the human reading the bundle, and it's the sort of thing that gets dropped and then wished for. StreamHeadas a dataclass rather than aBaseModelwith the reason given (it owns a live socket,stepsis consumed once). Worth the explanation.- Making
load_bundlego through_supported_manifesttoo. Previously onlycheck_freshnesschecked the version, soload_bundlewould happily read a foreign-version bundle and then miss on every call — this is a quiet bug fix riding along, not just plumbing for the bump.
Two things:
1. The v3 → v4 bump invalidates every existing recording. That's the correct call given the stored shape changed, and the error message pointing at E2E_FIXTURE_MODE=record is the right way to land it. Just confirm nothing committed is still on v3 — if any bundle is checked in rather than recorded locally, it needs the re-record in this PR, or replay is hard-red on day one for whoever runs it next.
2. _stream_steps only closes on exhaustion or GC. The finally: resp.close() fires when the generator completes or is closed, so a consumer that abandons steps part way through (a test failing mid-replay, an early break) leaves the response open until collection. Fine at e2e scale and the docstring warns that closing it closes the response — mentioning it so it's a known property rather than a surprise if the edge ever holds many concurrent streams.
code-quality is the recursive_detector red on llm_request_utils.py, which is base drift and gets fixed by #38149.
TLDR
Problem this solves:
/v1/messagesstreaming test couldn't be replayed offlineHow it solves it:
User Flow
Before: a developer trying to certify the streamed
/v1/messagespath offline can't, because a streamed response is stored as one coalesced body and the streaming test always calls the provider liveE2E_FIXTURE_MODE=record), which does POST/model/newto registeranthropic/claude-haiku-4-5, then POST/v1/messageswith"stream": truehttps://api.anthropic.com, so it spends real money in every mode and its response never lands in the recorded fixtureE2E_FIXTURE_MODE=replay) with bogus provider credentials fails: the streaming case still reacheshttps://api.anthropic.comand comes back 401After: the same developer records once against the real provider, then replays offline with bogus credentials and the streamed chunk boundaries reproduce exactly
/model/newnow registers the deployment against the local edge, POST/v1/messageswith"stream": truestreams back, and each SSE transfer chunk is recorded in the order the provider sent itkind: "streamed") with more than one chunk, the provider's real split points, not one coalesced bodyANTHROPIC_API_KEYand run replay mode: POST/v1/messagesis answered from the fixture, replaying the same chunk sequence, so the client sees at least two incrementalcontent_block_deltaevents, theusageevent between the last delta andmessage_stop, and the call spends nothing and never leaves the processRelevant issues
Linear ticket
Resolves LIT-5742
Pre-Submission checklist
Screenshots / Proof of Fix
Shared setup: a proxy booted from this branch on port 4823 with
general_settings.store_model_in_db: true, pointed at a native Postgres (dedicated QA DB) and Redis. The one test under proof registers ananthropic/claude-haiku-4-5deployment via POST/model/newand streams POST/v1/messages.Before (d0da90e)
The streamed
/v1/messagespath can't be recorded or replayed: the harness has no streamed bundle shape, and the streaming test stays on the live provider in every mode.git show d0da90ee6d:tests/e2e/fixture_bundle.py | grep BUNDLE_FORMAT_VERSIONprintsBUNDLE_FORMAT_VERSION: Final = 3, and the recorded-response model there is a single buffered body with no chunk-preserving shape, so a streamed response would be stored coalescedgit show d0da90ee6d:tests/e2e/llm_translation/test_messages_e2e.pyshowstest_messages_streams_completionregistering its deployment with noapi_base, and its own docstring reads "Stays on a live Anthropic deployment in every mode: the edge buffers a streamed response into one body, so chunk fidelity waits on LIT-5742." The assertions only check that somecontent_block_deltaand amessage_stopappear, not that the deltas stayed incrementalgit show d0da90ee6d:tests/e2e/CLAUDE.md | grep -n 5742prints the standing limit: "streaming chunk fidelity is LIT-5742 (a streamed response records as one buffered body)." The After run's bundle isformat_version4, which this version-3 loader refuses outright, so there is no offline streamed replay to capture before this changeAfter (b2f7216)
Records each chunk in order, then replays offline with bogus provider credentials and zero provider calls.
RECORD leg, proxy env holding the real
ANTHROPIC_API_KEY:============================== 1 passed in 16.86s ==============================Inspect the recorded interaction
/tmp/e2e-fixtures-5742-v2/test_messages_streams_completion-09c84255/0000-post-anthropic-v1-messages.json:"kind": "streamed",status_code200,content-type: text/event-stream; charset=utf-8,chunks_b64holds 5 entries (the provider's own split points, not one coalesced body),truncated: null.manifest.jsonformat_versionis 4REPLAY leg, proxy restarted with a bogus
ANTHROPIC_API_KEY=sk-ant-bogus-xxxx, everything else identical:============================== 1 passed in 15.40s ==============================The replay proxy log shows the inbound POST
/v1/messagesreturned 200 with 0 mentions ofapi.anthropic.comand 0 auth errors, and the deployment's storedapi_basewas the local edge (http://127.0.0.1:50526/anthropic), not the real provider. The edge served the recorded chunks, so the call spent nothing and never left the process, even with an invalid Anthropic keyType
🆕 New Feature
✅ Test
Caveats (if any)
QA runbook
tests/e2e/llm_translation/test_messages_e2e.py::TestAnthropicMessages::test_messages_streams_completion- a streamed/v1/messagesresponse records chunk-by-chunk and replays with the same split points, staying incremental rather than coalescingstore_model_in_db: trueand a Postgres/Redis it can reach, plus a realANTHROPIC_API_KEYE2E_FIXTURE_MODE=record E2E_FIXTURE_DIR=/tmp/e2e-fixtures uv run pytest tests/e2e/llm_translation/test_messages_e2e.py::TestAnthropicMessages::test_messages_streams_completion -v/tmp/e2e-fixtures/<slug>/: expect"kind": "streamed"with more than one entry inchunks_b64andtruncatednullANTHROPIC_API_KEY, then replay:E2E_FIXTURE_MODE=replay E2E_FIXTURE_DIR=/tmp/e2e-fixtures uv run pytest ...::test_messages_streams_completion -vand expect it to pass with zero provider callscontent_block_deltaand usage between the last delta andmessage_stop), so a replay that coalesced the body would fail it, not pass hand-wavilyFinal Attestation
The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR
b2f7216 passes /live-pr-risk
Note
Low Risk
Scope is limited to e2e fixture format, provider-edge relay logic, and tests; no production gateway or auth paths are modified.
Overview
E2e record/replay no longer collapses
text/event-streamprovider responses into one buffered body. Fixture bundles bump to format version 4 with a discriminatedkind: streamedshape (chunks_b64plus optional truncation metadata); older bundles are rejected on load instead of partially replayed.The provider edge relays SSE upstream one HTTP transfer chunk at a time in record and replay (
forward_streamine2e_http.py, chunkedEdgeStreamwrites inprovider_edge.py), so the proxy sees the same split points—including mid-event splits and mid-stream disconnects—as a live provider call.The Anthropic
/v1/messagesstreaming e2e is edge-wired for record/replay, with stricter assertions on incrementalcontent_block_deltaevents and usage ordering. Harness unit tests pin transfer-layer fidelity, bundle round-trips, and format-version gates; docs drop the LIT-5742 streaming limitation.Reviewed by Cursor Bugbot for commit b2f7216. Bugbot is set up for automated code reviews on this repo. Configure here.