diff --git a/agentscope-core/src/main/java/io/agentscope/core/formatter/dashscope/dto/DashScopeParameters.java b/agentscope-core/src/main/java/io/agentscope/core/formatter/dashscope/dto/DashScopeParameters.java index 520b9632b..62ee26067 100644 --- a/agentscope-core/src/main/java/io/agentscope/core/formatter/dashscope/dto/DashScopeParameters.java +++ b/agentscope-core/src/main/java/io/agentscope/core/formatter/dashscope/dto/DashScopeParameters.java @@ -64,6 +64,12 @@ public class DashScopeParameters { @JsonProperty("enable_search") private Boolean enableSearch; + /** + * Whether to stitch the reasoning_content of the assistant message in the conversation history to the model input. + */ + @JsonProperty("preserve_thinking") + private Boolean preserveThinking; + /** Token budget for thinking. */ @JsonProperty("thinking_budget") private Integer thinkingBudget; @@ -165,6 +171,14 @@ public void setEnableThinking(Boolean enableThinking) { this.enableThinking = enableThinking; } + public Boolean getPreserveThinking() { + return preserveThinking; + } + + public void setPreserveThinking(Boolean preserveThinking) { + this.preserveThinking = preserveThinking; + } + public Integer getThinkingBudget() { return thinkingBudget; } @@ -284,6 +298,11 @@ public Builder enableThinking(Boolean enableThinking) { return this; } + public Builder preserveThinking(Boolean preserveThinking) { + params.setPreserveThinking(preserveThinking); + return this; + } + public Builder thinkingBudget(Integer thinkingBudget) { params.setThinkingBudget(thinkingBudget); return this; 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 de1051eb9..b6843e4d4 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 @@ -57,6 +57,7 @@ public class DashScopeChatModel extends ChatModelBase { private final boolean stream; private final Boolean enableThinking; // nullable private final Boolean enableSearch; // nullable + private final Boolean preserveThinking; // nullable private final EndpointType endpointType; private final GenerateOptions defaultOptions; private final Formatter formatter; @@ -70,18 +71,20 @@ public class DashScopeChatModel extends ChatModelBase { *

This constructor maintains backward compatibility. API type defaults to AUTO, * which detects the endpoint based on model name. * - * @param apiKey the API key for DashScope authentication - * @param modelName the model name (e.g., "qwen-max", "qwen-vl-plus") - * @param stream whether streaming should be enabled (ignored if enableThinking is true) + * @param apiKey the API key for DashScope authentication + * @param modelName the model name (e.g., "qwen-max", "qwen-vl-plus") + * @param stream whether streaming should be enabled (ignored if enableThinking is true) * @param enableThinking whether thinking mode should be enabled (null for disabled) - * @param enableSearch whether search enhancement should be enabled (null for disabled) + * @param enableSearch whether search enhancement should be enabled (null for disabled) * @param defaultOptions default generation options (null for defaults) - * @param baseUrl custom base URL for DashScope API (null for default) - * @param formatter the message formatter to use (null for default DashScope formatter) - * @param httpTransport custom HTTP transport (null for default from factory) - * @param publicKeyId the RSA public key ID for encryption (null to disable encryption) - * @param publicKey the RSA public key for encryption (Base64-encoded, null to disable encryption) + * @param baseUrl custom base URL for DashScope API (null for default) + * @param formatter the message formatter to use (null for default DashScope formatter) + * @param httpTransport custom HTTP transport (null for default from factory) + * @param publicKeyId the RSA public key ID for encryption (null to disable encryption) + * @param publicKey the RSA public key for encryption (Base64-encoded, null to disable encryption) + * @deprecated Use {@link #builder()} instead. */ + @Deprecated(since = "1.0.12", forRemoval = true) public DashScopeChatModel( String apiKey, String modelName, @@ -112,19 +115,21 @@ public DashScopeChatModel( /** * Creates a new DashScope chat model instance with explicit API type. * - * @param apiKey the API key for DashScope authentication - * @param modelName the model name (e.g., "qwen-max", "qwen-vl-plus") - * @param stream whether streaming should be enabled (ignored if enableThinking is true) + * @param apiKey the API key for DashScope authentication + * @param modelName the model name (e.g., "qwen-max", "qwen-vl-plus") + * @param stream whether streaming should be enabled (ignored if enableThinking is true) * @param enableThinking whether thinking mode should be enabled (null for disabled) - * @param enableSearch whether search enhancement should be enabled (null for disabled) - * @param endpointType the endpoint type to use (null for AUTO detection) + * @param enableSearch whether search enhancement should be enabled (null for disabled) + * @param endpointType the endpoint type to use (null for AUTO detection) * @param defaultOptions default generation options (null for defaults) - * @param baseUrl custom base URL for DashScope API (null for default) - * @param formatter the message formatter to use (null for default DashScope formatter) - * @param httpTransport custom HTTP transport (null for default from factory) - * @param publicKeyId the RSA public key ID for encryption (null to disable encryption) - * @param publicKey the RSA public key for encryption (Base64-encoded, null to disable encryption) + * @param baseUrl custom base URL for DashScope API (null for default) + * @param formatter the message formatter to use (null for default DashScope formatter) + * @param httpTransport custom HTTP transport (null for default from factory) + * @param publicKeyId the RSA public key ID for encryption (null to disable encryption) + * @param publicKey the RSA public key for encryption (Base64-encoded, null to disable encryption) + * @deprecated Use {@link #builder()} instead. */ + @Deprecated(since = "1.0.12", forRemoval = true) public DashScopeChatModel( String apiKey, String modelName, @@ -138,6 +143,53 @@ public DashScopeChatModel( HttpTransport httpTransport, String publicKeyId, String publicKey) { + this( + apiKey, + modelName, + stream, + enableThinking, + enableSearch, + null, + null, + defaultOptions, + baseUrl, + formatter, + httpTransport, + publicKeyId, + publicKey); + } + + /** + * Creates a new DashScope chat model instance with explicit API type. + * + * @param apiKey the API key for DashScope authentication + * @param modelName the model name (e.g., "qwen-max", "qwen-vl-plus") + * @param stream whether streaming should be enabled (ignored if enableThinking is true) + * @param enableThinking whether thinking mode should be enabled (null for disabled) + * @param enableSearch whether search enhancement should be enabled (null for disabled) + * @param preserveThinking whether to append reasoning_content (null for disabled) + * @param endpointType the endpoint type to use (null for AUTO detection) + * @param defaultOptions default generation options (null for defaults) + * @param baseUrl custom base URL for DashScope API (null for default) + * @param formatter the message formatter to use (null for default DashScope formatter) + * @param httpTransport custom HTTP transport (null for default from factory) + * @param publicKeyId the RSA public key ID for encryption (null to disable encryption) + * @param publicKey the RSA public key for encryption (Base64-encoded, null to disable encryption) + */ + protected DashScopeChatModel( + String apiKey, + String modelName, + boolean stream, + Boolean enableThinking, + Boolean enableSearch, + Boolean preserveThinking, + EndpointType endpointType, + GenerateOptions defaultOptions, + String baseUrl, + Formatter formatter, + HttpTransport httpTransport, + String publicKeyId, + String publicKey) { this.modelName = modelName; // Thinking mode requires streaming; override stream setting if needed if (enableThinking != null && enableThinking && !stream) { @@ -148,6 +200,7 @@ public DashScopeChatModel( this.stream = enableThinking != null && enableThinking ? true : stream; this.enableThinking = enableThinking; this.enableSearch = enableSearch; + this.preserveThinking = preserveThinking; this.endpointType = endpointType != null ? endpointType : EndpointType.AUTO; this.defaultOptions = defaultOptions != null ? defaultOptions : GenerateOptions.builder().build(); @@ -187,8 +240,8 @@ public static Builder builder() { *

Supports timeout and retry configuration through GenerateOptions. * * @param messages AgentScope messages to send to the model - * @param tools Optional list of tool schemas - * @param options Optional generation options (null to use defaults) + * @param tools Optional list of tool schemas + * @param options Optional generation options (null to use defaults) * @return Flux stream of chat responses */ @Override @@ -337,6 +390,10 @@ private void applyThinkingMode(DashScopeRequest request, GenerateOptions options request.getParameters().setThinkingBudget(options.getThinkingBudget()); } + if (Boolean.TRUE.equals(enableThinking) && preserveThinking != null) { + request.getParameters().setPreserveThinking(preserveThinking); + } + // Model-specific settings for search mode if (enableSearch != null) { // Explicitly assign value for search mode @@ -359,6 +416,7 @@ public static class Builder { private String modelName; private boolean stream = true; private Boolean enableThinking; + private Boolean preserveThinking; private Boolean enableSearch; private EndpointType endpointType; private GenerateOptions defaultOptions = null; @@ -420,6 +478,19 @@ public Builder enableThinking(Boolean enableThinking) { return this; } + /** + * Sets whether to append the reasoning_content of the assistant message in the conversation history to the model input. + * + *

When enabled, this reasoning_content will be appended to the model input. + * + * @param preserveThinking true to append reasoning_content, false to disable, null for default (disabled) + * @return this builder instance + */ + public Builder preserveThinking(Boolean preserveThinking) { + this.preserveThinking = preserveThinking; + return this; + } + /** * Sets whether search enhancement should be enabled. * @@ -533,7 +604,7 @@ public Builder httpTransport(HttpTransport httpTransport) { * } * * @param enableEncrypt true to enable encryption (will fetch public key automatically), - * false to disable encryption + * false to disable encryption * @return this builder instance */ public Builder enableEncrypt(boolean enableEncrypt) { @@ -552,7 +623,7 @@ public Builder enableEncrypt(boolean enableEncrypt) { * * @return configured DashScopeChatModel instance * @throws DashScopeHttpClient.DashScopeHttpException if encryption is enabled and - * public key fetching fails + * public key fetching fails */ public DashScopeChatModel build() { GenerateOptions effectiveOptions = @@ -576,6 +647,7 @@ public DashScopeChatModel build() { stream, enableThinking, enableSearch, + preserveThinking, endpointType, effectiveOptions, baseUrl, diff --git a/agentscope-core/src/main/java/io/agentscope/core/model/DashScopeHttpClient.java b/agentscope-core/src/main/java/io/agentscope/core/model/DashScopeHttpClient.java index c554fff01..86dd66ea3 100644 --- a/agentscope-core/src/main/java/io/agentscope/core/model/DashScopeHttpClient.java +++ b/agentscope-core/src/main/java/io/agentscope/core/model/DashScopeHttpClient.java @@ -383,7 +383,8 @@ public static boolean isMultimodalModel(String modelName) { return lowerModelName.startsWith("qvq") || lowerModelName.contains("-vl") || lowerModelName.contains("-asr") - || lowerModelName.startsWith("qwen3.5"); + || lowerModelName.startsWith("qwen3.5") + || lowerModelName.startsWith("qwen3.6"); } /** @@ -484,7 +485,7 @@ public static PublicKeyResult fetchPublicKey( * @param publicKeyId the public key ID * @param publicKey the Base64-encoded public key */ - public static record PublicKeyResult(String publicKeyId, String publicKey) {} + public record PublicKeyResult(String publicKeyId, String publicKey) {} private Map buildHeaders( boolean streaming, Map additionalHeaders, EncryptionContext context) { diff --git a/agentscope-core/src/test/java/io/agentscope/core/formatter/dashscope/dto/DashScopeDtoSerializationTest.java b/agentscope-core/src/test/java/io/agentscope/core/formatter/dashscope/dto/DashScopeDtoSerializationTest.java index d0497895b..a3baef237 100644 --- a/agentscope-core/src/test/java/io/agentscope/core/formatter/dashscope/dto/DashScopeDtoSerializationTest.java +++ b/agentscope-core/src/test/java/io/agentscope/core/formatter/dashscope/dto/DashScopeDtoSerializationTest.java @@ -342,6 +342,7 @@ void testDashScopeParametersBuilder() { .topP(0.95) .maxTokens(2048) .enableThinking(true) + .preserveThinking(true) .thinkingBudget(500) .seed(42) .frequencyPenalty(0.5) @@ -354,6 +355,7 @@ void testDashScopeParametersBuilder() { assertEquals(0.95, params.getTopP()); assertEquals(2048, params.getMaxTokens()); assertTrue(params.getEnableThinking()); + assertTrue(params.getPreserveThinking()); assertEquals(500, params.getThinkingBudget()); assertEquals(42, params.getSeed()); assertEquals(0.5, params.getFrequencyPenalty()); 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 f74ad57f5..a4334c373 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 @@ -46,6 +46,8 @@ import org.junit.jupiter.api.Test; import reactor.core.publisher.Flux; import reactor.test.StepVerifier; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.ObjectMapper; /** * Unit tests for DashScopeChatModel. @@ -63,9 +65,12 @@ class DashScopeChatModelTest { private DashScopeChatModel model; private String mockApiKey; + private ObjectMapper objectMapper; @BeforeEach void setUp() { + objectMapper = new ObjectMapper(); + mockApiKey = ModelTestUtils.createMockApiKey(); // Create model with builder @@ -225,6 +230,8 @@ void testOverloadedConstructorWithoutEndpointType() { null, null, null, + null, + null, null); assertNotNull(model, "Model from overloaded constructor should be created"); @@ -241,6 +248,7 @@ void testFullConstructorWithEndpointType() { true, null, null, + null, EndpointType.MULTIMODAL, null, null, @@ -957,6 +965,194 @@ void testCacheControlNotAppliedWhenDisabled() throws Exception { mockServer.shutdown(); } + @Test + @DisplayName( + "Should set preserve_thinking to true in the request when preserveThinking option is" + + " enabled") + void testPreserveThinkingEnabled() throws Exception { + MockWebServer mockServer = new MockWebServer(); + mockServer.start(); + + mockServer.enqueue( + new MockResponse() + .setResponseCode(200) + .setBody( + """ + { + "request_id": "test", + "output": { + "choices": [] + } + } + """) + .setHeader("Content-Type", "application/json")); + + DashScopeChatModel chatModel = + DashScopeChatModel.builder() + .apiKey(mockApiKey) + .modelName("qwen-plus") + .enableThinking(true) + .preserveThinking(true) + .baseUrl(mockServer.url("/").toString().replaceAll("/$", "")) + .httpTransport(OkHttpTransport.builder().build()) + .build(); + + chatModel + .doStream( + List.of( + Msg.builder() + .role(MsgRole.SYSTEM) + .content( + TextBlock.builder() + .text("You are helpful.") + .build()) + .build(), + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("Hello").build()) + .build()), + List.of(), + GenerateOptions.builder().build()) + .blockLast(); + + RecordedRequest recorded = mockServer.takeRequest(); + String body = recorded.getBody().readUtf8(); + JsonNode jsonNode = objectMapper.readTree(body).get("parameters"); + assertTrue( + jsonNode.get("enable_thinking").asBoolean(), + "Request body should set enable_thinking to true: " + body); + assertTrue( + jsonNode.get("preserve_thinking").asBoolean(), + "Request body should set preserve_thinking to true: " + body); + + mockServer.shutdown(); + } + + @Test + @DisplayName( + "Should set preserve_thinking to false in the request when preserveThinking option is" + + " disabled") + void testPreserveThinkingDisabled() throws Exception { + MockWebServer mockServer = new MockWebServer(); + mockServer.start(); + + mockServer.enqueue( + new MockResponse() + .setResponseCode(200) + .setBody( + """ + { + "request_id": "test", + "output": { + "choices": [] + } + } + """) + .setHeader("Content-Type", "application/json")); + + DashScopeChatModel chatModel = + DashScopeChatModel.builder() + .apiKey(mockApiKey) + .modelName("qwen-plus") + .enableThinking(true) + .preserveThinking(false) + .baseUrl(mockServer.url("/").toString().replaceAll("/$", "")) + .httpTransport(OkHttpTransport.builder().build()) + .build(); + + chatModel + .doStream( + List.of( + Msg.builder() + .role(MsgRole.SYSTEM) + .content( + TextBlock.builder() + .text("You are helpful.") + .build()) + .build(), + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("Hello").build()) + .build()), + List.of(), + GenerateOptions.builder().build()) + .blockLast(); + + RecordedRequest recorded = mockServer.takeRequest(); + String body = recorded.getBody().readUtf8(); + JsonNode jsonNode = objectMapper.readTree(body).get("parameters"); + assertTrue( + jsonNode.get("enable_thinking").asBoolean(), + "Request body should set enable_thinking to true: " + body); + assertFalse( + jsonNode.get("preserve_thinking").asBoolean(), + "Request body should set preserve_thinking to false: " + body); + + mockServer.shutdown(); + } + + @Test + @DisplayName( + "Should not apply preserve_thinking to request when enableThinking option is disabled") + void testPreserveThinkingWhenEnableThinkingDisabled() throws Exception { + MockWebServer mockServer = new MockWebServer(); + mockServer.start(); + + mockServer.enqueue( + new MockResponse() + .setResponseCode(200) + .setBody( + """ + { + "request_id": "test", + "output": { + "choices": [] + } + } + """) + .setHeader("Content-Type", "application/json")); + + DashScopeChatModel chatModel = + DashScopeChatModel.builder() + .apiKey(mockApiKey) + .modelName("qwen-plus") + .enableThinking(false) + .preserveThinking(true) + .baseUrl(mockServer.url("/").toString().replaceAll("/$", "")) + .httpTransport(OkHttpTransport.builder().build()) + .build(); + + chatModel + .doStream( + List.of( + Msg.builder() + .role(MsgRole.SYSTEM) + .content( + TextBlock.builder() + .text("You are helpful.") + .build()) + .build(), + Msg.builder() + .role(MsgRole.USER) + .content(TextBlock.builder().text("Hello").build()) + .build()), + List.of(), + GenerateOptions.builder().build()) + .blockLast(); + + RecordedRequest recorded = mockServer.takeRequest(); + String body = recorded.getBody().readUtf8(); + JsonNode jsonNode = objectMapper.readTree(body).get("parameters"); + assertFalse( + jsonNode.get("enable_thinking").asBoolean(), + "Request body should set enable_thinking to false: " + body); + assertNull( + jsonNode.get("preserve_thinking"), + "Request body should not apply preserve_thinking: " + body); + + mockServer.shutdown(); + } + /** * Use reflection to invoke applyThinkingMode * diff --git a/agentscope-core/src/test/java/io/agentscope/core/model/DashScopeHttpClientTest.java b/agentscope-core/src/test/java/io/agentscope/core/model/DashScopeHttpClientTest.java index d786bfd5d..5b9097f69 100644 --- a/agentscope-core/src/test/java/io/agentscope/core/model/DashScopeHttpClientTest.java +++ b/agentscope-core/src/test/java/io/agentscope/core/model/DashScopeHttpClientTest.java @@ -195,6 +195,13 @@ void testIsMultimodalModelIncludesQwen35Family() { assertFalse(DashScopeHttpClient.isMultimodalModel("qwen-3.5-plus")); } + @Test + void testIsMultimodalModelIncludesQwen36Family() { + // Qwen 3.6 family is entirely multimodal (prefix-based matching) + assertTrue(DashScopeHttpClient.isMultimodalModel("qwen3.6-plus")); + assertTrue(DashScopeHttpClient.isMultimodalModel("qwen3.6-plus-2026-04-02")); + } + @Test void testIsMultimodalModelPatterns() { // qvq prefix diff --git a/agentscope-extensions/agentscope-extensions-rag-simple/src/main/java/io/agentscope/core/rag/store/MilvusStore.java b/agentscope-extensions/agentscope-extensions-rag-simple/src/main/java/io/agentscope/core/rag/store/MilvusStore.java index 3b3c41cbe..572347a9e 100644 --- a/agentscope-extensions/agentscope-extensions-rag-simple/src/main/java/io/agentscope/core/rag/store/MilvusStore.java +++ b/agentscope-extensions/agentscope-extensions-rag-simple/src/main/java/io/agentscope/core/rag/store/MilvusStore.java @@ -195,22 +195,6 @@ private MilvusStore(Builder builder) throws VectorStoreException { } } - /** - * Creates a new MilvusStore with minimal configuration. - * - * @param uri the Milvus server URI (e.g., "http://localhost:19530") - * @param collectionName the name of the collection to use - * @param dimensions the dimension of vectors that will be stored - * @return a new MilvusStore instance - * @throws VectorStoreException if initialization fails - * @deprecated Use {@link MilvusStore#builder()} instead - */ - @Deprecated(since = "1.0.11", forRemoval = true) - public static MilvusStore create(String uri, String collectionName, int dimensions) - throws VectorStoreException { - return builder().uri(uri).collectionName(collectionName).dimensions(dimensions).build(); - } - @Override public Mono add(List documents) { if (documents == null) {