From 1fe904933d01c6fa906db9b07d90837dc762c4f9 Mon Sep 17 00:00:00 2001 From: Tai An Date: Fri, 8 May 2026 09:29:37 -0700 Subject: [PATCH] fix(text-completion): forward proxy x-* headers through OpenAI text-completion path (#27410) `forward_client_headers_to_llm_api` correctly propagated client `x-*` headers to upstream providers for `/v1/chat/completions` and `/v1/embeddings`, but silently dropped them for `/v1/completions` (the OpenAI text-completion route). Two co-located gaps caused this: 1. `litellm/main.py` text-completion dispatch did not pass `headers` to `openai_text_completions.completion(...)` (the chat dispatch already does). 2. `litellm/llms/openai/completion/handler.py` accepted `headers` only for pre/post-call logging and `validate_environment` fallback. The kwarg never reached the OpenAI SDK call site, so user-provided proxy headers were discarded even when the caller passed them. This change: - Forwards `headers` from `litellm.completion()` into the text-completion handler, mirroring the chat-completion call site. - In the handler, tracks caller-supplied headers separately from the auto-generated auth dict produced by `validate_environment(api_key=...)`, and merges them into `data["extra_headers"]` so the OpenAI SDK sends them on the wire. Behavior is unchanged for callers that do not pass `headers`: the existing `validate_environment` fallback still runs for logging, and the SDK's own auth via `api_key=` continues to handle the wire-level Authorization header. --- litellm/llms/openai/completion/handler.py | 10 ++++++++++ litellm/main.py | 1 + 2 files changed, 11 insertions(+) diff --git a/litellm/llms/openai/completion/handler.py b/litellm/llms/openai/completion/handler.py index 1641615126e..3398fc6a41e 100644 --- a/litellm/llms/openai/completion/handler.py +++ b/litellm/llms/openai/completion/handler.py @@ -49,6 +49,11 @@ class OpenAITextCompletion(BaseLLM): headers: Optional[dict] = None, ): try: + # Track caller-provided headers (e.g. proxy-forwarded x-* headers) + # separately so they can be merged into extra_headers for the SDK call + # without being conflated with the auto-generated auth dict from + # validate_environment(). + extra_request_headers = headers if headers is None: headers = self.validate_environment(api_key=api_key) if model is None or messages is None: @@ -67,6 +72,11 @@ class OpenAITextCompletion(BaseLLM): optional_params=optional_params, headers=headers, ) + if extra_request_headers: + data["extra_headers"] = { + **(data.get("extra_headers") or {}), + **extra_request_headers, + } max_retries = data.pop("max_retries", 2) ## LOGGING logging_obj.pre_call( diff --git a/litellm/main.py b/litellm/main.py index 051a82fdd19..14242f01df5 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -2095,6 +2095,7 @@ def completion( # type: ignore # noqa: PLR0915 _response = openai_text_completions.completion( model=model, messages=messages, + headers=headers, model_response=model_response, print_verbose=print_verbose, api_key=api_key,