☰
Spring AI 实战:用 Spring Boot 快速接入大模型对话能力
2026/10/19 3:15:54 网站建设 项目流程

如果你和我一样是做 Java 后端的人,这两年多少有点焦虑。AI 的大潮铺天盖地,但圈子里聊得多的都是 Python、Node.js,Java 这边很长一段时间都处于“能用,但很费劲”的状态。想接一个大模型对话能力,得自己封装 HTTP 调用、处理 SSE 流式返回、维护多轮上下文、写一堆 JSON 解析代码,项目一复杂还容易散落各处,维护起来特别痛苦。直到 Spring AI 出现,我发现情况终于开始变了——它直接对标 Spring 家族的风格,把大模型接入这件事抽象成了一组统一 API,再配合 Spring Boot 的自动装配,第一次跑通一个可对话的接口,我只花了不到 20 分钟。

这篇文章就是我基于 Spring Boot + Spring AI 做的一次完整快速体验记录。我不打算给你罗列一堆官方文档翻译,而是把从零搭项目、写接口、调参数、踩坑排查的整个过程讲讲清楚,包括每一步为什么要这么做,以及代码背后到底发生了什么。适合两类人看:一类是想给现有 Spring Boot 项目加 AI 能力的后端开发,另一类是刚接触 Java 生态、想知道 Java 世界怎么接大模型的初学者。看完你应该能自己动手搭出一个能聊天的 AI 服务,并且知道怎么让它从“能聊”变成“能用”。

1. Spring AI 到底解决什么问题

1.1 Java 接入大模型的三个尴尬

在 Spring AI 之前,Java 开发者接大模型基本靠三样东西:HttpClient、JSON 库和一颗强大的心。缺点非常明显。

第一是接口协议碎片化。市面上主流的大模型服务商虽然都号称兼容 OpenAI 协议,但真正用起来,鉴权方式、请求字段、流式返回格式、错误提示多多少少有差异。今天接这家,明天换那家,调用代码得跟着改一遍,没有一个统一抽象。

第二是样板代码太多。一次对话请求,要构建请求头、设置 JSON body、序列化参数、处理响应、解析错误、管理超时重试。这些逻辑放到业务代码里非常碍眼,而且每个接口都要写一遍。

第三是生态没有一个“官方答案”。社区里也有各种封装,但大多围绕某一两家厂商或者某几个模型,生命周期短,文档也参差不齐。作为后端团队,很难把它当基础设施往上垒。

1.2 Spring AI 的定位:一套“方言”,接入多家大模型

Spring AI 聪明的地方在于,它不解决“模型怎么训练”的问题,那是模型厂商的事;它解决的是“应用怎么连模型”的问题。它本身不是一个 AI 引擎,更像是 Spring 生态里的一个集成层,定义了统一的 ChatModel、ChatClient、Prompt、Message 等概念,然后让各个模型厂商用自己的适配器来接这套规范。

如果你熟悉 Spring Data JPA 的思路,上手就很快——JPA 抽象了数据库方言,你写 JPQL,底层是 MySQL 还是 PostgreSQL 由方言实现决定。Spring AI 也是类似的逻辑:你的业务代码面向统一的客户端接口编程,底层接入哪个模型,由配置决定。换模型服务商,不用改业务代码,只改配置文件和依赖,这就是它作为“框架”的价值。

1.3 快速体验的核心思路:先跑通,再深化

我第一次拿到 Spring AI 的时候,并没有直接去研究它内部有多少组件,而是先搭了个最小工程,跑通一个“发消息-拿回答”的接口。这个策略看起来很朴素,但真的很管用。因为只有链路通了,你才知道哪些报错是配置问题,哪些是代码问题,哪些是网络问题。之后再逐步加入 System Prompt、结构化输出、多轮会话,每加一个能力,都能明确知道它在链路中的位置。

所以这篇文章的实操部分也按这个顺序来:先搭建可运行工程,再写最小对话接口,最后加进阶能力。

2. 环境准备:搭出一个能跑的最小工程

2.1 版本选型与依赖引入

