diff --git a/litellm/llms/openai/vector_stores/transformation.py b/litellm/llms/openai/vector_stores/transformation.py index 0e890f0fd5..76cd12be8e 100644 --- a/litellm/llms/openai/vector_stores/transformation.py +++ b/litellm/llms/openai/vector_stores/transformation.py @@ -14,6 +14,7 @@ from litellm.types.vector_stores import ( VectorStoreSearchRequest, VectorStoreSearchResponse, ) +from litellm.utils import add_openai_metadata if TYPE_CHECKING: from litellm.litellm_core_utils.litellm_logging import Logging as _LiteLLMLoggingObj @@ -119,12 +120,13 @@ class OpenAIVectorStoreConfig(BaseVectorStoreConfig): api_base: str, ) -> Tuple[str, Dict]: url = api_base # Base URL for creating vector stores + metadata = vector_store_create_optional_params.get("metadata", None) typed_request_body = VectorStoreCreateRequest( name=vector_store_create_optional_params.get("name", None), file_ids=vector_store_create_optional_params.get("file_ids", None), expires_after=vector_store_create_optional_params.get("expires_after", None), chunking_strategy=vector_store_create_optional_params.get("chunking_strategy", None), - metadata=vector_store_create_optional_params.get("metadata", None), + metadata=add_openai_metadata(metadata) if metadata is not None else None, ) dict_request_body = cast(dict, typed_request_body) diff --git a/litellm/proxy/route_llm_request.py b/litellm/proxy/route_llm_request.py index e57539a612..eb48f7de2d 100644 --- a/litellm/proxy/route_llm_request.py +++ b/litellm/proxy/route_llm_request.py @@ -153,6 +153,8 @@ async def route_request( "aget_responses", "adelete_responses", "alist_input_items", + "avector_store_create", + "avector_store_search", ]: # moderation endpoint does not require `model` parameter return getattr(llm_router, f"{route_type}")(**data) diff --git a/tests/test_litellm/llms/openai/vector_stores/test_openai_vector_stores_transformation.py b/tests/test_litellm/llms/openai/vector_stores/test_openai_vector_stores_transformation.py new file mode 100644 index 0000000000..17a611f571 --- /dev/null +++ b/tests/test_litellm/llms/openai/vector_stores/test_openai_vector_stores_transformation.py @@ -0,0 +1,67 @@ +import pytest + +from litellm.llms.openai.vector_stores.transformation import OpenAIVectorStoreConfig +from litellm.types.vector_stores import ( + VectorStoreCreateOptionalRequestParams, +) + + +class TestOpenAIVectorStoreAPIConfig: + + @pytest.mark.parametrize( + "metadata", [{}, None] + ) + def test_transform_create_vector_store_request_with_metadata_empty_or_none(self, metadata): + """ + Test transform_create_vector_store_request when metadata is None or empty dict. + """ + config = OpenAIVectorStoreConfig() + api_base = "https://api.openai.com/v1/vector_stores" + + vector_store_create_params: VectorStoreCreateOptionalRequestParams = { + "name": "test-vector-store", + "file_ids": ["file-123", "file-456"], + "metadata": metadata, + } + + url, request_body = config.transform_create_vector_store_request( + vector_store_create_params, api_base + ) + + assert url == api_base + assert request_body["name"] == "test-vector-store" + assert request_body["file_ids"] == ["file-123", "file-456"] + assert request_body["metadata"] == metadata + + + def test_transform_create_vector_store_request_with_large_metadata(self): + """ + Test transform_create_vector_store_request with metadata exceeding 16 keys. + + OpenAI limits metadata to 16 keys maximum. + """ + config = OpenAIVectorStoreConfig() + api_base = "https://api.openai.com/v1/vector_stores" + + # Create metadata with more than 16 keys + large_metadata = {f"key_{i}": f"value_{i}" for i in range(20)} + + vector_store_create_params: VectorStoreCreateOptionalRequestParams = { + "name": "test-vector-store", + "metadata": large_metadata, + } + + url, request_body = config.transform_create_vector_store_request( + vector_store_create_params, api_base + ) + + assert url == api_base + assert request_body["name"] == "test-vector-store" + + # Should be trimmed to 16 keys + assert len(request_body["metadata"]) == 16 + + # Should contain the first 16 keys (as per add_openai_metadata implementation) + for i in range(16): + assert f"key_{i}" in request_body["metadata"] + assert request_body["metadata"][f"key_{i}"] == f"value_{i}" diff --git a/tests/test_litellm/proxy/test_route_llm_request.py b/tests/test_litellm/proxy/test_route_llm_request.py index d4a65d8847..9d8aebd2d1 100644 --- a/tests/test_litellm/proxy/test_route_llm_request.py +++ b/tests/test_litellm/proxy/test_route_llm_request.py @@ -49,7 +49,7 @@ async def test_route_request_dynamic_credentials(route_type): @pytest.mark.asyncio async def test_route_request_no_model_required(): """Test route types that don't require model parameter""" - test_cases = ["amoderation", "aget_responses", "adelete_responses"] + test_cases = ["amoderation", "aget_responses", "adelete_responses", "avector_store_create", "avector_store_search"] for route_type in test_cases: # Test data without model parameter @@ -72,7 +72,7 @@ async def test_route_request_no_model_required(): @pytest.mark.asyncio async def test_route_request_no_model_required_with_router_settings(): """Test route types that don't require model parameter with router settings""" - test_cases = ["amoderation", "aget_responses", "adelete_responses"] + test_cases = ["amoderation", "aget_responses", "adelete_responses", "avector_store_create", "avector_store_search"] for route_type in test_cases: # Test data with model parameter (it will be ignored for these route types)