Litellm dev 04 10 2025 p3 (#9903)

* feat(managed_files.py): encode file type in unified file id

simplify calling gemini models

* fix(common_utils.py): fix extracting file type from unified file id

* fix(litellm_logging.py): create standard logging payload for create file call

* fix: fix linting error
This commit is contained in:
Krish Dholakia 2025-04-11 09:29:42 -07:00 committed by GitHub
parent a451fc7cad
commit 9e27d77756
10 changed files with 99 additions and 22 deletions

View file

@ -56,6 +56,7 @@ from litellm.types.llms.openai import (
Batch, Batch,
FineTuningJob, FineTuningJob,
HttpxBinaryResponseContent, HttpxBinaryResponseContent,
OpenAIFileObject,
ResponseCompletedEvent, ResponseCompletedEvent,
ResponsesAPIResponse, ResponsesAPIResponse,
) )
@ -902,6 +903,7 @@ class Logging(LiteLLMLoggingBaseClass):
FineTuningJob, FineTuningJob,
ResponsesAPIResponse, ResponsesAPIResponse,
ResponseCompletedEvent, ResponseCompletedEvent,
OpenAIFileObject,
LiteLLMRealtimeStreamLoggingObject, LiteLLMRealtimeStreamLoggingObject,
], ],
cache_hit: Optional[bool] = None, cache_hit: Optional[bool] = None,
@ -1095,6 +1097,7 @@ class Logging(LiteLLMLoggingBaseClass):
or isinstance(logging_result, FineTuningJob) or isinstance(logging_result, FineTuningJob)
or isinstance(logging_result, LiteLLMBatch) or isinstance(logging_result, LiteLLMBatch)
or isinstance(logging_result, ResponsesAPIResponse) or isinstance(logging_result, ResponsesAPIResponse)
or isinstance(logging_result, OpenAIFileObject)
or isinstance(logging_result, LiteLLMRealtimeStreamLoggingObject) or isinstance(logging_result, LiteLLMRealtimeStreamLoggingObject)
): ):
## HIDDEN PARAMS ## ## HIDDEN PARAMS ##

View file

