Add batching for more vector stores

Apply batching when adding Documents to the following vector stores:

- Chroma
- ElasticSearch
- Neo4j
- Qdrant
- Redis
- Typesense
- Weaviate

This improves efficiency by processing multiple Documents at once instead of individually, reducing the overhead for each operation.

Related to #1261
This commit is contained in:
Soby Chacko
2024-09-05 16:51:33 -04:00
committed by Mark Pollack
parent 4f62ac1cee
commit 15fdd05fac
21 changed files with 217 additions and 76 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023 - 2024 the original author or authors.
* Copyright 2023-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.
@@ -13,10 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.autoconfigure.vectorstore.chroma;
import org.springframework.ai.chroma.ChromaApi;
import org.springframework.ai.embedding.BatchingStrategy;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.TokenCountBatchingStrategy;
import org.springframework.ai.vectorstore.ChromaVectorStore;
import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention;
import org.springframework.beans.factory.ObjectProvider;
@@ -36,6 +39,7 @@ import io.micrometer.observation.ObservationRegistry;
/**
* @author Christian Tzolov
* @author Eddú Meléndez
* @author Soby Chacko
*/
@AutoConfiguration
@ConditionalOnClass({ EmbeddingModel.class, RestClient.class, ChromaVectorStore.class, ObjectMapper.class })
@@ -73,14 +77,21 @@ public class ChromaVectorStoreAutoConfiguration {
return chromaApi;
}
@Bean
@ConditionalOnMissingBean(BatchingStrategy.class)
BatchingStrategy chromaBatchingStrategy() {
return new TokenCountBatchingStrategy();
}
@Bean
@ConditionalOnMissingBean
public ChromaVectorStore vectorStore(EmbeddingModel embeddingModel, ChromaApi chromaApi,
ChromaVectorStoreProperties storeProperties, ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<VectorStoreObservationConvention> customObservationConvention) {
ObjectProvider<VectorStoreObservationConvention> customObservationConvention,
BatchingStrategy chromaBatchingStrategy) {
return new ChromaVectorStore(embeddingModel, chromaApi, storeProperties.getCollectionName(),
storeProperties.isInitializeSchema(), observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP),
customObservationConvention.getIfAvailable(() -> null));
customObservationConvention.getIfAvailable(() -> null), chromaBatchingStrategy);
}
static class PropertiesChromaConnectionDetails implements ChromaConnectionDetails {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023 - 2024 the original author or authors.
* Copyright 2023-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.
@@ -13,11 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.autoconfigure.vectorstore.elasticsearch;
import org.elasticsearch.client.RestClient;
import org.springframework.ai.embedding.BatchingStrategy;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.TokenCountBatchingStrategy;
import org.springframework.ai.vectorstore.ElasticsearchVectorStore;
import org.springframework.ai.vectorstore.ElasticsearchVectorStoreOptions;
import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention;
@@ -37,19 +40,26 @@ import io.micrometer.observation.ObservationRegistry;
* @author Wei Jiang
* @author Josh Long
* @author Christian Tzolov
* @author Soby Chacko
* @since 1.0.0
*/
@AutoConfiguration(after = ElasticsearchRestClientAutoConfiguration.class)
@ConditionalOnClass({ ElasticsearchVectorStore.class, EmbeddingModel.class, RestClient.class })
@EnableConfigurationProperties(ElasticsearchVectorStoreProperties.class)
class ElasticsearchVectorStoreAutoConfiguration {
@Bean
@ConditionalOnMissingBean(BatchingStrategy.class)
BatchingStrategy batchingStrategy() {
return new TokenCountBatchingStrategy();
}
@Bean
@ConditionalOnMissingBean
ElasticsearchVectorStore vectorStore(ElasticsearchVectorStoreProperties properties, RestClient restClient,
EmbeddingModel embeddingModel, ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<VectorStoreObservationConvention> customObservationConvention) {
ObjectProvider<VectorStoreObservationConvention> customObservationConvention,
BatchingStrategy batchingStrategy) {
ElasticsearchVectorStoreOptions elasticsearchVectorStoreOptions = new ElasticsearchVectorStoreOptions();
if (StringUtils.hasText(properties.getIndexName())) {
@@ -64,7 +74,7 @@ class ElasticsearchVectorStoreAutoConfiguration {
return new ElasticsearchVectorStore(elasticsearchVectorStoreOptions, restClient, embeddingModel,
properties.isInitializeSchema(), observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP),
customObservationConvention.getIfAvailable(() -> null));
customObservationConvention.getIfAvailable(() -> null), batchingStrategy);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023 - 2024 the original author or authors.
* Copyright 2023-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.
@@ -13,11 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.autoconfigure.vectorstore.neo4j;
import org.neo4j.driver.Driver;
import org.springframework.ai.embedding.BatchingStrategy;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.TokenCountBatchingStrategy;
import org.springframework.ai.vectorstore.Neo4jVectorStore;
import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention;
import org.springframework.beans.factory.ObjectProvider;
@@ -34,17 +37,25 @@ import io.micrometer.observation.ObservationRegistry;
* @author Jingzhou Ou
* @author Josh Long
* @author Christian Tzolov
* @author Soby Chacko
*/
@AutoConfiguration(after = Neo4jAutoConfiguration.class)
@ConditionalOnClass({ Neo4jVectorStore.class, EmbeddingModel.class, Driver.class })
@EnableConfigurationProperties({ Neo4jVectorStoreProperties.class })
public class Neo4jVectorStoreAutoConfiguration {
@Bean
@ConditionalOnMissingBean(BatchingStrategy.class)
BatchingStrategy batchingStrategy() {
return new TokenCountBatchingStrategy();
}
@Bean
@ConditionalOnMissingBean
public Neo4jVectorStore vectorStore(Driver driver, EmbeddingModel embeddingModel,
Neo4jVectorStoreProperties properties, ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<VectorStoreObservationConvention> customObservationConvention) {
ObjectProvider<VectorStoreObservationConvention> customObservationConvention,
BatchingStrategy batchingStrategy) {
Neo4jVectorStore.Neo4jVectorStoreConfig config = Neo4jVectorStore.Neo4jVectorStoreConfig.builder()
.withDatabaseName(properties.getDatabaseName())
.withEmbeddingDimension(properties.getEmbeddingDimension())
@@ -58,7 +69,7 @@ public class Neo4jVectorStoreAutoConfiguration {
return new Neo4jVectorStore(driver, embeddingModel, config, properties.isInitializeSchema(),
observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP),
customObservationConvention.getIfAvailable(() -> null));
customObservationConvention.getIfAvailable(() -> null), batchingStrategy);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023 - 2024 the original author or authors.
* Copyright 2023-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.
@@ -13,12 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.autoconfigure.vectorstore.qdrant;
import io.micrometer.observation.ObservationRegistry;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import org.springframework.ai.embedding.BatchingStrategy;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.TokenCountBatchingStrategy;
import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention;
import org.springframework.ai.vectorstore.qdrant.QdrantVectorStore;
import org.springframework.beans.factory.ObjectProvider;
@@ -32,6 +36,7 @@ import org.springframework.context.annotation.Bean;
* @author Anush Shetty
* @author Eddú Meléndez
* @author Christian Tzolov
* @author Soby Chacko
* @since 0.8.1
*/
@AutoConfiguration
@@ -58,14 +63,21 @@ public class QdrantVectorStoreAutoConfiguration {
return new QdrantClient(grpcClientBuilder.build());
}
@Bean
@ConditionalOnMissingBean(BatchingStrategy.class)
BatchingStrategy batchingStrategy() {
return new TokenCountBatchingStrategy();
}
@Bean
@ConditionalOnMissingBean
public QdrantVectorStore vectorStore(EmbeddingModel embeddingModel, QdrantVectorStoreProperties properties,
QdrantClient qdrantClient, ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<VectorStoreObservationConvention> customObservationConvention) {
ObjectProvider<VectorStoreObservationConvention> customObservationConvention,
BatchingStrategy batchingStrategy) {
return new QdrantVectorStore(qdrantClient, properties.getCollectionName(), embeddingModel,
properties.isInitializeSchema(), observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP),
customObservationConvention.getIfAvailable(() -> null));
customObservationConvention.getIfAvailable(() -> null), batchingStrategy);
}
static class PropertiesQdrantConnectionDetails implements QdrantConnectionDetails {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023 - 2024 the original author or authors.
* Copyright 2023-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.
@@ -13,9 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.autoconfigure.vectorstore.redis;
import org.springframework.ai.embedding.BatchingStrategy;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.TokenCountBatchingStrategy;
import org.springframework.ai.vectorstore.RedisVectorStore;
import org.springframework.ai.vectorstore.RedisVectorStore.RedisVectorStoreConfig;
import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention;
@@ -35,6 +38,7 @@ import redis.clients.jedis.JedisPooled;
/**
* @author Christian Tzolov
* @author Eddú Meléndez
* @author Soby Chacko
*/
@AutoConfiguration(after = RedisAutoConfiguration.class)
@ConditionalOnClass({ JedisPooled.class, JedisConnectionFactory.class, RedisVectorStore.class, EmbeddingModel.class })
@@ -42,11 +46,18 @@ import redis.clients.jedis.JedisPooled;
@EnableConfigurationProperties(RedisVectorStoreProperties.class)
public class RedisVectorStoreAutoConfiguration {
@Bean
@ConditionalOnMissingBean(BatchingStrategy.class)
BatchingStrategy batchingStrategy() {
return new TokenCountBatchingStrategy();
}
@Bean
@ConditionalOnMissingBean
public RedisVectorStore vectorStore(EmbeddingModel embeddingModel, RedisVectorStoreProperties properties,
JedisConnectionFactory jedisConnectionFactory, ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<VectorStoreObservationConvention> customObservationConvention) {
ObjectProvider<VectorStoreObservationConvention> customObservationConvention,
BatchingStrategy batchingStrategy) {
var config = RedisVectorStoreConfig.builder()
.withIndexName(properties.getIndex())
@@ -56,7 +67,7 @@ public class RedisVectorStoreAutoConfiguration {
return new RedisVectorStore(config, embeddingModel,
new JedisPooled(jedisConnectionFactory.getHostName(), jedisConnectionFactory.getPort()),
properties.isInitializeSchema(), observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP),
customObservationConvention.getIfAvailable(() -> null));
customObservationConvention.getIfAvailable(() -> null), batchingStrategy);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023 - 2024 the original author or authors.
* Copyright 2023-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.
@@ -13,9 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.autoconfigure.vectorstore.typesense;
import org.springframework.ai.embedding.BatchingStrategy;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.TokenCountBatchingStrategy;
import org.springframework.ai.vectorstore.TypesenseVectorStore;
import org.springframework.ai.vectorstore.TypesenseVectorStore.TypesenseVectorStoreConfig;
import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention;
@@ -38,6 +41,7 @@ import java.util.List;
/**
* @author Pablo Sanchidrian Herrera
* @author Eddú Meléndez
* @author Soby Chacko
*/
@AutoConfiguration
@ConditionalOnClass({ TypesenseVectorStore.class, EmbeddingModel.class })
@@ -51,11 +55,18 @@ public class TypesenseVectorStoreAutoConfiguration {
return new TypesenseVectorStoreAutoConfiguration.PropertiesTypesenseConnectionDetails(properties);
}
@Bean
@ConditionalOnMissingBean(BatchingStrategy.class)
BatchingStrategy batchingStrategy() {
return new TokenCountBatchingStrategy();
}
@Bean
@ConditionalOnMissingBean
public TypesenseVectorStore vectorStore(Client typesenseClient, EmbeddingModel embeddingModel,
TypesenseVectorStoreProperties properties, ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<VectorStoreObservationConvention> customObservationConvention) {
ObjectProvider<VectorStoreObservationConvention> customObservationConvention,
BatchingStrategy batchingStrategy) {
TypesenseVectorStoreConfig config = TypesenseVectorStoreConfig.builder()
.withCollectionName(properties.getCollectionName())
@@ -64,7 +75,7 @@ public class TypesenseVectorStoreAutoConfiguration {
return new TypesenseVectorStore(typesenseClient, embeddingModel, config, properties.isInitializeSchema(),
observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP),
customObservationConvention.getIfAvailable(() -> null));
customObservationConvention.getIfAvailable(() -> null), batchingStrategy);
}
@Bean

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.autoconfigure.vectorstore.weaviate;
import io.micrometer.observation.ObservationRegistry;
@@ -20,7 +21,10 @@ import io.weaviate.client.Config;
import io.weaviate.client.WeaviateAuthClient;
import io.weaviate.client.WeaviateClient;
import io.weaviate.client.v1.auth.exception.AuthException;
import org.springframework.ai.embedding.BatchingStrategy;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.TokenCountBatchingStrategy;
import org.springframework.ai.vectorstore.WeaviateVectorStore;
import org.springframework.ai.vectorstore.WeaviateVectorStore.WeaviateVectorStoreConfig;
import org.springframework.ai.vectorstore.WeaviateVectorStore.WeaviateVectorStoreConfig.MetadataField;
@@ -62,11 +66,18 @@ public class WeaviateVectorStoreAutoConfiguration {
}
}
@Bean
@ConditionalOnMissingBean(BatchingStrategy.class)
BatchingStrategy batchingStrategy() {
return new TokenCountBatchingStrategy();
}
@Bean
@ConditionalOnMissingBean
public WeaviateVectorStore vectorStore(EmbeddingModel embeddingModel, WeaviateClient weaviateClient,
WeaviateVectorStoreProperties properties, ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<VectorStoreObservationConvention> customObservationConvention) {
ObjectProvider<VectorStoreObservationConvention> customObservationConvention,
BatchingStrategy batchingStrategy) {
WeaviateVectorStoreConfig.Builder configBuilder = WeaviateVectorStore.WeaviateVectorStoreConfig.builder()
.withObjectClass(properties.getObjectClass())
@@ -79,7 +90,7 @@ public class WeaviateVectorStoreAutoConfiguration {
return new WeaviateVectorStore(configBuilder.build(), embeddingModel, weaviateClient,
observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP),
customObservationConvention.getIfAvailable(() -> null));
customObservationConvention.getIfAvailable(() -> null), batchingStrategy);
}
static class PropertiesWeaviateConnectionDetails implements WeaviateConnectionDetails {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023 - 2024 the original author or authors.
* Copyright 2023-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.
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.vectorstore;
import java.util.ArrayList;
@@ -26,7 +27,10 @@ import org.springframework.ai.chroma.ChromaApi.AddEmbeddingsRequest;
import org.springframework.ai.chroma.ChromaApi.DeleteEmbeddingsRequest;
import org.springframework.ai.chroma.ChromaApi.Embedding;
import org.springframework.ai.document.Document;
import org.springframework.ai.embedding.BatchingStrategy;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.EmbeddingOptionsBuilder;
import org.springframework.ai.embedding.TokenCountBatchingStrategy;
import org.springframework.ai.observation.conventions.VectorStoreProvider;
import org.springframework.ai.vectorstore.filter.FilterExpressionConverter;
import org.springframework.ai.vectorstore.observation.AbstractObservationVectorStore;
@@ -69,18 +73,21 @@ public class ChromaVectorStore extends AbstractObservationVectorStore implements
private final boolean initializeSchema;
private final BatchingStrategy batchingStrategy;
public ChromaVectorStore(EmbeddingModel embeddingModel, ChromaApi chromaApi, boolean initializeSchema) {
this(embeddingModel, chromaApi, DEFAULT_COLLECTION_NAME, initializeSchema);
}
public ChromaVectorStore(EmbeddingModel embeddingModel, ChromaApi chromaApi, String collectionName,
boolean initializeSchema) {
this(embeddingModel, chromaApi, collectionName, initializeSchema, ObservationRegistry.NOOP, null);
this(embeddingModel, chromaApi, collectionName, initializeSchema, ObservationRegistry.NOOP, null,
new TokenCountBatchingStrategy());
}
public ChromaVectorStore(EmbeddingModel embeddingModel, ChromaApi chromaApi, String collectionName,
boolean initializeSchema, ObservationRegistry observationRegistry,
VectorStoreObservationConvention customObservationConvention) {
VectorStoreObservationConvention customObservationConvention, BatchingStrategy batchingStrategy) {
super(observationRegistry, customObservationConvention);
@@ -89,6 +96,7 @@ public class ChromaVectorStore extends AbstractObservationVectorStore implements
this.collectionName = collectionName;
this.initializeSchema = initializeSchema;
this.filterExpressionConverter = new ChromaFilterExpressionConverter();
this.batchingStrategy = batchingStrategy;
}
public void setFilterExpressionConverter(FilterExpressionConverter filterExpressionConverter) {
@@ -108,11 +116,13 @@ public class ChromaVectorStore extends AbstractObservationVectorStore implements
List<String> contents = new ArrayList<>();
List<float[]> embeddings = new ArrayList<>();
this.embeddingModel.embed(documents, EmbeddingOptionsBuilder.builder().build(), this.batchingStrategy);
for (Document document : documents) {
ids.add(document.getId());
metadatas.add(document.getMetadata());
contents.add(document.getContent());
document.setEmbedding(this.embeddingModel.embed(document));
document.setEmbedding(document.getEmbedding());
embeddings.add(document.getEmbedding());
}

View File

@@ -26,6 +26,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.ai.chroma.ChromaApi;
import org.springframework.ai.document.Document;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.TokenCountBatchingStrategy;
import org.springframework.ai.observation.conventions.SpringAiKind;
import org.springframework.ai.observation.conventions.VectorStoreProvider;
import org.springframework.ai.openai.OpenAiEmbeddingModel;
@@ -169,7 +170,8 @@ public class ChromaVectorStoreObservationIT {
@Bean
public VectorStore chromaVectorStore(EmbeddingModel embeddingModel, ChromaApi chromaApi,
ObservationRegistry observationRegistry) {
return new ChromaVectorStore(embeddingModel, chromaApi, "TestCollection", true, observationRegistry, null);
return new ChromaVectorStore(embeddingModel, chromaApi, "TestCollection", true, observationRegistry, null,
new TokenCountBatchingStrategy());
}
@Bean

View File

@@ -28,7 +28,10 @@ import org.elasticsearch.client.RestClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.document.Document;
import org.springframework.ai.embedding.BatchingStrategy;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.EmbeddingOptionsBuilder;
import org.springframework.ai.embedding.TokenCountBatchingStrategy;
import org.springframework.ai.model.EmbeddingUtils;
import org.springframework.ai.observation.conventions.VectorStoreProvider;
import org.springframework.ai.observation.conventions.VectorStoreSimilarityMetric;
@@ -85,18 +88,21 @@ public class ElasticsearchVectorStore extends AbstractObservationVectorStore imp
private final boolean initializeSchema;
private final BatchingStrategy batchingStrategy;
public ElasticsearchVectorStore(RestClient restClient, EmbeddingModel embeddingModel, boolean initializeSchema) {
this(new ElasticsearchVectorStoreOptions(), restClient, embeddingModel, initializeSchema);
}
public ElasticsearchVectorStore(ElasticsearchVectorStoreOptions options, RestClient restClient,
EmbeddingModel embeddingModel, boolean initializeSchema) {
this(options, restClient, embeddingModel, initializeSchema, ObservationRegistry.NOOP, null);
this(options, restClient, embeddingModel, initializeSchema, ObservationRegistry.NOOP, null,
new TokenCountBatchingStrategy());
}
public ElasticsearchVectorStore(ElasticsearchVectorStoreOptions options, RestClient restClient,
EmbeddingModel embeddingModel, boolean initializeSchema, ObservationRegistry observationRegistry,
VectorStoreObservationConvention customObservationConvention) {
VectorStoreObservationConvention customObservationConvention, BatchingStrategy batchingStrategy) {
super(observationRegistry, customObservationConvention);
@@ -109,6 +115,7 @@ public class ElasticsearchVectorStore extends AbstractObservationVectorStore imp
this.embeddingModel = embeddingModel;
this.options = options;
this.filterExpressionConverter = new ElasticsearchAiSearchFilterExpressionConverter();
this.batchingStrategy = batchingStrategy;
}
@Override
@@ -120,11 +127,9 @@ public class ElasticsearchVectorStore extends AbstractObservationVectorStore imp
}
BulkRequest.Builder bulkRequestBuilder = new BulkRequest.Builder();
this.embeddingModel.embed(documents, EmbeddingOptionsBuilder.builder().build(), this.batchingStrategy);
for (Document document : documents) {
if (Objects.isNull(document.getEmbedding()) || document.getEmbedding().length == 0) {
logger.debug("Calling EmbeddingModel for document id = " + document.getId());
document.setEmbedding(this.embeddingModel.embed(document));
}
bulkRequestBuilder.operations(op -> op
.index(idx -> idx.index(this.options.getIndexName()).id(document.getId()).document(document)));
}

View File

@@ -33,6 +33,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
import org.springframework.ai.document.Document;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.TokenCountBatchingStrategy;
import org.springframework.ai.observation.conventions.SpringAiKind;
import org.springframework.ai.observation.conventions.VectorStoreProvider;
import org.springframework.ai.openai.OpenAiEmbeddingModel;
@@ -202,7 +203,7 @@ public class ElasticsearchVectorStoreObservationIT {
public ElasticsearchVectorStore vectorStoreDefault(EmbeddingModel embeddingModel, RestClient restClient,
ObservationRegistry observationRegistry) {
return new ElasticsearchVectorStore(new ElasticsearchVectorStoreOptions(), restClient, embeddingModel, true,
observationRegistry, null);
observationRegistry, null, new TokenCountBatchingStrategy());
}
@Bean

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023 - 2024 the original author or authors.
* Copyright 2023-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.
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.vectorstore;
import java.util.HashMap;
@@ -26,7 +27,10 @@ import org.neo4j.driver.Driver;
import org.neo4j.driver.SessionConfig;
import org.neo4j.driver.Values;
import org.springframework.ai.document.Document;
import org.springframework.ai.embedding.BatchingStrategy;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.EmbeddingOptionsBuilder;
import org.springframework.ai.embedding.TokenCountBatchingStrategy;
import org.springframework.ai.observation.conventions.VectorStoreProvider;
import org.springframework.ai.observation.conventions.VectorStoreSimilarityMetric;
import org.springframework.ai.vectorstore.filter.Neo4jVectorFilterExpressionConverter;
@@ -43,6 +47,7 @@ import io.micrometer.observation.ObservationRegistry;
* @author Michael Simons
* @author Christian Tzolov
* @author Thomas Vitale
* @author Soby Chacko
*/
public class Neo4jVectorStore extends AbstractObservationVectorStore implements InitializingBean {
@@ -284,31 +289,33 @@ public class Neo4jVectorStore extends AbstractObservationVectorStore implements
private final boolean initializeSchema;
private final BatchingStrategy batchingStrategy;
public Neo4jVectorStore(Driver driver, EmbeddingModel embeddingModel, Neo4jVectorStoreConfig config,
boolean initializeSchema) {
this(driver, embeddingModel, config, initializeSchema, ObservationRegistry.NOOP, null);
this(driver, embeddingModel, config, initializeSchema, ObservationRegistry.NOOP, null,
new TokenCountBatchingStrategy());
}
public Neo4jVectorStore(Driver driver, EmbeddingModel embeddingModel, Neo4jVectorStoreConfig config,
boolean initializeSchema, ObservationRegistry observationRegistry,
VectorStoreObservationConvention customObservationConvention) {
VectorStoreObservationConvention customObservationConvention, BatchingStrategy batchingStrategy) {
super(observationRegistry, customObservationConvention);
this.initializeSchema = initializeSchema;
Assert.notNull(driver, "Neo4j driver must not be null");
Assert.notNull(embeddingModel, "Embedding model must not be null");
this.driver = driver;
this.embeddingModel = embeddingModel;
this.config = config;
this.batchingStrategy = batchingStrategy;
}
@Override
public void doAdd(List<Document> documents) {
this.embeddingModel.embed(documents, EmbeddingOptionsBuilder.builder().build(), this.batchingStrategy);
var rows = documents.stream().map(this::documentToRecord).toList();
try (var session = this.driver.session()) {
@@ -398,8 +405,7 @@ public class Neo4jVectorStore extends AbstractObservationVectorStore implements
}
private Map<String, Object> documentToRecord(Document document) {
var embedding = this.embeddingModel.embed(document);
document.setEmbedding(embedding);
document.setEmbedding(document.getEmbedding());
var row = new HashMap<String, Object>();
@@ -411,7 +417,7 @@ public class Neo4jVectorStore extends AbstractObservationVectorStore implements
document.getMetadata().forEach((k, v) -> properties.put("metadata." + k, Values.value(v)));
row.put("properties", properties);
row.put(this.config.embeddingProperty, Values.value(embedding));
row.put(this.config.embeddingProperty, Values.value(document.getEmbedding()));
return row;
}

View File

@@ -31,6 +31,7 @@ import org.neo4j.driver.Driver;
import org.neo4j.driver.GraphDatabase;
import org.springframework.ai.document.Document;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.TokenCountBatchingStrategy;
import org.springframework.ai.observation.conventions.SpringAiKind;
import org.springframework.ai.observation.conventions.VectorStoreProvider;
import org.springframework.ai.openai.OpenAiEmbeddingModel;
@@ -174,7 +175,7 @@ public class Neo4jVectorStoreObservationIT {
ObservationRegistry observationRegistry) {
return new Neo4jVectorStore(driver, embeddingModel, Neo4jVectorStore.Neo4jVectorStoreConfig.defaultConfig(),
true, observationRegistry, null);
true, observationRegistry, null, new TokenCountBatchingStrategy());
}
@Bean

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023 - 2024 the original author or authors.
* Copyright 2023-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.
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.vectorstore.qdrant;
import static io.qdrant.client.PointIdFactory.id;
@@ -27,7 +28,10 @@ import java.util.UUID;
import java.util.concurrent.ExecutionException;
import org.springframework.ai.document.Document;
import org.springframework.ai.embedding.BatchingStrategy;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.EmbeddingOptionsBuilder;
import org.springframework.ai.embedding.TokenCountBatchingStrategy;
import org.springframework.ai.model.EmbeddingUtils;
import org.springframework.ai.observation.conventions.VectorStoreProvider;
import org.springframework.ai.observation.conventions.VectorStoreSimilarityMetric;
@@ -58,6 +62,7 @@ import io.qdrant.client.grpc.Points.UpdateStatus;
* @author Christian Tzolov
* @author Eddú Meléndez
* @author Josh Long
* @author Soby Chacko
* @since 0.8.1
*/
public class QdrantVectorStore extends AbstractObservationVectorStore implements InitializingBean {
@@ -78,6 +83,8 @@ public class QdrantVectorStore extends AbstractObservationVectorStore implements
private final boolean initializeSchema;
private final BatchingStrategy batchingStrategy;
/**
* Configuration class for the QdrantVectorStore.
*
@@ -161,7 +168,8 @@ public class QdrantVectorStore extends AbstractObservationVectorStore implements
*/
public QdrantVectorStore(QdrantClient qdrantClient, String collectionName, EmbeddingModel embeddingModel,
boolean initializeSchema) {
this(qdrantClient, collectionName, embeddingModel, initializeSchema, ObservationRegistry.NOOP, null);
this(qdrantClient, collectionName, embeddingModel, initializeSchema, ObservationRegistry.NOOP, null,
new TokenCountBatchingStrategy());
}
/**
@@ -175,7 +183,7 @@ public class QdrantVectorStore extends AbstractObservationVectorStore implements
*/
public QdrantVectorStore(QdrantClient qdrantClient, String collectionName, EmbeddingModel embeddingModel,
boolean initializeSchema, ObservationRegistry observationRegistry,
VectorStoreObservationConvention customObservationConvention) {
VectorStoreObservationConvention customObservationConvention, BatchingStrategy batchingStrategy) {
super(observationRegistry, customObservationConvention);
@@ -187,6 +195,7 @@ public class QdrantVectorStore extends AbstractObservationVectorStore implements
this.embeddingModel = embeddingModel;
this.collectionName = collectionName;
this.qdrantClient = qdrantClient;
this.batchingStrategy = batchingStrategy;
}
/**
@@ -196,16 +205,17 @@ public class QdrantVectorStore extends AbstractObservationVectorStore implements
@Override
public void doAdd(List<Document> documents) {
try {
List<PointStruct> points = documents.stream().map(document -> {
// Compute and assign an embedding to the document.
document.setEmbedding(this.embeddingModel.embed(document));
return PointStruct.newBuilder()
// Compute and assign an embedding to the document.
this.embeddingModel.embed(documents, EmbeddingOptionsBuilder.builder().build(), this.batchingStrategy);
List<PointStruct> points = documents.stream()
.map(document -> PointStruct.newBuilder()
.setId(id(UUID.fromString(document.getId())))
.setVectors(vectors(document.getEmbedding()))
.putAllPayload(toPayload(document))
.build();
}).toList();
.build())
.toList();
this.qdrantClient.upsertAsync(this.collectionName, points).get();
}

View File

@@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
import org.springframework.ai.document.Document;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.TokenCountBatchingStrategy;
import org.springframework.ai.mistralai.MistralAiEmbeddingModel;
import org.springframework.ai.mistralai.api.MistralAiApi;
import org.springframework.ai.observation.conventions.SpringAiKind;
@@ -191,8 +192,8 @@ public class QdrantVectorStoreObservationIT {
@Bean
public VectorStore qdrantVectorStore(EmbeddingModel embeddingModel, QdrantClient qdrantClient,
ObservationRegistry observationRegistry) {
return new QdrantVectorStore(qdrantClient, COLLECTION_NAME, embeddingModel, true, observationRegistry,
null);
return new QdrantVectorStore(qdrantClient, COLLECTION_NAME, embeddingModel, true, observationRegistry, null,
new TokenCountBatchingStrategy());
}
@Bean

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023 - 2024 the original author or authors.
* Copyright 2023-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.
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.vectorstore;
import java.text.MessageFormat;
@@ -29,7 +30,10 @@ import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.document.Document;
import org.springframework.ai.embedding.BatchingStrategy;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.EmbeddingOptionsBuilder;
import org.springframework.ai.embedding.TokenCountBatchingStrategy;
import org.springframework.ai.observation.conventions.VectorStoreProvider;
import org.springframework.ai.vectorstore.filter.FilterExpressionConverter;
import org.springframework.ai.vectorstore.observation.AbstractObservationVectorStore;
@@ -75,6 +79,7 @@ import redis.clients.jedis.search.schemafields.VectorField.VectorAlgorithm;
* @author Christian Tzolov
* @author Eddú Meléndez
* @author Thomas Vitale
* @author Soby Chacko
* @see VectorStore
* @see RedisVectorStoreConfig
* @see EmbeddingModel
@@ -278,15 +283,18 @@ public class RedisVectorStore extends AbstractObservationVectorStore implements
private FilterExpressionConverter filterExpressionConverter;
private final BatchingStrategy batchingStrategy;
public RedisVectorStore(RedisVectorStoreConfig config, EmbeddingModel embeddingModel, JedisPooled jedis,
boolean initializeSchema) {
this(config, embeddingModel, jedis, initializeSchema, ObservationRegistry.NOOP, null);
this(config, embeddingModel, jedis, initializeSchema, ObservationRegistry.NOOP, null,
new TokenCountBatchingStrategy());
}
public RedisVectorStore(RedisVectorStoreConfig config, EmbeddingModel embeddingModel, JedisPooled jedis,
boolean initializeSchema, ObservationRegistry observationRegistry,
VectorStoreObservationConvention customObservationConvention) {
VectorStoreObservationConvention customObservationConvention, BatchingStrategy batchingStrategy) {
super(observationRegistry, customObservationConvention);
@@ -298,6 +306,7 @@ public class RedisVectorStore extends AbstractObservationVectorStore implements
this.embeddingModel = embeddingModel;
this.config = config;
this.filterExpressionConverter = new RedisFilterExpressionConverter(this.config.metadataFields);
this.batchingStrategy = batchingStrategy;
}
public JedisPooled getJedis() {
@@ -307,12 +316,13 @@ public class RedisVectorStore extends AbstractObservationVectorStore implements
@Override
public void doAdd(List<Document> documents) {
try (Pipeline pipeline = this.jedis.pipelined()) {
for (Document document : documents) {
var embedding = this.embeddingModel.embed(document);
document.setEmbedding(embedding);
this.embeddingModel.embed(documents, EmbeddingOptionsBuilder.builder().build(), this.batchingStrategy);
for (Document document : documents) {
document.setEmbedding(document.getEmbedding());
var fields = new HashMap<String, Object>();
fields.put(this.config.embeddingFieldName, embedding);
fields.put(this.config.embeddingFieldName, document.getEmbedding());
fields.put(this.config.contentFieldName, document.getContent());
fields.putAll(document.getMetadata());
pipeline.jsonSetWithEscape(key(document.getId()), JSON_SET_PATH, fields);

View File

@@ -26,6 +26,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.ai.document.Document;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.TokenCountBatchingStrategy;
import org.springframework.ai.observation.conventions.SpringAiKind;
import org.springframework.ai.observation.conventions.VectorStoreProvider;
import org.springframework.ai.transformers.TransformersEmbeddingModel;
@@ -178,7 +179,7 @@ public class RedisVectorStoreObservationIT {
.build(),
embeddingModel,
new JedisPooled(jedisConnectionFactory.getHostName(), jedisConnectionFactory.getPort()), true,
observationRegistry, null);
observationRegistry, null, new TokenCountBatchingStrategy());
}
@Bean

View File

@@ -26,7 +26,10 @@ import java.util.stream.Stream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.document.Document;
import org.springframework.ai.embedding.BatchingStrategy;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.EmbeddingOptionsBuilder;
import org.springframework.ai.embedding.TokenCountBatchingStrategy;
import org.springframework.ai.observation.conventions.VectorStoreProvider;
import org.springframework.ai.observation.conventions.VectorStoreSimilarityMetric;
import org.springframework.ai.vectorstore.filter.FilterExpressionConverter;
@@ -85,6 +88,8 @@ public class TypesenseVectorStore extends AbstractObservationVectorStore impleme
private final boolean initializeSchema;
private final BatchingStrategy batchingStrategy;
public static class TypesenseVectorStoreConfig {
private final String collectionName;
@@ -162,12 +167,13 @@ public class TypesenseVectorStore extends AbstractObservationVectorStore impleme
public TypesenseVectorStore(Client client, EmbeddingModel embeddingModel, TypesenseVectorStoreConfig config,
boolean initializeSchema) {
this(client, embeddingModel, config, initializeSchema, ObservationRegistry.NOOP, null);
this(client, embeddingModel, config, initializeSchema, ObservationRegistry.NOOP, null,
new TokenCountBatchingStrategy());
}
public TypesenseVectorStore(Client client, EmbeddingModel embeddingModel, TypesenseVectorStoreConfig config,
boolean initializeSchema, ObservationRegistry observationRegistry,
VectorStoreObservationConvention customObservationConvention) {
VectorStoreObservationConvention customObservationConvention, BatchingStrategy batchingStrategy) {
super(observationRegistry, customObservationConvention);
@@ -178,19 +184,21 @@ public class TypesenseVectorStore extends AbstractObservationVectorStore impleme
this.embeddingModel = embeddingModel;
this.config = config;
this.initializeSchema = initializeSchema;
this.batchingStrategy = batchingStrategy;
}
@Override
public void doAdd(List<Document> documents) {
Assert.notNull(documents, "Documents must not be null");
this.embeddingModel.embed(documents, EmbeddingOptionsBuilder.builder().build(), this.batchingStrategy);
List<HashMap<String, Object>> documentList = documents.stream().map(document -> {
HashMap<String, Object> typesenseDoc = new HashMap<>();
typesenseDoc.put(DOC_ID_FIELD_NAME, document.getId());
typesenseDoc.put(CONTENT_FIELD_NAME, document.getContent());
typesenseDoc.put(METADATA_FIELD_NAME, document.getMetadata());
float[] embedding = this.embeddingModel.embed(document.getContent());
typesenseDoc.put(EMBEDDING_FIELD_NAME, embedding);
typesenseDoc.put(EMBEDDING_FIELD_NAME, document.getEmbedding());
return typesenseDoc;
}).toList();

View File

@@ -27,6 +27,7 @@ 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.embedding.TokenCountBatchingStrategy;
import org.springframework.ai.observation.conventions.SpringAiKind;
import org.springframework.ai.observation.conventions.VectorStoreProvider;
import org.springframework.ai.transformers.TransformersEmbeddingModel;
@@ -169,7 +170,8 @@ public class TypesenseVectorStoreObservationIT {
.withEmbeddingDimension(embeddingModel.dimensions())
.build();
return new TypesenseVectorStore(client, embeddingModel, config, true, observationRegistry, null);
return new TypesenseVectorStore(client, embeddingModel, config, true, observationRegistry, null,
new TokenCountBatchingStrategy());
}
@Bean

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.vectorstore;
import java.util.ArrayList;
@@ -24,7 +25,10 @@ import java.util.Optional;
import java.util.stream.Collectors;
import org.springframework.ai.document.Document;
import org.springframework.ai.embedding.BatchingStrategy;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.EmbeddingOptionsBuilder;
import org.springframework.ai.embedding.TokenCountBatchingStrategy;
import org.springframework.ai.model.EmbeddingUtils;
import org.springframework.ai.observation.conventions.VectorStoreProvider;
import org.springframework.ai.vectorstore.WeaviateVectorStore.WeaviateVectorStoreConfig.ConsistentLevel;
@@ -96,6 +100,8 @@ public class WeaviateVectorStore extends AbstractObservationVectorStore {
private final String weaviateObjectClass;
private final BatchingStrategy batchingStrategy;
/**
* List of metadata fields (as field name and type) that can be used in similarity
* search query filter expressions. The {@link Document#getMetadata()} can contain
@@ -290,7 +296,8 @@ public class WeaviateVectorStore extends AbstractObservationVectorStore {
*/
public WeaviateVectorStore(WeaviateVectorStoreConfig vectorStoreConfig, EmbeddingModel embeddingModel,
WeaviateClient weaviateClient) {
this(vectorStoreConfig, embeddingModel, weaviateClient, ObservationRegistry.NOOP, null);
this(vectorStoreConfig, embeddingModel, weaviateClient, ObservationRegistry.NOOP, null,
new TokenCountBatchingStrategy());
}
/**
@@ -303,7 +310,7 @@ public class WeaviateVectorStore extends AbstractObservationVectorStore {
*/
public WeaviateVectorStore(WeaviateVectorStoreConfig vectorStoreConfig, EmbeddingModel embeddingModel,
WeaviateClient weaviateClient, ObservationRegistry observationRegistry,
VectorStoreObservationConvention customObservationConvention) {
VectorStoreObservationConvention customObservationConvention, BatchingStrategy batchingStrategy) {
super(observationRegistry, customObservationConvention);
@@ -318,6 +325,7 @@ public class WeaviateVectorStore extends AbstractObservationVectorStore {
this.filterMetadataFields.stream().map(MetadataField::name).toList());
this.weaviateClient = weaviateClient;
this.weaviateSimilaritySearchFields = buildWeaviateSimilaritySearchFields();
this.batchingStrategy = batchingStrategy;
}
private Field[] buildWeaviateSimilaritySearchFields() {
@@ -347,6 +355,8 @@ public class WeaviateVectorStore extends AbstractObservationVectorStore {
return;
}
this.embeddingModel.embed(documents, EmbeddingOptionsBuilder.builder().build(), this.batchingStrategy);
List<WeaviateObject> weaviateObjects = documents.stream().map(this::toWeaviateObject).toList();
Result<ObjectGetResponse[]> response = this.weaviateClient.batch()
@@ -385,11 +395,6 @@ public class WeaviateVectorStore extends AbstractObservationVectorStore {
private WeaviateObject toWeaviateObject(Document document) {
if (document.getEmbedding() == null || document.getEmbedding().length == 0) {
float[] embedding = this.embeddingModel.embed(document);
document.setEmbedding(embedding);
}
// https://weaviate.io/developers/weaviate/config-refs/datatypes
Map<String, Object> fields = new HashMap<>();
fields.put(CONTENT_FIELD_NAME, document.getContent());

View File

@@ -25,6 +25,7 @@ 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.embedding.TokenCountBatchingStrategy;
import org.springframework.ai.observation.conventions.SpringAiKind;
import org.springframework.ai.observation.conventions.VectorStoreProvider;
import org.springframework.ai.transformers.TransformersEmbeddingModel;
@@ -165,7 +166,8 @@ public class WeaviateVectorStoreObservationIT {
.withConsistencyLevel(WeaviateVectorStoreConfig.ConsistentLevel.ONE)
.build();
return new WeaviateVectorStore(config, embeddingModel, weaviateClient, observationRegistry, null);
return new WeaviateVectorStore(config, embeddingModel, weaviateClient, observationRegistry, null,
new TokenCountBatchingStrategy());
}