Repository: agentscope-ai/agentscope-java (2026-07-15T12:54:20Z)

Issue:
[Bug]: ToolChoice.None 被映射为 Anthropic any
AnthropicToolsHelper 把 ToolChoice.None 映射成 any，会强制工具调用，和“禁用工具”语义相反。


Discussion:

Patch:
diff --git a/agentscope-extensions/agentscope-extensions-model/agentscope-extensions-model-anthropic/src/main/java/io/agentscope/extensions/model/anthropic/formatter/AnthropicBaseFormatter.java b/agentscope-extensions/agentscope-extensions-model/agentscope-extensions-model-anthropic/src/main/java/io/agentscope/extensions/model/anthropic/formatter/AnthropicBaseFormatter.java
index 5d384b3f41..9e0ac24d9f 100644
--- a/agentscope-extensions/agentscope-extensions-model/agentscope-extensions-model-anthropic/src/main/java/io/agentscope/extensions/model/anthropic/formatter/AnthropicBaseFormatter.java
+++ b/agentscope-extensions/agentscope-extensions-model/agentscope-extensions-model-anthropic/src/main/java/io/agentscope/extensions/model/anthropic/formatter/AnthropicBaseFormatter.java
@@ -58,8 +58,8 @@ public void applyOptions(
             MessageCreateParams.Builder paramsBuilder,
             GenerateOptions options,
             GenerateOptions defaultOptions) {
-        // Save options for applyTools
-        currentOptions.set(options);
+        // Save effective options for applyTools, including model-level defaults.
+        currentOptions.set(GenerateOptions.mergeOptions(options, defaultOptions));
 
         // Apply other options
         AnthropicToolsHelper.applyOptions(paramsBuilder, options, defaultOptions);
diff --git a/agentscope-extensions/agentscope-extensions-model/agentscope-extensions-model-anthropic/src/main/java/io/agentscope/extensions/model/anthropic/formatter/AnthropicToolsHelper.java b/agentscope-extensions/agentscope-extensions-model/agentscope-extensions-model-anthropic/src/main/java/io/agentscope/extensions/model/anthropic/formatter/AnthropicToolsHelper.java
index e335b088f1..e59d847cfa 100644
--- a/agentscope-extensions/agentscope-extensions-model/agentscope-extensions-model-anthropic/src/main/java/io/agentscope/extensions/model/anthropic/formatter/AnthropicToolsHelper.java
+++ b/agentscope-extensions/agentscope-extensions-model/agentscope-extensions-model-anthropic/src/main/java/io/agentscope/extensions/model/anthropic/formatter/AnthropicToolsHelper.java
@@ -17,6 +17,7 @@
 
 import static com.anthropic.models.messages.ToolChoice.ofAny;
 import static com.anthropic.models.messages.ToolChoice.ofAuto;
+import static com.anthropic.models.messages.ToolChoice.ofNone;
 import static com.anthropic.models.messages.ToolChoice.ofTool;
 
 import com.anthropic.core.JsonValue;
@@ -25,6 +26,7 @@
 import com.anthropic.models.messages.Tool;
 import com.anthropic.models.messages.ToolChoiceAny;
 import com.anthropic.models.messages.ToolChoiceAuto;
+import com.anthropic.models.messages.ToolChoiceNone;
 import com.anthropic.models.messages.ToolChoiceTool;
 import io.agentscope.core.model.GenerateOptions;
 import io.agentscope.core.model.ToolChoice;
@@ -93,8 +95,7 @@ private static void applyToolChoice(
         if (toolChoice instanceof ToolChoice.Auto) {
             builder.toolChoice(ofAuto(ToolChoiceAuto.builder().build()));
         } else if (toolChoice instanceof ToolChoice.None) {
-            // Anthropic doesn't have None, use Any instead
-            builder.toolChoice(ofAny(ToolChoiceAny.builder().build()));
+            builder.toolChoice(ofNone(ToolChoiceNone.builder().build()));
         } else if (toolChoice instanceof ToolChoice.Required) {
             // Anthropic doesn't have a direct "required" option, use "any" which forces tool
             // use


Test patch:
diff --git a/agentscope-extensions/agentscope-extensions-model/agentscope-extensions-model-anthropic/src/test/java/io/agentscope/extensions/model/anthropic/formatter/AnthropicChatFormatterTest.java b/agentscope-extensions/agentscope-extensions-model/agentscope-extensions-model-anthropic/src/test/java/io/agentscope/extensions/model/anthropic/formatter/AnthropicChatFormatterTest.java
index f1175a077e..9c4ce8e4ae 100644
--- a/agentscope-extensions/agentscope-extensions-model/agentscope-extensions-model-anthropic/src/test/java/io/agentscope/extensions/model/anthropic/formatter/AnthropicChatFormatterTest.java
+++ b/agentscope-extensions/agentscope-extensions-model/agentscope-extensions-model-anthropic/src/test/java/io/agentscope/extensions/model/anthropic/formatter/AnthropicChatFormatterTest.java
@@ -360,6 +360,46 @@ void testApplyTools() {
         assertNotNull(params);
     }
 
+    @Test
+    void testApplyToolsUsesDefaultToolChoiceNone() {
+        MessageCreateParams.Builder paramsBuilder = MessageCreateParams.builder();
+
+        ToolSchema searchTool =
+                ToolSchema.builder()
+                        .name("search")
+                        .description("Search the web")
+                        .parameters(Map.of("type", "object", "properties", Map.of()))
+                        .build();
+
+        GenerateOptions defaultOptions =
+                GenerateOptions.builder().toolChoice(new ToolChoice.None()).build();
+
+        formatter.applyOptions(paramsBuilder, null, defaultOptions);
+        formatter.applyTools(paramsBuilder, List.of(searchTool));
+
+        MessageCreateParams params =
+                paramsBuilder
+                        .model("claude-3-5-sonnet-20241022")
+                        .maxTokens(1024)
+                        .addMessage(
+                                MessageParam.builder()
+                                        .role(MessageParam.Role.USER)
+                                        .content(
+                                                MessageParam.Content.ofBlockParams(
+                                                        List.of(
+                                                                ContentBlockParam.ofText(
+                                                                        TextBlockParam.builder()
+                                                                                .text("test")
+                                                                                .build()))))
+                                        .build())
+                        .build();
+
+        assertTrue(params.toolChoice().isPresent());
+        assertTrue(params.toolChoice().get().isNone());
+        assertTrue(params.tools().isPresent());
+        assertEquals(1, params.tools().get().size());
+    }
+
     @Test
     void testApplyToolsWithEmptyList() {
         MessageCreateParams.Builder paramsBuilder = MessageCreateParams.builder();
diff --git a/agentscope-extensions/agentscope-extensions-model/agentscope-extensions-model-anthropic/src/test/java/io/agentscope/extensions/model/anthropic/formatter/AnthropicToolsHelperTest.java b/agentscope-extensions/agentscope-extensions-model/agentscope-extensions-model-anthropic/src/test/java/io/agentscope/extensions/model/anthropic/formatter/AnthropicToolsHelperTest.java
index c0b7f90e85..d2636f741a 100644
--- a/agentscope-extensions/agentscope-extensions-model/agentscope-extensions-model-anthropic/src/test/java/io/agentscope/extensions/model/anthropic/formatter/AnthropicToolsHelperTest.java
+++ b/agentscope-extensions/agentscope-extensions-model/agentscope-extensions-model-anthropic/src/test/java/io/agentscope/extensions/model/anthropic/formatter/AnthropicToolsHelperTest.java
@@ -156,8 +156,28 @@ void testApplyToolChoiceNone() {
 
         MessageCreateParams params = builder.build();
         assertTrue(params.toolChoice().isPresent());
-        // None maps to "any" in Anthropic
-        assertTrue(params.toolChoice().get().isAny());
+        // None correctly maps to "none" in Anthropic — tools exist but are disabled for this turn
+        assertTrue(params.toolChoice().get().isNone());
+        // Exclusive: must not be confused with "any" (forced tool use) or "auto"
+        assertTrue(!params.toolChoice().get().isAny());
+        assertTrue(!params.toolChoice().get().isAuto());
+
+        // Tools list must still be present and intact
+        assertTrue(params.tools().isPresent());
+        assertEquals(1, params.tools().get().size());
+    }
+
+    @Test
+    void testApplyToolChoiceNoneWithEmptyToolsSkipsToolChoice() {
+        MessageCreateParams.Builder builder = createBuilder();
+
+        GenerateOptions options =
+                GenerateOptions.builder().toolChoice(new ToolChoice.None()).build();
+        // Empty tools list — applyTools returns early, toolChoice should not be set
+        AnthropicToolsHelper.applyTools(builder, List.of(), options);
+
+        MessageCreateParams params = builder.build();
+        assertTrue(params.toolChoice().isEmpty());
     }
 
     @Test