欢迎光临
我们一直在努力

Spring AI Alibaba 多模型共存 + SSE 流式输出实战:通义千问 & DeepSeek 双模型集成

引言

在当下 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();};


    八、方案对比与选型建议

    对比维度方案 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 流式输出的落地细节。核心要点回顾:

    • 手动创建并命名 ChatModel Bean,杜绝自动装配;
    • 在 WebFlux 环境下使用 Flux<String> + text/event-stream 实现 SSE;
    • 根据需求选择“一个百炼 Starter”统一接入,或“百炼 + DeepSeek 两个 Starter”混合接入。

    掌握了这套方法,无论是同时接入 2 个还是 10 个大模型,都能轻松驾驭。希望这篇文章能帮助你在 AI 应用开发中少走弯路,快速搭建出健壮的流式对话服务。

    赞(0)
    未经允许不得转载:171主机测评 » Spring AI Alibaba 多模型共存 + SSE 流式输出实战:通义千问 & DeepSeek 双模型集成
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址