Spring AI 实战系列(三):多模型共存+双版本流式输出

Spring AI 实战系列(三):多模型共存+双版本流式输出


一、系列回顾与本篇定位

1.1 系列回顾

  • 第一篇:完成了 Spring AI 与阿里云百炼的基础集成,基于ChatModel实现了同步对话、API Key安全注入,跑通了从0到1的Spring AI 开发。
  • 第二篇:解锁了ChatClient,实现了全局统一配置、一行代码完成大模型调用,告别了重复的样板代码。
系列栏目:Spring AI                      

Spring AI 实战教程(一)入门示例

Spring AI 实战系列(二):ChatClient封装,告别大模型开发样板代码

Spring AI 实战系列(三):多模型共存+双版本流式输出

Spring AI 实战系列(四):Prompt工程深度实战

Spring AI 实战系列(五):结构化输出,让大模型严格适配你的业务数据模型

Spring AI 实战系列(六):Tool Calling深度实战,让大模型自动调用你的业务接口

Spring AI实战系列(七):Chat Memory实战,基于Redis实现持久化多轮对话

Spring AI 实战系列(八):多模态能力—— 文生图、语音合成与向量嵌入实战

Spring AI 实战系列(九):RAG检索实战 —— 私有知识库

Spring AI 实战系列(十):MCP深度集成 —— 工具暴露与跨服务调用

1.2 本篇定位

本篇是系列进阶篇,解决开发中最常见的两个痛点:

  1. 多模型无缝切换与共存:一套 Spring Boot 项目同时对接DeepSeek、Qwen,根据业务场景动态选择模型,无需重复搭建环境。
  2. 双版本流式输出实现:分别用ChatModelChatClient实现流式响应,对比两者的开发体验与适用场景,给生产环境选型提供明确建议。

二、核心痛点拆解

2.1 多模型共存的必要性

现在大模型市场百花齐放,没有任何一个模型能覆盖所有业务场景:

  • DeepSeek-V3:推理速度快,适合高频、低复杂度的场景(如客服问答、代码补全提示)。
  • Qwen-Max:专业能力强、多模态支持完善,适合复杂推理、文档分析、多模态交互的场景(如技术方案生成、企业知识库问答)。

如果每个模型单独建一个项目,不仅维护成本高,还无法共享业务逻辑、数据库连接等资源。

2.2 流式输出的必要性

大模型生成长文本(如技术方案、小说、代码)时,同步调用需要等待几十秒甚至几分钟,用户体验极差。流式输出可以像打字机一样逐字 / 逐 Token 返回结果,大幅提升用户的交互体验。

三、实战落地:多模型共存 + 双版本流式输出

3.1 环境前提

  • 已完成 JDK 17+、Spring Boot 3.2.x 环境搭建
  • 已配置阿里云百炼 API Key 环境变量DASHSCOPE_API_KEY(注意:DeepSeek 现在也可以通过阿里云百炼的 API 调用,无需单独申请 DeepSeek 的 Key)
  • 已在pom.xml中引入spring-ai-alibaba-starter-dashscope核心依赖(参考第一篇)

3.2 第一步:多模型全局配置类

