使用Redis Stack中间件作为向量数据库(Vector Database)实现文档数据的存储和查询功能。
已安装好的 redis stack ,redis stack 安装可参考:redis-stack 安装(Docker)
该项目基于Spring Boot 接入Ollama实现与Deepseek简单对话修改
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-redis-store-spring-boot-starter</artifactId>
</dependency>
spring:
application:
name: demo-boot-ollama
ai:
vectorstore:
redis:
initialize-schema: true
prefix: custom-prefix
index: custom-index
ollama: # ai 提供者 ollama
init:
pull-model-strategy: never # 下载策略
embedding:
additional-models:
- mxbai-embed-large #redis 向量库默认嵌入模型必须存在否则报错
- nomic-embed-text
- chroma/all-minilm-l6-v2-f32
base-url: http://192.168.31.162:11434
chat:
options:
model: deepseek-r1:8b
data:
redis:
host: 192.168.31.162
@SpringBootTest
public class RedisVectorTests {
@Resource
VectorStore vectorStore;
@Test
public void saveDocumentsAndQuery() {
List<Document> documents = List.of(
new Document("Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!!", Map.of("meta1", "meta1")),
new Document("The World is Big and Salvation Lurks Around the Corner"),
new Document("You walk forward facing the past and you turn back toward the future.", Map.of("meta2", "meta2")));
// 文档添加到redis
vectorStore.add(documents);
// 检索文档类似于查询
List<Document> results = this.vectorStore.similaritySearch(SearchRequest.builder().query("Spring").topK(5).build());
}
}
执行结果
提示:第二次执行,由于重复写入了两次所以数据有两份,但是查询结果确实根据相关度分数来的,且一共6个数据只查询了分数高的5个
Redis 数据查看:
通过源码可以看到在存入redis之前,对数据进行了大模型嵌入化,使用了mxbai-embed-large
模型
自定义RedisVectorStore, 添加以下配置文件:
@Configuration
public class CustomVectorEmbeddingModelConfig {
@Bean
public EmbeddingModel embeddingModel(OllamaApi ollamaApi) {
return new OllamaEmbeddingModel(
ollamaApi,
OllamaOptions.builder()
.model(OllamaModel.NOMIC_EMBED_TEXT.id()) //自定义嵌入模型 nomic-embed-text
.build(),
ObservationRegistry.NOOP,
ModelManagementOptions.defaults()
);
}
}
参考:org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreAutoConfiguration
中配置
可知,嵌入模型是通过外部已有的bean传递进来的,所以如果只是切换模型就直接重写EmbeddingModel
实现即可,参考上方。
启动之后断点查看
nomic-embed-text
注意:修改模型会导致已有数据不兼容!!!
自定义之后就可以继续玩复杂查询了
重写自定义RedisVectorStore(目前版本好像没有其他方法配置增加metadata过滤字段让查询时候生效),添加过滤metadata过滤字段
@Bean
public RedisVectorStore vectorStore(EmbeddingModel embeddingModel, RedisVectorStoreProperties properties, JedisConnectionFactory jedisConnectionFactory, ObjectProvider<ObservationRegistry> observationRegistry, ObjectProvider<VectorStoreObservationConvention> customObservationConvention, BatchingStrategy batchingStrategy) {
JedisPooled jedisPooled = this.jedisPooled(jedisConnectionFactory);
return ((RedisVectorStore.Builder)((RedisVectorStore.Builder)((RedisVectorStore.Builder)RedisVectorStore.builder(jedisPooled, embeddingModel)
.metadataFields(//定义 metadata 用于过滤(filtering)的数据字段
RedisVectorStore.MetadataField.tag("m1"),
RedisVectorStore.MetadataField.tag("m2")
)
.initializeSchema(properties.isInitializeSchema()).observationRegistry((ObservationRegistry)observationRegistry.getIfUnique(() -> {
return ObservationRegistry.NOOP;
}))).customObservationConvention((VectorStoreObservationConvention)customObservationConvention.getIfAvailable(() -> {
return null;
}))).batchingStrategy(batchingStrategy)).indexName(properties.getIndex()).prefix(properties.getPrefix()).build();
}
private JedisPooled jedisPooled(JedisConnectionFactory jedisConnectionFactory) {
String host = jedisConnectionFactory.getHostName();
int port = jedisConnectionFactory.getPort();
JedisClientConfig clientConfig = DefaultJedisClientConfig.builder().ssl(jedisConnectionFactory.isUseSsl()).clientName(jedisConnectionFactory.getClientName()).timeoutMillis(jedisConnectionFactory.getTimeout()).password(jedisConnectionFactory.getPassword()).build();
return new JedisPooled(new HostAndPort(host, port), clientConfig);
}
重点代码:
...其他忽略...
return ((RedisVectorStore.Builder)((RedisVectorStore.Builder)((RedisVectorStore.Builder)
RedisVectorStore.builder(jedisPooled, embeddingModel)
.metadataFields(//定义 metadata 用于过滤(filtering)的数据字段
RedisVectorStore.MetadataField.tag("m1"),
RedisVectorStore.MetadataField.tag("m2")
)
...其他忽略...
其余代码参考:org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreAutoConfiguration
生成新数据:
List<Document> documents = List.of(
new Document("Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!!", Map.of("m2", "meta1")),
new Document("The World is Big and Salvation Lurks Around the Corner"),
new Document("You walk forward facing the past and you turn back toward the future.", Map.of("m1", "meta2")));
metadata 过滤查询:
List<Document> result = redisVectorStore.similaritySearch(
SearchRequest.builder()
.query("Spring")
.topK(5)
.similarityThreshold(SearchRequest.SIMILARITY_THRESHOLD_ACCEPT_ALL)
.filterExpression(
// "m2 in ['meta1'] || m1 in ['meta2']"
b.or(
b.in("m2", "meta1"),
b.in("m1","meta2")
).build()
).build());
执行结果:
注意哈:虽然只匹配到了一个metadata但是评分高0.7***。评分和算法有关。
更多玩法:
http://blog.xqlee.com/article/2502181644396060.html