定位: 完整串联 RAG 全流程,从文档导入到智能问答
核心概念依赖:
Embedding (向量化) → 语义检索 (Retrieval) → RAG (检索+增强+生成)
一、什么是RAG?
1.1 为什么需要RAG?
大模型有两个致命问题:
问题1: 知识过时- 训练数据有截止时间,不知道最新信息- 例如: 无法回答"2026年最新政策"问题2: 幻觉问题- 会"一本正经地胡说八道"- 编造看似合理但错误的内容
RAG的解决方案: 开卷考试
闭卷考试 (普通对话):用户问题 → 大模型凭记忆回答 → 可能错误/过时开卷考试 (RAG):用户问题 → 先查资料 → 基于资料作答 → 有据可查
1.2 RAG = Retrieval + Augmented + Generation
┌──────────────────────────────────────────────────────┐│ 1. Retrieval (检索) ││ - 将用户问题转为向量 ││ - 在向量数据库中找最相关文档 │├──────────────────────────────────────────────────────┤│ 2. Augmented (增强) ││ - 把检索到的文档拼成上下文 ││ - 构建增强版Prompt: 参考资料 + 问题 │├──────────────────────────────────────────────────────┤│ 3. Generation (生成) ││ - 把增强Prompt发给大模型 ││ - 大模型基于上下文生成准确回答 │└──────────────────────────────────────────────────────┘
二、完整RAG流程
2.1 两阶段架构
阶段一: 知识库准备 (离线)原始文档 → 分块 → 向量化 → 存入数据库阶段二: 智能问答 (在线)用户问题 → 检索 → 增强Prompt → AI回答
2.2 技术栈
文档分块: TokenTextSplitter (Spring AI)向量化: EmbeddingModel (阿里云)存储: PGVector (PostgreSQL)检索: 余弦相似度 (ORDER BY embedding <=> ?)生成: ChatModel (DeepSeek)
三、阶段一: 文档导入与分块
3.1 为什么要分块?
问题: 一篇文档可能几千字,直接向量化会怎样?- Embedding API有长度限制- 整篇文档一个向量,检索精度差- 检索到整篇文档,上下文太多干扰AI解决: 切成小块,每块独立向量化- 每块500 Token,精准定位知识点- 检索时只返回相关片段,减少噪声
3.2 核心代码
RagService.importDocument()
public DocumentImportResponse importDocument(DocumentImportRequest request) {// 阶段1: 文档分块org.springframework.ai.document.Document sourceDoc =new org.springframework.ai.document.Document(request.getContent());TokenTextSplitter splitter = new TokenTextSplitter();List<org.springframework.ai.document.Document> chunks =splitter.apply(List.of(sourceDoc));// 阶段2: 批量向量化for (org.springframework.ai.document.Document chunk : chunks) {float[] embeddingArray = embeddingModel.embed(chunk.getText());String embeddingVector = VectorFormatUtil.formatToVector(embeddingArray);Document docEntity = new Document();docEntity.setContent(chunk.getText());docEntity.setEmbedding(embeddingVector);documentMapper.insert(docEntity);}return DocumentImportResponse.builder().totalChunks(chunks.size()).message("文档导入成功,共切分为 " + chunks.size() + " 个知识块").build();}
3.3 调用示例
POST http://localhost:8080/api/rag/import{"content": "Spring Boot是由Pivotal团队提供的全新框架...[几千字]","metadata": "{\"source\": \"官方文档\", \"category\": \"教程\"}"}
返回:
{"success": true,"totalChunks": 12,"message": "文档导入成功,共切分为12个知识块","chunks": [{"id": 101,"contentPreview": "Spring Boot是由Pivotal团队提供的...","contentLength": 485}]}
四、阶段二: RAG智能问答
4.1 完整流程代码
RagService.query()
public RagResponse query(RagRequest request) {// ========== 阶段1: Retrieval (检索) ==========// 1.1 问题向量化float[] queryEmbeddingArray = embeddingModel.embed(request.getQuery());String queryEmbedding = VectorFormatUtil.formatToVector(queryEmbeddingArray);// 1.2 语义检索 TopKList<Document> relevantDocs = documentMapper.selectSimilarDocuments(queryEmbedding, request.getTopK());// ========== 阶段2: Augmentation (增强) ==========// 2.1 拼接上下文String context = buildContextFromDocuments(relevantDocs);// 2.2 构建增强PromptString augmentedPrompt = buildAugmentedPrompt(context, request.getQuery());// ========== 阶段3: Generation (生成) ==========List<Message> messages = List.of(new SystemMessage(systemPrompt), // 角色定义new UserMessage(augmentedPrompt) // 参考资料 + 问题);Prompt prompt = new Prompt(messages);ChatResponse chatResponse = chatModel.call(prompt);String answer = chatResponse.getResult().getOutput().getText();return RagResponse.builder().query(request.getQuery()).answer(answer).sources(sources).augmentedPrompt(augmentedPrompt).build();}
4.2 关键技巧: Prompt模板
上下文构建:
private String buildContextFromDocuments(List<Document> documents) {StringBuilder context = new StringBuilder();for (int i = 0; i < documents.size(); i++) {context.append("=== 参考资料 ").append(i + 1).append(" ===\n");context.append(documents.get(i).getContent()).append("\n\n");}return context.toString();}
增强Prompt:
private String buildAugmentedPrompt(String context, String question) {return """请根据以下参考资料回答问题:%s问题: %s请回答:""".formatted(context, question);}
系统提示词:
private static final String DEFAULT_SYSTEM_PROMPT = """你是一个专业的知识问答助手。请根据以下提供的参考资料来回答用户的问题。回答要求:1. 仅基于提供的参考资料进行回答,不要编造信息2. 如果参考资料不足以回答问题,请明确告知用户3. 回答要准确、简洁、有条理""";
4.3 调用示例
POST http://localhost:8080/api/rag/query{"query": "Spring Boot的核心特性是什么?","topK": 5}
返回:
{"query": "Spring Boot的核心特性是什么?","answer": "根据参考资料,Spring Boot的核心特性包括:\n1. 自动配置...\n2. 内嵌服务器...","sources": [{"id": 101,"content": "Spring Boot提供了自动配置机制...","similarityDistance": 0.12,"metadata": "{\"source\": \"官方文档\"}"}],"augmentedPrompt": "请根据以下参考资料回答问题:\n=== 参考资料 1 ===\n..."}
五、效果对比
5.1 普通对话 vs RAG
| 知识来源 | ||
| 准确性 | ||
| 可追溯 | ||
| 适用场景 |
5.2 实际测试
问题: "2026年Spring AI最新版本是什么?"
普通对话:
"Spring AI最新版本是1.0.0..." (可能是过时信息)
RAG对话:
"根据知识库中的官方文档,Spring AI最新版本是1.1.2,发布于2026年3月。"
📎 参考来源: [文档#105] Spring AI 1.1.2 Release Notes...
六、核心要点总结
6.1 RAG三阶段
Retrieval: 问题向量化 → 语义检索 → 相关文档Augmented: 拼接上下文 → 构建增强PromptGeneration: 系统提示词 → AI生成 → 返回答案
6.2 关键技术点
1.文档分块: TokenTextSplitter按Token切分,避免过长/过短2.统一向量模型: 导入和检索必须用同一个Embedding模型3.Prompt设计: 明确区分参考资料和问题,引导AI基于上下文回答4.溯源机制: 返回来源文档,增强可信度
6.3 项目文件结构
src/main/java/com/springailearn/├── service/│ └── RagService.java # RAG核心逻辑├── controller/│ └── RagController.java # REST接口└── model/├── RagRequest.java # 请求对象├── RagResponse.java # 响应对象├── DocumentImportRequest.java # 导入请求└── DocumentImportResponse.java # 导入响应API接口:POST /api/rag/import # 导入文档POST /api/rag/query # RAG问答
References
[1] SpringAI实战-6-Embedding向量基础: ./SpringAI实战-6-Embedding向量基础.md[2] SpringAI实战-7-RAG之R(Retrieval)语义检索: ./SpringAI实战-7-RAG之R(Retrieval)语义检索.md
夜雨聆风