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

Python 爬虫项目:爬取 B 站视频标题与播放量

前言 B 站(哔哩哔哩)作为国内领先的视频内容平台,其视频标题、播放量等数据是分析内容趋势、用户偏好的重要依据。相较于静态网页爬取,B 站页面融合了动态加载等特性,对新手而言是进阶爬虫学习的典型场景。本文从零基础视角出发,系统讲解如何构建稳定的 B 站视频数据爬虫,涵盖动态页面分析、数据精准提取、反爬策略适配等核心知识点,帮助读者掌握针对主流视频平台的爬虫开发思路。 摘要 本文以B 站热门视频榜单(https://www.bilibili.com/v/popular/rank/all)为爬取目标,采用 requests 库发送 HTTP 请求获取页面源码,通过 BeautifulSoup 解析 HTML 结构,精准提取视频标题、播放量、UP 主、弹幕数等核心数据,并实现数据的结构化存储与可视化展示。针对 B

By Ne0inhk

Trae编译C++

一、前置准备 1. 安装 Trae: * 下载对应系统版本(Windows/Linux/macOS),解压到自定义目录(如D:\trae); * 配置环境变量(将 Trae 的可执行文件路径加入系统PATH),确保终端 / 命令行能直接输入trae调用。 2. 确认依赖:Trae 依赖 GCC/Clang,需先安装: * Windows:安装 MinGW(推荐 MinGW-w64),配置gcc环境变量; * Linux:sudo apt install gcc g++(Debian/Ubuntu); * macOS:xcode-select --install安装 Xcode 命令行工具。 二、用 Trae 编译 C++ 的核心步骤(

By Ne0inhk

MISRA C++静态分析集成CI/CD:项目应用示例

将MISRA C++静态分析融入CI/CD:一位嵌入式工程师的实战手记 最近在参与一个车载ECU模块的开发,团队面临的最大挑战不是功能实现,而是如何确保每一行代码都经得起功能安全标准ISO 26262的严苛审查。我们最终选择将 MISRA C++ 静态分析深度集成到CI/CD流程中——这不仅是一次工具链升级,更是一场从“写完再检”到“边写边防”的工程思维转变。 今天我想以这个真实项目为例,和你聊聊我们是如何一步步把这套看似“教条”的编码规范,变成流水线里沉默却可靠的“守门人”的。 为什么是MISRA C++?不只是合规,更是系统确定性的基石 如果你做过汽车电子、工业控制或航空航天类项目,大概率听说过 MISRA(Motor Industry Software Reliability Association) 。它最初由英国汽车工业界发起,目标很明确: 让C/C++这种强大但危险的语言,在关键系统中变得可控、可预测。 而 MISRA C+

By Ne0inhk
【C++】优选算法必修篇之双指针实战:有效三角形个数 & 和为s的两个数字

【C++】优选算法必修篇之双指针实战:有效三角形个数 & 和为s的两个数字

【C++】优选算法必修篇之双指针实战:有效三角形个数 & 和为s的两个数字 * 双指针应用场景 * 目录 * 1. 有效三角形个数 * 1.1 题目链接 * 1.2 题目描述 * 1.3 题目示例 * 1.4 算法思路 * 1.5 核心代码 * 1.6 示例测试(总代码) * 2. 和为s的两个数字 * 2.1 题目链接 * 2.2 题目描述 * 2.3 题目示例 * 2.4 算法思路 * 2.5 核心代码 * 2.6 示例测试(总代码) * 总结

By Ne0inhk