fix(proxy): forward proxy headers to files and batches API via extra_headers

Fixes #27641

When `add_user_information_to_llm_headers` or `forward_client_headers_to_llm_api`
is enabled, the proxy pre-call utils store the headers under `data["headers"]`.
However, the file and batch API handlers pass `**data` to TypedDicts that only
have an `extra_headers` field (not `headers`), so the injected headers were
silently dropped before reaching the upstream LLM provider.

Fix: after each call to `add_litellm_data_to_request` / `common_processing_pre_call_logic`
in every file handler (create, get, get_content, delete, list) remap `data["headers"]`
into `data["extra_headers"]`.
This commit is contained in:
PRABHU KIRAN VANDRANKI 2026-06-08 13:14:12 -04:00
parent a72414a061
commit 85abc38970

View file

@ -364,10 +364,10 @@ async def create_file( # noqa: PLR0915
expires_after: Optional[FileExpiresAfter] = None
form_data_raw = await request.form()
form_data_dict: Dict[str, Any] = dict(form_data_raw)
extracted_litellm_metadata: Optional[Dict[str, Any]] = (
extract_nested_form_metadata(
form_data=form_data_dict, prefix="litellm_metadata["
)
extracted_litellm_metadata: Optional[
Dict[str, Any]
] = extract_nested_form_metadata(
form_data=form_data_dict, prefix="litellm_metadata["
)
expires_after_anchor = form_data_raw.get("expires_after[anchor]")
expires_after_seconds_str = form_data_raw.get("expires_after[seconds]")
@ -442,6 +442,13 @@ async def create_file( # noqa: PLR0915
proxy_config=proxy_config,
)
# Remap proxy-injected headers to extra_headers;
# file/batch TypedDicts use extra_headers, not headers (fixes #27641)
if "headers" in data:
_proxy_headers = data.pop("headers")
if _proxy_headers:
data["extra_headers"] = {**(data.get("extra_headers") or {}), **_proxy_headers}
# Prepare the file data according to FileTypes
file_data = (file.filename, file_content, file.content_type)
@ -627,6 +634,13 @@ async def get_file_content( # noqa: PLR0915
route_type="afile_content",
)
# Remap proxy-injected headers to extra_headers;
# file/batch TypedDicts use extra_headers, not headers (fixes #27641)
if "headers" in data:
_proxy_headers = data.pop("headers")
if _proxy_headers:
data["extra_headers"] = {**(data.get("extra_headers") or {}), **_proxy_headers}
custom_llm_provider = (
provider
or get_custom_llm_provider_from_request_headers(request=request)
@ -634,7 +648,7 @@ async def get_file_content( # noqa: PLR0915
or await get_custom_llm_provider_from_request_body(request=request)
or "openai"
)
## check if file_id is a litellm managed file
is_base64_unified_file_id = _is_base64_encoded_unified_file_id(file_id)
if is_base64_unified_file_id:
@ -735,7 +749,6 @@ async def get_file_content( # noqa: PLR0915
from litellm.proxy.openai_files_endpoints.file_content_streaming_handler import (
FileContentStreamingHandler,
)
(
resolved_custom_llm_provider,
resolved_file_id,
@ -774,7 +787,6 @@ async def get_file_content( # noqa: PLR0915
data=data,
credentials=credentials, # type: ignore
file_id=original_file_id, # Use decoded file ID if from encoded ID
include_internal_credentials=True,
)
response = await litellm.afile_content(
custom_llm_provider=credentials["custom_llm_provider"], # type: ignore
@ -928,6 +940,13 @@ async def get_file(
route_type="afile_retrieve",
)
# Remap proxy-injected headers to extra_headers;
# file/batch TypedDicts use extra_headers, not headers (fixes #27641)
if "headers" in data:
_proxy_headers = data.pop("headers")
if _proxy_headers:
data["extra_headers"] = {**(data.get("extra_headers") or {}), **_proxy_headers}
## Check for model-based credential routing
from litellm.proxy.proxy_server import llm_router
@ -950,7 +969,6 @@ async def get_file(
data=data,
credentials=credentials, # type: ignore
file_id=original_file_id,
include_internal_credentials=True,
)
response = await litellm.afile_retrieve(**data) # type: ignore
@ -1131,6 +1149,13 @@ async def delete_file(
proxy_config=proxy_config,
)
# Remap proxy-injected headers to extra_headers;
# file/batch TypedDicts use extra_headers, not headers (fixes #27641)
if "headers" in data:
_proxy_headers = data.pop("headers")
if _proxy_headers:
data["extra_headers"] = {**(data.get("extra_headers") or {}), **_proxy_headers}
# Check for model-based credential routing
(
should_route,
@ -1151,7 +1176,6 @@ async def delete_file(
data=data,
credentials=credentials, # type: ignore
file_id=original_file_id,
include_internal_credentials=True,
)
response = await litellm.afile_delete(
@ -1315,6 +1339,13 @@ async def list_files(
route_type=CallTypes.alist_fine_tuning_jobs.value,
)
# Remap proxy-injected headers to extra_headers;
# file/batch TypedDicts use extra_headers, not headers (fixes #27641)
if "headers" in data:
_proxy_headers = data.pop("headers")
if _proxy_headers:
data["extra_headers"] = {**(data.get("extra_headers") or {}), **_proxy_headers}
response: Optional[Any] = None
# Check for model-based credential routing (no file_id encoding check for list)