Spring AI 接入与简单使用:从环境搭建到多轮对话(JDK 17 + Spring Boot 3.5)

前言

Spring AI 是 Spring 生态中用于对接大语言模型(LLM)的抽象层,可以统一调用 OpenAI、Azure OpenAI、以及各类 OpenAI 兼容 API(如 DeepSeek、国内大模型等)。通过少量配置和几行代码,就能实现同步调用流式输出,以及带上下文记忆的多轮对话,非常适合在现有 Spring Boot 项目里快速接入 AI 能力。本文基于 JDK 17Spring Boot 3.5Spring AI 1.1 记录从零接入到简单使用的完整过程,并总结对接时的注意项。

特别说明:本文除本段外,全部由AI生成。项目地址:https://gitee.com/husolar/fast-chat.git


一、环境准备

1.1 JDK 版本

  • 必须使用 JDK 17 及以上。本项目在 pom.xml 中指定 <java.version>17</java.version>
  • 若使用 JDK 8 等低版本,会出现类似「class file version 61.0, 应改为 52.0」的编译错误,因为 Spring Boot 3.x 与 Spring AI 1.x 均基于 Java 17 编译。

在终端验证:

java -version 

应看到 17 或更高版本。

1.2 项目基础

  • 使用 Spring Boot 3.5.11 作为 parent,Maven 项目即可。
  • 仅需 spring-boot-starter-web 即可同时支持同步接口和返回 Flux<String> 的流式接口,无需单独引入 spring-boot-starter-webflux(Spring Boot 已带 Reactor)。

二、依赖引入

在 pom.xml 中做三件事:指定 Spring AI 版本、引入 BOM、添加 Starter。

2.1 属性与 BOM(必选)

