news 2026/4/15 18:41:54

「Java AI实战」LangChain4J - 多模型路由 + Resilience4j 熔断降级

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
「Java AI实战」LangChain4J - 多模型路由 + Resilience4j 熔断降级

系列文章目录

第一章 「Java AI实战」LangChain4J - 接入Xinference本地大模型
第二章 「Java AI实战」LangChain4J - ChatAPI 及常用配置
第三章 「Java AI实战」LangChain4J - 向量数据库接入与语义检索
第四章 「Java AI实战」LangChain4J - Agent智能体开发
第五章 「Java AI实战」LangChain4J - 记忆缓存
第六章 「Java AI实战」LangChain4J - 文本分类器实践
第七章 「Java AI实战」LangChain4J - 多 Agent 智能体协作
第八章 「Java AI实战」LangChain4J - 多模型路由 + Resilience4j 熔断降级


文章目录

  • 系列文章目录
  • 前言
  • 一、简介:整体设计思路
  • 二、代码实践:从配置到路由完整打通
    • 2.1 多模型配置:MultiModelProperties
    • 2.2 ChatModel Bean 定义:LangChain4jConfig
    • 2.3 DTO & Controller:统一对外接口
    • 2.4 核心:多模型路由 + Resilience4j 熔断降级
    • 2.5 Resilience4j 配置
    • 2.6 启动类:打开 @ConfigurationPropertiesScan
  • 总结

前言

现在做 AI 接入,很容易进一个“单模型思维陷阱”:

  • 只接了一个云端大模型:效果很好,但、对外网依赖强;

  • 只接了一个本地大模型:成本可控、数据安全,但效果和稳定性未必始终在线;

  • 系统里不同场景的需求也不一样:

    • 一些场景追求质量(复杂问答、长文总结);
    • 一些场景追求时延(聊天、实时联机);

还有一些场景天然适合本地部署(SQL 助手、和数据库在同一内网)。

于是,多模型共存 + 智能路由 + 熔断降级,就变成了一个非常自然的工程化诉求:

  • 云端模型:QUALITY优先,作为主力模型;
  • 本地模型:作为FAST/ 专用场景 / 熔断降级的备份;
  • 当云端模型挂了、网络抖了、费用超标时,系统能自动切回本地,不至于整体不可用。

这篇文章,就用一套完整的 Demo,落地下面这件事 👇

一个统一的 /api/chat 接口,背后根据优先级、场景、上下文长度自动选择模型,并用 Resilience4j 为主模型加上熔断 +
重试 + 降级保护。


一、简介:整体设计思路

  • 多模型的角色分工
    在这个 Demo 里我们抽象了两个“角色”:
    • primary 模型:云端高质量模型
      • 示例:阿里云 DashScope 的 qwen-long
      • 负责长上下文、高质量生成类任务;
    • secondary 模型:本地自建 OpenAI 网关上的模型
      • 示例:Xinference + qwen2.5-vl-instruct
      • 负责低延迟、SQL 专用场景、熔断后的降级兜底
  • 路由维度设计
    路由逻辑不是简单的 if-else,而是从多个维度综合判断:
    • priority(优先级)
      • FAST:延迟敏感 → 尽量走本地 secondary;
      • QUALITY:质量优先 → 走云端 primary;
    • scene(场景)
      • sql:SQL 助手类场景 → 优先走本地(后续好挂 MCP 工具、查库);
      • summary:总结场景 → 可以继续扩展策略;
    • prompt 长度
      • 超长输入(例如 > 2000 字符) → 走长上下文的 qwen-long(primary)。
  • Resilience4j 的使用方式
    只对primary 模型做熔断 + 重试,secondary 作为备份:
    • @CircuitBreaker(name = “primaryModel”, fallbackMethod = “chatFallback”)
    • @Retry(name = “primaryModel”)

