新闻详情

新闻详情

首页 / 资讯中心 / 详情

Spring AI 多模型路由实战:DeepSeek、OpenAI 与本地模型动态切换的配置骨架

发布时间:2026/9/29 9:03:08来源:尧图网络
Spring AI 多模型路由实战:DeepSeek、OpenAI 与本地模型动态切换的配置骨架
1. 从一次线上告警说起为什么单 ChatModel 撑不住生产流量很多 Java 开发者第一次接触 Spring AI都是在本地写一个ChatClient注入一个ChatModelBean然后chatClient.prompt().user(你好).call().content()就完事了。演示环境里这套写法确实够用但一旦上线问题会集中爆发高质量云模型承接所有简单请求成本太高供应商限流和网络抖动会放大单点故障内部合同和客户资料又不能离开内网不同模型对上下文长度、结构化输出和工具调用的支持也各不相同。我试过在一个运营助手项目里把 FAQ 问答、营销文案、内部经营分析三类请求全部打到同一个云模型上。结果月底账单里超过六成费用花在了本可以用小模型处理的规则查询上更麻烦的是有一次供应商侧限流整个助手全线不可用连“活动规则是什么”这种简单问题都答不出来。所以“动态切换模型”不能理解成在 Controller 里写三段 if-else。一个可维护的多模型方案需要把业务意图翻译成可计算条件用统一协议屏蔽供应商差异在超时、限流和预算不足时执行有边界的降级并通过指标验证质量、成本与可用性。这篇文章就以一个抽奖系统的运营助手为例给出 Spring AI 多模型路由的配置骨架覆盖 DeepSeek、OpenAI 与本地模型的动态切换并演示通过接口请求验证不同模型命中与回退的完整动作。模型名称、价格与限额均以实际账号和所用 Spring AI 版本为准。2. TaoToken 前置统一 Key 与 API 通道让路由配置先跑起来在写路由代码之前得先解决一个现实问题DeepSeek、OpenAI 这些云模型的 Key 和接入地址各不相同如果每个供应商都单独申请、单独配置路由骨架还没写完配置管理就已经乱了。我的做法是先用 TaoToken 把云模型的统一 Key 和 API 通道准备好这样application.yml里只需要维护一套凭证路由层专注做模型选择逻辑。TaoToken 在这里扮演的是统一接入通道的角色你可以在它的控制台里创建 API Key然后通过一个兼容 OpenAI 协议的入口访问多个云模型。对 Spring AI 来说这意味着你可以用同一套OpenAiApi配置去调用不同供应商的模型只需要在请求里指定模型名。官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 注意这个地址不加 UTM 参数。具体操作上你需要先拿到 Key。打开控制台页面 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 创建一个 API Key然后在 API Keys 管理页 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 里可以看到 Key 列表和额度。如果你只是想先验证模型能不能通可以直接用模型对话页面 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 发一条消息试试确认 Key 有效再写代码。这里要提醒一句TaoToken 是统一接入通道不是让你绕过任何合规要求。内部机密数据该走本地模型还是走本地模型路由层的安全硬约束不能因为接入方便就放松。Key 本身要通过环境变量或密钥管理服务注入不要写进 Git也不要回显到 Actuator 端点。3. 可复制配置application.yml 路由骨架与 Spring AI 接入下面这份application.yml骨架把业务路由参数和供应商连接参数分开。业务路由参数可以放配置中心动态调整供应商连接参数包含密钥和内网地址需要更严格的访问控制。这份配置假设你已经通过 TaoToken 拿到了统一 Key并且本地模型通过 Ollama 或类似的推理服务暴露了 HTTP 接口。spring: ai: openai: # 通过 TaoToken 统一通道访问云模型 base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: deepseek-chat temperature: 0.7 ollama: base-url: ${LOCAL_MODEL_ENDPOINT:http://127.0.0.1:11434} chat: options: model: ${LOCAL_MODEL_NAME:qwen2.5:7b} ai: routing: total-timeout: 8s max-attempts: 3 profiles: deepseek-chat: provider: deepseek model: deepseek-chat local: false supports-tools: true supports-json-schema: true max-context-tokens: 64000 priority: 10 estimated-input-cost-per-million: 0.5 estimated-output-cost-per-million: 1.5 openai-quality: provider: openai model: gpt-4o-mini local: false supports-tools: true supports-json-schema: true max-context-tokens: 128000 priority: 20 estimated-input-cost-per-million: 1.0 estimated-output-cost-per-million: 3.0 local-private: provider: local model: qwen2.5:7b local: true supports-tools: false supports-json-schema: false max-context-tokens: 16000 priority: 30 estimated-input-cost-per-million: 0 estimated-output-cost-per-million: 0 endpoint: ${LOCAL_MODEL_ENDPOINT:http://127.0.0.1:11434} max-concurrency: 4这份配置里spring.ai.openai指向 TaoToken 的统一入口api-key从环境变量读取。ai.routing.profiles是模型能力目录每个 profile 描述上下文上限、部署位置、工具调用与结构化输出能力、优先级和成本估算。注意local-private的supports-tools和supports-json-schema都是 false这是硬约束路由层会据此过滤候选。对应的 Java 配置类把这份 YAML 绑定成ModelProfile列表ConfigurationProperties(prefix ai.routing) public record RoutingProperties( Duration totalTimeout, int maxAttempts, MapString, ProfileConfig profiles) { public record ProfileConfig( String provider, String model, boolean local, boolean supportsTools, boolean supportsJsonSchema, int maxContextTokens, int priority, BigDecimal estimatedInputCostPerMillion, BigDecimal estimatedOutputCostPerMillion, String endpoint, Integer maxConcurrency) { } }然后在启动时把每个 profile 注册成ModelAdapter。云模型统一走 Spring AI 的OpenAiChatModel本地模型走OllamaChatModel但对外都包装成同一个ModelAdapter接口。这样业务层不需要知道底层是 DeepSeek 还是本地推理服务路由层也只依赖能力目录。Configuration EnableConfigurationProperties(RoutingProperties.class) public class ModelAdapterConfig { Bean public MapString, ModelAdapter modelAdapters( RoutingProperties props, OpenAiChatModel openAiChatModel, OllamaChatModel ollamaChatModel) { MapString, ModelAdapter adapters new HashMap(); props.profiles().forEach((id, cfg) - { ChatClient client cfg.local() ? ChatClient.builder(ollamaChatModel).build() : ChatClient.builder(openAiChatModel).build(); adapters.put(id, new SpringAiModelAdapter(id, client)); }); return adapters; } }这里有个细节云模型虽然都走OpenAiChatModel但请求里的model参数需要按 profile 动态设置。可以在SpringAiModelAdapter内部根据modelId查配置调用时覆盖ChatOptions的 model 字段。这样一套客户端就能访问多个云模型不需要为每个供应商单独建 Bean。4. 验证请求用接口确认不同模型命中与回退配置写完得验证路由是否真的按预期工作。最直接的方式是暴露一个调试接口接收任务类型和数据等级返回实际命中的模型 ID 和响应内容。下面这个 Controller 可以帮你快速确认路由结果RestController RequestMapping(/api/ai) public class AiDebugController { private final ModelGatewayExecutor executor; private final ModelRouter router; public AiDebugController(ModelGatewayExecutor executor, ModelRouter router) { this.executor executor; this.router router; } PostMapping(/chat) public MapString, Object chat(RequestBody ChatRequest req) { RouteContext ctx new RouteContext( UUID.randomUUID().toString(), req.tenantId(), req.taskType(), req.dataLevel(), req.estimatedInputTokens(), req.reservedOutputTokens(), 0, 200, req.needJsonSchema(), req.needToolCalling(), false, STANDARD ); ListModelProfile candidates router.candidates(ctx); ModelResponse response executor.execute(ctx, new ModelRequest( req.systemPrompt(), req.userPrompt(), Map.of(), 0.7, 1024)); return Map.of( candidates, candidates.stream().map(ModelProfile::id).toList(), hitModel, response.modelId(), content, response.content(), totalMillis, response.totalMillis() ); } public record ChatRequest( String tenantId, TaskType taskType, DataLevel dataLevel, int estimatedInputTokens, int reservedOutputTokens, boolean needJsonSchema, boolean needToolCalling, String systemPrompt, String userPrompt) { } }启动应用后用 curl 发三类请求观察candidates和hitModel的变化# 场景一公开 FAQ预期命中低成本云模型 curl -X POST http://localhost:8080/api/ai/chat \ -H Content-Type: application/json \ -d { tenantId: t-001, taskType: FAQ, dataLevel: PUBLIC, estimatedInputTokens: 800, reservedOutputTokens: 500, needJsonSchema: false, needToolCalling: false, systemPrompt: 你是活动规则助手, userPrompt: 抽奖活动每人每天能抽几次 } # 场景二内部经营分析预期只命中本地模型 curl -X POST http://localhost:8080/api/ai/chat \ -H Content-Type: application/json \ -d { tenantId: t-001, taskType: DATA_ANALYSIS, dataLevel: CONFIDENTIAL, estimatedInputTokens: 2000, reservedOutputTokens: 800, needJsonSchema: true, needToolCalling: false, systemPrompt: 你是经营分析助手, userPrompt: 汇总本月各渠道转化率 }第一类请求的candidates应该包含deepseek-chat、openai-quality和local-privatehitModel大概率是deepseek-chat因为它的优先级分数最低。第二类请求的candidates只应该有local-private因为CONFIDENTIAL等级过滤掉了所有非本地模型。如果第二类请求的candidates里出现了云模型说明安全硬约束没生效必须立刻排查。回退验证可以手动制造故障把deepseek-chat的 endpoint 改成一个不可达地址或者用 Mock Server 返回 429。再次发第一类请求观察hitModel是否切换到openai-quality以及totalMillis是否在 8 秒预算内。如果主模型超时后备用模型也超时最终应该抛出AllModelsUnavailableException而不是无限重试。5. 本篇常见错排查配置、超时与安全边界错误一No qualifying bean of type ChatModel。这通常是因为同时引入了多个 Spring AI starter容器里有多个ChatModel实现注入时无法确定用哪个。解决办法是在配置类里用Qualifier明确指定或者像上面的ModelAdapterConfig一样直接注入具体的OpenAiChatModel和OllamaChatModel而不是注入接口。错误二路由候选为空直接抛AllModelsUnavailableException。先检查RouteContext里的requiredContextTokens()是否超过了所有模型的maxContextTokens。常见原因是estimatedInputTokens估得太大或者safetyMarginTokens设得过高。可以临时把安全冗余调小观察候选是否恢复。另一个原因是needJsonSchematrue但所有候选模型都不支持结构化输出这时候要么换支持 Schema 的模型要么显式降级到提示词约束模式。错误三超时配置不生效调用方已经降级但底层 HTTP 请求还在跑。这是最容易踩的坑。ModelAdapter.call(request, timeout)里的timeout参数如果只用来做Future.get(timeout)底层 HTTP 客户端并没有被取消连接池会被占满。正确做法是在 HTTP 客户端层设置连接超时、读取超时和整体响应超时并且在执行器外层用Deadline控制总预算。Spring AI 的OpenAiApi底层用的是 WebClient可以通过HttpClient自定义超时。错误四本地模型返回慢拖垮整个请求链路。本地模型没有公网账单但 GPU 推理有队列。如果max-concurrency设得太大请求会排队等待总耗时反而超过云模型。建议给本地模型单独设置较短的超时预算并且在健康快照里记录队列长度队列满时直接跳过该候选。错误五敏感数据通过日志泄露。路由层做了CONFIDENTIAL过滤但日志里把完整 Prompt 打出来了等于白做。建议在日志里只记录模板版本、摘要和 Token 长度不记录原文。如果确实需要排查可以用脱敏后的字段名代替具体值。6. 语义一致 CTA按你的场景选下一步如果你现在卡在接入配置上比如 Key 怎么注入、base-url 怎么写、Spring AI 版本和 TaoToken 通道怎么对齐建议先看接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有针对 Spring AI 的配置示例。Key 的创建和管理在 API Keys 页面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 建议先建一个测试 Key确认通道通了再写路由代码。如果你只是想快速验证 DeepSeek 和 OpenAI 在同一个通道下能不能正常返回可以直接用模型对话页面 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 发几条消息对比不同模型的响应质量和延迟再决定路由权重怎么配。如果你在做长期的编码助手或 Agent 项目需要稳定的模型调用配额和更细的用量管理可以了解 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。对于 Claude Code 这类编码场景Anthropic 兼容接入的说明在 https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaude_codeutm_campaignrewrite 配置方式和 OpenAI 兼容通道类似但请求格式有差异适配器里需要单独处理。路由骨架搭好之后下一步不是急着加更多模型而是先把可观测性补上。至少记录每次请求的requestId、候选列表、最终模型、尝试次数、错误类别和总耗时。有了这些数据你才能回答“这次降级到底值不值”这个问题。模型切换本身不产生价值让合适的请求落到合适的模型上同时守住安全和成本边界才是多模型路由真正要解决的问题。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Codex 实战:简历项目怎么讲清楚——从单元测试到 CI/CD 的微服务叙事 2026/9/29 9:55:28

