Spring AI 框架实战:Java 后端集成大模型的架构设计与工程落地
2026/7/1 12:48:21 网站建设 项目流程

Spring AI 框架实战:Java 后端集成大模型的架构设计与工程落地

一、API 调用之外的工程挑战:Java 应用集成 LLM 的真实痛点

当团队决定在 Java 后端系统中集成大语言模型时,最常见的起点是直接用RestTemplateWebClient调用 OpenAI 的 HTTP API。这种做法在 Demo 阶段看似可行,但一旦进入生产环境,问题便接踵而至:不同模型供应商的 API 格式各异(OpenAI、Azure OpenAI、Anthropic、国内模型),切换供应商需要重写调用逻辑;流式响应(SSE)的处理缺乏统一抽象;Prompt 模板散落在业务代码中,难以版本管理和 A/B 测试;调用失败时的重试与降级策略缺失,导致级联故障。

Spring AI 正是为了解决这些工程痛点而诞生的框架。它提供了一套统一的模型调用抽象、Prompt 管理机制、向量存储接口和 RAG(检索增强生成)管道,让 Java 开发者能够以 Spring 风格的方式集成大模型,而非在业务代码中堆砌 HTTP 调用。

本文将从架构视角出发,完整演示 Spring AI 在生产环境中的集成方案,覆盖模型调用、Prompt 管理、RAG 管道和可观测性四个核心环节。

二、从 ChatModel 到 VectorStore:Spring AI 的核心抽象与协作机制

Spring AI 的架构设计遵循 Spring 的一贯哲学:通过接口抽象屏蔽实现差异,通过自动装配降低集成成本。

flowchart TB subgraph 应用层 A[业务服务] --> B[ChatClient] A --> C[PromptTemplate] A --> D[RAG 管道] end subgraph Spring AI 核心抽象 B --> E[ChatModel 接口] C --> F[Prompt 管理] D --> G[DocumentReader] D --> H[DocumentTransformer] D --> I[VectorStore 接口] end subgraph 模型实现 E --> J[OpenAI ChatModel] E --> K[Azure OpenAI] E --> L[Ollama ChatModel] E --> M[其他供应商] end subgraph 向量存储实现 I --> N[PgVector] I --> O[Chroma] I --> P[Milvus] I --> Q[Redis] end subgraph 可观测性 B --> R[Micrometer 指标] E --> S[Token 用量统计] B --> T[OpenTelemetry Tracing] end

ChatModel 接口是 Spring AI 的核心抽象,定义了与 LLM 交互的统一契约。无论是 OpenAI、Azure OpenAI 还是本地部署的 Ollama,都通过实现ChatModel接口来适配。应用代码只依赖接口,切换模型供应商只需修改配置,无需改动业务逻辑。

PromptTemplate提供了参数化的 Prompt 管理能力。Prompt 不再硬编码在 Java 代码中,而是以模板文件的形式外部化管理,支持变量替换和条件逻辑。这使得 Prompt 的版本管理、A/B 测试和跨团队协作成为可能。

VectorStore 接口统一了向量存储的访问方式。无论是 PgVector、Chroma 还是 Milvus,应用代码都通过VectorStore接口进行向量写入和相似度检索,底层实现可随时替换。

三、生产级 Spring AI 集成:从模型调用到 RAG 管道

下面给出一个完整的生产级 Spring AI 集成方案,包含模型调用、Prompt 管理、RAG 管道和异常处理。

Maven 依赖配置:

<dependencies> <!-- Spring AI BOM 统一版本管理 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>1.0.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <!-- OpenAI 模型适配器 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> </dependency> <!-- PgVector 向量存储适配器 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId> </dependency> </dependencies>

应用配置:

