引言
在当下 AI 应用开发中,经常会遇到需要同时接入多个大模型的场景,比如用通义千问做中文对话、用 DeepSeek 处理复杂推理。Spring AI Alibaba 为我们屏蔽了不同厂商 API 的差异,但多模型并存也带来了 ChatModel Bean 冲突、流式输出适配等问题。本文将带你从零搭建一个 Spring Boot 项目,优雅地解决“多模型共存 + SSE 流式输出”两大核心需求,并给出两种主流接入方案。
一、核心知识点前置
在动手写代码之前,先梳理几个关键概念:
多模型共存当容器中存在多个 ChatModel Bean 时,必须手动指定 Bean 名称,然后使用 @Resource(name="xxx") 按名称精准注入,放弃 Spring Boot 的自动装配。
SSE (Server-Sent Events)一种基于 HTTP 的单向长连接协议,天然适合大模型“打字机”效果的流式返回。浏览器端直接监听接口,源源不断地接收文本片段,无需 WebSocket 的复杂握手。
两条实现路线
- 底层 API:ChatModel.stream(String prompt) 直接返回 Flux<String>
- 上层封装:ChatClient.prompt().stream().content() 更贴近业务语义,支持系统提示词、对话历史等
响应式环境要求返回类型 Flux<String> 属于响应式流,控制器必须运行在 WebFlux 环境下(引入 spring-boot-starter-webflux),普通 Spring MVC 无法推送流数据。
二、整体方案设计
我们面对两种典型的集成模式,区别在于引入的 Starter 数量:
-
方案 A:只引入百炼一个 StarterMaven 中仅添加 spring-ai-alibaba-starter-dashscope。所有模型(包括通义千问、DeepSeek 等)都通过阿里云百炼平台调用,只需在代码中通过 model 参数切换。适合企业统一管控、希望在阿里云上集中管理模型调用。
-
方案 B:同时引入百炼和 DeepSeek 两个 StarterMaven 中同时添加 spring-ai-alibaba-starter-dashscope 和 spring-ai-deepseek-starter。通义千问走百炼,DeepSeek 直连 DeepSeek 官网,两套独立的配置和连接,适合需要发挥各厂商原生能力的场景。
本文会同时覆盖这两种方案,你可以根据实际情况选其一。
三、Maven 依赖配置
方案 A:仅引入百炼 Starter(一个 Starter)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId></dependency><dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-alibaba-starter-dashscope</artifactId></dependency>
方案 B:引入百炼 + DeepSeek 两个 Starter
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId></dependency><dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-alibaba-starter-dashscope</artifactId></dependency><dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-deepseek-starter</artifactId></dependency>
四、application.yml 配置
方案 A(仅百炼)
spring: ai: dashscope: api-key: sk-xxxx chat: options: model: qwen-turbo # 默认模型,实际调用时可覆盖
方案 B(双 key)
spring: ai: dashscope: api-key: sk-xxxx chat: options: model: qwen-turbo deepseek: api-key: sk-xxxx chat: options: model: deepseek-chat
五、配置类:手工打造多模型 Bean
这是本文最关键的一步。为避免自动装配的混乱,我们完全手动创建所有 Bean 并命名。
5.1 方案 A:一个 Starter 多模型 Bean(均走百炼)
@Configurationpublic class DashScopeMultiModelConfig { @Bean public DashScopeApi dashScopeApi(DashScopeProperties properties) { return DashScopeApi.builder() .apiKey(properties.getApiKey()) .build(); } // Qwen 模型 Bean @Bean("qwen") public ChatModel qwenChatModel(DashScopeApi api) { DashScopeChatOptions options = DashScopeChatOptions.builder() .withModel("qwen-turbo") .build(); return new DashScopeChatModel(api, options); } // 通过百炼调用的 DeepSeek 模型(需在百炼平台开通) @Bean("deepseek-via-bailian") public ChatModel deepSeekViaBailianModel(DashScopeApi api) { DashScopeChatOptions options = DashScopeChatOptions.builder() .withModel("deepseek-chat") .build(); return new DashScopeChatModel(api, options); } @Bean("qwenChatClient") public ChatClient qwenChatClient(@Qualifier("qwen") ChatModel chatModel) { return ChatClient.builder(chatModel) .defaultSystem("你是通义千问,简洁回答问题") .build(); } @Bean("deepseekBailianChatClient") public ChatClient deepSeekBailianClient(@Qualifier("deepseek-via-bailian") ChatModel chatModel) { return ChatClient.builder(chatModel) .defaultSystem("你是 DeepSeek 大模型") .build(); }}
5.2 方案 B:两个 Starter 独立 Bean(百炼 + DeepSeek 直连)
@Configurationpublic class MultiLLMConfig { // ================= 通义千问(百炼) ================= @Bean public DashScopeApi dashScopeApi(DashScopeProperties props) { return DashScopeApi.builder() .apiKey(props.getApiKey()) .build(); } @Bean("qwen") public ChatModel qwenChatModel(DashScopeApi api, DashScopeProperties props) { return new DashScopeChatModel(api, props.getChat().getOptions()); } @Bean("qwenChatClient") public ChatClient qwenChatClient(@Qualifier("qwen") ChatModel chatModel) { return ChatClient.builder(chatModel) .defaultSystem("你是通义千问,简洁回答问题") .build(); } // ================= DeepSeek 直连 ================= @Bean public DeepSeekApi deepSeekApi(DeepSeekProperties props) { return DeepSeekApi.builder() .apiKey(props.getApiKey()) .build(); } @Bean("deepseek") public ChatModel deepseekChatModel(DeepSeekApi api, DeepSeekProperties props) { return new DeepSeekChatModel(api, props.getChat().getOptions()); } @Bean("deepseekChatClient") public ChatClient deepseekChatClient(@Qualifier("deepseek") ChatModel chatModel) { return ChatClient.builder(chatModel) .defaultSystem("你是 DeepSeek 大模型") .build(); }}
六、Controller:SSE 流式输出实战
下面展示基于方案 B 的四种典型接口,方案 A 只需要把注入的 Bean 名替换为对应的百炼 Bean 即可。
@RestControllerpublic class StreamController { @Resource(name = "deepseek") private ChatModel deepseekChatModel; @Resource(name = "qwen") private ChatModel qwenChatModel; @Resource(name = "deepseekChatClient") private ChatClient deepseekChatClient; @Resource(name = "qwenChatClient") private ChatClient qwenChatClient; // 1. DeepSeek ChatModel 流式 SSE @GetMapping(value = "/stream/deepseek-model", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<String> streamDeepSeekModel(@RequestParam(defaultValue = "你是谁") String question) { return deepseekChatModel.stream(question); } // 2. Qwen ChatModel 流式 SSE @GetMapping(value = "/stream/qwen-model", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<String> streamQwenModel(@RequestParam(defaultValue = "你是谁") String question) { return qwenChatModel.stream(question); } // 3. DeepSeek ChatClient 流式 SSE @GetMapping(value = "/stream/deepseek-client", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<String> streamDeepSeekClient(@RequestParam(defaultValue = "你是谁") String question) { return deepseekChatClient.prompt(question) .stream() .content(); } // 4. Qwen ChatClient 流式 SSE @GetMapping(value = "/stream/qwen-client", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<String> streamQwenClient(@RequestParam(defaultValue = "你是谁") String question) { return qwenChatClient.prompt(question) .stream() .content(); }}
关键点解析:
- produces = MediaType.TEXT_EVENT_STREAM_VALUE 告知浏览器这是 SSE 流。
- Flux<String> 使得每个文本片段都能被及时推送。
- ChatClient.prompt().stream().content() 是最推荐的方式,可轻松添加系统消息、历史消息等。
七、前端对接示例(原生 JS)
const eventSource = new EventSource('/stream/deepseek-client?question=你好');eventSource.onmessage = (event) => { document.getElementById('output').innerText += event.data;};eventSource.onerror = (err) => { eventSource.close();};
八、方案对比与选型建议
| 引入的依赖 | 仅 spring-ai-alibaba-starter-dashscope | spring-ai-alibaba-starter-dashscope + spring-ai-deepseek-starter |
| 配置复杂度 | 需手动指定每个模型的 model 名称 | 各 Starter 独立配置 api-key 和参数 |
| 厂商特性支持 | 依赖百炼封装,可能丢失细节 | 可调用各厂商原生参数和特性 |
| 适用场景 | 统一管控、企业级项目 | 需要利用厂商差异化能力 |
如果项目只需要简单对话,方案 A 更简洁;若需深度定制 prompt 参数、访问 DeepSeek 特有功能,方案 B 更灵活。
九、总结
本文完整演示了 Spring AI Alibaba 在多模型共存环境下的整合思路,重点解决了 Bean 冲突和 SSE 流式输出的落地细节。核心要点回顾:
- 手动创建并命名 ChatModel Bean,杜绝自动装配;
- 在 WebFlux 环境下使用 Flux<String> + text/event-stream 实现 SSE;
- 根据需求选择“一个百炼 Starter”统一接入,或“百炼 + DeepSeek 两个 Starter”混合接入。
掌握了这套方法,无论是同时接入 2 个还是 10 个大模型,都能轻松驾驭。希望这篇文章能帮助你在 AI 应用开发中少走弯路,快速搭建出健壮的流式对话服务。




