From fa23a6e7a458404bf9cb2ecd6982d70e8945444c Mon Sep 17 00:00:00 2001 From: Ramzi Alashmali <186906047+RAMZI0TO99@users.noreply.github.com> Date: Mon, 5 Oct 2026 21:35:01 +0300 Subject: [PATCH 1/2] fix(vector-store-files): preserve numeric and boolean attributes --- .../vector_store_files/transformation.py | 15 --- ...penai_vector_store_files_transformation.py | 95 +++++++++++++++++-- 2 files changed, 87 insertions(+), 23 deletions(-) diff --git a/litellm/llms/openai/vector_store_files/transformation.py b/litellm/llms/openai/vector_store_files/transformation.py index 8519b5f4bc4..0ee18b597e6 100644 --- a/litellm/llms/openai/vector_store_files/transformation.py +++ b/litellm/llms/openai/vector_store_files/transformation.py @@ -20,7 +20,6 @@ from litellm.types.vector_store_files import ( VectorStoreFileObject, VectorStoreFileUpdateRequest, ) -from litellm.utils import add_openai_metadata def _clean_dict(source: Mapping[str, object]) -> dict[str, object]: @@ -104,13 +103,6 @@ class OpenAIVectorStoreFilesConfig(BaseVectorStoreFilesConfig): api_base: str, ) -> tuple[str, dict[str, object]]: payload: Final[dict[str, object]] = _clean_dict(dict(create_request)) - attributes: Final = payload.get("attributes") - if isinstance(attributes, dict): - filtered_attributes: Final = add_openai_metadata(attributes) - if filtered_attributes is not None: - payload["attributes"] = filtered_attributes - else: - payload.pop("attributes", None) url: Final = api_base return url, payload @@ -209,13 +201,6 @@ class OpenAIVectorStoreFilesConfig(BaseVectorStoreFilesConfig): api_base: str, ) -> tuple[str, dict[str, object]]: payload: Final[dict[str, object]] = dict(update_request) - attributes: Final = payload.get("attributes") - if isinstance(attributes, dict): - filtered_attributes: Final = add_openai_metadata(attributes) - if filtered_attributes is not None: - payload["attributes"] = filtered_attributes - else: - payload.pop("attributes", None) encoded_file_id: Final = encode_url_path_segment(file_id, field_name="file_id") return f"{api_base}/{encoded_file_id}", payload diff --git a/tests/unit/llms/openai/vector_store_files/test_openai_vector_store_files_transformation.py b/tests/unit/llms/openai/vector_store_files/test_openai_vector_store_files_transformation.py index aebd93d062e..a9051c12a32 100644 --- a/tests/unit/llms/openai/vector_store_files/test_openai_vector_store_files_transformation.py +++ b/tests/unit/llms/openai/vector_store_files/test_openai_vector_store_files_transformation.py @@ -1,3 +1,6 @@ +import json +from typing import Final + import httpx import pytest @@ -5,6 +8,10 @@ from litellm.llms.openai.vector_store_files.transformation import ( OpenAIVectorStoreFilesConfig, ) from litellm.types.router import GenericLiteLLMParams +from litellm.types.vector_store_files import ( + VectorStoreFileCreateRequest, + VectorStoreFileUpdateRequest, +) @pytest.fixture() @@ -41,10 +48,7 @@ def test_get_complete_url_encodes_vector_store_id( litellm_params={}, ) - assert ( - url - == "https://api.example.com/v1/vector_stores/..%2Fvs_123%3Fx%3D1%23frag/files" - ) + assert url == "https://api.example.com/v1/vector_stores/..%2Fvs_123%3Fx%3D1%23frag/files" def test_transform_create_request(config: OpenAIVectorStoreFilesConfig): @@ -63,6 +67,84 @@ def test_transform_create_request(config: OpenAIVectorStoreFilesConfig): assert payload["attributes"]["key"] == "value" +@pytest.mark.parametrize( + "attributes", + [ + { + "category": "manual", + "year": 2024, + "score": 0.75, + "published": True, + "archived": False, + "count": 0, + "weight": 0.0, + "label": "", + }, + {"year": 2024, "archived": False}, + {}, + ], + ids=["mixed-types", "no-strings", "empty"], +) +def test_create_request_preserves_attribute_types( + config: OpenAIVectorStoreFilesConfig, + attributes: dict[str, str | int | float | bool], +) -> None: + api_base: Final = "https://api.example.com/v1/vector_stores/vs_123/files" + create_request: Final[VectorStoreFileCreateRequest] = { + "file_id": "file-abc", + "attributes": attributes, + } + expected_json: Final = json.dumps(create_request, sort_keys=True) + + url, payload = config.transform_create_vector_store_file_request( + vector_store_id="vs_123", + create_request=create_request, + api_base=api_base, + ) + + assert url == api_base + assert json.dumps(payload, sort_keys=True) == expected_json + assert json.dumps(create_request, sort_keys=True) == expected_json + + +@pytest.mark.parametrize( + "attributes", + [ + { + "category": "manual", + "year": 2024, + "score": 0.75, + "published": True, + "archived": False, + "count": 0, + "weight": 0.0, + "label": "", + }, + {"year": 2024, "archived": False}, + {}, + ], + ids=["mixed-types", "no-strings", "empty"], +) +def test_update_request_preserves_attribute_types( + config: OpenAIVectorStoreFilesConfig, + attributes: dict[str, str | int | float | bool], +) -> None: + api_base: Final = "https://api.example.com/v1/vector_stores/vs_123/files" + update_request: Final[VectorStoreFileUpdateRequest] = {"attributes": attributes} + expected_json: Final = json.dumps(update_request, sort_keys=True) + + url, payload = config.transform_update_vector_store_file_request( + vector_store_id="vs_123", + file_id="file-abc", + update_request=update_request, + api_base=api_base, + ) + + assert url == f"{api_base}/file-abc" + assert json.dumps(payload, sort_keys=True) == expected_json + assert json.dumps(update_request, sort_keys=True) == expected_json + + def test_transform_list_request(config: OpenAIVectorStoreFilesConfig): api_base = "https://api.example.com/v1/vector_stores/vs_123/files" url, params = config.transform_list_vector_store_files_request( @@ -84,10 +166,7 @@ def test_transform_file_request_encodes_file_id(config: OpenAIVectorStoreFilesCo api_base=api_base, ) - assert ( - url - == "https://api.example.com/v1/vector_stores/vs_123/files/..%2F..%2Ffiles%3Fx%3D1%23frag/content" - ) + assert url == "https://api.example.com/v1/vector_stores/vs_123/files/..%2F..%2Ffiles%3Fx%3D1%23frag/content" assert params == {} From 6297c1be5d2f5aecc8532e4cd90fa5034c587300 Mon Sep 17 00:00:00 2001 From: Ramzi Alashmali <186906047+RAMZI0TO99@users.noreply.github.com> Date: Mon, 5 Oct 2026 21:47:32 +0300 Subject: [PATCH 2/2] fix(harness): remove unused import blocking Ruff --- litellm/harness/options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/harness/options.py b/litellm/harness/options.py index 5d7c8ae0807..29536a3e093 100644 --- a/litellm/harness/options.py +++ b/litellm/harness/options.py @@ -4,7 +4,7 @@ from __future__ import annotations from collections.abc import Mapping, Sequence from dataclasses import dataclass, field -from typing import Any, Literal +from typing import Literal @dataclass(frozen=True)