spring: ai: openai: api-key: ${OPENAI_API_KEY} base-url: ${OPENAI_BASE_URL:https://api.openai.com} chat: options: model: gpt-4o temperature: 0.7 max-tokens: 2048 vectorstore: pgvector: index-type: HNSW distance-type: COSINE dimensions: 1536

Prompt 模板管理(外部化文件):

<!-- src/main/resources/prompts/customer-support.st --> 你是一个专业的客户支持助手。请根据以下知识库内容回答用户问题。 知识库内容: {{knowledge}} 用户问题:{{question}} 回答要求: 1. 仅基于知识库内容回答,不要编造信息 2. 如果知识库中没有相关信息,明确告知用户 3. 回答需简洁、准确、专业

RAG 管道服务实现:

/** * RAG 增强的客户支持服务 * 将用户问题与知识库向量检索结合,生成有依据的回答 */ @Service @Slf4j public class CustomerSupportRagService { private final ChatClient chatClient; private final VectorStore vectorStore; private final PromptTemplate promptTemplate; public CustomerSupportRagService(ChatClient.Builder chatClientBuilder, VectorStore vectorStore) { this.chatClient = chatClientBuilder.build(); this.vectorStore = vectorStore; // 加载外部化 Prompt 模板 var promptResource = new ClassPathResource("prompts/customer-support.st"); this.promptTemplate = new PromptTemplate(promptResource); } /** * RAG 查询主流程:检索 -> 构建 Prompt -> 调用模型 * * @param question 用户问题 * @return 基于知识库的回答 */ public String query(String question) { // 1. 向量检索:从知识库中找到与问题最相关的文档片段 List<Document> relevantDocs = retrieveRelevantDocuments(question); // 2. 拼接知识库上下文 String knowledge = relevantDocs.stream() .map(Document::getContent) .collect(Collectors.joining("\n\n")); // 3. 构建 Prompt Prompt prompt = promptTemplate.create(Map.of( "knowledge", knowledge, "question", question )); // 4. 调用模型,带重试与降级 try { ChatResponse response = chatClient.prompt(prompt) .call() .chatResponse(); // 记录 Token 用量,用于成本监控 logTokenUsage(response); return response.getResult().getOutput().getContent(); } catch (ApiException e) { log.error("LLM API 调用失败,状态码: {}, 原因: {}", e.getStatusCode(), e.getMessage()); return generateFallbackAnswer(question, relevantDocs); } } /** * 向量检索:基于问题语义查找最相关的知识库片段 * 限制 Top-K 和相似度阈值,避免无关内容干扰 */ private List<Document> retrieveRelevantDocuments(String question) { SearchRequest searchRequest = SearchRequest.builder() .query(question) .topK(5) // 返回最相关的 5 个片段 .similarityThreshold(0.7) // 相似度阈值,过滤低质量结果 .build(); return vectorStore.similaritySearch(searchRequest); } /** * 降级策略:当 LLM 调用失败时,返回检索到的原始文档片段 * 保证服务可用性,避免因模型不可用导致完全无响应 */ private String generateFallbackAnswer(String question, List<Document> relevantDocs) { if (relevantDocs.isEmpty()) { return "抱歉,当前无法获取回答,请稍后重试。"; } StringBuilder sb = new StringBuilder("以下是与您问题相关的知识库内容:\n\n"); for (int i = 0; i < relevantDocs.size(); i++) { sb.append("[").append(i + 1).append("] ") .append(relevantDocs.get(i).getContent()).append("\n\n"); } sb.append("(系统暂时无法生成总结,以上为原始检索结果)"); return sb.toString(); } /** * 记录 Token 用量,用于成本监控与预算控制 */ private void logTokenUsage(ChatResponse response) { Usage usage = response.getMetadata().getUsage(); log.info("Token 用量 - Prompt: {}, Completion: {}, Total: {}", usage.getPromptTokens(), usage.getGenerationTokens(), usage.getTotalTokens()); } }

知识库文档导入服务:

/** * 知识库文档导入服务 * 将文档分块、生成向量嵌入并写入 VectorStore */ @Service public class KnowledgeBaseIngestionService { private final VectorStore vectorStore; public KnowledgeBaseIngestionService(VectorStore vectorStore) { this.vectorStore = vectorStore; } /** * 导入文本文档到知识库 * 分块策略:按段落分割,每块不超过 500 Token,重叠 50 Token */ public void ingestDocument(String documentId, String content) { // 文档分块:控制块大小以平衡检索精度与上下文完整性 TextSplitter splitter = new TokenTextSplitter( 500, // 每块最大 Token 数 50, // 块间重叠 Token 数 5, // 最小块大小 10000, // 最大块大小 true // 保留分隔符 ); Document originalDoc = new Document(documentId, content, Map.of("source", documentId, "ingestedAt", Instant.now().toString())); List<Document> chunks = splitter.split(originalDoc); // 写入向量存储,Spring AI 自动调用 Embedding 模型生成向量 vectorStore.add(chunks); } }

四、Token 成本与延迟抖动:Spring AI 集成的架构权衡

Spring AI 显著降低了 Java 应用集成 LLM 的工程复杂度,但引入大模型调用链路后,系统面临的新约束必须被正视。

第一,Token 成本的不可预测性。LLM 的计费基于 Token 数量,而用户输入的长度和模型的输出长度在请求前无法精确预估。一个看似简单的 RAG 查询,可能因为检索到大量知识库片段而导致 Prompt Token 数飙升。在 Spring AI 的配置中,max-tokens参数可以限制输出长度,但无法限制输入长度。必须在业务层实现 Token 预算控制:在调用模型前估算 Prompt Token 数,超出预算时截断知识库片段或拒绝请求。

第二,模型调用的延迟不确定性。LLM 的推理延迟从数百毫秒到数十秒不等,取决于输入长度、输出长度和模型负载。Spring AI 默认使用同步调用模式,一个慢请求会阻塞业务线程。对于延迟敏感的接口,必须使用stream()方法进行流式调用,或设置合理的超时时间。Spring AI 的ChatClient支持通过RestClient.Builder自定义超时配置。

第三,向量检索的精度与召回率权衡。RAG 管道的质量高度依赖向量检索的效果。相似度阈值设置过高会导致召回不足(知识库中有答案但未被检索到),设置过低则引入噪声(检索到不相关的内容,干扰模型回答)。similarityThreshold参数需要根据业务场景反复调优,没有通用的最优值。

适用边界:Spring AI 适合需要集成多种模型供应商、管理复杂 Prompt、构建 RAG 管道的企业级 Java 应用。对于只需要调用单一模型 API 的简单场景,直接使用 HTTP 客户端可能更轻量。Spring AI 的抽象层引入了一定的学习成本和框架约束,团队需要评估其收益是否大于引入成本。

五、总结

Spring AI 为 Java 后端集成大模型提供了一套完整的工程化方案:ChatModel 接口统一了模型调用抽象,PromptTemplate 实现了 Prompt 的外部化管理,VectorStore 接口屏蔽了向量存储的实现差异,RAG 管道将检索与生成串联为端到端流程。降级策略和 Token 用量监控则是生产环境不可或缺的保障机制。

然而,Token 成本的不可预测性、推理延迟的不确定性、向量检索的精度与召回率权衡,都是架构师在设计 LLM 集成方案时必须纳入考量的约束条件。Spring AI 提供的是工具和抽象,而非银弹。

落地路线建议:第一步,以单一模型供应商(如 OpenAI)为起点,验证 Spring AI 的基本调用链路;第二步,将硬编码的 Prompt 迁移为外部化模板,建立 Prompt 版本管理流程;第三步,引入 VectorStore 和 RAG 管道,构建知识库增强的问答能力;第四步,实现 Token 预算控制和延迟监控,将 LLM 调用纳入系统的可观测性体系。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询