Spring AI 多模型集成实战:OpenAI/DeepSeek/通义千问/智谱一套代码全搞定
Spring AI 多模型集成实战:OpenAI/DeepSeek/通义千问/智谱一套代码全搞定
🌐 演示地址:http://ruoyioffice.com | 📦 源码1·GitHub:ruoyi-office | 📦 源码2·GitCode:ruoyi-office | 📦 源码3·Gitee:ruoyi-office | 💬 微信:17156169080(备注「RuoYi Office」)
业务方今天要 DeepSeek、明天要通义千问、后天又说"内网不能出公网,得换成本地 Ollama"。如果你的代码里到处是
new OpenAiChatModel(...),每换一家模型就要改代码、发版、重测一遍。企业级 AI 集成真正的难点,从来不是"调通一个模型",而是"让十几家模型在同一套代码里随时热切换、各租户用各自的 Key、参数可配、调用方零感知"。 本文以 RuoYi Office 的 AI 模块为样本,从两张配置表讲起,拆解如何用 Spring AI + 工厂模式把 OpenAI、DeepSeek、通义千问、智谱、文心、豆包、Ollama 等模型统一收口。
▲ 多模型集成能力地图:① 配置层(ai_api_key 存密钥 + ai_model 存模型参数)→ ② 工厂层(AiModelFactory 按平台 switch 构建 Spring AI ChatModel/EmbeddingModel/ImageModel 并缓存)→ ③ 能力层(对话/写作/知识库/绘图统一调用)→ ④ 平台矩阵(17 家大模型平台,国内国外多媒体全覆盖)
引言:企业接大模型,到底难在哪?
每个想给系统加 AI 能力的团队,几乎都会在"接模型"这步反复返工:
难点一:模型 SDK 各不相同。OpenAI、通义 DashScope、智谱、文心 QianFan、豆包,每家的 SDK 类名、鉴权方式、参数结构都不一样,硬编码一家,换家就重写。
难点二:Key 和参数写死在配置文件。application.yml 里写死一个 api-key,多租户场景下没法让 A 公司用自己的 Key、B 公司用另一个 Key;改个温度值还得重启。
难点三:换模型要发版。运营想把对话模型从 GPT 换成 DeepSeek 省成本,结果是一次开发改代码 + 测试 + 上线的完整流程,业务等不起。
难点四:本地与云端混用难。涉密场景要求模型部署在内网(Ollama / 本地推理),公开场景又想用云端大模型,两套逻辑各写一遍。
| 现状 | 后果 |
|---|---|
| 每家模型 SDK 硬编码 | 换模型 = 改代码 + 发版 |
| Key/参数写死配置文件 | 多租户无法各用各的 Key |
| 模型不可运营配置 | 调温度、换模型都要重启 |
| 云端/本地两套逻辑 | 维护成本翻倍,易出错 |
RuoYi Office 的解法是把"模型"做成可运营配置的数据:用 ai_api_key + ai_model 两张表存配置,用 AiModelFactory 工厂按平台动态构建 Spring AI 的标准 ChatModel,调用方只认 modelId、完全不感知底层是哪家。下面从设计讲起。
一、整体设计:把"模型"变成一行可配置的数据
Spring AI 是 Spring 官方推出的 AI 应用开发框架,它把不同厂商的大模型抽象成统一接口:ChatModel(对话)、EmbeddingModel(向量化)、ImageModel(绘图)、VectorStore(向量库)。只要拿到一个 ChatModel 实例,上层调用就完全一致,不用关心背后是 OpenAI 还是 DeepSeek。
RuoYi Office 在 Spring AI 之上再封一层"配置 + 工厂",把模型集成拆成清晰的三层:
| 层 | 职责 | 关键类/表 |
|---|---|---|
| 配置层 | 存密钥、存模型参数,可在管理后台增删改 | ai_api_key、ai_model |
| 工厂层 | 按平台动态构建并缓存 Spring AI 模型实例 | AiModelFactory |
| 能力层 | 对话/写作/知识库/绘图统一调用,只认 modelId | AiModelService 等 |
一句话概括设计哲学:「密钥和参数交给数据库、对象构建交给工厂、能力调用交给 Spring AI 标准接口」。三层解耦后,新增一家模型只需在枚举里加一项、在工厂里加一个分支,上层业务零改动。
Spring AI 即 Spring 官方的 AI 集成框架,用于在 Spring Boot 应用中以统一接口调用各家大模型,例如把"对话"统一抽象成
ChatModel.call(Prompt),屏蔽厂商差异。
二、配置层:两张表撑起多模型
2.1 ai_api_key:密钥与平台
第一张表 ai_api_key 只管"用哪个平台、哪把钥匙、连哪个地址"。把密钥单独拆一张表,是为了让多个模型复用同一把 Key(比如通义的对话模型和向量模型共用一个 DashScope Key):
@TableName("ai_api_key")
@Data
public class AiApiKeyDO extends BaseDO {
@TableId
private Long id;
private String name; // 名称,便于运营辨认
private String apiKey; // 密钥
private String platform; // 平台,对应 AiPlatformEnum
private String url; // API 地址(可空,用默认;自建/代理时填)
private Integer status; // 状态:开启/关闭
}

