fix(realtime): drop OpenAI-Beta header rejected by GA Realtime API#1516
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThe upstream WebSocket proxy no longer sends the ChangesRealtime WebSocket tests & proxy updates
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Code Review
This pull request removes the "OpenAI-Beta: realtime=v1" header from the WebSocket connection headers in both the end-to-end tests and the proxy implementation. This change is necessary because OpenAI's GA Realtime API now rejects the beta header with a "beta_api_shape_disabled" error. I have no feedback to provide as there were no review comments.
| input_cfg = audio.get("input") or {} | ||
| assert isinstance(input_cfg.get("turn_detection"), (dict, type(None))) |
There was a problem hiding this comment.
🟡 Nit: Unlike audio.output (where the isinstance(voice, str) assertion catches a missing key), a missing audio.input passes silently here — None or {} gives {}, then {}.get("turn_detection") is None, and isinstance(None, (dict, type(None))) is True.
Consider asserting input exists as a dict, mirroring the audio assertion on line 300:
| input_cfg = audio.get("input") or {} | |
| assert isinstance(input_cfg.get("turn_detection"), (dict, type(None))) | |
| input_cfg = audio.get("input") or {} | |
| assert isinstance(input_cfg, dict) and input_cfg, f"Expected session.audio.input dict, got: {input_cfg!r}" | |
| assert isinstance(input_cfg.get("turn_detection"), (dict, type(None))) |
| @@ -333,12 +355,14 @@ async def _run(): | |||
| asyncio.run(_run()) | |||
|
|
|||
| def test_response_text_delta_format(self, ws_url, ws_headers): | |||
There was a problem hiding this comment.
🟡 Nit: Method name still says text_delta but the event migrated to output_text.delta. Consider renaming for grep-ability:
| def test_response_text_delta_format(self, ws_url, ws_headers): | |
| def test_response_output_text_delta_format(self, ws_url, ws_headers): |
OpenAI's GA Realtime API now rejects `OpenAI-Beta: realtime=v1` with
`beta_api_shape_disabled` ("The Realtime Beta API is no longer
supported. Please use /v1/realtime for the GA API."). The upstream
accepts the WebSocket upgrade (HTTP 101), then immediately sends an
error frame and closes — which the test sees as a 1005 close / TCP RST.
This is the actual cause of the openai-realtime E2E failures that #1504
only partially addressed: bumping the model to the GA alias was
necessary but not sufficient, because the hardcoded beta header on
the upstream connection still triggers GA-shape rejection.
Remove the beta header from the upstream request in proxy.rs and from
the test's ws_headers fixture (the test header was never proxied
upstream anyway, but is no longer meaningful).
Signed-off-by: key4ng <rukeyang@gmail.com>
After dropping the `OpenAI-Beta: realtime=v1` header (so the upstream
WS now negotiates the GA Realtime API), the post-connect tests started
timing out / asserting wrong fields because they were still using beta
event shapes that GA renamed or relocated.
Confirmed from the GA `session.created` payload OpenAI now returns
(captured in the failing CI log):
{
"type": "realtime", "object": "realtime.session",
"model": "gpt-realtime",
"output_modalities": ["audio"],
"audio": {
"input": {"turn_detection": {...}, ...},
"output": {"voice": "alloy", ...}
},
...
}
Migrations applied:
- `session.update` payload: send `{"type": "realtime",
"output_modalities": ["text"]}` instead of `{"modalities": ["text"]}`
- `response.create` params: `output_modalities` instead of `modalities`
- Streaming delta event: `response.output_text.delta` instead of the
beta `response.text.delta`
- `response.done` output content type: `output_text` instead of `text`
- `session.created` schema check: `output_modalities` at the top, plus
`audio.input.turn_detection` and `audio.output.voice` (formerly
`session.turn_detection` and `session.voice`)
The 3 tests that did not use the realtime shape (basic connect,
invalid-event, missing-model, missing-auth) were already passing and
are untouched. The `OPENAI-Beta` header removal in proxy.rs is what
unblocked the connection; this commit makes the rest of the suite
match the GA wire format.
Signed-off-by: key4ng <rukeyang@gmail.com>
GA renamed `conversation.item.created` to `conversation.item.added` (emitted when an item is added to the default conversation; the old name is now a legacy event the server no longer sends for plain `conversation.item.create` requests). The previous run timed out 30s waiting for the old event name. Switch to the new name to match the GA wire format. Signed-off-by: key4ng <rukeyang@gmail.com>
5694628 to
cab68e7
Compare
|
https://github.com/lightseekorg/smg/actions/runs/26249385768/job/77256411786?pr=1516 |
Description
Problem
After #1504 bumped the realtime test to the GA model alias
gpt-realtime, theopenai-realtimejob is still failing on main with the same1005 (no status received)/ConnectionResetErrorpattern (run 26212745181).Direct upstream reproduction (forcing HTTP/1.1 so the WS upgrade isn't masked by HTTP/2 framing) shows what's actually happening:
OpenAI accepts the WebSocket upgrade (HTTP 101), then immediately sends an error event and closes the connection because the request carries
OpenAI-Beta: realtime=v1. SMG forwards that error frame, the upstream tears down, the upgrade closure ends, and the already-upgraded client socket gets dropped → client sees1005/ TCP RST.Repeating the same handshake without the beta header succeeds and returns a proper GA
session.created:{"type":"session.created","session":{"id":"sess_…","model":"gpt-realtime","object":"realtime.session", …}}So bumping the model in #1504 was necessary (the old preview snapshot 404s) but not sufficient (SMG's hardcoded beta header still triggers GA-shape rejection).
Solution
Drop the hardcoded
OpenAI-Beta: realtime=v1header from SMG's upstream WebSocket request. The GA Realtime API doesn't want it; the beta API that did want it has been retired.Changes
model_gateway/src/routers/openai/realtime/proxy.rs— remove the.insert("OpenAI-Beta", "realtime=v1")on the upstreamtokio_tungsteniterequest. Leave a short comment documenting why, so nobody re-adds it.e2e_test/realtime/test_realtime_ws.py— dropOpenAI-Betafrom the test'sws_headersfixture for cleanliness (the test sends it client→SMG, but SMG never proxies client headers upstream, so it was already inert).Test Plan
openai-realtimejob goes green.test_missing_model_returns_error,test_missing_auth_returns_error) remain passing.Direct verification with the upstream (already done locally):
OpenAI-Beta: realtime=v1→code: beta_api_shape_disabled, then close.session.createdframe with the full GA session object (modelgpt-realtime, output_modalities["audio"], server_vad turn detection, etc.).Checklist
cargo +nightly fmt --all -- --checkpassescargo check -p smgpassescargo clippy --all-targets --all-features -- -D warnings(relying on CI)Summary by CodeRabbit
Bug Fixes
Tests