1、文章以阿里百炼平台大模型做示例,本人已提前在Windows设置好了api-key的环境变量(如果要修改api-key的环境变量记得重启IDEA,避免读取不到),api-key可以从阿里百炼平台生成
2、也集成了ollama的模型,如果要使用,需要下载ollama和指定模型到本地,给你们链接,想玩的话可以去下载(代码中记得把对应的依赖和配置Bean的地方打开呦)
3、下面开始示例:
父pom.xml👇👇👇
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>SpringAiAlibaba_demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>SpringAiAlibaba_demo</name> <description>SpringAiAlibaba_demo</description> <modules> <module>demo2</module> </modules> <packaging>pom</packaging> <properties> <!-- <spring-ai.version>1.0.0</spring-ai.version>--> <!-- <spring-ai-alibaba.version>1.1.2.0</spring-ai-alibaba.version>--> <spring.ai.alibaba.version>1.1.2.0</spring.ai.alibaba.version> <spring-boot.version>3.5.1</spring-boot.version> <mysql.version>8.4.0</mysql.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!--子项目中用到了spring-ai-starter-model-ollama,ollama一直是Spring Ai在维护 这两个是spring ai在维护,只引入spring ai alibaba不行--> <!-- <dependency>--> <!-- <groupId>org.springframework.ai</groupId>--> <!-- <artifactId>spring-ai-bom</artifactId>--> <!-- <version>${spring-ai.version}</version>--> <!-- <type>pom</type>--> <!-- <scope>import</scope>--> <!-- </dependency>--> <!-- <dependency>--> <!-- <groupId>com.alibaba.cloud.ai</groupId>--> <!-- <artifactId>spring-ai-alibaba-bom</artifactId>--> <!-- <version>${spring-ai-alibaba.version}</version>--> <!-- <type>pom</type>--> <!-- <scope>import</scope>--> <!-- </dependency>--> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>${mysql.version}</version> </dependency> <!-- Spring AI Alibaba DashScope Starter:集成阿里云通义千问(DashScope)大模型能力,提供 ChatModel 等核心组件 --> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-starter-dashscope</artifactId> <version>${spring.ai.alibaba.version}</version> </dependency> <!-- 聊天记忆 JDBC 支持 --> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-starter-memory-jdbc</artifactId> <version>${spring.ai.alibaba.version}</version> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>${spring-boot.version}</version> </plugin> </plugins> </build> </project>子pom.xml👇👇👇
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.example</groupId> <artifactId>SpringAiAlibaba_demo</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <artifactId>demo2</artifactId> <name>demo2</name> <description>demo2</description> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-starter-dashscope</artifactId> </dependency> <!-- 聊天记忆 JDBC 支持 --> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-starter-memory-jdbc</artifactId> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- <dependency>--> <!-- <groupId>org.springframework.ai</groupId>--> <!-- <artifactId>spring-ai-starter-model-ollama</artifactId>--> <!-- </dependency>--> </dependencies> <build> <plugins> <!-- 指定Java源码版本为9,编译目标版本也为9。即项目使用Java9语法编写,并编译生成Java9兼容的字节码--> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>9</source> <target>9</target> </configuration> </plugin> </plugins> </build> </project>application.yml👇👇👇
server: port: 9999 servlet: encoding: enabled: true force: true charset: utf-8 spring: application: name: demo2 ai: dashscope: #使用本地大模型,这里也要指定api-key,因为Spring Ai Alibaba框架底层需要读取该参数,即使不用也要指定,不然启动会报错 api-key: ${QIANWEN} chat: memory: repository: jdbc: initialize-schema: always #总是自动创建chatMemory存储的表结构, platform: mariadb #兼容的数据库,这里mariadb对应MySQL datasource: url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver hikari: connection-init-sql: SET NAMES utf8mb4 COLLATE utf8mb4_0900_ai_cicontroller👇👇👇
package com.example.controller; import com.example.service.Demo2Service; import jakarta.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; @RestController public class Demo2Controller { @Autowired private Demo2Service demo2Service; @GetMapping(value = "/dochat/text") public Flux<String> doChatText(@RequestParam("userId") Long userId, @RequestParam("conversationId") Long conversationId, @RequestParam("question") String question) { return demo2Service.doChatText(userId, conversationId, question); } @GetMapping(value = "/dochat/img") public void doChatImg(HttpServletResponse response, @RequestParam("question") String question) { demo2Service.doChatImg(response, question); } }service👇👇👇
package com.example.service; import jakarta.servlet.http.HttpServletResponse; import reactor.core.publisher.Flux; public interface Demo2Service { Flux<String> doChatText(Long userId, Long conversationId, String question); void doChatImg(HttpServletResponse response, String question); }impl👇👇👇
package com.example.service.impl; import com.example.service.Demo2Service; import com.example.util.AiChatUtil; import jakarta.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import reactor.core.publisher.Flux; @Service public class Demo2ServiceImpl implements Demo2Service { @Autowired private AiChatUtil aiChatUtil; @Override public Flux<String> doChatText(Long userId, Long conversationId, String question) { return aiChatUtil.textStreamChatMemory(userId, conversationId, question); } @Override public void doChatImg(HttpServletResponse response, String question) { aiChatUtil.imageStreamChatMemory(response, question); } }util👇👇👇
package com.example.util; import jakarta.servlet.http.HttpServletResponse; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.image.ImageModel; import org.springframework.ai.image.ImagePrompt; import org.springframework.ai.image.ImageResponse; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import reactor.core.publisher.Flux; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URL; /** * AI 对话记忆管理工具类 * 该类封装了 Spring AI Alibaba 的chatClient、imageModel、ChatMemory组件, * 提供基于会话 ID (userId + conversationId)的多轮对话支持和记忆清理功能及文生图功能。 * 适用于需要在 Service 层或 Controller 层灵活控制对话上下文的场景。 */ @Component public class AiChatUtil { /** * 具备多轮对话能力的 ChatClient 实例 */ private final ChatClient chatClient; /** * 具备文生图能力的 ImageModel 实例 */ private final ImageModel imageModel; /** * 对话记忆存储组件 * 用于直接操作底层记忆数据(如清空指定会话的历史记录) */ private final ChatMemory chatMemory; /** * 构造函数注入 * * @param chatClient 预配置的 ChatClient,负责与大模型交互并自动处理上下文拼接 * @param chatMemory 预配置的 ChatMemory,负责历史消息的持久化存储与检索 */ public AiChatUtil(ChatClient chatClient, @Qualifier("cloudWanTwoPointFiveT2iPreview") ImageModel imageModel, ChatMemory chatMemory) { this.chatClient = chatClient; this.imageModel = imageModel; this.chatMemory = chatMemory; } //---------------------------------------------------------------记忆会话--------------------------------------------------------------------------------------------- /** * 执行带记忆的多轮对话 * 该方法通过动态参数覆盖默认的 Advisor 配置,实现针对特定会话的个性化记忆控制。 * * @param userId 用户ID * @param conversationId 会话唯一标识符,用于隔离不同用户或不同对话窗口的上下文 * @param question 用户当前输入的文本消息 * @return 大模型生成的回复内容(字符的形式返回) */ public String textCallChatMemory(Long userId, Long conversationId, String question) { return chatClient.prompt() // 配置 Advisors 的动态参数 .advisors(advisor -> advisor // 指定当前对话所属的会话 ID // ChatMemoryAdvisor 会根据此 ID 从存储中加载对应的历史消息 .param(ChatMemory.CONVERSATION_ID, userId + "" + conversationId) ) // 设置用户当前发送的消息 .user(question) // 发起同步调用并获取纯文本回复 .call() .content(); } /** * 执行带记忆的多轮对话 * 该方法通过动态参数覆盖默认的 Advisor 配置,实现针对特定会话的个性化记忆控制。 * * @param userId 用户ID * @param conversationId 会话唯一标识符,用于隔离不同用户或不同对话窗口的上下文 * @param question 用户当前输入的文本消息 * @return 大模型生成的回复内容(流的形式返回) */ public Flux<String> textStreamChatMemory(Long userId, Long conversationId, String question) { return chatClient.prompt() // 配置 Advisors 的动态参数 .advisors(advisor -> advisor // 指定当前对话所属的会话 ID // ChatMemoryAdvisor 会根据此 ID 从存储中加载对应的历史消息 .param(ChatMemory.CONVERSATION_ID, userId + "" + conversationId) ) // 设置用户当前发送的消息 .user(question) // 发起同步调用并获取纯文本回复 .stream() .content(); } /** * 清空指定会话的对话记忆 * 该操作会从底层存储(如内存、Redis 或数据库)中删除该 userId + conversationId 关联的所有历史消息。 * * 适用场景: * 1. 用户点击“新建对话”或“重置上下文”按钮 * 2. 用户退出登录或会话过期 * 3. 检测到敏感话题需要强制清除历史记录 * * @param userId 用户ID * @param conversationId 会话ID */ public void clearChatMemory(Long userId, Long conversationId) { // 调用 ChatMemory 接口的 clear 方法,物理删除指定会话的历史数据 chatMemory.clear(userId + "" + conversationId); } //---------------------------------------------------------------文生图--------------------------------------------------------------------------------------------- /** * 根据用户消息生成图片 * * @param question 用户当前输入的文本消息 * @return 大模型生成的回复内容(url的形式返回) */ public String imageCallChatMemory(String question) { return imageModel.call(new ImagePrompt(question)) .getResult() .getOutput() .getUrl(); } /** * 根据用户消息生成图片 * * @param question 用户当前输入的文本消息 * @return 大模型生成的回复内容(流的形式返回) */ public void imageStreamChatMemory(HttpServletResponse response, String question) { // 使用提示词生成图片 ImageResponse imageResponse = imageModel.call(new ImagePrompt(question)); // 提取生成的图片URL String imageUrl = imageResponse.getResult().getOutput().getUrl(); try { // 将图片URL转换为可读流 URL url = URI.create(imageUrl).toURL(); InputStream in = url.openStream(); // 设置响应头为PNG格式 response.setHeader("Content-Type", MediaType.IMAGE_PNG_VALUE); // 写出图片字节到HTTP响应输出流 response.getOutputStream().write(in.readAllBytes()); response.getOutputStream().flush(); } catch (IOException e) { // IO异常处理:设置500错误状态码 response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } }config👇👇👇(文中{WorkspaceId}记得换成你自己的业务空间ID呦!!!)
package com.example.config; import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; import com.alibaba.cloud.ai.dashscope.api.DashScopeImageApi; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; import com.alibaba.cloud.ai.dashscope.image.DashScopeImageModel; import com.alibaba.cloud.ai.dashscope.image.DashScopeImageOptions; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.memory.InMemoryChatMemoryRepository; import org.springframework.ai.chat.memory.MessageWindowChatMemory; import org.springframework.ai.image.ImageModel; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.retry.backoff.FixedBackOffPolicy; import org.springframework.retry.policy.SimpleRetryPolicy; import org.springframework.retry.support.RetryTemplate; @Configuration public class LLMConfig { @Bean public ChatMemory chatMemory() { //1.创建内存存储库 //InMemoryChatMemoryRepository 是 ChatMemory 的底层存储实现,负责实际保存和读取消息列表。 //这里使用内存存储,适用于单节点或测试环境;生产环境可替换为 RedisChatMemoryRepository 等持久化实现。 InMemoryChatMemoryRepository repository = new InMemoryChatMemoryRepository(); //2.构建 MessageWindowChatMemory 实例 //MessageWindowChatMemory 是 ChatMemory 接口的具体实现类,它封装了“窗口滑动”的逻辑。 return MessageWindowChatMemory.builder() //注入底层存储库 .chatMemoryRepository(repository) //设置窗口大小:仅保留最近的 10 条消息(包含用户提问和AI回答) .maxMessages(20) .build(); } @Bean(name = "cloudQwenThreePointSevenMax") public ChatClient cloudQwenThreePointSevenMaxChatClient(ChatMemory chatMemory) { return ChatClient.builder(DashScopeChatModel.builder() .dashScopeApi(DashScopeApi.builder() .apiKey(System.getenv("QIANWEN")) /** * 百炼为华北2(北京)、新加坡地域推出了业务空间专属域名,能够为推理请求提供卓越的性能和更高的稳定性,建议迁移至新域名: * 华北2(北京)地域:从 https://dashscope.aliyuncs.com 迁移至 https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com * 新加坡地域:从 https://dashscope-intl.aliyuncs.com 迁移至 https://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com * 其中 {WorkspaceId} 为您的业务空间 ID,可在百炼控制台的业务空间详情页面查看。现有域名仍可正常使用。 */ .baseUrl("https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com") .build()) .defaultOptions(DashScopeChatOptions.builder() .model("qwen3.7-max") //控制输出的随机性。值越低输出越确定/保守,值越高输出越多样/创意。0.7 是平衡创造性与稳定性的常用值 .temperature(0.7) //控制采样的词汇范围。模型从累积概率达到 topP 的候选词中采样。1.0 表示考虑所有候选词,即不做截断过滤 .topP(1.0) .build()) .build()) //增强器,访问大模型前或后去做一些事,这里去检查创建对应表结构 .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build()) .build(); } @Bean(name = "cloudWanTwoPointFiveT2iPreview") public ImageModel cloudWanTwoPointSevenT2vChatModel() { DashScopeImageApi dashScopeImageApi = DashScopeImageApi.builder() /** * 百炼为华北2(北京)、新加坡地域推出了业务空间专属域名,能够为推理请求提供卓越的性能和更高的稳定性,建议迁移至新域名: * 华北2(北京)地域:从 https://dashscope.aliyuncs.com 迁移至 https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com * 新加坡地域:从 https://dashscope-intl.aliyuncs.com 迁移至 https://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com * 其中 {WorkspaceId} 为您的业务空间 ID,可在百炼控制台的业务空间详情页面查看。现有域名仍可正常使用。 */ .baseUrl("https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com") .apiKey(System.getenv("QIANWEN")) .build(); DashScopeImageOptions build = DashScopeImageOptions.builder() //文生图可用模型地址,尽量不用最新的,都是问题,靠。。。。。。 //https://bailian.console.aliyun.com/cn-beijing?tab=api#/api/?type=model&url=2862677 //https://bailian.console.aliyun.com/cn-beijing?tab=api#/api/?type=model&url=2712483 .model("wan2.5-t2i-preview") //设置输出分辨率 .height(1440) .width(1440) //生成1张图/视频 .n(1) .build(); //Spring Ai Alibaba老版本下!!!这里的重试设置没用,Spring Ai Alibaba在new DashScopeImageModel时给固定设置了最大10次重试和间隔15_000L,无语。。。。。。 //我改用了新版本,这里的配置就生效了,哈哈哈 //因为图片生成较慢,请求有轮询机制时(比如浏览器请求)超过最大重试次数Ai会返回Null,所以需要增加重试次数 RetryTemplate retryTemplate = new RetryTemplate(); SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(); //最大重试次数 retryPolicy.setMaxAttempts(30); retryTemplate.setRetryPolicy(retryPolicy); FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy(); //重试间隔(秒) fixedBackOffPolicy.setBackOffPeriod(2000L); retryTemplate.setBackOffPolicy(fixedBackOffPolicy); return new DashScopeImageModel(dashScopeImageApi, build, retryTemplate); } // /** // * 如果要使用ollama的模型,需要增加Spring Ai的依赖,并且在子项目引入对应starter的依赖 // */ // @Bean(name = "localQwenThreePointFive") // public ChatClient localQwenThreePointFiveChatClient(ChatMemory chatMemory) { // return ChatClient.builder(OllamaChatModel.builder() // .ollamaApi(OllamaApi.builder() // .baseUrl("http://localhost:11434") // .build()) // .defaultOptions(OllamaOptions.builder() // .model("qwen3.5:2b") // //控制输出的随机性。值越低输出越确定/保守,值越高输出越多样/创意。0.7 是平衡创造性与稳定性的常用值 // .temperature(0.7) // //控制采样的词汇范围。模型从累积概率达到 topP 的候选词中采样。1.0 表示考虑所有候选词,即不做截断过滤 // .topP(1.0) // .build()) // .build()) // //增强器,访问大模型前或后去做一些事,这里去检查创建对应表结构 // .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build()) // .build(); // } }我们来试一下记忆会话👇👇👇
我们来试一下文生图👇👇👇