三框架模型动态切换实现对比:AgentScope / LangGraph4J / Spring AI
一、问题背景在 AI Agent 项目 babi 中三个模块分别使用三种 Java AI 框架AgentScope Java、LangGraph4J基于 LangChain4J和 Spring AI 2.0。三个模块各自实现了自定义的DashScopeChatModel将阿里云 DashScope SDK 适配到各自的框架接口。但有一个共同问题模型名在构造时通过final字段绑定启动后不可变。前端页面需要一个模型选择下拉框让用户在每次对话时选择不同的模型如 Qwen3.7 Plus、DeepSeek V4 Flash 等。这意味着后端必须支持per-request 模型覆写——同一次服务运行中不同请求可以使用不同的模型。三个框架的设计哲学不同实现 per-request 模型覆写的方式也截然不同。本文从源码层面剖析三种方案的底层机制。二、共同约束final 不可变的模型名三个模块的DashScopeChatModel都遵循相同的模式// babi-spring / babi-graph 自定义 DashScopeChatModelprivatefinalStringmodel;privatefinalbooleanmultimodal;privatefinalMultiModalConversationconversation;// 多模态API客户端privatefinalGenerationgeneration;// 文本API客户端publicDashScopeChatModel(StringapiKey,Stringmodel,...){this.modelmodel;this.multimodalDashScopeEndpointUtil.isMultimodalModel(model);this.conversationmultimodal?newMultiModalConversation():null;this.generationmultimodal?null:newGeneration();}babi-agent 模块更特殊——它使用的是 AgentScope SDK 外部类io.agentscope.extensions.model.dashscope.DashScopeChatModel源码不可修改// AgentScope SDK 内部private final 不可变privatefinalStringmodelName;// line 68privatefinalEndpointTypeendpointType;// line 72关键约束有两个第一模型名是final构造后不可更改第二多模态路由MultiModalConversationvsGeneration在构造时根据模型名决定整个生命周期固定走同一个 DashScope API 端点。如果用户从多模态模型qwen3.7-plus切换到文本模型deepseek-v4-flash必须走不同的 API 端点。三、Spring AI 2.0 方案原生 ChatOptions 覆写Spring AI 2.0 的ChatClient原生支持 per-request 选项覆写这是最干净的方案。3.1 框架机制Spring AI 的ChatClient.prompt()返回一个可链式配置的ChatClientRequestSpec其中.options()方法可以注入 per-request 的ChatOptions// BabiService.streamChat() — babi-spring 模块varpromptSpecchatClient.prompt().user(userMessage).advisors(a-a.param(ChatMemory.CONVERSATION_ID,sessionId));if(model!null!model.isBlank()){promptSpec.options(ToolCallingChatOptions.builder().model(model));}returnpromptSpec.stream().chatResponse()...;ToolCallingChatOptions.builder()传入的是 builder 而非已构建对象——Spring AI 2.0 的options()方法签名是B extends ChatOptions.BuilderB options(B builder)接受 builder 泛型以支持 fluent chaining。3.2 DashScopeChatModel 改造DashScopeChatModel 需要从Prompt.getOptions()中读取覆写的模型名// resolveModel 方法 — 每请求动态解析privateStringresolveModel(ChatOptionsoptions){if(options!nulloptions.getModel()!null!options.getModel().isBlank()){returnoptions.getModel();}returndefaultModel;}关键是两个 SDK 客户端MultiModalConversation和Generation都预创建——它们是无状态轻量对象不占资源。每次请求根据resolveModel()返回的模型名通过DashScopeEndpointUtil.isMultimodalModel(model)动态选择走哪个客户端OverridepublicChatResponsecall(Promptprompt){StringmodelresolveModel(prompt.getOptions());if(DashScopeEndpointUtil.isMultimodalModel(model)){// 用 conversation 客户端走多模态 API}else{// 用 generation 客户端走文本 API}}在参数构建阶段模型名也从final字段改为动态解析privateGenerationParam.GenerationParamBuilder?,?buildTextParam(ListMessagemessages,Promptprompt,booleanstreaming){StringmodelresolveModel(prompt.getOptions());returnGenerationParam.builder().apiKey(apiKey).model(model)// 动态模型名.messages(messages)...;}这是最符合框架原生设计的方案。Spring AI 的ChatOptions机制天然为 per-request 覆写而设计改造量最小。四、LangGraph4J 方案ThreadLocal 旁路传递LangGraph4J 的情况更复杂。AgentExecutor 在构造时绑定了StreamingChatModel其内部构建ChatRequest时使用固定参数不支持从 graph state 注入 per-request 的ChatRequestParameters。4.1 为什么不能用 ChatRequestParametersLangGraph4J 的AgentExecutor.builder().chatModel(streamingChatModel)在 build 时绑定了模型实例。运行时AgentExecutor内部通过doChat(ChatRequest)调用模型ChatRequest的参数来自defaultRequestParameters()无法从外部 graph state 注入。如果强行修改ChatRequestParameters需要侵入AgentExecutor的内部逻辑这违背了不改框架源码的原则。4.2 ThreadLocal 旁路方案解决思路与 graph 模块已有的 sink 注册模式一致——用一个静态ThreadLocal传递模型覆写DashScopeChatModel在每次调用时读取// babi-common 扩展 SessionContextHolderprivatestaticfinalThreadLocalStringMODEL_OVERRIDEnewThreadLocal();publicstaticvoidsetModelOverride(Stringmodel){MODEL_OVERRIDE.set(model);}publicstaticStringgetModelOverride(){returnMODEL_OVERRIDE.get();}publicstaticvoidclear(){SESSION_ID.remove();MODEL_OVERRIDE.remove();}BabiService在启动 graph 执行的线程上设置 ThreadLocal// BabiService.streamChat() — babi-graph 模块newThread(()-{try{SessionContextHolder.setSessionId(sessionId);if(model!null!model.isBlank()){SessionContextHolder.setModelOverride(model);}varresultgraph.stream(input,config);result.stream().forEach(output-{/* 驱动图执行 */});}finally{SessionContextHolder.clear();// 同时清理 sessionId 和 modelOverride}},babi-agent-sessionId).start();DashScopeChatModel的resolveModel()改为从 ThreadLocal 读取privateStringresolveModel(){StringoverrideSessionContextHolder.getModelOverride();if(override!null!override.isBlank()){returnoverride;}returndefaultModel;}这个方案巧妙之处在于模型覆写通过 ThreadLocal 在调用线程上传递不经过 graph state对AgentExecutor和CompiledGraph完全透明。DashScopeChatModel在doChat()和buildTextParam()中通过resolveModel()读取当前线程的模型覆写。与 graph 模块已有的THINKING_SINKS/TEXT_SINKS静态 Map 传递 session-scoped sink 的模式完全一致。五、AgentScope 方案Middleware 拦截 ModelCallInputbabi-agent 模块面临最复杂的挑战。AgentScope SDK 的DashScopeChatModel是外部类modelName是private final无法修改。SDK 的GenerateOptions虽然有modelName字段但DashScopeChatModel从不读取它——始终用自己的this.modelName。HarnessAgent.call()和streamEvents()都不接受模型参数。5.1 发现拦截点ReActAgent 的中间件链深入分析 AgentScope Java SDK 源码/Users/hongxi/github/agentscope-java在ReActAgent.java的 reasoning 阶段发现了一个关键的中间件拦截点// ReActAgent.java line 2131-2144FunctionModelCallInput,FluxAgentEventmodelCallCoremci-modelCallStream(context,mci,true);returnMiddlewareChain.build(middlewares,ReActAgent.this,rc,MiddlewareBase::onModelCall,// ← 拦截点modelCallCore).apply(newModelCallInput(messages,tools,options,modelForCall()));ModelCallInput是一个 Java record// ModelCallInput.javapublicrecordModelCallInput(ListMsgmessages,ListToolSchematools,GenerateOptionsoptions,Modelmodel){}核心模型调用使用mci.model().stream(...)// ReActAgent.java line 2170FluxAgentEventmodelEventsmci.model().stream(mci.messages(),mci.tools(),mci.options())...;关键在于ModelCallInput是 record不可变但可重建MiddlewareBase.onModelCall的默认实现将input直接传给next。如果自定义中间件构造一个新的ModelCallInput、替换其中的Model实例再传给next就能实现 per-request 模型切换。5.2 ModelSwitchingMiddleware 实现// ModelSwitchingMiddleware.java — babi-agent 模块publicclassModelSwitchingMiddlewareimplementsMiddlewareBase{privatestaticfinalStringMODEL_OVERRIDE_KEYmodelOverride;privatefinalStringapiKey;privatefinalbooleanenableSearch;privatefinalMapString,ModelmodelCachenewConcurrentHashMap();OverridepublicFluxAgentEventonModelCall(Agentagent,RuntimeContextctx,ModelCallInputinput,FunctionModelCallInput,FluxAgentEventnext){StringdesiredModelctx.get(MODEL_OVERRIDE_KEY);if(desiredModel!null!desiredModel.isBlank()){ModeloverriddenmodelCache.computeIfAbsent(desiredModel,this::createModel);// 重建 ModelCallInput替换 Model 实例ModelCallInputoverriddenInputnewModelCallInput(input.messages(),input.tools(),input.options(),overridden);returnnext.apply(overriddenInput);}returnnext.apply(input);}privateModelcreateModel(StringmodelName){varbuilderDashScopeChatModel.builder().apiKey(apiKey).modelName(modelName).stream(true).enableSearch(enableSearch);if(DashScopeEndpointUtil.isMultimodalModel(modelName)){builder.endpointType(EndpointType.MULTIMODAL);}returnbuilder.build();}}5.3 控制器注入 RuntimeContextAgentScope 的RuntimeContext支持任意 string-keyed 属性put(String, Object)/get(String)控制器在构建 context 时注入模型覆写// BabiAgentController.doStreamChat() — babi-agent 模块RuntimeContext.BuilderctxBuilderRuntimeContext.builder().sessionId(sessionId);if(model!null!model.isBlank()){ctxBuilder.put(modelOverride,model);}RuntimeContextctxctxBuilder.build();这个方案的核心优势是单实例HarnessAgent不需要为每个模型创建重量级 agent。模型实例按需缓存在ConcurrentHashMap中首次切换时创建后续直接命中。对 agent 的 ReAct 循环、工具调用、状态管理完全透明。六、三方案对比维度Spring AI 2.0LangGraph4JAgentScope机制ChatClient.prompt().options()ThreadLocal 旁路MiddlewareBase.onModelCall拦截模型覆写传递路径Prompt → ChatOptions → DashScopeChatModelThreadLocal → DashScopeChatModelRuntimeContext → Middleware → ModelCallInput是否改框架接口否否否是否改 DashScopeChatModel是加 resolveModel是加 resolveModel否SDK外部类不可改框架原生支持原生 per-request options无原生 per-request 机制中间件链设计为扩展点多模态路由每请求动态判断每请求动态判断预建不同 EndpointType 的 Model 实例侵入性最低中等最低仅新增一个 middleware三种方案反映了三个框架的设计哲学差异。Spring AI 的ChatOptions天然为 per-request 覆写设计从 API 层面就支持了模型切换。LangGraph4J 的AgentExecutor将模型绑定在构造时没有暴露 per-request 参数注入的口子只能用 ThreadLocal 绕过。AgentScope 虽然模型类不可变但MiddlewareBase.onModelCall这个洋葱模型的拦截点天然允许替换ModelCallInput的任何字段——中间件链的设计就是为了这种对核心逻辑透明地插入自定义行为的场景。从实现难度看Spring AI 最简单3行代码LangGraph4J 中等ThreadLocal clearAgentScope 最需要深入 SDK 源码才能发现拦截点。但从方案的优雅程度看AgentScope 的 Middleware 拦截是最干净的——不修改任何现有类纯增量新增一个 middleware 文件控制器加一行put。这也体现了洋葱模型中间件架构的价值核心逻辑保持不变横切关注点通过中间件注入。

