Spring AI 集成实战从 Prompt 模板到 RAG 检索增强的工程化路径一、大模型集成的工程化困境为什么裸调用 API 远远不够在企业级 Java 应用中集成大模型能力远不止调用一个 HTTP 接口那么简单。某保险公司在智能理赔项目中初期将 Prompt 硬编码在 Service 层随着业务场景从理赔分类扩展到条款解读、金额估算Prompt 管理迅速失控——超过 200 个 Prompt 字符串散落在 30 多个类中修改一个 Prompt 需要重新部署整个服务。更深层的挑战在于大模型存在知识截止日期和幻觉问题仅靠 Prompt Engineering 无法保证回答的事实准确性。检索增强生成RAG通过将企业知识库与模型推理结合有效缓解了这一问题。但 RAG 的工程化实现涉及向量检索、文档分片、上下文窗口管理等多个环节每个环节都有值得深究的工程细节。本文将以 Spring AI 框架为基础给出从 Prompt 模板管理到 RAG 检索增强的完整工程化方案。二、Spring AI 的核心抽象与 RAG 架构原理Spring AI 是 Spring 生态对 AI 应用开发的官方支持框架其核心设计理念是将 AI 能力抽象为可组合的 Spring Bean与现有的 Spring 应用架构无缝集成。flowchart TB subgraph 应用层 A[业务 Service] -- B[ChatClient] end subgraph Spring AI 抽象层 B -- C[Prompt 模板引擎] C -- D[Model 调用接口] D -- E[输出解析器] end subgraph RAG 增强层 F[用户查询] -- G[Query 改写] G -- H[向量检索] H -- I[上下文拼接] I -- C end subgraph 知识库层 J[文档加载器] -- K[文本分片器] K -- L[Embedding 模型] L -- M[向量存储] end H -- MRAG 的核心流程分为索引阶段和查询阶段。索引阶段将企业文档分片、向量化并存入向量数据库查询阶段将用户问题向量化检索相关文档片段拼接到 Prompt 上下文中供模型生成回答。sequenceDiagram participant User as 用户 participant App as 应用服务 participant Embed as Embedding 模型 participant VS as 向量存储 participant LLM as 大语言模型 Note over User,LLM: 索引阶段离线 App-App: 文档分片ChunkSize500, Overlap50 App-Embed: 文本 → 向量 Embed--App: 返回 Embedding App-VS: 存储向量 原文 Note over User,LLM: 查询阶段在线 User-App: 提问 App-Embed: 问题 → 向量 Embed--App: 返回 Query Embedding App-VS: 相似度检索 Top-K VS--App: 返回相关文档片段 App-App: 拼接 Prompt上下文 问题 App-LLM: 生成回答 LLM--App: 返回结果 App--User: 回答附来源引用关键设计决策在于文档分片策略。分片过大会导致检索到过多无关内容浪费上下文窗口分片过小会丢失上下文语义导致回答不完整。生产环境中建议分片大小 300—800 Token重叠 50—100 Token并根据文档类型调整策略。三、Spring AI RAG 的生产级代码实现以下代码展示了基于 Spring AI 的 RAG 系统核心实现涵盖文档索引、向量检索、Prompt 编排与回答生成。/** * RAG 检索增强服务 - 生产级实现 * 基于 Spring AI 框架集成向量检索与大模型推理 */ Service Slf4j public class RagEnhancedService { private final ChatClient chatClient; private final VectorStore vectorStore; private final EmbeddingModel embeddingModel; private final DocumentReader documentReader; public RagEnhancedService(ChatClient chatClient, VectorStore vectorStore, EmbeddingModel embeddingModel, DocumentReader documentReader) { this.chatClient chatClient; this.vectorStore vectorStore; this.embeddingModel embeddingModel; this.documentReader documentReader; } /** * 文档索引将企业文档分片、向量化并存入向量数据库 * 支持增量索引避免重复处理已索引文档 */ Async(ragIndexExecutor) public CompletableFutureVoid indexDocuments(ListResource documents) { for (Resource doc : documents) { try { // 1. 文档加载与分片 // 使用 TokenTextSplitter 控制分片粒度 TokenTextSplitter splitter new TokenTextSplitter( 500, // 每片最大 Token 数 80, // 相邻分片重叠 Token 数 5, // 最小分片长度 10000, // 最大文档长度 true // 保留分隔符 ); ListDocument chunks documentReader.get(doc) .stream() .flatMap(d - splitter.split(d).stream()) .collect(Collectors.toList()); // 2. 为每个分片添加元数据用于检索过滤 chunks.forEach(chunk - { chunk.getMetadata().put(source, doc.getFilename()); chunk.getMetadata().put(indexedAt, Instant.now().toString()); }); // 3. 向量化并存储Spring AI 内部调用 Embedding vectorStore.add(chunks); log.info(文档索引完成: file{}, chunks{}, doc.getFilename(), chunks.size()); } catch (Exception e) { log.error(文档索引失败: file{}, doc.getFilename(), e); // 单文档失败不影响整体索引流程 } } return CompletableFuture.completedFuture(null); } /** * RAG 查询检索增强生成 * 流程问题向量化 → 检索相关文档 → 拼接上下文 → 模型生成 */ public RagResponse query(RagRequest request) { // 1. 构建检索过滤条件 FilterExpressionBuilder filterBuilder new FilterExpressionBuilder(); // 限定检索范围只检索指定来源的文档 if (request.getSourceFilter() ! null) { filterBuilder.eq(source, request.getSourceFilter()); } // 2. 向量相似度检索 Top-K 文档片段 // similarityThreshold 控制最低相似度过滤无关内容 ListDocument relevantDocs vectorStore.similaritySearch( SearchRequest.query(request.getQuestion()) .withTopK(request.getTopK() ! null ? request.getTopK() : 5) .withSimilarityThreshold(0.7) .withFilterExpression(filterBuilder.build()) ); // 3. 拼接上下文将检索到的文档片段组装为 Prompt 上下文 String context relevantDocs.stream() .map(doc - { String source (String) doc.getMetadata().get(source); return 【来源: source 】\n doc.getContent(); }) .collect(Collectors.joining(\n\n)); // 4. 构建 Prompt系统指令 上下文 用户问题 String systemPrompt 你是一个专业的企业知识助手。请严格基于以下参考资料回答问题。 如果参考资料中没有相关信息请明确说明根据现有资料无法回答 不要编造内容。回答时请标注信息来源。 ; String userPrompt 参考资料 %s 用户问题%s .formatted(context, request.getQuestion()); // 5. 调用大模型生成回答 ChatResponse chatResponse chatClient.prompt() .system(systemPrompt) .user(userPrompt) .options(ChatOptionsBuilder.builder() .withTemperature(0.1) // 低温度减少幻觉 .withMaxTokens(2000) .build()) .call() .chatResponse(); // 6. 构建响应附带来源引用 ListString sources relevantDocs.stream() .map(doc - (String) doc.getMetadata().get(source)) .distinct() .collect(Collectors.toList()); return new RagResponse( chatResponse.getResult().getOutput().getContent(), sources, relevantDocs.size() ); } }关键设计点第一文档分片使用 TokenTextSplitter基于 Token 数量而非字符数分片与模型的上下文窗口单位一致。第二相似度阈值设为 0.7过滤低相关度文档避免噪声干扰模型输出。第三System Prompt 明确要求模型基于参考资料回答降低幻觉概率。第四Temperature 设为 0.1在知识问答场景中追求确定性输出。第五响应附带来源引用便于用户验证答案可信度。四、RAG 架构的局限与工程权衡RAG 并非万能方案在实际落地中需要清醒认识其边界。检索质量的瓶颈RAG 的回答质量高度依赖检索质量。当用户问题与文档表述存在语义鸿沟时如用户问退货政策文档写退换货规定向量检索可能无法命中相关片段。缓解方案包括 Query 改写将用户问题扩展为多个检索查询和混合检索向量检索 关键词检索但增加了系统复杂度。上下文窗口的限制即使检索到 5 个文档片段总 Token 数也可能超过模型的上下文窗口。生产环境中需要根据模型窗口大小动态调整 Top-K 值和分片大小并在 Prompt 中预留足够的生成空间。GPT-4 的 128K 窗口缓解了这一问题但成本也随之上升。索引更新的时效性企业知识库持续更新但向量索引的重建需要时间。对于实时性要求高的场景如政策变更通知需要设计增量索引机制并考虑向量数据库的写入性能瓶颈。适用边界当企业知识库规模较小 1000 篇文档且更新频率低时简单的向量检索即可满足需求。当知识库规模达到十万级以上时需要引入分层检索先粗筛再精排和缓存机制。对于需要精确计算的场景如数学运算、SQL 生成RAG 的检索增强效果有限应考虑 Function Calling 方案。五、总结Spring AI 为 Java 生态提供了 AI 应用开发的标准抽象将 Prompt 模板、模型调用、向量检索等能力封装为可组合的 Spring Bean。RAG 架构通过检索 生成的两阶段设计有效缓解了大模型的知识截止与幻觉问题。工程化落地的关键在于文档分片策略、相似度阈值调优、上下文窗口管理和来源引用追溯。RAG 适用于知识密集型问答场景但对检索质量高度依赖且受限于上下文窗口大小。架构选型时应根据知识库规模、更新频率和精度要求选择合适的检索策略和模型配置。
Spring AI 集成实战:从 Prompt 模板到 RAG 检索增强的工程化路径
Spring AI 集成实战从 Prompt 模板到 RAG 检索增强的工程化路径一、大模型集成的工程化困境为什么裸调用 API 远远不够在企业级 Java 应用中集成大模型能力远不止调用一个 HTTP 接口那么简单。某保险公司在智能理赔项目中初期将 Prompt 硬编码在 Service 层随着业务场景从理赔分类扩展到条款解读、金额估算Prompt 管理迅速失控——超过 200 个 Prompt 字符串散落在 30 多个类中修改一个 Prompt 需要重新部署整个服务。更深层的挑战在于大模型存在知识截止日期和幻觉问题仅靠 Prompt Engineering 无法保证回答的事实准确性。检索增强生成RAG通过将企业知识库与模型推理结合有效缓解了这一问题。但 RAG 的工程化实现涉及向量检索、文档分片、上下文窗口管理等多个环节每个环节都有值得深究的工程细节。本文将以 Spring AI 框架为基础给出从 Prompt 模板管理到 RAG 检索增强的完整工程化方案。二、Spring AI 的核心抽象与 RAG 架构原理Spring AI 是 Spring 生态对 AI 应用开发的官方支持框架其核心设计理念是将 AI 能力抽象为可组合的 Spring Bean与现有的 Spring 应用架构无缝集成。flowchart TB subgraph 应用层 A[业务 Service] -- B[ChatClient] end subgraph Spring AI 抽象层 B -- C[Prompt 模板引擎] C -- D[Model 调用接口] D -- E[输出解析器] end subgraph RAG 增强层 F[用户查询] -- G[Query 改写] G -- H[向量检索] H -- I[上下文拼接] I -- C end subgraph 知识库层 J[文档加载器] -- K[文本分片器] K -- L[Embedding 模型] L -- M[向量存储] end H -- MRAG 的核心流程分为索引阶段和查询阶段。索引阶段将企业文档分片、向量化并存入向量数据库查询阶段将用户问题向量化检索相关文档片段拼接到 Prompt 上下文中供模型生成回答。sequenceDiagram participant User as 用户 participant App as 应用服务 participant Embed as Embedding 模型 participant VS as 向量存储 participant LLM as 大语言模型 Note over User,LLM: 索引阶段离线 App-App: 文档分片ChunkSize500, Overlap50 App-Embed: 文本 → 向量 Embed--App: 返回 Embedding App-VS: 存储向量 原文 Note over User,LLM: 查询阶段在线 User-App: 提问 App-Embed: 问题 → 向量 Embed--App: 返回 Query Embedding App-VS: 相似度检索 Top-K VS--App: 返回相关文档片段 App-App: 拼接 Prompt上下文 问题 App-LLM: 生成回答 LLM--App: 返回结果 App--User: 回答附来源引用关键设计决策在于文档分片策略。分片过大会导致检索到过多无关内容浪费上下文窗口分片过小会丢失上下文语义导致回答不完整。生产环境中建议分片大小 300—800 Token重叠 50—100 Token并根据文档类型调整策略。三、Spring AI RAG 的生产级代码实现以下代码展示了基于 Spring AI 的 RAG 系统核心实现涵盖文档索引、向量检索、Prompt 编排与回答生成。/** * RAG 检索增强服务 - 生产级实现 * 基于 Spring AI 框架集成向量检索与大模型推理 */ Service Slf4j public class RagEnhancedService { private final ChatClient chatClient; private final VectorStore vectorStore; private final EmbeddingModel embeddingModel; private final DocumentReader documentReader; public RagEnhancedService(ChatClient chatClient, VectorStore vectorStore, EmbeddingModel embeddingModel, DocumentReader documentReader) { this.chatClient chatClient; this.vectorStore vectorStore; this.embeddingModel embeddingModel; this.documentReader documentReader; } /** * 文档索引将企业文档分片、向量化并存入向量数据库 * 支持增量索引避免重复处理已索引文档 */ Async(ragIndexExecutor) public CompletableFutureVoid indexDocuments(ListResource documents) { for (Resource doc : documents) { try { // 1. 文档加载与分片 // 使用 TokenTextSplitter 控制分片粒度 TokenTextSplitter splitter new TokenTextSplitter( 500, // 每片最大 Token 数 80, // 相邻分片重叠 Token 数 5, // 最小分片长度 10000, // 最大文档长度 true // 保留分隔符 ); ListDocument chunks documentReader.get(doc) .stream() .flatMap(d - splitter.split(d).stream()) .collect(Collectors.toList()); // 2. 为每个分片添加元数据用于检索过滤 chunks.forEach(chunk - { chunk.getMetadata().put(source, doc.getFilename()); chunk.getMetadata().put(indexedAt, Instant.now().toString()); }); // 3. 向量化并存储Spring AI 内部调用 Embedding vectorStore.add(chunks); log.info(文档索引完成: file{}, chunks{}, doc.getFilename(), chunks.size()); } catch (Exception e) { log.error(文档索引失败: file{}, doc.getFilename(), e); // 单文档失败不影响整体索引流程 } } return CompletableFuture.completedFuture(null); } /** * RAG 查询检索增强生成 * 流程问题向量化 → 检索相关文档 → 拼接上下文 → 模型生成 */ public RagResponse query(RagRequest request) { // 1. 构建检索过滤条件 FilterExpressionBuilder filterBuilder new FilterExpressionBuilder(); // 限定检索范围只检索指定来源的文档 if (request.getSourceFilter() ! null) { filterBuilder.eq(source, request.getSourceFilter()); } // 2. 向量相似度检索 Top-K 文档片段 // similarityThreshold 控制最低相似度过滤无关内容 ListDocument relevantDocs vectorStore.similaritySearch( SearchRequest.query(request.getQuestion()) .withTopK(request.getTopK() ! null ? request.getTopK() : 5) .withSimilarityThreshold(0.7) .withFilterExpression(filterBuilder.build()) ); // 3. 拼接上下文将检索到的文档片段组装为 Prompt 上下文 String context relevantDocs.stream() .map(doc - { String source (String) doc.getMetadata().get(source); return 【来源: source 】\n doc.getContent(); }) .collect(Collectors.joining(\n\n)); // 4. 构建 Prompt系统指令 上下文 用户问题 String systemPrompt 你是一个专业的企业知识助手。请严格基于以下参考资料回答问题。 如果参考资料中没有相关信息请明确说明根据现有资料无法回答 不要编造内容。回答时请标注信息来源。 ; String userPrompt 参考资料 %s 用户问题%s .formatted(context, request.getQuestion()); // 5. 调用大模型生成回答 ChatResponse chatResponse chatClient.prompt() .system(systemPrompt) .user(userPrompt) .options(ChatOptionsBuilder.builder() .withTemperature(0.1) // 低温度减少幻觉 .withMaxTokens(2000) .build()) .call() .chatResponse(); // 6. 构建响应附带来源引用 ListString sources relevantDocs.stream() .map(doc - (String) doc.getMetadata().get(source)) .distinct() .collect(Collectors.toList()); return new RagResponse( chatResponse.getResult().getOutput().getContent(), sources, relevantDocs.size() ); } }关键设计点第一文档分片使用 TokenTextSplitter基于 Token 数量而非字符数分片与模型的上下文窗口单位一致。第二相似度阈值设为 0.7过滤低相关度文档避免噪声干扰模型输出。第三System Prompt 明确要求模型基于参考资料回答降低幻觉概率。第四Temperature 设为 0.1在知识问答场景中追求确定性输出。第五响应附带来源引用便于用户验证答案可信度。四、RAG 架构的局限与工程权衡RAG 并非万能方案在实际落地中需要清醒认识其边界。检索质量的瓶颈RAG 的回答质量高度依赖检索质量。当用户问题与文档表述存在语义鸿沟时如用户问退货政策文档写退换货规定向量检索可能无法命中相关片段。缓解方案包括 Query 改写将用户问题扩展为多个检索查询和混合检索向量检索 关键词检索但增加了系统复杂度。上下文窗口的限制即使检索到 5 个文档片段总 Token 数也可能超过模型的上下文窗口。生产环境中需要根据模型窗口大小动态调整 Top-K 值和分片大小并在 Prompt 中预留足够的生成空间。GPT-4 的 128K 窗口缓解了这一问题但成本也随之上升。索引更新的时效性企业知识库持续更新但向量索引的重建需要时间。对于实时性要求高的场景如政策变更通知需要设计增量索引机制并考虑向量数据库的写入性能瓶颈。适用边界当企业知识库规模较小 1000 篇文档且更新频率低时简单的向量检索即可满足需求。当知识库规模达到十万级以上时需要引入分层检索先粗筛再精排和缓存机制。对于需要精确计算的场景如数学运算、SQL 生成RAG 的检索增强效果有限应考虑 Function Calling 方案。五、总结Spring AI 为 Java 生态提供了 AI 应用开发的标准抽象将 Prompt 模板、模型调用、向量检索等能力封装为可组合的 Spring Bean。RAG 架构通过检索 生成的两阶段设计有效缓解了大模型的知识截止与幻觉问题。工程化落地的关键在于文档分片策略、相似度阈值调优、上下文窗口管理和来源引用追溯。RAG 适用于知识密集型问答场景但对检索质量高度依赖且受限于上下文窗口大小。架构选型时应根据知识库规模、更新频率和精度要求选择合适的检索策略和模型配置。