当前位置: 首页 > news >正文

Spring AI 入门(持续更新)

介绍

Spring AI 是 Spring 项目中一个面向 AI 应用的模块,旨在通过集成开源框架、提供标准化的工具和便捷的开发体验,加速 AI 应用程序的构建和部署。

依赖

<!-- 基于 WebFlux 的响应式 SSE 传输 -->
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-mcp-client-webflux-spring-boot-starter</artifactId>
</dependency>
<!-- mcp -->
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId>
</dependency>
<!-- spring-ai -->
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
<!-- spring-web -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>

配置文件

配置大模型的 API Key 模型类型

spring:ai:openai:base-url: ${AI_BASE_URL}api-key: ${AI_API_KEY} # 通过环境变量文件 .env 获取chat:options:model: ${AI_MODEL}temperature: 0.8

我这里使用的是 DeepSeek 的 API,可以去官网查看:https://platform.deepseek.com/

# AI URL
AI_BASE_URL=https://api.deepseek.com
# AI 密钥
AI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxx
# AI 模型
AI_MODEL=deepseek-chat

配置类

概念

首先,简单介绍一些概念

  1. ChatClient

ChatClient 提供了与 AI 模型通信的 Fluent API,它支持同步和反应式(Reactive)编程模型。

ChatClient 类似于应用程序开发中的服务层,它为应用程序直接提供 AI 服务,开发者可以使用 ChatClient Fluent API 快速完成一整套 AI 交互流程的组装

  1. ChatModel

