【第10篇】玩透RAG之RAG + Pgvector
版本说明:基于 Spring AI Alibaba 最新稳定版本(0.8.x),兼容 Spring Boot 3.x,使用 PostgreSQL 16 + pgvector 扩展实现企业级向量检索。
开源镜像地址:rag-pgvector-example
一、概述
使用 PostgreSQL + pgvector 作为向量数据库,实现了文档导入、存储、检索和智能问答的完整流程。
📦 项目结构
rag-pgvector-example/
├── pom.xml # Maven 依赖配置
├── README.md # 项目说明文档
├── docker-compose.yml # 数据库快速部署
├── init.sql # 数据库初始化脚本
├── src/main/resources/
│ ├── application.yml # 应用配置(含环境变量)
│ ├── prompts/system-qa.st # 系统提示词模板
│ └── data/spring_ai_alibaba_quickstart.pdf # 示例文档
└── src/main/java/.../
├── controller/
│ └── RagPgVectorController.java # 核心控制器(6个API端点)
├── config/
│ └── VectorStoreConfig.java # 向量存储配置
└── service/
└── DocumentService.java # 文档处理服务
二、详细解析
🔄 完整工作流程
步骤详解
| 步骤 | 技术组件 | 作用说明 |
|---|---|---|
| 1. 文档解析 | TikaDocumentReader / PagePdfDocumentReader | 读取 PDF、Word、Excel、PPT、TXT 等多种格式 |
| 2. 文本分块 | TokenTextSplitter | 将长文档切成 500 tokens 的小块,重叠 50 tokens 保持上下文连贯 |
| 3. 向量化 | DashScope Embedding API (text-embedding-v2) | 将文本转为 1536 维稠密向量 |
| 4. 存储 | PostgreSQL + pgvector | 存入 vector_store 表,使用 HNSW 索引加速检索 |
| 5. 检索 | HNSW 近似最近邻搜索 | 用户提问 → 向量化 → 相似度搜索 → 取 top-K 相关片段 |
| 6. 重排序 | Rerank 模型 (text-reranker-v1) | 对检索结果二次精排,提升相关性 |
| 7. 生成 | Qwen 大模型 (qwen-turbo/max) | 将相关片段作为上下文,生成最终回答 |
三、依赖分析(pom.xml)
✅ 核心依赖清单
| 依赖 | 作用 | 版本说明 |
|---|---|---|
spring-boot-starter-web |
Web 服务 | 提供 REST API 基础能力 |
spring-ai-alibaba-starter-dashscope |
阿里云 AI 服务 | 调用 Qwen 对话模型、Embedding 模型、Rerank 模型 |
spring-ai-pdf-document-reader |
PDF 解析 | 基于 Apache PDFBox 读取 PDF 文件内容 |
spring-ai-tika-document-reader |
通用文档解析 | 基于 Apache Tika,支持 1000+ 种文件格式 |
spring-ai-pgvector-store |
向量存储 | 集成 pgvector 扩展,提供 VectorStore 接口实现 |
postgresql |
PostgreSQL 驱动 | JDBC 连接驱动 |
⚠️ 依赖配置注意事项
关于 Rerank 依赖:
Spring AI Alibaba 的 Rerank 功能已集成在 spring-ai-alibaba-starter-dashscope 中,无需额外引入 spring-ai-rerank-store 依赖。但需要在配置中显式指定 Rerank 模型:
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<version>0.8.1</version>
</dependency>
父 POM 版本管理:
建议使用 Spring AI Alibaba BOM 统一管理版本,避免版本冲突:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-bom</artifactId>
<version>0.8.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
四、配置文件详解(application.yml)
📋 生产级配置模板
spring:
application:
name: rag-pgvector-example
# 数据库配置(使用环境变量,避免硬编码)
datasource:
url: jdbc:postgresql://${DB_HOST:127.0.0.1}:${DB_PORT:5432}/${DB_NAME:rag_db}
username: ${DB_USERNAME:postgres}
password: ${DB_PASSWORD:}
driver-class-name: org.postgresql.Driver
hikari:
maximum-pool-size: 10
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
jpa:
hibernate:
ddl-auto: none # 关闭自动建表,使用手动 SQL 初始化
show-sql: false
# Spring AI 配置
ai:
dashscope:
api-key: ${DASH_SCOPE_API_KEY} # 必须:阿里云百炼 API Key
chat:
options:
model: qwen-turbo # 对话模型:qwen-turbo / qwen-max / qwen-plus
temperature: 0.7 # 温度参数:0-1,越低越确定
top-p: 0.8 # 核采样参数
embedding:
options:
model: text-embedding-v2 # Embedding 模型:固定 1536 维
rerank:
options:
model: text-reranker-v1 # Rerank 模型:精排优化
vectorstore:
pgvector:
dimensions: 1536 # 必须:与 text-embedding-v2 匹配
index-type: hnsw # 索引类型:HNSW(高性能近似搜索)
distance-type: cosine_distance # 距离度量:余弦相似度(适合语义搜索)
table-name: vector_store # 向量表名
initialize-schema: false # 手动初始化表结构(生产环境推荐)
# 文件上传配置
servlet:
multipart:
max-file-size: 50MB
max-request-size: 100MB
enabled: true
# 日志配置
logging:
level:
org.springframework.ai: DEBUG
com.alibaba.cloud.ai: DEBUG
🔧 关键配置项
1. 向量维度(dimensions)
text-embedding-v2固定输出 1536 维 向量text-embedding-v3默认 1024 维,支持 768/512 可选text-embedding-v4默认 1024 维(最新推荐模型)- 必须确保
dimensions配置与所选模型输出维度完全一致,否则会报错 “Vector dimension mismatch”
2. HNSW 索引参数
-- 建表时指定 HNSW 索引参数
CREATE INDEX vector_store_embedding_idx
ON vector_store USING HNSW (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
| 参数 | 默认值 | 作用 | 调优建议 |
|---|---|---|---|
m |
16 | 每层最大连接数 | 数据量 < 10万:16;10-100万:32;>100万:64 |
ef_construction |
64 | 构建时候选列表大小 | 精度要求高:128-256;构建速度快:40-64 |
3. 距离度量方式
cosine_distance:余弦相似度(推荐,适合语义相似度)l2_distance:欧氏距离(适合空间向量)inner_product:内积(适合归一化向量)
五、数据库部署
🐳 方案 A:Docker Compose 快速部署(推荐)
创建 docker-compose.yml:
version: '3.8'
services:
postgres:
image: pgvector/pgvector:pg16 # 官方镜像,已集成 pgvector 0.7.0+
container_name: rag-postgres
environment:
POSTGRES_USER: ${DB_USERNAME:-postgres}
POSTGRES_PASSWORD: ${DB_PASSWORD:-mysecretpassword}
POSTGRES_DB: ${DB_NAME:-rag_db}
ports:
- "${DB_PORT:-5432}:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro # 自动初始化
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME:-postgres} -d ${DB_NAME:-rag_db}"]
interval: 5s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
volumes:
postgres_data:
driver: local
创建 init.sql(数据库初始化脚本):
-- 启用必要扩展
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "vector";
-- 创建向量存储表(与 Spring AI PgVectorStore 兼容)
CREATE TABLE IF NOT EXISTS vector_store (
id uuid DEFAULT uuid_generate_v4() PRIMARY KEY,
content text NOT NULL, -- 原始文本内容
metadata jsonb DEFAULT '{}', -- 元数据(fileId、文件名等)
embedding vector(1536) NOT NULL -- 向量数据,维度与模型匹配
);
-- 创建 HNSW 索引(关键!大幅提升检索速度)
-- m=16, ef_construction=64 是平衡点,适合大多数场景
CREATE INDEX IF NOT EXISTS vector_store_embedding_idx
ON vector_store USING HNSW (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- 可选:创建 GIN 索引加速 metadata 过滤查询
CREATE INDEX IF NOT EXISTS vector_store_metadata_idx
ON vector_store USING GIN (metadata);
-- 可选:创建全文检索索引(混合检索场景)
CREATE INDEX IF NOT EXISTS vector_store_content_idx
ON vector_store USING GIN (to_tsvector('chinese', content));
-- 表注释
COMMENT ON TABLE vector_store IS 'RAG 向量存储表 - Spring AI Alibaba PgVectorStore';
COMMENT ON COLUMN vector_store.embedding IS '1536维向量,使用 text-embedding-v2 模型生成';
启动命令:
# 1. 设置环境变量(或创建 .env 文件)
export DB_PASSWORD="your_secure_password"
export DASH_SCOPE_API_KEY="sk-your-api-key"
# 2. 启动服务
docker-compose up -d
# 3. 查看日志
docker-compose logs -f postgres
# 4. 验证数据库
docker exec -it rag-postgres psql -U postgres -d rag_db -c "\dt"
docker exec -it rag-postgres psql -U postgres -d rag_db -c "SELECT * FROM pg_extension WHERE extname = 'vector';"
🖥️ 方案 B:本地 PostgreSQL 安装
Ubuntu24:
# 安装 PostgreSQL 16
sudo apt update
sudo apt install postgresql-16 postgresql-contrib-16
# 安装 pgvector 扩展
sudo apt install postgresql-16-pgvector
# 启动服务
sudo systemctl enable postgresql
sudo systemctl start postgresql
# 执行初始化 SQL
sudo -u postgres psql -f init.sql
macOS (Homebrew):
# 安装
brew install postgresql@16
brew install pgvector
# 启动
brew services start postgresql@16
# 执行初始化 SQL
psql -U postgres -f init.sql
验证 pgvector 安装:
-- 检查扩展版本
SELECT * FROM pg_extension WHERE extname = 'vector';
-- 测试向量操作
SELECT '[1,2,3]'::vector <-> '[4,5,6]'::vector AS l2_distance;
六、核心代码解析
📝 RagPgVectorController.java
1️⃣ 依赖注入(构造函数注入)
@RestController
@RequestMapping("/ai/rag")
public class RagPgVectorController {
private final VectorStore vectorStore; // 向量存储接口(PgVectorStore 实现)
private final ChatModel chatModel; // 对话模型(DashScopeChatModel)
private final RerankModel rerankModel; // 重排序模型(DashScopeRerankModel)
private final Resource systemResource; // 系统提示词模板资源
// Spring 自动注入(推荐构造函数注入,便于单元测试)
public RagPgVectorController(
VectorStore vectorStore,
ChatModel chatModel,
RerankModel rerankModel,
@Value("classpath:prompts/system-qa.st") Resource systemResource) {
this.vectorStore = vectorStore;
this.chatModel = chatModel;
this.rerankModel = rerankModel;
this.systemResource = systemResource;
}
}
设计说明:
VectorStore是 Spring AI 的抽象接口,底层由PgVectorStore实现- 使用构造函数注入而非
@Autowired字段注入,符合 Spring 最佳实践 Resource自动从 classpath 加载提示词模板文件
2️⃣ 文档导入接口(基础版)
@GetMapping("/importDocument")
public ResponseEntity<String> importDocument() {
try {
// 1. 读取内置 PDF 资源(classpath 下的示例文档)
DocumentReader reader = new PagePdfDocumentReader(springAiResource);
List<Document> documents = reader.get();
log.info("成功读取 PDF,共 {} 页", documents.size());
// 2. 文本分块(关键步骤!)
// 默认参数:chunkSize=500 tokens, overlap=50 tokens
// 分块策略:按 Token 边界切分,保持语义连贯性
List<Document> splitDocuments = new TokenTextSplitter().apply(documents);
log.info("分块完成,生成 {} 个文本片段", splitDocuments.size());
// 3. 向量化并存储(自动调用 DashScope Embedding API)
// 每个片段会被转换为 1536 维向量,存入 PostgreSQL
vectorStore.add(splitDocuments);
return ResponseEntity.ok(
String.format("成功导入 %d 个文本片段", splitDocuments.size())
);
} catch (Exception e) {
log.error("文档导入失败", e);
return ResponseEntity.internalServerError()
.body("导入失败: " + e.getMessage());
}
}
💡 分块策略详解:
TokenTextSplitter 的默认参数经过大量实践验证:
- chunkSize=500:平衡上下文完整性和检索精度。太大(>1000)会降低检索精度,太小(<200)会丢失上下文。
- overlap=50:相邻块重叠 50 tokens,确保跨边界语义不中断。例如:“今天天气很好,适合出门。我想去公园散步。” 如果在"适合出门"处切分,重叠确保下一块包含"适合出门",保持连贯性。
自定义分块参数:
// 适合长文档场景(如技术手册)
TokenTextSplitter splitter = new TokenTextSplitter(800, 100);
List<Document> splitDocs = splitter.apply(documents);
3️⃣ 文本导入接口(直接文本)
@GetMapping("/importText")
public ResponseEntity<String> insertText(@RequestParam("text") String text) {
// 1. 参数校验(Spring 的 StringUtils 工具)
if (!StringUtils.hasText(text)) {
return ResponseEntity.badRequest().body("请输入非空文本");
}
// 2. 包装为 Document 对象(Spring AI 的标准文档格式)
// Document 包含 content(文本)和 metadata(元数据)两部分
Document document = new Document(text);
// 3. 分块(即使是短文本也走统一流程,保持处理一致性)
List<Document> splitDocuments = new TokenTextSplitter().apply(List.of(document));
// 4. 存储(自动向量化)
vectorStore.add(splitDocuments);
return ResponseEntity.ok(
String.format("成功插入 %d 个文本片段", splitDocuments.size())
);
}
4️⃣ 文件上传接口(多文件支持)
@PostMapping(value = "/importFile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<String> insertFiles(@RequestPart MultipartFile file) {
// 1. 文件基础校验
if (file == null || file.isEmpty()) {
return ResponseEntity.badRequest().body("必须上传非空文件");
}
// 2. 使用 Tika 解析(支持 PDF、Word、Excel、PPT、TXT、HTML 等 1000+ 格式)
// TikaDocumentReader 自动检测文件类型并提取文本
List<Document> docs = new TikaDocumentReader(file.getResource()).get();
log.info("文件解析成功:{},提取 {} 个文档块", file.getOriginalFilename(), docs.size());
// 3. 分块处理
List<Document> splitDocs = new TokenTextSplitter().apply(docs);
// 4. 存储到向量库
vectorStore.add(splitDocs);
return ResponseEntity.ok(
String.format("文件 %s 成功插入 %d 个文本片段",
file.getOriginalFilename(), splitDocs.size())
);
}
⚠️ 大文件上传配置:
如果上传文件超过 1MB 报错 413 Payload Too Large,在 application.yml 中添加:
spring:
servlet:
multipart:
max-file-size: 50MB # 单个文件最大 50MB
max-request-size: 100MB # 整个请求最大 100MB
file-size-threshold: 0 # 超过此大小写入临时文件
5️⃣ 核心 RAG 问答接口(流式输出)
@GetMapping(value = "/rag", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ChatResponse> generate(@RequestParam String message) throws IOException {
// 1. 构建检索请求(取前 2 个最相关片段)
// topK 越小,上下文越聚焦;越大,信息越全面但可能引入噪声
SearchRequest searchRequest = SearchRequest.builder()
.topK(2)
.similarityThreshold(0.1) // 相似度阈值,低于此值的片段会被过滤
.build();
// 2. 读取系统提示词模板(从 classpath:prompts/system-qa.st 加载)
String promptTemplate = systemResource.getContentAsString(StandardCharsets.UTF_8);
// 3. 构建 ChatClient 并配置 RAG 增强(RetrievalRerankAdvisor)
return ChatClient.builder(chatModel)
.defaultAdvisors(
new RetrievalRerankAdvisor(
vectorStore, // 向量存储源
rerankModel, // 重排序模型(精排)
searchRequest, // 检索参数(topK=2)
new SystemPromptTemplate(promptTemplate), // 系统提示词模板
0.1 // 相似度阈值(与 SearchRequest 一致)
)
)
.build()
.prompt()
.user(message) // 用户问题
.stream() // 流式输出(SSE 格式)
.chatResponse(); // 返回 ChatResponse 流
}
🧠 RetrievalRerankAdvisor 工作流程详解:
系统提示词模板(prompts/system-qa.st):
你是一位专业的技术文档助手,请严格根据以下上下文回答问题。
【回答规则】
1. 仅基于提供的上下文回答,不要引入外部知识
2. 如果上下文中没有相关信息,请明确回答"根据现有资料,我无法回答这个问题"
3. 保持回答简洁、准确、有条理
4. 技术术语请保持准确,必要时可引用原文
【上下文】
{documents}
【用户问题】
{question}
请用中文回答:
6️⃣ 带文件 ID 的版本管理接口(V2)
为什么需要 fileId?
在实际业务中,文档会频繁更新。通过 fileId 可以实现:
- 版本隔离:不同版本的文档互不干扰
- 精准检索:只搜索特定文件的内容
- 精确删除:删除文件时清除所有关联向量
导入文件并标记:
@PostMapping("/importFileV2")
public ResponseEntity<String> importFileV2(@RequestPart MultipartFile file) {
// 1-3. 解析 + 分块(同基础版)
List<Document> docs = new TikaDocumentReader(file.getResource()).get();
List<Document> splitDocs = new TokenTextSplitter().apply(docs);
// 4. ⭐ 关键:为每个片段添加 fileId 元数据
// UUID 作为文件唯一标识,同一文件的所有片段共享同一 fileId
String fileId = UUID.randomUUID().toString();
for (Document doc : splitDocs) {
doc.getMetadata().put("fileId", fileId);
doc.getMetadata().put("fileName", file.getOriginalFilename());
doc.getMetadata().put("uploadTime", Instant.now().toString());
}
// 5. 存储(metadata 会存入 PostgreSQL 的 jsonb 字段)
vectorStore.add(splitDocs);
return ResponseEntity.ok(
String.format("成功插入 %d 个片段,fileId: %s", splitDocs.size(), fileId)
);
}
按 fileId 精准检索:
@GetMapping(value = "/searchV2", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> search(@RequestParam String message,
@RequestParam String fileId) {
// 1. 构建过滤表达式:只搜索指定 fileId 的片段
// FilterExpressionBuilder 提供类型安全的过滤条件构建
FilterExpressionBuilder builder = new FilterExpressionBuilder();
Filter.Expression expression = builder.eq("fileId", fileId).build();
// 2. 检索请求(带过滤条件)
SearchRequest searchRequest = SearchRequest.builder()
.topK(1) // 只取最相关的一个片段
.filterExpression(expression) // 关键:添加 fileId 过滤
.similarityThreshold(0.1)
.build();
// 3. 执行 RAG 流程(同基础版,但检索范围受限)
return ChatClient.builder(chatModel)
.defaultAdvisors(new RetrievalRerankAdvisor(
vectorStore,
rerankModel,
searchRequest,
new SystemPromptTemplate(systemResource.getContentAsString(StandardCharsets.UTF_8)),
0.1
))
.build()
.prompt()
.user(message)
.stream()
.content(); // 只返回回答内容字符串,不包含完整 ChatResponse 元数据
}
删除指定文件:
@PostMapping("/deleteFilesV2")
public ResponseEntity<String> deleteFiles(@RequestParam String fileId) {
// 1. 构建删除过滤条件
FilterExpressionBuilder builder = new FilterExpressionBuilder();
Filter.Expression expression = builder.eq("fileId", fileId).build();
// 2. 删除所有匹配的向量片段
// 底层执行:DELETE FROM vector_store WHERE metadata->>'fileId' = ?
vectorStore.delete(expression);
return ResponseEntity.ok(
String.format("成功删除 fileId=%s 的所有向量片段", fileId)
);
}
七、完整部署实操指南
🚀 步骤 1:环境准备
# 1. 检查 JDK 版本(需要 JDK 17+)
java -version
# 2. 克隆项目
git clone https://github.com/spring-ai-alibaba/spring-ai-alibaba-examples.git
cd spring-ai-alibaba-examples/spring-ai-alibaba-rag-example/rag-pgvector-example
# 3. 准备环境变量(建议写入 ~/.bashrc 或 ~/.zshrc)
export DASH_SCOPE_API_KEY="sk-your-actual-api-key"
export DB_PASSWORD="your-secure-password"
export DB_HOST="127.0.0.1"
export DB_PORT="5432"
export DB_NAME="rag_db"
# 验证环境变量
echo $DASH_SCOPE_API_KEY
🐳 步骤 2:启动 PostgreSQL + Pgvector
# 使用 Docker Compose(推荐)
docker-compose up -d
# 等待数据库就绪(约 10-15 秒)
sleep 10
# 验证数据库连接
docker exec -it rag-postgres psql -U postgres -d rag_db -c "SELECT version();"
# 验证 pgvector 扩展
docker exec -it rag-postgres psql -U postgres -d rag_db -c "SELECT * FROM pg_extension WHERE extname = 'vector';"
🔧 步骤 3:编译并启动应用
# 1. 清理并编译(跳过测试加速)
mvn clean package -DskipTests
# 2. 启动应用
mvn spring-boot:run
# 或使用 jar 包启动(生产环境推荐)
java -jar target/rag-pgvector-example-*.jar
# 查看启动日志,看到 "Started RagExampleApplication" 表示成功
✅ 步骤 4:验证部署
# 1. 测试健康检查(如果有 actuator)
curl http://localhost:8080/actuator/health
# 2. 测试文档导入(读取内置 PDF)
curl -X GET http://localhost:8080/ai/rag/importDocument
# 3. 测试文本导入
curl -G 'http://localhost:8080/ai/rag/importText' \
--data-urlencode 'text=Spring AI Alibaba 是一个基于 Spring Boot 的 AI 应用框架,简化大模型应用开发。'
# 4. 测试文件上传(V2 版本,返回 fileId)
curl -X POST http://localhost:8080/ai/rag/importFileV2 \
-F "file=@/path/to/your/document.pdf"
# 5. 测试基础 RAG 问答(流式输出)
curl -N 'http://localhost:8080/ai/rag?message=什么是 Spring AI Alibaba?'
# 6. 测试带 fileId 的精准检索(使用上一步返回的 fileId)
curl -N 'http://localhost:8080/ai/rag/searchV2?message=文档的核心内容是什么&fileId=xxxxx-xxxxx-xxxxx'
八、常见问题排查
❌ 问题 1:数据库连接失败
症状:
Cannot get JDBC Connection: Connection refused
排查步骤:
# 1. 检查 PostgreSQL 容器是否运行
docker ps | grep postgres
# 2. 检查端口占用
netstat -tlnp | grep 5432 # Linux
lsof -i :5432 # macOS
# 3. 测试连接
psql -h 127.0.0.1 -U postgres -d rag_db
# 4. 查看数据库日志
docker-compose logs postgres
修复:
# 确保数据库已创建
docker exec -it rag-postgres psql -U postgres -c "CREATE DATABASE rag_db;"
# 检查防火墙设置(云服务器需要开放 5432 端口)
❌ 问题 2:pgvector 扩展未安装
症状:
ERROR: extension "vector" does not exist
修复:
-- 进入数据库执行
CREATE EXTENSION IF NOT EXISTS "vector";
-- 验证安装
SELECT * FROM pg_extension WHERE extname = 'vector';
-- 应显示 vector | 0.7.0 | ...
❌ 问题 3:向量维度不匹配
症状:
Vector dimension mismatch: expected 1536, got 1024
原因:Embedding 模型输出维度与 application.yml 配置不一致。
修复:
# 确认模型与维度对应关系
# text-embedding-v1: 1536
# text-embedding-v2: 1536(本指南使用)
# text-embedding-v3: 1024(默认)或 768/512
# text-embedding-v4: 1024(默认)或 2048/1536/768/512/256/128/64
spring:
ai:
vectorstore:
pgvector:
dimensions: 1536 # 必须与使用的模型匹配
❌ 问题 4:HNSW 索引未生效(检索慢)
症状:查询速度慢,EXPLAIN 显示 Seq Scan 而非 Index Scan。
诊断:
-- 检查索引是否存在
SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = 'vector_store';
-- 查看执行计划
EXPLAIN ANALYZE
SELECT * FROM vector_store
ORDER BY embedding <=> '[0.1, 0.2, ...]'::vector
LIMIT 5;
修复:
-- 重建 HNSW 索引
DROP INDEX IF EXISTS vector_store_embedding_idx;
CREATE INDEX vector_store_embedding_idx
ON vector_store USING HNSW (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- 更新统计信息
ANALYZE vector_store;
-- 设置查询参数(提高召回率)
SET hnsw.ef_search = 100; -- 默认 40,增大可提高精度
❌ 问题 5:API Key 无效或额度不足
症状:
DashScope API error: 401 Unauthorized 或 Quota exceeded
修复:
# 1. 检查 API Key 格式(应以 sk- 开头)
echo $DASH_SCOPE_API_KEY
# 2. 在阿里云百炼控制台检查:
# - API Key 状态是否正常
# - 账户余额或免费额度是否充足
# - 模型调用权限是否开通(text-embedding-v2、qwen-turbo 等)
# 3. 测试 API Key
curl https://dashscope.aliyuncs.com/api/v1/services/embeddings/text-embedding/text-embedding \
-H "Authorization: Bearer $DASH_SCOPE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "text-embedding-v2", "input": {"texts": ["测试"]}}'
九、替代方案对比
🔄 方案 A:更换向量数据库
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Pgvector | 与 PostgreSQL 集成、ACID 事务、成熟稳定、无需额外运维 | 单节点性能瓶颈,>100万向量时检索延迟增加 | 中小规模(<100万向量)、已有 PostgreSQL 基础设施 |
| Milvus | 超大规模(十亿级向量)、分布式架构、专门优化向量检索 | 部署复杂(需 Etcd、MinIO、Pulsar)、运维成本高 | 大规模(>1000万向量)、高并发检索场景 |
| Elasticsearch | 全文检索 + 向量检索混合、生态完善、Kibana 可视化 | 内存占用大、向量检索性能不如专用数据库 | 需要混合检索(关键词 + 语义)的场景 |
| Chroma | 轻量级、开发友好、嵌入 Python 应用 | 生产环境成熟度低、分布式支持弱 | 原型开发、本地测试、小型应用 |
| Weaviate | 多模态支持、GraphQL 查询、模块化设计 | 学习曲线陡峭、社区相对较小 | 复杂检索需求、多模态数据(文本+图像) |
Milvus 替代配置示例:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-milvus-store</artifactId>
</dependency>
spring:
ai:
vectorstore:
milvus:
client:
host: localhost
port: 19530
collection-name: rag_collection
embedding-dimension: 1536
index-type: IVF_FLAT # 或 HNSW
metric-type: COSINE
🔄 方案 B:更换 Embedding 模型
| 模型 | 维度 | 优点 | 缺点 | 价格(元/百万 tokens) |
|---|---|---|---|---|
| DashScope text-embedding-v2 | 1536 | 中文效果好、稳定性高、与 Spring AI Alibaba 集成最佳 | 速度较 v1 稍慢 | 1.2 |
| DashScope text-embedding-v1 | 1536 | 性价比高、速度较快 | 精度略低于 v2 | 0.6 |
| DashScope text-embedding-v3 | 1024(默认)/768/512 | 支持 50+ 语言、可自定义维度 | 相对较新 | 参考官网 |
| DashScope text-embedding-v4 | 1024(默认) | 最新推荐模型、效果最佳 | 批大小限制 10 | 参考官网 |
| BGE-M3 | 1024 | 开源免费、多语言支持、可本地部署 | 需要自建推理服务 | 免费(自建成本) |
BGE-M3 本地部署配置:
spring:
ai:
embedding:
options:
model: bge-m3
base-url: http://localhost:8080 # 本地推理服务地址
🔄 方案 C:简化版(去掉 Rerank)
如果 Rerank 模型不可用或成本敏感,可以简化流程:
@GetMapping("/rag/simple")
public Flux<ChatResponse> generateSimple(@RequestParam String message) {
SearchRequest searchRequest = SearchRequest.builder()
.topK(5) // 增大 topK 补偿精度损失
.similarityThreshold(0.7) // 提高阈值过滤低质量片段
.build();
return ChatClient.builder(chatModel)
.defaultAdvisors(
new RetrievalAdvisor(vectorStore, searchRequest) // 使用基础版 Advisor,去掉 Rerank
)
.build()
.prompt()
.user(message)
.stream()
.chatResponse();
}
性能对比:
- 带 Rerank:检索精度高(NDCG@10 提升 5-10%),但增加一次模型调用延迟(+50-100ms)
- 不带 Rerank:响应更快,适合对延迟敏感的场景,但需增大 topK 并提高阈值保证质量
十、性能优化建议
🚀 数据库层面
-- 1. 调整 PostgreSQL 配置(postgresql.conf)
shared_buffers = 256MB -- 共享内存缓冲区
work_mem = 16MB -- 排序/哈希操作内存
maintenance_work_mem = 128MB -- 维护操作内存(VACUUM、建索引)
effective_cache_size = 1GB -- 操作系统缓存估计
max_parallel_workers_per_gather = 4 -- 并行查询 worker 数
-- 2. HNSW 索引参数调优(根据数据量选择)
-- 数据量 < 10 万:m=16, ef_construction=64(平衡)
-- 数据量 10-100 万:m=32, ef_construction=128(精度优先)
-- 数据量 > 100 万:m=64, ef_construction=256(大规模)
-- 3. 查询参数调优
SET hnsw.ef_search = 100; -- 增大搜索候选列表,提高召回率(默认 40)[ ^2^]
-- 4. 定期维护
VACUUM ANALYZE vector_store; -- 更新统计信息,回收空间
REINDEX INDEX CONCURRENTLY vector_store_embedding_idx; -- 重建索引(不锁表)
-- 5. 分区表(超大规模,>500万向量)
CREATE TABLE vector_store_2024q1 PARTITION OF vector_store
FOR VALUES FROM ('2024-01-01') TO ('2024-04-01');
🚀 应用层面
// 1. 批量插入(提升 10 倍性能)
// 避免逐条插入,减少网络往返和事务开销
List<Document> batch = new ArrayList<>();
for (Document doc : documents) {
batch.add(doc);
if (batch.size() >= 100) {
vectorStore.add(batch);
batch.clear();
}
}
if (!batch.isEmpty()) {
vectorStore.add(batch);
}
// 2. 异步处理(避免阻塞主线程)
@Async("taskExecutor")
public CompletableFuture<String> importFileAsync(MultipartFile file) {
// 后台处理大文件导入
processFile(file);
return CompletableFuture.completedFuture("处理中");
}
// 3. 缓存检索结果(对热点问题有效)
@Cacheable(value = "rag-results", key = "#message.hashCode() + #fileId")
public Flux<String> search(String message, String fileId) {
// ...
}
// 4. 连接池优化
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.hikari")
public HikariConfig hikariConfig() {
return new HikariConfig();
}
}
🚀 检索优化
// 1. 动态 topK(根据问题复杂度调整)
int topK = message.length() > 50 ? 5 : 3; // 长问题可能需要更多上下文
// 2. 混合检索(向量 + 关键词)
SearchRequest searchRequest = SearchRequest.builder()
.topK(10)
.similarityThreshold(0.6)
.includeValues(false) // 不返回向量,节省带宽
.build();
// 3. 多路召回(向量检索 + 全文检索合并结果)
// 需要自定义 Advisor 实现
十一、生产环境检查清单
部署到生产环境前,请确认以下事项:
| 类别 | 检查项 | 状态 |
|---|---|---|
| 安全 | 数据库密码使用环境变量或密钥管理服务(Vault) | ☐ |
| 安全 | API Key 使用密钥管理服务,避免硬编码 | ☐ |
| 安全 | 配置 HTTPS,启用 TLS 1.2+ | ☐ |
| 安全 | 配置 CORS 策略,限制跨域来源 | ☐ |
| 安全 | 添加请求限流(Rate Limiting)防止滥用 | ☐ |
| 安全 | 实现用户认证授权(JWT/OAuth2) | ☐ |
| 运维 | 配置日志级别(INFO 生产环境,DEBUG 开发环境) | ☐ |
| 运维 | 集成监控(Micrometer + Prometheus + Grafana) | ☐ |
| 运维 | 配置健康检查和就绪探针(/actuator/health) | ☐ |
| 运维 | 设置数据库连接池和超时参数 | ☐ |
| 运维 | 定期备份向量数据(pg_dump 或物理备份) | ☐ |
| 性能 | 配置 JVM 参数(-Xmx2g -Xms2g -XX:+UseG1GC) | ☐ |
| 性能 | 配置容器资源限制(CPU、内存) | ☐ |
| 性能 | 启用 Gzip 压缩响应 | ☐ |
| 文档 | 补充 API 文档(SpringDoc OpenAPI) | ☐ |
| 测试 | 补充单元测试和集成测试 | ☐ |
十二、总结
✅ 项目优点
- 架构清晰:Controller → Advisor → VectorStore 分层合理,符合 Spring AI 设计哲学
- 技术选型成熟:Spring AI Alibaba + Pgvector 是经过验证的生产级组合
- 支持版本管理:fileId 机制支持文档版本控制和精准删除
- 流式输出:提升用户体验,减少首字等待时间
- Rerank 支持:通过二次精排显著提升检索质量
⚠️ 需要改进的地方
| 优先级 | 改进项 | 预计工时 | 说明 |
|---|---|---|---|
| P0 | 添加全局异常处理 | 2 小时 | 使用 @ControllerAdvice 统一处理异常 |
| P0 | 配置环境变量和密钥管理 | 1 小时 | 使用 Spring Cloud Vault 或阿里云 KMS |
| P1 | 添加 API 文档 | 3 小时 | 集成 SpringDoc OpenAPI,自动生成 Swagger UI |
| P1 | 补充集成测试 | 4 小时 | 使用 Testcontainers 测试 PostgreSQL 集成 |
| P2 | 添加监控和告警 | 6 小时 | 集成 Micrometer + Prometheus + Grafana |
| P2 | 性能压测和优化 | 8 小时 | 使用 JMeter 压测,优化 HNSW 参数 |
🎯 进阶学习
- 深入 RAG:学习查询重写(Query Rewriting)、多跳检索(Multi-hop Retrieval)
- Agent 集成:结合 Spring AI Alibaba Agent 实现工具调用、任务规划
- 多模态扩展:支持图像、音频的向量化检索
- 分布式部署:学习 Milvus 或 Weaviate 集群部署,支持十亿级向量
更多推荐


所有评论(0)