diff --git a/spring-ai-spring-boot-autoconfigure/pom.xml b/spring-ai-spring-boot-autoconfigure/pom.xml index a37ec565d..9c8f275ae 100644 --- a/spring-ai-spring-boot-autoconfigure/pom.xml +++ b/spring-ai-spring-boot-autoconfigure/pom.xml @@ -109,6 +109,27 @@ true + + + com.oracle.database.jdbc + ojdbc11 + ${oracle.version} + true + + + com.oracle.database.jdbc + ucp + ${oracle.version} + true + + + com.oracle.database.ha + simplefan + ${oracle.version} + true + + + org.springframework.ai @@ -351,6 +372,12 @@ test + + org.testcontainers + oracle-free + test + + com.redis testcontainers-redis diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/oracle/OracleAIVectorSearchStoreAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/oracle/OracleVectorStoreAutoConfiguration.java similarity index 91% rename from spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/oracle/OracleAIVectorSearchStoreAutoConfiguration.java rename to spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/oracle/OracleVectorStoreAutoConfiguration.java index 540e4c1bf..ccde66c47 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/oracle/OracleAIVectorSearchStoreAutoConfiguration.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/oracle/OracleVectorStoreAutoConfiguration.java @@ -32,13 +32,13 @@ import javax.sql.DataSource; */ @AutoConfiguration(after = JdbcTemplateAutoConfiguration.class) @ConditionalOnClass({ OracleVectorStore.class, DataSource.class, JdbcTemplate.class }) -@EnableConfigurationProperties(OracleAIVectorSearchStoreProperties.class) -public class OracleAIVectorSearchStoreAutoConfiguration { +@EnableConfigurationProperties(OracleVectorStoreProperties.class) +public class OracleVectorStoreAutoConfiguration { @Bean @ConditionalOnMissingBean public OracleVectorStore vectorStore(JdbcTemplate jdbcTemplate, EmbeddingModel embeddingModel, - OracleAIVectorSearchStoreProperties properties) { + OracleVectorStoreProperties properties) { return new OracleVectorStore(jdbcTemplate, embeddingModel, properties.getTableName(), properties.getIndexType(), properties.getDistanceType(), properties.getDimensions(), properties.getSearchAccuracy(), properties.isInitializeSchema(), properties.isRemoveExistingVectorStoreTable(), diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/oracle/OracleAIVectorSearchStoreProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/oracle/OracleVectorStoreProperties.java similarity index 76% rename from spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/oracle/OracleAIVectorSearchStoreProperties.java rename to spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/oracle/OracleVectorStoreProperties.java index 67901d3d5..2d70e23f7 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/oracle/OracleAIVectorSearchStoreProperties.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/oracle/OracleVectorStoreProperties.java @@ -24,16 +24,16 @@ import static org.springframework.ai.vectorstore.OracleVectorStore.DEFAULT_SEARC /** * @author Loïc Lefèvre */ -@ConfigurationProperties(OracleAIVectorSearchStoreProperties.CONFIG_PREFIX) -public class OracleAIVectorSearchStoreProperties extends CommonVectorStoreProperties { +@ConfigurationProperties(OracleVectorStoreProperties.CONFIG_PREFIX) +public class OracleVectorStoreProperties extends CommonVectorStoreProperties { public static final String CONFIG_PREFIX = "spring.ai.vectorstore.oracle"; private String tableName = OracleVectorStore.DEFAULT_TABLE_NAME; - private OracleVectorStore.OracleAIVectorSearchIndexType indexType = OracleVectorStore.DEFAULT_INDEX_TYPE; + private OracleVectorStore.OracleVectorStoreIndexType indexType = OracleVectorStore.DEFAULT_INDEX_TYPE; - private OracleVectorStore.OracleAIVectorSearchDistanceType distanceType = OracleVectorStore.DEFAULT_DISTANCE_TYPE; + private OracleVectorStore.OracleVectorStoreDistanceType distanceType = OracleVectorStore.DEFAULT_DISTANCE_TYPE; private int dimensions = OracleVectorStore.DEFAULT_DIMENSIONS; @@ -51,19 +51,19 @@ public class OracleAIVectorSearchStoreProperties extends CommonVectorStoreProper this.tableName = tableName; } - public OracleVectorStore.OracleAIVectorSearchIndexType getIndexType() { + public OracleVectorStore.OracleVectorStoreIndexType getIndexType() { return indexType; } - public void setIndexType(OracleVectorStore.OracleAIVectorSearchIndexType indexType) { + public void setIndexType(OracleVectorStore.OracleVectorStoreIndexType indexType) { this.indexType = indexType; } - public OracleVectorStore.OracleAIVectorSearchDistanceType getDistanceType() { + public OracleVectorStore.OracleVectorStoreDistanceType getDistanceType() { return distanceType; } - public void setDistanceType(OracleVectorStore.OracleAIVectorSearchDistanceType distanceType) { + public void setDistanceType(OracleVectorStore.OracleVectorStoreDistanceType distanceType) { this.distanceType = distanceType; } diff --git a/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index d2f5549df..d39b769ec 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -15,7 +15,7 @@ org.springframework.ai.autoconfigure.bedrock.titan.BedrockTitanChatAutoConfigura org.springframework.ai.autoconfigure.bedrock.titan.BedrockTitanEmbeddingAutoConfiguration org.springframework.ai.autoconfigure.ollama.OllamaAutoConfiguration org.springframework.ai.autoconfigure.mistralai.MistralAiAutoConfiguration -org.springframework.ai.autoconfigure.vectorstore.oracle.OracleAIVectorSearchStoreAutoConfiguration +org.springframework.ai.autoconfigure.vectorstore.oracle.OracleVectorStoreAutoConfiguration org.springframework.ai.autoconfigure.vectorstore.pgvector.PgVectorStoreAutoConfiguration org.springframework.ai.autoconfigure.vectorstore.pinecone.PineconeVectorStoreAutoConfiguration org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusVectorStoreAutoConfiguration diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/oracle/OracleVectorStoreAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/oracle/OracleVectorStoreAutoConfigurationIT.java new file mode 100644 index 000000000..d89554b69 --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/oracle/OracleVectorStoreAutoConfigurationIT.java @@ -0,0 +1,110 @@ +/* + * Copyright 2024 - 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ai.autoconfigure.vectorstore.oracle; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.springframework.ai.document.Document; +import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.transformers.TransformersEmbeddingModel; +import org.springframework.ai.vectorstore.SearchRequest; +import org.springframework.ai.vectorstore.VectorStore; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.DefaultResourceLoader; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.oracle.OracleContainer; +import org.testcontainers.utility.MountableFile; + +/** + * @author Christian Tzolov + */ +@Testcontainers +public class OracleVectorStoreAutoConfigurationIT { + + @Container + static OracleContainer oracle23aiContainer = new OracleContainer("gvenzl/oracle-free:23-slim") + .withCopyFileToContainer(MountableFile.forClasspathResource("/oracle/initialize.sql"), + "/container-entrypoint-initdb.d/initialize.sql"); + + List documents = List.of( + new Document(getText("classpath:/test/data/spring.ai.txt"), Map.of("spring", "great")), + new Document(getText("classpath:/test/data/time.shelter.txt")), + new Document(getText("classpath:/test/data/great.depression.txt"), Map.of("depression", "bad"))); + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(OracleVectorStoreAutoConfiguration.class)) + .withUserConfiguration(Config.class) + .withPropertyValues("spring.ai.vectorstore.oracle.distanceType=COSINE", + // JdbcTemplate configuration + String.format("spring.datasource.url=jdbc:oracle:thin:@//%s:%d/%s", oracle23aiContainer.getHost(), + oracle23aiContainer.getMappedPort(1521), "freepdb1"), + "spring.datasource.username=mlops", "spring.datasource.password=mlops", + "spring.datasource.type=oracle.jdbc.pool.OracleDataSource"); + + @Test + public void addAndSearch() { + contextRunner.run(context -> { + + VectorStore vectorStore = context.getBean(VectorStore.class); + + vectorStore.add(documents); + + List results = vectorStore + .similaritySearch(SearchRequest.query("What is Great Depression?").withTopK(1)); + + assertThat(results).hasSize(1); + Document resultDoc = results.get(0); + assertThat(resultDoc.getId()).isEqualTo(documents.get(2).getId()); + assertThat(resultDoc.getMetadata()).containsKeys("depression", "distance"); + + // Remove all documents from the store + vectorStore.delete(documents.stream().map(doc -> doc.getId()).toList()); + results = vectorStore.similaritySearch(SearchRequest.query("Great Depression").withTopK(1)); + assertThat(results).hasSize(0); + }); + } + + public static String getText(String uri) { + var resource = new DefaultResourceLoader().getResource(uri); + try { + return resource.getContentAsString(StandardCharsets.UTF_8); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Configuration(proxyBeanMethods = false) + static class Config { + + @Bean + public EmbeddingModel embeddingModel() { + return new TransformersEmbeddingModel(); + } + + } + +} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/oracle/OracleVectorStorePropertiesTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/oracle/OracleVectorStorePropertiesTests.java new file mode 100644 index 000000000..d7e00a9ef --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/oracle/OracleVectorStorePropertiesTests.java @@ -0,0 +1,54 @@ +/* + * Copyright 2024 - 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ai.autoconfigure.vectorstore.oracle; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import org.springframework.ai.vectorstore.OracleVectorStore; +import org.springframework.ai.vectorstore.OracleVectorStore.OracleVectorStoreDistanceType; +import org.springframework.ai.vectorstore.OracleVectorStore.OracleVectorStoreIndexType; + +/** + * @author Christian Tzolov + */ +public class OracleVectorStorePropertiesTests { + + @Test + public void defaultValues() { + var props = new OracleVectorStoreProperties(); + assertThat(props.getDimensions()).isEqualTo(OracleVectorStore.DEFAULT_DIMENSIONS); + assertThat(props.getDistanceType()).isEqualTo(OracleVectorStoreDistanceType.COSINE); + assertThat(props.getIndexType()).isEqualTo(OracleVectorStoreIndexType.IVF); + assertThat(props.isRemoveExistingVectorStoreTable()).isFalse(); + } + + @Test + public void customValues() { + var props = new OracleVectorStoreProperties(); + + props.setDimensions(1536); + props.setDistanceType(OracleVectorStoreDistanceType.EUCLIDEAN); + props.setIndexType(OracleVectorStoreIndexType.IVF); + props.setRemoveExistingVectorStoreTable(true); + + assertThat(props.getDimensions()).isEqualTo(1536); + assertThat(props.getDistanceType()).isEqualTo(OracleVectorStoreDistanceType.EUCLIDEAN); + assertThat(props.getIndexType()).isEqualTo(OracleVectorStoreIndexType.IVF); + assertThat(props.isRemoveExistingVectorStoreTable()).isTrue(); + } + +} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/resources/oracle/initialize.sql b/spring-ai-spring-boot-autoconfigure/src/test/resources/oracle/initialize.sql new file mode 100644 index 000000000..ac38a1965 --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/test/resources/oracle/initialize.sql @@ -0,0 +1,10 @@ +-- Exit on any errors +WHENEVER SQLERROR EXIT SQL.SQLCODE + +-- Configure the size of the Vector Pool to 1 GiB. +ALTER SYSTEM SET vector_memory_size=1G SCOPE=SPFILE; + +SHUTDOWN ABORT; +STARTUP; + +exit; diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-oracle-store/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-oracle-store/pom.xml index 62ef671aa..210cb8301 100644 --- a/spring-ai-spring-boot-starters/spring-ai-starter-oracle-store/pom.xml +++ b/spring-ai-spring-boot-starters/spring-ai-starter-oracle-store/pom.xml @@ -37,6 +37,23 @@ spring-ai-oracle-store ${project.parent.version} + + + + com.oracle.database.jdbc + ojdbc11 + ${oracle.version} + + + com.oracle.database.jdbc + ucp + ${oracle.version} + + + com.oracle.database.ha + simplefan + ${oracle.version} + diff --git a/vector-stores/spring-ai-oracle-store/src/main/java/org/springframework/ai/vectorstore/OracleVectorStore.java b/vector-stores/spring-ai-oracle-store/src/main/java/org/springframework/ai/vectorstore/OracleVectorStore.java index d6cc9002a..0c3610c2f 100644 --- a/vector-stores/spring-ai-oracle-store/src/main/java/org/springframework/ai/vectorstore/OracleVectorStore.java +++ b/vector-stores/spring-ai-oracle-store/src/main/java/org/springframework/ai/vectorstore/OracleVectorStore.java @@ -44,7 +44,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import static org.springframework.ai.vectorstore.OracleVectorStore.OracleAIVectorSearchDistanceType.DOT; +import static org.springframework.ai.vectorstore.OracleVectorStore.OracleVectorStoreDistanceType.DOT; import static org.springframework.jdbc.core.StatementCreatorUtils.setParameterValue; /** @@ -76,7 +76,7 @@ public class OracleVectorStore implements VectorStore, InitializingBean { public static final double SIMILARITY_THRESHOLD_EXACT_MATCH = 1.0d; - public enum OracleAIVectorSearchIndexType { + public enum OracleVectorStoreIndexType { /** * Performs exact nearest neighbor search. @@ -123,7 +123,7 @@ public class OracleVectorStore implements VectorStore, InitializingBean { } - public enum OracleAIVectorSearchDistanceType { + public enum OracleVectorStoreDistanceType { /** * Default metric. It calculates the cosine distane between two vectors. @@ -162,9 +162,9 @@ public class OracleVectorStore implements VectorStore, InitializingBean { public static final String DEFAULT_TABLE_NAME = "SPRING_AI_VECTORS"; - public static final OracleAIVectorSearchIndexType DEFAULT_INDEX_TYPE = OracleAIVectorSearchIndexType.IVF; + public static final OracleVectorStoreIndexType DEFAULT_INDEX_TYPE = OracleVectorStoreIndexType.IVF; - public static final OracleAIVectorSearchDistanceType DEFAULT_DISTANCE_TYPE = OracleAIVectorSearchDistanceType.COSINE; + public static final OracleVectorStoreDistanceType DEFAULT_DISTANCE_TYPE = OracleVectorStoreDistanceType.COSINE; public static final int DEFAULT_DIMENSIONS = -1; @@ -189,12 +189,12 @@ public class OracleVectorStore implements VectorStore, InitializingBean { * Index type used to index the vectors. It can impact performance and database memory * consumption. */ - private final OracleAIVectorSearchIndexType indexType; + private final OracleVectorStoreIndexType indexType; /** * Distance type to use for computing vector distances. */ - private final OracleAIVectorSearchDistanceType distanceType; + private final OracleVectorStoreDistanceType distanceType; /** * Expected number of dimensions for vectors. Enforcing vector dimensions is very @@ -217,7 +217,7 @@ public class OracleVectorStore implements VectorStore, InitializingBean { } public OracleVectorStore(JdbcTemplate jdbcTemplate, EmbeddingModel embeddingModel, String tableName, - OracleAIVectorSearchIndexType indexType, OracleAIVectorSearchDistanceType distanceType, int dimensions, + OracleVectorStoreIndexType indexType, OracleVectorStoreDistanceType distanceType, int dimensions, int searchAccuracy, boolean initializeSchema, boolean removeExistingVectorStoreTable, boolean forcedNormalization) { if (dimensions != DEFAULT_DIMENSIONS) { @@ -496,7 +496,7 @@ public class OracleVectorStore implements VectorStore, InitializingBean { } else { if (!forcedNormalization - || (distanceType != OracleAIVectorSearchDistanceType.COSINE && distanceType != DOT)) { + || (distanceType != OracleVectorStoreDistanceType.COSINE && distanceType != DOT)) { throw new RuntimeException( "Similarity threshold filtering requires all vectors to be normalized, see the forcedNormalization parameter for this Vector store. Also only COSINE and DOT distance types are supported."); } diff --git a/vector-stores/spring-ai-oracle-store/src/test/java/org/springframework/ai/vectorstore/OracleVectorStoreIT.java b/vector-stores/spring-ai-oracle-store/src/test/java/org/springframework/ai/vectorstore/OracleVectorStoreIT.java index 7d49ceb11..8eb30a79f 100644 --- a/vector-stores/spring-ai-oracle-store/src/test/java/org/springframework/ai/vectorstore/OracleVectorStoreIT.java +++ b/vector-stores/spring-ai-oracle-store/src/test/java/org/springframework/ai/vectorstore/OracleVectorStoreIT.java @@ -63,7 +63,7 @@ public class OracleVectorStoreIT { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withUserConfiguration(TestClient.class) - .withPropertyValues("test.spring.ai.vectorstore.oracle.distanceType=COSINE_DISTANCE", + .withPropertyValues("test.spring.ai.vectorstore.oracle.distanceType=COSINE", "test.spring.ai.vectorstore.oracle.dimensions=384", // JdbcTemplate configuration String.format("app.datasource.url=%s", oracle23aiContainer.getJdbcUrl()), @@ -76,7 +76,7 @@ public class OracleVectorStoreIT { public static class TestClient { @Value("${test.spring.ai.vectorstore.oracle.distanceType}") - OracleVectorStore.OracleAIVectorSearchDistanceType distanceType; + OracleVectorStore.OracleVectorStoreDistanceType distanceType; @Value("${test.spring.ai.vectorstore.oracle.searchAccuracy}") int searchAccuracy; @@ -84,7 +84,7 @@ public class OracleVectorStoreIT { @Bean public VectorStore vectorStore(JdbcTemplate jdbcTemplate, EmbeddingModel embeddingModel) { return new OracleVectorStore(jdbcTemplate, embeddingModel, OracleVectorStore.DEFAULT_TABLE_NAME, - OracleVectorStore.OracleAIVectorSearchIndexType.IVF, distanceType, 384, searchAccuracy, true, true, + OracleVectorStore.OracleVectorStoreIndexType.IVF, distanceType, 384, searchAccuracy, true, true, true); }