模型抽象层一行配置切换 OpenAI/DeepSeek/通义/智谱本文是专栏《Spring AI 入门与实战》的第 4 篇上一篇我们聊了《第一个 AI 应用ChatClient 十分钟上手》。这一篇钻到 ChatClient 背后ChatModel/EmbeddingModel 抽象是怎么工作的为什么OpenAI 兼容协议成了国产模型接入的事实标准怎么在一个应用里让多个模型共存并按业务路由以及模型版本锁定与降级链路这两个生产必备课题。一、场景引入朋友的创业团队用 DeepSeek 跑了三个月的客服助手一切正常。某个周一早上群里炸了DeepSeek 平台限流客服接口大面积超时客服主管在群里 所有人。他们想立刻切到之前申请好的通义千问结果发现整个 AI 代码里十几处地方直接引用了 DeepSeek 特有的响应字段切换意味着一轮回归测试加连夜改代码——不敢动只能干等平台恢复。事后复盘问题不在 DeepSeek在于代码和具体模型焊死了。模型会限流、会涨价、会停服旧版本、会有新模型性价比反超换模型在 AI 应用的生命周期里不是意外是必然事件。这一篇讲的抽象层就是让你的代码在模型变更那天只需要改配置、最多改路由而不需要改业务逻辑。二、核心讲解2.1 抽象是怎么设计的Spring AI 的模型抽象核心是几个接口以ChatModel为例EmbeddingModel、ImageModel同构publicinterfaceChatModelextendsModelPrompt,ChatResponse{// 最底层接收 Prompt含消息列表与参数返回 ChatResponseChatResponsecall(Promptprompt);// 便捷方法单条字符串进字符串出defaultStringcall(Stringmessage){...}// 流式版本defaultFluxChatResponsestream(Promptprompt){...}}围绕这个接口每个厂商有各自的实现与自动配置OpenAI 的 starter 装配OpenAiChatModelAnthropic 的装配AnthropicChatModelOllama 的装配OllamaChatModel。而ChatOptions模型调用参数如 temperature、model、maxTokens同样有抽象层与厂商层——通用参数定义在ChatOptions接口OpenAiChatOptions等子类补充厂商特有参数。这套设计的直接收益就是你已经体验过的事实spring.ai.openai.*下换三行配置同样的chatModel.call(...)代码从 DeepSeek 换到通义。自动配置做的事也不复杂读取连接属性base-url、api-key构造一个指向该端点的 HTTP 客户端读chat.options.*构造默认参数然后装配出一个ChatModelBean 放进容器。理解这一点很重要因为它是下一节多模型共存的基础——自动配置只能给你一个但你可以自己造多个。2.2 OpenAI 兼容协议事实标准为什么一个名为 openai 的 starter 能连 DeepSeek、通义、智谱因为 OpenAI 早年定义的 HTTP API 形状——POST /v1/chat/completions、Authorization: Bearer key、messages 数组加 SSE 流式——成了行业事实标准。国产主流模型几乎都提供兼容端点模型base-url模型名示例DeepSeekhttps://api.deepseek.comdeepseek-chat、deepseek-reasoner阿里通义https://dashscope.aliyuncs.com/compatible-modeqwen-plus、qwen-turbo、qwen-max智谱 GLMhttps://open.bigmodel.cn/api/paas/v4glm-4.5、glm-4.5-air切换就是改配置三份对照# DeepSeekspring.ai.openai:base-url:https://api.deepseek.comapi-key:${DEEPSEEK_API_KEY}chat.options.model:deepseek-chat# 通义千问spring.ai.openai:base-url:https://dashscope.aliyuncs.com/compatible-modeapi-key:${DASHSCOPE_API_KEY}chat.options.model:qwen-plus# 智谱spring.ai.openai:base-url:https://open.bigmodel.cn/api/paas/v4api-key:${ZHIPU_API_KEY}chat.options.model:glm-4.5要清醒的一点兼容≠完全兼容。各家在 JSON Schema 结构化输出、工具调用的细节字段、流式事件类型上都有差异和阉割“能聊天不代表所有功能可用”。选定模型后把你用到的每个特性结构化输出、工具调用、流式都过一遍冒烟测试再谈替换。2.3 多模型共存一个应用里的模型路由单模型配置在现实中不够用客服走便宜档、报告生成走推理档、测试环境走 Ollama 本地小模型。让多个模型共存思路是自己构造多个ChatModelBean绕开自动配置只能装配一个的限制再为每个模型配一个ChatClient!-- 依赖不变多个 OpenAI 兼容端点仍共用 openai starter --dependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-starter-model-openai/artifactId/dependencyapp:models:deepseek:base-url:https://api.deepseek.comapi-key:${DEEPSEEK_API_KEY}model:deepseek-chat# 通用档客服、问答qwen:base-url:https://dashscope.aliyuncs.com/compatible-modeapi-key:${DASHSCOPE_API_KEY}model:qwen-plus# 备用档 文案档packagecom.example.models;importorg.springframework.ai.chat.model.ChatModel;importorg.springframework.ai.openai.OpenAiChatModel;importorg.springframework.ai.openai.OpenAiChatOptions;importorg.springframework.ai.openai.api.OpenAiApi;importorg.springframework.beans.factory.annotation.Qualifier;importorg.springframework.boot.context.properties.ConfigurationProperties;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;ConfigurationpublicclassMultiModelConfig{BeanpublicChatModeldeepseekChatModel(MultiModelPropsprops){varpprops.deepseek();OpenAiApiapiOpenAiApi.builder().baseUrl(p.baseUrl()).apiKey(p.apiKey()).build();returnOpenAiChatModel.builder().openAiApi(api).defaultOptions(OpenAiChatOptions.builder().model(p.model()).temperature(0.5).build()).build();}BeanpublicChatModelqwenChatModel(MultiModelPropsprops){varpprops.qwen();OpenAiApiapiOpenAiApi.builder().baseUrl(p.baseUrl()).apiKey(p.apiKey()).build();returnOpenAiChatModel.builder().openAiApi(api).defaultOptions(OpenAiChatOptions.builder().model(p.model()).temperature(0.8).build()).build();}}ConfigurationProperties(app.models)publicrecordMultiModelProps(Modeldeepseek,Modelqwen){publicrecordModel(StringbaseUrl,StringapiKey,Stringmodel){}}注意ConfigurationProperties这段代码需要加EnableConfigurationProperties(MultiModelProps.class)或用ConfigurationPropertiesScan才生效。接着用这两个 ChatModel 构建两个 ChatClient并写一个按业务路由的门面packagecom.example.models;importorg.springframework.ai.chat.client.ChatClient;importorg.springframework.stereotype.Service;ServicepublicclassChatRouter{publicenumTier{STANDARD,CREATIVE}privatefinalChatClientstandard;// deepseek-chatprivatefinalChatClientcreative;// qwen-pluspublicChatRouter(ChatClient.Builderbuilder,org.springframework.ai.chat.model.ChatModeldeepseekChatModel,org.springframework.ai.chat.model.ChatModelqwenChatModel){// 注意Builder 默认绑定容器里的 ChatModel这里分别显式指定this.standardChatClient.builder(deepseekChatModel).defaultSystem(你是严谨的客服助手。).build();this.creativeChatClient.builder(qwenChatModel).defaultSystem(你是创意文案助手。).build();}publicStringask(Tiertier,Stringquestion){ChatClientclient(tierTier.CREATIVE)?creative:standard;returnclient.prompt().user(question).call().content();}}这个结构有几个讲究路由逻辑集中在一个类而不是散落在各 Controller 的 if-else 里每档模型的系统提示词、temperature 各自独立将来加智谱就是加一个 Bean、一个枚举值。如果路由规则复杂按时段、按用户等级、按成本预算把它抽成独立策略类即可门面不动。2.4 参数调优通用经验值temperature分类/抽取/SQL 生成 0~0.2问答与客服 0.3~0.6文案创意 0.8~1.2。topP与 temperature 二选一调即可不要同时大改两者。需要更窄的分布时把 topP 降到 0.8 左右比极端压 temperature 更平滑。maxTokens作为成本上限设置按业务回答长度的 1.2 倍取值。模型选择优先于参数调优参数只能在模型能力范围内微调deepseek-reasoner和deepseek-chat的差距远大于 temperature 从 0.3 调到 0.7。三、生产视角模型版本要锁定不要用最新。配置里写deepseek-chat这种别名是方便但它背后的快照会随平台静默更新你今天验证过的提示词效果下个月可能悄悄漂移。生产做法能锁具体版本号就锁版本号锁不了就在可观测性第 22 篇里记录每次响应返回的模型标识变更时告警。同时订阅所选平台的模型停服公告——旧版本模型下线是各平台常态操作提前一两个月就要排期验证替代版本。降级链路是必需品不是加分项。多模型共存最大的价值就在这。最小实现调用失败超时、429、5xx时按优先级切下一个模型。用 Spring Retry 包一层即可起步publicStringaskWithFallback(Stringquestion){try{returnstandard.prompt().user(question).call().content();}catch(Exceptione){log.warn(主模型调用失败降级到备用模型: {},e.getMessage());returncreative.prompt().user(question).call().content();}}进阶版是熔断 备用 排队主模型连续失败触发熔断一段时间内直接走备用避免每个请求都先超时一次超时等待本身就是成本。用 Resilience4j 的 CircuitBreaker 注解可以做到声明式实现思路与数据库故障切换一脉相承不再展开。成本用模型分档直接控制。多模型路由天然是成本治理工具把高成本模型划为需要审批的 Tier把日志摘要、内部质检这类高频低价值场景钉死在最低档。每周拉一次各档位调用量 × 单价的报表比任何省钱技巧都有效——前提还是那句话得有观测数据第 22 篇。数据合规随模型走。不同模型的部署位置境外/境内、公有/私有决定了能发什么数据过去。路由层是做合规拦截的好位置涉密数据只允许路由到私有化部署的 Ollama/Qwen 专区这个规则用路由枚举加一条断言就能实现。四、踩坑记录坑一多 Bean 冲突。定义了两个ChatModelBean 后启动直接失败Parameter 0 of constructor in com.example.models.ChatRouter required a single bean, but 2 were found: - deepseekChatModel: defined in com.example.models.MultiModelConfig - qwenChatModel: defined in com.example.models.MultiModelConfig报错本身不冤容器里有两个ChatModelSpring 不知道该把哪个注给ChatClient.Builder自动配置的 Builder 也依赖唯一的 ChatModel同样受影响。第一反应加Qualifier能解决注入点但自动配置的 Builder 还是懵的。正解是给主力模型标Primary让它继续充当默认模型其他模型按需注入。另外一个隐蔽的次生坑自动配置看到你自己定义了ChatModelBean 后条件装配会退位ConditionalOnMissingBean某些 starter 提供的默认参数装配也随之失效——所以我在上面每个 Bean 里都显式传了defaultOptions不依赖任何隐式默认。坑二切换智谱时模型名 404。配置从 DeepSeek 切到智谱第一发请求就报org.springframework.web.client.HttpClientErrorException$NotFound: 404 Not Found on POST request for https://open.bigmodel.cn/api/paas/v4/v1/chat/completions看 URL 就破案了/api/paas/v4/v1/chat/completions——路径里出现了两个版本段。Spring AI 的 OpenAI 客户端会在 base-url 后拼接默认路径/v1/chat/completions而智谱的兼容端点本身就以/api/paas/v4结尾它不需要也不认识后面的/v1。解决方法有两个要么把 base-url 写成https://open.bigmodel.cn/api/paas/v4并用配置项覆盖拼接路径spring.ai.openai.chat.completions-path设为/chat/completions要么自定义 Bean 时在OpenAiApi构造时指定路径。教训OpenAI 兼容各家对路径、鉴权头的实现细节不一接入新模型先用 curl 手发一次请求确认路径形状再进 Spring AI。五、小结与练习本篇要点ChatModel/EmbeddingModel 是模型抽象层自动配置按 starter 装配实现OpenAI 兼容协议是国产模型接入的事实标准DeepSeek/通义/智谱共用 openai starter切换只改 base-url/api-key/model一个应用多模型共存用多个手写 ChatModel Bean 按业务路由的 ChatClient 门面主力模型标Primary参数上 temperature 按场景分档maxTokens 管成本生产必备模型版本锁定与降级链路路由层同时是成本与合规的执行点。练习在 2.3 节代码基础上加第三档本地 Ollama提示引入spring-ai-starter-model-ollama本地ollama pull qwen2.5:1.5b后Ollama 的 ChatModel Bean 与 OpenAI 系互不冲突可直接装配然后把askWithFallback改成三级降级DeepSeek 失败切通义、再失败切本地模型。跑通后故意断掉一个 Key观察降级是否生效。下一篇《Prompt 工程PromptTemplate 与系统提示词》我们从换模型转向喂模型——同样一个模型提示词的写法能带来比换模型更大的效果差异。
企业数字化 ERP 产品动态
相关推荐
挂轨式墙面收纳系统的结构原理:轨道、定位件与免工具调节是怎么实现的 # 挂轨式墙面收纳系统的结构原理:轨道、定位件与免工具调节是怎么实现的> 面向技术社区与行业观察者,讲清一套「竖向轨道 挂接组件」系统在结构上是怎么成立的。> 本文不涉及具体报价与交付政策;文中结构参数为品类通用信息࿰… · 2026/9/26 6:39:34
NVIDIA Model Optimizer 安装指南:Linux 与 Windows 全平台环境搭建与验证实战 【免费下载链接】Model-Optimizer A unified library of SOTA model optimization techniques like quantization, distillation, pruning, neural architecture search, speculative decoding, etc. It compresses deep learning models for downstream deployment frameworks… · 2026/9/26 6:39:34
Tesseract 3.02.02 Win32开发包集成指南:C++工程配置与避坑 简介:在Windows平台使用C/C进行OCR应用开发时,常因缺少Tesseract 3.02.02的头文件、静态库与动态库而无法编译链接。这里提供的正是一套可直接引用的SDK源文件包,面向需要将OCR能力集成到原生程序的开发者,省去手工整理依赖的麻烦… · 2026/9/26 6:39:10
手机录音隐藏功能全攻略:从降噪到转文字,开会学习效率翻倍 很多人手机里都装着那个系统自带的录音图标,但真正把它用明白的人少之又少。尤其对于经常开会、上课、做访谈的人来说,手机录音绝不只是“按一下红色按钮”这么简单——它背后藏着一整套降噪、变速、跳静音、转文字、自动摘要的能力,用好了能… · 2026/9/26 7:09:15
LoadRunner压测SAP全攻略:从协议选型到性能瓶颈排查 做了这么多年SAP性能测试,我最大的感受是:SAP系统不是不能压,而是很多人一上来就选错了工具和协议。项目标题里写的“使用LoadRunner工具对SAP进行压测”,看着简单,实际上从脚本录制、关联、参数化到后端监控ÿ… · 2026/9/26 7:09:15
MyBatis优缺点深度解析:从SQL控制力到缓存与分页实践 1. 为什么MyBatis能火这么多年:先把优点说透“MyBatis有哪些优点和缺点”这个问题,几乎每个做Java后端的人都被面试官问过。说实话,这题目看起来像背八股,但真要在项目里选型、排查性能问题、设计数据访问层时,你会发现… · 2026/9/26 7:09:15
设备部经理绩效考核指标量表与绩效评估 该设备部经理绩效考核表涵盖了设备部经理的各项核心工作绩效指标,包括设备的有效利用、负荷、保养等多个方面。每一项指标都有具体的考核标准和权重,目标明确并与部门的运营效率、设备管理以及安全操作密切相关。绩效目标的达成情况直接影响到设备部经理的考核得分。该表格不… · 2026/9/26 7:08:57
Wand-Enhancer开源补丁:原理、部署与实战避坑指南 1. 从"Wand"这个名字说起:它到底是个什么东西第一次看到"Wand"这个词,很多人会一头雾水。它既不是某个知名开源框架,也不是主流开发工具链里的常客。实际上,在工具软件圈子里,Wand 通常指的是一类… · 2026/9/26 7:08:26
彩虹云商城模板实战拆解:Vue3+Vite前后台分离架构与二次开发指南 做商城项目这些年,接到的需求里十个有八个都是“要一个前台好看、后台好用的商城系统”。市面上的开源商城不少,但真正把前端用户界面和后台管理界面一起打磨到位、拿来能直接用、改起来又不费劲的模板,其实并不多。所以当看到“彩虹云商城前… · 2026/9/26 7:08:26
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍 简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践 一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46