fix(proxy): preserve HTTP operations when injecting WebSocket stubs i… - #27244
fix(proxy): preserve HTTP operations when injecting WebSocket stubs i…#27244michelligabriele wants to merge 1 commit into
Conversation
…nto OpenAPI schema
Greptile SummaryFixes a documentation-layer regression (introduced in v1.82.x) where adding
Confidence Score: 5/5Safe to merge — the change is confined to OpenAPI schema generation (a cold, documentation-only path) and cannot affect live request routing. The fix is minimal and surgical: one wholesale dict assignment replaced by setdefault plus a guard, wrapped in a new testable helper. The bug only affected what appeared in /openapi.json and Swagger UI, never actual request dispatch. Four new unit tests directly exercise the regression case and all boundary conditions. No existing tests were weakened, no auth or request paths were touched. No files require special attention.
|
| Filename | Overview |
|---|---|
| litellm/proxy/proxy_server.py | Extracts WebSocket-stub injection into _inject_websocket_stubs_into_openapi_schema; replaces wholesale dict assignment with setdefault+if "get" not in guard so HTTP operations on shared paths are preserved |
| tests/test_litellm/proxy/test_openapi_schema_validation.py | Adds 4 targeted unit tests for the new helper — no real network calls, covers clobber regression, WebSocket-only paths, GET precedence, and router registration sanity check |
Reviews (1): Last reviewed commit: "fix(proxy): preserve HTTP operations whe..." | Re-trigger Greptile
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
🤖 litellm-agent: Merged into staging branch Triage Summary Merge Confidence: 5/5 ✅ READY All checks green. Greptile 5/5, no blocking pattern findings, CircleCI passed. |
Relevant issues
Linear ticket
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
tests/test_litellm/directory, Adding at least 1 test is a hard requirement - see detailsmake test-unit@greptileaiand received a Confidence Score of at least 4/5 before requesting a maintainer reviewDelays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
CI (LiteLLM team)
Branch creation CI run
Link:
CI run for the last commit
Link:
Merge / cherry-pick CI run
Links:
Screenshots / Proof of Fix
Starting v1.82.x,
POST /v1/responsesandPOST /responsesdisappeared from Swagger UI even though both routes still serve traffic.Before the fix — Swagger UI's
responsestag, with the two POST entries missing (onlyPOST /openai/v1/responsesis visible at the top, since it doesn't have a WebSocket twin):After the fix — same view, both

POST /v1/responsesandPOST /responsesrestored alongsidePOST /openai/v1/responses:JSON-level proof from probing
/openapi.jsonbefore and after:The routes themselves were registered and serving traffic in both states — this was a documentation-layer bug, not a routing one.
Type
🐛 Bug Fix
Changes
What was wrong
get_openapi_schema()inlitellm/proxy/proxy_server.pyinjects a synthetic GET stub for every WebSocket route so it shows up in Swagger UI. The injection was a wholesale dict assignment:Before v1.82.x every WebSocket path was unique, so this never bit. v1.82.x added
@router.websocket("/v1/responses")and@router.websocket("/responses")— paths that already had@router.post(...)registered. The wholesale assignment overwrote the existing path entry, dropping the POST operation from the served OpenAPI schema entirely.POST /openai/v1/responsesstayed visible only because it has no WebSocket twin.A vanilla FastAPI app with the same
@app.post(...)+@app.websocket(...)pair on the same path produces a correct OpenAPI schema (FastAPI's ownget_openapi()skips WebSocket routes by design), so the regression was entirely in LiteLLM's custom WebSocket-injection.What this changes
_inject_websocket_stubs_into_openapi_schema(openapi_schema, websocket_routes)so it can be unit-tested in isolation.setdefault(base_path, {})(gets the existing entry or creates an empty one — never clobbers) plus aif "get" not in path_entryguard (skips the synthetic stub if a real GET is already documented). This restores the missing POST entries and also closes the same trap for any future GET-vs-WebSocket path collision.setdefault({})creates a fresh entry, then the same stub gets written.Tests
New
TestWebSocketStubInjectionclass intests/test_litellm/proxy/test_openapi_schema_validation.pywith 4 unit tests:test_websocket_stub_does_not_clobber_existing_post— when a WebSocket route shares a path with a POST, the POST survives and the GET stub is added alongside.test_websocket_stub_added_when_path_is_new— WebSocket-only paths still get a fresh{"get": stub}entry (no behavior change).test_websocket_stub_skipped_when_existing_get— a real GET takes precedence over the synthetic stub (closes the same trap for future GET collisions).test_responses_post_routes_registered_on_router— sanity check that/v1/responses,/responses, and/openai/v1/responsesare still wired with POST on the responses router. Guards against silent removal at the source.Files touched
litellm/proxy/proxy_server.py— extract helper + switch tosetdefaultmergetests/test_litellm/proxy/test_openapi_schema_validation.py— addTestWebSocketStubInjectionclassVerification
tests/test_litellm/proxy/test_openapi_schema_validation.py— 11 passed (7 existing + 4 new)tests/test_litellm/proxy/test_lazy_openapi_snapshot.pyandtests/mcp_tests/test_openapi_spec_path_url.py— passed (adjacent OpenAPI tests, no regressions)make lint-ruff— cleanuv run black— clean/openapi.jsonshows both missing POSTs flip fromNOtoYES; control endpoint and HTTP-route-alive checks unchanged; Swagger UI at/docsrenders the restored entries under theresponsestag (see screenshots above).