Spring AI 目前的版本节奏比较快,而且对 Spring Boot 的版本有明确要求。我自己用的时候,Spring Boot 3.2.x 配 Spring AI 的某个里程碑版本是能跑通的,但如果你想少踩坑,最好看一眼你选用的 Spring AI 版本对应的 Spring Boot 版本要求,别直接用最新的 Spring Boot 去配一个老版本的 Spring AI,很容易出现自动装配失效。

工程方面,建议直接用 Spring Initializr 生成一个基础项目,只需要勾选 Web 依赖,其它后面手动加。Spring AI 相关的依赖,核心坐标大致长这样:

<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-openai</artifactId> <version>1.0.0-M6</version> </dependency>

注意这里有个大坑:Spring AI 的很多版本是里程碑版本(M 后缀),默认的中央仓库可能没有,需要在 pom.xml 里额外配置 Spring 的里程碑仓库:

<repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>

我最初没加这个仓库,Maven 一直报依赖找不到,还以为是网络问题,后来才发现是仓库没有配。如果你是第一次接触,先把这一步搞定,比后面调代码省心十倍。

2.2 application.yml 与 API Key 管理

依赖引入之后,真正决定能不能跑通的是配置文件。一个最小配置如下:

spring: ai: model: provider: your-provider openai: base-url: https://api.your-provider.example.com api-key: ${AI_API_KEY} chat: options: model: your-model-name temperature: 0.7

这里的 base-url 指向你选择的大模型服务商兼容接口地址,api-key 从环境变量读取,千万不要把密钥硬编码进 YAML。model 换成你服务商实际提供的模型名称。

为什么 API Key 一定要用环境变量?因为项目代码迟早要提交到仓库,哪怕现在是私有仓库,也有泄露风险。我见过有人直接把密钥写在 application.yml 里然后提交到 Git 仓库,结果过了几个月发现密钥被刷爆了。从第一天就养成从环境变量读取的习惯,成本为零,收益长远。

2.3 两个方法验证工程健康

依赖和配置都就绪之后,不用急着写业务代码,先做两个验证。

第一个验证:项目能不能正常启动。直接运行启动类,如果控制台没有任何异常,说明自动装配没有大问题。第二个验证:Spring 容器里有没有生成 chatClient 这个 Bean。可以临时在一个启动类里打印一下:

@SpringBootApplication public class DemoApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args); System.out.println(context.containsBean("chatClient")); } }

如果输出 true,说明 ChatClient 已经准备好了,接下来就可以直接注入使用。如果这里就报错,优先回头检查依赖版本、仓库配置和 YAML 里的模型名是否正确。

3. 第一个对话接口:从 Controller 到大模型

3.1 写一个最简的 ChatService

跑通最小验证之后,接下来就是让应用具备真正的对话能力。Spring AI 的核心入口是 ChatClient,它就像 Spring 里的 RestTemplate 一样,封装了整个调用链路。我习惯给 AI 调用单独做一个 Service,不让 Controller 直接操作 ChatClient,这样以后换模型、加日志、做缓存都有一个统一的地方。

@Service public class ChatService { private final ChatClient chatClient; public ChatService(ChatClient.Builder builder) { this.chatClient = builder.build(); } public String chat(String message) { return chatClient.prompt(message) .call() .content(); } }

这段代码一共做了三件事:通过 Builder 构建一个 ChatClient,接收用户消息并构建 Prompt,发起调用并拿回模型生成的文本。注意这里用的是构造器注入,推荐的依赖注入方式,字段注入能不用就不用。

3.2 暴露 HTTP 接口并完成自测

Service 写好后,Controller 就非常简单了:

@RestController public class ChatController { private final ChatService chatService; public ChatController(ChatService chatService) { this.chatService = chatService; } @PostMapping("/chat") public Map<String, String> chat(@RequestBody ChatRequest request) { String answer = chatService.chat(request.message()); return Map.of("answer", answer); } public record ChatRequest(String message) {} }

启动项目之后,用 curl 测一下:

