ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Spring AI 七天实战:从 ChatClient 到 RAG 的 Java 大模型开发

2026/9/18 3:32:33 拓冰建站 浏览量
Spring AI 七天实战:从 ChatClient 到 RAG 的 Java 大模型开发 Java圈子里想学 Spring AI 的人不少但大多数人不是被新框架吓退而是被东一榔头西一棒子的资料搞晕。我今年在好几个项目里用 Java Spring Boot 接大模型从最早的 Demo 到线上稳定运行把 Spring AI 这条技能树的前前后后摸了个遍期间踩过的坑拿记事本都记不完。这篇文章就按 7 天的节奏把 Spring AI 技能树从上到下点亮一遍每一阶段的源码结构、依赖配置和避坑点都会展开讲。已经熟悉 Spring Boot 基础、想快速切入 AI 应用开发的 Java 工程师可以直接照着走准备面试想系统梳理 Spring AI 核心概念的同学也能从里面捋出主线。1. 学习路线与技能树拆解7天时间到底怎么分配1.1 Spring AI 到底解决了什么问题先想清楚一件事Spring AI 不是让 Java 开发者去研究算法、训练模型它是把“调用大模型”这件事封装成了 Spring 风格的 API。以前我们要接大模型流程基本是手写 HTTP 调用、拼接 JSON、处理鉴权、管理会话状态而且换一家模型厂商整套代码可能都要推倒重来。Spring AI 的做法跟当年 Spring 封装 JDBC 的思路很像。你面向 ChatClient 编程底层对接的是 OpenAI、通义千问、Ollama 还是本地模型业务代码基本不用动。对 Java 后端来说核心价值是把 AI 能力变成 Spring 生态里的一个普通组件能复用 Starter 机制、自动配置、配置中心和监控体系。这也是它这两年火起来的最根本原因Java 开发者的资产是现有业务系统、事务、部署体系Spring AI 让 AI 功能可以低成本地嵌进这套体系里而不是另起炉灶。1.2 7天进度表从 Hello World 到能上线的应用很多人学新东西最大的问题不是不努力而是不知道每天该干什么。我按自己的实战经验把 Spring AI 技能树砍成了 7 天每天只聚焦一个主题。天数学习主题当天产出物Day 1环境搭建、依赖管理、ChatClient 入门跑通第一个对话接口Day 2Prompt Template 与结构化输出让模型按 JSON 格式返回可用的 Java 对象Day 3流式输出与多轮对话做出体验正常的聊天窗口Day 4Function Calling让模型调用你的 Java 服务方法Day 5RAG 与多模态给模型接入私域知识Day 6本地模型与国内云模型实战对接 DeepSeek 或通义千问Day 7综合实战完成一个 AI 客服或文档问答应用这个顺序是经过考量的核心逻辑是“先会调再调得对最后能落地”。很多人一上来就研究向量库、Agent 编排结果连最简单的对话都跑不通信心直接被打没。先把链路打通再逐步加深是最高效的路径。1.3 前置条件Java版本和Spring Boot版本怎么选Spring AI 是基于 Spring Boot 3.x 的所以 JDK 至少是 17我建议直接用 21。Maven 或 Gradle 都行无所谓。如果手上还有 Spring Boot 2.x 的老项目想接入 Spring AI第一步不是写代码而是先把项目升级到 Spring Boot 3.x这一步绕不开。除了 Java 环境还需要一个能调用的模型服务。Day 1 的时候最省事的方式是先在本地装个 Ollama拉一个几 B 的小模型先跑通不需要注册任何云服务也不需要准备 API Key。等后面熟练了再切换到云端模型或者公司内部部署的模型。2. 环境搭建版本选型、依赖配置与第一个对话应用2.1 别在版本上翻车Spring AI 1.0与2.0怎么选Spring AI 的迭代速度在 Java 生态里算相当激进的。截至我写这篇内容的时间点Spring AI 已经进入 2.0 时代1.0 也早已 GA。但网上大量教程还停留在旧版 API照着抄大概率跑不起来。1.0 和 2.0 的核心差异主要在自动配置、多模型路由、包名和部分配置项上。比如某些 starter 的坐标变了某些配置前缀也调整了。建议新项目直接上当前稳定版 2.0老项目如果已经跑在 1.0 上没必要盲目升级先锁定小版本等业务稳定再迁移。另外Spring AI Alibaba 作为国内生态的补充有自己独立的版本线跟 Spring AI 官方版本的对应关系要查官方说明不要只按博客里的坐标抄。这里有一个必须养成的习惯看完任何 Spring AI 教程先去官方文档的 Release Notes 确认一下版本再决定要不要照抄。AI 领域变化太快别人半年前写的文章里面一半内容可能已经过时了。2.2 最小可运行依赖Maven配置与本地模型接入我用 Maven 做演示第一件事是引入 BOM统一管理 Spring AI 相关依赖的版本。如果直接写死版本号很容易出现某个依赖升了、另一个没升导致的诡异问题。dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version2.0.x/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement然后引入模型 starter。这里我建议第一天先用 OpenAI 兼容的接口去连本地 Ollama因为 Spring AI 对 OpenAI 协议的支持最成熟而 Ollama 刚好暴露了 OpenAI 兼容端点两边一拍即合。dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId /dependency对应的 application.yml 配置长这样spring: ai: openai: base-url: http://localhost:11434/v1 api-key: ollama chat: options: model: qwen2.5:7bbase-url 指向 Ollama 的 OpenAI 兼容地址api-key 随便填一个占位符就行因为本地服务不校验。model 名称填你本地已经拉取的模型名。这套配置背后的思路很重要Spring AI 的 OpenAI starter 并不限定只能用 OpenAI 官方服务凡是兼容 OpenAI 接口的模型服务都可以通过改 base-url 接入。后面接本地部署的 DeepSeek、接 vLLM 部署的模型思路完全一致。如果你确实是用 OpenAI 官方服务那把 api-key 配好即可如果访问官方服务的网络条件不理想更推荐本地模型或国内云厂商的兼容接口工程上更省心。2.3 用 ChatClient 写第一个对话接口依赖和配置都就位之后代码其实非常少。先定义一个配置类把 ChatClient 注入容器Configuration public class AppConfig { Bean ChatClient chatClient(ChatClient.Builder builder) { return builder.build(); } }然后写个 Controller 测试RestController public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient chatClient) { this.chatClient chatClient; } GetMapping(/chat) public String chat(RequestParam String message) { return chatClient.prompt() .user(message) .call() .content(); } }启动项目后访问/chat?message你好就能收到模型的回复。这段代码值得细看的地方是调用链prompt()开始构建一次提示词请求user()设置用户消息call()表示同步等待结果content()拿到最终的文本字符串。ChatClient 的设计明显参考了 WebClient 和 RestTemplate 的经验链式调用的语义非常清晰。第一天不用深挖底层先把这个链路跑通建立“Spring AI 也不是那么难”的信心比什么都重要。3. 核心能力拆解提示词、结构化输出与参数调优3.1 Prompt Template把提示词从代码里拿出来第一天的代码里提示词是直接写在 Java 方法里的。这样写 Demo 没问题一旦进入真实项目马上会暴露问题业务方想调整人设、调整回答风格难道每次都要改代码重新发版Spring AI 提供了 PromptTemplate用法跟 MyBatis 的 SQL 模板有点像。先定义一个模板String template 你是一位{role}请用{style}的风格回答用户问题。 用户问题{question} ; PromptTemplate promptTemplate new PromptTemplate(template); Message message promptTemplate.createMessage(Map.of( role, 资深Java架构师, style, 简洁直接不要废话, question, userQuestion ));模板本身可以放到资源文件、数据库或者配置中心运行时按需填充参数。这样一来Prompt 的调整就不需要动 Java 代码运营同学改个配置就行。这个模式对团队协作特别重要我见过太多项目把提示词写死在代码里每次调 Prompt 都要排期发版效率极低。3.2 结构化输出让模型回答变成可用的Java对象聊天示例返回的是字符串但实际业务中我们往往需要模型输出一段结构化数据比如从合同中抽取关键字段、把用户问题分类。大模型本身只输出文本要想拿到 JSON就得在 Prompt 里明确要求格式再用转换器解析成 Java 对象。Spring AI 的 BeanOutputConverter 就是干这个的。假设我要让模型推荐一本书返回书名、作者和页数record Book(String title, String author, int pages) {} BeanOutputConverterBook converter new BeanOutputConverter(Book.class); String json chatClient.prompt() .user(u - u.text(推荐一本Spring AI相关的书籍返回格式{format}) .param(format, converter.getFormat())) .call() .content(); Book book converter.convert(json);注意看converter.getFormat()会把“请以 JSON 格式返回字段包括 title、author、pages”这段格式说明拼进 Prompt这样模型就知道该输出什么结构。拿到 JSON 字符串后converter.convert()再把它解析成 Book 对象。这里有个实战经验模型输出的 JSON 不一定总是合法的。尤其在 temperature 偏高或者模型能力较弱的时候偶尔会多出几句解释文字导致解析失败。生产环境一定要对解析失败做兜底比如捕获异常后重试一次或者要求模型只输出 JSON、不要任何多余内容。3.3 模型参数temperature、maxTokens到底控制什么接入模型之后很多人会忽略参数的重要性。其实模型能不能稳定输出预期效果参数调优占了很大比重。我整理了一张常用参数速查表参数作用经验值temperature控制随机性值越高回答越发散数据抽取/代码生成用 0~0.3文案创作用 0.7~0.9maxTokens限制最大输出 token 数按场景设置防止模型长篇大论烧钱topP核采样控制候选词范围一般配合 temperature二者不要同时大调比如做结构化输出时如果 temperature 设成 1.0模型可能自由发挥输出各种不规范的 JSON把它调到 0.2 左右模型会更倾向于遵循 Prompt 中的格式要求。做聊天机器人时temperature 又需要稍微调高一点不然回答会显得生硬机械化。Spring AI 里可以在 application.yml 中配置默认参数也可以在一次请求中覆盖chatClient.prompt() .user(message) .options(ChatOptions.builder() .temperature(0.3) .maxTokens(500) .build()) .call() .content();建议还是优先在配置文件中设置默认值个别请求再单独覆盖这样比较好管理。4. 场景进阶多轮记忆、流式输出、工具调用与RAG4.1 流式输出聊天体验的底线第一天的示例是同步等待完整结果模型把整段话都生成完才返回。实际对话场景中大模型生成几百字可能需要好几秒如果一直转圈圈用户早就没耐心了。流式输出的做法是把返回结果改成响应式流FluxString stream chatClient.prompt() .user(message) .stream() .content(); stream.subscribe(content - { // 每生成一段内容就推送一次 });在 WebFlux 环境下可以直接把这个 Flux 返回给前端配合 SSE 就能实现打字机效果。如果项目还是 Spring MVC也可以借助 SseEmitter 做类似的功能。经验之谈只要涉及用户实时交互流式输出是必须做的这不是锦上添花而是基本体验。但也要注意流式返回对 Log 和异常处理更麻烦因为错误可能发生在流中间。所以我一般会在生产环境加上超时设置和降级策略防止模型服务异常导致客户端一直挂着。4.2 多轮对话与上下文管理ChatClient 默认是没有记忆的每次调用都是一次全新的对话。要实现多轮对话必须自己把历史消息传给模型。最简单的方式是维护一个消息列表ListMessage history new ArrayList(); history.add(new UserMessage(你好我叫小明)); history.add(new AssistantMessage(你好小明有什么可以帮你)); history.add(new UserMessage(我叫什么名字)); String answer chatClient.prompt() .messages(history) .call() .content();真实项目里肯定不会把 history 放在内存里而是按 sessionId 存到 Redis 或数据库每次请求拉取最近的 N 条消息再组进去。这里有个关键点上下文不是越多越好。模型对上下文长度有限制而且传太多历史消息会显著增加 token 消耗和响应延迟。我在实践中一般只保留最近 10~20 条既保证对话连贯又控制成本。4.3 Function Calling让模型学会调用你的Java方法多轮对话只是把聊天做好真正让 Spring AI 产生业务价值的是 Function Calling也叫 Tool Calling。核心思想模型本身不会查数据库、不会调下单接口但它可以根据用户提问生成工具调用参数由 Spring AI 框架去执行你注册的 Java 方法再把执行结果回填给模型让模型基于结果生成最终回答。先注册一个工具Component public class OrderTool { Tool(name queryOrderStatus, description 根据订单号查询订单当前状态) public String queryOrderStatus(String orderId) { // 调用订单服务返回状态 return orderService.queryStatus(orderId); } }调用时把这个工具挂到 ChatClient 上String answer chatClient.prompt() .user(帮我查一下订单 20250001 的状态) .tools(new OrderTool()) .call() .content();用户说“查订单”模型先判断这需要调用queryOrderStatus方法然后生成参数orderId20250001框架执行方法拿到结果后再让模型组织语言给用户。整个过程模型并没有真的“查到”数据而是它“指挥”了我们的代码去查。这是从聊天机器人走向 Agent 的必经之路。我强烈建议第四天把重点放在这块因为它是 Spring AI 落地最实用的能力。很多所谓的“AI 客服能查订单”底层都是这个机制。4.4 RAG实战给模型接入私域知识模型训练数据有截止日期也不可能知道你公司内部的文档内容。想让模型回答“员工手册里关于年假的规定是什么”只有两条路要么微调模型要么用 RAG。RAG 的全称是检索增强生成。流程可以拆成四步先把文档切块再用 Embedding 模型把每一块转成向量存入向量数据库用户提问时先向量化用户问题在数据库里检索最相似的几个片段把片段拼进 Prompt最后让模型基于这些片段回答。Spring AI 对 RAG 做了大量封装不需要从头写向量化逻辑。一个简化版的代码大概长这样Bean VectorStore vectorStore(EmbeddingModel embeddingModel) { return new SimpleVectorStore(embeddingModel); } // 文档入库 DocumentReader reader new PagePdfDocumentReader(classpath:docs/employee-manual.pdf); ListDocument docs reader.get(); vectorStore.add(docs); // 查询时自动检索相关内容并拼入Prompt String answer chatClient.prompt() .advisors(QuestionAnswerAdvisor.builder(vectorStore).build()) .user(年假超过15天需要什么审批流程) .call() .content();这里QuestionAnswerAdvisor的作用就是自动完成“检索相关文档片段 拼进 Prompt 让模型只基于片段回答”。RAG 效果好坏很大程度不取决于模型而是取决于文档切块策略。我踩过不少坑按固定字符数切块经常把语义完整的段落截断后来改成按标题、章节切相关性和回答准确率提升明显。另外选一个好的 Embedding 模型非常关键同一批文档用不同 Embedding 模型检索效果可能有天壤之别。5. 落地实战Spring AI Alibaba、本地DeepSeek与生产配置5.1 对接本地部署的模型以Ollama跑DeepSeek为例前面提到过用 Ollama 拉本地小模型。具体到 DeepSeek操作也不复杂。先把模型拉下来ollama pull deepseek-r1:7b ollama run deepseek-r1:7b然后在 Spring AI 配置里把 model 改成对应名称spring: ai: openai: base-url: http://localhost:11434/v1 api-key: ollama chat: options: model: deepseek-r1:7b这样一套下来Spring AI 就连上了本地运行的 DeepSeek请求完全走内网数据不用出服务器用在内部知识库问答这类场景非常合适。本地模型的优势是隐私和数据可控但代价是模型能力有限。7B 参数的模型和云端几百 B 的模型在复杂推理、代码生成上差距仍然明显。我自己的实践是内部提数、工单摘要这类任务本地 7B 模型完全足够涉及复杂逻辑和高质量生成还是要切换到云端大模型。生产环境建议做一个模型网关按业务场景动态路由而不是全局只用一种模型。5.2 国内云模型接入通义千问与Spring AI Alibaba对接国内云模型Spring AI 提供了对应 starter比如 DashScope 的适配。使用前先去阿里云百炼或 DashScope 控制台申请 API Key。Maven 依赖大致长这样具体版本号以官方发布为准dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter/artifactId version具体版本见官方说明/version /dependency配置上通过环境变量注入 API Key 更安全spring: ai: dashscope: api-key: ${DASHSCOPE_API_KEY} chat: options: model: qwen-plusSpring AI Alibaba 这个项目做的事情是把 Spring AI 的能力跟国内模型、国内云生态更好地衔接起来。它提供了不少示例代码、管理后台、可观测性能力。比如官方仓库里有 Spring AI Alibaba Admin 这样的运维后台用于查看模型调用、Token 消耗等指标可以通过 Docker 快速部署。由于版本迭代很快具体镜像名和启动参数建议以官方 README 为准不要直接照搬过时的教程。这里我多说一句Spring AI 的接入层统一之后多模型切换非常方便。同一个业务代码今天接通义千问明天接本地 DeepSeek很多时候只需要改配置和依赖逻辑代码不用动。这也是我推荐团队用 Spring AI 的核心理由。5.3 生产环境必须注意的几件事把 Spring AI 应用推到生产有几件事别等线上出问题才想起来。第一API Key 绝对不要硬编码在代码里。用环境变量、配置中心或者 Kubernetes Secret 管理一旦泄露就要立即轮换。第二模型调用要设置合理的超时和重试。模型服务不是数据库响应时间波动很大超时时间设得太短容易误杀健康请求设得太长又影响用户体验。我一般把连接超时设为 3 秒读取超时设 30 秒到 60 秒重试次数不超过 2 次而且要对非幂等场景谨慎重试。第三成本控制。大模型是按 token 计费的生产环境必须关注用量。对用户输入做长度限制设置合理的 maxTokens同一个请求不要反复把大段上下文发给模型。建议在日志里记录每次调用的 usage配合 Spring AI Alibaba Admin 这类工具做每日配额和告警。第四隐私和数据安全。不要因为图方便把用户敏感信息直接扔给模型厂商。涉及隐私的数据优先走本地模型或私有化部署方案。这一步在架构设计阶段就要想清楚后期再补会很痛苦。6. 一周踩坑实录常见问题与排查思路6.1 我遇到的几个高频报错学习过程中报错是常态我把这周最容易碰到的问题整理成了一张表报错信息可能原因排查思路401 UnauthorizedAPI Key 错误或未配置检查 spring.ai.*.api-key确认环境变量是否生效ConnectException / timeout网络不通或模型服务没启动检查 base-url本地模型先确认 Ollama 进程是否在跑No ChatModel bean found没有引入对应模型的 starter检查依赖里是否引入 starter配置类是否被扫描JSON parse error模型返回内容不合法降低 temperature启用 BeanOutputConverter增加重试Port already in use本地服务端口冲突换端口或杀掉占用进程ClassNotFoundExceptionSpring Boot 与 Spring AI 版本不兼容统一用依赖 BOM 管理严格按官方版本对应关系模型回答明显偏离主题Prompt 缺少约束或模型能力不足优化 Prompt 或切换更大的模型这些问题的共同特点看起来像是代码问题实际上大多出在版本、配置、模型服务状态上。排查时先确认“模型服务本身能不能通”再排查“Spring AI 配置对不对”最后才是“代码有没有写错”。6.2 效果不行的调优思路代码跑通只是第一步遇到“模型回答不理想”才是日常。我总结了几个有效的调优方向。第一先怀疑 Prompt再怀疑代码。很多人一遇到效果不好就去调代码其实更应该先审视提示词有没有给出清晰的人设、范围和示例。在 Prompt 里加一个典型例子效果往往立竿见影。第二评估动作要固化。我在团队里定了条规矩任何 Prompt 改动都要拿同一组测试问题重新跑一遍。否则很容易出现“改好了问题 A搞坏了问题 B”的情况。准备 20 道覆盖常见场景的测试题每次调整后全量回归这是最笨也最有效的方法。第三结构化输出失败时不要指望模型自觉。除了降低 temperature还可以在后端加一层校验和修复逻辑。比如要求模型先输出 JSON 再解析解析失败就重试一次重试时明确告诉模型“上次格式错了请只输出 JSON”。第四区分是效果问题还是模型能力问题。本地 7B 模型做复杂推理表现不好这是模型能力边界不是代码问题。该换模型就换模型不要在一个小模型上死磕 Prompt。6.3 给新人的实操建议最后分享几条比较个人的经验都是踩坑踩出来的。如果你是想把 Spring AI 引入现有项目建议先找一个边缘场景试水比如工单摘要、智能问答、客服辅助不要一上来就搞全自动 Agent。很多团队失败不是因为技术不行而是第一步切入的场景太复杂流程长、依赖多出了问题根本定位不到环节。先用简单场景跑通整个链路建立信心和评估标准再扩大应用边界。另外我习惯把所有模型调用都包在一个 Service 层里不让 Controller 直接面对 ChatClient。这样做的直接好处是以后换模型、加日志、加限流、加缓存都只需要改这一层不会把 AI 相关代码散落到各个业务模块里。这个习惯帮我省了大量麻烦。关于源码Spring AI 官方仓库的 example 目录非常值得翻里面有针对每个功能的可运行示例。我自己的经验是与其在网上找碎片化的教程不如直接读官方示例代码再结合文档理解设计思路。Spring AI 迭代快网上文章很多都过时了但官方示例会持续维护读它不会走偏。7 天点亮技能树并不代表 7 天精通。Spring AI 自己也在快速进化今天的最佳实践可能到下个版本就成了历史。但只要把核心概念——ChatClient、Prompt、结构化输出、Function Calling、RAG 这条主线吃透后面版本再变你也能快速跟上来。Java 工程师做 AI 应用不需要去补炼丹那套东西把模型能力变成产品能力才是我们最该发挥价值的战场。