相关新闻

天气丹包装盒尺寸定制,不想被客诉打回原形?韩系奢华套盒的车间验货内参来了

天气丹包装盒尺寸定制,不想被客诉打回原形?韩系奢华套盒的车间验货内参来了

每个月都有三五个白牌定制老板拿着一张韩系高端抗老套盒体系的包装参考图找过来,开口就是“照这个感觉做个尺寸一样的”。但最韩系宫廷配方做出来的东西拎两下就掉底、开盒一股酸味、放了一个月灰板就拱起来。说白了,尺寸定制不是找个印刷厂照葫芦画瓢—…

2026/8/15 8:24:03 阅读更多 →
大牌化妆品小样源头工厂的水到底有多深?看完车间这几道工序再决定找谁合作

大牌化妆品小样源头工厂的水到底有多深?看完车间这几道工序再决定找谁合作

都说小样是实体店引流的“敲门砖”,可真去源头跑一圈工厂,你会发现同样是“大牌感”的小管子,报价能从三毛钱跨度到三块钱。便宜的那些你敢往客户脸上抹吗?反正我是不敢。干了这么多年美妆代工车间的业务,我劝你别光盯…

2026/8/15 8:24:03 阅读更多 →
SK-II高端发酵精华水源头工厂料体拿货,别盯着低价散装货,先搞懂发酵滤液这三个硬指标

