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 / ES2.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 groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.0/version /parent dependencies !-- Spring AI Alibaba DashScope Starter -- dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter-dashscope/artifactId version1.1.2.0/version /dependency !-- Spring AI Alibaba Graph CoreAgent Framework 核心 -- dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-graph-core/artifactId version1.0.0.3/version /dependency !-- Spring AI Chat Client 自动配置 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-autoconfigure-model-chat-client/artifactId /dependency !-- Spring AI RAG 支持 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-rag/artifactId /dependency !-- Elasticsearch 向量存储 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-elasticsearch-store-spring-boot-starter/artifactId /dependency !-- 文档解析 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-tika-document-reader/artifactId /dependency !-- Spring Boot Web -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency /dependencies3.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: 104. 环境搭建4.1 启动 Elasticsearch带向量支持docker run -d \ --name es-vector \ -p 9200:9200 \ -e discovery.typesingle-node \ -e xpack.security.enabledfalse \ docker.elastic.co/elasticsearch/elasticsearch:8.12.04.2 验证连接SpringBootTest class VectorStoreConnectionTest { Autowired private VectorStore vectorStore; Test void testConnection() { ListDocument results vectorStore.similaritySearch( SearchRequest.builder() .query(测试连接) .topK(1) .build() ); System.out.println(连接成功返回 results.size() 条结果); } }5. 文档处理 Pipeline5.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 { ListFile 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 - 文档加载 ListDocument documents loadDocument(file); // Step 2: Transform - 文本分块 ListDocument chunks splitDocuments(documents); // Step 3: Load - 写入向量库 vectorStore.add(chunks); System.out.printf(文档 %s 处理完成共 %d 个分块%n, file.getName(), chunks.size()); } private ListDocument 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 ListDocument splitDocuments(ListDocument 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; }元数据设计原则source和timestamp是必选字段业务字段用驼峰命名如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(ListDocument allChunks, int batchSize) { // 按 1000 条/批次写入避免单次写入过多 for (int i 0; i allChunks.size(); i batchSize) { int end Math.min(i batchSize, allChunks.size()); ListDocument 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 ListDocument search(String query, int topK, double threshold) { return vectorStore.similaritySearch( SearchRequest.builder() .query(query) .topK(topK) .similarityThreshold(threshold) .build() ); } }7.2 带元数据过滤的检索/** * 按产品 ID 过滤的检索 */ public ListDocument 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 ListDocument 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 ListDocument 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 快速入门QuestionAnswerAdvisorRestController 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 ListQuery 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 ListDocument process(ListDocument 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 ListDocument process(ListDocument 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 FluxString streamAnswer(String question) { return chatClient.prompt() .user(question) .stream() .content(); } }10.3 REST APIRestController RequestMapping(/api/rag) public class RagController { private final EcommerceRagService ragService; public RagController(EcommerceRagService ragService) { this.ragService ragService; } /** * 同步问答 */ PostMapping(/ask) public ApiResponseAnswerResult 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 FluxString 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_construction200检索延迟 20ms生成层流式输出 chatClient.stream()首字延迟减少 60%生成层简单问题用 qwen-turbo复杂问题用 qwen-plus降低 40% 推理成本文档层ContentCompressorPostProcessor 压缩长文档Token 消耗降低 30%文档层分块大小从 512 降至 300提升精确度减少无关内容11.2 缓存策略实现Component public class RagCacheService { private final CacheString, String answerCache; public RagCacheService() { this.answerCache Caffeine.newBuilder() .maximumSize(10000) .expireAfterWrite(Duration.ofHours(24)) .build(); } public OptionalString 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) { ListDocument 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性能测试 监控报警建立知识库文档管理规范添加/更新/删除流程