
当业务需要一个“能读懂内部文档、能查数据库、能调用内部系统完成复杂任务”的智能助手时Java 后端团队的目光很容易落到 Spring AI 上。过去大模型接入 Java 项目要么自己封装 HTTP 请求要么引入非官方 SDK维护成本高、版本跟进慢。而 Spring AI 的出现把模型接入、RAG、结构化输出、工具调用等能力统一进了 Spring 生态让 Java 开发者可以用熟悉的方式开发 AI 应用。本文围绕 Spring AI 2.0 GA 展开一套企业级实战教程内容覆盖环境搭建、基础对话、RAG 知识库问答、智能体设计、业务封装、部署上线以及高频问题排查。无论你是刚开始接触 Spring AI 的 Java 后端还是已经做过简单 AI 应用、想往生产级方向推进的开发者这篇文章都能提供一条完整的落地路径。文章中的代码和配置都以可执行为目标你可以照着写、照着改再根据自己项目的实际情况调整。写这篇文章之前我特意梳理了几个容易混淆的概念Spring AI 和 LangChain 有什么区别RAG 和智能体是什么关系结构化输出到底解决什么问题后面的章节会逐一拆开讲。先把整体框架看清楚再进入代码你会少走很多弯路。1. Spring AI 2.0 GA 是什么为什么 Java 后端值得关注1.1 从 Spring AI 1.x 到 2.0 GA 的演进Spring AI 是 Spring 官方生态中面向 AI 应用开发的模块目标不是做一个大而全的“AI 平台”而是把大模型接入、提示词管理、向量检索、工具调用等能力抽象成 Spring 风格的 API让 Java 开发者可以快速把 AI 能力集成到现有业务系统中。从早期版本到现在的 2.0 GASpring AI 经历了一个从快速迭代到逐步稳定的过程。早期版本中很多 API 变化频繁比如 ChatClient 的构建方式、VectorStore 的配置方式、文档切块组件的类名在不同小版本之间可能都有差异。到了 GA 版本后公共 API 和核心抽象趋于稳定社区和企业项目开始把 Spring AI 作为生产级技术选型。需要注意的是Spring AI 的发展节奏非常快即便在 GA 之后小版本的演进依然存在。你在参考本文示例时不要死记类名和方法名重点理解设计思路和调用链路。实际项目中一定要以你引入的具体版本对应的官方文档为准。1.2 RAG 与智能体在 Spring AI 中的定位RAGRetrieval-Augmented Generation检索增强生成是目前企业落地大模型最常用的方案。它的核心思路是不把企业私有知识直接塞进模型而是在用户提问时先从知识库中检索相关文档片段把片段作为上下文传给大模型让模型基于这些内容生成回答。这样做的好处有三个第一回答内容可以追溯到企业内部的真实资料减少模型凭空捏造的概率。 第二知识更新不需要重新训练模型替换或新增文档即可。 第三企业敏感数据不需要上传给模型做训练只需要在请求时传入相关片段安全性更容易控制。智能体则更进一步。普通对话是“用户问一句、模型答一句”而智能体能根据用户的目标自主决定调用哪些工具、执行哪些步骤。比如用户问“帮我把昨天销售额超过 10 万的订单导出来并生成一份摘要”智能体可能需要先调用订单查询接口再调用文件生成工具最后把结果整理给用户。这种“规划 工具调用 多轮执行”的能力在 Spring AI 中通过 Tool Calling 和 Agent 编排支持。1.3 Spring AI 的核心组件与工作流程从使用角度Spring AI 的核心组件可以拆为以下几层ChatModel / ChatClient负责与大模型对话是使用频率最高的入口。EmbeddingModel负责把文本转换成向量用于 RAG 的召回阶段。VectorStore负责向量数据的存储和相似度检索。DocumentReader / DocumentTransformer负责文档加载和切块处理。Tool Calling允许模型在对话过程中调用你注册的 Java 方法。Prompt Template把用户输入和业务上下文组合成模型可理解的提示词。一个典型的 RAG 流程是这样的先通过 DocumentReader 加载文档再按一定策略切块然后通过 EmbeddingModel 把每个文本块转成向量并写入 VectorStore用户提问时系统把问题转成向量在 VectorStore 中检索最相似的文本块把这些文本块作为上下文与原始问题一起构造 Prompt最后交给 ChatModel 生成回答。智能体流程则是在这个基础上增加了工具层模型在生成回复前可以决定是否需要调用某个工具调用的参数由模型生成Spring AI 负责把模型输出映射到 Java 方法的参数上并把方法返回值再交给模型让模型继续生成最终回答。2. 环境准备与版本说明2.1 技术选型JDK、Spring Boot、构建工具在开始写代码之前先把基础环境准备好。本文的示例以 Java 17 和 Spring Boot 3.x 为基础这是 Spring AI 2.0 GA 常见的组合。如果你的项目还在使用 Java 8 或 Spring Boot 2.x那么需要先考虑升级因为 Spring AI 2.0 对 Spring Boot 3.x 的集成体验更完整。构建工具方面Maven 和 Gradle 都可以本文以 Maven 为例。IDE 推荐 IntelliJ IDEA当然也可以使用 Eclipse 或 VS Code只要支持 Spring Boot 项目即可。操作系统不限Windows、macOS、Linux 均可但要注意模型服务网络是否可达。版本说明Spring AI 迭代速度很快不同小版本的 API 存在差异。本文不锁定一个具体版本号而是建议你到 Spring Initializr 或 Maven 中央仓库查看当前稳定版本。以下依赖写法中version需要替换为你实际使用的版本。2.2 模型服务选型云端 API 与本地模型Spring AI 支持多种模型提供方包括 OpenAI 兼容接口、通义千问 DashScope、智谱、DeepSeek、Ollama 本地模型等。企业场景中国内开发者通常选择国内可直接访问的云模型服务或者在内网部署本地模型。本地模型方案中Ollama 是非常受欢迎的部署工具可以一键运行 Qwen、Llama 等开源模型。它特别适合开发测试阶段不需要申请 API Key也不需要担心调用费用。但本地模型的效果取决于机器配置生产环境需要单独评估推理性能和显存占用。云端模型的优势是效果稳定、并发能力强但会带来成本和管理问题。实际项目中建议采用“配置隔离 模型网关”的方式代码里不写死模型地址和 Key而是通过配置中心和环境变量区分测试、预发、生产环境。2.3 创建项目并引入依赖建议直接使用 Spring Initializr 创建项目添加 Spring Web 和 Spring AI 相关依赖。如果使用 Maven核心依赖大致如下!-- 文件路径pom.xml -- parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.3.5/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- Spring AI 基础依赖 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model/artifactId version${spring-ai.version}/version /dependency !-- 本文以 Ollama 为例实际项目可换成其它模型提供方 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-ollama/artifactId version${spring-ai.version}/version /dependency !-- RAG 相关向量存储 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-vector-store/artifactId version${spring-ai.version}/version /dependency !-- PDF / Word 文档解析 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-pdf-document-reader/artifactId version${spring-ai.version}/version /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-tika-document-reader/artifactId version${spring-ai.version}/version /dependency /dependencies需要注意Spring AI 的部分依赖需要单独的依赖管理如果使用的是 Spring Boot 3.3 以上版本官方 BOM 通常会包含 Spring AI 的依赖管理。如果 IDE 报版本冲突可以在dependencyManagement中显式声明 Spring AI BOM。2.4 基础配置模型端点与 Key以 Ollama 本地模型为例假设你在本机已经安装并启动了 Ollama并且拉取了qwen2.5:7b或类似模型配置文件可以这样写# 文件路径src/main/resources/application.yml spring: application: name: spring-ai-demo ai: ollama: base-url: http://localhost:11434 chat: options: model: qwen2.5:7b temperature: 0.7 embedding: options: model: nomic-embed-text datasource: url: jdbc:mysql://localhost:3306/ai_demo?useUnicodetruecharacterEncodingutf8 username: root password: your_password vectorstore: pgvector: index-type: HNSW distance-type: COSINE_DISTANCE dimensions: 768如果你使用云端模型例如通义千问 DashScope通常需要配置 API Key 和模型名称。推荐把敏感信息放到环境变量或配置中心而不是直接写死在application.yml中。例如spring: ai: dashscope: api-key: ${DASHSCOPE_API_KEY} chat: options: model: qwen-plus配置完成后推荐先写一个最简单的测试接口确认模型服务能通再继续后续的 RAG 和智能体开发。这样可以避免把“模型连不上”和“代码写错”混在一起排查。3. 从零完成一次大模型对话ChatClient 基础用法3.1 ChatClient 最小示例Spring AI 中ChatClient是面向开发者的主要入口。它类似于 Spring Web 中的RestClient提供了同步、流式、回调等丰富的调用方式。先来看一个最简单的同步调用。// 文件路径src/main/java/com/example/aimall/ChatController.java RestController RequestMapping(/ai) public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient.Builder builder) { this.chatClient builder.build(); } GetMapping(/chat) public String chat(RequestParam String message) { return chatClient.prompt() .user(message) .call() .content(); } }这里把ChatClient.Builder通过构造器注入这是 Spring AI 推荐的用法。prompt()创建一个 Prompt 构建器user(message)设置用户输入call()执行同步调用content()获取模型返回的文本内容。接口启动后访问http://localhost:8080/ai/chat?message你好如果配置正确会返回模型生成的文本。这个示例虽然简单但已经完成了“Java 后端调用大模型”的闭环。在实际项目中不建议直接把ChatClient塞进 Controller更好的做法是单独封装一个 Service 层把 Prompt 构造、参数处理、结果解析都放在 Service 中Controller 只负责接收请求和返回结果。3.2 流式输出与异步调用大模型生成文本通常需要几秒到几十秒如果同步等待完整结果用户体验会很差。对于对话类场景推荐使用流式输出。Spring AI 对流式调用提供了很好的支持GetMapping(value /chat/stream, produces text/plain;charsetUTF-8) public FluxString chatStream(RequestParam String message) { return chatClient.prompt() .user(message) .stream() .content(); }接口返回类型改为FluxString底层基于 Reactor 实现响应式流。前端可以通过 SSEServer-Sent Events接收文本片段实现类似 ChatGPT 的逐字输出效果。流式输出可以显著降低首字延迟但这也会带来一些工程问题比如网关的超时时间需要调大否则流还没结束就被网关断开了。日志记录不能简单地把整个返回文本拼在一起需要单独收集。如果模型输出中途异常需要给前端返回一个错误标记。因此在实际项目中流式接口通常会在 Service 层做一层包装把 Flux 的订阅、错误回调、完成回调都管理起来。3.3 使用结构化输出定义实体类模型返回的是纯文本但在业务系统中我们往往希望它直接返回 JSON方便程序解析。Spring AI 提供了结构化输出能力可以把模型输出映射到 Java 实体类。先定义一个实体类例如一个“商品推荐”的结果// 文件路径src/main/java/com/example/aimall/dto/ProductRecommendation.java public class ProductRecommendation { private String productName; private String reason; private BigDecimal price; // getter / setter 省略实际开发中使用 Lombok 可以简化 }然后在 ChatClient 调用时使用.entity()方法ProductRecommendation recommendation chatClient.prompt() .user(推荐一款适合程序员办公的机械键盘并说明理由) .call() .entity(ProductRecommendation.class);Spring AI 会在底层引导模型输出符合实体结构的 JSON并自动反序列化为 Java 对象。不过这里有一个容易踩坑的地方模型返回的 JSON 字段名必须和实体类的属性名匹配否则反序列化可能失败。建议在 Prompt 中明确要求“只返回 JSON不要包含 Markdown 代码块标记”同时保留默认值兜底。结构化输出非常适用于信息抽取、分类、表单自动填充等场景。它让大模型从“闲聊工具”变成了“业务处理组件”。在实际项目中通常会把实体类放在独立的 DTO 包中并且在字段上使用JsonProperty或统一规范命名减少字段名不一致导致的解析问题。3.4 参数校验与错误处理调用大模型和调用普通 HTTP 接口不同它可能因为网络波动、模型限流、内容安全策略等原因失败。如果代码不做异常处理用户看到的将是 500 错误或一段崩溃日志。推荐的做法是定义一个统一的 AI 服务异常把上游错误包装成业务异常再配合全局异常处理器返回友好提示。// 文件路径src/main/java/com/example/aimall/service/AiChatService.java Service public class AiChatService { private final ChatClient chatClient; public AiChatService(ChatClient.Builder builder) { this.chatClient builder.build(); } public String chat(String message) { try { return chatClient.prompt() .user(message) .call() .content(); } catch (Exception e) { throw new AiServiceException(大模型服务调用失败请稍后重试, e); } } }这里需要注意的是user(message)中的输入不能直接信任。如果用户输入内容超长或者包含异常字符应该先做统一校验。同时模型返回内容也可能包含不合适的片段业务侧需要做好内容安全过滤。4. RAG 实战搭建企业知识库问答系统4.1 RAG 在企业场景中的价值很多企业已经拥有大量的产品文档、内部制度、运维手册甚至几十万字的项目文档。员工想要快速找到答案传统搜索只能做关键词匹配准确率有限。大模型虽然能理解自然语言但不了解企业内部私有知识。RAG 把这两者结合起来让大模型基于企业内部文档回答问题。相比重新训练一个行业大模型RAG 的成本和落地周期都有明显优势。你可以随时把最新的制度文档加入知识库不需要重新训练。因此RAG 是目前企业知识库问答系统的主流实现方式。在 Spring AI 中一个完整的 RAG 流程由三个环节组成文档加载与切块、向量化与存储、检索与回答。任何一个环节做不好都会影响最终回答质量。4.2 文档加载从 Word、PDF、网页读取内容企业知识库的文档来源多种多样PDF、Word、Markdown、HTML 网页甚至数据库中的文本字段。Spring AI 提供了不同的 DocumentReader 来处理这些格式。以读取 PDF 为例// 文件路径src/main/java/com/example/aimall/rag/DocumentLoader.java Component public class DocumentLoader { private final VectorStore vectorStore; public DocumentLoader(VectorStore vectorStore) { this.vectorStore vectorStore; } public void loadPdf(InputStream inputStream) { var reader new PagePdfDocumentReader(inputStream); ListDocument documents reader.get(); // 切块处理 TokenTextSplitter splitter new TokenTextSplitter(); ListDocument chunks splitter.apply(documents); // 写入向量库 vectorStore.add(chunks); } }这里的PagePdfDocumentReader按页读取 PDF 内容TokenTextSplitter负责把每一页内容按 Token 数量切块。文档中的元信息比如来源文件名、页码、标题Spring AI 会一并保存到Document的 metadata 中这些元信息在后续“引用溯源”场景非常有用。不同格式的文档需要选择不同的 Reader。比如 Word 文档可以使用 Apache Tika 相关的 ReaderMarkdown 可以直接使用通用文本读取。实际项目中建议先做一个“文档上传”接口后台同步解析并记录解析成功或失败的状态。4.3 切块策略为什么切块直接影响回答质量切块是 RAG 中最容易被忽视的环节。切块过大检索出来的片段可能包含大量无关信息模型回答时容易被干扰切块过小单个片段可能缺少完整上下文导致回答不完整。常见的切块策略有三种按固定 token 数量切块例如每 500 个 token 一块块间设置 50 个 token 的重叠。这种方式简单稳定适合大多数场景。按文档结构切块例如按章节、段落切分适合结构清晰的 Markdown 文档。Spring AI 中可以用自定义DocumentTransformer实现。按语义切块通过语义相似度判断文本边界效果最好但成本更高。实际项目中推荐先按 token 切块保持一定的重叠。切块后可以用一个简单的测试问题验证检索效果如果检索结果前三条都不相关优先调整切块大小和重叠 token 数而不是急着换模型。4.4 向量化与向量库存储文本块切好之后需要把每个文本块转换成向量。这个转换过程依赖 EmbeddingModel。在 Spring AI 中向量化通常不是手动调用的而是在配置好 VectorStore 后由vectorStore.add(documents)自动完成。向量库的选择很重要。生产环境常见的方案包括PostgreSQL pgvector适合已经使用 PostgreSQL 的企业运维成本低。Elasticsearch如果公司已经有 ES 集群可以复用且支持全文检索与向量检索混合查询。Milvus专门为向量检索设计的数据库适合大规模数据和高并发场景。Redis内存型数据库检索速度快适合中小规模数据。Spring AI 对多种向量库提供了抽象支持配置方式大同小异。以 pgvector 为例只要在application.yml中配置数据源和向量库类型Spring AI 会自动创建向量表。存入向量库后知识库就建好了。接下来要做的不是“把文档发给模型”而是“根据问题召回相关片段”。4.5 检索增强相似度检索与 Prompt 组装用户提问时系统需要把问题转成向量然后在向量库中找到最相似的文本块。Spring AI 提供了VectorStore.similaritySearch()用法如下// 文件路径src/main/java/com/example/aimall/service/RagChatService.java Service public class RagChatService { private final VectorStore vectorStore; private final ChatClient chatClient; public RagChatService(VectorStore vectorStore, ChatClient.Builder builder) { this.vectorStore vectorStore; this.chatClient builder.build(); } public String ask(String question) { ListDocument documents vectorStore.similaritySearch(question); String context documents.stream() .map(Document::getContent) .collect(Collectors.joining(\n\n)); String prompt 请根据以下资料回答用户问题。 如果资料中没有答案请明确说明“知识库中未找到相关内容”不要编造。 资料 %s 问题 %s .formatted(context, question); return chatClient.prompt().user(prompt).call().content(); } }这里的关键是 Prompt 设计。要明确告诉模型知识库中没有的内容不要编造。这个约束直接关系到回答的可信度。你可以进一步要求模型在回答中包含引用的文档标题或页码但前提是切块时保留了对应的 metadata。在生产环境中similaritySearch通常会加上topK限制和相似度阈值过滤避免把低相关的片段也塞进上下文。4.6 引用溯源与回答可信度RAG 系统上线后业务方最关心的一个问题就是“答案是从哪里来的”。如果模型输出一段结论但没有来源说明业务方很难放心使用。实现引用溯源的核心是让模型在回答时标注依据来自哪个文档。我们可以在切块时把文档标题、页码存入 metadata在检索后把这些信息拼进 Prompt并要求模型输出引用。例如 Prompt 可以进一步扩展资料 [1] 来源产品说明书.pdf 第3页 内容…… [2] 来源运维手册.docx 第12页 内容…… 请先根据资料回答然后在回答末尾列出引用编号。由于模型可能不稳定地输出引用更好的方案是在代码里把检索到的文档列表返回给前端让前端展示“相关文档”区域。这样即使模型没有主动引用用户也能看到答案背后的依据。引用溯源不仅提升了可信度也方便了后续的审计。如果某个回答被用户投诉运营人员可以通过引用记录快速定位问题出在哪个文档。5. 智能体实战Tool Calling 与多轮任务执行5.1 什么是智能体和普通对话有什么区别很多开发者对“智能体”有误解以为它是一个类似 ChatGPT 的聊天机器人。其实智能体的本质是“模型 工具 执行循环”。模型负责理解目标和拆解步骤工具负责执行外部操作循环负责把工具执行结果反馈给模型直到完成最终任务。举个例子。用户问“帮我查一下订单 A10086 当前到哪一步了如果已发货就告诉用户预计送达时间。”普通对话模型只能凭训练知识回答它并不知道订单状态。智能体则需要调用订单查询工具获取订单状态。如果状态是“已发货”再调用物流查询工具获取预计送达时间。汇总结果回复用户。在 Spring AI 中Tool Calling 实现了这个机制。模型在生成回答时如果发现需要外部数据会输出一个“工具调用请求”Spring AI 拦截这个请求执行对应的 Java 方法把返回值再交给模型继续生成。5.2 用 Tool 注册业务工具Spring AI 允许你通过注解把一个普通的 Service 方法暴露给模型。下面是一个订单查询工具的示例// 文件路径src/main/java/com/example/aimall/agent/OrderTools.java Component public class OrderTools { Tool(description 根据订单号查询订单状态返回状态码与状态描述) public String queryOrderStatus(String orderId) { // 实际项目中这里会调用订单服务或数据库 if (A10086.equals(orderId)) { return 订单已发货物流公司为顺丰运单号 SF1234567890; } return 未查询到订单 orderId; } }Tool注解中的 description 非常重要它是模型决定“什么时候调用这个工具”的依据。描述写得越具体模型判断越准确。方法参数也需要遵循简单类型和清晰命名因为模型需要自动生成参数值。之后在构建 ChatClient 时需要把工具注入进去ChatClient chatClient ChatClient.builder(chatModel) .defaultTools(new OrderTools()) .build();当用户输入的问题涉及订单查询时模型会在生成回答之前调用queryOrderStatus拿到结果后再组织语言。这里特别强调工具方法必须做好权限校验和参数校验。模型虽然很聪明但它在生成参数时也可能出错而且工具一旦暴露给用户就存在被恶意利用的风险。生产环境中工具方法内部要做用户身份校验确保用户只能查询自己有权限的数据。5.3 多轮对话与状态管理智能体不是一次调用就能完成的。一个复杂任务可能需要多次“模型生成 - 工具调用 - 结果返回”的循环。每次循环之间模型需要记住之前的对话上下文和已经执行过的工具结果。Spring AI 中ChatMemory负责保存对话历史。最简单的实现是InMemoryChatMemory适合单机开发环境。生产环境中推荐把对话历史持久化到 Redis 或数据库中方便多实例共享。实际开发中我建议把一次完整对话的 ID 作为业务标识。每次用户发送消息时先根据对话 ID 从存储中恢复历史消息再调用模型接口最后把新的消息追加到历史中保存。对话历史的长度也需要控制。如果聊天轮次很多历史消息会占用大量上下文窗口导致响应变慢甚至超限。可以在保存前截断只保留最近 N 轮或者对历史消息做摘要压缩。5.4 多智能体协作与编排当业务复杂度进一步提升单个智能体可能难以胜任。比如一个客服助手既需要处理售后问题又需要回答产品知识还需要处理用户投诉。如果把所有工具都注册到一个智能体上模型可能频繁误选工具效果反而不如拆分成多个专业智能体。多智能体编排的核心思想是一个主智能体负责理解用户意图然后分发给子智能体执行。Spring AI 对于多智能体没有强制规定它更倾向于让开发者通过 Java 代码编排。你可以使用 Spring 的Service把不同智能体封装成不同组件再通过一个路由 Service 做分发。// 文件路径src/main/java/com/example/aimall/agent/AgentRouter.java Service public class AgentRouter { private final AfterSaleAgent afterSaleAgent; private final ProductAgent productAgent; public AgentRouter(AfterSaleAgent afterSaleAgent, ProductAgent productAgent) { this.afterSaleAgent afterSaleAgent; this.productAgent productAgent; } public String route(String question) { if (question.contains(退货) || question.contains(退款)) { return afterSaleAgent.chat(question); } return productAgent.chat(question); } }这种基于规则的分发方式简单可靠适合大多数业务。更复杂的做法是让一个主模型先判断意图再分发但会增加调用成本和延迟。在实际项目落地时建议先从规则分发入手积累足够数据后再考虑模型分发。6. 业务封装把 Spring AI 嵌入企业应用6.1 统一 API 返回与异常体系当 AI 能力不再只是 Demo而是要提供给前端、小程序、App 使用时第一步就是把访问入口标准化。统一 API 返回结构可以避免前端每个页面各自处理异常。定义一个通用的响应体// 文件路径src/main/java/com/example/aimall/common/ApiResponse.java public record ApiResponseT(int code, String message, T data) { public static T ApiResponseT success(T data) { return new ApiResponse(0, success, data); } public static T ApiResponseT error(int code, String message) { return new ApiResponse(code, message, null); } }在 Controller 中把返回值统一包裹为ApiResponse。如果 Service 抛出AiServiceException通过RestControllerAdvice统一捕获并转换为ApiResponse.error(...)。这里有一个值得注意的点AI 接口的“错误”不只是 HTTP 错误还包括“知识库未找到内容”“模型内容被安全策略拦截”等业务错误。因此建议在统一的响应体中预留data以外的字段例如traceId方便后续排查问题。6.2 超时、重试与限流大模型接口的响应时间波动很大高峰期可能几十秒甚至超时。如果业务系统直接等待线程池容易被打满。推荐从三个层面控制超时控制在 HTTP 客户端级别设置连接超时、读取超时。Spring AI 中也提供对应的超时配置属性。重试策略仅对“可重试”的异常进行重试比如网络超时、HTTP 503。对于模型提示词违规、参数错误等 4xx 错误不要重试。限流保护AI 接口调用通常会消耗 tokens生产环境必须做限流。可以在网关层按用户维度限流也可以在 Service 层使用 Redis Lua 实现令牌桶。实现时可以引入 Resilience4j用注解方式定义重试和熔断策略。例如Retry(name aiRetry, fallbackMethod chatFallback) public String chat(String question) { return chatClient.prompt().user(question).call().content(); } public String chatFallback(String question, Exception e) { log.error(AI 调用失败question{}, question, e); return 服务暂时繁忙请稍后重试; }重试不是越多越好。每重试一次都会增加一个模型的请求产生一次费用。建议最多重试 2 到 3 次并且退避时间指数增长。6.3 日志、审计与敏感信息脱敏AI 应用涉及用户输入和模型输出这些内容可能包含个人信息、商业机密。日志记录时必须谨慎。首先不要在日志中完整打印用户的提问内容尤其是涉及手机号、身份证号、地址等敏感信息时先做脱敏处理。可以写一个脱敏工具类对常见字段格式做替换例如手机号只保留前三位和后四位。其次如果业务需要审计用户与 AI 的对话建议单独设计一张对话日志表包含用户 ID、会话 ID、消息内容脱敏后、模型名称、token 消耗、耗时、返回状态。这些数据既满足合规审计需求也能为后续模型效果分析提供素材。最后模型返回的内容也可能包含不安全信息。在展示给用户之前可以增加一层内容安全过滤对违法违规内容做拦截。这是 AI 应用上线前必须考虑的问题。6.4 配置管理多环境与动态刷新企业项目通常有开发、测试、预发、生产多套环境。每套环境的模型地址、API Key、向量库地址可能都不一样。如果把这些配置写死在application.yml中发版时会非常痛苦。推荐做法是把 AI 相关配置统一放到配置中心例如 Nacos 或 Apollo。以 Apollo 为例可以把以下配置项抽取到公共命名空间模型服务地址和 API Key。模型名称、温度、最大 Token 数。向量库连接信息。RAG 切块大小、检索 topK。是否开启流式输出。使用 Spring Boot 的ConfigurationProperties绑定一组配置类让代码只面向配置对象编程// 文件路径src/main/java/com/example/aimall/config/AiProperties.java Component ConfigurationProperties(prefix app.ai) public class AiProperties { private String modelName; private int topK 5; private int chunkSize 500; private int chunkOverlap 50; // getter / setter 省略 }当模型供应商调整或版本升级时只需要修改配置不需要重新发版。多环境之间的配置通过配置中心的命名空间和环境标签隔离能有效避免 Key 泄露到生产环境以外的场景。6.5 本地缓存与热点问题优化知识库中经常存在一些高频问题比如“公司年假制度是什么”如果每次都走完整的 RAG 链路会产生不必要的向量检索和模型调用成本。可以在检索结果层面做本地缓存以问题文本的哈希值为 key缓存模型返回结果设置过期时间。由于相同问题在不同时间可能得到不同答案所以缓存过期时间不宜过长。实现缓存时可以使用 Spring Cache 抽象Service public class FaqCacheService { Cacheable(cacheNames aiAnswer, key #question) public String getAnswer(String question) { return ragChatService.ask(question); } }这里有一个陷阱不能直接把用户输入的完整文本作为缓存 key因为同一问题可能因为标点、大小写不同而被视为不同 key。比较稳妥的做法是对问题先做归一化比如去掉空格、统一全半角再计算哈希。如果热点问题非常集中还可以在后台定时预热把常见问题的答案提前生成并放入缓存这样用户请求时直接命中缓存响应速度会快很多。不过需要注意预热任务会消耗模型调用额度要放在业务低峰期执行。7. 项目上线部署、压测与监控7.1 容器化部署与镜像构建Spring Boot 3.x 项目推荐使用 Docker 容器化部署这样模型服务、向量库、应用可以做到隔离管理。一个最小化的 Dockerfile 大致如下# 文件路径Dockerfile FROM eclipse-temurin:17-jre WORKDIR /app COPY target/*.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]构建镜像前需要注意镜像中不应该包含模型 API Key 等敏感信息。这些信息应该在容器启动时通过环境变量注入。配合 Kubernetes 或 Docker Compose可以把环境变量统一管理。在编排上应用服务、数据库、向量库建议分开部署。如果使用 pgvector数据库实例需要预留足够的磁盘空间因为向量数据的大小通常比原始文本大很多索引也需要额外空间。7.2 上线前的性能与成本评估AI 应用上线前需要回答几个问题单次对话平均耗时是多少高峰并发时是否可接受每个用户的平均 token 消耗是多少按照用户量估算月成本。模型调用失败率是多少是否需要降级方案建议先做一轮压测模拟真实用户行为观察模型服务、数据库、应用线程池的负载情况。如果发现模型服务是瓶颈可以增加本地缓存、减少重复请求或者通过模型网关做负载均衡。成本方面RAG 场景中一次普通对话通常包含一次 Embedding 调用和一次 Chat 调用。如果做了多轮智能体还可能有多次工具调用。上线后要对每个用户、每个接口的 token 消耗做明细统计避免出现成本失控。7.3 监控指标与告警设计AI 应用除了常规的 QPS、延迟、错误率监控外还需要增加模型特有的监控指标token 消耗量按模型、按接口、按用户维度统计。请求失败原因分布网络错误、超时、限流、内容安全拦截分别占比多少。检索召回率知识库中是否总是能检索到相关内容。首字延迟流式输出场景中第一个 token 返回的耗时。这些指标可以通过 Spring Boot Actuator 暴露也可以自定义埋点到 Prometheus。告警规则建议覆盖两个方向一是技术故障比如错误率超过 5%二是业务异常比如“知识库无结果”的比例持续升高说明知识库内容可能已经落后于业务。7.4 灰度发布与回滚策略AI 模型的输出不像普通代码那样稳定即使同一个模型版本不同时间点的效果也可能有差异。因此升级模型版本、修改 Prompt、调整切块策略都应该通过灰度发布来验证。灰度方案可以有多种按用户比例灰度先让 5% 的用户走新模型或新 Prompt观察满意度指标。按业务场景灰度某些渠道走新方案某些渠道继续走旧方案。按问题类型灰度复杂问题走新方案简单问题走旧方案。回滚的关键是 Prompt 和配置版本化。建议把 Prompt 模板存入配置中心而不是硬编码在 Java 代码中。这样一旦新 Prompt 效果不好修改配置就能立即回滚不需要重新发版。需要特别注意的是向量库中的数据版本也需要管理。如果知识库重新导入了一次线上用户可能检索到新旧混用的问题。建议给向量数据打上版本标签在检索时只查询当前生效的版本。8. 常见问题与排查清单8.1 高频问题汇总问题现象常见原因解决思路启动时报模型连接失败模型服务地址配置错误或未启动检查 base-url、API Key先 curl 测试模型服务调用接口返回 401/403API Key 未配置或权限不足检查环境变量是否注入确认账号是否有模型调用权限RAG 检索结果完全不相关切块过大或向量库中未写入数据检查向量库数据量调整切块大小和重叠参数结构化输出时 JSON 解析失败模型返回了 Markdown 代码块或字段名不一致在 Prompt 中明确要求纯 JSON用工具类清洗返回内容流式输出断连网关超时或内存不足增加网关超时时间检查容器内存配置回答引用来源错误metadata 未正确保存或 Prompt 指令不清检查 Document 的 metadata调整 Prompt 要求多轮对话后上下文丢失ChatMemory 配置错误或未开启检查会话 ID 传递确认历史消息已加入请求线程池耗尽同步调用阻塞时间过长改流式调用设置合理超时增加线程池监控8.2 模型返回 JSON 解析失败详解这是 Spring AI 开发中非常常见的问题。现象是entity()转换时抛出 Jackson 解析异常。排查思路如下第一步先打印模型原始返回内容确认是否带有 json 这类标记。很多模型在返回 JSON 时会自动包裹代码块Spring AI 通常能处理但版本不同表现也不同。第二步检查实体类的字段类型。如果字段是int而模型返回10或10.0可能导致类型转换失败。推荐使用包装类型Integer、BigDecimal更容错。第三步检查字段名称是否匹配。如果使用 Lombok 时出现Builder冲突也可能影响反序列化。保持实体类简单直接能显著减少这类问题。8.3 RAG 回答质量差怎么调回答质量差不要第一时间换大模型。先检查知识库侧检索到的片段是否包含答案如果不包含说明切块或召回有问题。片段是否足够完整如果关键信息被切到了下一块适当增大切块大小或重叠数。Prompt 是否清晰如果 Prompt 没有限制“基于资料回答”模型可能会凭自己知识编造。如果以上都正常但效果仍不理想再考虑升级模型版本。模型能力只是 RAG 的一个环节数据质量和检索质量才是决定系统上限的因素。8.4 Java 工程常见工具链问题在 Spring AI 项目开发过程中还有一些非业务但很烦人的问题Lombok 报 “You arent using a compiler supported by Lombok” 警告通常是因为 IDE 中 Lombok 插件版本和 JDK 版本不匹配检查插件版本或改用 JDK 17。Maven 依赖冲突项目中同时引入了不同版本的 Spring AI 模块建议通过 BOM 统一版本管理。应用启动时出现OutOfMemoryError: Insufficient memory通常是容器内存设置太小尤其是向量库嵌入到应用进程时需要单独评估内存占用。遇到这些问题时建议先查看完整堆栈定位到具体依赖和版本再决定升级还是降级。不要盲目升级到最新版本稳定性和兼容性比新功能更重要。9. 最佳实践与学习路线9.1 工程规范建议在 Spring AI 项目实践中我总结下来最值得推荐几条规范面向接口编程。模型供应商可能切换建议把 ChatModel、EmbeddingModel、VectorStore 的访问封装在 Service 层避免业务代码到处都是模型类型判断。Prompt 模板化。不要散落在代码中拼接字符串使用 Spring AI 的 Prompt Template 或配置中心统一管理。会话上下文统一管理。对话历史不要随请求传来传去由后端统一维护这样方便后续做审计和限流。工具函数幂等。所有 Tool 方法都应当幂等避免模型重复调用产生副作用。配置外部化。模型名称、Key、向量库地址、切块大小这些参数都不能写死在代码中。9.2 安全合规建议AI 应用的安全问题比传统应用更复杂。模型输出的不可控性、工具调用的越权风险、用户输入的注入风险都需要重视。用户权限校验必须在工具方法内部完成不能依赖模型“自觉”不调用越权工具。对用户输入做长度限制和内容过滤防止超长 Prompt 消耗大量 token。外部网页内容加载到知识库前要检查内容的版权和合规性不要随意抓取。模型输出展示给用户前建议经过内容安全服务过滤。对话数据和操作日志要加密存储访问权限严格控制。9.3 从入门到落地的学习路线如果你刚开始接触 Spring AI建议按照下面的顺序推进第一步跑通基础对话。不追求花哨只把 ChatClient 同步调用和流式调用跑通理解配置项。第二步做一个最小 RAG。选择一个小型 PDF 文档完成加载、切块、向量化、检索、回答整个链路重点观察切块对结果的影响。第三步实现一个业务工具调用。找一个常见的业务查询场景比如查订单、查库存用 Tool 暴露给模型。第四步封装成可用的业务接口。把统一返回、异常处理、超时重试、缓存加上让它像一个真正的企业后台接口。第五步加入配置中心、监控和日志审计考虑部署上线。每一步都做实再去看更复杂的多智能体编排、模型微调就会轻松很多。本文中每一章对应的代码示例我都建议你亲手敲一遍尤其是切块参数、检索结果、工具调用这些细节运行过才会有体感。后续你还可以继续关注 Spring AI 的 Agent 相关组件、AI 网关、可观测性集成等方向。技术生态更新很快保持动手和阅读官方 Release Notes 的习惯比盯着任何一篇教程都重要。等你把一个 RAG 问答系统真正部署到生产环境、持续运行一段时间后你会对模型调用成本、上下文管理、知识库维护有更深刻的理解。到那时候再回头优化 Prompt 和切块策略就是水到渠成的事情了。