springai Alibaba(上)
一.SAA1.1 SAA为什么出现随着人工智能(AI)技术的迅猛发展越来越多的开发者开始将目光投向AI应用的开发。然而目前市场上多数AI框架和工具如LangChain、PyTorch等主要支持Python而Java开发者常常面临工具缺乏和学习门较高的问题但是不用担心谁让Java/Spring群体强大呢?所以任何一个框架/XXX云服务器想要大面积推广就应该不会忘记庞大的Spring社区和Java程序员所以传统的微服务和大模型要联系在一起需要借助中间件Langchain4J,SpringAI或者SpringAI Alibaba1.2 什么是SSA对于Java开发者来说我们需要一款AI应用开发框架来简化AI应用开发。在这样的背景下Spring官方开源了Spring AI框架用来简化 Spring开发者开发智能体应用的过程。随后阿里巴巴开源了 Spring AI Alibaba它基于Spring AI同时与阿里云百炼大模型服务、通义系列大模型做了深度集成与最佳实践。基于 Spring AI AlibabaJava开发者可以非常方便的开发 AI智能体应用。所以Spring AI Alibaba是一款以Spring AI为基础深度集成百炼平台支持ChatBot,工具流多智能体应用开发模式的AI框架。二.入门案例1.确定 Spring AI Alibaba 与 Spring Al、SpringBoot版本的兼容关系Spring AI Alibaba使用四位版本号的版本管理方式前三位版本号与Spring AI主版本对应SpringAI Alibaba社区在前三位主版本基础上持续迭代第四位版本号。需注意Spring AI Alibaba需要与Spring AI版本对应2. 接入通义模型:阿里云百炼平台入口官网大模型服务平台百炼控制台 (aliyun.com)大模型调用三件套:获得API-key获得模型名获得baseUrl开发地址3.IDEA工具中创建project夫工程通过bom统一版本管理properties !--引入-- spring-ai.version1.0.0/spring-ai.version spring-ai-alibaba.version1.0.0.2/spring-ai-alibaba.version spring-boot.version3.5.5/spring-boot.version /properties dependencyManagement dependencies !-- Spring Boot -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-dependencies/artifactId version${spring-boot.version}/version typepom/type scopeimport/scope /dependency !-- Spring AI Alibaba -- dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-bom/artifactId version${spring-ai-alibaba.version}/version typepom/type scopeimport/scope /dependency !-- Spring AI -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version${spring-ai.version}/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId version${spring-boot.version}/version /plugin /plugins /build4.开发五步骤:建module----建一个SAA-01HelloWorld改POMdependencies !-- 引入 springai alibaba DashScope 模型适配的 Starter-- !--对于开发者来说DashScope 的意义在于它把所有大模型的 API 接口标准化了-- dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter-dashscope/artifactId /dependency /dependencies写yml#防止乱码的配置 server.servlet.encoding.enabledtrue server.servlet.encoding.forcetrue server.servlet.encoding.charsetUTF-8 spring.application.nameSAA-01HelloWorld # SpringAIAlibaba Config spring.ai.dashscope.api-key密钥 spring.ai.dashscope.base-url spring.ai.dashscope.chat.options.model名字主启动业务类对话模型Chat Model对话模型(Chat Model)接收一系列消息(Message)作为输入与模型LLM服务进行交互并接收返回的聊天消息(Chat Message)作为输出。相比于普通的程序输入模型的输入与输出消息不止支持纯字符文本还支持包括语音、图片、视频等为输入输出。同时在SpringAIAlibaba中消息中还支持包含不同的角色帮助底层模型区分来自模型、用户和系统指令等的不同消息。Spring AI Alibaba复用了Spring AI抽象的 Model API并与通义系列大模型服务进行适配(如通义千问、通义万相等)目前支持纯文本聊天、文生图、文生语音、语音转文本等。以下是框架定义的几个核心API:ChatModel文本聊天交互模型支持纯文本格式作为输入并将模型的输出以格式化文本形式返回。ImageModel接收用户文本输入并将模型生成的图片作为输出返回。AudioModel接收用户文本输入并将模型合成的语音作为输出返回。模式方法表现适用场景同步 (Sync)chatModel.call()等 AI 全部写完后一次性返回。后端逻辑处理、短文本生成。流式 (Stream)chatModel.stream()边生成边返回打字机效果基于Flux。聊天机器人、长文创作用户体验好。package com.alibaba.cloud.ai.example.helloworld.controller; import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; import jakarta.annotation.Resource; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatModel; import org.springframework.boot.context.properties.bind.DefaultValue; 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 ChatHelloController { Resource // 对话模型调用阿里云百炼平台 private ChatModel chatModel; /** * 通用调用 * param msg * return */ GetMapping(value /hello/dochat) public String doChat(RequestParam(name msg,defaultValue你是谁) String msg) { String result chatModel.call(msg); return result; } /** * 流式返回调用 * param msg * return */ GetMapping(value /hello/streamchat) public FluxString stream(RequestParam(name msg,defaultValue你是谁) String msg) { return chatModel.stream(msg); } }访问:localhost:8001/hello/dochat注意: 如果结果出不来的请注意一下模型别用成多模态的模型Qwen3.5-plus就是多模态的当我不想用qwen了想用deepseek这些该怎么办呢?此时只需要改名称即可百炼提供了各大模型如下所示:效果:上述可能出现密钥暴露的情况所以我们的ApiKey可以配置到环境变量改yml:spring.ai.dashscope.api-key${aliQwen-api}新建配置类:SaaLLMConfig有两种方式方式一:Configuration public class SaaLLMConfig { /** * 方式1:${} * 持有yml文件配置spring.ai.dashscope.api-key${aliQwen-api} */ Value(${spring.ai.dashscope.api-key}) private String apiKey; Bean public DashScopeApi dashScopeApi() { return DashScopeApi.builder().apiKey(apiKey).build(); } }方式二:Configuration public class SaaLLMConfig { /** * 方式2:System.getenv(环境变量) * 持有yml文件配置spring.ai.dashscope.api-key${aliQwen-api} * return */ Bean public DashScopeApi dashScopeApi() { return DashScopeApi.builder() .apiKey(System.getenv(aliQwen-api)) .build(); } }三.Ollama私有化部署和对接本地大模型3.1 Ollama之入门理论Ollama作为一个工具是专门用来做本地化部署。旨在简化在Docker容器中部署和管理大型语言模型(LLM)的过程。它帮助用户快速在本地运行大模型通过简单的安装指令用户可以执行一条命令就在本地运行开源大型语言模型如Llama 2.Ollama极大地简化了在Docker容器内部署和管理LLM的过程它优化了设置和配置细节包括GPU使用情况并将模型权重、配置和数据捆绑到一个包中定义成Modelfile。此外Ollama还提供了多种大型语言模型的开源仓库用户可以通过简单的命令行操作来下载和运行这些模型。总的来说Ollama是一个用于在本地高效运行大型语言模型的工具。Docker Hub玩镜像Ollama Hub玩模型3.2 Ollama安装1.可以进入Ollama的官网下载 Ollama2.自定义安装路径:因为ollama在拉取大模型的时候占用的空间可能比较大所以需要我们安装在C盘以外的盘在文件路径上输入cmd回车打开命令窗口然后再其输入:OllamaSetup.exe /DIRD:\develop\ollama3.手动创建大模型的存储路径进行“环境变量”配置:在“系统变量”中点击“新建”变量名OLLAMA_MODELS变量值D:\develop\ollama\models。创建完环境变量把Ollama停止然后进入C盘--用户--你自己的电脑名称--.ollama--复制整个models到刚刚上面新建的存储目录下。复制完成后要删除C盘目录下的models文件夹。重启Ollama打开CMD输入。之后每次想要用模型的时候就输入 ollama run qwen3:1.7b(模型名)3.3 Ollama下载模型命令在官网Ollama中搜索想要的模型首先在下载之前需要知道b代表什么意思:在此我演示一个占用较小空间的演示:点进入后copy安装成功之后此时我输入:3.4 微服务调用ollama本地模型下面我们通过一个编码案例来介绍如何在微服务中调用ollama本地模型步骤还是和之前一样1.建module2.改pom---在其基础上改动新建!--ollama-- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-ollama/artifactId version1.0.0/version /dependency3.改propertiesserver.port8002 server.servlet.encoding.enabledtrue server.servlet.encoding.forcetrue server.servlet.encoding.charsetUTF-8 spring.application.nameSAA-02Ollama # SpringAIAlibaba Config spring.ai.dashscope.api-key自己的key spring.ai.ollama.base-urlhttp://localhost:11434 spring.ai.ollama.chat.modelqwen3:1.7b4.主启动4.业务类正常启动业务类会有两个chatmodel(一个allama一个dashscope)所以需要我们指定使用哪一个Resource(name ollamaChatModel) private ChatModel chatModel;四.ChatClient和ChatModel4.1 简介通过之前的实战案例相信大家对ChatModel已经不陌生了,对话模型(ChatModel)是底层接口直接与具体大语言模型交互提供call0和stream0方法适合简单大模型交互场景。那什么是ChatClient呢?由上可知ChatClient是高级封装基于ChatModel构建适合快速构建标准化复杂AI服务支持同步和流式交互集成多种高级功能。特性ChatModelChatClient定位底层适配器Low-level高层 API 封装High-level关注点模型的连接与推理参数开发者体验与业务逻辑状态感通常是无状态的点对点可以通过 Advisor 轻松实现有状态记住对话灵活性决定了你用哪个“脑子”决定了你如何与“脑子”沟通类比于java中的知识点ChatModel数据库驱动 (JDBC Driver)ChatClientJdbcTemplate / MyBatis下述是二者对比:4.2 编码案例和第一个module一样建module,改pom,写yml,主启动然后写业务类业务类第一版:---先只注入chatmodelpackage com.alibaba.ai.controller; import jakarta.annotation.Resource; import org.springframework.ai.chat.model.ChatModel; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; RestController public class ChatModelController { Resource //阿里云百炼 private ChatModel dashScopeChatModel; GetMapping(/chatmodel/dochat) public String doChat(RequestParam(name msg,defaultValue 你是谁) String msg) { String result dashScopeChatModel.call(msg); System.out.println(响应 result); return result; } }但是此时如果我像chatmodel一样注入chatclient重启微服务会报错由此可知道ChatClient只支持手动注入不支持自动注入那么如何解决ChatClient无法自动注入呢?由此引出了第二版:业务类第二版:---先只注入chatclient使用自动配置的构造注入ChatClient.Builder在 Spring 中我们通常不直接注入ChatClient而是注入ChatClient.Builder。原因Builder是一个工厂它可以为不同的业务场景“定制”不同的管家比如一个带记忆的管家一个不带记忆的管家。package com.alibaba.ai.controller; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.model.ChatModel; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; RestController public class ChatClientController { private final ChatClient dashScopechatClient; /** * 使用自动配置的 ChatClient.Builder * param dashscopeChatModel */ public ChatClientController(ChatModel dashscopeChatModel) { this.dashScopechatClient ChatClient.builder(dashscopeChatModel).build(); } /** * http://localhost:8003/chatclient/dochat * param msg * return */ GetMapping(/chatclient/dochat) public String doChat(RequestParam(name msg,defaultValue 2加4等于几) String msg) { String result dashScopechatClient.prompt().user(msg).call().content(); System.out.println(响应 result); return result; } }一个完整的ChatClient调用流程通常遵循prompt()-设置参数-call()/stream()-获取结果1.设置输入(Input).user(String text)设置用户发送的消息。.system(String text)设置系统角色System Prompt定义 AI 的性格和行为边界。.prompt(Prompt prompt)如果你已经有一个现成的Prompt对象可以直接传入。2. 配置参数Configuration.advisors(ConsumerAdvisorSpec consumer)这是最强大的方法。用于挂载插件比如MessageChatMemoryAdvisor记忆或QuestionAnswerAdvisorRAG/知识库。.options(ChatOptions options)设置模型参数比如temperature随机性或topP。.functions(String... functionNames)开启 Function Calling让 AI 可以调用你定义的 Java 方法比如查天气、查数据库。3. 执行动作Execution.call()同步阻塞。发出请求原地等待 AI 写完所有字后一次性返回。适用于生成短文本。.stream()流式响应。像打字机一样AI 写一个字后端就往前端传一个字。适用于长文生成用户体验极佳。4.获得产出一旦执行了.call()你就可以决定要什么格式的结果.content()最常用直接返回 AI 回复的字符串。.entity(ClassT type) 自动将 AI 的回复解析为指定的 Java 对象JSON 自动转 Bean。.chatResponse()返回完整的响应对象包含 Token 消耗情况、原始元数据等。由上述代码可知ChatClient不支持自动注入依赖ChatModel对象接口。但是在实际业务中chatmodel和chatclient同时存在由此引出业务的第三版:业务类第三版:---chatmodel和chatclient混合使用1. 修改配置类SaaLLMConfigConfiguration public class SaaLLMConfig { /** * 知识出处 * https://java2ai.com/docs/1.0.0.2/tutorials/basics/chat-client/?spm5176.29160081.0.0.2856aa5cmUTyXC#%E5%88%9B%E5%BB%BA-chatclient * param dashscopeChatModel * return */ Bean public ChatClient chatClient(ChatModel dashscopeChatModel) { return ChatClient.builder(dashscopeChatModel).build(); } }2. 新建ChatClientControllerV2RestController public class ChatClientControllerV2 { /** * chatModel ChatClient 混合使用 */ Resource private ChatModel chatModel; Resource private ChatClient dashScopechatClientv2; /** * http://localhost:8003/chatclientv2/dochat * param msg * return */ GetMapping(/chatclientv2/dochat) public String doChat(RequestParam(name msg,defaultValue 你是谁) String msg) { String result dashScopechatClientv2.prompt().user(msg).call().content(); System.out.println(ChatClient响应 result); return result; } /** * http://localhost:8003/chatmodelv2/dochat * param msg * return */ GetMapping(/chatmodelv2/dochat) public String doChat2(RequestParam(name msg,defaultValue 你是谁) String msg) { String result chatModel.call(msg); System.out.println(ChatModel响应 result); return result; } }那如果我想要同时存在多种大模型产品在系统中共存使用怎么办呢?可以看下一章节五.SSE服务器发送事件在本章节我们将会讲到Server-SentEvents(SSE实现Stream流式输出和多模型共存5.1 流式输出(StreamingOutput)是一种逐步返回大模型生成结果的技术生成一点返回一点允许服务器将响应内容分批次实时传输给客户端而不是等待全部内容生成完毕后再一次性返回。这种机制能显著提升用户体验尤其适用于大模型响应较慢的场景如生成长文本或复杂推理结果。前置知识:Springboot3响应式编程响应式编程是能实现一个全异步式的非阻塞式框架。在响应式世界里数据不再是简单的String或List而是像流水一样。Spring 使用了Project Reactor库它有两个核心的“包装盒”Mono代表0 到 1个元素的流。比如获取一个用户的详情。Flux代表0 到 N个元素的流。比如获取一个列表或者实时刷新的股票行情。响应式编程其核心:1. 数据流:数据源头2. 变化传播: 数据操作(中间操作)3. 异步编程模式: 底层控制异步subscribe5.2 SSE概念SSE即Server-Sent Events,翻译过来就是服务器发送事件它由两部分组成:Server-Sent:由服务器发送。Events:事件指服务器主动推送给客户端的数据或消息。Server-SentEvents(SSE)是一种允许服务端可以持续推送数据片段(如逐词或逐句)到前端的Web技术。通过单向的HTTP长连接使用一个长期存在的连接让服务器可以主动将数据推给客户端SSE是轻量级的单向通信协议适合AI对话这类服务端主导的场景。SSE的核心思想是:客户端发起一个请求服务器保持这个连接打开并在有新数据时通过这个连接将数据发送给客户端。这与传统的请求-响应模式(客户端请求一次服务器响应一次连接关闭)有本质区别。SSE下一代(StreamableHttp)总之SSE是一种让服务器能主动持续地向客户端(比如你的网页浏览器)推送数据的技术。5.3 编码案例接下来我们将实现一个案例要求同时存在多种大模型产品在系统中共存使用前面的步骤还是一致新建子模块Module,改pom,写yml,主启动先看第一版:package com.ai.alibaba.config; import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; import org.springframework.ai.chat.model.ChatModel; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class SaaLLMConfig { // 模板名称常量定义一套系统多模型共存 private final String DEEPSEEK_MODELdeepseek-v3; private final String QWEN_MODEL qwen3-max; Bean(name deepSeek) public ChatModel deepSeek(){ return DashScopeChatModel.builder() .dashScopeApi(DashScopeApi.builder().apiKey(System.getenv(aliQwen-api)).build()) .defaultOptions(DashScopeChatOptions.builder().withModel(DEEPSEEK_MODEL).build()) .build(); } Bean(name qwen) public ChatModel qwen(){ return DashScopeChatModel.builder() .dashScopeApi(DashScopeApi.builder().apiKey(System.getenv(aliQwen-api)).build()) .defaultOptions(DashScopeChatOptions.builder().withModel(QWEN_MODEL).build()) .build(); } }package com.ai.alibaba.controller; import jakarta.annotation.Resource; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.model.ChatModel; 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 StreamOutputController { //V1 通过ChatModel实现stream实现流式输出 Resource(name deepSeek) private ChatModel deepseekChatModel; Resource(name qwen) private ChatModel qwenChatModel; GetMapping(value /stream/chatflux1) public FluxString chatflux(RequestParam(name question,defaultValue 你是谁) String question) { return deepseekChatModel.stream(question); } GetMapping(value /stream/chatflux2) public FluxString chatflux2(RequestParam(name question,defaultValue 你是谁) String question) { return qwenChatModel.stream(question); } }结果为:看第二版:package com.ai.alibaba.config; import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class SaaLLMConfig { // 模板名称常量定义一套系统多模型共存 private final String DEEPSEEK_MODELdeepseek-v3; private final String QWEN_MODEL qwen3-max; Bean(name deepSeek) public ChatModel deepSeek(){ return DashScopeChatModel.builder() .dashScopeApi(DashScopeApi.builder().apiKey(System.getenv(aliQwen-api)).build()) .defaultOptions(DashScopeChatOptions.builder().withModel(DEEPSEEK_MODEL).build()) .build(); } Bean(name qwen) public ChatModel qwen(){ return DashScopeChatModel.builder() .dashScopeApi(DashScopeApi.builder().apiKey(System.getenv(aliQwen-api)).build()) .defaultOptions(DashScopeChatOptions.builder().withModel(QWEN_MODEL).build()) .build(); } Bean(name deepseekChatClient) public ChatClient deepseekChatClient(Qualifier(deepSeek) ChatModel deepSeek) { return ChatClient.builder(deepSeek) .defaultOptions(ChatOptions.builder() .model(DEEPSEEK_MODEL) .build()) .build(); } Bean(name qwenChatClient) public ChatClient qwenChatClient(Qualifier(qwen) ChatModel qwen) { return ChatClient.builder(qwen) .defaultOptions(ChatOptions.builder() .model(QWEN_MODEL) .build()) .build(); } }//V2 通过ChatClient实现stream实现流式输出 Resource(name deepseekChatClient) private ChatClient deepseekChatClient; Resource(name qwenChatClient) private ChatClient qwenChatClient; ......... GetMapping(value /stream/chatflux3) public FluxString chatflux3(RequestParam(name question,defaultValue 你是谁) String question) { return deepseekChatClient.prompt(question).stream().content(); } GetMapping(value /stream/chatflux4) public FluxString chatflux4(RequestParam(name question,defaultValue 你是谁) String question) { return qwenChatClient.prompt(question).stream().content(); }5.4 SSE前端页面前端效果:Flux是SpringWebFlux中的一个核心组件属于响应式编程模型的一部分。它主要用于处理异步、非阻塞的流式数据能够高效地处理高并发场景。Flux可以生成和处理一系列的事件或数据如流式输出等。看类注释和类所在的jar包我们就明白:SAA中的流式输出是通过ReactorStreams技术实现的和SpringWebFlux的底层实现是一样的技术。具体执行流程:ReactorStreams会订阅数据源当有数据时ReactorStreams以分块流的方式发送给客户端用户。前端代码:!DOCTYPE html html head titleSSE流式chat/title style body { font-family: Arial, sans-serif; background-color: #f4f4f4; margin: 0; padding: 20px; } #messageInput { width: 90%; padding: 10px; font-size: 16px; border: 1px solid #ccc; border-radius: 4px; margin-bottom: 10px; } button { padding: 10px 20px; font-size: 16px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; } button:hover { background-color: #0056b3; } #messages { margin-top: 20px; padding: 15px; background-color: #f9f9f9; border: 1px solid #ddd; border-radius: 8px; max-height: 300px; overflow-y: auto; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } #messages div { padding: 8px 0; border-bottom: 1px solid #eee; font-size: 14px; color: #333; } #messages div:last-child { border-bottom: none; } /style /head body textarea idmessageInput rows4 cols50 placeholder请输入你的问题.../textareabr button onclicksendMsg()发送提问/button div idmessages/div script function sendMsg() { // 获取用户输入的消息 const message document.getElementById(messageInput).value; if (message ) return false; //1 客户端使用 JavaScript 的 EventSource 对象连接到服务器上的一个特定端点URL const eventSource new EventSource(stream/chatflux2?question message); //2 监听消息事件 eventSource.onmessage function (event) { // 获取流式返回的数据 const data event.data; // 将接收到的数据展示到页面上 const messagesDiv document.getElementById(messages); messagesDiv.innerHTML event.data; }; //3 监听错误事件 eventSource.onerror function (error) { console.error(EventSource 发生错误, error); eventSource.close(); // 关闭连接 }; } /script /body /html该代码应该放置到: