主题
Redis
本节引导你设置 RedisVectorStore 以存储文档嵌入并执行相似度搜索。
Redis 是一个开源(BSD 许可)、内存中的数据结构存储,可用作数据库、缓存、消息代理和流式引擎。Redis 提供诸如字符串、哈希、列表、集合、带范围查询的有序集、位图、hyperloglog、地理空间索引和流等数据结构。
Redis 搜索与查询 扩展了 Redis OSS 的核心能力,让你能把 Redis 当作向量数据库使用:
- 在哈希或 JSON 文档中存储向量及相关的元数据
- 检索向量
- 执行向量相似度搜索(KNN)
- 用半径阈值执行基于范围的向量搜索
- 对 TEXT 字段执行全文搜索
- 支持多种距离度量(COSINE、L2、IP)和向量算法(HNSW、FLAT)
前置条件
- 一个 Redis Stack 实例
- Redis Cloud(推荐)
- Docker 镜像 redis/redis-stack:latest
- 用于计算文档嵌入的
EmbeddingModel实例。有多个可选方案:
- 如有需要,为 EmbeddingModel 配置一个 API 密钥,以生成
RedisVectorStore所存储的嵌入。
自动配置
NOTE
Spring AI 的自动配置、starter 模块的制品名称发生了重大变更。 更多信息请参阅 升级说明。
Spring AI 为 Redis 向量库提供 Spring Boot 自动配置。 要启用它,请将以下依赖添加到你的项目的 Maven pom.xml 文件:
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-redis</artifactId>
</dependency>或添加到你的 Gradle build.gradle 构建文件。
groovy
dependencies {
implementation 'org.springframework.ai:spring-ai-starter-vector-store-redis'
}TIP
参见 依赖管理 一节,把 Spring AI BOM 添加到你的构建文件。
TIP
参见 制品仓库 一节,把 Maven Central 和/或 Snapshot 仓库添加到你的构建文件。
向量库实现可以为你初始化所需的模式,但你必须显式开启——在相应的构造器中指定 initializeSchema 布尔值,或在 application.properties 文件中设置 ...initialize-schema=true。
NOTE
这是一处破坏性变更!在较早的 Spring AI 版本中,此模式初始化默认进行。
请查看该向量库的配置参数 列表,了解默认值和配置选项。
此外,你还需要一个已配置的 EmbeddingModel bean。更多信息参见 EmbeddingModel 一节。
现在你可以在应用中把 RedisVectorStore 作为向量库自动装配。
java
@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 模式,定义一个 List<RedisVectorStore.MetadataField> bean:
java
@Bean
List<RedisVectorStore.MetadataField> redisMetadataFields() {
return List.of(RedisVectorStore.MetadataField.tag("conversationId"));
}配置属性
要连接到 Redis 并使用 RedisVectorStore,你需要提供实例的访问详情。 一个简单的配置可以通过 Spring Boot 的 application.yml 提供,
yaml
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 提供。
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 | 是否初始化所需的模式 | 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 上利用通用、可移植的元数据过滤器。
例如,你可以使用文本表达式语言:
java
vectorStore.similaritySearch(SearchRequest.builder()
.query("The World")
.topK(TOP_K)
.similarityThreshold(SIMILARITY_THRESHOLD)
.filterExpression("country in ['UK', 'NL'] && year >= 2020").build());或以编程方式使用 Filter.Expression DSL:
java
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());NOTE
这些(可移植的)过滤表达式会被自动转换为 Redis 搜索查询。
例如,这个可移植的过滤表达式:
sql
country in ['UK', 'NL'] && year >= 2020会被转换为 Redis 专有的过滤格式:
@country:{UK | NL} @year:[2020 inf]upsert 支持
RedisVectorStore 实现了 upsert(List<EmbeddedDocument>),因此你可以把文档连同你在别处算好的嵌入一起写入,而不必让存储去嵌入它们。 再次写入同一个 id 会替换既有行,因此用稳定 id 重跑不会产生重复。 细节参见写入用户提供的嵌入。
Redis 会存储你写入的每个元数据键,但一次搜索只返回通过 metadataFields 声明的字段,其余的都缺失而归。 请声明你需要被返回的键,而不只是你据以过滤的键:
java
RedisVectorStore.builder(jedisClient, embeddingModel)
.metadataFields(MetadataField.tag(DocumentMetadata.CONTENT_REF.value()))
.initializeSchema(true)
.build();该存储会在首次写入一个携带未声明字段的文档时记录一条警告。
WARNING
宽度与索引不匹配的向量会被成功写入却永不进索引,因此该行存在,但没有搜索会返回它。
唯一的痕迹是 FT.INFO 里的 hash_indexing_failures 计数。 向一个已有索引的存储添加字段意味着重建索引。
手动配置
与其使用 Spring Boot 自动配置,不如手动配置 Redis 向量库。为此你需要把 spring-ai-redis-store 添加到你的项目:
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-redis-store</artifactId>
</dependency>或添加到你的 Gradle build.gradle 构建文件。
groovy
dependencies {
implementation 'org.springframework.ai:spring-ai-redis-store'
}创建一个 RedisClient bean:
java
@Bean
public RedisClient jedisClient() {
return RedisClient.builder().hostAndPort("<host>", 6379).build();
}然后使用构建器模式创建 RedisVectorStore bean:
java
@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());
}NOTE
对于在过滤表达式中使用的任何元数据字段,你必须显式列出所有的元数据字段名和类型(TAG、TEXT 或 NUMERIC)。 上面的 metadataFields 注册了可过滤的元数据字段:类型为 TAG 的 country、类型为 NUMERIC 的 year。
访问原生客户端
Redis 向量库实现通过 getNativeClient() 方法提供对底层原生 Redis 客户端(RedisClient)的访问:
java
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
}原生客户端让你得以访问那些可能未通过 VectorStore 接口暴露的 Redis 特定功能与操作。
距离度量
Redis 向量库支持三种用于向量相似度的距离度量:
- COSINE:余弦相似度(默认)- 衡量向量间夹角的余弦
- L2:欧氏距离 - 衡量向量间的直线距离
- IP:内积 - 衡量向量间的点积
每种度量都会被自动归一化为 0-1 的相似度分数,1 表示最相似。
java
RedisVectorStore vectorStore = RedisVectorStore.builder(jedisClient, embeddingModel)
.distanceMetric(DistanceMetric.COSINE) // or L2, IP
.build();HNSW 算法配置
Redis 向量库默认使用 HNSW(分层可导航小世界)算法进行高效的近似最近邻搜索。你可以为你的特定用例调优 HNSW 参数:
java
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 算法:
java
RedisVectorStore vectorStore = RedisVectorStore.builder(jedisClient, embeddingModel)
.vectorAlgorithm(Algorithm.FLAT)
.build();文本搜索
Redis 向量库使用 Redis 查询引擎的全文搜索特性提供文本搜索能力。这让你能依据 TEXT 字段中的关键词和短语查找文档:
java
// 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 语义的词项搜索 - 停止词过滤以忽略常见词
- 多种文本打分算法
在构造时配置文本搜索行为:
java
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:TF-IDF 的现代版本,带词项饱和(默认)
- TFIDF:经典的词频-逆文档频率
- BM25STD:标准化的 BM25
- DISMAX:析取最大值
- DOCSCORE:文档分数
分数会被归一化到 0-1 范围,以与向量相似度分数保持一致。
范围搜索
范围搜索返回指定半径阈值内的所有文档,而非固定数量的最近邻:
java
// Search with explicit radius
List<Document> rangeResults = vectorStore.searchByRange(
"AI and machine learning", // query
0.8, // radius (similarity threshold)
"category == 'AI'" // optional filter expression
);你也可以在构造时设置一个默认范围阈值:
java
RedisVectorStore vectorStore = RedisVectorStore.builder(jedisClient, embeddingModel)
.defaultRangeThreshold(0.8) // Set default threshold
.build();
// Use default threshold
List<Document> results = vectorStore.searchByRange("query");当你想取回高于某相似度阈值的所有相关文档、而非限制到特定数量时,范围搜索很有用。
语义缓存
语义缓存是一种强大的优化技术,它利用 Redis 向量搜索能力,依据用户查询的语义相似度而非精确字符串匹配来缓存并检索 AI 聊天响应。 这使得即使用户用不同措辞提出相似问题,也能智能地复用响应。
为何用语义缓存?
传统缓存依赖精确键匹配,当用户以不同措辞提出语义等价的问题时就会失效:
- "What is the capital of France?"
- "Tell me France's capital city"
- "Which city is the capital of France?"
这三个查询有相同的答案,但传统缓存会把它们当作不同的请求,导致冗余的 LLM API 调用。 语义缓存通过使用向量嵌入比较查询的含义来解决这一点。
优势:
- 降低 API 成本:避免对昂贵 LLM API 的冗余调用
- 更低延迟:立即返回缓存响应而非等待模型推理
- 更好的可扩展性:处理更高查询量而 API 成本不成比例增长
- 一致的响应:对语义相似的问题返回相同答案
自动配置
Spring AI 为 Redis 语义缓存提供 Spring Boot 自动配置。 要启用它,请将以下依赖添加到你的项目的 Maven pom.xml 文件:
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-redis-semantic-cache</artifactId>
</dependency>或添加到你的 Gradle build.gradle 构建文件:
groovy
dependencies {
implementation 'org.springframework.ai:spring-ai-starter-vector-store-redis-semantic-cache'
}TIP
自动配置提供一个为语义缓存优化的默认嵌入模型(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 中的配置示例:
yaml
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 顾问模式无缝集成。 它会自动缓存响应,并为相似查询返回缓存结果:
java
@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 交互以获得细粒度控制:
java
@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();手动配置
为了更多控制,你可以手动配置语义缓存组件:
java
@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();
}
}用命名空间隔离缓存
对于多租户应用或当你需要独立的缓存空间时,使用不同的索引名来隔离缓存条目:
java
// 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 人格或上下文相关行为的应用不可或缺。
java
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
对于高级用例,你可以直接使用上下文感知的缓存方法:
java
// 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):允许更宽泛的语义匹配。 提升缓存命中率,但可能返回相关性较低的缓存响应。
java
// 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();TIP
从较高的阈值(更严格匹配)开始,并依据你应用对语义差异的容忍度逐步调低。
TTL 与缓存过期
被缓存的响应可配置一个存活时长(TTL)以自动过期。 这对时间敏感的数据不可或缺:
java
// 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工作原理
语义缓存按以下流程运作:
查询嵌入:当一个查询到达时,它用所配置的
EmbeddingModel被转换为一个向量嵌入向量搜索:Redis 执行一次基于范围的向量搜索(
VECTOR_RANGE)以在相似度阈值内找到缓存条目缓存命中:如果找到一个语义相似的查询,立即返回被缓存的
ChatResponse缓存未命中:如果未找到匹配,查询继续到 LLM,且响应被缓存以供将来使用
该实现借助 Redis 高效的向量索引(HNSW 算法)实现快速相似度搜索,即便缓存规模很大。