一旦 primary 模型调用失败:

  1. 由 Resilience4j 触发 chatFallback;
  2. 在 chatFallback 中优先尝试 secondary 本地模型;
  3. 如果 secondary 也挂了,再返回硬编码兜底文案。

这样,可以把“多模型路由” + “主备切换”都封装到 服务层,对 Controller 和前端来说,永远只有一个 /api/chat。

二、代码实践:从配置到路由完整打通

2.1 多模型配置:MultiModelProperties

用 @ConfigurationProperties 管理两个模型的配置,方便以后扩展更多模型。

packageorg.example.config;importlombok.Data;importorg.springframework.boot.context.properties.ConfigurationProperties;importjava.time.Duration;@Data@ConfigurationProperties(prefix="multi-model")publicclassMultiModelProperties{privateModelConfigprimary=newModelConfig();privateModelConfigsecondary=newModelConfig();@DatapublicstaticclassModelConfig{/** 逻辑名称:cloud-deepseek / local-xinference-qwen */privateStringname;/** OpenAI 协议 baseUrl */privateStringbaseUrl;/** API Key */privateStringapiKey;/** 模型名:gpt-4o-mini / deepseek-chat / qwen2.5-chat 等 */privateStringmodelName;/** 温度 */privateDoubletemperature=0.3;/** 超时时间 */privateDurationtimeout=Duration.ofSeconds(30);}}

application.properties 中对应的配置:

# ----------------- 基础配置 ----------------- server.port=8080 spring.application.name=multi-model-router # ----------------- 多模型配置 ----------------- # primary:云端高质量模型 multi-model.primary.name=cloud-deepseek multi-model.primary.base-url=https://dashscope.aliyuncs.com/compatible-mode/v1 multi-model.primary.model-name=qwen-long multi-model.primary.temperature=0.3 multi-model.primary.timeout=40s # secondary:本地自建 OpenAI 网关 multi-model.secondary.name=local-xinference-qwen multi-model.secondary.base-url=http://本地ip:9997/v1 multi-model.secondary.api-key=11111111 multi-model.secondary.model-name=qwen2.5-vl-instruct multi-model.secondary.temperature=0.2 multi-model.secondary.timeout=1200s

✅ 后面你可以很方便地再加一个 imageModel、codeModel 等,只需要扩展配置和 Bean 定义就行。

2.2 ChatModel Bean 定义:LangChain4jConfig

使用 LangChain4J 的 OpenAiChatModel,分别创建两个模型 Bean:

packageorg.example.config;importdev.langchain4j.model.chat.ChatModel;importdev.langchain4j.model.openai.OpenAiChatModel;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;@ConfigurationpublicclassLangChain4jConfig{@Bean("primaryChatModel")publicChatModelprimaryChatModel(MultiModelPropertiesproperties){MultiModelProperties.ModelConfigcfg=properties.getPrimary();returnOpenAiChatModel.builder().baseUrl(cfg.getBaseUrl()).apiKey(System.getenv("LANGCHAIN4J_KEY")).modelName(cfg.getModelName()).temperature(cfg.getTemperature()).timeout(cfg.getTimeout()).build();}@Bean("secondaryChatModel")publicChatModelsecondaryChatModel(MultiModelPropertiesproperties){MultiModelProperties.ModelConfigcfg=properties.getSecondary();returnOpenAiChatModel.builder().baseUrl(cfg.getBaseUrl()).apiKey(System.getenv("LANGCHAIN4J_KEY")).modelName(cfg.getModelName()).temperature(cfg.getTemperature()).timeout(cfg.getTimeout()).build();}}

这里我用的是环境变量 LANGCHAIN4J_KEY,也可以直接用 cfg.getApiKey(),按你的安全策略来。

2.3 DTO & Controller:统一对外接口

请求 DTO:

packageorg.example.dto;importlombok.Data;@DatapublicclassChatRequest{/** 用户输入问题 */privateStringmessage;/** 场景:general / sql / summary ... */privateStringscene="general";/** * 优先级: * - FAST:追求速度 → 走本地 secondary 模型 * - QUALITY:追求效果 → 走云端 primary 模型(带熔断降级) */privateStringpriority="QUALITY";}

