Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
* @author Eddú Meléndez
* @author Christian Tzolov
* @author Soby Chacko
* @author Anders Swanson
*/
@AutoConfiguration
@ConditionalOnClass({ OracleVectorStore.class, DataSource.class, JdbcTemplate.class })
Expand Down Expand Up @@ -69,6 +70,11 @@ public OracleVectorStore vectorStore(JdbcTemplate jdbcTemplate, EmbeddingModel e
.distanceType(properties.getDistanceType())
.dimensions(properties.getDimensions())
.searchAccuracy(properties.getSearchAccuracy())
.hnswNeighbors(properties.getHnswNeighbors())
.hnswEfConstruction(properties.getHnswEfConstruction())
.ivfNeighborPartitions(properties.getIvfNeighborPartitions())
.ivfSamplePerPartition(properties.getIvfSamplePerPartition())
.ivfMinVectorsPerPartition(properties.getIvfMinVectorsPerPartition())
.initializeSchema(properties.isInitializeSchema())
.removeExistingVectorStoreTable(properties.isRemoveExistingVectorStoreTable())
.forcedNormalization(properties.isForcedNormalization())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
* Configuration properties for Oracle Vector Store.
*
* @author Loïc Lefèvre
* @author Anders Swanson
*/
@ConfigurationProperties(OracleVectorStoreProperties.CONFIG_PREFIX)
public class OracleVectorStoreProperties extends CommonVectorStoreProperties {
Expand All @@ -44,6 +45,16 @@ public class OracleVectorStoreProperties extends CommonVectorStoreProperties {

private int searchAccuracy = OracleVectorStore.DEFAULT_SEARCH_ACCURACY;

private int hnswNeighbors = OracleVectorStore.DEFAULT_HNSW_NEIGHBORS;

private int hnswEfConstruction = OracleVectorStore.DEFAULT_HNSW_EF_CONSTRUCTION;

private int ivfNeighborPartitions = OracleVectorStore.DEFAULT_IVF_NEIGHBOR_PARTITIONS;

private int ivfSamplePerPartition = OracleVectorStore.DEFAULT_IVF_SAMPLE_PER_PARTITION;

private int ivfMinVectorsPerPartition = OracleVectorStore.DEFAULT_IVF_MIN_VECTORS_PER_PARTITION;

public String getTableName() {
return this.tableName;
}
Expand Down Expand Up @@ -100,4 +111,94 @@ public void setSearchAccuracy(int searchAccuracy) {
this.searchAccuracy = searchAccuracy;
}

/**
* Returns the configured HNSW neighbors value.
* @return the configured HNSW neighbors value
* @since 2.0.0
*/
public int getHnswNeighbors() {
return this.hnswNeighbors;
}

/**
* Sets the HNSW neighbors value.
* @param hnswNeighbors the HNSW neighbors value
* @since 2.0.0
*/
public void setHnswNeighbors(int hnswNeighbors) {
this.hnswNeighbors = hnswNeighbors;
}

/**
* Returns the configured HNSW efConstruction value.
* @return the configured HNSW efConstruction value
* @since 2.0.0
*/
public int getHnswEfConstruction() {
return this.hnswEfConstruction;
}

/**
* Sets the HNSW efConstruction value.
* @param hnswEfConstruction the HNSW efConstruction value
* @since 2.0.0
*/
public void setHnswEfConstruction(int hnswEfConstruction) {
this.hnswEfConstruction = hnswEfConstruction;
}

/**
* Returns the configured IVF neighbor partitions value.
* @return the configured IVF neighbor partitions value
* @since 2.0.0
*/
public int getIvfNeighborPartitions() {
return this.ivfNeighborPartitions;
}

/**
* Sets the IVF neighbor partitions value.
* @param ivfNeighborPartitions the IVF neighbor partitions value
* @since 2.0.0
*/
public void setIvfNeighborPartitions(int ivfNeighborPartitions) {
this.ivfNeighborPartitions = ivfNeighborPartitions;
}

/**
* Returns the configured IVF sample per partition value.
* @return the configured IVF sample per partition value
* @since 2.0.0
*/
public int getIvfSamplePerPartition() {
return this.ivfSamplePerPartition;
}

/**
* Sets the IVF sample per partition value.
* @param ivfSamplePerPartition the IVF sample per partition value
* @since 2.0.0
*/
public void setIvfSamplePerPartition(int ivfSamplePerPartition) {
this.ivfSamplePerPartition = ivfSamplePerPartition;
}

/**
* Returns the configured IVF minimum vectors per partition value.
* @return the configured IVF minimum vectors per partition value
* @since 2.0.0
*/
public int getIvfMinVectorsPerPartition() {
return this.ivfMinVectorsPerPartition;
}

/**
* Sets the IVF minimum vectors per partition value.
* @param ivfMinVectorsPerPartition the IVF minimum vectors per partition value
* @since 2.0.0
*/
public void setIvfMinVectorsPerPartition(int ivfMinVectorsPerPartition) {
this.ivfMinVectorsPerPartition = ivfMinVectorsPerPartition;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,15 @@
* @author Christian Tzolov
* @author Eddú Meléndez
* @author Thomas Vitale
* @author Anders Swanson
*/
@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");
static OracleContainer oracleContainer = new OracleContainer("gvenzl/oracle-free:23-slim").withCopyFileToContainer(
MountableFile.forClasspathResource("/oracle/initialize.sql"),
"/container-entrypoint-initdb.d/initialize.sql");

private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(OracleVectorStoreAutoConfiguration.class,
Expand All @@ -68,9 +69,9 @@ public class OracleVectorStoreAutoConfigurationIT {
"spring.ai.vectorstore.oracle.initialize-schema=true",
"test.spring.ai.vectorstore.oracle.dimensions=384",
// JdbcTemplate configuration
String.format("spring.datasource.url=%s", oracle23aiContainer.getJdbcUrl()),
String.format("spring.datasource.username=%s", oracle23aiContainer.getUsername()),
String.format("spring.datasource.password=%s", oracle23aiContainer.getPassword()),
String.format("spring.datasource.url=%s", oracleContainer.getJdbcUrl()),
String.format("spring.datasource.username=%s", oracleContainer.getUsername()),
String.format("spring.datasource.password=%s", oracleContainer.getPassword()),
"spring.datasource.type=oracle.jdbc.pool.OracleDataSource");

List<Document> documents = List.of(
Expand Down Expand Up @@ -152,6 +153,60 @@ public void autoConfigurationEnabledWhenTypeIsOracle() {
});
}

@Test
public void customOracleIndexPropertiesAreApplied() {
this.contextRunner
.withPropertyValues("spring.ai.vectorstore.oracle.initialize-schema=false",
"spring.ai.vectorstore.oracle.index-type=HNSW", "spring.ai.vectorstore.oracle.hnsw-neighbors=64",
"spring.ai.vectorstore.oracle.hnsw-ef-construction=300",
"spring.ai.vectorstore.oracle.ivf-neighbor-partitions=32",
"spring.ai.vectorstore.oracle.ivf-sample-per-partition=16",
"spring.ai.vectorstore.oracle.ivf-min-vectors-per-partition=8")
.run(context -> {
OracleVectorStoreProperties properties = context.getBean(OracleVectorStoreProperties.class);
OracleVectorStore vectorStore = context.getBean(OracleVectorStore.class);

assertThat(properties.getIndexType()).isEqualTo(OracleVectorStore.OracleVectorStoreIndexType.HNSW);
assertThat(properties.getHnswNeighbors()).isEqualTo(64);
assertThat(properties.getHnswEfConstruction()).isEqualTo(300);
assertThat(properties.getIvfNeighborPartitions()).isEqualTo(32);
assertThat(properties.getIvfSamplePerPartition()).isEqualTo(16);
assertThat(properties.getIvfMinVectorsPerPartition()).isEqualTo(8);

assertThat(vectorStore.getIndexType()).isEqualTo(OracleVectorStore.OracleVectorStoreIndexType.HNSW);
assertThat(vectorStore.getHnswNeighbors()).isEqualTo(64);
assertThat(vectorStore.getHnswEfConstruction()).isEqualTo(300);
assertThat(vectorStore.getIvfNeighborPartitions()).isEqualTo(32);
assertThat(vectorStore.getIvfSamplePerPartition()).isEqualTo(16);
assertThat(vectorStore.getIvfMinVectorsPerPartition()).isEqualTo(8);
});
}

@Test
public void invalidHnswPropertiesFailAtStartup() {
this.contextRunner
.withPropertyValues("spring.ai.vectorstore.oracle.initialize-schema=false",
"spring.ai.vectorstore.oracle.index-type=HNSW", "spring.ai.vectorstore.oracle.hnsw-neighbors=0")
.run(context -> {
assertThat(context).hasFailed();
assertThat(context.getStartupFailure()).hasRootCauseInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("HNSW neighbors must be greater than 0");
});
}

@Test
public void invalidIvfPropertiesFailAtStartup() {
this.contextRunner
.withPropertyValues("spring.ai.vectorstore.oracle.initialize-schema=false",
"spring.ai.vectorstore.oracle.index-type=IVF",
"spring.ai.vectorstore.oracle.ivf-sample-per-partition=-2")
.run(context -> {
assertThat(context).hasFailed();
assertThat(context.getStartupFailure()).hasRootCauseInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("IVF sample per partition must be greater than 0");
});
}

@Configuration(proxyBeanMethods = false)
static class Config {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

/**
* @author Christian Tzolov
* @author Anders Swanson
*/
public class OracleVectorStorePropertiesTests {

Expand All @@ -35,6 +36,12 @@ public void defaultValues() {
assertThat(props.getDimensions()).isEqualTo(OracleVectorStore.DEFAULT_DIMENSIONS);
assertThat(props.getDistanceType()).isEqualTo(OracleVectorStoreDistanceType.COSINE);
assertThat(props.getIndexType()).isEqualTo(OracleVectorStoreIndexType.IVF);
assertThat(props.getHnswNeighbors()).isEqualTo(OracleVectorStore.DEFAULT_HNSW_NEIGHBORS);
assertThat(props.getHnswEfConstruction()).isEqualTo(OracleVectorStore.DEFAULT_HNSW_EF_CONSTRUCTION);
assertThat(props.getIvfNeighborPartitions()).isEqualTo(OracleVectorStore.DEFAULT_IVF_NEIGHBOR_PARTITIONS);
assertThat(props.getIvfSamplePerPartition()).isEqualTo(OracleVectorStore.DEFAULT_IVF_SAMPLE_PER_PARTITION);
assertThat(props.getIvfMinVectorsPerPartition())
.isEqualTo(OracleVectorStore.DEFAULT_IVF_MIN_VECTORS_PER_PARTITION);
assertThat(props.isRemoveExistingVectorStoreTable()).isFalse();
}

Expand All @@ -44,12 +51,22 @@ public void customValues() {

props.setDimensions(1536);
props.setDistanceType(OracleVectorStoreDistanceType.EUCLIDEAN);
props.setIndexType(OracleVectorStoreIndexType.IVF);
props.setIndexType(OracleVectorStoreIndexType.HNSW);
props.setHnswNeighbors(64);
props.setHnswEfConstruction(300);
props.setIvfNeighborPartitions(20);
props.setIvfSamplePerPartition(16);
props.setIvfMinVectorsPerPartition(8);
props.setRemoveExistingVectorStoreTable(true);

assertThat(props.getDimensions()).isEqualTo(1536);
assertThat(props.getDistanceType()).isEqualTo(OracleVectorStoreDistanceType.EUCLIDEAN);
assertThat(props.getIndexType()).isEqualTo(OracleVectorStoreIndexType.IVF);
assertThat(props.getIndexType()).isEqualTo(OracleVectorStoreIndexType.HNSW);
assertThat(props.getHnswNeighbors()).isEqualTo(64);
assertThat(props.getHnswEfConstruction()).isEqualTo(300);
assertThat(props.getIvfNeighborPartitions()).isEqualTo(20);
assertThat(props.getIvfSamplePerPartition()).isEqualTo(16);
assertThat(props.getIvfMinVectorsPerPartition()).isEqualTo(8);
assertThat(props.isRemoveExistingVectorStoreTable()).isTrue();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ Spring AI supports multiple relational databases via a dialect abstraction. The
- MySQL / MariaDB
- SQL Server
- HSQLDB
- Oracle Database
- Oracle AI Database

The correct dialect can be auto-detected from the JDBC URL when using `JdbcChatMemoryRepositoryDialect.from(DataSource)`. You can extend support for other databases by implementing the `JdbcChatMemoryRepositoryDialect` interface.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ These are the available implementations of the `VectorStore` interface:
* xref:api/vectordbs/mongodb.adoc[MongoDB Atlas Vector Store] - The https://www.mongodb.com/atlas/database[MongoDB Atlas] vector store.
* xref:api/vectordbs/neo4j.adoc[Neo4j Vector Store] - The https://neo4j.com/[Neo4j] vector store.
* xref:api/vectordbs/opensearch.adoc[OpenSearch Vector Store] - The https://opensearch.org/platform/search/vector-database.html[OpenSearch] vector store.
* xref:api/vectordbs/oracle.adoc[Oracle Vector Store] - The https://docs.oracle.com/en/database/oracle/oracle-database/23/vecse/overview-ai-vector-search.html[Oracle Database] vector store.
* xref:api/vectordbs/oracle.adoc[Oracle Vector Store] - The https://docs.oracle.com/en/database/oracle/oracle-database/26/vecse/overview-ai-vector-search.html[Oracle AI Database] vector store.
* xref:api/vectordbs/pgvector.adoc[PgVector Store] - The https://github.com/pgvector/pgvector[PostgreSQL/PGVector] vector store.
* xref:api/vectordbs/pinecone.adoc[Pinecone Vector Store] - https://www.pinecone.io/[Pinecone] vector store.
* xref:api/vectordbs/qdrant.adoc[Qdrant Vector Store] - https://www.qdrant.tech/[Qdrant] vector store.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
= Oracle Database 23ai - AI Vector Search
= Oracle AI Database - AI Vector Search

The link:https://docs.oracle.com/en/database/oracle/oracle-database/23/vecse/overview-ai-vector-search.html[AI Vector Search] capabilities of the Oracle Database 23ai (23.4+) are available as a Spring AI `VectorStore` to help you to store document embeddings and perform similarity searches. Of course, all other features are also available.
The link:https://docs.oracle.com/en/database/oracle/oracle-database/26/vecse/overview-ai-vector-search.html[AI Vector Search] capabilities of Oracle AI Database are available as a Spring AI `VectorStore` to help you to store document embeddings and perform similarity searches. Of course, all other features are also available.

TIP: The <<Run Oracle Database 23ai locally,Run Oracle Database 23ai locally>> appendix shows how to start a database with a lightweight Docker container.
TIP: The <<Run Oracle AI Database locally,Run Oracle AI Database locally>> appendix shows how to start a database with a lightweight Docker container.

== Auto-Configuration

Expand Down Expand Up @@ -35,6 +35,8 @@ If you need this vector store to initialize the schema for you then you'll need

NOTE: this is a breaking change! In earlier versions of Spring AI, this schema initialization happened by default.

NOTE: HNSW index creation requires an Oracle AI Database release that supports HNSW vector indexes. The local Testcontainers example below uses `gvenzl/oracle-free:23-slim`.

The Vector Store, also requires an `EmbeddingModel` instance to calculate embeddings for the documents.
You can pick one of the available xref:api/embeddings.adoc#available-implementations[EmbeddingModel Implementations].

Expand Down Expand Up @@ -109,7 +111,7 @@ You can use the following properties in your Spring Boot configuration to custom
|===
|Property| Description | Default value

|`spring.ai.vectorstore.oracle.index-type`| Nearest neighbor search index type. Options are `NONE` - exact nearest neighbor search, `IVF` - Inverted Flat File index. It has faster build times and uses less memory than HNSW, but has lower query performance (in terms of speed-recall tradeoff). `HNSW` - creates a multilayer graph. It has slower build times and uses more memory than IVF, but has better query performance (in terms of speed-recall tradeoff). | NONE
|`spring.ai.vectorstore.oracle.index-type`| Nearest neighbor search index type. Options are `NONE` - exact nearest neighbor search, `IVF` - Inverted Flat File index. It has faster build times and uses less memory than HNSW, but has lower query performance (in terms of speed-recall tradeoff). `HNSW` - creates a multilayer graph. It has slower build times and uses more memory than IVF, but has better query performance (in terms of speed-recall tradeoff). If Oracle rejects the configured index DDL at startup, initialization fails and the application must be reconfigured. | IVF
|`spring.ai.vectorstore.oracle.distance-type`| Search distance type among `COSINE` (default), `DOT`, `EUCLIDEAN`, `EUCLIDEAN_SQUARED`, and `MANHATTAN`.

NOTE: If vectors are normalized, you can use `DOT` or `COSINE` for best performance.| COSINE
Expand All @@ -122,6 +124,11 @@ NOTE: If vectors are normalized, you can use `DOT` or `COSINE` for best performa
|`spring.ai.vectorstore.oracle.remove-existing-vector-store-table` | Drops the existing table on start up. | false
|`spring.ai.vectorstore.oracle.initialize-schema` | Whether to initialize the required schema. | false
|`spring.ai.vectorstore.oracle.search-accuracy` | Denote the requested accuracy target in the presence of index. Disabled by default. You need to provide an integer in the range [1,100] to override the default index accuracy (95). Using lower accuracy provides approximate similarity search trading off speed versus accuracy. | -1 (`DEFAULT_SEARCH_ACCURACY`)
|`spring.ai.vectorstore.oracle.hnsw-neighbors` | HNSW `NEIGHBORS` index build parameter. Used only when `index-type=HNSW`. | 40
|`spring.ai.vectorstore.oracle.hnsw-ef-construction` | HNSW `EFCONSTRUCTION` index build parameter. Used only when `index-type=HNSW`. | 500
|`spring.ai.vectorstore.oracle.ivf-neighbor-partitions` | IVF `NEIGHBOR PARTITIONS` index build parameter. Used only when `index-type=IVF`. | 10
|`spring.ai.vectorstore.oracle.ivf-sample-per-partition` | Optional IVF `SAMPLE_PER_PARTITION` index build parameter. Omitted unless explicitly configured. | -1
|`spring.ai.vectorstore.oracle.ivf-min-vectors-per-partition` | Optional IVF `MIN_VECTORS_PER_PARTITION` index build parameter. Omitted unless explicitly configured. | -1

|===

Expand Down Expand Up @@ -192,19 +199,34 @@ To configure the `OracleVectorStore` in your application, you can use the follow
public VectorStore vectorStore(JdbcTemplate jdbcTemplate, EmbeddingModel embeddingModel) {
return OracleVectorStore.builder(jdbcTemplate, embeddingModel)
.tableName("my_vectors")
.indexType(OracleVectorStoreIndexType.IVF)
.indexType(OracleVectorStoreIndexType.HNSW)
.distanceType(OracleVectorStoreDistanceType.COSINE)
.dimensions(1536)
.searchAccuracy(95)
.hnswNeighbors(64)
.hnswEfConstruction(300)
.initializeSchema(true)
.build();
}
----

== Run Oracle Database 23ai locally
To customize IVF index creation instead, configure the IVF-specific builder settings:

[source,java]
----
docker run --rm --name oracle23ai -p 1521:1521 -e APP_USER=mlops -e APP_USER_PASSWORD=mlops -e ORACLE_PASSWORD=mlops gvenzl/oracle-free:23-slim
OracleVectorStore.builder(jdbcTemplate, embeddingModel)
.indexType(OracleVectorStoreIndexType.IVF)
.ivfNeighborPartitions(32)
.ivfSamplePerPartition(16)
.ivfMinVectorsPerPartition(8)
.initializeSchema(true)
.build();
----

== Run Oracle AI Database locally

----
docker run --rm --name oracle-ai-db -p 1521:1521 -e APP_USER=mlops -e APP_USER_PASSWORD=mlops -e ORACLE_PASSWORD=mlops gvenzl/oracle-free:23-slim
----

You can then connect to the database using:
Expand All @@ -221,10 +243,8 @@ The Oracle Vector Store implementation provides access to the underlying native
----
OracleVectorStore vectorStore = context.getBean(OracleVectorStore.class);
Optional<OracleConnection> nativeClient = vectorStore.getNativeClient();

if (nativeClient.isPresent()) {
OracleConnection connection = nativeClient.get();
// Use the native client for Oracle-specific operations
OracleConnection connection = nativeClient.get(); // Use the native client for Oracle-specific operations
}
----

Expand Down
2 changes: 1 addition & 1 deletion vector-stores/spring-ai-oracle-store/README.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
[Oracle AI Vector Search Documentation](https://docs.oracle.com/en/database/oracle/oracle-database/23/nfcoa/ai_vector_search.html)
[Oracle AI Vector Search Documentation](https://docs.oracle.com/en/database/oracle/oracle-database/26/nfcoa/ai_vector_search.html)
Loading
Loading