SK-II高端发酵精华水源头工厂料体拿货,别盯着低价散装货,先搞懂发酵滤液这三个硬指标

拿着“某日系头部高端发酵水”的瓶子来找我做同款料体的老板,十有八九开口就问“多少钱一公斤”。不是我说,这么问的,基本都被低价散装料坑过。▼ 源头车间质检备案与合作授权说明 ▼有个做私域团购的大姐,图便宜进了一批几块钱一…

2026/8/15 8:24:03 阅读更多 →

最新新闻

AI Agent开发实战:MCP协议与Skill设计赋能智能体高效执行

AI Agent开发实战:MCP协议与Skill设计赋能智能体高效执行

1. 项目概述:当AI Agent学会“套路”最近在折腾AI Agent开发的朋友,估计没少被两个词刷屏:MCP和Skill。乍一看,这俩词儿挺唬人,一个叫“模型上下文协议”,一个叫“技能”,听起来像是某种高深莫测…

2026/8/15 9:10:20 阅读更多 →
FPGA常用型号参考

FPGA常用型号参考

FPGA市场主要由国际巨头和快速崛起的国内厂商两大阵营构成。国际厂商凭借深厚的技术积累和广泛的产品线,覆盖从顶尖数据中心到低功耗边缘计算的全场景;国内厂商则在通信、工业控制和汽车电子等特定市场加速追赶,部分已实现中高端突破。下表汇…

