From 8b69a16d8e377a82ff519f6b9a8cedcc7fee82e9 Mon Sep 17 00:00:00 2001 From: guoming Date: Sat, 25 Apr 2026 08:17:54 +0800 Subject: [PATCH] feat(config): add multimodal endpoint interceptor for DashScope models MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原因:qwen3.6-plus 须走 DashScope 多模态端点;Graph 下发的 ModelRequest 里 multiModel=false 会在合并时盖掉 application.yml 的默认配置,仍走纯文本端点,从而触发官方文档中的 url error, please check url!(模型与 API 端点不匹配)。 修复:DashScopeMultimodalEndpointInterceptor 放在模型拦截器链最前,在 multi-model 为 true 时把请求 options 规范为 multiModel=true。 --- .../start/config/CodeactAgentConfig.java | 7 ++ ...ashScopeMultimodalEndpointInterceptor.java | 65 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 assistant-agent-start/src/main/java/com/alibaba/assistant/agent/start/interceptor/modelInterceptor/DashScopeMultimodalEndpointInterceptor.java diff --git a/assistant-agent-start/src/main/java/com/alibaba/assistant/agent/start/config/CodeactAgentConfig.java b/assistant-agent-start/src/main/java/com/alibaba/assistant/agent/start/config/CodeactAgentConfig.java index 11e4517a..95a03c83 100644 --- a/assistant-agent-start/src/main/java/com/alibaba/assistant/agent/start/config/CodeactAgentConfig.java +++ b/assistant-agent-start/src/main/java/com/alibaba/assistant/agent/start/config/CodeactAgentConfig.java @@ -30,7 +30,9 @@ import com.alibaba.assistant.agent.common.enums.Language; import com.alibaba.assistant.agent.extension.prompt.CodeactToolSignatureInjectionToolCallback; import com.alibaba.assistant.agent.extension.prompt.PromptContributionToolCallback; +import com.alibaba.assistant.agent.start.interceptor.modelInterceptor.DashScopeMultimodalEndpointInterceptor; import com.alibaba.cloud.ai.graph.agent.hook.Hook; +import com.alibaba.cloud.ai.graph.agent.interceptor.ModelInterceptor; import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,6 +43,7 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; import java.util.ArrayList; import java.util.List; @@ -222,6 +225,7 @@ def calculate_sum(a, b): @Bean public CodeactAgent grayscaleCodeactAgent( ChatModel chatModel, + @Autowired(required = false) Environment environment, @Autowired(required = false) List replyCodeactTools, @Autowired(required = false) SearchCodeactToolFactory searchCodeactToolFactory, @Autowired(required = false) List triggerCodeactTools, @@ -313,6 +317,9 @@ public CodeactAgent grayscaleCodeactAgent( logger.info("CodeactAgentConfig#grayscaleCodeactAgent - reason=统一配置 Hooks, total={}", allHooks != null ? allHooks.size() : 0); + List modelInterceptors = new ArrayList<>(); + modelInterceptors.add(new DashScopeMultimodalEndpointInterceptor(environment)); + CodeactAgent.CodeactAgentBuilder builder = CodeactAgent.builder() .name("CodeactAgent") .description("通过编写和执行 Python 代码来解决问题的代码驱动智能体") diff --git a/assistant-agent-start/src/main/java/com/alibaba/assistant/agent/start/interceptor/modelInterceptor/DashScopeMultimodalEndpointInterceptor.java b/assistant-agent-start/src/main/java/com/alibaba/assistant/agent/start/interceptor/modelInterceptor/DashScopeMultimodalEndpointInterceptor.java new file mode 100644 index 00000000..a4edef44 --- /dev/null +++ b/assistant-agent-start/src/main/java/com/alibaba/assistant/agent/start/interceptor/modelInterceptor/DashScopeMultimodalEndpointInterceptor.java @@ -0,0 +1,65 @@ +package com.alibaba.assistant.agent.start.interceptor.modelInterceptor; + +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; +import com.alibaba.cloud.ai.graph.agent.interceptor.ModelCallHandler; +import com.alibaba.cloud.ai.graph.agent.interceptor.ModelInterceptor; +import com.alibaba.cloud.ai.graph.agent.interceptor.ModelRequest; +import com.alibaba.cloud.ai.graph.agent.interceptor.ModelResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.model.ModelOptionsUtils; +import org.springframework.ai.model.tool.ToolCallingChatOptions; +import org.springframework.core.env.Environment; + +/** + * 确保 Graph 传入的 {@link ModelRequest#getOptions()} 在合并后仍走 DashScope 多模态端点。 + * + *

阿里云文档:qwen3.6-plus 等模型若用纯文本端点会报 {@code url error}。框架层可能下发 + * {@code multiModel=false} 的选项并覆盖 {@code application.yml} 默认值,本拦截器在调用链最前段纠偏。 + */ +public class DashScopeMultimodalEndpointInterceptor extends ModelInterceptor { + + private static final Logger log = LoggerFactory.getLogger(DashScopeMultimodalEndpointInterceptor.class); + + private final Environment environment; + + public DashScopeMultimodalEndpointInterceptor(Environment environment) { + this.environment = environment; + } + + @Override + public String getName() { + return "DashScopeMultimodalEndpointInterceptor"; + } + + @Override + public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) { + if (!Boolean.parseBoolean( + environment.getProperty("spring.ai.dashscope.chat.options.multi-model", "true"))) { + return handler.call(request); + } + ToolCallingChatOptions options = request.getOptions(); + if (options == null) { + return handler.call(request); + } + try { + DashScopeChatOptions dash; + if (options instanceof DashScopeChatOptions dso) { + dash = DashScopeChatOptions.fromOptions(dso); + } else { + dash = ModelOptionsUtils.copyToTarget(options, ToolCallingChatOptions.class, DashScopeChatOptions.class); + } + Boolean before = dash.getMultiModel(); + if (!Boolean.TRUE.equals(before)) { + dash.setMultiModel(true); + log.debug("DashScopeMultimodalEndpointInterceptor: set multiModel true (was {})", before); + ModelRequest patched = ModelRequest.builder(request).options(dash).build(); + return handler.call(patched); + } + } catch (Exception e) { + log.warn("DashScopeMultimodalEndpointInterceptor: failed to patch options, passing request through: {}", + e.toString()); + } + return handler.call(request); + } +} \ No newline at end of file