From e4d01d1d781ac1efe41f5356a14f1adc1ee250a6 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Sat, 19 Sep 2026 03:52:48 -0700 Subject: [PATCH] fix(s3_vectors): reject a store id with an empty bucket or index part A "bucket:" or ":index" id split into an empty name, so ingestion silently generated a fresh index and search sent the empty name to AWS. Both sides now raise the existing format error through the shared helper. --- .../s3_vectors/vector_stores/transformation.py | 10 +++++----- .../test_s3_vectors_transformation.py | 16 ++++++++++++++++ .../rag/ingestion/test_s3_vectors_ingestion.py | 13 +++++++++++++ 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/litellm/llms/s3_vectors/vector_stores/transformation.py b/litellm/llms/s3_vectors/vector_stores/transformation.py index 04f561aa2ca..b02734e316d 100644 --- a/litellm/llms/s3_vectors/vector_stores/transformation.py +++ b/litellm/llms/s3_vectors/vector_stores/transformation.py @@ -33,12 +33,12 @@ S3_VECTORS_STORE_ID_ERROR: Final = ( def split_s3_vectors_store_id(vector_store_id: str, fallback_bucket_name: object) -> tuple[str, str]: - if ":" in vector_store_id: - bucket_name, index_name = vector_store_id.split(":", 1) - return bucket_name, index_name - if not isinstance(fallback_bucket_name, str) or not fallback_bucket_name: + id_bucket_name, separator, id_index_name = vector_store_id.partition(":") + bucket_name: Final = id_bucket_name if separator else fallback_bucket_name + index_name: Final = id_index_name if separator else vector_store_id + if not isinstance(bucket_name, str) or not bucket_name or not index_name: raise ValueError(S3_VECTORS_STORE_ID_ERROR) - return fallback_bucket_name, vector_store_id + return bucket_name, index_name class S3VectorsVectorStoreConfig(BaseQueryEmbeddingVectorStoreConfig, BaseAWSLLM): diff --git a/tests/test_litellm/llms/s3_vectors/vector_stores/test_s3_vectors_transformation.py b/tests/test_litellm/llms/s3_vectors/vector_stores/test_s3_vectors_transformation.py index e313b749d06..781e92ea7d9 100644 --- a/tests/test_litellm/llms/s3_vectors/vector_stores/test_s3_vectors_transformation.py +++ b/tests/test_litellm/llms/s3_vectors/vector_stores/test_s3_vectors_transformation.py @@ -236,6 +236,22 @@ class TestS3VectorsVectorStoreConfig: assert executor.calls == [] + @pytest.mark.parametrize("vector_store_id", ["test-bucket:", ":test-index"]) + def test_transform_search_request_rejects_an_empty_bucket_or_index_in_the_id(self, vector_store_id): + config = S3VectorsVectorStoreConfig() + executor = _RecordingExecutor() + + with pytest.raises(ValueError, match="vector_store_id must be in format 'bucket_name:index_name'"): + config.transform_search_vector_store_request( + **_search_kwargs( + vector_store_id=vector_store_id, + litellm_params={"vector_bucket_name": "test-bucket"}, + embedding_executor=executor, + ) + ) + + assert executor.calls == [] + def test_transform_search_request_bucket_from_litellm_params(self): config = S3VectorsVectorStoreConfig() diff --git a/tests/test_litellm/rag/ingestion/test_s3_vectors_ingestion.py b/tests/test_litellm/rag/ingestion/test_s3_vectors_ingestion.py index 30f9adf07b3..3256de48ef9 100644 --- a/tests/test_litellm/rag/ingestion/test_s3_vectors_ingestion.py +++ b/tests/test_litellm/rag/ingestion/test_s3_vectors_ingestion.py @@ -50,3 +50,16 @@ def test_bucket_alone_leaves_the_index_to_be_generated(): def test_no_bucket_anywhere_is_rejected(vector_store): with pytest.raises(ValueError, match=STORE_ID_FORMAT_ERROR): _ingestion(**vector_store) + + +@pytest.mark.parametrize( + "vector_store", + [ + {"vector_store_id": "my-embeddings:"}, + {"vector_store_id": ":my-index"}, + {"vector_store_id": "my-embeddings:", "vector_bucket_name": "my-embeddings"}, + ], +) +def test_an_empty_bucket_or_index_in_the_store_id_is_rejected_instead_of_generating_an_index(vector_store): + with pytest.raises(ValueError, match=STORE_ID_FORMAT_ERROR): + _ingestion(**vector_store)