2026/8/15 9:10:20 阅读更多 →
汽车零部件大批量生产,单件检测时间如何压到 30 秒?

汽车零部件大批量生产,单件检测时间如何压到 30 秒?

汽车零部件大批量生产,单件检测时间如何压到 30 秒? Equator 比对仪是雷尼绍(Renishaw)推出的车间级在线比对测量系统,专门面向中大批量生产场景,以"标准件比对"为原理,在 2μm&#…

2026/8/15 9:10:20 阅读更多 →
多线程同时调用通信接口,为什么会出现数据乱码?

多线程同时调用通信接口,为什么会出现数据乱码?

在工业上位机开发中,这是非常经典的“玄学故障”:单线程调试一切正常,设备多了、业务模块多了之后,就开始偶发数据乱码、CRC校验失败、返回值对不上;错误率时高时低,压力越大出错越频繁;很多人第…

2026/8/15 9:10:20 阅读更多 →
PDF.js Viewer.html 实战指南:快速集成与深度定制

PDF.js Viewer.html 实战指南:快速集成与深度定制

1. 项目概述:为什么需要Viewer.html这种“现成”方案?如果你在前端开发中处理过PDF预览,大概率听说过甚至用过pdf.js这个库。它的第一种方式——直接使用PDFViewer等API进行编程式集成——给了开发者极大的灵活性,你可以自定义UI、…

