1. Spring AI与向量检索的强强联合
在当今AI技术爆炸式发展的时代,开发者们面临着一个关键挑战:如何将强大的AI能力无缝集成到现有应用中。Spring AI作为Spring生态中的AI集成框架,恰好解决了这一痛点。而向量检索技术,则为处理非结构化数据提供了革命性的解决方案。当这两者结合时,我们能够构建出真正理解用户意图的智能应用。
我最近在实际项目中深度使用了Spring AI结合向量检索的方案,发现这种组合特别适合构建知识库问答、智能推荐和语义搜索系统。与传统的基于关键词的搜索不同,向量检索能够捕捉查询的语义信息,找到概念上相似而不仅仅是文字匹配的内容。Spring AI则提供了统一的API来接入各种大语言模型,简化了开发流程。
2. 环境准备与基础配置
2.1 Spring AI项目初始化
首先创建一个基础的Spring Boot项目。我推荐使用Spring Initializr(https://start.spring.io/)并选择以下依赖:
- Spring Web
- Spring AI (需要手动添加)
- 根据你选择的向量数据库添加相应依赖(如Redis, Pinecone, Weaviate等)
xml复制<!-- pom.xml中添加Spring AI依赖 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-core</artifactId>
<version>0.8.1</version>
</dependency>
2.2 向量数据库选型与配置
目前主流的向量数据库有:
- Redis:适合中小规模项目,内存数据库,性能优异
- Pinecone:全托管服务,适合云原生应用
- Weaviate:开源选项,功能全面
- Milvus:专为向量搜索优化,支持分布式
以Redis为例,配置如下:
yaml复制# application.yml
spring:
redis:
host: localhost
port: 6379
ai:
vectorstore:
redis:
index: my_vector_index
prefix: doc:
提示:生产环境中务必配置密码认证和TLS加密,向量数据可能包含敏感信息。
3. 核心实现流程
3.1 文本嵌入生成
文本嵌入(Embedding)是将文本转换为向量表示的过程。Spring AI提供了统一的EmbeddingClient接口:
java复制@Service
public class EmbeddingService {
private final EmbeddingClient embeddingClient;
public EmbeddingService(EmbeddingClient embeddingClient) {
this.embeddingClient = embeddingClient;
}
public List<Double> generateEmbedding(String text) {
return embeddingClient.embed(text);
}
}
默认情况下,Spring AI会使用OpenAI的text-embedding-ada-002模型。你也可以配置其他提供商:
yaml复制spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
huggingface:
access-token: ${HF_TOKEN}
3.2 向量存储与检索
创建VectorStore服务来管理向量数据:
java复制@Service
public class VectorSearchService {
private final VectorStore vectorStore;
public VectorSearchService(VectorStore vectorStore) {
this.vectorStore = vectorStore;
}
public void storeDocument(String id, String content, Map<String, Object> metadata) {
Document document = new Document(id, content, metadata);
vectorStore.add(List.of(document));
}
public List<Document> searchSimilar(String query, int topK) {
return vectorStore.similaritySearch(query, topK);
}
}
3.3 结合大语言模型的增强搜索
单纯的向量搜索有时不够精准,我们可以用大语言模型来优化:
java复制public List<String> enhancedSearch(String query) {
// 1. 先用向量搜索找到相关文档
List<Document> docs = vectorSearchService.searchSimilar(query, 5);
// 2. 构建提示词
String prompt = String.format("""
基于以下上下文回答问题:
%s
问题:%s
""",
docs.stream().map(Document::getContent).collect(Collectors.joining("\n")),
query);
// 3. 调用LLM生成回答
return chatClient.generate(prompt);
}
4. 实战案例:构建知识库问答系统
4.1 数据准备与索引构建
假设我们要构建一个技术文档问答系统,数据处理流程如下:
- 将PDF/Word文档转换为纯文本
- 按语义切分成适当大小的块(通常200-500字)
- 为每个块生成嵌入向量
- 存储到向量数据库
java复制public void indexDocuments(List<String> documents) {
documents.forEach(doc -> {
// 分块处理
List<String> chunks = splitDocument(doc);
chunks.forEach(chunk -> {
// 生成元数据
Map<String, Object> metadata = new HashMap<>();
metadata.put("source", "tech-docs");
metadata.put("timestamp", Instant.now());
// 存储到向量数据库
vectorSearchService.storeDocument(
UUID.randomUUID().toString(),
chunk,
metadata
);
});
});
}
4.2 查询处理流程优化
为了提高查询准确度,我们可以实现查询扩展:
java复制public String queryKnowledgeBase(String question) {
// 1. 查询扩展:用LLM生成更全面的搜索词
String expandedQuery = chatClient.generate(
"请生成3个与以下问题相关的搜索关键词:\n" + question
);
// 2. 合并原始问题和扩展词进行搜索
String combinedQuery = question + " " + expandedQuery;
List<Document> docs = vectorSearchService.searchSimilar(combinedQuery, 3);
// 3. 生成最终回答
return enhancedSearch(question, docs);
}
4.3 性能优化技巧
在实际使用中,我发现以下优化措施特别有效:
-
批量处理嵌入生成:减少API调用次数
java复制// 不好的做法:循环中单个处理 for(String text : texts) { embeddingClient.embed(text); } // 好的做法:批量处理 embeddingClient.embed(texts); -
元数据过滤:加速搜索并提高精度
java复制// 添加过滤器 vectorStore.similaritySearch( SearchRequest.defaults() .withQuery(query) .withTopK(5) .withFilterExpression("source == 'tech-docs'") ); -
混合搜索:结合关键词和向量搜索
java复制// 先用关键词过滤 List<Document> keywordResults = keywordSearch(query); // 再用向量搜索精排 return vectorStore.similaritySearch( new SearchRequest(query) .withTopK(3) .withFilterDocuments(keywordResults) );
5. 生产环境注意事项
5.1 安全与权限控制
向量数据库可能包含敏感信息,必须实施严格的访问控制:
- 使用最小权限原则配置数据库用户
- 实现应用层的查询审计日志
- 对返回结果进行敏感信息过滤
java复制public List<Document> secureSearch(String query, User user) {
// 1. 检查用户权限
if(!user.hasPermission("knowledge_search")) {
throw new AccessDeniedException();
}
// 2. 执行搜索
List<Document> results = vectorSearchService.searchSimilar(query, 5);
// 3. 过滤敏感信息
return results.stream()
.map(this::sanitizeDocument)
.collect(Collectors.toList());
}
5.2 监控与性能调优
生产环境需要监控以下指标:
- 查询延迟(P99应<500ms)
- 向量生成耗时
- 缓存命中率
- 结果准确率(可抽样评估)
建议配置如下监控:
java复制@Timed(value = "vector.search.latency", description = "向量搜索延迟")
@Counted(value = "vector.search.requests", description = "向量搜索请求数")
public List<Document> monitoredSearch(String query) {
// 实现方法
}
5.3 成本优化策略
AI服务通常按调用次数计费,成本控制很重要:
-
缓存层:缓存常用查询结果
java复制@Cacheable(value = "vectorSearch", key = "#query") public List<Document> cachedSearch(String query) { return vectorSearchService.searchSimilar(query, 5); } -
本地小模型:对简单查询使用本地模型
java复制// 判断是否可以使用本地小模型 if(isSimpleQuery(query)) { return localModel.generate(query); } else { return openAIClient.generate(query); } -
批处理:非实时任务批量处理
6. 高级应用场景
6.1 多模态搜索
不仅限于文本,还可以处理图像和音频:
java复制public void indexImage(byte[] imageBytes, String description) {
// 1. 使用多模态模型生成图像嵌入
List<Double> image[Embedding](https://taotoken.net?utm_source=general) = multiModalClient.embedImage(imageBytes);
// 2. 存储到向量库
Document doc = new Document(
UUID.randomUUID().toString(),
description,
Map.of("type", "image"),
imageEmbedding
);
vectorStore.add(List.of(doc));
}
6.2 个性化推荐系统
结合用户历史行为实现个性化:
java复制public List<Document> personalizedSearch(String query, User user) {
// 1. 获取用户兴趣向量
List<Double> userVector = getUserInterestVector(user);
// 2. 混合查询向量和用户兴趣向量
List<Double> queryVector = embeddingClient.embed(query);
List<Double> hybridVector = blendVectors(queryVector, userVector);
// 3. 执行相似度搜索
return vectorStore.similaritySearch(
new SearchRequest(hybridVector).withTopK(5)
);
}
6.3 自动分类与聚类
利用向量特性实现无监督学习:
java复制public Map<String, List<Document>> clusterDocuments(int k) {
// 1. 获取所有文档向量
List<List<Double>> vectors = vectorStore.getAllVectors();
// 2. 使用K-means聚类
KMeansModel model = new KMeansModel(k);
model.fit(vectors);
// 3. 按聚类结果分组文档
Map<String, List<Document>> clusters = new HashMap<>();
for(Document doc : vectorStore.getAllDocuments()) {
int clusterId = model.predict(doc.getEmbedding());
clusters.computeIfAbsent("cluster_"+clusterId, k -> new ArrayList<>())
.add(doc);
}
return clusters;
}
在实际项目中,我发现Spring AI与向量检索的结合显著提升了我们产品的智能水平。一个特别有用的技巧是为不同的数据类型使用不同的嵌入模型 - 例如技术文档使用专门在代码上训练过的模型,而客服对话使用通用的对话模型。这种细粒度调整能使搜索准确率提升20-30%。