<properties> <java.version>17</java.version> <spring-ai.version>1.1.2</spring-ai.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>${spring-ai.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> 

注意:Spring AI 各模块版本由 BOM 统一管理,不要在子依赖里再写版本号,避免与 Spring Boot 依赖冲突。

2.2 引入 OpenAI 兼容 API 的 Starter

对接 OpenAI 或任意 OpenAI 兼容 的接口(如 DeepSeek)时,使用:

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

若需要多轮对话上下文记忆,再增加:

<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-chat-memory</artifactId> </dependency> 

三、配置文件

在 application.yaml(或 application.properties)中配置 API Key、Base URL 和模型名。

3.1 示例(以 DeepSeek 为例)

spring: ai: openai: api-key: ${OPENAI_API_KEY} base-url: https://api.deepseek.com chat: options: model: deepseek-chat 
  • api-key:建议用环境变量 OPENAI_API_KEY,不要写死在配置或代码里。
  • base-url:对接国内或第三方时改为对应地址(如 DeepSeek、OpenAI 官方等)。
  • model:对应服务商提供的模型名。

3.2 可选:对话记忆窗口

若使用了 spring-ai-starter-model-chat-memory,可配置单会话保留的最近消息条数(默认 20):

app: chat: memory: max-messages: 20 

四、简单使用:同步与流式

Spring AI 自动配置 ChatClient.Builder,注入后 build() 得到 ChatClient,即可发起调用。

4.1 注入并构建 ChatClient

@RestController public class HelloController { private final ChatClient chatClient; public HelloController(ChatClient.Builder builder) { this.chatClient = builder.build(); } } 

4.2 同步调用

一行即可拿到完整回复文本:

@GetMapping("/hello") public String hello(@RequestParam(value = "input", defaultValue = "讲一个笑话") String input) { return chatClient.prompt(input).call().content(); } 

4.3 流式调用

返回 Flux<String>,接口需声明 SSE 类型,便于浏览器按流式展示:

@GetMapping(value = "/hello/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<String> helloStream(@RequestParam(value = "input", defaultValue = "讲一个笑话") String input) { return chatClient.prompt(input).stream().content(); } 

4.4 使用 Prompt 模板(可选)

通过 PromptTemplate 占位符注入角色、用户输入等:

PromptTemplate template = new PromptTemplate("你是一个{role},请根据以下内容生成回复:{input}"); Map<String, Object> params = Map.of("role", "幽默的助手", "input", input); return chatClient.prompt(template.render(params)).stream().content(); 

五、进阶:带上下文记忆的多轮对话

大模型本身是无状态的,不会记住上一轮对话。若要实现「你说了上一句,模型能结合历史回复」,需要在服务端维护会话历史,并在每次请求时把历史一并发给模型。Spring AI 提供了 ChatMemory 与 MessageChatMemoryAdvisor,按 conversationId 隔离会话即可。

5.1 引入依赖

上文已包含:

<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-chat-memory</artifactId> </dependency> 

5.2 配置 ChatMemory 与带记忆的 ChatClient

自定义 ChatMemory(可配置窗口大小)和另一个 ChatClient,专门用于多轮对话:

@Configuration public class ChatMemoryConfig { @Bean public ChatMemory chatMemory(ChatMemoryRepository repository, @Value("${app.chat.memory.max-messages:20}") int maxMessages) { return MessageWindowChatMemory.builder() .chatMemoryRepository(repository) .maxMessages(maxMessages) .build(); } @Bean public ChatClient chatClientWithMemory(ChatClient.Builder builder, ChatMemory chatMemory) { return builder .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build()) .build(); } } 
  • MessageWindowChatMemory:只保留最近 N 条消息,避免上下文过长。
  • MessageChatMemoryAdvisor:在每次请求前注入历史消息,请求后再把本轮 user/assistant 写入 memory。

5.3 提供带 conversationId 的接口

每次请求带上 conversationId,同一会话使用同一 ID,即可共享历史:

@RestController public class ChatController { private final ChatClient chatClientWithMemory; private final ChatMemory chatMemory; public ChatController(ChatClient chatClientWithMemory, ChatMemory chatMemory) { this.chatClientWithMemory = chatClientWithMemory; this.chatMemory = chatMemory; } @GetMapping("/chat") public String chat(@RequestParam("conversationId") String conversationId, @RequestParam("input") String input) { return chatClientWithMemory.prompt() .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId)) .user(input) .call() .content(); } @GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<String> chatStream(@RequestParam("conversationId") String conversationId, @RequestParam("input") String input) { return chatClientWithMemory.prompt() .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId)) .user(input) .stream() .content(); } @DeleteMapping("/chat/{conversationId}") public void clearHistory(@PathVariable("conversationId") String conversationId) { chatMemory.clear(conversationId); } } 
  • 同步GET /chat?conversationId=xxx&input=yyy
  • 流式GET /chat/stream?conversationId=xxx&input=yyy
  • 清空该会话历史DELETE /chat/{conversationId}

前端或客户端需自己生成并维护 conversationId(如 UUID),同一会话内保持不变即可。


