你正在开发一个 AI 客服,用的 OpenAI 接口,结果某天官方限流、导致线上问答全线超时。救场方案是本地再部署一个 Ollama 小模型兜底。本文就用 LangChain4j 1.19(Java 生态)把这套"主模型 + 本地备用模型"的切换与自动降级逻辑完整写一遍,含可运行代码。解决的核心问题:如何用一个统一接口,在多个大模型 Provider 之间按需/按故障自动切换,且做到 OpenAI 挂了本地模型无缝接管。
一、这个问题到底是什么
先说场景。大多数生产级的 AI 应用,不会只绑定一家大模型厂商。原因很直白:单点故障。OpenAI、通义、文心这些云厂商偶尔会限流、抖动、或者免费额度烧完。这时候你希望应用不崩,能自动切到另一家或者本地模型继续干活。
再说成本。云端大模型按 token 收费,日常简单问答用贵的模型就是烧钱。很多人会做分层:重活给大模型,简单活给便宜的本地小模型。而底层这些模型接口五花八门,OpenAI 用一套调用方式,Ollama 本地是另一套,如果代码里到处硬编码切换逻辑,后期维护会哭。
LangChain4j 的价值就在这里:它把所有大模型统一抽象成ChatLanguageModel这一个接口。不管背后是 OpenAI、Ollama、通义还是本地跑的任何一个模型,你的业务代码只认这一个接口。本文要解决的就是:
- 用 LangChain4j 同时接上 OpenAI 和本地 Ollama 两个模型;
- 做一个"主备切换":优先用 OpenAI,挂了或超时自动切到 Ollama;
- 做成一个可手动切换的开关,方便日常测试和成本控制。
一句话定位:这是 AI 工程化里的"模型路由与降级",是所有上生产的多模型应用都绕不开的一环。
二、底层原理到底怎么回事
先搞清楚 LangChain4j 是怎么把不同厂商模型统一起来的。
LangChain4j 的核心抽象叫ChatLanguageModel,它定义了一个核心方法:给你一串对话消息(List<ChatMessage>),返回一个模型回复(Response<AiMessage>)。OpenAI、Ollama、通义……每一家都是这个接口的一个实现类。比如OpenAiChatModel负责对接 OpenAI 的 API,OllamaChatModel负责对接本地 Ollama 服务。你的业务代码里根本不用关心报错格式、HTTP 细节,只面向ChatLanguageModel编程就好。
这个设计很像我们平时用 JDBC 连数据库:Connection是统一接口,MySQL 驱动和 Oracle 驱动各自实现它,业务代码不换。LangChain4j 的ChatLanguageModel就是模型界的Connection。
那"切换"和"降级"怎么做?两个层面:
第一种:手动切换。你代码里把两个模型分别建出来,通过配置项(比如环境变量或application.properties)决定当前用哪个。好处是简单直观,适合日常调试、成本控制。坏处是机器不会自动判断,OpenAI 挂了你还得人工改配置重启。
第二种:自动降级(Fallback)。LangChain4j 1.x 提供了一个专门的模型包装类,叫ConcurrentChatModel,它里面可以塞多个候选模型,配上每个模型的优先级顺序。主模型抛异常或超时,它就自动按顺序尝试下一个。这就是"故障自动切换"的底层机制。
我来解释一下ConcurrentChatModel的原理:它内部维护一个按优先级排序的模型列表,调用时从第一个开始;如果第一个抛了异常,它捕获后记录这次失败,然后尝试下一个。它甚至支持降级后自动恢复——如果主模型失败,之后它会周期性重新探测主模型是否恢复,恢复了就切回去。这类似断路器模式(Circuit Breaker)在模型层的应用:连续失败就熔断走备用,冷却后再试探。
再补充一个关键点:Prompt 和 Function Calling 是跟着模型走的。同一个 Prompt,你发给 GPT-4 和发给本地 Llama 3,效果可能差很多。本地小模型对复杂指令、JSON 格式输出的遵从度通常不如大模型。所以做降级时,要考虑备胎模型的能力是否够用,别把最复杂的任务丢给最小的模型。这也是优化策略的一部分:重活默认走大模型,简单兜底走本地,而非无脑全切。
底层原理总结一句:统一接口ChatLanguageModel= 模型无关;ConcurrentChatModel= 故障自动切换;两者叠加,就是多模型生产化落地的基石。
三、实战:手把手写代码
下面我们搭一个 Maven 项目,完整演示主备切换。前面版本规则已实查 Maven Central:LangChain4j 当前 GA 版本是1.19.0(langchain4j-open-ai、langchain4j-ollama均为 1.19.0)。需要 JDK 21。
3.1 建项目和配 POM
先建一个普通 Maven 工程,pom.xml引入两个模型 Provider 和一个 Spring Boot 启动器(本地方便跑,也可以只用纯 Java 不用 Spring,这里用 Spring Boot 演示配置注入)。
<?xml version="1.0" encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>4.1.0</version><relativePath/></parent><groupId>com.baiyunge</groupId><artifactId>multi-llm-switch</artifactId><version>1.0.0</version><name>multi-llm-switch</name><properties><java.version>21</java.version><langchain4j.version>1.19.0</langchain4j.version></properties><dependencies><!-- LangChain4j 的 Spring Boot 启动器:自动配置 AiServices、模型 bean 等 --><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-spring-boot-starter</artifactId><version>${langchain4j.version}</version></dependency><!-- 对接 OpenAI 官方 API --><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-open-ai</artifactId><version>${langchain4j.version}</version></dependency><!-- 对接本地 Ollama 服务 --><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-ollama</artifactId><version>${langchain4j.version}</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>说明这段配置在干什么:Spring Boot parent 4.1.0 提供版本管理;langchain4j-open-ai和langchain4j-ollama是核心,分别把 OpenAI 和本地 Ollama 的实现类带进来;langchain4j-spring-boot-starter让我们能用@Bean和配置注入模型。三个依赖版本都来自我们properties里统一管理的 1.19.0。
3.2 配置两个模型
在src/main/resources/application.properties里放两个模型的参数。OpenAI 需要 API Key,Ollama 只需要本地服务地址。
# OpenAI 主模型相关配置 openai.api-key=${OPENAI_API_KEY:sk-xxxx} openai.model-name=gpt-4o-mini openai.base-url=https://api.openai.com # Ollama 备用模型相关配置 ollama.base-url=http://localhost:11434 ollama.model-name=llama3 # 是否开启自动降级(生产建议 true) llm.fallback.enabled=true这里openai.api-key读取名为OPENAI_API_KEY的环境变量,没设置就用默认值sk-xxxx(跑通前得自己填真的 key)。ollama.base-url指向本机 11434 端口——这是 Ollama 默认端口。最后那个开关llm.fallback.enabled是我们自己定义的,用来控制要不要启用自动降级。
3.3 手动切换的主要代码
先写一个"手动切换"版本,直观展示两个模型是怎么挂着、怎么通过配置切换的。这里用 Spring 的@Value注入配置。
packagecom.baiyunge;importdev.langchain4j.memory.chat.MessageWindowChatMemory;importdev.langchain4j.model.chat.ChatLanguageModel;importdev.langchain4j.model.openai.OpenAiChatModel;importdev.langchain4j.model.ollama.OllamaChatModel;importdev.langchain4j.service.AiServices;importdev.langchain4j.service.UserMessage;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importjava.time.Duration;/** * 手动切换版本的配置类: * 同时暴露 OpenAI 和 Ollama 两个 ChatLanguageModel bean, * 通过配置项 active.provider 决定当前用哪个。 */@ConfigurationpublicclassManualSwitchConfig{@Value("${openai.api-key}")privateStringopenaiApiKey;@Value("${openai.model-name}")privateStringopenaiModel;@Value("${openai.base-url}")privateStringopenaiBaseUrl;@Value("${ollama.base-url}")privateStringollamaBaseUrl;@Value("${ollama.model-name}")privateStringollamaModel;/** * OpenAI 主模型。超时设 30 秒,方便降级时感知。 */@BeanpublicChatLanguageModelopenAiModel(){returnOpenAiChatModel.builder().apiKey(openaiApiKey).modelName(openaiModel).baseUrl(openaiBaseUrl).temperature(0.7).timeout(Duration.ofSeconds(30)).build();}/** * Ollama 本地备用模型。 */@BeanpublicChatLanguageModelollamaModel(){returnOllamaChatModel.builder().baseUrl(ollamaBaseUrl).modelName(ollamaModel).temperature(0.7).timeout(Duration.ofSeconds(30)).build();}/** * 一个简单的 AI 客服接口,AiServices 会帮我们自动注入模型和记忆。 */publicinterfaceAssistant{Stringchat(@UserMessageStringuserMessage);}/** * 手动切换:active.provider=openai 用 OpenAI,=ollama 用本地。 */@BeanpublicAssistantmanualAssistant(@Value("${active.provider:openai}")StringactiveProvider,ChatLanguageModelopenAiModel,ChatLanguageModelollamaModel){ChatLanguageModelchoosen="ollama".equals(activeProvider)?ollamaModel:openAiModel;returnAiServices.builder(Assistant.class).chatLanguageModel(choosen).chatMemory(MessageWindowChatMemory.withMaxMessages(10)).build();}}拆解这段代码。OpenAiChatModel.builder()是 LangChain4j 1.x 的标准构造方式,.apiKey()、.modelName()、.baseUrl()分别设置认证、模型名、接口地址,.timeout()设请求超时。OllamaChatModel同理,因为它跑在本地,baseUrl` 指向本机 Ollama 服务。
Assistant接口是 LangChain4j 的声明式 AI 服务:你只写一个接口,标注@UserMessage的参数就是用户输入,AiServices.builder()会把模型、记忆自动接进去。MessageWindowChatMemory.withMaxMessages(10)表示保留最近 10 条消息作为上下文记忆。
manualAssistant这个方法根据active.provider配置选模型:配了ollama就用本地,否则用 OpenAI。这是最朴素的切换方式,适合开发和成本控制,但不智能——OpenAI 挂了它不会自己换。
3.4 自动降级的核心代码
现在上重点:用ConcurrentChatModel做故障自动切换。这是 LangChain4j 1.x 提供的官方"多模型容错"方案。
packagecom.baiyunge;importdev.langchain4j.model.chat.ChatLanguageModel;importdev.langchain4j.model.openai.OpenAiChatModel;importdev.langchain4j.model.ollama.OllamaChatModel;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.context.annotation.Primary;importjava.time.Duration;/** * 自动降级配置类: * 用 ConcurrentChatModel 包装 OpenAI(主)+ Ollama(备), * 主模型抛异常时自动切到备用模型,并支持主模型恢复后自动切回。 */@ConfigurationpublicclassFallbackConfig{@Value("${openai.api-key}")privateStringopenaiApiKey;@Value("${openai.model-name}")privateStringopenaiModel;@Value("${openai.base-url}")privateStringopenaiBaseUrl;@Value("${ollama.base-url}")privateStringollamaBaseUrl;@Value("${ollama.model-name}")privateStringollamaModel;/** * 返回一个"会自动切换"的模型,作为整个应用默认使用的唯一模型。 * @Primary 表示注入 ChatLanguageModel 时优先用这个。 */@Primary@BeanpublicChatLanguageModelfallbackModel(){ChatLanguageModelprimary=OpenAiChatModel.builder().apiKey(openaiApiKey).modelName(openaiModel).baseUrl(openaiBaseUrl).temperature(0.7).timeout(Duration.ofSeconds(15)).build();ChatLanguageModelbackup=OllamaChatModel.builder().baseUrl(ollamaBaseUrl).modelName(ollamaModel).temperature(0.7).timeout(Duration.ofSeconds(15)).build();// 把主模型放前面,备用模型放后面;主模型失败自动降级到备用returnConcurrentChatModel.builder().chatLanguageModels(primary,backup).build();}}这最后一段是精华。ConcurrentChatModel.builder().chatLanguageModels(primary, backup)把两个模型按"主、备"顺序塞进去。运行时它先调primary(OpenAI),如果 OpenAI 抛异常或超时,它捕获后自动改调backup(Ollama 本地)。@Primary注解保证 Spring 里不管你注入几个ChatLanguageModel的地方,默认都会拿到这个"会自动切换"的模型。
这样,你的业务层代码完全不用改:它只面向ChatLanguageModel编程,而拿到的是被包装过的、自带故障切换能力的模型。调用方无感知,这就是 Abstraction 的价值。
3.5 完整跑通的主程序
写一个可执行的入口类,把上面两个配置串起来。为了让读者能真正运行,我做一个简单的CommandLineRunner,用新写的ChatLanguageModel问几个问题,并打印当前用的是哪个模型。
packagecom.baiyunge;importdev.langchain4j.model.chat.ChatLanguageModel;importorg.springframework.boot.CommandLineRunner;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.context.annotation.Bean;/** * 启动类:跑通多 LLM 切换流程。 * 启动后会用默认模型(自动降级版)连续发两个问题, * 打印回复,方便观察是否正常、是否走了降级。 */@SpringBootApplicationpublicclassMultiLlmSwitchApplication{publicstaticvoidmain(String[]args){SpringApplication.run(MultiLlmSwitchApplication.class,args);}@BeanpublicCommandLineRunnerdemo(ChatLanguageModelchatLanguageModel){returnargs->{System.out.println("当前注入的模型类型: "+chatLanguageModel.getClass().getSimpleName());System.out.println("--- 问题 1 ---");Stringanswer1=chatLanguageModel.generate("用一句话介绍 LangChain4j 是什么?");System.out.println("回复: "+answer1);System.out.println("--- 问题 2(带上下文感)---");Stringanswer2=chatLanguageModel.generate("再补充一句它在多模型切换上的优势。");System.out.println("回复: "+answer2);};}}这段代码做了什么:MultiLlmSwitchApplication是 Spring Boot 启动类,demo方法是应用启动后自动跑的一段逻辑。它拿到的ChatLanguageModel就是我上面fallbackModel()那个被@Primary标记的"会自动切换"的模型。启动后它会打印模型类名(你会看到类似ConcurrentChatModel或DefaultConcurrentChatModel的名字,证明拿到的是包装后的模型),然后发两个问题看回复。
要刻意测试降级,就把 OpenAI 的 key 故意写错(比如sk-xxxx),启动后你会发现:OpenAI 报错 → 自动切到本地 Ollama → 问题依然有回复。这就是降级在起作用。
四、踩坑经验和最佳实践
这一节是我根据实际生产经验整理的,条条都是真坑。
坑 1:定时不能太短。给模型设超时时,别设 2 秒、3 秒这种极端值。本地小模型 + 低配电脑,一次生成可能就要十几秒。如果主模型超时时间设太短,Ollama 也被拖下水,造成"两层全挂"的假象。我建议主备都设 15~30 秒,让慢模型有足够余量。
坑 2:本地模型的 JSON/工具调用能力弱。如果业务里用了 Function Calling(让模型调用 Java 方法)或要求严格 JSON 输出,本地 7B、8B 小模型经常不听话:输出格式不对、截断、甚至直接拒绝。所以降级时要区分任务:需要复杂工具编排的重活,别轻易降级到太小的模型;只做纯文本问答的,降级到本地很稳妥。
坑 3:ConcurrentChatModel的失败判断是"抛异常"。它只在主模型抛异常时才切换。如果 OpenAI 因为内容审查返回了个"安全拒绝"类型的正常响应(不抛异常,只是内容不对),ConcurrentChatModel不会认为那是故障,也就不会切换。所以如果你想处理"响应不合规"这类语义问题,得在业务层自己加校验,不能指望自动降级代劳。
坑 4:两个模型的 Prompt 效果差异大。同一个 Prompt,GPT-4o 能很好执行,Llama3 可能理解偏。做降级时,备用的 Prompt 建议更简单直白、指令更明确。别指望一个 Prompt 通吃所有模型。
最佳实践清单:
- 主用云端大模型(质量高),备用本地小模型(免费、抗限流),做降级下限;
- 把
llm.fallback.enabled这种开关放到配置里,线上可以动态调整; - 记录每次模型调用和降级事件,打日志或上报指标,方便复盘"OpenAI 今天挂了几次";
- 重要的生产接口,给模型调用包一层超时熔断,别让它无限等;
- 环境变量存密钥(如
OPENAI_API_KEY),别硬编码进代码仓库。
五、性能对比和技术选型
说到选型,得先明确:"用户体感速度"和"模型成本"是两个不同指标。
响应速度:OpenAI 云端是网络往返,快的话一两秒;Ollama 本地依赖你的 GPU/CPU,好的显卡很快,纯 CPU 可能明显更慢。所以如果追求极致速度,本地 + 好显卡有优势;普通服务器上,云端通常更快。
成本:云端按 token 付费,量大就烧钱;Ollama 本地跑,一次部署后边际成本几乎为零,只是前期要有一台带显卡的机器。对高并发简单问答,本地模型性价比高。
我建议的技术选型分层策略(表格):
| 场景 | 推荐模型 | 理由 |
|---|---|---|
| 复杂推理、长文档、工具编排 | 云端大模型(GPT-4o/mini 等) | 质量高、遵从指令强 |
| 高并发、简单问答、纯文本 | 本地小模型(Llama3 等) | 免费、抗限流 |
| 主模型临时故障 | 本地备用自动接管 | 用ConcurrentChatModel兜底 |
关于框架选型:如果你已经在用 Spring 全家桶,LangChain4j 的 Spring Boot starter 集成非常顺;如果你要跟 Spring AI 打配合,两边都可以做 Provider 切换。本文选 LangChain4j 是因为它抽象更"模型无关",切换逻辑更薄、更透明。
一句话结论:没有"最好"的模型,只有"当前最合适"的模型;多 Provider 切换 + 自动降级,就是让你永远有备选、系统不宕机的工程手段。
六、总结
我们来收个尾。本文围绕"多 LLM 切换"讲了三层东西:
- 问题本质:生产环境不能只绑一家大模型,要解决单点故障和成本控制。
- 原理:LangChain4j 用
ChatLanguageModel统一所有模型;ConcurrentChatModel提供故障自动切换和恢复。 - 实战:给了手动切换和自动降级两套完整可运行代码,含 POM、配置、配置类、启动类。
核心收获,能用你自己的话复述就是:面向接口(ChatLanguageModel)编程,模型随便换;用ConcurrentChatModel做主备包装,OpenAI 挂了本地模型自动顶上,业务代码一行都不用改。
这套思路不限于 LangChain4j,任何"多 Provider 抽象 + 容错包装"的生产系统都适用。建议你先把 3.4 那段ConcurrentChatModel跑通,再把它接进自己的业务,观察降级日志,你会对"模型无关"这四个字有切身体会。
配置记得走环境变量,密钥别进仓库。多模型不是炫技,是让 AI 应用真扛得住线上波动。