Add builder pattern to HanaCloudVectorStore and refactor package name

This commit is contained in:
Soby Chacko
2024-12-17 16:16:10 -05:00
committed by Mark Pollack
parent 7a7a914dc0
commit 71584e5238
11 changed files with 190 additions and 42 deletions

View File

@@ -142,7 +142,7 @@ import jakarta.persistence.Table;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.jackson.Jacksonized;
import org.springframework.ai.vectorstore.HanaVectorEntity;
import org.springframework.ai.vectorstore.hanadb.HanaVectorEntity;
@Entity
@Table(name = "CRICKET_WORLD_CUP")
@@ -165,7 +165,7 @@ package com.interviewpedia.spring.ai.hana;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import org.springframework.ai.vectorstore.HanaVectorRepository;
import org.springframework.ai.vectorstore.hanadb.HanaVectorRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@@ -246,7 +246,7 @@ import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.ai.document.Document;
import org.springframework.ai.reader.pdf.PagePdfDocumentReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.HanaCloudVectorStore;
import org.springframework.ai.vectorstore.hanadb.HanaCloudVectorStore;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;

View File

@@ -21,10 +21,9 @@ import javax.sql.DataSource;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.vectorstore.HanaCloudVectorStore;
import org.springframework.ai.vectorstore.HanaCloudVectorStoreConfig;
import org.springframework.ai.vectorstore.HanaVectorEntity;
import org.springframework.ai.vectorstore.HanaVectorRepository;
import org.springframework.ai.vectorstore.hanadb.HanaCloudVectorStore;
import org.springframework.ai.vectorstore.hanadb.HanaVectorEntity;
import org.springframework.ai.vectorstore.hanadb.HanaVectorRepository;
import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
@@ -39,6 +38,7 @@ import org.springframework.context.annotation.Bean;
*
* @author Rahul Mittal
* @author Christian Tzolov
* @author Soby Chacko
* @since 1.0.0
*/
@AutoConfiguration(after = { JpaRepositoriesAutoConfiguration.class })
@@ -53,13 +53,14 @@ public class HanaCloudVectorStoreAutoConfiguration {
ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<VectorStoreObservationConvention> customObservationConvention) {
return new HanaCloudVectorStore(repository, embeddingModel,
HanaCloudVectorStoreConfig.builder()
.tableName(properties.getTableName())
.topK(properties.getTopK())
.build(),
observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP),
customObservationConvention.getIfAvailable(() -> null));
return HanaCloudVectorStore.builder()
.repository(repository)
.embeddingModel(embeddingModel)
.tableName(properties.getTableName())
.topK(properties.getTopK())
.observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP))
.customObservationConvention(customObservationConvention.getIfAvailable(() -> null))
.build();
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.ai.vectorstore;
package org.springframework.ai.vectorstore.hanadb;
import java.util.Collections;
import java.util.List;
@@ -34,9 +34,12 @@ import org.springframework.ai.model.EmbeddingUtils;
import org.springframework.ai.observation.conventions.VectorStoreProvider;
import org.springframework.ai.observation.conventions.VectorStoreSimilarityMetric;
import org.springframework.ai.util.JacksonUtils;
import org.springframework.ai.vectorstore.AbstractVectorStoreBuilder;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.observation.AbstractObservationVectorStore;
import org.springframework.ai.vectorstore.observation.VectorStoreObservationContext;
import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention;
import org.springframework.util.Assert;
/**
* The <b>SAP HANA Cloud vector engine</b> offers multiple use cases in AI scenarios.
@@ -67,6 +70,7 @@ import org.springframework.ai.vectorstore.observation.VectorStoreObservationConv
* @author Rahul Mittal
* @author Christian Tzolov
* @author Sebastien Deleuze
* @author Soby Chacko
* @see <a href=
* "https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-vector-engine-guide/introduction">SAP
* HANA Database Vector Engine Guide</a>
@@ -78,30 +82,71 @@ public class HanaCloudVectorStore extends AbstractObservationVectorStore {
private final HanaVectorRepository<? extends HanaVectorEntity> repository;
private final EmbeddingModel embeddingModel;
private final String tableName;
private final HanaCloudVectorStoreConfig config;
private final int topK;
private final ObjectMapper objectMapper;
/**
* Creates a new HanaCloudVectorStore with basic configuration.
* @param repository the HANA vector repository
* @param embeddingModel the embedding model to use
* @param config the vector store configuration
* @deprecated Since 1.0.0-M5, use {@link #builder()} instead
*/
@Deprecated(since = "1.0.0-M5", forRemoval = true)
public HanaCloudVectorStore(HanaVectorRepository<? extends HanaVectorEntity> repository,
EmbeddingModel embeddingModel, HanaCloudVectorStoreConfig config) {
this(repository, embeddingModel, config, ObservationRegistry.NOOP, null);
}
/**
* Creates a new HanaCloudVectorStore with observation configuration.
* @param repository the HANA vector repository
* @param embeddingModel the embedding model to use
* @param config the vector store configuration
* @param observationRegistry the observation registry
* @param customObservationConvention the custom observation convention
* @deprecated Since 1.0.0-M5, use {@link #builder()} instead
*/
@Deprecated(since = "1.0.0-M5", forRemoval = true)
public HanaCloudVectorStore(HanaVectorRepository<? extends HanaVectorEntity> repository,
EmbeddingModel embeddingModel, HanaCloudVectorStoreConfig config, ObservationRegistry observationRegistry,
VectorStoreObservationConvention customObservationConvention) {
super(observationRegistry, customObservationConvention);
this(builder().repository(repository)
.embeddingModel(embeddingModel)
.tableName(config.getTableName())
.topK(config.getTopK())
.observationRegistry(observationRegistry)
.customObservationConvention(customObservationConvention));
}
this.repository = repository;
this.embeddingModel = embeddingModel;
this.config = config;
/**
* Protected constructor that accepts a builder instance. This is the preferred way to
* create new HanaCloudVectorStore instances.
* @param builder the configured builder instance
*/
protected HanaCloudVectorStore(HanaCloudBuilder builder) {
super(builder);
Assert.notNull(builder.repository, "Repository must not be null");
this.repository = builder.repository;
this.tableName = builder.tableName;
this.topK = builder.topK;
this.objectMapper = JsonMapper.builder().addModules(JacksonUtils.instantiateAvailableModules()).build();
}
/**
* Creates a new builder for configuring and creating HanaCloudVectorStore instances.
* @return a new builder instance
*/
public static HanaCloudBuilder builder() {
return new HanaCloudBuilder();
}
@Override
public void doAdd(List<Document> documents) {
int count = 1;
@@ -110,27 +155,27 @@ public class HanaCloudVectorStore extends AbstractObservationVectorStore {
document.getId());
String content = document.getContent().replaceAll("\\s+", " ");
String embedding = getEmbedding(document);
this.repository.save(this.config.getTableName(), document.getId(), embedding, content);
this.repository.save(this.tableName, document.getId(), embedding, content);
}
logger.info("Embeddings saved in HanaCloudVectorStore for {} documents", count - 1);
}
@Override
public Optional<Boolean> doDelete(List<String> idList) {
int deleteCount = this.repository.deleteEmbeddingsById(this.config.getTableName(), idList);
int deleteCount = this.repository.deleteEmbeddingsById(this.tableName, idList);
logger.info("{} embeddings deleted", deleteCount);
return Optional.of(deleteCount == idList.size());
}
public int purgeEmbeddings() {
int deleteCount = this.repository.deleteAllEmbeddings(this.config.getTableName());
int deleteCount = this.repository.deleteAllEmbeddings(this.tableName);
logger.info("{} embeddings deleted", deleteCount);
return deleteCount;
}
@Override
public List<Document> similaritySearch(String query) {
return similaritySearch(SearchRequest.query(query).withTopK(this.config.getTopK()));
return similaritySearch(SearchRequest.query(query).withTopK(this.topK));
}
@Override
@@ -141,8 +186,8 @@ public class HanaCloudVectorStore extends AbstractObservationVectorStore {
}
String queryEmbedding = getEmbedding(request);
List<? extends HanaVectorEntity> searchResult = this.repository
.cosineSimilaritySearch(this.config.getTableName(), request.getTopK(), queryEmbedding);
List<? extends HanaVectorEntity> searchResult = this.repository.cosineSimilaritySearch(this.tableName,
request.getTopK(), queryEmbedding);
logger.info("Hana cosine-similarity for query={}, with topK={} returned {} results", request.getQuery(),
request.getTopK(), searchResult.size());
@@ -175,8 +220,63 @@ public class HanaCloudVectorStore extends AbstractObservationVectorStore {
return VectorStoreObservationContext.builder(VectorStoreProvider.HANA.value(), operationName)
.dimensions(this.embeddingModel.dimensions())
.collectionName(this.config.getTableName())
.collectionName(this.tableName)
.similarityMetric(VectorStoreSimilarityMetric.COSINE.value());
}
/**
* Builder class for creating {@link HanaCloudVectorStore} instances.
* <p>
* Provides a fluent API for configuring all aspects of the HANA Cloud vector store.
*
* @since 1.0.0
*/
public static class HanaCloudBuilder extends AbstractVectorStoreBuilder<HanaCloudBuilder> {
private HanaVectorRepository<? extends HanaVectorEntity> repository;
private String tableName;
private int topK;
/**
* Sets the HANA vector repository.
* @param repository the repository to use
* @return the builder instance
* @throws IllegalArgumentException if repository is null
*/
public HanaCloudBuilder repository(HanaVectorRepository<? extends HanaVectorEntity> repository) {
Assert.notNull(repository, "Repository must not be null");
this.repository = repository;
return this;
}
/**
* Sets the table name for vector storage.
* @param tableName the name of the table to use
* @return the builder instance
*/
public HanaCloudBuilder tableName(String tableName) {
this.tableName = tableName;
return this;
}
/**
* Sets the number of top results to return.
* @param topK the number of results
* @return the builder instance
*/
public HanaCloudBuilder topK(int topK) {
this.topK = topK;
return this;
}
@Override
public HanaCloudVectorStore build() {
validate();
return new HanaCloudVectorStore(this);
}
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.ai.vectorstore;
package org.springframework.ai.vectorstore.hanadb;
/**
* The {@code HanaCloudVectorStoreConfig} class represents the configuration for the
@@ -23,7 +23,9 @@ package org.springframework.ai.vectorstore;
*
* @author Rahul Mittal
* @since 1.0.0
* @deprecated Since 1.0.0-M5, use {@link HanaCloudVectorStore#builder()}
*/
@Deprecated(since = "1.0.0-M5", forRemoval = true)
public final class HanaCloudVectorStoreConfig {
private String tableName;
@@ -33,34 +35,64 @@ public final class HanaCloudVectorStoreConfig {
private HanaCloudVectorStoreConfig() {
}
/**
* Creates a new builder for HanaCloudVectorStoreConfig.
* @return a new builder instance
* @deprecated Since 1.0.0-M5, use {@link HanaCloudVectorStore#builder()}
*/
@Deprecated(since = "1.0.0-M5", forRemoval = true)
public static HanaCloudVectorStoreConfigBuilder builder() {
return new HanaCloudVectorStoreConfigBuilder();
}
/**
* @deprecated Since 1.0.0-M5, use {@link HanaCloudVectorStore#builder()}
*/
@Deprecated(since = "1.0.0-M5", forRemoval = true)
public String getTableName() {
return this.tableName;
}
/**
* @deprecated Since 1.0.0-M5, use {@link HanaCloudVectorStore#builder()}
*/
@Deprecated(since = "1.0.0-M5", forRemoval = true)
public int getTopK() {
return this.topK;
}
/**
* @deprecated Since 1.0.0-M5, use {@link HanaCloudVectorStore#builder()}
*/
@Deprecated(since = "1.0.0-M5", forRemoval = true)
public static class HanaCloudVectorStoreConfigBuilder {
private String tableName;
private int topK;
/**
* @deprecated Since 1.0.0-M5, use {@link HanaCloudVectorStore#builder()}
*/
@Deprecated(since = "1.0.0-M5", forRemoval = true)
public HanaCloudVectorStoreConfigBuilder tableName(String tableName) {
this.tableName = tableName;
return this;
}
/**
* @deprecated Since 1.0.0-M5, use {@link HanaCloudVectorStore#builder()}
*/
@Deprecated(since = "1.0.0-M5", forRemoval = true)
public HanaCloudVectorStoreConfigBuilder topK(int topK) {
this.topK = topK;
return this;
}
/**
* @deprecated Since 1.0.0-M5, use {@link HanaCloudVectorStore#builder()}
*/
@Deprecated(since = "1.0.0-M5", forRemoval = true)
public HanaCloudVectorStoreConfig build() {
HanaCloudVectorStoreConfig config = new HanaCloudVectorStoreConfig();
config.tableName = this.tableName;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.ai.vectorstore;
package org.springframework.ai.vectorstore.hanadb;
import jakarta.persistence.Column;
import jakarta.persistence.Id;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.ai.vectorstore;
package org.springframework.ai.vectorstore.hanadb;
import java.util.List;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.ai.vectorstore;
package org.springframework.ai.vectorstore.hanadb;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.ai.vectorstore;
package org.springframework.ai.vectorstore.hanadb;
import java.io.IOException;
import java.util.List;
@@ -33,6 +33,7 @@ import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.ai.document.Document;
import org.springframework.ai.reader.pdf.PagePdfDocumentReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.ai.vectorstore;
package org.springframework.ai.vectorstore.hanadb;
import java.util.List;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.ai.vectorstore;
package org.springframework.ai.vectorstore.hanadb;
import java.util.List;
import java.util.function.Function;
@@ -34,6 +34,7 @@ import org.springframework.ai.openai.OpenAiEmbeddingModel;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.ai.reader.pdf.PagePdfDocumentReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@@ -89,8 +90,13 @@ public class HanaCloudVectorStoreIT {
@Bean
public VectorStore hanaCloudVectorStore(CricketWorldCupRepository cricketWorldCupRepository,
EmbeddingModel embeddingModel) {
return new HanaCloudVectorStore(cricketWorldCupRepository, embeddingModel,
HanaCloudVectorStoreConfig.builder().tableName("CRICKET_WORLD_CUP").topK(1).build());
return HanaCloudVectorStore.builder()
.repository(cricketWorldCupRepository)
.embeddingModel(embeddingModel)
.tableName("CRICKET_WORLD_CUP")
.topK(1)
.build();
}
@Bean

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.ai.vectorstore;
package org.springframework.ai.vectorstore.hanadb;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@@ -36,6 +36,8 @@ import org.springframework.ai.observation.conventions.VectorStoreProvider;
import org.springframework.ai.observation.conventions.VectorStoreSimilarityMetric;
import org.springframework.ai.openai.OpenAiEmbeddingModel;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.vectorstore.observation.DefaultVectorStoreObservationConvention;
import org.springframework.ai.vectorstore.observation.VectorStoreObservationDocumentation.HighCardinalityKeyNames;
import org.springframework.ai.vectorstore.observation.VectorStoreObservationDocumentation.LowCardinalityKeyNames;
@@ -164,9 +166,15 @@ public class HanaVectorStoreObservationIT {
@Bean
public VectorStore hanaCloudVectorStore(CricketWorldCupRepository cricketWorldCupRepository,
EmbeddingModel embeddingModel, ObservationRegistry observationRegistry) {
return new HanaCloudVectorStore(cricketWorldCupRepository, embeddingModel,
HanaCloudVectorStoreConfig.builder().tableName(TEST_TABLE_NAME).topK(1).build(),
observationRegistry, null);
return HanaCloudVectorStore.builder()
.repository(cricketWorldCupRepository)
.embeddingModel(embeddingModel)
.tableName(TEST_TABLE_NAME)
.topK(1)
.observationRegistry(observationRegistry)
.customObservationConvention(null)
.build();
}
@Bean