fix(proxy): stop answering with the literal string "None" on the proxy-server, auth, and health routes - #39540
Open
mateo-berri wants to merge 1 commit into
Conversation
…y-server, auth, and health routes The error objects these routes return defaulted `type` and `param` to the four-character string "None", so a client's error handler matched no known OpenAI type and fell into its generic branch. Route them through the shared openai_error_payload helpers instead, so `type` comes from the carried type or the HTTP status and `param` serializes as JSON null.
Contributor
Greptile SummaryThis PR normalizes proxy, authentication, streaming, assistants, audio, and health-route error payloads so missing parameters serialize as JSON null and missing error types derive from HTTP status.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| litellm/proxy/auth/auth_exception_handler.py | Authentication failures now serialize absent error parameters as null while preserving existing classifications and status codes. |
| litellm/proxy/auth/handle_jwt.py | Unavailable-JWKS errors now expose a nullable parameter without changing authentication behavior. |
| litellm/proxy/health_endpoints/_health_endpoints.py | Health-service exception conversion now uses the shared nullable-parameter normalization helper. |
| litellm/proxy/proxy_server.py | Proxy endpoint and streaming exception paths now derive stable OpenAI error types and nullable parameters through shared helpers. |
| tests/test_litellm/proxy/auth/test_auth_exception_handler.py | Adds serialization checks for HTTP, database-unavailable, and generic authentication failures. |
| tests/test_litellm/proxy/auth/test_handle_jwt.py | Adds coverage for the OpenAI-compatible unavailable-JWKS error payload. |
| tests/test_litellm/proxy/health_endpoints/test_health_endpoints.py | The unknown-service test reaches the modified HTTPException conversion branch and verifies a null parameter. |
| tests/test_litellm/proxy/test_proxy_server.py | Adds route-level coverage for assistants error type and parameter serialization. |
Reviews (2): Last reviewed commit: "fix(proxy): stop answering with the lite..." | Re-trigger Greptile
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Open
6 tasks
Contributor
Author
9 tasks
This was referenced Sep 3, 2026
yucheng-berri
approved these changes
Sep 3, 2026
Base automatically changed from
litellm_openai_error_payload_non_llm_routes
to
litellm_internal_staging
September 8, 2026 23:41
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TLDR
Problem this solves:
proxy_server.pyanswers"type": "None"on 63 error sites"param": "None"tooparamis typed nullable, so a string is plain wrong thereHow it solves it:
typefalls back to what the HTTP status stands forparamserializes as JSONnullUser Flow
Before: a developer whose app talks to the gateway gets errors no OpenAI SDK can classify, so every failure on these routes lands in their catch-all branch
POST https://litellm-domain/v1/chat/completionswith noAuthorizationheader{"error":{"message":"Authentication Error, No api key passed in.","type":"auth_error","param":"None","code":"401"}}, so their handler readsparamas the four-character stringNonewhere it expected a JSONnulland renders "problem with field None"POST https://litellm-domain/v1/audio/transcriptionswithmodel=no-such-audio-model"type":"None", so their branch oninvalid_request_errornever fires and the request is reported as an unknown error with no guidanceGET https://litellm-domain/v1/assistants?custom_llm_provider=openaiat a gateway with no assistants provider configured"type":"None"and"param":"None", so their retry logic cannot tell a server fault from a bad requestGET https://litellm-domain/health/services?service=bogus_serviceand get HTTP 400 with a real"type":"auth_error"but"param":"None"againAfter: the same requests come back with a real OpenAI error type and a JSON null param, so the handler they already wrote classifies them
POST https://litellm-domain/v1/chat/completionswith noAuthorizationheader{"error":{"message":"Authentication Error, No api key passed in.","type":"auth_error","param":null,"code":"401"}}, so their handler readsparamasnulland correctly reports that no single field was namedPOST https://litellm-domain/v1/audio/transcriptionswithmodel=no-such-audio-model"type":"invalid_request_error", so their branch fires and the caller sees a "fix your request" messageGET https://litellm-domain/v1/assistants?custom_llm_provider=openaiat a gateway with no assistants provider configured"type":"internal_server_error"and"param":null, so their retry logic can tell a server fault from a bad requestGET https://litellm-domain/health/services?service=bogus_servicestill returns HTTP 400 with"type":"auth_error", now with"param":nullRelevant issues
Linear ticket
Part of LIT-6829
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*,make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more@greptileaito re-request a review after pushing changes)Delays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
Both legs run the same proxy, two uvicorn workers each, against the same Postgres, and differ only in the commit they were booted from.
bede8b5ea4is this PR's merge base, the commit it branches from on #39536;32074cf4deis this PR's tip.Config used on both legs:
Boot on each leg (ports differ,
<port>is 20091 before and 46742 after):Before (bede8b5)
Assistants with no assistants provider configured
typeandparamas the stringNone{"error":{"message":"'custom_llm_provider' must be set. Either via:\n `Router(assistants_config={'custom_llm_provider': ..})` \nor\n `router.arun_thread(custom_llm_provider=..)`","type":"None","param":"None","code":"500"}}Audio transcription with an unknown model
typeandparamas the stringNone{"error":{"message":"{'error': '/audio/transcriptions: Invalid model name passed in model=no-such-audio-model. Call `/v1/models` to view available models for your key.'}","type":"None","param":"None","code":"400"}}Health services with an unknown service
typebutparamas the stringNone{"error":{"message":"{'error': \"Service must be in list. Service=bogus_service not in typing.Union[typing.Literal['slack_budget_alerts', 'langfuse', 'langfuse_otel', 'slack', 'ms_teams', 'openmeter', 'webhook', 'email', 'braintrust', 'datadog', 'datadog_llm_observability', 'generic_api', 'arize', 'galileo', 'newrelic', 'sqs'], str]\"}","type":"auth_error","param":"None","code":"400"}}Chat completion with no API key
paramas the stringNone{"error":{"message":"Authentication Error, No api key passed in.","type":"auth_error","param":"None","code":"401"}}Control: a real chat completion still works
{"id":"chatcmpl-EJyeQ5uib5jz9Y61fpGAHqduCPeVU","created":1788430082,"model":"gpt-4o-mini","object":"chat.completion","choices":[{"finish_reason":"stop","index":0,"message":{"content":"OK!","role":"assistant"}}],"usage":{"completion_tokens":2,"prompt_tokens":9,"total_tokens":11}}After (32074cf)
Assistants with no assistants provider configured
typeand a JSONnullparam{"error":{"message":"'custom_llm_provider' must be set. Either via:\n `Router(assistants_config={'custom_llm_provider': ..})` \nor\n `router.arun_thread(custom_llm_provider=..)`","type":"internal_server_error","param":null,"code":"500"}}Audio transcription with an unknown model
typeand a JSONnullparam{"error":{"message":"{'error': '/audio/transcriptions: Invalid model name passed in model=no-such-audio-model. Call `/v1/models` to view available models for your key.'}","type":"invalid_request_error","param":null,"code":"400"}}Health services with an unknown service
typewith a JSONnullparam{"error":{"message":"{'error': \"Service must be in list. Service=bogus_service not in typing.Union[typing.Literal['slack_budget_alerts', 'langfuse', 'langfuse_otel', 'slack', 'ms_teams', 'openmeter', 'webhook', 'email', 'braintrust', 'datadog', 'datadog_llm_observability', 'generic_api', 'arize', 'galileo', 'newrelic', 'sqs'], str]\"}","type":"auth_error","param":null,"code":"400"}}Chat completion with no API key
nullparam{"error":{"message":"Authentication Error, No api key passed in.","type":"auth_error","param":null,"code":"401"}}Control: a real chat completion still works
{"id":"chatcmpl-EJyhAkxsDATmI2HrPfglA7mjix89e","created":1788430252,"model":"gpt-4o-mini","object":"chat.completion","choices":[{"finish_reason":"stop","index":0,"message":{"content":"OK!","role":"assistant"}}],"usage":{"completion_tokens":2,"prompt_tokens":9,"total_tokens":11}}Observations from the run:
paramwas wrong on more routes thantypewas/v1/assistantsanswers 500 for a caller's config mistakePOST /v1/audio/speechanswers{"detail": ...}, not an error objectType
🐛 Bug Fix
Caveats (if any)
Low
is_premium(), so no run reaches ittest_jwks_unavailable_exception_is_openai_shapedcode=getattr(e, "code", ...)reads left as they wereFinal Attestation