@ -4,6 +4,7 @@ Common utility functions used for translating messages across providers
import io import io
import mimetypes import mimetypes
import re
from os import PathLike from os import PathLike
from typing import Dict, List, Literal, Mapping, Optional, Union, cast from typing import Dict, List, Literal, Mapping, Optional, Union, cast
@ -18,6 +19,7 @@ from litellm.types.utils import (
ExtractedFileData, ExtractedFileData,
FileTypes, FileTypes,
ModelResponse, ModelResponse,
SpecialEnums,
StreamingChoices, StreamingChoices,
) )
@ -325,6 +327,29 @@ def get_file_ids_from_messages(messages: List[AllMessageValues]) -> List[str]:
return file_ids return file_ids
def get_format_from_file_id(file_id: Optional[str]) -> Optional[str]:
"""
Gets format from file id
unified_file_id = litellm_proxy:{};unified_id,{}
If not a unified file id, returns 'file' as default format
"""
if not file_id:
return None
try:
if file_id.startswith(SpecialEnums.LITELM_MANAGED_FILE_ID_PREFIX.value):
match = re.match(
f"{SpecialEnums.LITELM_MANAGED_FILE_ID_PREFIX.value}:(.*?);unified_id",
file_id,
)
if match:
return match.group(1)
return None
except Exception:
return None
def update_messages_with_model_file_ids( def update_messages_with_model_file_ids(
messages: List[AllMessageValues], messages: List[AllMessageValues],
model_id: str, model_id: str,
@ -350,12 +375,18 @@ def update_messages_with_model_file_ids(
file_object = cast(ChatCompletionFileObject, c) file_object = cast(ChatCompletionFileObject, c)
file_object_file_field = file_object["file"] file_object_file_field = file_object["file"]
file_id = file_object_file_field.get("file_id") file_id = file_object_file_field.get("file_id")
format = file_object_file_field.get(
"format", get_format_from_file_id(file_id)
)
if file_id: if file_id:
provider_file_id = ( provider_file_id = (
model_file_id_mapping.get(file_id, {}).get(model_id) model_file_id_mapping.get(file_id, {}).get(model_id)
or file_id or file_id
) )
file_object_file_field["file_id"] = provider_file_id file_object_file_field["file_id"] = provider_file_id
if format:
file_object_file_field["format"] = format
return messages return messages
@ -421,6 +452,7 @@ def extract_file_data(file_data: FileTypes) -> ExtractedFileData:
headers=file_headers, headers=file_headers,
) )
def unpack_defs(schema, defs): def unpack_defs(schema, defs):
properties = schema.get("properties", None) properties = schema.get("properties", None)
if properties is None: if properties is None:
@ -452,4 +484,3 @@ def unpack_defs(schema, defs):
unpack_defs(ref, defs) unpack_defs(ref, defs)
value["items"] = ref value["items"] = ref
continue continue

View file

@ -11,10 +11,6 @@ model_list:
- model_name: "bedrock-nova" - model_name: "bedrock-nova"
litellm_params: litellm_params:
model: us.amazon.nova-pro-v1:0 model: us.amazon.nova-pro-v1:0
- model_name: "gemini-2.0-flash"
litellm_params:
model: gemini/gemini-2.0-flash
api_key: os.environ/GEMINI_API_KEY
- model_name: openrouter_model - model_name: openrouter_model
litellm_params: litellm_params:
model: openrouter/openrouter_model model: openrouter/openrouter_model
@ -33,6 +29,7 @@ model_list:
model_info: model_info:
base_model: azure/gpt-4o-realtime-preview-2024-10-01 base_model: azure/gpt-4o-realtime-preview-2024-10-01
litellm_settings: litellm_settings:
num_retries: 0 num_retries: 0
callbacks: ["prometheus"] callbacks: ["prometheus"]

View file

@ -2692,10 +2692,6 @@ class PrismaCompatibleUpdateDBModel(TypedDict, total=False):
updated_by: str updated_by: str
class SpecialEnums(enum.Enum):
LITELM_MANAGED_FILE_ID_PREFIX = "litellm_proxy/"
class SpecialManagementEndpointEnums(enum.Enum): class SpecialManagementEndpointEnums(enum.Enum):
DEFAULT_ORGANIZATION = "default_organization" DEFAULT_ORGANIZATION = "default_organization"

View file

@ -9,10 +9,16 @@ from litellm import verbose_logger
from litellm.caching.caching import DualCache from litellm.caching.caching import DualCache
from litellm.integrations.custom_logger import CustomLogger from litellm.integrations.custom_logger import CustomLogger
from litellm.litellm_core_utils.prompt_templates.common_utils import ( from litellm.litellm_core_utils.prompt_templates.common_utils import (
extract_file_data,
get_file_ids_from_messages, get_file_ids_from_messages,
) )
from litellm.proxy._types import CallTypes, SpecialEnums, UserAPIKeyAuth from litellm.proxy._types import CallTypes, UserAPIKeyAuth
from litellm.types.llms.openai import OpenAIFileObject, OpenAIFilesPurpose from litellm.types.llms.openai import (
CreateFileRequest,
OpenAIFileObject,
OpenAIFilesPurpose,
)
from litellm.types.utils import SpecialEnums
if TYPE_CHECKING: if TYPE_CHECKING:
from opentelemetry.trace import Span as _Span from opentelemetry.trace import Span as _Span
@ -106,14 +112,21 @@ class _PROXY_LiteLLMManagedFiles(CustomLogger):
@staticmethod @staticmethod
async def return_unified_file_id( async def return_unified_file_id(
file_objects: List[OpenAIFileObject], file_objects: List[OpenAIFileObject],
create_file_request: CreateFileRequest,
purpose: OpenAIFilesPurpose, purpose: OpenAIFilesPurpose,
internal_usage_cache: InternalUsageCache, internal_usage_cache: InternalUsageCache,
litellm_parent_otel_span: Span, litellm_parent_otel_span: Span,
) -> OpenAIFileObject: ) -> OpenAIFileObject:
unified_file_id = SpecialEnums.LITELM_MANAGED_FILE_ID_PREFIX.value + str( ## GET THE FILE TYPE FROM THE CREATE FILE REQUEST
uuid.uuid4() file_data = extract_file_data(create_file_request["file"])
file_type = file_data["content_type"]
unified_file_id = SpecialEnums.LITELLM_MANAGED_FILE_COMPLETE_STR.value.format(
file_type, str(uuid.uuid4())
) )
## CREATE RESPONSE OBJECT
## CREATE RESPONSE OBJECT ## CREATE RESPONSE OBJECT
response = OpenAIFileObject( response = OpenAIFileObject(
id=unified_file_id, id=unified_file_id,

View file

@ -34,7 +34,11 @@ from litellm.proxy.common_utils.openai_endpoint_utils import (
from litellm.proxy.hooks.managed_files import _PROXY_LiteLLMManagedFiles from litellm.proxy.hooks.managed_files import _PROXY_LiteLLMManagedFiles
from litellm.proxy.utils import ProxyLogging from litellm.proxy.utils import ProxyLogging
from litellm.router import Router from litellm.router import Router
from litellm.types.llms.openai import OpenAIFileObject, OpenAIFilesPurpose from litellm.types.llms.openai import (
CREATE_FILE_REQUESTS_PURPOSE,
OpenAIFileObject,
OpenAIFilesPurpose,
)
router = APIRouter() router = APIRouter()
@ -147,6 +151,7 @@ async def create_file_for_each_model(
responses.append(individual_response) responses.append(individual_response)
response = await _PROXY_LiteLLMManagedFiles.return_unified_file_id( response = await _PROXY_LiteLLMManagedFiles.return_unified_file_id(
file_objects=responses, file_objects=responses,
create_file_request=_create_file_request,
purpose=purpose, purpose=purpose,
internal_usage_cache=proxy_logging_obj.internal_usage_cache, internal_usage_cache=proxy_logging_obj.internal_usage_cache,
litellm_parent_otel_span=user_api_key_dict.parent_otel_span, litellm_parent_otel_span=user_api_key_dict.parent_otel_span,
@ -232,7 +237,7 @@ async def create_file(
# Cast purpose to OpenAIFilesPurpose type # Cast purpose to OpenAIFilesPurpose type
purpose = cast(OpenAIFilesPurpose, purpose) purpose = cast(OpenAIFilesPurpose, purpose)
data = {"purpose": purpose} data = {}
# Include original request and headers in the data # Include original request and headers in the data
data = await add_litellm_data_to_request( data = await add_litellm_data_to_request(
@ -258,7 +263,9 @@ async def create_file(
model=router_model, llm_router=llm_router model=router_model, llm_router=llm_router
) )
_create_file_request = CreateFileRequest(file=file_data, **data) _create_file_request = CreateFileRequest(
file=file_data, purpose=cast(CREATE_FILE_REQUESTS_PURPOSE, purpose), **data
)
response: Optional[OpenAIFileObject] = None response: Optional[OpenAIFileObject] = None
if ( if (

View file

@ -2760,9 +2760,6 @@ class Router:
stripped_model, custom_llm_provider, _, _ = get_llm_provider( stripped_model, custom_llm_provider, _, _ = get_llm_provider(
model=data["model"] model=data["model"]
) )
# kwargs["file"] = replace_model_in_jsonl(
# file_content=kwargs["file"], new_model_name=stripped_model
# )
response = litellm.acreate_file( response = litellm.acreate_file(
**{ **{

View file

@ -288,7 +288,10 @@ class OpenAIFileObject(BaseModel):
`error` field on `fine_tuning.job`. `error` field on `fine_tuning.job`.
""" """
_hidden_params: dict = {} _hidden_params: dict = {"response_cost": 0.0} # no cost for writing a file
CREATE_FILE_REQUESTS_PURPOSE = Literal["assistants", "batch", "fine-tune"]
# OpenAI Files Types # OpenAI Files Types
@ -307,8 +310,8 @@ class CreateFileRequest(TypedDict, total=False):
timeout: Optional[float] = None timeout: Optional[float] = None
""" """
file: FileTypes file: Required[FileTypes]
purpose: Literal["assistants", "batch", "fine-tune"] purpose: Required[CREATE_FILE_REQUESTS_PURPOSE]
extra_headers: Optional[Dict[str, str]] extra_headers: Optional[Dict[str, str]]
extra_body: Optional[Dict[str, str]] extra_body: Optional[Dict[str, str]]
timeout: Optional[float] timeout: Optional[float]

View file

@ -2221,3 +2221,8 @@ class ExtractedFileData(TypedDict):
content: bytes content: bytes
content_type: Optional[str] content_type: Optional[str]
headers: Mapping[str, str] headers: Mapping[str, str]
class SpecialEnums(Enum):
LITELM_MANAGED_FILE_ID_PREFIX = "litellm_proxy"
LITELLM_MANAGED_FILE_COMPLETE_STR = "litellm_proxy:{};unified_id,{}"

View file

@ -0,0 +1,25 @@
import json
import os
import sys
from unittest.mock import MagicMock, patch
import pytest
sys.path.insert(
0, os.path.abspath("../../..")
) # Adds the parent directory to the system path
from litellm.litellm_core_utils.prompt_templates.common_utils import (
get_format_from_file_id,
update_messages_with_model_file_ids,
)
def test_update_messages_with_model_file_ids():
unified_file_id = (
"litellm_proxy:application/pdf;unified_id,cbbe3534-8bf8-4386-af00-f5f6b7e370bf"
)
format = get_format_from_file_id(unified_file_id)
assert format == "application/pdf"