
1. 为什么 Spring AI 多模型适配总在配置阶段翻车Spring AI 把不同厂商的大模型 API 抽象成统一的ChatModel接口这件事本身很优雅。但真正落到 Java 后端项目里同时接入通义千问和 DeepSeek 时问题往往不在业务代码而在配置层两个模型走的是不同的 starter、不同的 base-url、不同的参数命名甚至同一个ChatModel类型在容器里出现多个 Bean 时Spring 直接抛No qualifying bean。我见过太多项目卡在这一步——单接一个模型跑得挺好一旦要通义千问 DeepSeek 双活application.yml就开始打架Autowired不知道注入哪个切换模型要改代码重新打包。这跟 Spring AI 想表达的改一行配置就换模型完全背道而驰。这篇面向需要多模型切换的 Java 后端场景目标很明确给你一套可复制的application.yml与ChatModel骨架配置通过统一 Key/API 通道 TaoToken 完成双模型调用与切换验证一次配置跑通两个模型。适合正在做 AI 应用、需要成本与质量动态权衡、又不想为每个厂商维护一套对接代码的后端同学。核心检索词先摆出来Spring AI 是 Spring 生态里对接大模型的统一抽象层能做什么——把通义千问、DeepSeek 这类厂商 API 收敛成同一个ChatModel接口适合谁——需要多模型路由、故障切换、成本控制的 Java 后端团队。2. TaoToken 前置统一 Key 与 API 通道多模型适配最烦的一点是 Key 管理。通义千问一个 Key、DeepSeek 一个 Key每个都要单独申请、单独配置、单独轮换项目里环境变量越堆越多。TaoToken 在这里的作用是提供一条统一的 API 通道和一个统一 Key让 Spring AI 侧只需要维护一份凭证就能同时调用通义千问和 DeepSeek。官网入口https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentAPI 基础地址https://taotoken.net/api需要提前准备的东西不多一个 TaoToken 账号、一个 API Key、以及本地能跑起来的 Spring Boot 项目。API Key 在控制台的 API Keys 页面创建创建后立刻复制保存页面刷新后就看不到完整 Key 了。模型对话验证模型是否通https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmodel_chatAPI Keys 管理https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentapi_keys接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentdoc注意TaoToken 是合规的 API 聚合通道Key 只放在服务端环境变量或配置中心绝对不要写进前端代码或提交到 Git 仓库。3. 可复制配置application.yml 与 ChatModel 骨架3.1 Maven 依赖Spring AI 目前建议锁定1.0.0-M6不要写latest里程碑版本之间 API 差异不小。通义千问用 DashScope starterDeepSeek 走 OpenAI 兼容 starter两者都通过 TaoToken 的 base-url 接入。dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-dashscope/artifactId version1.0.0-M6/version /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-openai/artifactId version1.0.0-M6/version /dependency3.2 application.yml 双模型配置关键点在于两个模型都指向 TaoToken 的 API 地址用同一个 Key只是model字段不同。这样配置层就统一了切换模型只改model值。spring: ai: dashscope: api-key: ${TAOTOKEN_API_KEY} base-url: https://taotoken.net/api chat: enabled: true options: model: qwen-plus temperature: 0.7 max-tokens: 2000 openai: api-key: ${TAOTOKEN_API_KEY} base-url: https://taotoken.net/api/v1 chat: enabled: true options: model: deepseek-chat temperature: 0.7 max-tokens: 4096环境变量在启动时注入export TAOTOKEN_API_KEYsk-你的Key3.3 多 ChatModel Bean 骨架配置当容器里同时存在多个ChatModel实现时必须显式区分否则注入报错。用Qualifier给每个 Bean 命名业务层按名字取用。Configuration public class MultiModelConfig { Bean(qwenChatModel) Primary public ChatModel qwenChatModel(DashScopeChatProperties properties) { DashScopeChatOptions options DashScopeChatOptions.builder() .withModel(qwen-plus) .withTemperature(0.3) .build(); return new DashScopeChatModel( new DashScopeApi(properties.getApiKey()), options); } Bean(deepseekChatModel) public ChatModel deepseekChatModel(OpenAiChatProperties properties) { OpenAiApi api new OpenAiApi( https://taotoken.net/api/v1, properties.getApiKey()); OpenAiChatOptions options OpenAiChatOptions.builder() .withModel(deepseek-chat) .withTemperature(0.7) .build(); return new OpenAiChatModel(api, options); } }Primary标记通义千问为默认模型这样没有Qualifier的地方也能正常注入避免启动失败。3.4 业务层统一调用骨架业务代码只依赖ChatModel接口通过Qualifier选择实现。下面这个 Controller 演示了同一套逻辑如何切换两个模型。RestController RequestMapping(/api/chat) public class ChatController { private final ChatModel qwenChatModel; private final ChatModel deepseekChatModel; public ChatController( Qualifier(qwenChatModel) ChatModel qwenChatModel, Qualifier(deepseekChatModel) ChatModel deepseekChatModel) { this.qwenChatModel qwenChatModel; this.deepseekChatModel deepseekChatModel; } PostMapping(/qwen) public String chatWithQwen(RequestBody String question) { return qwenChatModel.call(question); } PostMapping(/deepseek) public String chatWithDeepSeek(RequestBody String question) { return deepseekChatModel.call(question); } }到这里配置层已经统一一个 Key、一条通道、两个模型业务代码零重复。4. 验证请求一次配置跑通两个模型4.1 启动与健康检查启动 Spring Boot 应用观察日志里两个ChatModelBean 是否都注册成功。如果看到No qualifying bean of type ChatModel说明Qualifier名字对不上回到 3.3 检查 Bean 名称。4.2 用 curl 验证双模型先验证通义千问curl -X POST http://localhost:8080/api/chat/qwen \ -H Content-Type: text/plain \ -d 用一句话解释什么是依赖倒置再验证 DeepSeekcurl -X POST http://localhost:8080/api/chat/deepseek \ -H Content-Type: text/plain \ -d 用一句话解释什么是依赖倒置两个请求都返回正常文本说明统一 Key 通道生效双模型跑通。如果其中一个返回 401检查TAOTOKEN_API_KEY是否注入成功返回 404检查 base-url 是否写成了/api/v1还是/apiDashScope 和 OpenAI 兼容端点的路径规则不同。4.3 流式输出验证多模型适配里流式输出是高频需求验证一下stream方法PostMapping(value /stream, produces MediaType.TEXT_EVENT_STREAM_VALUE) public FluxString stream(RequestBody String question) { Prompt prompt new Prompt(new UserMessage(question)); return deepseekChatModel.stream(prompt) .map(resp - resp.getResult().getOutput().getContent()); }用浏览器或 curl 访问能看到逐字返回说明流式通道也走通了。4.4 运行时动态切换如果不想为每个模型写一个接口可以注入MapString, ChatModel运行时按名字选Autowired private MapString, ChatModel chatModels; public String route(String modelName, String question) { ChatModel model chatModels.getOrDefault(modelName, chatModels.get(qwenChatModel)); return model.call(question); }这样新增模型只需要加一个 Bean路由层不用改。5. 本篇常见错排查5.1 多 Bean 注入冲突报错expected single matching bean but found 2原因是容器里有两个ChatModelSpring 不知道注入哪个。三种解法给默认模型加Primary注入处加Qualifier(beanName)注入MapString, ChatModel按名取。推荐组合使用PrimaryQualifier。5.2 base-url 路径写错DashScope 和 OpenAI 兼容端点的 base-url 规则不同。DashScope 用https://taotoken.net/apiOpenAI 兼容用https://taotoken.net/api/v1。写错会返回 404 或路径拼接异常。实测下来把两个 base-url 分别配置、不要共用同一个变量最省心。5.3 API Key 未注入api-key读的是${TAOTOKEN_API_KEY}如果环境变量没设置启动时不会报错但调用时返回 401。排查方法在启动类里打印System.getenv(TAOTOKEN_API_KEY)是否为 null或者改用application-local.yml本地覆盖。5.4 模型名不匹配通义千问的模型名是qwen-plus、qwen-max这类DeepSeek 是deepseek-chat、deepseek-reasoner。写错模型名会返回模型不存在。切换模型时只改model字段不要动 base-url 和 Key。5.5 超时与限流高峰期调用慢或返回 429说明触发了限流。给RestClient配置连接超时 5 秒、读取超时 60 秒并在路由层加降级通义千问限流时自动切到 DeepSeek。熔断器连续失败 5 次后打开2 分钟后半开重试。5.6 Spring AI 版本差异0.8.x和1.0.x的 API 差异很大比如ChatClient构造方式、Prompt包装方式都变了。锁死1.0.0-M6不要混用不同版本的 starter否则会出现方法找不到的编译错误。6. 多模型路由与长期编码场景的落地建议配置跑通只是第一步生产环境真正需要的是路由策略。成本敏感场景用 DeepSeek质量优先场景用通义千问敏感数据走本地模型故障时自动切换。这些策略都可以在ChatModel抽象之上用策略模式实现业务代码依然只依赖接口。如果你打算把多模型能力接到长期编码或 Agent 工作流里建议用 Coding Plan 统一管理调用配额与模型编排Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcoding_plan控制台https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentconsole接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentdoc最后留一个我踩过的坑多模型项目里ConfigurationProperties统一管理配置比散落在各个Value里强太多。把每个模型的 api-key、base-url、model、temperature、timeout 收进一个MapString, ModelConfig新增模型只加一段 yaml代码零改动。这一步做完你的 Spring AI 多模型适配才算真正可维护。