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.
This commit is contained in:
Tai An 2026-05-08 09:29:37 -07:00
parent 98cd057f38
commit 1fe904933d
2 changed files with 11 additions and 0 deletions

View file

@ -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(

View file

@ -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,