2026/6/10 2:13:31
网站建设
项目流程
东莞网站建设 硅橡胶,协会网站建设目的,上海网站论坛建设,网站建设黄页在线免费系列文章目录
第一章 「Java AI实战」LangChain4J - 接入Xinference本地大模型 第二章 「Java AI实战」LangChain4J - ChatAPI 及常用配置 第三章 「Java AI实战」LangChain4J - 向量数据库接入与语义检索 第四章 「Java AI实战」LangChain4J - Agent智能体开发 第五章 「Java…系列文章目录第一章 「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 多模型配置MultiModelProperties2.2 ChatModel Bean 定义LangChain4jConfig2.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延迟敏感 → 尽量走本地 secondaryQUALITY质量优先 → 走云端 primaryscene场景sqlSQL 助手类场景 → 优先走本地后续好挂 MCP 工具、查库summary总结场景 → 可以继续扩展策略prompt 长度超长输入例如 2000 字符 → 走长上下文的 qwen-longprimary。Resilience4j 的使用方式只对primary 模型做熔断 重试secondary 作为备份CircuitBreaker(name “primaryModel”, fallbackMethod “chatFallback”)Retry(name “primaryModel”)一旦 primary 模型调用失败由 Resilience4j 触发 chatFallback在 chatFallback 中优先尝试 secondary 本地模型如果 secondary 也挂了再返回硬编码兜底文案。这样可以把“多模型路由” “主备切换”都封装到 服务层对 Controller 和前端来说永远只有一个 /api/chat。二、代码实践从配置到路由完整打通2.1 多模型配置MultiModelProperties用 ConfigurationProperties 管理两个模型的配置方便以后扩展更多模型。packageorg.example.config;importlombok.Data;importorg.springframework.boot.context.properties.ConfigurationProperties;importjava.time.Duration;DataConfigurationProperties(prefixmulti-model)publicclassMultiModelProperties{privateModelConfigprimarynewModelConfig();privateModelConfigsecondarynewModelConfig();DatapublicstaticclassModelConfig{/** 逻辑名称cloud-deepseek / local-xinference-qwen */privateStringname;/** OpenAI 协议 baseUrl */privateStringbaseUrl;/** API Key */privateStringapiKey;/** 模型名gpt-4o-mini / deepseek-chat / qwen2.5-chat 等 */privateStringmodelName;/** 温度 */privateDoubletemperature0.3;/** 超时时间 */privateDurationtimeoutDuration.ofSeconds(30);}}application.properties 中对应的配置# ----------------- 基础配置 ----------------- server.port8080 spring.application.namemulti-model-router # ----------------- 多模型配置 ----------------- # primary云端高质量模型 multi-model.primary.namecloud-deepseek multi-model.primary.base-urlhttps://dashscope.aliyuncs.com/compatible-mode/v1 multi-model.primary.model-nameqwen-long multi-model.primary.temperature0.3 multi-model.primary.timeout40s # secondary本地自建 OpenAI 网关 multi-model.secondary.namelocal-xinference-qwen multi-model.secondary.base-urlhttp://本地ip:9997/v1 multi-model.secondary.api-key11111111 multi-model.secondary.model-nameqwen2.5-vl-instruct multi-model.secondary.temperature0.2 multi-model.secondary.timeout1200s✅ 后面你可以很方便地再加一个 imageModel、codeModel 等只需要扩展配置和 Bean 定义就行。2.2 ChatModel Bean 定义LangChain4jConfig使用 LangChain4J 的 OpenAiChatModel分别创建两个模型 Beanpackageorg.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.ModelConfigcfgproperties.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.ModelConfigcfgproperties.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统一对外接口请求 DTOpackageorg.example.dto;importlombok.Data;DatapublicclassChatRequest{/** 用户输入问题 */privateStringmessage;/** 场景general / sql / summary ... */privateStringscenegeneral;/** * 优先级 * - FAST追求速度 → 走本地 secondary 模型 * - QUALITY追求效果 → 走云端 primary 模型带熔断降级 */privateStringpriorityQUALITY;}响应 DTOpackageorg.example.dto;importlombok.AllArgsConstructor;importlombok.Data;DataAllArgsConstructorpublicclassChatResponse{/** 实际使用的模型名 */privateStringmodel;/** 模型回复 */privateStringcontent;/** 是否发生降级 */privatebooleandegraded;}Controllerpackageorg.example.controller;importjakarta.annotation.Resource;importorg.example.dto.ChatRequest;importorg.example.dto.ChatResponse;importorg.example.service.MultiModelChatService;importorg.springframework.web.bind.annotation.*;RestControllerRequestMapping(/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;Slf4jServicepublicclassMultiModelChatService{Resource(nameprimaryChatModel)privateChatModelprimaryChatModel;Resource(namesecondaryChatModel)privateChatModelsecondaryChatModel;ResourceprivateMultiModelPropertiesproperties;/** * 对外唯一入口 * 1先根据场景/长度/优先级做路由决策 * 2若决定走主模型则由 Resilience4j 负责熔断重试降级 */CircuitBreaker(nameprimaryModel,fallbackMethodchatFallback)Retry(nameprimaryModel)publicChatResponsechat(ChatRequestrequest){Stringscenerequest.getScene();Stringpriorityrequest.getPriority();StringpromptbuildPrompt(scene,request.getMessage());RouteDecisiondecisiondecideRoute(scene,priority,prompt);log.info(本次请求路由决策target{}reason{},decision.target(),decision.reason());// 直接命中本地模型的路由不经过熔断逻辑if(decision.target()TargetModel.SECONDARY_DIRECT){StringanswersecondaryChatModel.chat(prompt);returnnewChatResponse(properties.getSecondary().getName(),answer,false);}// 需要走主模型带熔断保护StringanswerprimaryChatModel.chat(prompt);returnnewChatResponse(properties.getPrimary().getName(),answer,false);// 注意如果这里抛异常会进 chatFallback(...)}/** * 熔断/重试之后的降级逻辑主模型不可用时自动切到本地模型。 */publicChatResponsechatFallback(ChatRequestrequest,Throwablethrowable){Stringscenerequest.getScene();StringpromptbuildPrompt(scene,request.getMessage());log.warn(primary 模型调用失败降级到 secondary 模型: {}异常{},properties.getSecondary().getName(),throwable.toString());try{StringbackupsecondaryChatModel.chat(prompt);returnnewChatResponse(properties.getSecondary().getName(),[降级到本地模型]\nbackup,true);}catch(Exceptione){log.error(secondary 本地模型也调用失败执行最终兜底,e);StringsafeAnswer当前智能助手服务暂时不可用请稍后再试。;returnnewChatResponse(hard-fallback,safeAnswer,true);}}/** * 路由决策根据 priority scene prompt 长度 多维度选择模型。 * - FAST优先用本地模型延迟敏感 * - scenesql默认用本地离数据库近同时你后面可以挂 MCP 工具 * - 超长输入走 qwen-longprimary利用长上下文 * - 其他默认走 primary 高质量模型 */privateRouteDecisiondecideRoute(Stringscene,Stringpriority,Stringprompt){StringsceneSafescene!null?scene:general;StringprioritySafepriority!null?priority:QUALITY;intlengthprompt!null?prompt.length():0;// 1延迟优先FAST → 直接本地if(FAST.equalsIgnoreCase(prioritySafe)){returnnewRouteDecision(TargetModel.SECONDARY_DIRECT,priorityFAST走本地模型以降低延迟);}// 2SQL 场景走本地后续可以挂 MCP 工具走 DB 相关增强if(sql.equalsIgnoreCase(sceneSafe)){returnnewRouteDecision(TargetModel.SECONDARY_DIRECT,scenesql优先走本地 SQL 专用模型);}// 3超长输入走 qwen-longprimaryif(length2000){returnnewRouteDecision(TargetModel.PRIMARY,promptLengthlength超长上下文走云端 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 字以内\nuserMessage;}// 默认普通聊天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 本地模型返回内容前加上一句 [降级到本地模型]第二层兜底真的什么都挂了返回固定提示 modelhard-fallback方便监控侧统计。2.5 Resilience4j 配置application.properties 中的熔断 重试配置# ----------------- Resilience4j 熔断配置 ----------------- resilience4j.circuitbreaker.instances.primaryModel.register-health-indicatortrue resilience4j.circuitbreaker.instances.primaryModel.sliding-window-typeCOUNT_BASED resilience4j.circuitbreaker.instances.primaryModel.sliding-window-size10 resilience4j.circuitbreaker.instances.primaryModel.minimum-number-of-calls5 resilience4j.circuitbreaker.instances.primaryModel.failure-rate-threshold50 resilience4j.circuitbreaker.instances.primaryModel.wait-duration-in-open-state10s resilience4j.circuitbreaker.instances.primaryModel.permitted-number-of-calls-in-half-open-state2 resilience4j.circuitbreaker.instances.primaryModel.automatic-transition-from-open-to-half-open-enabledtrue # ----------------- Resilience4j 重试配置 ----------------- resilience4j.retry.instances.primaryModel.max-attempts2 resilience4j.retry.instances.primaryModel.wait-duration200ms # ----------------- Actuator 端点暴露 ----------------- management.endpoints.web.exposure.includehealth,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;SpringBootApplicationConfigurationPropertiesScanpublicclassMultiModelRouterApplication{publicstaticvoidmain(String[]args){SpringApplication.run(MultiModelRouterApplication.class,args);}}总结从单模型调用到「多模型路由 高可用」的一小步这篇文章我们用一个相对轻量的 Demo走通了这样一条路径用 LangChain4J 接多个模型同时连上云端 DashScope 模型和本地 Xinference 网关把模型配置抽象成 MultiModelProperties便于扩展。在 Service 层封装多模型路由策略按 priority / scene / promptLength 决定走哪个模型用简单的枚举 record 把路由决策结构化便于以后接更多维度用户等级、调用成本等。用 Resilience4j 给主模型加上熔断 重试 降级CircuitBreaker Retry fallbackMethod 组合主模型挂了自动切本地全部挂了再返回兜底文案日志里带 model / degraded方便后续埋点 监控。对外暴露一个简单的 /api/chat前端/调用方只需要关心一个统一入口模型怎么选、怎么降级全在后端服务里“黑盒”处理。