From 431798451bb51b884d5afd357cd10a04c406e92d Mon Sep 17 00:00:00 2001 From: rrrjqy Date: Fri, 20 Mar 2026 15:53:38 +0800 Subject: [PATCH 1/3] fix(dashscope): avoid 400 for non-streaming qwen3.5 multimodal requests --- .../core/model/DashScopeChatModel.java | 15 ++++ .../core/model/DashScopeChatModelTest.java | 85 +++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/agentscope-core/src/main/java/io/agentscope/core/model/DashScopeChatModel.java b/agentscope-core/src/main/java/io/agentscope/core/model/DashScopeChatModel.java index 4991665c4..289d2e789 100644 --- a/agentscope-core/src/main/java/io/agentscope/core/model/DashScopeChatModel.java +++ b/agentscope-core/src/main/java/io/agentscope/core/model/DashScopeChatModel.java @@ -26,6 +26,7 @@ import io.agentscope.core.model.transport.HttpTransportFactory; import java.time.Instant; import java.util.List; +import java.util.Locale; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; @@ -322,6 +323,13 @@ private void applyThinkingMode(DashScopeRequest request, GenerateOptions options if (enableThinking != null) { // Explicitly assign value for thinking mode request.getParameters().setEnableThinking(enableThinking); + } else if (!stream && isThinkingEnabledByDefaultModel(modelName)) { + // Qwen 3.5 defaults to thinking mode on DashScope, but thinking mode only supports + // streaming. For non-streaming requests we must opt out explicitly. + log.debug( + "Disabling default thinking mode for non-streaming DashScope request: model={}", + modelName); + request.getParameters().setEnableThinking(false); } if (Boolean.TRUE.equals(enableThinking) && options.getThinkingBudget() != null) { @@ -335,6 +343,13 @@ private void applyThinkingMode(DashScopeRequest request, GenerateOptions options } } + private boolean isThinkingEnabledByDefaultModel(String modelName) { + if (modelName == null) { + return false; + } + return modelName.toLowerCase(Locale.ROOT).startsWith("qwen3.5"); + } + /** * Gets the model name for logging and identification. * diff --git a/agentscope-core/src/test/java/io/agentscope/core/model/DashScopeChatModelTest.java b/agentscope-core/src/test/java/io/agentscope/core/model/DashScopeChatModelTest.java index 42195de0a..64e69be8c 100644 --- a/agentscope-core/src/test/java/io/agentscope/core/model/DashScopeChatModelTest.java +++ b/agentscope-core/src/test/java/io/agentscope/core/model/DashScopeChatModelTest.java @@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.fasterxml.jackson.core.type.TypeReference; import io.agentscope.core.formatter.dashscope.DashScopeChatFormatter; import io.agentscope.core.formatter.dashscope.DashScopeMultiAgentFormatter; import io.agentscope.core.formatter.dashscope.dto.DashScopeParameters; @@ -32,6 +33,7 @@ import io.agentscope.core.message.TextBlock; import io.agentscope.core.model.test.ModelTestUtils; import io.agentscope.core.model.transport.OkHttpTransport; +import io.agentscope.core.util.JsonUtils; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; @@ -677,6 +679,26 @@ void testApplyThinkingModeWithNull() { assertNull(request.getParameters().getThinkingBudget()); } + @Test + @DisplayName("DashScope qwen3.5 non-stream should disable default thinking mode") + void testApplyThinkingModeDisablesDefaultThinkingForQwen35NonStream() { + DashScopeChatModel chatModel = + DashScopeChatModel.builder().apiKey(mockApiKey).modelName("qwen3.5-plus").stream( + false) + .build(); + + DashScopeRequest request = + DashScopeRequest.builder() + .parameters(DashScopeParameters.builder().build()) + .build(); + + GenerateOptions options = GenerateOptions.builder().build(); + + assertDoesNotThrow(() -> invokeApplyThinkingMode(chatModel, request, options)); + + assertFalse(request.getParameters().getEnableThinking()); + } + @Test @DisplayName( "Should throw an IllegalStateException when setting thinkingBudget while thinking mode" @@ -746,6 +768,69 @@ void testDoNonStreamErrorHandling() throws Exception { mockServer.shutdown(); } + @Test + @DisplayName( + "DashScope qwen3.5 non-stream should use multimodal endpoint and opt out of thinking") + void testDoNonStreamQwen35UsesMultimodalEndpointAndDisablesThinking() throws Exception { + MockWebServer mockServer = new MockWebServer(); + mockServer.start(); + + mockServer.enqueue( + new MockResponse() + .setResponseCode(200) + .setBody( + """ + { + "request_id": "test", + "output": { + "choices": [{ + "message": { + "role": "assistant", + "content": [{"text": "ok"}] + }, + "finish_reason": "stop" + }] + } + } + """) + .setHeader("Content-Type", "application/json")); + + DashScopeChatModel chatModel = + DashScopeChatModel.builder().apiKey(mockApiKey).modelName("qwen3.5-plus").stream( + false) + .baseUrl(mockServer.url("/").toString().replaceAll("/$", "")) + .httpTransport(OkHttpTransport.builder().build()) + .build(); + + chatModel + .doStream( + List.of( + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("test").build()) + .build()), + List.of(), + GenerateOptions.builder().build()) + .blockLast(); + + RecordedRequest recorded = mockServer.takeRequest(); + assertEquals(DashScopeHttpClient.MULTIMODAL_GENERATION_ENDPOINT, recorded.getPath()); + + Map requestBody = + JsonUtils.getJsonCodec() + .fromJson(recorded.getBody().readUtf8(), new TypeReference<>() {}); + Map parameters = (Map) requestBody.get("parameters"); + Map input = (Map) requestBody.get("input"); + List> messages = (List>) input.get("messages"); + Map firstMessage = messages.get(0); + List> content = (List>) firstMessage.get("content"); + + assertEquals(Boolean.FALSE, parameters.get("enable_thinking")); + assertEquals("test", content.get(0).get("text")); + + mockServer.shutdown(); + } + // ========== Encryption Configuration Tests ========== @Test From 7f02a61cf18d4130b6961e8d3d6f4692fec8e7f6 Mon Sep 17 00:00:00 2001 From: rrrjqy Date: Tue, 24 Mar 2026 21:59:06 +0800 Subject: [PATCH 2/3] fix(dashscope): avoid 400 for non-streaming qwen3.5 multimodal requests and wrap shutdown() in try-finally to avoid resource leaks --- .../core/model/DashScopeChatModelTest.java | 105 +++++++++--------- 1 file changed, 54 insertions(+), 51 deletions(-) diff --git a/agentscope-core/src/test/java/io/agentscope/core/model/DashScopeChatModelTest.java b/agentscope-core/src/test/java/io/agentscope/core/model/DashScopeChatModelTest.java index c11327fdb..1b8e388a2 100644 --- a/agentscope-core/src/test/java/io/agentscope/core/model/DashScopeChatModelTest.java +++ b/agentscope-core/src/test/java/io/agentscope/core/model/DashScopeChatModelTest.java @@ -775,60 +775,63 @@ void testDoNonStreamQwen35UsesMultimodalEndpointAndDisablesThinking() throws Exc MockWebServer mockServer = new MockWebServer(); mockServer.start(); - mockServer.enqueue( - new MockResponse() - .setResponseCode(200) - .setBody( - """ - { - "request_id": "test", - "output": { - "choices": [{ - "message": { - "role": "assistant", - "content": [{"text": "ok"}] - }, - "finish_reason": "stop" - }] - } - } - """) - .setHeader("Content-Type", "application/json")); - - DashScopeChatModel chatModel = - DashScopeChatModel.builder().apiKey(mockApiKey).modelName("qwen3.5-plus").stream( - false) - .baseUrl(mockServer.url("/").toString().replaceAll("/$", "")) - .httpTransport(OkHttpTransport.builder().build()) - .build(); - - chatModel - .doStream( - List.of( - Msg.builder() - .role(MsgRole.USER) - .content(TextBlock.builder().text("test").build()) - .build()), - List.of(), - GenerateOptions.builder().build()) - .blockLast(); - - RecordedRequest recorded = mockServer.takeRequest(); - assertEquals(DashScopeHttpClient.MULTIMODAL_GENERATION_ENDPOINT, recorded.getPath()); + try { + mockServer.enqueue( + new MockResponse() + .setResponseCode(200) + .setBody( + """ + { + "request_id": "test", + "output": { + "choices": [{ + "message": { + "role": "assistant", + "content": [{"text": "ok"}] + }, + "finish_reason": "stop" + }] + } + } + """) + .setHeader("Content-Type", "application/json")); - Map requestBody = - JsonUtils.getJsonCodec() - .fromJson(recorded.getBody().readUtf8(), new TypeReference<>() {}); - Map parameters = (Map) requestBody.get("parameters"); - Map input = (Map) requestBody.get("input"); - List> messages = (List>) input.get("messages"); - Map firstMessage = messages.get(0); - List> content = (List>) firstMessage.get("content"); + DashScopeChatModel chatModel = + DashScopeChatModel.builder().apiKey(mockApiKey).modelName("qwen3.5-plus").stream( + false) + .baseUrl(mockServer.url("/").toString().replaceAll("/$", "")) + .httpTransport(OkHttpTransport.builder().build()) + .build(); - assertEquals(Boolean.FALSE, parameters.get("enable_thinking")); - assertEquals("test", content.get(0).get("text")); + chatModel + .doStream( + List.of( + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("test").build()) + .build()), + List.of(), + GenerateOptions.builder().build()) + .blockLast(); - mockServer.shutdown(); + RecordedRequest recorded = mockServer.takeRequest(); + assertEquals(DashScopeHttpClient.MULTIMODAL_GENERATION_ENDPOINT, recorded.getPath()); + + Map requestBody = + JsonUtils.getJsonCodec() + .fromJson(recorded.getBody().readUtf8(), new TypeReference<>() {}); + Map parameters = (Map) requestBody.get("parameters"); + Map input = (Map) requestBody.get("input"); + List> messages = (List>) input.get("messages"); + Map firstMessage = messages.get(0); + List> content = + (List>) firstMessage.get("content"); + + assertEquals(Boolean.FALSE, parameters.get("enable_thinking")); + assertEquals("test", content.get(0).get("text")); + } finally { + mockServer.shutdown(); + } } // ========== Encryption Configuration Tests ========== From 4a563098508dfd94b6456eec47dce1dfcb5e0587 Mon Sep 17 00:00:00 2001 From: rrrjqy Date: Tue, 24 Mar 2026 22:10:50 +0800 Subject: [PATCH 3/3] fix(dashscope): avoid 400 for non-streaming qwen3.5 multimodal requests and wrap shutdown() in try-finally to avoid resource leaks --- .../io/agentscope/core/model/DashScopeChatModelTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/agentscope-core/src/test/java/io/agentscope/core/model/DashScopeChatModelTest.java b/agentscope-core/src/test/java/io/agentscope/core/model/DashScopeChatModelTest.java index 1b8e388a2..b1c0d61b4 100644 --- a/agentscope-core/src/test/java/io/agentscope/core/model/DashScopeChatModelTest.java +++ b/agentscope-core/src/test/java/io/agentscope/core/model/DashScopeChatModelTest.java @@ -797,8 +797,10 @@ void testDoNonStreamQwen35UsesMultimodalEndpointAndDisablesThinking() throws Exc .setHeader("Content-Type", "application/json")); DashScopeChatModel chatModel = - DashScopeChatModel.builder().apiKey(mockApiKey).modelName("qwen3.5-plus").stream( - false) + DashScopeChatModel.builder() + .apiKey(mockApiKey) + .modelName("qwen3.5-plus") + .stream(false) .baseUrl(mockServer.url("/").toString().replaceAll("/$", "")) .httpTransport(OkHttpTransport.builder().build()) .build();