响应 DTO:

packageorg.example.dto;importlombok.AllArgsConstructor;importlombok.Data;@Data@AllArgsConstructorpublicclassChatResponse{/** 实际使用的模型名 */privateStringmodel;/** 模型回复 */privateStringcontent;/** 是否发生降级 */privatebooleandegraded;}

Controller

packageorg.example.controller;importjakarta.annotation.Resource;importorg.example.dto.ChatRequest;importorg.example.dto.ChatResponse;importorg.example.service.MultiModelChatService;importorg.springframework.web.bind.annotation.*;@RestController@RequestMapping("/api/chat")publicclassChatController{@ResourceprivateMultiModelChatServicechatService;@PostMappingpublicChatResponsechat(@RequestBodyChatRequestchatRequest){returnchatService.chat(chatRequest);}}

2.4 核心:多模型路由 + Resilience4j 熔断降级

真正的逻辑都在 MultiModelChatService 里:

packageorg.example.service;importdev.langchain4j.model.chat.ChatModel;importio.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;importio.github.resilience4j.retry.annotation.Retry;importjakarta.annotation.Resource;importlombok.extern.slf4j.Slf4j;importorg.example.config.MultiModelProperties;importorg.example.dto.ChatRequest;importorg.example.dto.ChatResponse;importorg.springframework.stereotype.Service;@Slf4j@ServicepublicclassMultiModelChatService{@Resource(name="primaryChatModel")privateChatModelprimaryChatModel;@Resource(name="secondaryChatModel")privateChatModelsecondaryChatModel;@ResourceprivateMultiModelPropertiesproperties;/** * 对外唯一入口: * 1)先根据场景/长度/优先级做路由决策; * 2)若决定走主模型,则由 Resilience4j 负责熔断+重试+降级; */@CircuitBreaker(name="primaryModel",fallbackMethod="chatFallback")@Retry(name="primaryModel")publicChatResponsechat(ChatRequestrequest){Stringscene=request.getScene();Stringpriority=request.getPriority();Stringprompt=buildPrompt(scene,request.getMessage());RouteDecisiondecision=decideRoute(scene,priority,prompt);log.info("本次请求路由决策:target={},reason={}",decision.target(),decision.reason());// 直接命中本地模型的路由,不经过熔断逻辑if(decision.target()==TargetModel.SECONDARY_DIRECT){Stringanswer=secondaryChatModel.chat(prompt);returnnewChatResponse(properties.getSecondary().getName(),answer,false);}// 需要走主模型(带熔断保护)Stringanswer=primaryChatModel.chat(prompt);returnnewChatResponse(properties.getPrimary().getName(),answer,false);// 注意:如果这里抛异常,会进 chatFallback(...)}/** * 熔断/重试之后的降级逻辑:主模型不可用时,自动切到本地模型。 */publicChatResponsechatFallback(ChatRequestrequest,Throwablethrowable){Stringscene=request.getScene();Stringprompt=buildPrompt(scene,request.getMessage());log.warn("primary 模型调用失败,降级到 secondary 模型: {},异常={}",properties.getSecondary().getName(),throwable.toString());try{Stringbackup=secondaryChatModel.chat(prompt);returnnewChatResponse(properties.getSecondary().getName(),"[降级到本地模型]\n"+backup,true);}catch(Exceptione){log.error("secondary 本地模型也调用失败,执行最终兜底",e);StringsafeAnswer="当前智能助手服务暂时不可用,请稍后再试。";returnnewChatResponse("hard-fallback",safeAnswer,true);}}/** * 路由决策:根据 priority + scene + prompt 长度 多维度选择模型。 * - FAST:优先用本地模型(延迟敏感) * - scene=sql:默认用本地(离数据库近,同时你后面可以挂 MCP 工具) * - 超长输入:走 qwen-long(primary),利用长上下文 * - 其他:默认走 primary 高质量模型 */privateRouteDecisiondecideRoute(Stringscene,Stringpriority,Stringprompt){StringsceneSafe=scene!=null?scene:"general";StringprioritySafe=priority!=null?priority:"QUALITY";intlength=prompt!=null?prompt.length():0;// 1)延迟优先:FAST → 直接本地if("FAST".equalsIgnoreCase(prioritySafe)){returnnewRouteDecision(TargetModel.SECONDARY_DIRECT,"priority=FAST,走本地模型以降低延迟");}// 2)SQL 场景:走本地(后续可以挂 MCP 工具,走 DB 相关增强)if("sql".equalsIgnoreCase(sceneSafe)){returnnewRouteDecision(TargetModel.SECONDARY_DIRECT,"scene=sql,优先走本地 SQL 专用模型");}// 3)超长输入:走 qwen-long(primary)if(length>2000){returnnewRouteDecision(TargetModel.PRIMARY,"promptLength="+length+",超长上下文,走云端 qwen-long");}// 4)其他:走 primary 高质量云端模型returnnewRouteDecision(TargetModel.PRIMARY,"默认策略:QUALITY 优先,走云端主模型");}privateStringbuildPrompt(Stringscene,StringuserMessage){if("sql".equalsIgnoreCase(scene)){return"你是一个资深数据库开发助手,请用简洁的 SQL 回答问题,并附上简要说明。\n用户问题:"+userMessage;}if("summary".equalsIgnoreCase(scene)){return"请用中文帮我总结下面内容,控制在 200 字以内:\n"+userMessage;}// 默认普通聊天returnuserMessage;}/** * 路由目标枚举 */enumTargetModel{PRIMARY,// 走主模型(带熔断保护)SECONDARY_DIRECT// 直接走本地模型}/** * 路由决策结果 */recordRouteDecision(TargetModeltarget,Stringreason){}}

核心逻辑拆解

  • 入口方法 chat(…)

    • 做了两件事:路由 + 调用;
    • 只有选择 PRIMARY 时,才进入被 @CircuitBreaker 保护的逻辑;
    • SECONDARY_DIRECT 直接走本地模型,不额外套熔断。
  • 路由决策decideRoute(…)

    • 根据 priority、scene、prompt.length(),返回一个 RouteDecision:
    • 这样可以很容易扩展新策略(比如给 summary 场景单独定规则)。
  • 熔断 + 重试

    • @Retry(name = “primaryModel”) 负责在失败时再试一次(应用层 Retrying);
    • 超过阈值后,由 @CircuitBreaker 统计失败率,熔断打开之后直接短路到 chatFallback,保护后端模型。
  • 降级逻辑chatFallback(…)

    • 第一层降级:调用 secondary 本地模型,返回内容前加上一句 [降级到本地模型];
    • 第二层兜底:真的什么都挂了,返回固定提示 + model=hard-fallback,方便监控侧统计。

2.5 Resilience4j 配置

application.properties 中的熔断 + 重试配置:

# ----------------- Resilience4j 熔断配置 ----------------- resilience4j.circuitbreaker.instances.primaryModel.register-health-indicator=true resilience4j.circuitbreaker.instances.primaryModel.sliding-window-type=COUNT_BASED resilience4j.circuitbreaker.instances.primaryModel.sliding-window-size=10 resilience4j.circuitbreaker.instances.primaryModel.minimum-number-of-calls=5 resilience4j.circuitbreaker.instances.primaryModel.failure-rate-threshold=50 resilience4j.circuitbreaker.instances.primaryModel.wait-duration-in-open-state=10s resilience4j.circuitbreaker.instances.primaryModel.permitted-number-of-calls-in-half-open-state=2 resilience4j.circuitbreaker.instances.primaryModel.automatic-transition-from-open-to-half-open-enabled=true # ----------------- Resilience4j 重试配置 ----------------- resilience4j.retry.instances.primaryModel.max-attempts=2 resilience4j.retry.instances.primaryModel.wait-duration=200ms # ----------------- Actuator 端点暴露 ----------------- management.endpoints.web.exposure.include=health,info,metrics

在生产环境,你可以结合 Actuator 的 /actuator/metrics、Prometheus 等,把 primaryModel 的熔断状态、失败率等指标都监控起来。

2.6 启动类:打开 @ConfigurationPropertiesScan

最后是标准的 Spring Boot 启动类:

packageorg.example;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.boot.context.properties.ConfigurationPropertiesScan;@SpringBootApplication@ConfigurationPropertiesScanpublicclassMultiModelRouterApplication{publicstaticvoidmain(String[]args){SpringApplication.run(MultiModelRouterApplication.class,args);}}

总结

从单模型调用,到「多模型路由 + 高可用」的一小步,
这篇文章,我们用一个相对轻量的 Demo,走通了这样一条路径:

  1. 用 LangChain4J 接多个模型
    • 同时连上云端 DashScope 模型和本地 Xinference 网关;
    • 把模型配置抽象成 MultiModelProperties,便于扩展。
  2. 在 Service 层封装多模型路由策略
    • 按 priority / scene / promptLength 决定走哪个模型;
    • 用简单的枚举 + record 把路由决策结构化,便于以后接更多维度(用户等级、调用成本等)。
  3. 用 Resilience4j 给主模型加上熔断 + 重试 + 降级
    • @CircuitBreaker + @Retry + fallbackMethod 组合;
    • 主模型挂了自动切本地,全部挂了再返回兜底文案;
    • 日志里带 model / degraded,方便后续埋点 & 监控。
  4. 对外暴露一个简单的 /api/chat
    • 前端/调用方只需要关心一个统一入口;
    • 模型怎么选、怎么降级,全在后端服务里“黑盒”处理。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/4 2:26:24

零基础学习使用DockerHub:手把手教你发布容器镜像

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容: 创建一个交互式DockerHub新手教程应用,通过步骤式引导帮助用户完成从注册账号、构建Docker镜像到发布到DockerHub的全过程。包含实时命令行模拟器和常见问题解答。使用V…

作者头像 李华
网站建设 2026/4/10 17:29:33

3步实战粒子群优化:从问题建模到高效求解

3步实战粒子群优化:从问题建模到高效求解 【免费下载链接】pyswarms A research toolkit for particle swarm optimization in Python 项目地址: https://gitcode.com/gh_mirrors/py/pyswarms 粒子群优化算法是解决复杂优化问题的利器,如何在真实…

作者头像 李华
网站建设 2026/4/14 15:14:27

手把手带你解析复现3D点云检测经典之作PointNet

传统的目标检测算法已经非常成熟,例如 YOLO 系列、DETR、Faster R-CNN 等,它们主要处理的是规则的二维图像数据。在图像中,像素按照规则网格排列,不同网格之间排列的不同会导致图像结果完全不同,这种有序性非常适合卷积…

作者头像 李华
网站建设 2026/4/15 4:52:49

如何轻松管理浏览器标签页:Tab-Session-Manager完整指南

如何轻松管理浏览器标签页:Tab-Session-Manager完整指南 【免费下载链接】Tab-Session-Manager WebExtensions for restoring and saving window / tab states 项目地址: https://gitcode.com/gh_mirrors/ta/Tab-Session-Manager 你是否曾经遇到过这样的情况…

作者头像 李华
网站建设 2026/4/14 6:31:06

配网潮流计算与MATLAB编程:探索分布式电源的影响

配网潮流计算/MATLAB编程 1.配网潮流计算(前推回代法) 2.考虑分布式电源对配网潮流的影响。 注:下图为IEEE33节点系统接入分布式电源之后的潮流仿真图在电力系统领域,配网潮流计算是一项至关重要的任务,它帮助我们了解电力网络中的…

作者头像 李华