mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-06 02:48:13 +00:00
Merge 57d2be2926 into b781d157d7
This commit is contained in:
commit
40a2eee88c
13 changed files with 606 additions and 0 deletions
|
|
@ -293,6 +293,7 @@ def create_batch(
|
|||
or os.getenv("OPENAI_ORGANIZATION", None)
|
||||
or None # default - https://github.com/openai/openai-python/blob/284c1799070c723c6a553337134148a7ab088dd8/openai/util.py#L105
|
||||
)
|
||||
project: Final = optional_params.project or os.getenv("OPENAI_PROJECT", None) or None
|
||||
# set API KEY
|
||||
api_key = (
|
||||
optional_params.api_key
|
||||
|
|
@ -305,6 +306,7 @@ def create_batch(
|
|||
api_base=api_base,
|
||||
api_key=api_key,
|
||||
organization=organization,
|
||||
project=project,
|
||||
create_batch_data=_create_batch_request,
|
||||
timeout=timeout,
|
||||
max_retries=optional_params.max_retries,
|
||||
|
|
@ -455,6 +457,7 @@ def _handle_retrieve_batch_providers_without_provider_config(
|
|||
or os.getenv("OPENAI_ORGANIZATION", None)
|
||||
or None # default - https://github.com/openai/openai-python/blob/284c1799070c723c6a553337134148a7ab088dd8/openai/util.py#L105
|
||||
)
|
||||
project: Final = optional_params.project or os.getenv("OPENAI_PROJECT", None) or None
|
||||
# set API KEY
|
||||
api_key = (
|
||||
optional_params.api_key
|
||||
|
|
@ -469,6 +472,7 @@ def _handle_retrieve_batch_providers_without_provider_config(
|
|||
api_base=api_base,
|
||||
api_key=api_key,
|
||||
organization=organization,
|
||||
project=project,
|
||||
timeout=timeout,
|
||||
max_retries=optional_params.max_retries,
|
||||
)
|
||||
|
|
@ -807,6 +811,7 @@ def list_batches(
|
|||
or os.getenv("OPENAI_ORGANIZATION", None)
|
||||
or None # default - https://github.com/openai/openai-python/blob/284c1799070c723c6a553337134148a7ab088dd8/openai/util.py#L105
|
||||
)
|
||||
project: Final = optional_params.project or os.getenv("OPENAI_PROJECT", None) or None
|
||||
|
||||
response = openai_batches_instance.list_batches(
|
||||
_is_async=_is_async,
|
||||
|
|
@ -815,6 +820,7 @@ def list_batches(
|
|||
api_base=api_base,
|
||||
api_key=api_key,
|
||||
organization=organization,
|
||||
project=project,
|
||||
timeout=timeout,
|
||||
max_retries=optional_params.max_retries,
|
||||
)
|
||||
|
|
@ -1004,6 +1010,7 @@ def cancel_batch(
|
|||
organization: Final = (
|
||||
optional_params.organization or litellm.organization or os.getenv("OPENAI_ORGANIZATION", None) or None
|
||||
)
|
||||
project: Final = optional_params.project or os.getenv("OPENAI_PROJECT", None) or None
|
||||
api_key = optional_params.api_key or litellm.api_key or litellm.openai_key or os.getenv("OPENAI_API_KEY")
|
||||
|
||||
response = openai_batches_instance.cancel_batch(
|
||||
|
|
@ -1012,6 +1019,7 @@ def cancel_batch(
|
|||
api_base=api_base,
|
||||
api_key=api_key,
|
||||
organization=organization,
|
||||
project=project,
|
||||
timeout=timeout,
|
||||
max_retries=optional_params.max_retries,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -253,6 +253,7 @@ def create_file(
|
|||
api_base=optional_params.api_base,
|
||||
api_key=optional_params.api_key,
|
||||
organization=optional_params.organization,
|
||||
project=optional_params.project,
|
||||
)
|
||||
response = openai_files_instance.create_file(
|
||||
_is_async=_is_async,
|
||||
|
|
@ -261,6 +262,7 @@ def create_file(
|
|||
timeout=timeout,
|
||||
max_retries=optional_params.max_retries,
|
||||
organization=openai_creds.organization,
|
||||
project=openai_creds.project,
|
||||
create_file_data=_create_file_request,
|
||||
)
|
||||
elif custom_llm_provider == "azure":
|
||||
|
|
@ -374,6 +376,7 @@ def file_retrieve(
|
|||
api_base=optional_params.api_base,
|
||||
api_key=optional_params.api_key,
|
||||
organization=optional_params.organization,
|
||||
project=optional_params.project,
|
||||
)
|
||||
response = openai_files_instance.retrieve_file(
|
||||
file_id=file_id,
|
||||
|
|
@ -383,6 +386,7 @@ def file_retrieve(
|
|||
timeout=timeout,
|
||||
max_retries=optional_params.max_retries,
|
||||
organization=openai_creds.organization,
|
||||
project=openai_creds.project,
|
||||
)
|
||||
elif custom_llm_provider == "azure":
|
||||
azure_creds: Final = get_azure_credentials(
|
||||
|
|
@ -555,6 +559,7 @@ def file_delete(
|
|||
api_base=optional_params.api_base,
|
||||
api_key=optional_params.api_key,
|
||||
organization=optional_params.organization,
|
||||
project=optional_params.project,
|
||||
)
|
||||
response = openai_files_instance.delete_file(
|
||||
file_id=file_id,
|
||||
|
|
@ -564,6 +569,7 @@ def file_delete(
|
|||
timeout=timeout,
|
||||
max_retries=optional_params.max_retries,
|
||||
organization=openai_creds.organization,
|
||||
project=openai_creds.project,
|
||||
)
|
||||
elif custom_llm_provider == "azure":
|
||||
azure_creds: Final = get_azure_credentials(
|
||||
|
|
@ -761,6 +767,7 @@ def file_list(
|
|||
api_base=optional_params.api_base,
|
||||
api_key=optional_params.api_key,
|
||||
organization=optional_params.organization,
|
||||
project=optional_params.project,
|
||||
)
|
||||
response = openai_files_instance.list_files(
|
||||
purpose=purpose,
|
||||
|
|
@ -770,6 +777,7 @@ def file_list(
|
|||
timeout=timeout,
|
||||
max_retries=optional_params.max_retries,
|
||||
organization=openai_creds.organization,
|
||||
project=openai_creds.project,
|
||||
)
|
||||
elif custom_llm_provider == "azure":
|
||||
azure_creds: Final = get_azure_credentials(
|
||||
|
|
@ -958,6 +966,7 @@ def file_content(
|
|||
api_base=optional_params.api_base,
|
||||
api_key=optional_params.api_key,
|
||||
organization=optional_params.organization,
|
||||
project=optional_params.project,
|
||||
)
|
||||
response = openai_files_instance.file_content(
|
||||
_is_async=_is_async,
|
||||
|
|
@ -967,6 +976,7 @@ def file_content(
|
|||
timeout=timeout,
|
||||
max_retries=optional_params.max_retries,
|
||||
organization=openai_creds.organization,
|
||||
project=openai_creds.project,
|
||||
)
|
||||
elif custom_llm_provider == "azure":
|
||||
azure_creds: Final = get_azure_credentials(
|
||||
|
|
@ -1077,6 +1087,7 @@ def file_content_streaming(
|
|||
api_base=optional_params.api_base,
|
||||
api_key=optional_params.api_key,
|
||||
organization=optional_params.organization,
|
||||
project=optional_params.project,
|
||||
)
|
||||
response = openai_files_instance.file_content_streaming(
|
||||
_is_async=_is_async,
|
||||
|
|
@ -1086,6 +1097,7 @@ def file_content_streaming(
|
|||
timeout=timeout,
|
||||
max_retries=optional_params.max_retries,
|
||||
organization=openai_creds.organization,
|
||||
project=openai_creds.project,
|
||||
chunk_size=chunk_size,
|
||||
client=client if isinstance(client, (OpenAI, AsyncOpenAI)) else None,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -353,12 +353,14 @@ class OpenAICredentials(NamedTuple):
|
|||
api_base: str
|
||||
api_key: str | None
|
||||
organization: str | None
|
||||
project: str | None = None
|
||||
|
||||
|
||||
def get_openai_credentials(
|
||||
api_base: str | None = None,
|
||||
api_key: str | None = None,
|
||||
organization: str | None = None,
|
||||
project: str | None = None,
|
||||
) -> OpenAICredentials:
|
||||
"""Resolve OpenAI credentials from params, litellm globals, and env vars."""
|
||||
resolved_api_base: Final = (
|
||||
|
|
@ -369,9 +371,11 @@ def get_openai_credentials(
|
|||
or "https://api.openai.com/v1"
|
||||
)
|
||||
resolved_organization = organization or litellm.organization or os.getenv("OPENAI_ORGANIZATION", None) or None
|
||||
resolved_project: Final = project or os.getenv("OPENAI_PROJECT", None) or None
|
||||
resolved_api_key: Final = api_key or litellm.api_key or litellm.openai_key or os.getenv("OPENAI_API_KEY")
|
||||
return OpenAICredentials(
|
||||
api_base=resolved_api_base,
|
||||
api_key=resolved_api_key,
|
||||
organization=resolved_organization,
|
||||
project=resolved_project,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1689,6 +1689,7 @@ class OpenAIFilesAPI(BaseLLM):
|
|||
timeout: float | httpx.Timeout,
|
||||
max_retries: int | None,
|
||||
organization: str | None,
|
||||
project: str | None = None,
|
||||
client: OpenAI | AsyncOpenAI | None = None,
|
||||
_is_async: bool = False,
|
||||
) -> OpenAI | AsyncOpenAI | None:
|
||||
|
|
@ -1729,6 +1730,7 @@ class OpenAIFilesAPI(BaseLLM):
|
|||
timeout: float | httpx.Timeout,
|
||||
max_retries: int | None,
|
||||
organization: str | None,
|
||||
project: str | None = None,
|
||||
client: OpenAI | AsyncOpenAI | None = None,
|
||||
) -> OpenAIFileObject | Coroutine[None, None, OpenAIFileObject]:
|
||||
openai_client: Final[OpenAI | AsyncOpenAI | None] = self.get_openai_client(
|
||||
|
|
@ -1737,6 +1739,7 @@ class OpenAIFilesAPI(BaseLLM):
|
|||
timeout=timeout,
|
||||
max_retries=max_retries,
|
||||
organization=organization,
|
||||
project=project,
|
||||
client=client,
|
||||
_is_async=_is_async,
|
||||
)
|
||||
|
|
@ -1771,6 +1774,7 @@ class OpenAIFilesAPI(BaseLLM):
|
|||
timeout: float | httpx.Timeout,
|
||||
max_retries: int | None,
|
||||
organization: str | None,
|
||||
project: str | None = None,
|
||||
client: OpenAI | AsyncOpenAI | None = None,
|
||||
) -> HttpxBinaryResponseContent | Coroutine[None, None, HttpxBinaryResponseContent]:
|
||||
openai_client: Final[OpenAI | AsyncOpenAI | None] = self.get_openai_client(
|
||||
|
|
@ -1779,6 +1783,7 @@ class OpenAIFilesAPI(BaseLLM):
|
|||
timeout=timeout,
|
||||
max_retries=max_retries,
|
||||
organization=organization,
|
||||
project=project,
|
||||
client=client,
|
||||
_is_async=_is_async,
|
||||
)
|
||||
|
|
@ -1835,6 +1840,7 @@ class OpenAIFilesAPI(BaseLLM):
|
|||
timeout: float | httpx.Timeout,
|
||||
max_retries: int | None,
|
||||
organization: str | None,
|
||||
project: str | None = None,
|
||||
chunk_size: int = 1024 * 1024,
|
||||
client: OpenAI | AsyncOpenAI | None = None,
|
||||
) -> FileContentStreamingResult:
|
||||
|
|
@ -1844,6 +1850,7 @@ class OpenAIFilesAPI(BaseLLM):
|
|||
timeout=timeout,
|
||||
max_retries=max_retries,
|
||||
organization=organization,
|
||||
project=project,
|
||||
client=client,
|
||||
_is_async=_is_async,
|
||||
)
|
||||
|
|
@ -1899,6 +1906,7 @@ class OpenAIFilesAPI(BaseLLM):
|
|||
timeout: float | httpx.Timeout,
|
||||
max_retries: int | None,
|
||||
organization: str | None,
|
||||
project: str | None = None,
|
||||
client: OpenAI | AsyncOpenAI | None = None,
|
||||
):
|
||||
openai_client: Final[OpenAI | AsyncOpenAI | None] = self.get_openai_client(
|
||||
|
|
@ -1907,6 +1915,7 @@ class OpenAIFilesAPI(BaseLLM):
|
|||
timeout=timeout,
|
||||
max_retries=max_retries,
|
||||
organization=organization,
|
||||
project=project,
|
||||
client=client,
|
||||
_is_async=_is_async,
|
||||
)
|
||||
|
|
@ -1945,6 +1954,7 @@ class OpenAIFilesAPI(BaseLLM):
|
|||
timeout: float | httpx.Timeout,
|
||||
max_retries: int | None,
|
||||
organization: str | None,
|
||||
project: str | None = None,
|
||||
client: OpenAI | AsyncOpenAI | None = None,
|
||||
):
|
||||
openai_client: Final[OpenAI | AsyncOpenAI | None] = self.get_openai_client(
|
||||
|
|
@ -1953,6 +1963,7 @@ class OpenAIFilesAPI(BaseLLM):
|
|||
timeout=timeout,
|
||||
max_retries=max_retries,
|
||||
organization=organization,
|
||||
project=project,
|
||||
client=client,
|
||||
_is_async=_is_async,
|
||||
)
|
||||
|
|
@ -1993,6 +2004,7 @@ class OpenAIFilesAPI(BaseLLM):
|
|||
timeout: float | httpx.Timeout,
|
||||
max_retries: int | None,
|
||||
organization: str | None,
|
||||
project: str | None = None,
|
||||
purpose: str | None = None,
|
||||
client: OpenAI | AsyncOpenAI | None = None,
|
||||
):
|
||||
|
|
@ -2002,6 +2014,7 @@ class OpenAIFilesAPI(BaseLLM):
|
|||
timeout=timeout,
|
||||
max_retries=max_retries,
|
||||
organization=organization,
|
||||
project=project,
|
||||
client=client,
|
||||
_is_async=_is_async,
|
||||
)
|
||||
|
|
@ -2047,6 +2060,7 @@ class OpenAIBatchesAPI(BaseLLM):
|
|||
timeout: float | httpx.Timeout,
|
||||
max_retries: int | None,
|
||||
organization: str | None,
|
||||
project: str | None = None,
|
||||
client: OpenAI | AsyncOpenAI | None = None,
|
||||
_is_async: bool = False,
|
||||
) -> OpenAI | AsyncOpenAI | None:
|
||||
|
|
@ -2087,6 +2101,7 @@ class OpenAIBatchesAPI(BaseLLM):
|
|||
timeout: float | httpx.Timeout,
|
||||
max_retries: int | None,
|
||||
organization: str | None,
|
||||
project: str | None = None,
|
||||
client: OpenAI | AsyncOpenAI | None = None,
|
||||
) -> LiteLLMBatch | Coroutine[None, None, LiteLLMBatch]:
|
||||
openai_client: Final[OpenAI | AsyncOpenAI | None] = self.get_openai_client(
|
||||
|
|
@ -2095,6 +2110,7 @@ class OpenAIBatchesAPI(BaseLLM):
|
|||
timeout=timeout,
|
||||
max_retries=max_retries,
|
||||
organization=organization,
|
||||
project=project,
|
||||
client=client,
|
||||
_is_async=_is_async,
|
||||
)
|
||||
|
|
@ -2131,6 +2147,7 @@ class OpenAIBatchesAPI(BaseLLM):
|
|||
timeout: float | httpx.Timeout,
|
||||
max_retries: int | None,
|
||||
organization: str | None,
|
||||
project: str | None = None,
|
||||
client: OpenAI | None = None,
|
||||
):
|
||||
openai_client: Final[OpenAI | AsyncOpenAI | None] = self.get_openai_client(
|
||||
|
|
@ -2139,6 +2156,7 @@ class OpenAIBatchesAPI(BaseLLM):
|
|||
timeout=timeout,
|
||||
max_retries=max_retries,
|
||||
organization=organization,
|
||||
project=project,
|
||||
client=client,
|
||||
_is_async=_is_async,
|
||||
)
|
||||
|
|
@ -2174,6 +2192,7 @@ class OpenAIBatchesAPI(BaseLLM):
|
|||
timeout: float | httpx.Timeout,
|
||||
max_retries: int | None,
|
||||
organization: str | None,
|
||||
project: str | None = None,
|
||||
client: OpenAI | None = None,
|
||||
):
|
||||
openai_client: Final[OpenAI | AsyncOpenAI | None] = self.get_openai_client(
|
||||
|
|
@ -2182,6 +2201,7 @@ class OpenAIBatchesAPI(BaseLLM):
|
|||
timeout=timeout,
|
||||
max_retries=max_retries,
|
||||
organization=organization,
|
||||
project=project,
|
||||
client=client,
|
||||
_is_async=_is_async,
|
||||
)
|
||||
|
|
@ -2221,6 +2241,7 @@ class OpenAIBatchesAPI(BaseLLM):
|
|||
timeout: float | httpx.Timeout,
|
||||
max_retries: int | None,
|
||||
organization: str | None,
|
||||
project: str | None = None,
|
||||
after: str | None = None,
|
||||
limit: int | None = None,
|
||||
client: OpenAI | None = None,
|
||||
|
|
@ -2231,6 +2252,7 @@ class OpenAIBatchesAPI(BaseLLM):
|
|||
timeout=timeout,
|
||||
max_retries=max_retries,
|
||||
organization=organization,
|
||||
project=project,
|
||||
client=client,
|
||||
_is_async=_is_async,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ from litellm.proxy.common_request_processing import (
|
|||
from litellm.proxy.common_utils.callback_utils import sanitize_openai_provider_metadata
|
||||
from litellm.proxy.common_utils.http_parsing_utils import _read_request_body
|
||||
from litellm.proxy.common_utils.openai_endpoint_utils import (
|
||||
apply_openai_project_to_data,
|
||||
get_custom_llm_provider_from_request_headers,
|
||||
get_custom_llm_provider_from_request_query,
|
||||
)
|
||||
|
|
@ -268,6 +269,7 @@ async def create_batch(
|
|||
"Request received by LiteLLM:\n%s",
|
||||
json.dumps(data, indent=4),
|
||||
)
|
||||
apply_openai_project_to_data(data=data, request=request, general_settings=general_settings)
|
||||
base_llm_response_processor: Final = ProxyBaseLLMRequestProcessing(data=data)
|
||||
(
|
||||
data,
|
||||
|
|
@ -596,6 +598,7 @@ async def retrieve_batch(
|
|||
proxy_config=proxy_config,
|
||||
route_type="aretrieve_batch",
|
||||
)
|
||||
apply_openai_project_to_data(data=data, request=request, general_settings=general_settings)
|
||||
|
||||
unified_model_id: Final = get_model_id_from_unified_batch_id(unified_batch_id) if unified_batch_id else None
|
||||
if unified_model_id is not None:
|
||||
|
|
@ -899,6 +902,8 @@ async def list_batches(
|
|||
proxy_config=proxy_config,
|
||||
route_type="alist_batches",
|
||||
)
|
||||
data = dict(data) # mutable-ok: the project resolution below drops or sets `project`
|
||||
apply_openai_project_to_data(data=data, request=request, general_settings=general_settings)
|
||||
|
||||
# Try to use managed objects table for listing batches (returns encoded IDs).
|
||||
managed_files_obj: Final = proxy_logging_obj.get_proxy_hook("managed_files")
|
||||
|
|
@ -1093,6 +1098,7 @@ async def cancel_batch(
|
|||
proxy_config=proxy_config,
|
||||
route_type="acancel_batch",
|
||||
)
|
||||
apply_openai_project_to_data(data=data, request=request, general_settings=general_settings)
|
||||
|
||||
# Include original request and headers in the data
|
||||
data = await add_litellm_data_to_request(
|
||||
|
|
|
|||
|
|
@ -79,3 +79,35 @@ def get_custom_llm_provider_from_request_headers(request: Request) -> str | None
|
|||
if "custom-llm-provider" in request.headers:
|
||||
return request.headers["custom-llm-provider"]
|
||||
return None
|
||||
|
||||
|
||||
def apply_openai_project_to_data(
|
||||
data: dict,
|
||||
request: Request,
|
||||
general_settings: dict | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
Resolve the OpenAI `project` for a files/batches request and store it in `data`.
|
||||
|
||||
A caller can ask for a project with the `OpenAI-Project` header or a `project`
|
||||
field in the request body / multipart form. The proxy holds one OpenAI credential
|
||||
that reaches every project under it, so a caller-supplied project is a credential
|
||||
selector: it is only forwarded when the admin opted in with
|
||||
`general_settings: forward_openai_project: true`, mirroring `forward_openai_org_id`.
|
||||
Without the opt-in both inputs are dropped and the deployment credential or the
|
||||
server-side `OPENAI_PROJECT` keeps control.
|
||||
|
||||
Every files/batches handler calls this once so a file or batch created in a project
|
||||
can still be retrieved, listed, cancelled or deleted through the proxy afterwards.
|
||||
"""
|
||||
if not isinstance(general_settings, dict) or general_settings.get("forward_openai_project") is not True:
|
||||
data.pop("project", None)
|
||||
return
|
||||
header_project: Final[str | None] = request.headers.get("OpenAI-Project")
|
||||
body_value: Final[object] = data.get("project")
|
||||
body_project: Final[str | None] = body_value if isinstance(body_value, str) else None
|
||||
resolved_project: Final[str | None] = header_project or body_project or None
|
||||
if resolved_project is None:
|
||||
data.pop("project", None)
|
||||
return
|
||||
data["project"] = resolved_project
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ from litellm.proxy.common_utils.http_parsing_utils import (
|
|||
extract_nested_form_metadata,
|
||||
)
|
||||
from litellm.proxy.common_utils.openai_endpoint_utils import (
|
||||
apply_openai_project_to_data,
|
||||
get_custom_llm_provider_from_request_body,
|
||||
get_custom_llm_provider_from_request_headers,
|
||||
get_custom_llm_provider_from_request_query,
|
||||
|
|
@ -552,6 +553,7 @@ async def create_file(
|
|||
custom_llm_provider: str = Form(default="openai"),
|
||||
file: UploadFile = File(...),
|
||||
litellm_metadata: str | None = Form(default=None),
|
||||
project: str | None = Form(default=None),
|
||||
passthrough: bool = Form(default=False),
|
||||
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
|
||||
):
|
||||
|
|
@ -758,6 +760,10 @@ async def create_file(
|
|||
proxy_config=proxy_config,
|
||||
)
|
||||
|
||||
if project is not None:
|
||||
data["project"] = project
|
||||
apply_openai_project_to_data(data=data, request=request, general_settings=general_settings)
|
||||
|
||||
uploaded_file_info: Final[UploadedFileInfo] = {
|
||||
"filename": file.filename,
|
||||
"content_type": file.content_type,
|
||||
|
|
@ -993,6 +999,7 @@ async def get_file_content(
|
|||
proxy_config=proxy_config,
|
||||
route_type="afile_content",
|
||||
)
|
||||
apply_openai_project_to_data(data=data, request=request, general_settings=general_settings)
|
||||
|
||||
custom_llm_provider: Final = (
|
||||
provider
|
||||
|
|
@ -1306,6 +1313,7 @@ async def get_file(
|
|||
proxy_config=proxy_config,
|
||||
route_type="afile_retrieve",
|
||||
)
|
||||
apply_openai_project_to_data(data=data, request=request, general_settings=general_settings)
|
||||
|
||||
## Check for model-based credential routing
|
||||
from litellm.proxy.proxy_server import llm_router
|
||||
|
|
@ -1512,6 +1520,7 @@ async def delete_file(
|
|||
proxy_config=proxy_config,
|
||||
route_type="afile_delete",
|
||||
)
|
||||
apply_openai_project_to_data(data=data, request=request, general_settings=general_settings)
|
||||
|
||||
# Include original request and headers in the data
|
||||
data = await add_litellm_data_to_request(
|
||||
|
|
@ -1719,6 +1728,7 @@ async def list_files(
|
|||
proxy_config=proxy_config,
|
||||
route_type=CallTypes.alist_fine_tuning_jobs.value,
|
||||
)
|
||||
apply_openai_project_to_data(data=data, request=request, general_settings=general_settings)
|
||||
|
||||
response: Any | None = None
|
||||
|
||||
|
|
|
|||
|
|
@ -393,6 +393,7 @@ class GenericLiteLLMParams(CredentialLiteLLMParams, CustomPricingLiteLLMParams):
|
|||
max_retries: int | None = None
|
||||
drop_params: bool | str | None = None
|
||||
organization: str | None = None # for openai orgs
|
||||
project: str | None = None # for openai projects
|
||||
configurable_clientside_auth_params: CONFIGURABLE_CLIENTSIDE_AUTH_PARAMS = None
|
||||
litellm_credential_name: str | None = None
|
||||
provider_affinity_header: str | None = None
|
||||
|
|
|
|||
169
tests/batches_tests/test_openai_project_passthrough.py
Normal file
169
tests/batches_tests/test_openai_project_passthrough.py
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
# What is this?
|
||||
## Unit tests: OpenAI `project` passthrough for the Batches and Files APIs.
|
||||
## https://github.com/BerriAI/litellm/issues/41803
|
||||
import os
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
import litellm
|
||||
from litellm.llms.openai.common_utils import get_openai_credentials
|
||||
|
||||
|
||||
def test_get_openai_credentials_project_param():
|
||||
creds = get_openai_credentials(api_key="sk-test", organization="org-abc", project="proj-xyz")
|
||||
assert creds.organization == "org-abc"
|
||||
assert creds.project == "proj-xyz"
|
||||
|
||||
|
||||
def test_get_openai_credentials_project_env_fallback(monkeypatch):
|
||||
monkeypatch.setenv("OPENAI_PROJECT", "proj-from-env")
|
||||
creds = get_openai_credentials(api_key="sk-test")
|
||||
assert creds.project == "proj-from-env"
|
||||
|
||||
|
||||
def test_openai_batches_api_create_batch_forwards_project():
|
||||
from litellm.llms.openai.openai import OpenAIBatchesAPI
|
||||
from litellm.types.utils import LiteLLMBatch
|
||||
|
||||
batch_response = LiteLLMBatch.model_validate(
|
||||
{
|
||||
"id": "batch_abc123",
|
||||
"object": "batch",
|
||||
"endpoint": "/v1/chat/completions",
|
||||
"input_file_id": "file-abc123",
|
||||
"status": "validating",
|
||||
"completion_window": "24h",
|
||||
"created_at": 1700000000,
|
||||
}
|
||||
)
|
||||
with patch("litellm.llms.openai.openai.OpenAI") as mock_openai:
|
||||
mock_openai.return_value.batches.create.return_value = batch_response
|
||||
OpenAIBatchesAPI().create_batch(
|
||||
_is_async=False,
|
||||
create_batch_data={
|
||||
"completion_window": "24h",
|
||||
"endpoint": "/v1/chat/completions",
|
||||
"input_file_id": "file-abc123",
|
||||
},
|
||||
api_key="sk-test",
|
||||
api_base=None,
|
||||
timeout=600.0,
|
||||
max_retries=2,
|
||||
organization="org-abc",
|
||||
project="proj-xyz",
|
||||
)
|
||||
|
||||
kwargs = mock_openai.call_args.kwargs
|
||||
assert kwargs["project"] == "proj-xyz"
|
||||
assert kwargs["organization"] == "org-abc"
|
||||
|
||||
|
||||
def test_openai_batches_api_create_batch_omits_project_when_none():
|
||||
from litellm.llms.openai.openai import OpenAIBatchesAPI
|
||||
from litellm.types.utils import LiteLLMBatch
|
||||
|
||||
batch_response = LiteLLMBatch.model_validate(
|
||||
{
|
||||
"id": "batch_abc123",
|
||||
"object": "batch",
|
||||
"endpoint": "/v1/chat/completions",
|
||||
"input_file_id": "file-abc123",
|
||||
"status": "validating",
|
||||
"completion_window": "24h",
|
||||
"created_at": 1700000000,
|
||||
}
|
||||
)
|
||||
with patch("litellm.llms.openai.openai.OpenAI") as mock_openai:
|
||||
mock_openai.return_value.batches.create.return_value = batch_response
|
||||
OpenAIBatchesAPI().create_batch(
|
||||
_is_async=False,
|
||||
create_batch_data={
|
||||
"completion_window": "24h",
|
||||
"endpoint": "/v1/chat/completions",
|
||||
"input_file_id": "file-abc123",
|
||||
},
|
||||
api_key="sk-test",
|
||||
api_base=None,
|
||||
timeout=600.0,
|
||||
max_retries=2,
|
||||
organization=None,
|
||||
project=None,
|
||||
)
|
||||
|
||||
kwargs = mock_openai.call_args.kwargs
|
||||
assert "project" not in kwargs
|
||||
assert "organization" not in kwargs
|
||||
|
||||
|
||||
def test_openai_files_api_retrieve_file_forwards_project():
|
||||
from litellm.llms.openai.openai import OpenAIFilesAPI
|
||||
|
||||
with patch("litellm.llms.openai.openai.OpenAI") as mock_openai:
|
||||
OpenAIFilesAPI().retrieve_file(
|
||||
_is_async=False,
|
||||
file_id="file-abc123",
|
||||
api_base=None,
|
||||
api_key="sk-test",
|
||||
timeout=600.0,
|
||||
max_retries=2,
|
||||
organization=None,
|
||||
project="proj-xyz",
|
||||
)
|
||||
|
||||
kwargs = mock_openai.call_args.kwargs
|
||||
assert kwargs["project"] == "proj-xyz"
|
||||
|
||||
|
||||
def test_litellm_create_batch_project_param(monkeypatch):
|
||||
from litellm.batches import main as batches_main
|
||||
|
||||
monkeypatch.delenv("OPENAI_PROJECT", raising=False)
|
||||
mock_instance = MagicMock()
|
||||
with patch.object(batches_main, "openai_batches_instance", mock_instance):
|
||||
litellm.create_batch(
|
||||
completion_window="24h",
|
||||
endpoint="/v1/chat/completions",
|
||||
input_file_id="file-abc123",
|
||||
custom_llm_provider="openai",
|
||||
api_key="sk-test",
|
||||
project="proj-xyz",
|
||||
)
|
||||
|
||||
kwargs = mock_instance.create_batch.call_args.kwargs
|
||||
assert kwargs["project"] == "proj-xyz"
|
||||
|
||||
|
||||
def test_litellm_create_batch_project_env_fallback(monkeypatch):
|
||||
from litellm.batches import main as batches_main
|
||||
|
||||
monkeypatch.setenv("OPENAI_PROJECT", "proj-from-env")
|
||||
mock_instance = MagicMock()
|
||||
with patch.object(batches_main, "openai_batches_instance", mock_instance):
|
||||
litellm.create_batch(
|
||||
completion_window="24h",
|
||||
endpoint="/v1/chat/completions",
|
||||
input_file_id="file-abc123",
|
||||
custom_llm_provider="openai",
|
||||
api_key="sk-test",
|
||||
)
|
||||
|
||||
kwargs = mock_instance.create_batch.call_args.kwargs
|
||||
assert kwargs["project"] == "proj-from-env"
|
||||
|
||||
|
||||
def test_litellm_file_retrieve_project_param(monkeypatch):
|
||||
from litellm.files import main as files_main
|
||||
|
||||
monkeypatch.delenv("OPENAI_PROJECT", raising=False)
|
||||
mock_instance = MagicMock()
|
||||
with patch.object(files_main, "openai_files_instance", mock_instance):
|
||||
files_main.file_retrieve(
|
||||
file_id="file-abc123",
|
||||
custom_llm_provider="openai",
|
||||
api_key="sk-test",
|
||||
project="proj-xyz",
|
||||
)
|
||||
|
||||
kwargs = mock_instance.retrieve_file.call_args.kwargs
|
||||
assert kwargs["project"] == "proj-xyz"
|
||||
|
|
@ -3269,3 +3269,92 @@ async def test_cancel__executed_batch_rejects_key_without_model_grant(cancel_har
|
|||
factory.assert_not_called()
|
||||
runner.cancel.assert_not_called()
|
||||
cancel_harness.router_acancel.assert_not_called()
|
||||
|
||||
|
||||
# =========================================================================== #
|
||||
# OpenAI `project` forwarding. The project a batch was created in keeps applying
|
||||
# to retrieve/list/cancel, and a caller can only steer it through the
|
||||
# `forward_openai_project` opt-in.
|
||||
# =========================================================================== #
|
||||
|
||||
PROJECT_HEADER = {"OpenAI-Project": "proj_forwarded"}
|
||||
FORWARD_PROJECT_SETTINGS = {"forward_openai_project": True}
|
||||
CREATE_BODY = {
|
||||
"input_file_id": "file-plain",
|
||||
"endpoint": "/v1/chat/completions",
|
||||
"completion_window": "24h",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create__openai_project_header_forwarded_when_opted_in(harness, openai_env_creds, monkeypatch):
|
||||
monkeypatch.setattr(proxy_server, "general_settings", FORWARD_PROJECT_SETTINGS)
|
||||
set_body(harness, CREATE_BODY)
|
||||
|
||||
await call_create(harness, headers=PROJECT_HEADER)
|
||||
|
||||
assert harness.acreate_kwargs()["project"] == "proj_forwarded"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create__openai_project_body_value_forwarded_when_opted_in(harness, openai_env_creds, monkeypatch):
|
||||
monkeypatch.setattr(proxy_server, "general_settings", FORWARD_PROJECT_SETTINGS)
|
||||
set_body(harness, {**CREATE_BODY, "project": "proj_from_body"})
|
||||
|
||||
await call_create(harness)
|
||||
|
||||
assert harness.acreate_kwargs()["project"] == "proj_from_body"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create__openai_project_dropped_without_opt_in(harness, openai_env_creds):
|
||||
set_body(harness, {**CREATE_BODY, "project": "proj_from_body"})
|
||||
|
||||
await call_create(harness, headers=PROJECT_HEADER)
|
||||
|
||||
assert "project" not in harness.acreate_kwargs()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_retrieve__openai_project_header_forwarded_when_opted_in(
|
||||
retrieve_harness, openai_env_creds, monkeypatch
|
||||
):
|
||||
"""Regression: creation honored the header, retrieval ignored it, so a batch
|
||||
made inside a project could not be read back through the proxy."""
|
||||
monkeypatch.setattr(proxy_server, "general_settings", FORWARD_PROJECT_SETTINGS)
|
||||
|
||||
await call_retrieve(retrieve_harness, "batch-raw-xyz", headers=PROJECT_HEADER)
|
||||
|
||||
assert retrieve_harness.aretrieve_kwargs()["project"] == "proj_forwarded"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_retrieve__openai_project_dropped_without_opt_in(retrieve_harness, openai_env_creds):
|
||||
await call_retrieve(retrieve_harness, "batch-raw-xyz", headers=PROJECT_HEADER)
|
||||
|
||||
assert "project" not in retrieve_harness.aretrieve_kwargs()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list__openai_project_header_forwarded_when_opted_in(list_harness, monkeypatch):
|
||||
monkeypatch.setattr(proxy_server, "general_settings", FORWARD_PROJECT_SETTINGS)
|
||||
|
||||
await call_list(list_harness, headers=PROJECT_HEADER)
|
||||
|
||||
assert list_harness.alist_kwargs()["project"] == "proj_forwarded"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list__openai_project_dropped_without_opt_in(list_harness):
|
||||
await call_list(list_harness, headers=PROJECT_HEADER)
|
||||
|
||||
assert "project" not in list_harness.alist_kwargs()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cancel__openai_project_header_forwarded_when_opted_in(cancel_harness, openai_env_creds, monkeypatch):
|
||||
monkeypatch.setattr(proxy_server, "general_settings", FORWARD_PROJECT_SETTINGS)
|
||||
|
||||
await call_cancel(cancel_harness, "batch-raw-xyz", headers=PROJECT_HEADER)
|
||||
|
||||
assert cancel_harness.acancel_kwargs()["project"] == "proj_forwarded"
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import copy
|
|||
import pytest
|
||||
|
||||
from litellm.proxy.common_utils.openai_endpoint_utils import (
|
||||
apply_openai_project_to_data,
|
||||
remove_sensitive_info_from_deployment,
|
||||
)
|
||||
|
||||
|
|
@ -121,3 +122,84 @@ def test_remove_sensitive_info_from_deployment_with_excluded_keys():
|
|||
|
||||
# api_key should still be removed (popped) regardless of excluded_keys
|
||||
assert "api_key" not in sanitized_config["litellm_params"]
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# apply_openai_project_to_data: a caller-supplied OpenAI project selects which
|
||||
# project the proxy's shared credential acts inside, so it only reaches the
|
||||
# provider once the admin opted in with `forward_openai_project`.
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
FORWARDED: dict = {"forward_openai_project": True}
|
||||
|
||||
|
||||
class _FakeRequest:
|
||||
"""Stand-in for the FastAPI request; the helper only reads `.headers`."""
|
||||
|
||||
def __init__(self, headers: dict | None = None):
|
||||
self.headers = headers or {}
|
||||
|
||||
|
||||
def test_openai_project_is_dropped_without_the_forwarding_opt_in():
|
||||
data = {"project": "proj_from_client"}
|
||||
|
||||
apply_openai_project_to_data(
|
||||
data=data,
|
||||
request=_FakeRequest({"OpenAI-Project": "proj_from_header"}),
|
||||
general_settings={},
|
||||
)
|
||||
|
||||
assert "project" not in data
|
||||
|
||||
|
||||
def test_openai_project_is_dropped_when_general_settings_is_missing():
|
||||
data = {"project": "proj_from_client"}
|
||||
|
||||
apply_openai_project_to_data(data=data, request=_FakeRequest())
|
||||
|
||||
assert "project" not in data
|
||||
|
||||
|
||||
def test_openai_project_header_is_forwarded_when_opted_in():
|
||||
data: dict = {}
|
||||
|
||||
apply_openai_project_to_data(data=data, request=_FakeRequest({"OpenAI-Project": "proj_a"}), general_settings=FORWARDED)
|
||||
|
||||
assert data["project"] == "proj_a"
|
||||
|
||||
|
||||
def test_openai_project_header_wins_over_the_request_body_value():
|
||||
data = {"project": "proj_from_body"}
|
||||
|
||||
apply_openai_project_to_data(
|
||||
data=data,
|
||||
request=_FakeRequest({"OpenAI-Project": "proj_from_header"}),
|
||||
general_settings=FORWARDED,
|
||||
)
|
||||
|
||||
assert data["project"] == "proj_from_header"
|
||||
|
||||
|
||||
def test_openai_project_body_value_is_forwarded_when_opted_in_without_header():
|
||||
data = {"project": "proj_from_body"}
|
||||
|
||||
apply_openai_project_to_data(data=data, request=_FakeRequest(), general_settings=FORWARDED)
|
||||
|
||||
assert data["project"] == "proj_from_body"
|
||||
|
||||
|
||||
def test_openai_project_ignores_a_non_string_body_value():
|
||||
data = {"project": {"nested": "value"}}
|
||||
|
||||
apply_openai_project_to_data(data=data, request=_FakeRequest(), general_settings=FORWARDED)
|
||||
|
||||
assert "project" not in data
|
||||
|
||||
|
||||
def test_openai_project_stays_absent_when_no_caller_supplied_one():
|
||||
data: dict = {"batch_id": "batch-1"}
|
||||
|
||||
apply_openai_project_to_data(data=data, request=_FakeRequest(), general_settings=FORWARDED)
|
||||
|
||||
assert "project" not in data
|
||||
assert data == {"batch_id": "batch-1"}
|
||||
|
|
|
|||
|
|
@ -5907,3 +5907,164 @@ def test_create_file_passthrough_fails_closed_when_guardrails_would_scan_the_bat
|
|||
assert error["param"] == "passthrough"
|
||||
assert "guardrails" in error["message"]
|
||||
assert forwarded_calls == []
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# OpenAI `project` forwarding on /v1/files: the form field and the header must
|
||||
# reach the provider only through the `forward_openai_project` opt-in, and every
|
||||
# endpoint of the file lifecycle must resolve the same project.
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
FILE_UPLOAD_CONTENT = b'{"prompt": "Hello", "completion": "Hi"}'
|
||||
|
||||
|
||||
def _stub_create_file_route(monkeypatch, captured: dict) -> None:
|
||||
"""Replace the dispatch step so the test can read the forwarded request."""
|
||||
import litellm.proxy.proxy_server as ps
|
||||
from litellm.proxy._types import LitellmUserRoles
|
||||
from litellm.proxy.openai_files_endpoints import files_endpoints as fe
|
||||
from litellm.types.llms.openai import OpenAIFileObject
|
||||
|
||||
async def fake_route_create_file(*, _create_file_request, **kwargs):
|
||||
captured["request"] = dict(_create_file_request)
|
||||
return OpenAIFileObject(
|
||||
id="file-abc123",
|
||||
object="file",
|
||||
bytes=len(FILE_UPLOAD_CONTENT),
|
||||
created_at=1234567890,
|
||||
filename="data.jsonl",
|
||||
purpose="user_data",
|
||||
status="uploaded",
|
||||
)
|
||||
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.master_key", None)
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", None)
|
||||
monkeypatch.setattr(fe, "route_create_file", fake_route_create_file)
|
||||
app.dependency_overrides[ps.user_api_key_auth] = lambda: UserAPIKeyAuth(
|
||||
user_role=LitellmUserRoles.PROXY_ADMIN, user_id="test-user"
|
||||
)
|
||||
|
||||
|
||||
def _upload_file(form: dict, headers: dict):
|
||||
return client.post(
|
||||
"/v1/files",
|
||||
files={"file": ("data.jsonl", FILE_UPLOAD_CONTENT, "application/json")},
|
||||
data={"purpose": "user_data", **form},
|
||||
headers={"Authorization": "Bearer test-key", **headers},
|
||||
)
|
||||
|
||||
|
||||
def test_create_file_project_form_field_forwarded_when_opted_in(monkeypatch, llm_router: Router):
|
||||
import litellm.proxy.proxy_server as ps
|
||||
|
||||
captured: dict = {}
|
||||
_stub_create_file_route(monkeypatch, captured)
|
||||
monkeypatch.setattr(ps, "general_settings", {"forward_openai_project": True})
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", llm_router)
|
||||
setup_proxy_logging_object(monkeypatch, llm_router)
|
||||
|
||||
try:
|
||||
response = _upload_file({"project": "proj_from_form"}, {})
|
||||
assert response.status_code == 200, response.text
|
||||
assert captured["request"]["project"] == "proj_from_form"
|
||||
|
||||
captured.clear()
|
||||
response = _upload_file({"project": "proj_from_form"}, {"OpenAI-Project": "proj_from_header"})
|
||||
assert response.status_code == 200, response.text
|
||||
assert captured["request"]["project"] == "proj_from_header"
|
||||
finally:
|
||||
app.dependency_overrides.pop(ps.user_api_key_auth, None)
|
||||
|
||||
|
||||
def test_create_file_drops_client_project_without_opt_in(monkeypatch, llm_router: Router):
|
||||
import litellm.proxy.proxy_server as ps
|
||||
|
||||
captured: dict = {}
|
||||
_stub_create_file_route(monkeypatch, captured)
|
||||
monkeypatch.setattr(ps, "general_settings", {})
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", llm_router)
|
||||
setup_proxy_logging_object(monkeypatch, llm_router)
|
||||
|
||||
try:
|
||||
response = _upload_file({"project": "proj_from_form"}, {"OpenAI-Project": "proj_from_header"})
|
||||
assert response.status_code == 200, response.text
|
||||
assert "project" not in captured["request"]
|
||||
finally:
|
||||
app.dependency_overrides.pop(ps.user_api_key_auth, None)
|
||||
|
||||
|
||||
def test_retrieve_file_forwards_project_header_when_opted_in(monkeypatch, llm_router: Router):
|
||||
"""A file uploaded inside a project is only readable with that project."""
|
||||
import litellm.proxy.proxy_server as ps
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from litellm.proxy._types import LitellmUserRoles
|
||||
from litellm.types.llms.openai import OpenAIFileObject
|
||||
|
||||
retrieved = OpenAIFileObject(
|
||||
id="file-abc123",
|
||||
object="file",
|
||||
bytes=0,
|
||||
created_at=1234567890,
|
||||
filename="data.jsonl",
|
||||
purpose="user_data",
|
||||
status="uploaded",
|
||||
)
|
||||
afile_retrieve = AsyncMock(return_value=retrieved)
|
||||
monkeypatch.setattr(litellm, "afile_retrieve", afile_retrieve)
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.master_key", None)
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", None)
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", llm_router)
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.general_settings", {"forward_openai_project": True})
|
||||
setup_proxy_logging_object(monkeypatch, llm_router)
|
||||
app.dependency_overrides[ps.user_api_key_auth] = lambda: UserAPIKeyAuth(
|
||||
user_role=LitellmUserRoles.PROXY_ADMIN, user_id="test-user"
|
||||
)
|
||||
|
||||
try:
|
||||
response = client.get(
|
||||
"/v1/files/file-abc123",
|
||||
headers={"Authorization": "Bearer test-key", "OpenAI-Project": "proj_from_header"},
|
||||
)
|
||||
assert response.status_code == 200, response.text
|
||||
assert afile_retrieve.call_args.kwargs["project"] == "proj_from_header"
|
||||
finally:
|
||||
app.dependency_overrides.pop(ps.user_api_key_auth, None)
|
||||
|
||||
|
||||
def test_retrieve_file_drops_project_header_without_opt_in(monkeypatch, llm_router: Router):
|
||||
import litellm.proxy.proxy_server as ps
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from litellm.proxy._types import LitellmUserRoles
|
||||
from litellm.types.llms.openai import OpenAIFileObject
|
||||
|
||||
retrieved = OpenAIFileObject(
|
||||
id="file-abc123",
|
||||
object="file",
|
||||
bytes=0,
|
||||
created_at=1234567890,
|
||||
filename="data.jsonl",
|
||||
purpose="user_data",
|
||||
status="uploaded",
|
||||
)
|
||||
afile_retrieve = AsyncMock(return_value=retrieved)
|
||||
monkeypatch.setattr(litellm, "afile_retrieve", afile_retrieve)
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.master_key", None)
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", None)
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", llm_router)
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.general_settings", {})
|
||||
setup_proxy_logging_object(monkeypatch, llm_router)
|
||||
app.dependency_overrides[ps.user_api_key_auth] = lambda: UserAPIKeyAuth(
|
||||
user_role=LitellmUserRoles.PROXY_ADMIN, user_id="test-user"
|
||||
)
|
||||
|
||||
try:
|
||||
response = client.get(
|
||||
"/v1/files/file-abc123",
|
||||
headers={"Authorization": "Bearer test-key", "OpenAI-Project": "proj_from_header"},
|
||||
)
|
||||
assert response.status_code == 200, response.text
|
||||
assert "project" not in afile_retrieve.call_args.kwargs
|
||||
finally:
|
||||
app.dependency_overrides.pop(ps.user_api_key_auth, None)
|
||||
|
|
|
|||
10
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
10
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
|
|
@ -25805,6 +25805,8 @@ export interface components {
|
|||
* @default false
|
||||
*/
|
||||
passthrough: boolean;
|
||||
/** Project */
|
||||
project?: string | null;
|
||||
/** Purpose */
|
||||
purpose: string;
|
||||
/**
|
||||
|
|
@ -25834,6 +25836,8 @@ export interface components {
|
|||
* @default false
|
||||
*/
|
||||
passthrough: boolean;
|
||||
/** Project */
|
||||
project?: string | null;
|
||||
/** Purpose */
|
||||
purpose: string;
|
||||
/**
|
||||
|
|
@ -25863,6 +25867,8 @@ export interface components {
|
|||
* @default false
|
||||
*/
|
||||
passthrough: boolean;
|
||||
/** Project */
|
||||
project?: string | null;
|
||||
/** Purpose */
|
||||
purpose: string;
|
||||
/**
|
||||
|
|
@ -33287,6 +33293,8 @@ export interface components {
|
|||
output_cost_per_video_token?: number | null;
|
||||
/** Output Vector Size */
|
||||
output_vector_size?: number | null;
|
||||
/** Project */
|
||||
project?: string | null;
|
||||
/** Provider Affinity Header */
|
||||
provider_affinity_header?: string | null;
|
||||
/** Quality Router Config */
|
||||
|
|
@ -47118,6 +47126,8 @@ export interface components {
|
|||
output_cost_per_video_token?: number | null;
|
||||
/** Output Vector Size */
|
||||
output_vector_size?: number | null;
|
||||
/** Project */
|
||||
project?: string | null;
|
||||
/** Provider Affinity Header */
|
||||
provider_affinity_header?: string | null;
|
||||
/** Quality Router Config */
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue