diff --git a/spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/observation/AbstractObservationVectorStore.java b/spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/observation/AbstractObservationVectorStore.java index 52c8106a48e..39e46d7d6f7 100644 --- a/spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/observation/AbstractObservationVectorStore.java +++ b/spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/observation/AbstractObservationVectorStore.java @@ -74,7 +74,7 @@ public AbstractObservationVectorStore(AbstractVectorStoreBuilder builder) { */ @Override public void add(List documents) { - + validateNonTextDocuments(documents); VectorStoreObservationContext observationContext = this .createObservationContextBuilder(VectorStoreObservationContext.Operation.ADD.value()) .build(); @@ -85,6 +85,17 @@ public void add(List documents) { .observe(() -> this.doAdd(documents)); } + private void validateNonTextDocuments(List documents) { + if (documents == null) + return; + for (Document document : documents) { + if (document != null && !document.isText()) { + throw new IllegalArgumentException( + "Only text documents are supported for now. One of the documents contains non-text content."); + } + } + } + @Override public void delete(List deleteDocIds) { diff --git a/spring-ai-vector-store/src/test/java/org/springframework/ai/vectorstore/SimpleVectorStoreTests.java b/spring-ai-vector-store/src/test/java/org/springframework/ai/vectorstore/SimpleVectorStoreTests.java index 85fe0b384c6..3600d4e13c3 100644 --- a/spring-ai-vector-store/src/test/java/org/springframework/ai/vectorstore/SimpleVectorStoreTests.java +++ b/spring-ai-vector-store/src/test/java/org/springframework/ai/vectorstore/SimpleVectorStoreTests.java @@ -32,13 +32,18 @@ import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDir; +import org.springframework.ai.content.Media; import org.springframework.ai.document.Document; import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.Resource; +import org.springframework.util.MimeType; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -259,4 +264,15 @@ void shouldHandleNullVectors() { .hasMessage("Vectors must not be null"); } + @Test + void shouldFailNonTextDocuments() { + Media media = new Media(MimeType.valueOf("image/png"), new ByteArrayResource(new byte[] { 0x00 })); + + Document imgDoc = Document.builder().media(media).metadata(Map.of("fileName", "pixel.png")).build(); + + Exception exception = assertThrows(IllegalArgumentException.class, () -> this.vectorStore.add(List.of(imgDoc))); + assertEquals("Only text documents are supported for now. One of the documents contains non-text content.", + exception.getMessage()); + } + } diff --git a/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreIT.java b/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreIT.java index 2a943e895a1..84b79f0edb2 100644 --- a/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreIT.java +++ b/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreIT.java @@ -32,11 +32,11 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariables; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.qdrant.QdrantContainer; +import org.springframework.ai.content.Media; import org.springframework.ai.document.Document; import org.springframework.ai.document.DocumentMetadata; import org.springframework.ai.embedding.EmbeddingModel; @@ -49,8 +49,12 @@ import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.util.MimeType; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; /** * @author Anush Shetty @@ -59,6 +63,7 @@ * @author Thomas Vitale * @author Soby Chacko * @author Jonghoon Park + * @author Kim San * @since 0.8.1 */ @Testcontainers @@ -334,6 +339,20 @@ void shouldConvertLongToString() { }); } + @Test + void testNonTextDocuments() { + this.contextRunner.run(context -> { + QdrantVectorStore vectorStore = context.getBean(QdrantVectorStore.class); + Media media = new Media(MimeType.valueOf("image/png"), new ByteArrayResource(new byte[] { 0x00 })); + + Document imgDoc = Document.builder().media(media).metadata(Map.of("fileName", "pixel.png")).build(); + + Exception exception = assertThrows(IllegalArgumentException.class, () -> vectorStore.add(List.of(imgDoc))); + assertEquals("Only text documents are supported for now. One of the documents contains non-text content.", + exception.getMessage()); + }); + } + @SpringBootConfiguration public static class TestApplication {