ChatModel 即对话模型,它接收一系列消息(Message)作为输入,与模型 LLM 服务进行交互,并接收返回的聊天消息(ChatMessage)作为输出。目前,它有 3 种类型:

  • ChatModel:文本聊天交互模型,支持纯文本格式作为输入,并将模型的输出以格式化文本形式返回

  • ImageModel:接收用户文本输入,并将模型生成的图片作为输出返回(文生图

  • AudioModel:接收用户文本输入,并将模型合成的语音作为输出返回

    ChatModel 的工作原理是接收 Prompt 或部分对话作为输入,将输入发送给后端大模型,模型根据其训练数据和对自然语言的理解生成对话响应,应用程序可以将响应呈现给用户或用于进一步处理。

问题

一个项目中可能会存在多个大模型的调用实例,例如 ZhiPuAiChatModel(智谱)、OllamaChatModel(Ollama本地模型)、OpenAiChatModel(OpenAi),这些实例都实现了ChatModel 接口,当然,我们可以直接使用这些模型实例来实现需求,但我们通常通过 ChatModel 来构建 ChatClient,因为这更通用

可以通过在 yml 配置文件中设置 spring.ai.chat.client.enabled=false 来禁用 ChatClient bean 的自动配置,然后为每个聊天模型 build 出一个 ChatClient。

spring:ai:chat:client:enabled: false

配置类

package cn.onism.mcp.config;import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.zhipuai.ZhiPuAiChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** ChatClient 配置** @author wxw* @date 2025-03-25*/
@Configuration
public class ChatClientConfig {@Resourceprivate OpenAiChatModel openAiChatModel;@Resourceprivate ZhiPuAiChatModel zhiPuAiChatModel;@Resourceprivate OllamaChatModel ollamaChatModel;@Resourceprivate ToolCallbackProvider toolCallbackProvider;@Bean("openAiChatClient")public ChatClient openAiChatClient() {return ChatClient.builder(openAiChatModel)// 默认加载所有的工具,避免重复 new.defaultTools(toolCallbackProvider.getToolCallbacks()).defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory())).build();}@Bean("zhiPuAiChatClient")public ChatClient zhiPuAiChatClient() {return ChatClient.builder(zhiPuAiChatModel)// 默认加载所有的工具,避免重复 new.defaultTools(toolCallbackProvider.getToolCallbacks()).defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory())).build();}@Bean("ollamaChatClient")public ChatClient ollamaChatClient() {return ChatClient.builder(ollamaChatModel)// 默认加载所有的工具,避免重复 new.defaultTools(toolCallbackProvider.getToolCallbacks()).defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory())).build();}
}

使用 ChatClient 的时候,@Resource 注解会按 Bean 的名称注入

@Resource
private ChatClient openAiChatClient;@Resource
private ChatClient ollamaChatClient;@Resource
private ChatClient zhiPuAiChatClient;

基础对话

普通响应

使用 call 方法来调用大模型

private ChatClient openAiChatModel;@GetMapping("/chat")
public String chat(){Prompt prompt = new Prompt("你好,请介绍下你自己");String response = openAiChatModel.prompt(prompt).call().content();return response;
}

流式响应

call 方法修改为 stream,最终返回一个 Flux 对象

@GetMapping(value = "/chat/stream", produces = "text/html;charset=UTF-8")
public Flux<String> stream() {Prompt prompt = new Prompt("你好,请介绍下你自己");String response = openAiChatModel.prompt(prompt).stream().content();return response;
}

tips:我们可以通过缓存减少重复请求,提高性能。可以使用 Spring Cache 的 @Cacheable 注解实现:

@Cacheable("getChatResponse")
public String getChatResponse(String message){String response = openAiChatModel.prompt().user(message).call().content();return response;
}

tips: 适用于批量处理场景。可以使用 Spring 的 @Async 注解实现:

@Async
public CompletableFuture<String> getAsyncChatResponse(String message) {return CompletableFuture.supplyAsync(() -> openAiChatModel.prompt().user(message).call().content());
}

3 种组织提示词的方式

Prompt

通过 Prompt 来封装提示词实体,适用于简单场景

Prompt prompt = new Prompt("介绍下你自己");
PromptTemplate

使用提示词模板 PromptTemplate 来复用提示词,即将提示词的大体框架构建好,用户仅输入关键信息完善提示词

其中,{ } 作为占位符,promptTemplate.render 方法来填充

@GetMapping("/chat/formatPrompt")
public String formatPrompt(@RequestParam(value = "money") String money,@RequestParam(value = "number") String number,@RequestParam(value = "brand") String brand
) {PromptTemplate promptTemplate = new PromptTemplate("""根据我目前的经济情况{money},只推荐{number}部{brand}品牌的手机。""");Prompt prompt = new Prompt(promptTemplate.render(Map.of("money",money,"number", number, "brand", brand)));return openAiChatModel.prompt(prompt).call().content();
}
Message

使用 Message ,提前约定好大模型的功能或角色

消息类型:

系统消息(SystemMessage):设定对话的背景、规则或指令,引导 AI 的行为
用户消息(UserMessage):表示用户的输入,即用户向 AI 提出的问题或请求
助手消息(AssistantMessage):表示 AI 的回复,即模型生成的回答
工具响应消息(ToolResponseMessage):当 AI 调用外部工具(如 API)后,返回 工具的执行结果,供 AI 进一步处理
@GetMapping("/chat/messagePrompt")
public String messagePrompt(@RequestParam(value = "book", defaultValue = "《白夜行》") String book) {// 用户输入UserMessage userMessage = new UserMessage(book);log.info("userMessage: {}", userMessage);// 对系统的指令SystemMessage systemMessage = new SystemMessage("你是一个专业的评书人,给出你的评价吧!");log.info("systemMessage: {}", systemMessage);// 组合成完整的提示词,注意,只能是系统指令在前,用户消息在后,否则会报错Prompt prompt = new Prompt(List.of(systemMessage, userMessage));return openAiChatModel.prompt(prompt).call().content();
}
保存 prompt

prompt 不宜嵌入到代码中,可以将作为一个 .txt 文件 其保存到 src/main/resources/prompt 目录下,使用读取文件的工具类就可以读取到 prompt

package cn.onism.mcp.utils;import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;/*** @description: 读取文件内容的工具类* @date: 2025/5/8*/
@Component
public class FileContentReader {public String readFileContent(String filePath) {StringBuilder content = new StringBuilder();try {// 创建 ClassPathResource 对象以获取类路径下的资源ClassPathResource resource = new ClassPathResource(filePath);// 打开文件输入流InputStream inputStream = resource.getInputStream();// 创建 BufferedReader 用于读取文件内容BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));String line;// 逐行读取文件内容while ((line = reader.readLine()) != null) {content.append(line).append("\n");}// 关闭输入流reader.close();} catch (IOException e) {// 若读取文件时出现异常,打印异常信息e.printStackTrace();}return content.toString();}
}
PromptTemplate promptTemplate = new PromptTemplate(fileContentReader.readFileContent("prompt/formatPrompt.txt")
);

解析模型输出(结构化)

模型输出的格式是不固定的,无法直接解析或映射到 Java 对象,因此,Spring AI 通过在提示词中添加格式化指令要求大模型按特定格式返回内容,在拿到大模型输出数据后通过转换器做结构化输出。

实体类 Json 格式

首先我们定义一个实体类 ActorInfo

@Data
@Description("演员信息")
public class ActorInfo {@JsonPropertyDescription("演员姓名")private String name;@JsonPropertyDescription("演员年龄")private Integer age;@JsonPropertyDescription("演员性别")private String gender;@JsonPropertyDescription("演员出生日期")private String birthday;@JsonPropertyDescription("演员国籍")private String nationality;
}

在 call 方法后面调用 entity 方法,把对应实体类的 class 传递进去即能做到结构化输出

@GetMapping("/chat/actor")
public ActorInfo queryActorInfo(@RequestParam(value = "actorName") String actorName) {PromptTemplate promptTemplate = new PromptTemplate("查询{actorName}演员的详细信息");Prompt prompt = new Prompt(promptTemplate.render(Map.of("actorName", actorName)));ActorInfo response = openAiChatModel.prompt(prompt).call().entity(ActorInfo.class);return response;
}

结果符合要求

List 列表格式

在 entity 方法中传入 new ListOutputConverter(new DefaultConversionService())

@GetMapping("/chat/actorMovieList")
public List<String> queryActorMovieList(@RequestParam(value = "actorName") String actorName) {PromptTemplate promptTemplate = new PromptTemplate("查询{actorName}主演的电影");Prompt prompt = new Prompt(promptTemplate.render(Map.of("actorName", actorName)));List<String> response = openAiChatModel.prompt(prompt).call().entity(new ListOutputConverter(new DefaultConversionService()));return response;
}
Map 格式

tips: 目前在 Map 中暂不支持嵌套复杂类型,因此 Map 中不能返回实体类,而只能是 Object。

在 entity 方法中传入 new ParameterizedTypeReference<>() {}

@GetMapping("/chat/actor")
public Map<String, Object> queryActorInfo(@RequestParam(value = "actorName") String actorName) {PromptTemplate promptTemplate = new PromptTemplate("查询{actorName}演员及另外4名相关演员的详细信息");Prompt prompt = new Prompt(promptTemplate.render(Map.of("actorName", actorName)));Map<String, Object> response = openAiChatModel.prompt(prompt).call().entity(new ParameterizedTypeReference<>() {});return response;
}

多模态

deepseek 暂时不支持多模态,因此这里选用 智谱:https://bigmodel.cn/

依赖与配置

<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-zhipuai</artifactId><version>1.0.0-M6</version>
</dependency>
spring:ai:zhipuai:api-key: ${ZHIPUAI_API_KEY}chat:options:model: ${ZHIPUAI_MODEL}temperature: 0.8
# api-key
ZHIPUAI_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxx
# 所选模型
ZHIPUAI_MODEL=glm-4v-plus-0111
理解图片

在 src/main/resources/images 目录下保存图片

创建一个 ZhiPuAiChatModel,将用户输入和图片封装成一个 UserMessage,然后使用 call 方法传入一个 prompt,最后获得输出

@Resource
private ZhiPuAiChatModel zhiPuAiChatModel;@GetMapping("/chat/pic")
public String pic() {Resource imageResource = new ClassPathResource("images/mcp.png");// 构造用户消息var userMessage = new UserMessage("解释一下你在这幅图中看到了什么?",new Media(MimeTypeUtils.IMAGE_PNG, imageResource));ChatResponse chatResponse = zhiPuAiChatModel.call(new Prompt(userMessage));return chatResponse.getResult().getOutput().getText();
}

文生图

这里需要使用 zhiPuAiImageModel,我们调用它的 call 方法,传入一个 ImagePrompt,ImagePrompt 由**用户图片描述输入 ImageMessage **和 **图片描述信息 OpenAiImageOptions **所构成,

其中,

  • model 要选择适用于图像生成任务的模型,这里我们选择了 cogview-4-250304
  • quality 为图像生成图像的质量,默认为 standard
    • hd : 生成更精细、细节更丰富的图像,整体一致性更高,耗时约20 秒
    • standard :快速生成图像,适合对生成速度有较高要求的场景,耗时约 5-10 秒
@Autowired
ZhiPuAiImageModel ziPuAiImageModel;@Autowired
private FileUtils fileUtils;@GetMapping("/outputImg")
public void outputImg() throws IOException {ImageMessage userMessage = new ImageMessage("一个仙人掌大象");OpenAiImageOptions chatOptions = OpenAiImageOptions.builder().model("cogview-4-250304").quality("hd").N(1).height(1024).width(1024).build();ImagePrompt prompt = new ImagePrompt(userMessage, chatOptions);// 调用ImageResponse imageResponse = ziPuAiImageModel.call(prompt);// 输出的图片Image image = imageResponse.getResult().getOutput();// 保存到本地InputStream in = new URL(image.getUrl()).openStream();fileUtils.saveStreamToFile(in,"src/main/resources/images", "pic"+RandomUtils.insecure().randomInt(0, 100)+".png");
}
@Component
public class FileUtils {public String saveStreamToFile(InputStream inputStream, String filePath, String fileName) throws IOException {// 创建目录(如果不存在)Path dirPath = Paths.get(filePath);if (!Files.exists(dirPath)) {Files.createDirectories(dirPath);}// 构建完整路径Path targetPath = dirPath.resolve(fileName);// 使用 try-with-resources 确保流关闭try (inputStream) {Files.copy(inputStream, targetPath, StandardCopyOption.REPLACE_EXISTING);}return targetPath.toAbsolutePath().toString();}
}

调用本地模型

_**tips: **_若想零成本调用大模型,并且保障隐私,可以阅读本章节

下载安装 Ollama

下载安装 Ollama:https://ollama.com/ [Ollama 是一个开源的大型语言模型服务工具,旨在帮助用户快速在本地运行大模型。通过简单的安装指令,用户可以通过一条命令轻松启动和运行开源的大型语言模型。Ollama 是 LLM 领域的 Docker],安装完成后执行 ollama 得到如下输出则表明安装成功

选择一个模型下载到本地:https://ollama.com/search,我这里选择了 qwen3:8b

引入 ollama 依赖

<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency>

配置

spring:ai:ollama:chat:model: ${OLLAMA_MODEL}base-url: ${OLLAMA_BASE_URL}
# 本地模型
OLLAMA_MODEL=qwen3:8b
# URL
OLLAMA_BASE_URL=http://localhost:11434

实际调用

/*** ollama本地模型测试* @param input* @return*/
@GetMapping("/ollama/chat")
public String ollamaChat(@RequestParam(value = "input") String input) {Prompt prompt = new Prompt(input);return ollamaChatModel.call(prompt).getResult().getOutput().getText();
}

结果

对话记忆

内存记忆(短期)

MessageChatMemoryAdvisor 可以用来提供聊天记忆功能,这需要传递一个基于内存记忆的 ChatMemory

/*** 内存记忆/短期记忆* @param input* @return*/
@GetMapping("/memory/chat")
public String chat(@RequestParam(value = "input") String input) {Prompt prompt = new Prompt(input);// 内存记忆的 ChatMemoryInMemoryChatMemory inMemoryChatMemory = new InMemoryChatMemory();return openAiChatClient.prompt(prompt).advisors(new MessageChatMemoryAdvisor(inMemoryChatMemory)).call().content();
}

测试

隔离

多用户与 AI 对话,每个用户的对话记录是相互隔离的,因此记忆也要隔离。

有 bug,明天看看怎么个事儿

/*** 短期记忆,按用户id隔离* @param input* @param userId* @return*/
@GetMapping("/memory/chat/user")
public String chatByUser(@RequestParam(value = "input") String input, @RequestParam(value = "userId") String userId) {Prompt prompt = new Prompt(input);return openAiChatClient.prompt(prompt).advisors(new MessageChatMemoryAdvisor(inMemoryChatMemory,userId,AbstractChatMemoryAdvisor.DEFAULT_CHAT_MEMORY_RESPONSE_SIZE)).call().content();
}
集成 Redis

TODO

集成 MySQL

TODO

MCP

TODO

RAG

TODO

相关文章:

  • 深入解析建造者模式(Builder Pattern)——以Java实现复杂对象构建的艺术
  • 支持鸿蒙next的uts插件
  • 计算机学习路线与编程语言选择(信息差)
  • LLaMA模型本地部署全攻略:从零搭建私有化AI助手
  • 突破网络限制:Windows平台离线搭建Linux环境+Docker化部署AI知识库RAGFlow实战
  • 平板收银系统、国产系统,鸿蒙系统,小键盘的封装与应用—仙盟创梦IDE
  • Matlab 数控车床进给系统的建模与仿真
  • Java执行linux服务器本地命令
  • HTTP Error 500.31 - Failed to load ASP.NET Core runtime
  • 第三节第一部分:Static修饰类变量、成员变量
  • xiaopiu原型设计工具笔记
  • 多环串级PID
  • Spring Boot 启动原理的核心机制
  • Git实战经验分享:深入掌握git commit --amend的进阶技巧
  • 一种机载扫描雷达实时超分辨成像方法——论文阅读
  • uniapp|实现多终端视频弹幕组件、内容轮询、信息表情发送(自定义全屏半屏切换、弹幕启用)
  • k8s(11) — 探针和钩子
  • 【Redis】持久化与事务
  • 电容的基本介绍
  • iNeuOS工业互联网操作系统,集成DeepSeek大模型应用
  • 迪拜金融市场CEO:2024年市场表现出色,超八成新投资者来自海外
  • 习近平同俄罗斯总统普京会谈
  • 谜语的强制力:弗洛伊德与俄狄浦斯
  • 再有20余篇论文出现“妇科男患者”“前列腺女患者”,如何破除“水论文”灰产链?
  • 强沙尘暴压城近万名游客被困,敦煌如何用3小时跑赢12级狂风?
  • 郭旭涛转任河北省科协党组书记、常务副主席,曾任团省委书记