2.2 ai_model:模型与调用参数
第二张表 ai_model 描述"具体哪个模型 + 调用参数"。一把 Key 下可以挂多个模型(如 deepseek-chat 和 deepseek-reasoner),对话类参数(温度、最大 token、上下文条数)也存在这里,运营随时可调:
@TableName("ai_model")
@Data
public class AiModelDO extends BaseDO {
@TableId
private Long id;
private Long keyId; // 关联 ai_api_key
private String name; // 模型名称
private String model; // 模型标识,如 deepseek-chat、qwen-max
private String platform; // 平台
private Integer type; // 类型:对话/向量/图片/语音…(AiModelTypeEnum)
private Integer sort;
private Integer status;
// 对话参数:调用时透传给大模型
private Double temperature; // 温度:越高越发散
private Integer maxTokens; // 单次回复最大 token
private Integer maxContexts; // 携带的历史消息条数
}

为什么拆成两张表而不是一张? 因为"密钥"和"模型"是一对多关系——一把 DashScope Key 下有 qwen-max、qwen-plus、text-embedding-v3 多个模型,密钥轮换时只改一行,所有挂在它下面的模型自动生效。这是典型的"配置归一化"。
三、平台枚举:一张表看懂支持哪些模型
所有支持的平台集中在 AiPlatformEnum 一个枚举里,新增厂商先在这里登记。RuoYi Office 当前覆盖国内主流、国外主流与多媒体三类共 17 个平台:
| 分类 | 平台(platform 值) | 说明 |
|---|---|---|
| 国内对话 | TongYi / YiYan / DeepSeek / ZhiPu / XingHuo / DouBao / HunYuan | 通义千问 / 文心 / DeepSeek / 智谱 / 星火 / 豆包 / 混元 |
| 国内对话 | SiliconFlow / MiniMax / Moonshot / BaiChuan | 硅基流动 / MiniMax / 月之暗面 / 百川 |
| 国外对话 | OpenAI / AzureOpenAI / Anthropic / Gemini / Grok | GPT / Azure / Claude / Gemini / Grok |
| 本地 | Ollama | 内网私有化部署,数据不出网 |
| 多媒体 | StableDiffusion / Midjourney / Suno | 绘图 / 绘图 / 音乐 |
模型类型用另一个枚举 AiModelTypeEnum 区分:CHAT(1) 对话、IMAGE(2) 绘图、VOICE(3) 语音、VIDEO(4) 视频、EMBEDDING(5) 向量、RERANK(6) 重排。同一个平台可以同时提供对话和向量模型,靠 type 区分用途。
关键设计:平台与类型用枚举集中管理,意味着"支持哪些模型"是一份可枚举、可校验的清单,而不是散落在各处的 if-else,这对后续维护和排错极其友好。