Codex 实战:简历项目怎么讲清楚——从单元测试到 CI/CD 的微服务叙事

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
小米 Q2 研发 92 亿、MiMo-V2.5 登顶 OpenRouter:手机厂大模型的端云协同怎么配 TaoToken 2026/9/29 9:55:28

小米 Q2 研发 92 亿、MiMo-V2.5 登顶 OpenRouter:手机厂大模型的端云协同怎么配 TaoToken

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
TaoToken 配置实战:用 settings.json 骨架复现李蓓地产交易决策的“不看“逻辑 2026/9/29 9:55:28

TaoToken 配置实战:用 settings.json 骨架复现李蓓地产交易决策的“不看“逻辑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
GitHub Copilot CLI 实战:/fleet、Autopilot、Hooks 和 /delegate 怎么配合用(TaoToken 统一 Key 接入版) 2026/9/29 9:55:27

GitHub Copilot CLI 实战:/fleet、Autopilot、Hooks 和 /delegate 怎么配合用(TaoToken 统一 Key 接入版)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
步进电机驱动器接线本质:信号链路精准匹配指南 2026/9/29 9:55:12

步进电机驱动器接线本质:信号链路精准匹配指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
OpenCV入门:图像读取、显示与写入的完整实践指南 2026/9/29 9:55:03

OpenCV入门:图像读取、显示与写入的完整实践指南

1. 准备工作:先把OpenCV环境搞定说实话,OpenCV入门最大的门槛往往不是代码本身,而是环境安装。我见过太多初学者卡在import cv2这一步,明明按照教程装完了,一运行就报ModuleNotFoundError,心态直接崩掉。这…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