RAG 实战教学完全指南

📅 2026/7/13 0:01:17 👁️ 阅读次数 📝 编程学习
RAG 实战教学完全指南

1. RAG 是什么?为什么需要它?

1.1 大模型的三个致命短板

痛点表现根源
知识滞后问最新政策,回答停留在训练截止日期训练数据有截止时间
幻觉问题一本正经地编造不存在的 API、数据概率生成,无事实校验
私有知识盲区问公司内部文档,完全不知道训练集不包含企业私有数据

1.2 RAG 如何解决?

用户提问 → [检索] → 从知识库找到相关文档 → [增强] → 文档+问题拼成 Prompt → [生成] → LLM 回答

核心思想:不让 LLM 凭空编造,先把相关文档"塞"进 Prompt 里,让它基于事实回答。

1.3 一个对比

场景纯 LLMRAG 增强
"公司的报销流程是什么?"编造通用流程检索公司内部制度文档,精确回答
"这款产品电池能用多久?"猜测典型值检索产品说明书,引用具体参数
"Spring AI 2.0 新特性?"不知道(训练数据旧)检索最新官方文档,实时回答

2. RAG 核心工作流程

2.1 离线阶段(知识入库)

原始文档 → 文档加载 → 文本分块 → 向量嵌入 → 向量数据库存储

↓ ↓ ↓ ↓ ↓

PDF/Word Document Token Embedding Milvus /

Markdown Reader Splitter Model Redis / ES

2.2 在线阶段(问答检索)

用户提问 → 查询重写 → 向量检索 → 文档后处理 → Prompt 增强 → LLM 生成 → 返回回答

↓ ↓ ↓ ↓ ↓ ↓ ↓

"怎么退款" 压缩历史 相似度计算 去重+排序 "根据以下 ChatModel 最终答案

topK 返回 时效过滤 上下文..."

2.3 模块化 RAG 四阶段

│ Pre-Retrieval │ Retrieval │ Post-Retrieval │ Generation

│ 预检索 │ 检索 │ 后检索 │ 生成 │

│ 查询重写 │ 向量检索 │ 去重 │ 上下文增强 │

│ 查询扩展 │ 混合检索 │ 重排序 │ 空上下文处理 │

│ 查询压缩 │ 元数据过滤 │ 内容压缩 │ 流式输出 │

│ 查询翻译 │ 文档合并 │ 时效过滤 │ 引用标注 │

3. 技术选型与架构设计

3.1 推荐技术栈

层级技术选择说明
应用框架Spring Boot 3.2+企业级 Java 后端
AI 框架Spring AI Alibaba 1.1.xSpring AI + 阿里云 DashScope 扩展
LLM通义千问 (DashScope)国内可用,中文理解最佳
EmbeddingDashScope text-embedding-v31536 维,中文效果优秀
向量数据库Milvus / Elasticsearch生产级向量检索
文档解析Apache Tika + Spring AI支持 PDF / Word / Markdown
缓存Redis / Caffeine常见问题缓存

3.2 项目依赖 (Maven)

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.0</version> </parent> <dependencies> <!-- Spring AI Alibaba DashScope Starter --> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-starter-dashscope</artifactId> <version>1.1.2.0</version> </dependency> <!-- Spring AI Alibaba Graph Core(Agent Framework 核心) --> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-graph-core</artifactId> <version>1.0.0.3</version> </dependency> <!-- Spring AI Chat Client 自动配置 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-autoconfigure-model-chat-client</artifactId> </dependency> <!-- Spring AI RAG 支持 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-rag</artifactId> </dependency> <!-- Elasticsearch 向量存储 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-elasticsearch-store-spring-boot-starter</artifactId> </dependency> <!-- 文档解析 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-tika-document-reader</artifactId> </dependency> <!-- Spring Boot Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>

3.3 配置文件 (application.yml)

spring: ai: dashscope: api-key: ${DASHSCOPE_API_KEY} chat: model: qwen-max-latest temperature: 0.0 embedding: model: text-embedding-v3 elasticsearch: vector-store: uris: http://localhost:9200 index-name: knowledge_base dimensions: 1536 distance-type: COSINE index-type: HNSW rag: chunk: size: 512 overlap: 64 retrieval: top-k: 5 similarity-threshold: 0.7 agent: max-iterations: 10

4. 环境搭建

4.1 启动 Elasticsearch(带向量支持)

