Spring AI Alibaba 多模型共存 + SSE 流式输出实战:通义千问 & DeepSeek 双模型集成
引言
在当下 AI 应用开发中,经常会遇到需要同时接入多个大模型的场景,比如用通义千问做中文对话、用 DeepSeek 处理复杂推理。Spring AI Alibaba 为我们屏蔽了不同厂商 API 的差异,但多模型并存也带来了 ChatModel Bean 冲突、流式输出适配等问题。本文将带你从零搭建一个 Spring Boot 项目,优雅地解决“多模型共存 + SSE 流式输出”两大核心需求,并给出两种主流接入方案。
一、核心知识点前置
在动手写代码之前,先梳理几个关键概念:
-
多模型共存
当容器中存在多个ChatModelBean 时,必须手动指定 Bean 名称,然后使用@Resource(name="xxx")按名称精准注入,放弃 Spring Boot 的自动装配。 -
SSE (Server-Sent Events)
一种基于 HTTP 的单向长连接协议,天然适合大模型“打字机”效果的流式返回。浏览器端直接监听接口,源源不断地接收文本片段,无需 WebSocket 的复杂握手。 -
两条实现路线
- 底层 API:
ChatModel.stream(String prompt)直接返回Flux<String> - 上层封装:
ChatClient.prompt().stream().content()更贴近业务语义,支持系统提示词、对话历史等
- 底层 API:
-
响应式环境要求
返回类型Flux<String>属于响应式流,控制器必须运行在 WebFlux 环境下(引入spring-boot-starter-webflux),普通 Spring MVC 无法推送流数据。
二、整体方案设计
我们面对两种典型的集成模式,区别在于引入的 Starter 数量:
-
方案 A:只引入百炼一个 Starter
Maven 中仅添加spring-ai-alibaba-starter-dashscope。所有模型(包括通义千问、DeepSeek 等)都通过阿里云百炼平台调用,只需在代码中通过model参数切换。适合企业统一管控、希望在阿里云上集中管理模型调用。 -
方案 B:同时引入百炼和 DeepSeek 两个 Starter
Maven 中同时添加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(均走百炼)
@Configuration
public 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 直连)
@Configuration
public 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 即可。
@RestController
public 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();
};
八、方案对比与选型建议
| 对比维度 | 方案 A(仅百炼 Starter) | 方案 B(百炼 + DeepSeek 两个 Starter) |
|---|---|---|
| 引入的依赖 | 仅 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 流式输出的落地细节。核心要点回顾:
- 手动创建并命名
ChatModelBean,杜绝自动装配; - 在 WebFlux 环境下使用
Flux<String>+text/event-stream实现 SSE; - 根据需求选择“一个百炼 Starter”统一接入,或“百炼 + DeepSeek 两个 Starter”混合接入。
掌握了这套方法,无论是同时接入 2 个还是 10 个大模型,都能轻松驾驭。希望这篇文章能帮助你在 AI 应用开发中少走弯路,快速搭建出健壮的流式对话服务。
更多推荐
所有评论(0)