Jackie
Jackie

Reputation: 23607

How do I use Redis and have multiple Vector stores in Spring AI?

I am trying to do something where I have 2 advisors, one is a typical RAG vector store for my QuestionAnswerAdvisor and one is using redis for an embedding based cache of questions. The original QuestionAnswerAdvisor works great but when I add the redis store I don't see how to qualify the injected bean to make sure it is using redis instead of pinecone. I tried this

@Bean(name = "openAiBuildClient")
public ChatClient buildClient(
  @Qualifier("openAiChatClientBuilder") ChatClient.Builder openAiBuilder,
  MessageChatMemoryAdvisor messageChatMemoryAdvisor,
  RedisVectorStore redisVectorStore,
  PineconeVectorStore pineconeVectorStore
) {
spring:
  application:
    name: "my-spring-ai"
  ai:
    vectorstore:
      redis:
        index: spring-ai-example
        uri: redis://localhost:6379
      pinecone:
        apiKey: ${PINECONE_API_KEY}
        index-name: spring-ai-example

But it doesn't work like that...

Description:

Parameter 2 of method buildClient in org.example.config.clients.OpenAIClientConfig required a bean of type 'org.springframework.ai.vectorstore.RedisVectorStore' that could not be found.

The injection point has the following annotations:
    - @org.springframework.beans.factory.annotation.Qualifier("openAiBuildClient")

So is it possible to have multiple vector stores at the same time with an @Qualifier? If so how do I do it?

Update

It looks like this is why it isn't working

 RedisVectorStoreAutoConfiguration:
      Did not match:
         - @ConditionalOnBean (types: org.springframework.data.redis.connection.jedis.JedisConnectionFactory; SearchStrategy: all) did not find any beans of type org.springframework.data.redis.connection.jedis.JedisConnectionFactory (OnBeanCondition)
      Matched:
         - @ConditionalOnClass found required classes 'redis.clients.jedis.JedisPooled', 'org.springframework.data.redis.connection.jedis.JedisConnectionFactory', 'org.springframework.ai.vectorstore.RedisVectorStore', 'org.springframework.ai.embedding.EmbeddingModel' (OnClassCondition)

But I have

implementation "org.springframework.ai:spring-ai-redis-store-spring-boot-starter:${springAiVersion}"
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

I also tried

implementation 'org.springframework.data:spring-data-redis'

I also don't see anything about needing to declare a JedisConnectionFactory in the redis docs.

https://docs.spring.io/spring-ai/reference/api/vectordbs/redis.html#_manual_configuration

Update 2 ok back to the original question

I added (even though it must be missed in the docs)

@Bean
public JedisConnectionFactory redisConnectionFactory() {
  return new JedisConnectionFactory();
}

And now I get

Description:

The bean 'vectorStore', defined in class path resource [org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStoreAutoConfiguration.class], could not be registered. A bean with that name has already been defined in class path resource [org/springframework/ai/autoconfigure/vectorstore/pinecone/PineconeVectorStoreAutoConfiguration.class] and overriding is disabled.

Upvotes: 0

Views: 170

Answers (1)

Jackie
Jackie

Reputation: 23607

I am not going to accept this answer because it is a hack work around, however, manually creating the VectorStore worked...

package org.example.vectorstore;

import io.micrometer.observation.ObservationRegistry;
import org.springframework.ai.embedding.BatchingStrategy;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.vectorstore.RedisVectorStore;
import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPooled;

// TODO: add conditional
@Configuration
public class CustomRedisVectorStoreConfig {
    @Bean
    public JedisConnectionFactory redisConnectionFactory() {
        return new JedisConnectionFactory();
    }
    @Bean
    public RedisTemplate<String, Message> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Message> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new StringRedisSerializer());
        return template;
    }

    @Bean(name = "customRedisVectorStore")
    public RedisVectorStore redisVectorStore(EmbeddingModel embeddingModel, JedisConnectionFactory jedisConnectionFactory,
                                             ObjectProvider<ObservationRegistry> observationRegistry,
                                             ObjectProvider<VectorStoreObservationConvention> customObservationConvention,
                                             BatchingStrategy batchingStrategy) {
        var config = RedisVectorStore.RedisVectorStoreConfig.builder()
                                                            .withIndexName("spring-ai-example")
                                                            .withPrefix("prefix")
                                                            .build();

        return new RedisVectorStore(config, embeddingModel,
                new JedisPooled(jedisConnectionFactory.getHostName(), jedisConnectionFactory.getPort()),
                true, observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP),
                customObservationConvention.getIfAvailable(() -> null), batchingStrategy);
    }
}

Then I could inject it in with a qualifier...

 @Qualifier("customRedisVectorStore") VectorStore redisVectorStore,
 @Qualifier("vectorStore") VectorStore pineconeVectorStore

The problem here is the documentation do not say you should need to declare public JedisConnectionFactory redisConnectionFactory() or public RedisTemplate<String, Message> redisTemplate(RedisConnectionFactory connectionFactory)

Upvotes: 0

Related Questions