curl -X POST http://localhost:8080/chat \ -H "Content-Type: application/json" \ -d '{"message":"用一句话介绍你自己"}'

正常情况下,返回的 JSON 里就会带一个 answer 字段,内容就是模型生成的回答。我记得自己第一次看到这个返回的时候,第一反应是“这么简单吗?”——确实就这么简单,Spring AI 把请求构建、鉴权、响应解析全部消化在 ChatClient 内部了。

3.3 从代码反推 ChatClient 的工作原理

看完了能跑的代码,我建议你花五分钟想一想这背后发生了什么,因为只有理解了链路,后面遇到问题才知道去查哪一段。

当调用chatClient.prompt(message).call().content()时,内部大致经历了这么几步:先把普通字符串包装成 Prompt 对象,再交给 ChatModel 实现类,也就是某个模型服务商的适配器,适配器负责把 Prompt 转换成厂商要求的 HTTP 请求结构,并附加 API Key、模型名、temperature 这些参数,然后发起远程调用,拿到厂商返回的原始响应,最后解析出文本内容返回给业务代码。

也就是说,你写的代码是稳定的,换模型厂商只是换底层适配器而已。这也是为什么我推荐你从一开始就基于 ChatClient 写业务,而不是自己去调用底层厂商 SDK——两者都能跑通,但前者给了你未来换模型不伤筋动骨的底气。

4. 从“能聊”到“能用”:模板与结构化输出

4.1 PromptTemplate 让提示词不再写死在代码里

跑通对话接口只是第一步,真实业务里几乎没有裸聊天的需求。很多时候,你希望模型扮演某个角色,按照既定规则处理输入。比如做一个“代码审查助手”,希望用户提交代码片段,模型按固定维度输出审查意见。

这时最忌讳的做法是把一大段提示词拼在业务代码里。正确做法是用 Spring AI 的 PromptTemplate,把提示词主体放到模板中,动态内容用占位符替换:

@Service public class CodeReviewService { private final ChatClient chatClient; public CodeReviewService(ChatClient.Builder builder) { this.chatClient = builder.build(); } public String reviewCode(String code) { String promptTemplate = """ 你是一名经验丰富的 Java 架构师,请对下面的代码做审查。 重点检查:潜在 bug、性能问题、可读性、安全隐患。 请给出具体问题和修改建议。 需要审查的代码: %s """.formatted(code); return chatClient.prompt(promptTemplate) .call() .content(); } }

用系统提示词的方式会更符合 Spring AI 的习惯,把“角色设定”和“用户输入”分离:系统提示词定义角色和行为规则,用户输入放具体内容。这样模型会更稳定,不会因为用户的一句话跑偏。Spring AI 里面可以直接在 prompt 上指定系统文本:

String answer = chatClient.prompt() .system("你是一名严谨的 Java 架构师,输出时使用中文,逐条列出问题。") .user(code) .call() .content();

这两段代码跑起来效果差别不大,但关注点分离之后,维护提示词会轻松很多。随着项目变复杂,你肯定希望提示词能独立管理,甚至将来用提示词文件替代硬编码字符串。

4.2 让模型按固定结构返回 JSON

聊天返回一段文本没问题,但如果模型输出要直接对接业务系统,比如从用户反馈中抽取“情绪、分类、紧急程度”三个字段,你再拿一段自由文本去做 Promise 解析就太累了。更现实的需求是:让模型直接返回一个 JSON 对象,Java 这边反序列化成 Bean。

Spring AI 的结构化输出能力正好干这个。它能在提示词中附加格式约束,让模型按你的 Java 类定义输出 JSON。假设你定义了一个反馈分析结果类:

public record FeedbackAnalysis( String sentiment, String category, int urgency ) {}

然后使用 BeanOutputConverter 生成格式说明并转换结果:

BeanOutputConverter<FeedbackAnalysis> converter = new BeanOutputConverter<>(FeedbackAnalysis.class); String formatInstruction = converter.getFormat(); String prompt = """ 分析下面的用户反馈,返回结果严格按 JSON 格式,字段包括 sentiment、category、urgency。 %s 用户反馈: 服务态度很好,但响应太慢了,希望能改进。 """.formatted(formatInstruction); String content = chatClient.prompt(prompt).call().content(); FeedbackAnalysis analysis = converter.convert(content);

这样拿到的 content 就不再是自由文本,而是能被converter.convert转成 Java 对象的稳定结构。我在实际体验中发现,模型在结构化输出模式下“走神”的概率明显降低,偶尔还是会漏字段,所以代码里对 convert 要做异常捕获,别直接往下游传。

这种方式特别适合做信息抽取、内容分类、客服工单自动打标一类的场景。一旦模型输出能跟 Java 类型直接绑定,AI 就不再是一个“聊天玩具”,而是可以真正参与业务逻辑处理。

4.3 多轮对话的会话状态管理思路

聊到上下文,这是新手最容易忽略的地方:默认情况下,每次调用 ChatClient 都是无状态的,模型不会记得你上一轮说了什么。要实现“多轮对话”,必须把之前的历史消息一起发给模型。

Spring AI 提供了会话内存机制,常见做法是使用消息窗口容器,按会话 ID 保存聊天历史。大致思路是:

ChatMemory chatMemory = new MessageWindowChatMemory(10); ChatClient chatClient = ChatClient.builder(model) .defaultChatMemory(chatMemory) .build();

然后每次请求传入一个 conversationId,框架会自动从内存里取出该会话的历史记录,拼进下一次请求。窗口大小需要控制,因为大模型的上下文窗口有限,聊天记录太长既费 token 又可能把核心指令“挤”出注意力范围。10 到 20 条通常是比较稳妥的值。

这里要提醒一句:内存方案只适合单机、低并发、测试体验场景。如果要做生产级的多轮对话,历史消息得落到 Redis 这类外部存储里,并且在传入模型前做长度裁剪或摘要压缩。Spring AI 本身也在持续完善存储抽象,但基础原理不会变——上下文本质上就是把历史消息递给模型。

5. 参数调优与控制:让模型“听话”

5.1 三个最常用的生成参数

聊完功能,再聊聊调参。Spring AI 暴露的生成参数里,有三个最常用,而且直接影响输出质量。

第一个是 temperature,控制随机性。取值越低,输出越确定、越保守,适合代码生成、事实抽取;取值越高,回答越发散、越有创造性,适合文案生成、头脑风暴。我在做代码审查助手时,temperature 一般设 0.2 左右,几乎不让模型自由发挥。

第二个是 max-tokens,限制模型生成的最大 token 数。这个参数除了控制成本,还直接影响接口响应时间。如果模型需要输出很长的内容,结果被截断,可以先确认是不是这个值设置太小。

第三个是 top-p,也是采样控制。它和 temperature 作用类似,但机制不同,通常两者不要同时猛调,调一个就够了。多数场景下,固定 temperature,把 top-p 保持默认,是比较稳妥的做法。

下面是我自己常用的参考表,仅供参考,因为不同模型的实际表现有差异:

参数通俗理解适用场景建议值
temperature越大越“敢说”文案、创意0.7-0.9
temperature越小越“严谨”代码、抽取0.0-0.3
max-tokens最多说多长长文、短答按需设置
top-p候选词范围一般不用调默认 1

参数不是越多越好,先调 temperature 和 max-tokens,基本就能覆盖大部分需求。参数都写在配置里,也可以按接口动态覆盖,Spring AI 允许在 prompt 级别追加选项,这个灵活性特别实用。

5.2 超时与流式响应的控制

真实体验中还有一个容易被忽视的点:大模型接口的响应时间不稳定,有时 2 秒,有时 20 秒。如果 HTTP 客户端超时设置太短,业务侧会频繁报错。

Spring AI 底层用的是 RestClient,可以参考它的超时配置方式,在 application.yml 里调整 connect 和 read 超时时间。我一般把连接超时设为 10 秒,读取超时设为 60 秒,避免因为网络抖动导致正常请求被中断。