▲ AI 对话页面:右上角可随时切换底层模型(通义千问 / DeepSeek / 智谱等),温度、上下文条数等参数随模型配置生效,调用方完全无感。
四、工厂层:AiModelFactory 是整套设计的心脏
这是整个多模型集成最关键的一环。AiModelFactory 是一个工厂接口,负责把"平台 + Key + URL"翻译成 Spring AI 的标准模型对象。它的接口定义就是一份"能造什么"的清单:
public interface AiModelFactory {
// 按平台 + 密钥构建对话模型(核心)
ChatModel getOrCreateChatModel(AiPlatformEnum platform, String apiKey, String url);
// 向量模型,用于知识库 Embedding
EmbeddingModel getOrCreateEmbeddingModel(AiPlatformEnum platform, String apiKey, String url, String model);
// 绘图模型
ImageModel getOrCreateImageModel(AiPlatformEnum platform, String apiKey, String url);
// 向量库(知识库 RAG 用)
VectorStore getOrCreateVectorStore(Class<? extends VectorStore> type,
EmbeddingModel embeddingModel, Map<String, Class<?>> metadataFields);
}
4.1 按平台 switch,构建对应 ChatModel
实现类 getOrCreateChatModel 的核心就是一个 switch:拿到平台枚举,分发到对应厂商的构建方法。每个 case 内部用对应的 Spring AI Starter 或自研包装类创建实例:
@Override
public ChatModel getOrCreateChatModel(AiPlatformEnum platform, String apiKey, String url) {
// 用「类型 + 平台 + Key + URL」拼出缓存 Key,命中则复用,避免重复创建
String cacheKey = buildClientCacheKey(ChatModel.class, platform, apiKey, url);
return Singleton.get(cacheKey, (Func0<ChatModel>) () -> {
switch (platform) {
case DEEP_SEEK: return buildDeepSeekChatModel(apiKey, url);
case TONG_YI: return buildTongYiChatModel(apiKey); // 阿里 DashScope
case ZHI_PU: return buildZhiPuChatModel(apiKey, url);
case YI_YAN: return buildYiYanChatModel(apiKey); // 文心 QianFan
case DOU_BAO: return buildDouBaoChatModel(apiKey);
case OPENAI: return buildOpenAiChatModel(apiKey, url);
case ANTHROPIC: return buildAnthropicChatModel(apiKey, url);
case OLLAMA: return buildOllamaChatModel(url); // 本地,无需 Key
// … 通义/星火/混元/硅基流动/MiniMax/月之暗面/百川/Gemini/Grok 等
default: throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
}
});
}
两个值得学习的设计点:
- 客户端缓存(
Singleton.get):同一组「平台 + Key + URL」只构建一次模型客户端,后续命中缓存直接复用。大模型客户端内部往往持有连接池、HTTP 客户端,频繁 new 既慢又浪费资源。 - 统一返回 Spring AI 的
ChatModel:无论底层是官方 Starter、阿里 DashScope SDK,还是自研的DouBaoChatModel、XingHuoChatModel包装类,对外都收敛成同一个接口,上层零差异。
4.2 业务入口:只认 modelId
上层业务从不直接碰工厂,而是通过 AiModelService.getChatModel(Long id)——传一个模型 ID,内部完成"查模型 → 查密钥 → 调工厂"的全过程:
@Override
public ChatModel getChatModel(Long id) {
// 1. 校验模型存在 + 校验密钥可用
AiModelDO model = validateModel(id);
AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId());
// 2. 委托工厂构建(命中缓存则复用)
return modelFactory.getOrCreateChatModel(
AiPlatformEnum.validatePlatform(apiKey.getPlatform()),
apiKey.getApiKey(), apiKey.getUrl());
}
这一步是"调用方零感知"的关键:对话、写作、知识库问答等所有能力,拿到的都是一个标准 ChatModel,它们根本不知道(也不需要知道)背后是 DeepSeek 还是通义。换模型?改一下会话/角色绑定的 modelId 即可,代码一行不动。
五、能力层:流式对话怎么调
拿到 ChatModel 后,发起一次对话就是 Spring AI 的标准用法。企业场景几乎都要"打字机"式流式输出,用 stream() 返回 Flux,配合 SSE 推给前端:
// 1. 构建消息:系统设定 + 用户输入
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage(systemPrompt)); // 角色设定
messages.add(new UserMessage(userInput)); // 用户问题
// 2. 构建参数:温度、最大 token 来自 ai_model 配置
ChatOptions options = AiUtils.buildChatOptions(platform, model.getModel(),
model.getTemperature(), model.getMaxTokens());
// 3. 流式调用,逐块返回
Flux<ChatResponse> stream = streamingChatModel.stream(new Prompt(messages, options));
return stream.map(chunk -> {
String text = chunk.getResult() != null ? chunk.getResult().getOutput().getText() : "";
return success(StrUtil.nullToDefault(text, "")); // 包成 CommonResult 推给前端
});
这段代码的价值在于:它对所有平台都成立。无论 streamingChatModel 背后是 GPT 还是通义,流式输出的写法完全一致——这正是 Spring AI 统一抽象带来的红利。
六、RuoYi Office 多模型设计的创新点
6.1 配置即模型,换模型不发版
模型平台、密钥、参数全部入库,管理后台增删改即时生效。运营把对话模型从 GPT 换成 DeepSeek,只是改一行数据,不动代码、不发版。
6.2 工厂 + 缓存,新增厂商成本极低
接入一家新模型,只需:枚举加一项、工厂加一个 case 分支。客户端按「平台+Key+URL」缓存,性能与资源都有保障。
6.3 多租户各用各的 Key
ai_api_key 继承平台基础能力支持租户隔离,A 公司用自己的通义 Key、B 公司用自己的 DeepSeek Key,互不串号,计费清晰。
6.4 云端与本地一视同仁
Ollama 作为本地平台与云端模型走同一套工厂逻辑。涉密数据走内网 Ollama、公开问答走云端大模型,由模型配置决定,代码无分叉。
七、数据结构
ai_api_key(密钥表)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| name | varchar | 密钥名称 |
| api_key | varchar | 密钥(敏感,建议加密存储) |
| platform | varchar | 平台标识,对应 AiPlatformEnum |
| url | varchar | API 地址,可空走默认 |
| status | tinyint | 状态:0 开启 / 1 关闭 |
ai_model(模型表)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| key_id | bigint | 关联 ai_api_key |
| name | varchar | 模型名称 |
| model | varchar | 模型标识,如 deepseek-chat |
| platform | varchar | 平台 |
| type | tinyint | 类型:1 对话 / 2 图片 / 5 向量… |
| temperature | double | 温度 |
| max_tokens | int | 单次最大 token |
| max_contexts | int | 携带历史消息条数 |
| status | tinyint | 状态 |
设计要点:
- 密钥与模型 1:N:一把 Key 挂多个模型,密钥轮换改一行;
- 参数随模型存:温度/上下文条数等可运营调,不写死配置;
- 继承 BaseDO:自带 creator/create_time/tenant_id,天然多租户隔离。
八、技术亮点总结
| 设计要点 | 实现方式 | 价值 |
|---|---|---|
| 配置入库 | ai_api_key + ai_model 两表 | 换模型不发版,运营可自助 |
| 统一抽象 | Spring AI ChatModel/EmbeddingModel | 上层调用零差异 |
| 工厂构建 | AiModelFactory 按平台 switch | 新增厂商只加一个分支 |
| 客户端缓存 | Singleton 按 平台+Key+URL 缓存 | 复用连接,省资源 |
| 流式输出 | ChatModel.stream → Flux + SSE | 打字机式体验,全平台一致 |
| 多租户 | 密钥按租户隔离 | 各用各的 Key,计费清晰 |
| 本地/云端 | Ollama 与云端同走工厂 | 涉密内网与公网统一逻辑 |
九、快速体验
在线演示:http://ruoyioffice.com/web/(账号 admin / admin123),进入「AI 大模型 → 模型配置 / AI 对话」。
推荐体验流程:
- 进入「AI 大模型 → API 密钥」,新增一个平台密钥(如 DeepSeek / 通义千问);
- 进入「模型管理」,在该密钥下新增一个对话模型,设置温度、最大 token;
- 打开「AI 对话」,新建会话并选中刚配置的模型,发起一次提问;
- 切换到另一个平台的模型,发同样的问题,对比效果与速度;
- 体会"换模型只是切配置、代码零改动"的设计价值。
源码仓库:
| 仓库 | 地址 |
|---|---|
| GitHub | github.com/yuqing2026/ruoyi-office |
| GitCode | gitcode.com/zhouzhongyan/ruoyi-office |
| Gitee | gitee.com/yqzy1688/ruoyi-office |
结语
多模型集成的精髓,不在于"会调某一家模型的 API",而在于把密钥、模型、能力三件事彻底解耦:密钥与参数交给数据库、对象构建交给工厂、能力调用交给 Spring AI 标准接口。一旦解耦,“接大模型"就从"绑死一家厂商的工程"变成了"配一条数据的运营动作”。这套「配置即模型 + 工厂动态构建」的思路,同样适用于支付渠道、短信通道、对象存储等任何"多厂商、需热切换"的集成场景。
你们的系统接 AI 时是怎么管理多模型的?是写死一家,还是也做了可配置切换?欢迎在评论区聊聊。
常见问题(FAQ)
RuoYi Office 支持哪些大模型?可以体验吗?
支持。后端基于 Spring Boot 3.5 + Spring AI,已接入通义千问、文心一言、DeepSeek、智谱、星火、豆包、混元、硅基流动、MiniMax、月之暗面、百川,以及 OpenAI、Azure、Anthropic、Gemini、Grok 与本地 Ollama 等共 17 个平台,提供在线演示与本地部署体验;完整能力与持续维护由商业版提供。
怎么从 GPT 换成 DeepSeek?要改代码吗?
不用改代码。在管理后台「API 密钥」里新增 DeepSeek 密钥、「模型管理」里新增对应模型,然后把会话或角色绑定的模型改成 DeepSeek 即可,底层由 AiModelFactory 自动构建,上层无感。
数据不能出公网怎么办?
用本地模型。把平台选为 Ollama,填内网推理服务地址,对话/向量化全部在内网完成,数据不出网;它与云端模型走同一套工厂逻辑,业务代码不分叉。
多个租户能各用各的模型密钥吗?
可以。密钥表按租户隔离,每个租户配置自己的平台 Key 与模型,互不影响,计费与额度也各自独立。
Spring AI 和直接调各家 SDK 有什么区别?
Spring AI 把各厂商模型抽象成统一的 ChatModel/EmbeddingModel/ImageModel 接口,屏蔽了 SDK 差异。配合工厂模式,新增一家厂商只需加一个枚举与一个构建分支,上层"对话/写作/知识库"代码完全不用动。
💡 想要体验 RuoYi Office 的强大功能?
🌐 在线演示:http://ruoyioffice.com/web/(账号 admin / admin123)
📦 源码仓库:GitHub | GitCode | Gitee
💬 技术咨询:添加微信 17156169080,备注「RuoYi Office」
⭐ 如果觉得不错,请给个 Star 支持一下!

更多推荐


所有评论(0)