
Spring AI 2.0 GA是可以让Java后端真正把大模型能力做进业务系统的版本。这篇文章围绕Spring AI 2.0企业级实战把环境搭建、RAG问答、智能体设计、业务封装、项目上线整条链路走一遍。如果你是Java后端、有Spring Boot基础但还没系统做过AI应用这篇可以当一个从零到上线的路线图。RAG和智能体这两个概念网上讲得很多真正麻烦的是工程细节模型怎么接、文档怎么切、检索结果怎么组装、工具调用怎么控制权限和异常这些都决定了Demo和线上系统的差距。1. Spring AI 2.0到底解决了什么问题1.1 Java后端接大模型缺的不是模型而是抽象很多团队接大模型早期做法是用HttpClient直接调模型API自己拼Prompt手动解析JSON。单个接口还好一旦要支持多个模型、多轮对话、RAG检索、工具调用散装代码很快就会失控。Spring AI做的事是把这些动作抽象成Spring风格的组件。ChatModel管理模型接入和调用。ChatClient管理对话、工具调用、结构化输出。DocumentReader负责文档解析。TextSplitter负责文本切块。VectorStore负责向量存储和检索。Advisor负责把检索结果和问题拼成更合理的Prompt。Java开发者不需要重新学一套框架还是Controller、Service、Bean、配置那一套。模型供应商变了换依赖、换配置业务代码基本不用大改。这是Spring AI最核心的价值。1.2 2.0版本里值得重点关注的几个变化从实际用下来的体验看Spring AI 2.0相比早期版本有几个明显变化。第一模块拆分更清晰。模型、向量库、RAG、工具、可观测性都是独立模块项目里用到什么引什么不会像以前一样一个starter把一堆东西全带进来。第二结构化输出变得稳定很多。让模型返回一个Java对象不需要自己写复杂的JSON解析定义好实体类通过entity()方法就能拿到结果。第三ChatClient的API收敛了。普通对话、流式输出、工具调用、结构化响应都在同一个入口下做学习成本比1.x时代低一些。第四对智能体、图编排、可观测性的支持更完整企业落地时更容易把链路监控起来。当然Spring AI还在快速迭代不同版本之间API会有调整。本文示例用2.0.0的风格来写你落地时还是要以当前实际GA版本为准。下面按一条完整路径来拆环境搭建、RAG问答、智能体设计、业务封装、项目上线。2. 环境搭建从JDK、Spring Boot到可用的ChatClient2.1 环境准备和版本选择Spring AI 2.0是基于Spring Boot 3.x构建的一般会和Spring Boot 3.4、3.5配合使用。JDK至少需要17实际项目中建议直接用21如果你在准备面试或者自己学习21也是目前Java生态更主流的选择。除了Java和Maven/Gradle你还需要一个模型入口至少要满足其中一种外部模型API例如OpenAI或兼容OpenAI协议的接口。国内模型服务例如DashScope等通常也提供OpenAI兼容模式。本地模型例如通过Ollama跑Qwen2.5、Llama等模型适合开发和隐私要求高的场景。如果你的机器配置一般我建议先用本地Ollama跑一个小模型做入门验证。配置不高也能跑但要控制并发不要同时开很多任务。后面做RAG时本地Embedding模型也能覆盖。2.2 创建项目和引入依赖先创建一个普通Spring Boot工程依赖上除了web还要引入Spring AI的BOM和模型starter。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.5.0/version relativePath/ /parent dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version2.0.0/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- 模型基础模块 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-model/artifactId /dependency !-- 根据实际模型服务商选择 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai/artifactId /dependency !-- 本地模型可选 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-ollama/artifactId /dependency /dependencies注意Spring AI版本更新很快。代码里写2.0.0只是示例你创建项目时应该去Maven Central或Spring官网看当前实际GA版本把版本号换成最新的。2.3 配置文件里的关键参数application.yml里主要配置模型选择、API Key、Base URL和模型名称。spring: application: name: spring-ai-demo ai: model: chat: openai openai: api-key: ${OPENAI_API_KEY:sk-xxx} base-url: ${OPENAI_BASE_URL:https://api.openai.com} chat: options: model: gpt-4o-mini temperature: 0.7 ollama: base-url: http://localhost:11434 chat: options: model: qwen2.5:7b这里有几个点容易踩坑。第一API Key不要硬编码在配置文件里。开发时可以从环境变量读生产环境更要放到配置中心或密钥管理服务里。第二spring.ai.model.chat用来指定当前生效的ChatModel。项目里同时引入OpenAI和Ollama时这个字段决定启动时加载哪个。第三不同版本的配置层级可能不一样。IDE一般会有配置提示也可以直接看依赖里spring-configuration-metadata.json不要凭记忆硬写。2.4 写一个最简单的对话接口配置好之后先跑通一个最小对话接口。RestController RequestMapping(/api/chat) public class ChatController { private final ChatClient chatClient; public ChatController(ChatModel chatModel) { this.chatClient ChatClient.builder(chatModel).build(); } PostMapping(/simple) public String simple(RequestBody String message) { return chatClient.prompt(message) .call() .content(); } }这里解释一下为什么用ChatClient而不是直接用ChatModel。ChatModel更像是底层的模型封装负责发请求、收响应。ChatClient在它之上提供了更贴近业务的方法比如.prompt()、.tools()、.entity()。企业项目里建议把ChatClient作为Bean交给Spring管理不要每次请求都重新build否则连接、配置、Advisor都要重复创建。先跑单条任务。能正常返回内容说明环境通了。如果返回报错按这个顺序排查看API Key有没有传到环境变量。看Base URL能不能从当前网络访问。看模型名称是否匹配当前模型服务商。看日志里的HTTP状态码和响应体区分是鉴权错误还是模型不存在。3. RAG问答实战文档加载、切块、向量化、检索、增强3.1 企业知识问答为什么不能只靠大模型记忆大模型不会知道你公司内部的产品文档、售后问题、项目规范而且知识经常更新重新训练不现实。把整份文档都拼到Prompt里成本高还会超出上下文窗口。RAG的思路是先检索再生成。具体流程是文档加载、文本切块、向量化、存入向量库用户提问时先去向量库检索相关片段把片段拼进Prompt再让模型基于片段回答。Spring AI把这一套流程抽象成组件开发时不需要自己写太多胶水代码。3.2 文档加载与解析在Spring AI里文档加载主要通过DocumentReader完成。文本文件可以用TextFileReader。PDF文档可以用PagePdfDocumentReader。多种格式混合场景可以用TikaDocumentReader。加载完成的Document包含两个部分文本内容和metadata。metadata非常重要后续做引用溯源靠的就是它。一个典型的加载代码框架如下// 以文本文件为例读取classpath下的多个文档 var reader new TextFileReader(classpath:/docs/*.txt); var documents reader.read(); System.out.println(文档数量: documents.size()); System.out.println(第一个文档内容长度: documents.get(0).getText().length());不要一上来就加载几千份大文件。先拿一份文档跑通全流程确认内容被正确读取再处理批量。PDF加载经常出问题。不是PDF文件损坏而是扫描件、加密PDF、复杂表格页解析效果差。如果发现检索出来的是乱码或空内容先看这个PDF能否被正常复制文本不能的话需要OCR这不是Spring AI自身能解决的。3.3 切块策略决定RAG质量的第一步切块是RAG里最容易被忽略、又最影响效果的环节。同一个问题切块方式不同检索结果可能完全不同。几种常见切块方式切块方式适用场景建议参数按固定字符切普通文本、公告、新闻500到1000字一块按Token切中文、混合中英文chunkSize 500到1000overlap 50到200按标题/章节切Markdown、结构化文档用标题作为边界尽量不跨章节按页面切PDF、扫描报告每页一块保留页码metadata自定义分隔符切代码、日志、固定模板按分隔符拆再控制长度对中文场景按Token切的效果通常好于按字符切因为中文一个字的语义信息量比英文字母高按字切容易出现碎片。不管用哪种方式都要设置overlap也就是相邻块之间有少量重叠。否则一个完整段落刚好落在两块边界上检索时两边都不完整。代码里可以先使用默认的文本切分器var splitter new TokenTextSplitter(); var chunks splitter.apply(documents);如果你想自己控制参数务必在你引入的版本里确认构造方法参数顺序。不要把网上搜到的参数顺序直接抄到代码里Spring AI的不同版本对splitter参数调整过。别把块切得太小。块太小语义被切碎模型看不到完整上下文。块太大检索噪声变大还容易超过模型上下文窗口成本也会上升。判断切块是否合理可以这样做随机抽几个问题去向量库看检索出来的Top5片段。人工判断这些片段是否与问题相关。如果多份相似文档互相干扰考虑在metadata里增加分类字段检索时按分类过滤。3.4 向量化与向量库选型切块之后要把文本转成向量。这一步由EmbeddingModel完成。它和对话模型不是一个东西需要单独配置。如果你的应用要接外部大模型向量化也可以用同一个厂商的Embedding接口。如果做本地化Ollama也可以跑Embedding模型开发阶段够用。向量库选择可以参考这个表向量库适合场景注意点SimpleVectorStore本地测试、Demo、小数据量重启后数据需要重新加载PgVector已有PostgreSQL的业务系统需要启用vector扩展Redis实时性要求高、已有Redis集群注意向量索引和过期策略Milvus/Qdrant数据量大、需要复杂过滤和分布式需要单独部署运维成本高如果只是入门建议先用SimpleVectorStore把RAG流程跑通。不要一开始就上分布式向量库业务还没验证清楚先解决流程问题再考虑性能问题。3.5 检索增强和引用溯源向量库建好之后在ChatClient里挂上QuestionAnswerAdvisor就能实现“先检索后生成”。ChatClient chatClient ChatClient.builder(chatModel) .defaultAdvisors(new QuestionAnswerAdvisor(vectorStore)) .defaultSystem(请基于提供的资料回答问题不要编造。如果资料里没有相关信息请明确说明。) .build();这里有两个关键点。第一检索结果不是越多越好。默认TopK可能取4到5条你可以根据文档质量调整。取太多模型容易被无关信息干扰取太少有效信息可能漏掉。第二回答必须可溯源。企业级场景里模型说“根据我们的制度报销需要三天内提交”用户会问依据是哪份文件你需要在文档加载和切块时保留source、page等metadata并在回答时把引用返回给前端。一个简单的做法是定义一个引用结构public record Reference(String source, String page, String excerpt) { }Service层在返回答案时同时返回命中的文档来源和摘要片段。这样既方便用户审核也方便后续做Groundedness检查也就是判断回答是否真的基于检索内容减少了模型自由发挥的概率。如果回答经常引用错误优先检查两件事一是切块是否合适二是metadata是否完整。不要直接怀疑模型很多问题出在检索上游。4. 智能体设计工具调用、系统提示词、多智能体4.1 智能体的核心是工具调用大模型本身不知道你系统里的订单状态、库存数量、优惠券数据。想让模型完成真实业务操作就要给它提供工具。Spring AI里工具调用很简单用Tool注解描述一个方法即可。Component public class OrderTools { private final OrderService orderService; public OrderTools(OrderService orderService) { this.orderService orderService; } Tool(description 根据订单号查询订单状态) public String getOrderStatus(String orderNo) { return orderService.findStatus(orderNo); } Tool(description 获取用户可用优惠券数量) public int getCouponCount(String userId) { return orderService.countCoupons(userId); } }调用时把工具对象传给ChatClientString answer chatClient.prompt(订单 A1001 现在什么状态) .tools(new OrderTools()) .call() .content();模型看到用户问题后会判断是否需要调用某个工具然后自动带上参数调用。调用完再把结果作为上下文生成最终回答。工具调用真正坑人的地方不在注解而在工具本身的设计。工具方法名和描述要清晰因为模型靠这些判断该不该调用。参数要简单、稳定。复杂对象尽量拆成多个基本参数。工具执行要幂等。用户可能让模型重复调用不能因为一次重复执行就扣两次款、改两次状态。工具里要捕获异常并返回可理解的错误信息。不要直接把NullPointerException堆栈丢给模型。不要给模型太多无关工具。工具越多模型选择越不稳定先只开放核心工具。4.2 System Prompt、结构化输出和实体类定义智能体不能只靠工具还要有行为约束。System Prompt里要写清楚角色、任务、边界和输出要求。ChatClient chatClient ChatClient.builder(chatModel) .defaultSystem( 你叫小安是XX系统的智能客服助手。 你只能根据提供的资料和工具返回结果来回答。 如果信息不足直接说不知道不要编造。 回答要简洁控制在200字以内。 ) .build();有些场景要求系统提示词不能被用户覆盖Spring AI里也有对应机制根据版本不同一般可以在System Prompt或Reminder里设置。实际项目中我建议把用户的输入和内部System Prompt分开防止用户通过提示词注入绕过系统约束。再说结构化输出。后端接口要给前端返回规范JSON不能让模型自由发挥。定义实体类public record OrderAnalyzeResult( String orderNo, String status, ListString problems, ListString suggestions ) { }然后直接让模型返回这个类型的对象OrderAnalyzeResult result chatClient.prompt(分析订单A1001的问题) .entity(OrderAnalyzeResult.class) .call() .entity();底层会自动生成JSON Schema并约束模型返回符合结构的JSON再帮我们反序列化成Java对象。实体类设计有几个原则字段要少类型要简单。字段越多模型越容易出错。枚举值要固定不要让模型自己发明状态。比如status只允许PENDING、PROCESSING、DONE。集合字段要给数量限制。比如problems最多给5个否则模型可能输出一长串。不要把实体类设计成万能大对象一个场景一个类型更稳定。如果结构化输出不稳定先检查字段类型是否太复杂再看Prompt里有没有把输出要求讲清楚。4.3 多智能体与Agentic RAG单智能体能处理简单对话但企业场景往往有多个角色。例如一个售后系统里可以有知识库问答智能体、订单查询智能体、退款处理智能体。多个智能体之间需要调度和上下文传递这就是多智能体设计。Spring AI本身提供了工具调用和编排能力Spring AI Alibaba的Graph项目则支持更复杂的工作流和节点编排。具体做法要看项目规模和团队能力。还有一种思路是Agentic RAG。普通RAG是每个问题都检索一轮Agentic RAG让模型自己决定哪些问题需要检索、需要检索几次、检索结果不够时要不要追问用户、要不要调用其他工具。它的优点是更智能缺点是流程复杂、token消耗更高、排错更难。我的建议是不要一上来就做多智能体和Agentic RAG。先把单智能体、单轮RAG跑稳日志和监控做好用户反馈和评估数据积累下来再决定要不要加复杂编排。如果你团队里已经在用Dify、Coze这类低代码平台也可以把它们当前置编排层后端用Spring AI承载业务工具和数据结构。但轻量场景没必要增加系统复杂度Java技术栈自己就能闭环。5. 业务封装Service拆分、DTO、异常、并发和成本控制5.1 不要在Controller里拼Prompt很多初学者把Prompt写在Controller里Controller一边做参数校验一边调模型还一边解析结果。功能能跑但项目稍微一大就乱了。更稳妥的做法是分层Controller只做HTTP参数校验、调用Service、返回统一响应。Service层负责组装Prompt、调用ChatClient、处理工具调用、组装引用信息。独立的Client或Repository层负责外部模型通信。metadata、documents、vectorStore相关逻辑放到知识库服务里。好处有几个可以替换模型服务商可以加缓存和限流可以方便写单元测试还可以统一处理异常和日志。5.2 输入输出DTO、异常和降级不要把模型返回的原始字符串直接抛给前端。模型可能输出多余语气词、Markdown格式、甚至异常内容。接口层应该定义清晰DTO。请求类可以包含query、userId、sessionId、topK、是否启用检索等字段。public record ChatRequest(String query, String userId, String sessionId, Integer topK) { }响应类可以包含答案、引用列表、token用量、耗时、业务状态码。public record ChatResponse( String answer, ListReference references, Usage usage, long elapsedMs, String code ) { }同时要对模型调用做降级模型超时返回友好提示而不是500。模型返回空内容重试一次仍为空则记录日志。外部模型不可用如果同义问题有缓存可以返回缓存没有缓存则给用户一个降级话术。工具调用失败把错误信息作为上下文返回给模型让模型尝试换一种方式或明确告知用户无法完成。5.3 并发、缓存、限流和Token成本LLM调用是网络IO而且产生费用。企业项目里不能像跑Demo一样每个请求都直接打模型。并发控制方面可以先确定模型服务商的Rate Limit和自家业务峰值。不要一上来就开最大并发要先做一次最小并发压测观察失败率和延迟。本地Ollama跑模型时显存和CPU是瓶颈并发开高了任务会排队甚至直接OOM。缓存方面高频FAQ问题可以用短时缓存。同样的问题在一小时内重复问没必要每次都调模型。缓存Key要包含用户上下文和参数差异不要把所有请求都命中同一个缓存。限流方面可以为用户维度加每秒/每天调用次数限制。AI接口成本高没有限流线上很容易被个别用户拖垮。Token成本方面至少要能在日志里看到每次请求的Token用量。线上问题排查时如果某个用户消耗异常能很快定位。同时控制Prompt长度不相关的知识不要全部塞进去。5.4 安全合规与权限调用外部大模型时要注意隐私和敏感信息。用户输入里包含手机号、身份证、内部系统路径时要做脱敏。不要轻易把企业核心经营数据作为Prompt发送到外部模型。工具调用必须做用户维度鉴权不能用工具返回越权数据。模型回答内容也要做一定程度的合规过滤。如果模型被用户诱导输出不当内容不能只怪模型系统层面要有兜底。Spring AI有Moderation相关能力可以根据需要开启更基础的方案是在Prompt边界、输入侧过滤、输出侧过滤三层同时保护。6. 项目上线打包、部署、监控和上线前检查6.1 打包与Docker部署Spring AI项目本质上是Spring Boot应用打包方式和传统项目一致。mvn clean package命令执行完target目录下会生成可执行jar。本地先跑一下java -jar target/spring-ai-demo.jar如果本地能起来并且接口正常再做容器化。一个比较简单的DockerfileFROM eclipse-temurin:17-jre WORKDIR /app COPY target/*.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]如果系统里还依赖本地模型需要把模型服务单独部署不要把模型打近应用容器里否则镜像会非常大。向量库如果使用PostgreSQL或Redis也建议单独部署通过环境变量配置连接地址。6.2 配置、日志和监控上线前必须把配置从代码里剥离。API Key、Base URL、向量库连接串、模型名称全部放到环境变量或配置中心。日志方面AI应用最需要记录的是traceId、userId、sessionId、问题内容、回答内容、引用来源、Token用量、耗时、是否走缓存。但要注意日志里不要记录敏感原文问题内容可以记录用户手机号这类信息要脱敏。监控方面可以用Spring Boot Actuator暴露健康检查把关键指标接入PrometheusJVM内存和线程。ChatClient调用次数、失败率、响应耗时。Token用量趋势。工具调用成功率。向量库检索耗时和命中数量。这些指标不需要一开始全部做至少先把调用失败率和Token成本监控起来。否则上线后出了问题只能靠用户反馈排查。6.3 上线前的回归测试清单别只测一个“你好”。AI应用要回归的是完整链路。上线前建议按这个清单过一遍单条对话是否正常模型是否能正确响应。流式输出是否正常前端是否能正确渲染。RAG知识库文档是否重新加载切块和检索是否符合预期。引用溯源是否准确每个answer是否都能找到source。工具调用是否鉴权越权场景是否被拦截。工具执行是否幂等重复调用是否产生脏数据。并发压测是否通过模型服务商Rate Limit是否在可控范围。模型不可用时降级文案和状态码是否正确。日志、监控、告警是否接通。环境变量和密钥是否已经替换代码仓库里是否还有硬编码Key。如果这些都能过再考虑小流量发布。发布后观察一小时日志和监控重点看失败率、Token成本、慢请求确认没问题再放量。最后说一点个人体会。Spring AI 2.0把AI功能接入Java生态的门槛降得很低但真正上线需要盯的工程细节一点不比传统后端少。建议先把最小闭环跑通再逐步做RAG和智能体不要一上来就上复杂编排。先把日志、Token监控、异常降级、权限控制这些基本功做扎实比追新特性更有价值。