六、对接过程中的注意项

  1. JDK 版本
    必须 JDK 17+,否则会报 class file version 相关错误。
  2. BOM 与版本
    Spring AI 依赖统一由 spring-ai-bom 管理,子依赖不要再写 <version>,以免与 Spring Boot 冲突。
  3. API Key 与 base-url
    • API Key 建议用环境变量(如 OPENAI_API_KEY)注入,不要写死在配置或代码中。
    • 对接国内或第三方时,只需修改 base-url(如 DeepSeek:https://api.deepseek.com),模型名改为对方提供的名称即可。
  4. 流式与 SSE
    流式接口返回的是 SSE(Server-Sent Events),前端需按 data: 行解析并拼接内容再展示;若直接把原始流当纯文本显示,会看到满屏的 data: xxx 而不是连贯句子。
  5. Reactor 依赖
    仅使用 spring-boot-starter-web 即可支持返回 Flux<String> 的流式接口,无需额外引入 webflux;Spring Boot 已传递 Reactor 依赖。
  6. 多 Bean 注入
    若同时存在「无记忆」和「带记忆」的 ChatClient,注入时需用 @Qualifier("chatClientWithMemory") 或按 bean 名称注入,避免注入错误实例。

七、总结

本文以 JDK 17Spring Boot 3.5.11Spring AI 1.1.2 为基础,介绍了从依赖引入、配置 API Key 与 base-url,到同步调用流式调用以及带上下文记忆的多轮对话的完整接入过程。核心步骤可以概括为:在 pom.xml 中通过 BOM 引入 spring-ai-starter-model-openai(及可选的 chat-memory Starter),在配置文件中设置 api-keybase-url 和 model,在代码中注入 ChatClient.Builder 或自定义的带记忆 ChatClient,即可用 prompt(...).call().content() 或 prompt(...).stream().content() 完成调用。多轮对话时,使用 MessageChatMemoryAdvisor 配合 ChatMemory,并按 conversationId 隔离会话即可。

对接时请务必注意 JDK 17+BOM 统一管理版本API Key 使用环境变量以及流式接口在前端的 SSE 解析,可少踩很多坑。若你使用的是其他 OpenAI 兼容服务,只需更换 base-url 和 model 名称,代码无需改动。

Read more

动态规划 路径类 DP 入门:3 道经典例题(最小路径和 + 迷雾森林 + 过河卒)全解析

动态规划 路径类 DP 入门:3 道经典例题(最小路径和 + 迷雾森林 + 过河卒)全解析

文章目录 * 矩阵的最小路径和 * 迷雾森林 * 过河卒 路径类 dp 是线性 dp 的⼀种,它是在⼀个 n × m 的矩阵中设置⼀个⾏⾛规则,研究从起点⾛到终点的 ⽅案数、最⼩路径和或者最⼤路径和等等的问题。 ⼊⻔阶段的《数字三⻆形》其实就是路径类 dp。 矩阵的最小路径和 题目描述 题目解析 1、状态表示 dp[i][j]表示从[1 1]格子走到[i j]格子时,所有方案下的最小路径和。 2、状态转移方程 我们还是以最后一步来推导状态转移方程,走到最后一个格子dp[n][m]

By Ne0inhk
优选算法——双指针专题 3.快乐数 4.盛水最多的容器

优选算法——双指针专题 3.快乐数 4.盛水最多的容器

优选算法——双指针专题 3.快乐数 4.盛水最多的容器 一.快乐数 1.题目解析 [题目传送门](202. 快乐数 - 力扣(LeetCode)) 2.原理解析 第一种情况:数最后变成1 第二种情况:无限循环但不是1 但两种都可以抽象成一种,有点像之前做过的带环链表 解法:快慢双指针 1.定义快慢指针 2.慢指针每次向后移动一步,快指针每次向后移动两步 3.判断相遇时候的值 3.代码实现 classSolution{public:intBitSum(int n)//返回每一位数上的平方和{int sum=0;while(n){int m=n%10;

By Ne0inhk
【优选算法必刷100题】第027~28题(前缀和算法):寻找数组的中心下标、除自身以外数组的乘积

【优选算法必刷100题】第027~28题(前缀和算法):寻找数组的中心下标、除自身以外数组的乘积

🔥艾莉丝努力练剑:个人主页 ❄专栏传送门:《C语言》、《数据结构与算法》、C/C++干货分享&学习过程记录、Linux操作系统编程详解、笔试/面试常见算法:从基础到进阶 ⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平 🎬艾莉丝的简介: 🎬艾莉丝的算法专栏简介: 目录 027  寻找数组的中心下标  1.1  算法思路:前缀和 1.2  算法实现 1.2.1  C++实现 1.2.2  Java实现 1.3  博主手记 028  除自身以外数组的乘积 2.1  算法思路 2.2  算法实现

By Ne0inhk
【数据结构与算法】指针美学与链表思维:单链表核心操作全实现与深度精讲

【数据结构与算法】指针美学与链表思维:单链表核心操作全实现与深度精讲

🔥小龙报:个人主页 🎬作者简介:C++研发,嵌入式,机器人等方向学习者 ❄️个人专栏:《C语言》《【初阶】数据结构与算法》 ✨ 永远相信美好的事情即将发生 文章目录 * 前言 * 一、查找 * 二、指定位置之前或之后插入元素 * 2.1 在指定位置之前 * 2.2 在指定位置之后 * 三、指定位置删除或指定位置之后删除 * 3.1 在指定位置 * 3.2 指定位置之后 * 四、代码展现 * 4.1 SList.h * 4.2 SList.c * 4.3 test.c * 五、顺序表和链表的区别 * 总结与每日励志 前言

By Ne0inhk