我们创建LLMConfig.java,同时注册DeepSeek和Qwen的ChatModelChatClient Bean,通过@Qualifier注解区分注入,避免 Bean 冲突。

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.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * Spring AI 多模型共存配置类 */ @Configuration public class LLMConfig { // 模型名称常量定义,统一管理,避免硬编码 private static final String DEEPSEEK_MODEL = "deepseek-v3"; private static final String QWEN_MODEL = "qwen-max"; // ==================== 1. ChatModel 原子API Bean 注册 ==================== /** * DeepSeek-V3 ChatModel 实例 * 通过阿里云百炼API调用,无需单独申请DeepSeek Key */ @Bean(name = "deepseek") public ChatModel deepSeekChatModel() { return DashScopeChatModel.builder() // 从系统环境变量读取API Key,避免硬编码泄露 .dashScopeApi(DashScopeApi.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .build()) // 全局默认模型参数,统一管理 .defaultOptions(DashScopeChatOptions.builder() .withModel(DEEPSEEK_MODEL) .withTemperature(0.7) .withMaxTokens(2000) .build()) .build(); } /** * Qwen-Max ChatModel 实例 */ @Bean(name = "qwen") public ChatModel qwenChatModel() { return DashScopeChatModel.builder() .dashScopeApi(DashScopeApi.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .build()) .defaultOptions(DashScopeChatOptions.builder() .withModel(QWEN_MODEL) .withTemperature(0.7) .withMaxTokens(2000) .build()) .build(); } // ==================== 2. ChatClient Fluent API Bean 注册 ==================== /** * DeepSeek-V3 ChatClient 实例 * 基于已注册的deepseek ChatModel构建 */ @Bean(name = "deepseekChatClient") public ChatClient deepseekChatClient(@Qualifier("deepseek") ChatModel deepseek) { return ChatClient.builder(deepseek) // 可选:全局默认系统提示词,所有调用都会自动携带 .defaultSystem("你是一个专业的AI助手,回答问题简洁、高效、有逻辑") .build(); } /** * Qwen-Max ChatClient 实例 * 基于已注册的qwen ChatModel构建 */ @Bean(name = "qwenChatClient") public ChatClient qwenChatClient(@Qualifier("qwen") ChatModel qwen) { return ChatClient.builder(qwen) .defaultSystem("你是一个专业的Java后端开发工程师,擅长Spring生态技术栈,回答问题专业、有可落地的代码示例") .build(); } }

关键说明

  1. 模型名称统一管理:用常量定义模型名称,避免硬编码分散在业务代码中,后续切换模型只需修改常量即可。
  2. API Key 复用:DeepSeek现在已接入阿里云百炼生态,无需单独申请 DeepSeek的API Key,直接复用DASHSCOPE_API_KEY即可。
  3. Bean 命名规范:通过@Bean(name = "xxx")@Qualifier("xxx")明确区分不同模型的 Bean,避免Spring容器的注入歧义。
  4. 全局默认配置分离:ChatModel的全局默认参数(模型版本、温度、最大Token数)和 ChatClient 的全局默认系统提示词分离,职责清晰。

3.3 第二步:双版本流式输出接口开发

我们创建StreamOutputController.java,分别用ChatModelChatClient实现DeepSeek和Qwen 的流式响应接口,直观对比两者的开发体验。

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; /** * Spring AI 多模型双版本流式输出接口 */ @RestController public class StreamOutputController { // ==================== 1. ChatModel 原子API 流式输出 ==================== @Resource(name = "deepseek") private ChatModel deepseekChatModel; @Resource(name = "qwen") private ChatModel qwenChatModel; /** * DeepSeek-V3 ChatModel 流式输出接口 */ @GetMapping(value = "/stream/chatflux1", produces = "text/html;charset=utf-8") public Flux<String> chatflux1(@RequestParam(name = "question", defaultValue = "你是谁") String question) { // ChatModel 原生流式调用,一行完成,但复杂场景需要手动组装提示词 return deepseekChatModel.stream(question); } /** * Qwen-Max ChatModel 流式输出接口 */ @GetMapping(value = "/stream/chatflux2", produces = "text/html;charset=utf-8") public Flux<String> chatflux2(@RequestParam(name = "question", defaultValue = "你是谁") String question) { return qwenChatModel.stream(question); } // ==================== 2. ChatClient Fluent API 流式输出 ==================== @Resource(name = "deepseekChatClient") private ChatClient deepseekChatClient; @Resource(name = "qwenChatClient") private ChatClient qwenChatClient; /** * DeepSeek-V3 ChatClient 流式输出接口 */ @GetMapping(value = "/stream/chatflux3", produces = "text/html;charset=utf-8") public Flux<String> chatflux3(@RequestParam(name = "question", defaultValue = "你是谁") String question) { // ChatClient 链式流式调用,一行完成,自动携带全局系统提示词 return deepseekChatClient.prompt(question).stream().content(); } /** * Qwen-Max ChatClient 流式输出接口 */ @GetMapping(value = "/stream/chatflux4", produces = "text/html;charset=utf-8") public Flux<String> chatflux4(@RequestParam(name = "question", defaultValue = "你是谁") String question) { return qwenChatClient.prompt(question).stream().content(); } }

关键说明

  1. produces 属性:必须设置produces = "text/html;charset=utf-8"produces = "text/plain;charset=utf-8",否则浏览器可能不会逐字显示,而是等待完整结果返回。
  2. ChatClient 简化调用:ChatClient 的prompt(question)prompt().user(question)的简写,代码更简洁;同时自动携带配置类中设置的全局系统提示词,无需每次调用手动拼接。
  3. 接口命名规范:接口名清晰区分模型(deepseek/qwen)和实现方式(chatflux1-2 是 ChatModel,chatflux3-4 是 ChatClient),便于测试和维护。

3.4 接口测试

启动项目后,在 Chrome 或 Edge 浏览器中访问以下接口,即可看到流式输出的效果:

  • DeepSeek ChatModelhttp://localhost:8003/stream/chatflux1?question=写一篇1000字的Java后端成长路线
  • Qwen-Max ChatClienthttp://localhost:8003/stream/chatflux4?question=用Spring Boot写一个完整的用户登录注册接口

四、ChatModel vs ChatClient 流式输出对比

对比维度ChatModel 原子 APIChatClient Fluent API
代码量简单场景一行完成,复杂场景需要手动组装提示词、处理消息结构所有场景一行完成,自动携带全局配置,无冗余代码
全局配置仅支持模型参数的全局配置,系统提示词需每次调用手动拼接支持模型参数、系统提示词、函数定义、Advisor 切面的全局统一配置
开发体验底层灵活,但需要处理大量样板代码高层封装,链式调用,开发效率高,代码可读性强
适用场景需要极致底层灵活性的场景(如自定义消息结构、手动处理流式元数据)绝大多数企业级业务场景(如客服问答、技术方案生成、知识库问答)

五、实践建议

  1. 多模型动态路由不要在 Controller 中硬编码注入不同的 ChatModel/ChatClient,而是通过配置文件或数据库动态选择模型,根据业务场景(如用户等级、问题复杂度)自动切换。
  2. 流式输出的异常处理流式输出过程中可能出现网络中断、模型限流等异常,需要通过Flux.onErrorResume()等方法统一处理,给用户友好的提示。
  3. Token 消耗统计生产环境中需要统计每个模型的 Token 消耗,用于成本核算与监控。ChatClient 可以通过stream().chatResponse()获取完整的响应元数据,包括 Token 使用量。
  4. 提示词模板外部化复杂的系统提示词不要硬编码在 Java 代码中,放到application.yml配置文件或独立的资源文件中,通过@ValueResource注入,便于产品与运营同学修改优化。

六、避坑指南

  1. 坑点 1:流式输出浏览器不逐字显示必须在Controller 的@GetMapping注解中设置produces = "text/html;charset=utf-8"produces = "text/plain;charset=utf-8",否则浏览器会等待完整结果返回。
  2. 坑点 2:多模型Bean注入歧义若项目中存在多个 ChatModel/ChatClient 实例,必须通过@Bean(name = "xxx")@Qualifier("xxx")明确区分注入,否则 Spring 会抛出NoUniqueBeanDefinitionException异常。
  3. 坑点 3:环境变量 API Key 读取失败System.getenv()读取的是系统环境变量,IDE 本地运行时,需要在启动配置的Environment variables 中添加DASHSCOPE_API_KEY,否则会出现 API Key 为空的错误。
  4. 坑点 4:DeepSeek 模型名称错误通过阿里云百炼调用DeepSeek时,模型名称必须是deepseek-v3deepseek-chat,不能直接写deepseek,否则会出现模型不存在的错误。

七、本篇总结

本篇我们完成了Spring AI多模型共存与双版本流式输出的实战落地:

  • 基于阿里云百炼生态,一套代码同时对接了 DeepSeek-V3和Qwen-Max 两个主流大模型,无需重复搭建环境。
  • 分别用ChatModelChatClient实现了流式响应,对比了两者的开发体验与适用场景。

八、下篇预告

本篇我们掌握了多模型共存与流式输出的核心能力,实现了从基础demo到工程化开发的进一步升级。在本系列的下一篇中,将深度拆解Spring AI Prompt工程全体系,从底层结构到模板化动态生成,带你彻底掌握驾驭大模型的核心能力。

传送门:Spring AI 实战系列(四):Prompt工程深度实战

如果本文对你有帮助,欢迎点赞、收藏、评论,跟着系列教程一步步完成Spring AI应用。

Read more

腾讯版“小龙虾“WorkBuddy一键部署教程:AI办公智能体即刻上手

🚀 腾讯版"小龙虾"WorkBuddy一键部署教程:AI办公智能体即刻上手 作者:[您的ZEEKLOG用户名] 更新时间:2026年3月10日 关键词:腾讯云 WorkBuddy AI智能体 一键部署 办公自动化 📖 前言:什么是WorkBuddy? 最近AI领域最火的话题之一就是"小龙虾"(OpenClaw),而腾讯云刚刚推出了自己的桌面AI智能体——WorkBuddy。相比于其他需要复杂部署的AI工具,WorkBuddy主打零部署、一键安装、1分钟配置,真正做到了"开箱即用"。 WorkBuddy的核心优势: * ✅ 完全兼容OpenClaw技能(Skills) * ✅ 无需复杂部署,下载即用 * ✅ 支持企业微信、QQ、飞书、钉钉集成 * ✅ 内置20+技能包,支持无限扩展 * ✅ 多窗口、多Agent并行工作 📥 第一步:下载安装WorkBuddy(1分钟搞定)

脉脉独家【AI创作者xAMA】| 多维价值与深远影响

脉脉独家【AI创作者xAMA】| 多维价值与深远影响

🔥草莓熊Lotso:个人主页 ❄️个人专栏: 《C++知识分享》《Linux 入门到实践:零基础也能懂》 ✨生活是默默的坚持,毅力是永久的享受! 🎬 博主简介: 文章目录 * 前言: * 一、对AI创作者的个人价值 * (一)提升技术能力与创作水平 * (二)建立个人影响力与品牌 * (三)拓展职业可能性与收入来源 * 二、对AI创作行业的推动作用 * (一)促进技术创新与发展 * (二)规范行业秩序与标准 * (三)推动行业商业化与产业化 * 三、对社会的价值与意义 * (一)促进知识传播与共享 * (二)推动社会创新与发展 * (三)缓解就业压力与促进就业 * 结尾: 前言: 在AI技术飞速发展的当下,AI创作领域正迎来前所未有的机遇与挑战。脉脉平台推出的【AI创作者xAMA】活动,不仅为AI创作者提供了一个学习、交流和成长的平台,还对整个AI创作行业乃至社会产生了深远的影响。 一、对AI创作者的个人价值 (一)

【AI】2026年AI学习路线(从入门到精通)重点版

一、2026年AI学习知识图谱(从入门到精通) (一)入门阶段(0-6个月):建立认知,夯实基础 核心目标:掌握AI基础概念、必备数学与编程能力,能实现简单机器学习模型,建立系统的AI认知框架。 核心内容: * AI通识:AI发展史、核心概念、主要学派、经典案例,了解2026年AI前沿趋势(如多模态、具身智能)。 * 数学基础:微积分、线性代数、概率论与统计、优化理论,掌握AI算法所需的数学工具。 * 编程基础:Python核心语法、数据结构与算法、CUDA基础,能熟练使用Python处理数据、编写简单代码。 * 传统机器学习入门:监督/无监督学习基础、线性回归、决策树、模型评估方法,入门Scikit-learn工具。 * 基础实践:完成鸢尾花分类、房价预测等简单项目,参与Kaggle入门赛,积累基础实战经验。 (二)进阶阶段(6-12个月):掌握核心算法,

2025年终总结,这就是AI的时代

2025年终总结,这就是AI的时代

今天是2025年的最后一天,又到了写年终总结的时候了。 我先去翻看了前几年的年终总结,突然发现,在过去的几年里,每年的年终总结我都写得比较消极。 可能这就是我这几年的个人体感,和当下整体大环境的趋势也许是比较相符的。 那么今年还继续消极吗?是的,从大环境方面来看,今年我更加消极了。但是我决定,今年的年终总结文章我要写得积极一点,至少在今天,我们一起都乐观向上一下。 还是先来说说公众号吧。 在去年的年终总结中,我宣布了公众号运营策略将进行重大调整。从25年开始,本公众号只会发布我的原创文章,不再接收其他技术文章的投稿。 这当然不是我主动想要进行的调整,主要还是因为有投稿意愿的作者,或者说还在写Android类技术文章的作者越来越少了,我实在没有办法再像往常那样维持日更的节奏。所以说,这也是一个在当下大环境的趋势下,不得不进行的一个调整。 不过,虽然公众号无法做到技术文章日更了,广告商的需求还是有的。只不过现在找我的基本没有Android类的广告商了,全都是和AI相关的。 我并不想让我的公众号变成一个广告性质过强的公众号,所以我把绝大部分找我的广告商全都拒绝了,只保留了极少