2026/8/15 9:10:20 阅读更多 →
Agent Skills:将个人经验转化为AI可复用的团队能力

Agent Skills:将个人经验转化为AI可复用的团队能力

1. 项目概述:从“一次性经验”到“可复用能力”的进化在任何一个团队里,你肯定都见过这样的场景:某个同事为了解决一个棘手问题,花了整整两天时间,查遍了各种文档、试了无数种方法,最后终于搞定。他长舒一口…

2026/8/15 9:09:20 阅读更多 →

日新闻

内景 空间站内部 中国空间站 太空 内仓

内景 空间站内部 中国空间站 太空 内仓

本项目为前几天收费帮学妹做的一个项目,在工作环境中基本使用不到,但是很多学校把这个当作编程入门的项目来做,故分享出本项目供初学者参考。 一、项目描述 空间站内部 中国空间站 太空 内仓 地址:本地PC端运行(或Web…

2026/8/15 0:00:30 阅读更多 →
重新定义数据接口:3个突破性场景让通达信数据读取更智能

重新定义数据接口:3个突破性场景让通达信数据读取更智能

重新定义数据接口:3个突破性场景让通达信数据读取更智能 【免费下载链接】mootdx 通达信数据读取的一个简便使用封装 项目地址: https://gitcode.com/GitHub_Trending/mo/mootdx 当我们面对海量金融数据时,传统的数据获取方式往往让我们陷入困境—…

2026/8/15 0:00:30 阅读更多 →
一文读懂快消WMS怎么选?2026年国内外10大主流WMS品牌盘点

一文读懂快消WMS怎么选?2026年国内外10大主流WMS品牌盘点

快消品(FMCG)是流通速度较快、竞争较为激烈的行业之一。一瓶饮料从出厂到消费者手中,往往只有几十天甚至几天的周转窗口。这决定了快消行业的仓储管理系统(WMS)与制造业、电商行业存在明显区别:它不仅需要管…

2026/8/15 0:02:30 阅读更多 →

周新闻

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁 【免费下载链接】baidupankey 在线查询网盘提取码(维护中 rm repo) 项目地址: https://gitcode.com/gh_mirrors/ba/baidupankey 你是否曾经在深夜寻找一份重要资料&#x…

2026/8/13 2:38:34 阅读更多 →
如何快速生成中国车牌图片:Python开源工具完整指南

如何快速生成中国车牌图片:Python开源工具完整指南

如何快速生成中国车牌图片:Python开源工具完整指南 【免费下载链接】chinese_license_plate_generator 中国车牌生成器 项目地址: https://gitcode.com/gh_mirrors/ch/chinese_license_plate_generator 中国车牌生成器是一个基于Python的开源项目&#xff0c…

2026/8/13 10:41:52 阅读更多 →
收藏!小白程序员轻松入门大模型,从Harness工程开始实践

收藏!小白程序员轻松入门大模型,从Harness工程开始实践

文章强调学习大模型不应只关注模型本身,而应重视模型外的系统搭建,即Harness。提出AgentModelHarness的实用公式,详细介绍Harness的四个层次:持久化层、执行层、控制层和观察与验证层。文章还探讨了上下文工程、工具设计、AGENTS.…

2026/8/13 10:41:51 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/14 13:40:53 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/14 14:06:45 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/15 2:35:29 阅读更多 →