多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

LangChain4j 多LLM切换实战:OpenAI断连秒切Ollama本地模型

LangChain4j 多LLM切换实战:OpenAI断连秒切Ollama本地模型 你正在开发一个 AI 客服用的 OpenAI 接口结果某天官方限流、导致线上问答全线超时。救场方案是本地再部署一个 Ollama 小模型兜底。本文就用 LangChain4j 1.19Java 生态把这套主模型 本地备用模型的切换与自动降级逻辑完整写一遍含可运行代码。解决的核心问题如何用一个统一接口在多个大模型 Provider 之间按需/按故障自动切换且做到 OpenAI 挂了本地模型无缝接管。一、这个问题到底是什么先说场景。大多数生产级的 AI 应用不会只绑定一家大模型厂商。原因很直白单点故障。OpenAI、通义、文心这些云厂商偶尔会限流、抖动、或者免费额度烧完。这时候你希望应用不崩能自动切到另一家或者本地模型继续干活。再说成本。云端大模型按 token 收费日常简单问答用贵的模型就是烧钱。很多人会做分层重活给大模型简单活给便宜的本地小模型。而底层这些模型接口五花八门OpenAI 用一套调用方式Ollama 本地是另一套如果代码里到处硬编码切换逻辑后期维护会哭。LangChain4j 的价值就在这里它把所有大模型统一抽象成ChatLanguageModel这一个接口。不管背后是 OpenAI、Ollama、通义还是本地跑的任何一个模型你的业务代码只认这一个接口。本文要解决的就是用 LangChain4j 同时接上 OpenAI 和本地 Ollama 两个模型做一个主备切换优先用 OpenAI挂了或超时自动切到 Ollama做成一个可手动切换的开关方便日常测试和成本控制。一句话定位这是 AI 工程化里的模型路由与降级是所有上生产的多模型应用都绕不开的一环。二、底层原理到底怎么回事先搞清楚 LangChain4j 是怎么把不同厂商模型统一起来的。LangChain4j 的核心抽象叫ChatLanguageModel它定义了一个核心方法给你一串对话消息ListChatMessage返回一个模型回复ResponseAiMessage。OpenAI、Ollama、通义……每一家都是这个接口的一个实现类。比如OpenAiChatModel负责对接 OpenAI 的 APIOllamaChatModel负责对接本地 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 CentralLangChain4j 当前 GA 版本是1.19.0langchain4j-open-ai、langchain4j-ollama均为 1.19.0。需要 JDK 21。3.1 建项目和配 POM先建一个普通 Maven 工程pom.xml引入两个模型 Provider 和一个 Spring Boot 启动器本地方便跑也可以只用纯 Java 不用 Spring这里用 Spring Boot 演示配置注入。?xml version1.0 encodingUTF-8?projectxmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion4.1.0/versionrelativePath//parentgroupIdcom.baiyunge/groupIdartifactIdmulti-llm-switch/artifactIdversion1.0.0/versionnamemulti-llm-switch/namepropertiesjava.version21/java.versionlangchain4j.version1.19.0/langchain4j.version/propertiesdependencies!-- LangChain4j 的 Spring Boot 启动器自动配置 AiServices、模型 bean 等 --dependencygroupIddev.langchain4j/groupIdartifactIdlangchain4j-spring-boot-starter/artifactIdversion${langchain4j.version}/version/dependency!-- 对接 OpenAI 官方 API --dependencygroupIddev.langchain4j/groupIdartifactIdlangchain4j-open-ai/artifactIdversion${langchain4j.version}/version/dependency!-- 对接本地 Ollama 服务 --dependencygroupIddev.langchain4j/groupIdartifactIdlangchain4j-ollama/artifactIdversion${langchain4j.version}/version/dependency/dependenciesbuildpluginsplugingroupIdorg.springframework.boot/groupIdartifactIdspring-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 KeyOllama 只需要本地服务地址。# OpenAI 主模型相关配置 openai.api-key${OPENAI_API_KEY:sk-xxxx} openai.model-namegpt-4o-mini openai.base-urlhttps://api.openai.com # Ollama 备用模型相关配置 ollama.base-urlhttp://localhost:11434 ollama.model-namellama3 # 是否开启自动降级生产建议 true llm.fallback.enabledtrue这里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.provideropenai 用 OpenAIollama 用本地。 */BeanpublicAssistantmanualAssistant(Value(${active.provider:openai})StringactiveProvider,ChatLanguageModelopenAiModel,ChatLanguageModelollamaModel){ChatLanguageModelchoosenollama.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 时优先用这个。 */PrimaryBeanpublicChatLanguageModelfallbackModel(){ChatLanguageModelprimaryOpenAiChatModel.builder().apiKey(openaiApiKey).modelName(openaiModel).baseUrl(openaiBaseUrl).temperature(0.7).timeout(Duration.ofSeconds(15)).build();ChatLanguageModelbackupOllamaChatModel.builder().baseUrl(ollamaBaseUrl).modelName(ollamaModel).temperature(0.7).timeout(Duration.ofSeconds(15)).build();// 把主模型放前面备用模型放后面主模型失败自动降级到备用returnConcurrentChatModel.builder().chatLanguageModels(primary,backup).build();}}这最后一段是精华。ConcurrentChatModel.builder().chatLanguageModels(primary, backup)把两个模型按主、备顺序塞进去。运行时它先调primaryOpenAI如果 OpenAI 抛异常或超时它捕获后自动改调backupOllama 本地。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 ---);Stringanswer1chatLanguageModel.generate(用一句话介绍 LangChain4j 是什么);System.out.println(回复: answer1);System.out.println(--- 问题 2带上下文感---);Stringanswer2chatLanguageModel.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 小模型经常不听话输出格式不对、截断、甚至直接拒绝。所以降级时要区分任务需要复杂工具编排的重活别轻易降级到太小的模型只做纯文本问答的降级到本地很稳妥。坑 3ConcurrentChatModel的失败判断是抛异常。它只在主模型抛异常时才切换。如果 OpenAI 因为内容审查返回了个安全拒绝类型的正常响应不抛异常只是内容不对ConcurrentChatModel不会认为那是故障也就不会切换。所以如果你想处理响应不合规这类语义问题得在业务层自己加校验不能指望自动降级代劳。坑 4两个模型的 Prompt 效果差异大。同一个 PromptGPT-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 应用真扛得住线上波动。
返回列表