如果你的场景是“边生成边展示”,比如聊天机器人打字机效果,那么要研究流式响应。Spring AI 提供了流式 API,大致是chatClient.prompt(...).stream(),返回的是一个 Flux,前端可以配合 SSE 实时接收逐字输出。这个我没有在快速体验里做,但如果你要接实时对话,方向是明确了的:后端用 WebFlux 或者控制器返回 SseEmitter,前端接 EventSource 或 fetch 流式读取。先跑通同步接口,再升级流式,是比较合适的节奏。

6. 我踩过的坑和排查思路实录

6.1 依赖版本冲突

这是我遇到的第一个坑。Spring 生态对版本要求向来严格,Spring AI 更是如此。当时我把 Spring Boot 升到了 3.4,结果 Spring AI 的里程碑版本还停在对应 3.3 的适配,启动时直接提示某个自动配置类不存在。

排查思路比较简单:先看启动日志的 Caused by 部分,找最底下的那个异常,通常就是版本不兼容。解法也比较粗暴,回退 Spring Boot 版本,或者升级 Spring AI 到对应版本。如果你也遇到奇怪启动报错,优先怀疑版本组合,而不是业务代码。

6.2 请求超时和连接异常

第二个坑是超时。第一次调用大模型接口,默认超时配置只有几秒,而我选的模型响应偏慢,结果业务侧频繁抛超时异常。这个问题很容易被误判成网络问题或者 API Key 问题,实际上就是超时阈值太短。

排查方法很简单:先用 curl 直接请求一次模型服务商的接口,如果 curl 能拿到结果,但应用里超时,那基本就是客户端超时配置的问题。把读取超时调到 60 秒左右再试,大概率解决。

6.3 API Key 的安全隐患

第三个坑是关于配置的。第一次我图省事,把 API Key 直接写在 application.yml 里,项目启动后朋友帮我做代码 review,第一眼就发现了这个问题。后来我把密钥迁移到了环境变量,并且检查了 Git 历史,确认这个 Key 没有真的被提交到远程仓库。

这里我要特别提醒:如果你的 Key 已经提交到任何远程仓库,不要只是删掉文件,视为已泄露,去服务商控制台把旧 Key 作废,换新 Key。密钥泄露的代价有时候不是钱的问题,而是整个项目的安全信任问题。

6.4 模型输出不稳定

第四个坑来自模型本身:结构化输出偶尔不老实。我遇到过明明给了 JSON 格式约束,模型还是输出了一行解释文字,导致反序列化失败。这不是代码 bug,而是大模型的概率性行为。

我的应对方案有三层:第一层,在提示词里反复强调“只输出 JSON,不要解释”;第二层,解析时加 try-catch,失败则记录原始输出,方便人工介入;第三层,设计上不要把结构化输出当成绝对可靠的基础设施,重要链路要做人工兜底或二次校验。模型输出再稳定也是概率事件,生产系统必须对此有预期。

7. 最后补充一点个人经验

这套 Spring Boot + Spring AI 的快速体验做下来,我最大的感受是:Java 生态在 AI 集成这件事上,终于有了一个可以认真对待的框架。它未必像 Python 生态那样自由,但它的抽象方式和 Spring 一贯的风格高度一致,对于已经用 Spring Boot 构建了大量业务系统的团队来说,学习成本低,集成路径顺滑。

我给新手的建议是:先从最小工程跑通,不要一开始就研究高级特性。ChatClient 的同步对话接口跑通之后,再加模板、结构化输出,最后才是流式和会话管理,每一步都验证一次。中间那些坑,版本冲突、密钥泄露、超时设置、输出解析失败,都是可以提前预防的,希望这篇文章能让你少走几轮弯路。

如果你照这个过程跑通了,接下来值得探索的方向是:把 AI 能力嵌入到一个真实业务场景里,例如工单分类、文档问答、代码审查工具,这些场景才能真正体现 Spring AI 的价值。毕竟框架只是一个开始,让它解决实际问题,才是我们做后端的人最该关心的事情。

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

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

立即咨询