docker run -d \ --name es-vector \ -p 9200:9200 \ -e "discovery.type=single-node" \ -e "xpack.security.enabled=false" \ docker.elastic.co/elasticsearch/elasticsearch:8.12.0

4.2 验证连接

@SpringBootTest class VectorStoreConnectionTest { @Autowired private VectorStore vectorStore; @Test void testConnection() { List<Document> results = vectorStore.similaritySearch( SearchRequest.builder() .query("测试连接") .topK(1) .build() ); System.out.println("连接成功,返回 " + results.size() + " 条结果"); } }

5. 文档处理 Pipeline

5.1 ETL 完整流程

@Component public class DocumentEtlPipeline { private final VectorStore vectorStore; private final EmbeddingModel embeddingModel; public DocumentEtlPipeline(VectorStore vectorStore, EmbeddingModel embeddingModel) { this.vectorStore = vectorStore; this.embeddingModel = embeddingModel; } /** * 完整的 ETL 流程:加载 → 分块 → 嵌入 → 存储 */ public void processDocuments(String directoryPath) throws IOException { List<File> files = Files.list(Paths.get(directoryPath)) .map(Path::toFile) .filter(this::isSupportedFormat) .collect(Collectors.toList()); for (File file : files) { processSingleDocument(file); } } private void processSingleDocument(File file) { // Step 1: Extract - 文档加载 List<Document> documents = loadDocument(file); // Step 2: Transform - 文本分块 List<Document> chunks = splitDocuments(documents); // Step 3: Load - 写入向量库 vectorStore.add(chunks); System.out.printf("文档 %s 处理完成,共 %d 个分块%n", file.getName(), chunks.size()); } private List<Document> loadDocument(File file) { return switch (getExtension(file)) { case "pdf" -> new PagePdfDocumentReader(file).get(); case "md" -> new MarkdownDocumentReader( new FileSystemResource(file.getPath())).get(); case "docx" -> new TikaDocumentReader(file).get(); default -> throw new UnsupportedOperationException( "不支持的文件格式: " + file.getName()); }; } private List<Document> splitDocuments(List<Document> docs) { TokenTextSplitter splitter = new TokenTextSplitter(); splitter.setChunkSize(512); splitter.setChunkOverlap(64); return splitter.apply(docs); } private boolean isSupportedFormat(File file) { String ext = getExtension(file); return Set.of("pdf", "md", "docx", "txt").contains(ext); } private String getExtension(File file) { String name = file.getName(); int dot = name.lastIndexOf('.'); return dot > 0 ? name.substring(dot + 1).toLowerCase() : ""; } }

5.2 分块策略选择指南

策略组件适用场景示例
Token 分块TokenTextSplitter(512, 64)需要精确控制 LLM 上下文窗口产品手册、技术文档
固定长度FixedTextSplitter(1000, 200)无明显结构的纯文本日志、聊天记录
语义分块SemanticSplitter结构化文档(含表格、标题)法律文书、学术论文

5.3 元数据设计(重要!)

好的元数据设计是高效检索的基础:

private Document enrichMetadata(Document doc, File source) { doc.getMetadata().put("source", source.getName()); doc.getMetadata().put("type", determineDocType(source)); doc.getMetadata().put("timestamp", Instant.now().toString()); doc.getMetadata().put("department", "客服部"); // 业务字段 doc.getMetadata().put("productId", "PROD-001"); // 业务字段 doc.getMetadata().put("version", "v2.1"); // 版本管理 return doc; }

元数据设计原则

  • sourcetimestamp是必选字段
  • 业务字段用驼峰命名(如productId
  • 避免存入过长的文本内容(元数据应轻量)

6. 向量嵌入与存储

6.1 嵌入模型配置

@Configuration public class EmbeddingConfig { @Bean public EmbeddingModel embeddingModel(DashScopeApi dashScopeApi) { return new DashScopeEmbeddingModel(dashScopeApi); } }

6.2 向量库索引初始化

@Configuration public class VectorStoreConfig { @Bean public VectorStore vectorStore(ElasticsearchVectorStoreProperties properties) { return new ElasticsearchVectorStore( properties.getVectorIndexConfig(), properties.getRestClient() ); } }

6.3 批量写入优化

public void batchIngest(List<Document> allChunks, int batchSize) { // 按 1000 条/批次写入,避免单次写入过多 for (int i = 0; i < allChunks.size(); i += batchSize) { int end = Math.min(i + batchSize, allChunks.size()); List<Document> batch = allChunks.subList(i, end); vectorStore.add(batch); System.out.printf("批次 %d/%d 写入完成%n", (i / batchSize) + 1, (allChunks.size() + batchSize - 1) / batchSize); } }

6.4 关键参数速查

参数推荐值说明
dimensions1536 (DashScope v3) / 1536 (OpenAI)向量维度,建索引后不可改
distance-typeCOSINE余弦相似度,适合文本语义匹配
index-typeHNSW分层可导航小世界,检索性能最优
ef-construction200HNSW 构建参数,越大精度越高但速度越慢
m16HNSW 每个节点的连接数

7. 检索策略设计

7.1 基础向量检索

@Component public class KnowledgeRetriever { private final VectorStore vectorStore; public KnowledgeRetriever(VectorStore vectorStore) { this.vectorStore = vectorStore; } /** * 基础向量检索 */ public List<Document> search(String query, int topK, double threshold) { return vectorStore.similaritySearch( SearchRequest.builder() .query(query) .topK(topK) .similarityThreshold(threshold) .build() ); } }

7.2 带元数据过滤的检索

/** * 按产品 ID 过滤的检索 */ public List<Document> searchByProduct(String query, String productId) { FilterExpression filter = new FilterExpressionBuilder() .eq("productId", productId) .build(); return vectorStore.similaritySearch( SearchRequest.builder() .query(query) .topK(5) .similarityThreshold(0.7) .filterExpression(filter) .build() ); } /** * 多条件组合过滤 */ public List<Document> advancedSearch(String query, String dept, String type) { FilterExpression filter = new FilterExpressionBuilder() .eq("department", dept) .and() .eq("type", type) .build(); return vectorStore.similaritySearch( SearchRequest.builder() .query(query) .topK(8) .filterExpression(filter) .build() ); }

7.3 动态过滤(多租户场景)

/** * 动态过滤表达式 — 根据当前租户自动过滤 */ @Component public class TenantAwareRetriever { private final VectorStore vectorStore; public List<Document> search(String query) { FilterExpression filter = () -> new FilterExpressionBuilder() .eq("tenantId", TenantContext.getCurrentTenant()) .build(); return vectorStore.similaritySearch( SearchRequest.builder() .query(query) .topK(5) .filterExpression(filter) .build() ); } }

7.4 检索策略对比

策略召回率精确率延迟适用场景
纯向量检索语义模糊的问题
纯关键词检索极低精确 ID/编码查询
混合检索 (7:3)通用场景(推荐)
语义路由问题类型明确时

8. RAG Advisor 配置

8.1 快速入门:QuestionAnswerAdvisor

@RestController public class SimpleRagController { private final ChatClient chatClient; public SimpleRagController(ChatModel chatModel, VectorStore vectorStore) { var advisor = QuestionAnswerAdvisor.builder(vectorStore) .searchRequest(SearchRequest.builder() .similarityThreshold(0.7) .topK(5) .build()) .build(); this.chatClient = ChatClient.builder(chatModel) .defaultAdvisors(advisor) .build(); } @PostMapping("/rag/ask") public String ask(@RequestBody QuestionRequest request) { return chatClient.prompt() .user(request.getQuestion()) .call() .content(); } }

8.2 进阶配置:RetrievalAugmentationAdvisor

生产级推荐配置,支持全链路自定义:

@Configuration public class RagConfiguration { @Bean public Advisor ragAdvisor(VectorStore vectorStore, ChatClient.Builder chatClientBuilder) { return RetrievalAugmentationAdvisor.builder() // 1. 查询重写:压缩对话历史,提出独立查询 .queryTransformers( CompressionQueryTransformer.builder() .chatClientBuilder(chatClientBuilder) .build(), RewriteQueryTransformer.builder() .chatClientBuilder(chatClientBuilder) .build() ) // 2. 文档检索:向量检索 + 元数据过滤 .documentRetriever( VectorStoreDocumentRetriever.builder() .vectorStore(vectorStore) .similarityThreshold(0.65) .topK(6) .build() ) // 3. 检索后处理:去重 + 排序 + 时效过滤 .documentPostProcessors( new DuplicateRemoverPostProcessor(), new RelevanceRankerPostProcessor(0.7) ) // 4. 查询增强:拼接上下文 + 问题 .queryAugmenter( ContextualQueryAugmenter.builder() .allowEmptyContext(false) .build() ) .build(); } }

8.3 自定义 Prompt 模板

private static final String RAG_PROMPT_TEMPLATE = """ 你是一个专业的知识库助手。请严格基于以下上下文信息回答问题。 上下文信息: --------------------- {context} --------------------- 回答规则: 1. 仅使用上下文中明确提供的信息 2. 如果上下文中没有相关信息,直接回答"根据现有资料无法回答该问题" 3. 引用具体数据时需标注来源 4. 回答应简洁、准确、有条理 用户问题: {question} """; @Bean public Advisor ragAdvisorWithCustomPrompt(VectorStore vectorStore, ChatClient.Builder chatClientBuilder) { return RetrievalAugmentationAdvisor.builder() .documentRetriever( VectorStoreDocumentRetriever.builder() .vectorStore(vectorStore) .topK(6) .build() ) .queryAugmenter( ContextualQueryAugmenter.builder() .promptTemplate(RAG_PROMPT_TEMPLATE) .allowEmptyContext(false) .build() ) .build(); }

9. 查询增强与后处理

9.1 查询变换策略

/** * 查询变换工具类 */ @Component public class QueryTransformService { private final ChatClient.Builder chatClientBuilder; /** * 查询压缩:去除对话历史中的冗余 * 输入: "我之前问了你关于报销的事...那出差住宿标准是什么?" * 输出: "出差住宿标准是多少?" */ public Query compressQuery(String conversationHistory, String currentQuery) { CompressionQueryTransformer transformer = CompressionQueryTransformer.builder() .chatClientBuilder(chatClientBuilder) .build(); return transformer.transform( new Query(conversationHistory + "\n" + currentQuery) ); } /** * 多查询扩展:生成语义变体提升覆盖率 * 输入: "如何申请报销" * 输出: ["如何申请报销", "报销申请步骤", "费用报销流程说明"] */ public List<Query> expandQueries(String query) { MultiQueryExpander expander = MultiQueryExpander.builder() .chatClientBuilder(chatClientBuilder) .numberOfQueries(3) .includeOriginal(true) .build(); return expander.expand(new Query(query)); } }

9.2 自定义文档后处理器

时效过滤处理器

/** * 过滤超过 30 天的过期文档 */ public class FreshnessFilterPostProcessor implements DocumentPostProcessor { private static final Duration MAX_AGE = Duration.ofDays(30); @Override public List<Document> process(List<Document> documents) { Instant cutoff = Instant.now().minus(MAX_AGE); return documents.stream() .filter(doc -> { String timestamp = doc.getMetadata().get("timestamp"); if (timestamp == null) return true; return Instant.parse(timestamp).isAfter(cutoff); }) .collect(Collectors.toList()); } } /** * 对过长文档进行关键信息提取,避免 Prompt 超出 Token 上限 */ @Component public class ContentCompressorPostProcessor implements DocumentPostProcessor { private static final int MAX_CONTENT_LENGTH = 2000; @Override public List<Document> process(List<Document> documents) { return documents.stream() .map(doc -> { if (doc.getText().length() > MAX_CONTENT_LENGTH) { String compressed = doc.getText().substring(0, MAX_CONTENT_LENGTH) + "\n... [内容过长,已截断]"; return new Document(compressed, doc.getMetadata()); } return doc; }) .collect(Collectors.toList()); } }

10. 完整实战:智能客服系统

10.1 业务场景

一个电商平台的智能客服 RAG 服务,支持:

  • 商品参数查询(规格、价格、库存)
  • 售后政策解答(退换货条件、保修期限)
  • 操作指南咨询(使用说明、常见问题)

10.2 核心服务实现

@Service public class EcommerceRagService { private final ChatClient chatClient; private final VectorStore vectorStore; private final KnowledgeRetriever retriever; public EcommerceRagService(ChatModel chatModel, VectorStore vectorStore, ChatClient.Builder chatClientBuilder) { this.vectorStore = vectorStore; this.retriever = new KnowledgeRetriever(vectorStore); // 构建高级 RAG Advisor Advisor ragAdvisor = RetrievalAugmentationAdvisor.builder() .queryTransformers( RewriteQueryTransformer.builder() .chatClientBuilder(chatClientBuilder) .build() ) .documentRetriever( VectorStoreDocumentRetriever.builder() .vectorStore(vectorStore) .similarityThreshold(0.65) .topK(6) .build() ) .documentPostProcessors( new DuplicateRemoverPostProcessor(), new FreshnessFilterPostProcessor(), new RelevanceRankerPostProcessor(0.7) ) .queryAugmenter( ContextualQueryAugmenter.builder() .allowEmptyContext(false) .build() ) .build(); this.chatClient = ChatClient.builder(chatModel) .defaultAdvisors(ragAdvisor) .build(); } /** * 通用客服咨询 */ public String answerGeneralQuestion(String question) { return chatClient.prompt() .user(question) .call() .content(); } /** * 按商品 ID 检索回答 */ public String answerProductQuestion(String question, String productId) { return chatClient.prompt() .user(question) .advisors(a -> a.param( VectorStoreDocumentRetriever.FILTER_EXPRESSION, "productId == '" + productId + "'" )) .call() .content(); } /** * 按部门 + 文档类型检索 */ public String answerFilteredQuestion(String question, String department, String docType) { return chatClient.prompt() .user(question) .advisors(a -> a.param( VectorStoreDocumentRetriever.FILTER_EXPRESSION, "department == '" + department + "' AND type == '" + docType + "'" )) .call() .content(); } /** * 流式回答(大段内容,边生成边返回) */ public Flux<String> streamAnswer(String question) { return chatClient.prompt() .user(question) .stream() .content(); } }

10.3 REST API

@RestController @RequestMapping("/api/rag") public class RagController { private final EcommerceRagService ragService; public RagController(EcommerceRagService ragService) { this.ragService = ragService; } /** * 同步问答 */ @PostMapping("/ask") public ApiResponse<AnswerResult> ask(@RequestBody QuestionRequest request) { String answer; if (request.getProductId() != null) { answer = ragService.answerProductQuestion( request.getQuestion(), request.getProductId()); } else { answer = ragService.answerGeneralQuestion( request.getQuestion()); } return ApiResponse.success(new AnswerResult(answer)); } /** * 流式问答(SSE) */ @PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<String> streamAsk(@RequestBody QuestionRequest request) { return ragService.streamAnswer(request.getQuestion()); } } @Data class QuestionRequest { @NotBlank private String question; private String productId; // 可选:商品过滤条件 private String department; // 可选:部门过滤 } @Data @AllArgsConstructor class AnswerResult { private String answer; }

10.4 测试验证

@SpringBootTest class EcommerceRagServiceTest { @Autowired private EcommerceRagService ragService; @Test void testGeneralQuestion() { String answer = ragService.answerGeneralQuestion("7天无理由退货的条件是什么?"); assertNotNull(answer); assertFalse(answer.contains("不知道")); System.out.println("回答: " + answer); } @Test void testProductSpecificQuestion() { String answer = ragService.answerProductQuestion( "电池续航多久?", "SMARTPHONE-X"); assertNotNull(answer); System.out.println("商品 X 续航回答: " + answer); } @Test void testUnanswerableQuestion() { String answer = ragService.answerGeneralQuestion( "公司CEO的狗叫什么名字?"); assertTrue(answer.contains("无法回答") || answer.contains("不知道")); } }

10.5 预期效果

用户问题检索逻辑回答结果
"手机 X 电池续航多久?"向量检索 + productId 过滤"智能手机 X 的待机时间为 72 小时,连续通话可达 15 小时。"
"PROD-123 退换货政策"元数据匹配 productId"商品 PROD-123 支持 7 天无理由退换,需保持包装完好且未激活。"
"蓝牙耳机怎么连接?"纯向量语义检索"打开充电仓→手机开启蓝牙→搜索设备→点击配对"

11. 性能优化

11.1 优化清单

优化方向具体措施预期效果
检索层批量写入(1000 条/批次)减少 70% I/O 次数
检索层常见问题 Redis 缓存命中时延迟降至 5ms 以下
检索层HNSW 索引 + ef_construction=200检索延迟 < 20ms
生成层流式输出 chatClient.stream()首字延迟减少 60%
生成层简单问题用 qwen-turbo,复杂问题用 qwen-plus降低 40% 推理成本
文档层ContentCompressorPostProcessor 压缩长文档Token 消耗降低 30%
文档层分块大小从 512 降至 300提升精确度,减少无关内容

11.2 缓存策略实现

@Component public class RagCacheService { private final Cache<String, String> answerCache; public RagCacheService() { this.answerCache = Caffeine.newBuilder() .maximumSize(10000) .expireAfterWrite(Duration.ofHours(24)) .build(); } public Optional<String> getCachedAnswer(String question) { return Optional.ofNullable(answerCache.getIfPresent(normalize(question))); } public void cacheAnswer(String question, String answer) { answerCache.put(normalize(question), answer); } private String normalize(String question) { return question.trim().toLowerCase().replaceAll("\\s+", " "); } }

11.3 性能基准参考

阶段目标延迟瓶颈
文档加载1-3s / 文件文件 I/O
文本嵌入50-100ms / 块Embedding API
向量检索10-30ms向量索引
Prompt 增强< 5ms字符串拼接
LLM 推理500ms-3s模型推理
端到端1-4sLLM 推理

12. 常见问题排查

12.1 问题速查表

问题现象可能原因排查步骤解决方案
检索结果为空向量库无数据检查 vectorStore.addAll() 是否执行确认 ETL pipeline 运行成功
检索结果为空过滤条件过严单独测试无 filter 的检索放宽 FILTER_EXPRESSION
检索结果为空相似度阈值过高逐步降低阈值测试从 0.8 降至 0.6 再试
回答出现幻觉上下文不足检查检索返回的文档数增大 topK 至 8-10
回答出现幻觉Prompt 无严格约束检查 Prompt 模板添加"严格基于上下文"指令
回答不相关分块语义割裂检查分块边界增大 chunkOverlap 至 128
响应延迟高检索未命中索引GET /_cat/indices重建 HNSW 索引
响应延迟高文档块过大检查平均 Token 数减小 chunkSize 至 256
向量维数不匹配换了 Embedding 模型对比新旧模型维度重建索引(不可在线改维度)

12.2 调试工具类

@Component public class RagDebugger { private final VectorStore vectorStore; /** * 打印检索结果,用于排查检索质量问题 */ public void debugRetrieval(String query, int topK) { List<Document> results = vectorStore.similaritySearch( SearchRequest.builder() .query(query) .topK(topK) .build() ); System.out.println("=== 检索调试: " + query + " ==="); for (int i = 0; i < results.size(); i++) { Document doc = results.get(i); System.out.printf("[%d] 相似度: %.4f | 来源: %s%n", i + 1, doc.getScore(), doc.getMetadata().get("source")); System.out.printf(" 内容预览: %.100s...%n%n", doc.getText()); } System.out.println("=== 共检索到 " + results.size() + " 条 ==="); } /** * 检查向量库中的文档数量 */ public long countDocuments() { // 取决于具体 VectorStore 实现 return vectorStore.similaritySearch( SearchRequest.builder() .query("") .topK(10000) .build() ).size(); } }

12.3 向量索引重建脚本

@Component public class IndexRebuildService { private final VectorStore vectorStore; private final ElasticsearchRestClient esClient; /** * 删除旧索引并重建 */ public void rebuildIndex(String indexName) { // 1. 删除旧索引 esClient.indices().delete(d -> d.index(indexName)); // 2. 创建新索引(通过 VectorStore 自动创建) // 重新运行 ETL pipeline System.out.println("索引 " + indexName + " 已重建"); } /** * 迁移数据到新索引 */ public void migrateData(String oldIndex, String newIndex) { // 1. 从旧索引读取所有文档 // 2. 重新 Embedding // 3. 写入新索引 System.out.println("数据迁移完成: " + oldIndex + " -> " + newIndex); } }

总结:从零到一的 Checklist

  • 搭建 Spring Boot 3.2+ 项目,引入 Spring AI Alibaba 依赖
  • 配置 DashScope API Key 和 Elasticsearch 连接
  • 实现 ETL Pipeline(加载→分块→嵌入→存储)
  • 设计元数据结构(source、type、timestamp、业务字段)
  • 配置 QuestionAnswerAdvisor(快速验证)
  • 升级为 RetrievalAugmentationAdvisor(生产级)
  • 添加查询变换(压缩 + 重写 + 扩展)
  • 添加文档后处理(去重 + 排序 + 时效过滤)
  • 添加缓存层(Caffeine/Redis)
  • 性能测试 + 监控报警
  • 建立知识库文档管理规范(添加/更新/删除流程)