Redis
本节将指导您设置 RedisVectorStore,以存储文档嵌入并执行相似度搜索。
Redis 是一款开源(BSD 许可证)的内存数据结构存储,可用作数据库、缓存、消息代理和流式引擎。Redis 提供诸如字符串、哈希、列表、集合、支持范围查询的有序集合、位图、超日志、地理空间索引和流等数据结构。
Redis Search and Query 扩展了 Redis OSS 的核心功能,并允许您将 Redis 用作向量数据库:
-
在哈希或 JSON 文档中存储向量及其关联的元数据
-
检索向量
-
执行向量相似度搜索 (KNN)
-
执行带有半径阈值的范围向量搜索
-
对 TEXT 字段执行全文搜索
-
支持多种距离度量 (COSINE, L2, IP) 和向量算法 (HNSW, FLAT)
先决条件
-
一个 Redis Stack 实例
- Redis Cloud(推荐)
- Docker 镜像 redis/redis-stack:latest
-
EmbeddingModel实例,用于计算文档嵌入。可选方案包括:- 如需要,为 EmbeddingModel 提供 API 密钥,以生成由
RedisVectorStore存储的嵌入。
- 如需要,为 EmbeddingModel 提供 API 密钥,以生成由
自动配置
Spring AI 自动配置以及 Starter 模块的制品名称发生了重大变更。请参阅 升级说明 以获取更多信息。
Spring AI 为 Redis 向量存储提供了 Spring Boot 自动配置功能。要启用该功能,请将以下依赖项添加到项目的 Maven pom.xml 文件中:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-redis</artifactId>
</dependency>
或添加到您的 Gradle build.gradle 构建文件中。
dependencies {
implementation 'org.springframework.ai:spring-ai-starter-vector-store-redis'
}
请参考依赖管理部分,将 Spring AI BOM 添加到您的构建文件中。
请参见 构件仓库 部分,以便在你的构建文件中添加 Maven Central 和/或 Snapshot 仓库。
向量存储实现可以为您初始化所需的架构,但您必须通过以下方式选择启用:在相应的构造函数中指定 initializeSchema 布尔值,或在 application.properties 文件中设置 …initialize-schema=true。
这是一个破坏性变更!在早期版本的 Spring AI 中,此 schema 初始化默认会执行。
请查看向量存储的 配置参数列表,以了解默认值和配置选项。
此外,你还需要配置一个 EmbeddingModel Bean。更多信息请参考 EmbeddingModel 部分。
现在你可以在应用程序中将 RedisVectorStore 作为向量存储进行自动装配。
@Autowired VectorStore vectorStore;
// ...
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")));
// Add the documents to Redis
vectorStore.add(documents);
// Retrieve documents similar to a query
List<Document> results = this.vectorStore.similaritySearch(SearchRequest.builder().query("Spring").topK(5).build());
配置属性
要连接 Redis 并使用 RedisVectorStore,需要提供实例的访问详情。可以通过 Spring Boot 的 application.yml 提供一个简单的配置,
spring:
data:
redis:
url: <redis instance url>
ai:
vectorstore:
redis:
initialize-schema: true
index-name: custom-index
prefix: custom-prefix
对于 redis 连接配置,或者可以通过 Spring Boot 的 application.properties 提供简单的配置。
spring.data.redis.host=localhost
spring.data.redis.port=6379
spring.data.redis.username=default
spring.data.redis.password=
以 spring.ai.vectorstore.redis.* 开头的属性用于配置 RedisVectorStore:
| 属性 | 描述 | 默认值 |
|---|---|---|
spring.ai.vectorstore.redis.initialize-schema | 是否初始化所需的 schema | false |
spring.ai.vectorstore.redis.index-name | 用于存储向量的索引名称 | spring-ai-index |
spring.ai.vectorstore.redis.prefix | Redis 键的前缀 | embedding: |
spring.ai.vectorstore.redis.distance-metric | 向量相似度距离度量(COSINE、L2、IP) | COSINE |
spring.ai.vectorstore.redis.vector-algorithm | 向量索引算法(HNSW、FLAT) | HNSW |
spring.ai.vectorstore.redis.hnsw-m | HNSW:最大出连接数 | 16 |
spring.ai.vectorstore.redis.hnsw-ef-construction | HNSW:构建索引时的最大连接数 | 200 |
spring.ai.vectorstore.redis.hnsw-ef-runtime | HNSW:搜索时考虑的连接数 | 10 |
spring.ai.vectorstore.redis.default-range-threshold | 范围搜索的默认半径阈值 | 0.8 |
spring.ai.vectorstore.redis.text-scorer | 文本评分算法(BM25、TFIDF、BM25STD、DISMAX、DOCSCORE) | BM25 |
元数据过滤
你还可以在 Redis 中使用通用且可移植的元数据过滤器。
例如,你可以使用文本表达式语言:
vectorStore.similaritySearch(SearchRequest.builder()
.query("The World")
.topK(TOP_K)
.similarityThreshold(SIMILARITY_THRESHOLD)
.filterExpression("country in ['UK', 'NL'] && year >= 2020").build());
或者以编程方式使用 Filter.Expression DSL:
FilterExpressionBuilder b = new FilterExpressionBuilder();
vectorStore.similaritySearch(SearchRequest.builder()
.query("The World")
.topK(TOP_K)
.similarityThreshold(SIMILARITY_THRESHOLD)
.filterExpression(b.and(
b.in("country", "UK", "NL"),
b.gte("year", 2020)).build()).build());
这些(可移植的)过滤表达式会被自动转换为 Redis 搜索查询。
例如,以下便携式过滤表达式:
country in ['UK', 'NL'] && year >= 2020
转换为专有 Redis 过滤格式:
@country:{UK | NL} @year:[2020 inf]
手动配置
您可以手动配置 Redis 向量存储,而不是使用 Spring Boot 自动配置。为此,您需要在项目中添加 spring-ai-redis-store:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-redis-store</artifactId>
</dependency>
或添加到你的 Gradle build.gradle 构建文件中。
dependencies {
implementation 'org.springframework.ai:spring-ai-redis-store'
}
创建一个 RedisClient Bean:
@Bean
public RedisClient jedisClient() {
return RedisClient.builder().hostAndPort("<host>", 6379).build();
}
然后使用建造者模式创建 RedisVectorStore Bean:
@Bean
public VectorStore vectorStore(RedisClient jedisClient, EmbeddingModel embeddingModel) {
return RedisVectorStore.builder(jedisClient, embeddingModel)
.indexName("custom-index") // Optional: defaults to "spring-ai-index"
.prefix("custom-prefix") // Optional: defaults to "embedding:"
.contentFieldName("content") // Optional: field for document content
.embeddingFieldName("embedding") // Optional: field for vector embeddings
.vectorAlgorithm(Algorithm.HNSW) // Optional: HNSW or FLAT (defaults to HNSW)
.distanceMetric(DistanceMetric.COSINE) // Optional: COSINE, L2, or IP (defaults to COSINE)
.hnswM(16) // Optional: HNSW connections (defaults to 16)
.hnswEfConstruction(200) // Optional: HNSW build parameter (defaults to 200)
.hnswEfRuntime(10) // Optional: HNSW search parameter (defaults to 10)
.defaultRangeThreshold(0.8) // Optional: default radius for range searches
.textScorer(TextScorer.BM25) // Optional: text scoring algorithm (defaults to BM25)
.metadataFields( // Optional: define metadata fields for filtering
MetadataField.tag("country"),
MetadataField.numeric("year"),
MetadataField.text("description"))
.initializeSchema(true) // Optional: defaults to false
.batchingStrategy(new TokenCountBatchingStrategy()) // Optional: defaults to TokenCountBatchingStrategy
.build();
}
// This can be any EmbeddingModel implementation
@Bean
public EmbeddingModel embeddingModel() {
return new OpenAiEmbeddingModel(OpenAiEmbeddingOptions.builder().apiKey(System.getenv("OPENAI_API_KEY")).build());
}
必须显式列出任何在过滤表达式中使用的元数据字段的所有字段名和类型(TAG、TEXT 或 NUMERIC)。上述 metadataFields 注册了可过滤的元数据字段:类型为 TAG 的 country 和类型为 NUMERIC 的 year。
访问原生客户端
Redis 向量存储实现通过 getNativeClient() 方法提供对底层原生 Redis 客户端(RedisClient)的访问:
RedisVectorStore vectorStore = context.getBean(RedisVectorStore.class);
Optional<RedisClient> nativeClient = vectorStore.getNativeClient();
if (nativeClient.isPresent()) {
RedisClient jedisClient = nativeClient.get();
// Use the native client for Redis-specific operations
}
原生客户端为您提供访问 Redis 特定功能和操作的能力,这些功能可能未通过 VectorStore 接口暴露。
距离度量
Redis Vector Store 支持三种用于向量相似度的距离度量:
-
COSINE:余弦相似度(默认)- 测量向量间夹角的余弦值
-
L2:欧几里得距离 - 测量向量间的直线距离
-
IP:内积 - 测量向量间的点积
每个指标会自动归一化为 0-1 的相似度分数,其中 1 表示最相似。
RedisVectorStore vectorStore = RedisVectorStore.builder(jedisClient, embeddingModel)
.distanceMetric(DistanceMetric.COSINE) // or L2, IP
.build();
HNSW 算法配置
Redis 向量存储默认使用 HNSW(Hierarchical Navigable Small World,分层可导航小世界)算法以进行高效的近似最近邻搜索。您可以根据具体使用场景调整 HNSW 参数:
RedisVectorStore vectorStore = RedisVectorStore.builder(jedisClient, embeddingModel)
.vectorAlgorithm(Algorithm.HNSW)
.hnswM(32) // Maximum outgoing connections per node (default: 16)
.hnswEfConstruction(100) // Connections during index building (default: 200)
.hnswEfRuntime(50) // Connections during search (default: 10)
.build();
参数指南:
-
M: 更高的值能提升召回率,但会增加内存使用和索引时间。典型值:12-48。
-
EF_CONSTRUCTION: 更高的值能提升索引质量,但会增加构建时间。典型值:100-500。
-
EF_RUNTIME: 更高的值能提升搜索准确性,但会增加延迟。典型值:10-100。
对于较小的数据集,或者当需要精确结果时,请使用 FLAT 算法:
RedisVectorStore vectorStore = RedisVectorStore.builder(jedisClient, embeddingModel)
.vectorAlgorithm(Algorithm.FLAT)
.build();
文本搜索
Redis 向量存储通过 Redis 查询引擎的全文搜索功能提供文本搜索能力。这允许您根据 TEXT 字段中的关键词和短语来查找文档:
// Search for documents containing specific text
List<Document> textResults = vectorStore.searchByText(
"machine learning", // search query
"content", // field to search (must be TEXT type)
10, // limit
"category == 'AI'" // optional filter expression
);
文本搜索支持:
-
单词搜索
-
当
inOrder为 true 时,执行精确匹配的短语搜索 -
当
inOrder为 false 时,执行基于术语的 OR 语义搜索 -
停用词过滤,用于忽略常见词
-
多种文本评分算法
在构造时配置文本搜索行为:
RedisVectorStore vectorStore = RedisVectorStore.builder(jedisClient, embeddingModel)
.textScorer(TextScorer.TFIDF) // Text scoring algorithm
.inOrder(true) // Match terms in order
.stopwords(Set.of("is", "a", "the", "and")) // Ignore common words
.metadataFields(MetadataField.text("description")) // Define TEXT fields
.build();
文本评分算法
有多种文本评分算法可用:
-
BM25:带有词项饱和(term saturation)的 TF-IDF 现代版本(默认)
-
TFIDF:经典的词频-逆文档频率
-
BM25STD:标准化 BM25
-
DISMAX:析取最大值
-
DOCSCORE:文档得分
分数会归一化到 0-1 的范围,以与向量相似度分数保持一致。
范围搜索
范围搜索返回指定半径阈值内的所有文档,而不是固定数量的最近邻:
// Search with explicit radius
List<Document> rangeResults = vectorStore.searchByRange(
"AI and machine learning", // query
0.8, // radius (similarity threshold)
"category == 'AI'" // optional filter expression
);
还可以在构造时设置默认的区间阈值:
RedisVectorStore vectorStore = RedisVectorStore.builder(jedisClient, embeddingModel)
.defaultRangeThreshold(0.8) // Set default threshold
.build();
// Use default threshold
List<Document> results = vectorStore.searchByRange("query");
当希望检索所有相似度高于某个阈值的文档,而不是限制为特定数量时,范围搜索(Range search)非常有用。
语义缓存
语义缓存是一种强大的优化技术,它利用 Redis 的向量搜索能力,基于用户查询的语义相似度(而非精确字符串匹配)来缓存和检索 AI 聊天响应。即使用户以不同的措辞提出相似的问题,也能实现智能的响应复用。
为什么使用语义缓存?
传统的缓存依赖于精确的键匹配,当用户使用不同措辞提出语义等价的问题时,这种方法就会失效:
-
"What is the capital of France?"
-
"Tell me France’s capital city"
-
"Which city is the capital of France?"
这三个查询具有相同的答案,但传统的缓存机制会将它们视为不同的请求,从而导致冗余的 LLM API 调用。语义缓存通过使用向量嵌入(vector embeddings)来比较查询的语义,从而解决了这一问题。
优点:
-
降低 API 成本:避免对高成本 LLM API 的冗余调用
-
更低延迟:即时返回缓存的响应,而非等待模型推理
-
提升可扩展性:处理更高的查询量,而无需成比例地增加 API 成本
-
一致的响应:为语义相似的问题返回相同的答案
自动配置
Spring AI 为 Redis 语义缓存提供了 Spring Boot 自动配置。要启用此功能,请将以下依赖项添加到项目的 Maven pom.xml 文件中:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-redis-semantic-cache</artifactId>
</dependency>
或添加至您的 Gradle build.gradle 构建文件中:
dependencies {
implementation 'org.springframework.ai:spring-ai-starter-vector-store-redis-semantic-cache'
}
自动配置提供了一种针对语义缓存优化的默认嵌入模型(redis/langcache-embed-v1)。你可以通过提供自己的 EmbeddingModel Bean 来覆盖它。
配置属性
以 spring.ai.vectorstore.redis.semantic-cache.* 开头的属性用于配置语义缓存:
| 属性 | 描述 | 默认值 |
|---|---|---|
spring.ai.vectorstore.redis.semantic-cache.enabled | 启用或禁用语义缓存 | true |
spring.ai.vectorstore.redis.semantic-cache.host | Redis 服务器主机 | localhost |
spring.ai.vectorstore.redis.semantic-cache.port | Redis 服务器端口 | 6379 |
spring.ai.vectorstore.redis.semantic-cache.similarity-threshold | 缓存命中的相似度阈值 (0.0-1.0)。较高的值要求更接近的语义匹配。 | 0.95 |
spring.ai.vectorstore.redis.semantic-cache.index-name | 缓存条目对应的 Redis 搜索索引名称 | semantic-cache-index |
spring.ai.vectorstore.redis.semantic-cache.prefix | Redis 中缓存条目的键前缀 | semantic-cache: |
application.yml 中的示例配置:
spring:
ai:
vectorstore:
redis:
semantic-cache:
enabled: true
host: localhost
port: 6379
similarity-threshold: 0.85
index-name: my-app-cache
prefix: "my-app:semantic-cache:"
使用 SemanticCacheAdvisor
SemanticCacheAdvisor 能够与 Spring AI 的 ChatClient 顾问(advisor)模式无缝集成。它会自动缓存响应,并为相似查询返回缓存结果:
@Autowired
private SemanticCache semanticCache;
@Autowired
private ChatModel chatModel;
public void example() {
// Create the cache advisor
SemanticCacheAdvisor cacheAdvisor = SemanticCacheAdvisor.builder()
.cache(semanticCache)
.build();
// First query - calls the LLM and caches the response
ChatResponse response1 = ChatClient.builder(chatModel)
.build()
.prompt("What is the capital of France?")
.advisors(cacheAdvisor)
.call()
.chatResponse();
// Similar query - returns cached response (no LLM call)
ChatResponse response2 = ChatClient.builder(chatModel)
.build()
.prompt("Tell me the capital city of France")
.advisors(cacheAdvisor)
.call()
.chatResponse();
// response1 and response2 contain the same cached answer
}
顾问会自动:
- 在调用 LLM 之前,先检查缓存中是否存在语义相似的查询
- 当找到相似度高于阈值的匹配项时,返回缓存的响应
- 在 LLM 调用成功后,将新响应存入缓存
- 同时支持同步和流式聊天操作
直接缓存使用
也可以直接与 SemanticCache 交互,以实现更细粒度的控制:
@Autowired
private SemanticCache semanticCache;
// Store a response with a query
semanticCache.set("What is the capital of France?", chatResponse);
// Store with TTL (time-to-live) for automatic expiration
semanticCache.set("What's the weather today?", weatherResponse, Duration.ofHours(1));
// Retrieve a semantically similar response
Optional<ChatResponse> cached = semanticCache.get("Tell me France's capital");
if (cached.isPresent()) {
// Use the cached response
String answer = cached.get().getResult().getOutput().getText();
}
// Clear all cached entries
semanticCache.clear();
手动配置
如需更精细的控制,您可以手动配置语义缓存组件:
@Configuration
public class SemanticCacheConfig {
@Bean
public RedisClient jedisClient() {
return RedisClient.builder().hostAndPort("localhost", 6379).build();
}
@Bean
public SemanticCache semanticCache(RedisClient jedisClient, EmbeddingModel embeddingModel) {
return DefaultSemanticCache.builder()
.jedisClient(jedisClient)
.embeddingModel(embeddingModel)
.distanceThreshold(0.3) // Lower = stricter matching
.indexName("my-semantic-cache")
.prefix("cache:")
.build();
}
@Bean
public SemanticCacheAdvisor semanticCacheAdvisor(SemanticCache cache) {
return SemanticCacheAdvisor.builder()
.cache(cache)
.build();
}
}
使用命名空间进行缓存隔离
对于多租户应用,或者需要独立缓存空间的场景,请使用不同的索引名称来隔离缓存条目:
// Create isolated caches for different users or contexts
SemanticCache user1Cache = DefaultSemanticCache.builder()
.jedisClient(jedisClient)
.embeddingModel(embeddingModel)
.indexName("user-1-cache")
.build();
SemanticCache user2Cache = DefaultSemanticCache.builder()
.jedisClient(jedisClient)
.embeddingModel(embeddingModel)
.indexName("user-2-cache")
.build();
// Each user gets their own isolated cache space
SemanticCacheAdvisor user1Advisor = SemanticCacheAdvisor.builder()
.cache(user1Cache)
.build();
系统提示词隔离
SemanticCacheAdvisor 会根据系统提示词自动隔离缓存的响应。这确保了使用不同系统提示词的相同用户查询会返回不同的缓存响应,这对于具有多个 AI 角色或依赖上下文行为的应用程序至关重要。
SemanticCacheAdvisor cacheAdvisor = SemanticCacheAdvisor.builder()
.cache(semanticCache)
.build();
// Query with technical support persona
ChatResponse technicalResponse = ChatClient.builder(chatModel)
.build()
.prompt()
.system("You are a technical support specialist. Provide detailed technical answers.")
.user("How do I reset my password?")
.advisors(cacheAdvisor)
.call()
.chatResponse();
// Same query with customer service persona - cache MISS (different context)
ChatResponse serviceResponse = ChatClient.builder(chatModel)
.build()
.prompt()
.system("You are a friendly customer service agent. Keep responses brief and helpful.")
.user("How do I reset my password?")
.advisors(cacheAdvisor)
.call()
.chatResponse();
// Same query with technical support persona again - cache HIT
ChatResponse technicalAgain = ChatClient.builder(chatModel)
.build()
.prompt()
.system("You are a technical support specialist. Provide detailed technical answers.")
.user("How do I reset my password?")
.advisors(cacheAdvisor)
.call()
.chatResponse();
// Returns the cached technical response
工作原理:
顾问会计算系统提示词的确定性哈希值,并将其用作存储和检索缓存响应时的元数据过滤器:
-
相同的用户问题 + 相同的系统提示词 → 缓存命中
-
相同的用户问题 + 不同的系统提示词 → 缓存未命中(单独的缓存条目)
-
没有系统提示词的查询共享同一个缓存空间
上下文感知缓存 API
对于高级用例,您可以直接使用上下文感知缓存方法:
// Store with explicit context hash
String contextHash = "technical-support-context";
semanticCache.set("How do I reset my password?", response, contextHash);
// Retrieve with context filtering
Optional<ChatResponse> cached = semanticCache.get("How do I reset my password?", contextHash);
// Different context hash returns empty (no match)
Optional<ChatResponse> otherContext = semanticCache.get("How do I reset my password?", "billing-context");
调整相似度阈值
相似度阈值决定了查询必须与缓存条目匹配得多严格才能被视为命中。阈值表示为 0.0 到 1.0 之间的值:
-
更高的阈值(例如,0.95):要求语义高度匹配。可减少误报,但可能会遗漏有效的缓存命中。
-
更低的阈值(例如,0.70):允许更宽泛的语义匹配。可提高缓存命中率,但可能返回相关性较低的缓存响应。
// Strict matching - only very similar queries hit the cache
SemanticCache strictCache = DefaultSemanticCache.builder()
.jedisClient(jedisClient)
.embeddingModel(embeddingModel)
.distanceThreshold(0.2) // Strict (distance-based, lower = stricter)
.build();
// Lenient matching - broader semantic similarity accepted
SemanticCache lenientCache = DefaultSemanticCache.builder()
.jedisClient(jedisClient)
.embeddingModel(embeddingModel)
.distanceThreshold(0.5) // Lenient
.build();
从较高的阈值(更严格的匹配)开始,并根据应用程序对语义差异的容忍度逐步降低该阈值。
TTL 与缓存过期
可以为缓存响应配置存活时间(TTL),以实现自动过期。这对于时间敏感型数据至关重要:
// Cache weather data for 1 hour
semanticCache.set("What's the weather in New York?", weatherResponse, Duration.ofHours(1));
// Cache general knowledge indefinitely (no TTL)
semanticCache.set("What is photosynthesis?", scienceResponse);
// Redis automatically removes expired entries
工作原理
语义缓存按照以下流程运行:
-
查询嵌入(Query embedding):当查询到达时,使用配置的
EmbeddingModel将其转换为向量嵌入。 -
向量搜索:Redis 执行基于范围的向量搜索(
VECTOR_RANGE),以找到相似度阈值内的缓存条目。 -
缓存命中:如果找到语义相似的查询,立即返回缓存的
ChatResponse。 -
缓存未命中:如果未找到匹配项,查询将传递给 LLM,并且响应会被缓存以供将来使用。
该实现利用 Redis 的高效向量索引(HNSW 算法)实现快速的相似度搜索,即使面对较大的缓存规模也能保持高效。