Repository: agentscope-ai/agentscope (python)

Issue:
[Bug]: Repeated Tool Calls with Identical Parameters in Qwen3-Max

**<u>AgentScope is an open-source project. To involve a broader community, we recommend asking your questions in English.</u>**

**Describe the bug**
When using the Qwen3-Max large language model, the same tool is invoked multiple times consecutively with identical parameters when multiple tools are available; switching to Qwen-Plus restores normal behavior.

**To Reproduce**
Steps to reproduce the behavior:

My Code
```
import asyncio
import random

from agentscope.agent import ReActAgent
from agentscope.formatter import DashScopeChatFormatter
from agentscope.memory import InMemoryMemory
from agentscope.message import Msg, TextBlock
from agentscope.model import DashScopeChatModel
from agentscope.tool import Toolkit, ToolResponse


async def calc_plus(
    num1: float,
    num2: float,
) -> ToolResponse:
    """
    加法计算器，计算两个数字的和

    Args:
        num1 (float): 第一个数字
        num2 (float): 第二个数字
    """

    return ToolResponse(
        content=[
            TextBlock(
                type="text",
                text=str(num1 + num2),
            ),
        ]
    )
    
async def calc_minus(
    num1: float,
    num2: float,
) -> ToolResponse:
    """
    减法计算器，计算两个数字的差

    Args:
        num1 (float): 第一个数字
        num2 (float): 第二个数字
    """

    return ToolResponse(
        content=[
            TextBlock(
                type="text",
                text=str(num1 - num2),
            ),
        ]
    )

async def get_weather(
    city: str,
) -> ToolResponse:
    """
    获取天气信息

    Args:
        city (str): 城市名称
    """
    weather_conditions = ["晴天", "多云", "雨天"]
    random_weather = random.choice(weather_conditions)
    location = city
    return ToolResponse(
        content=[
            TextBlock(
                type="text",
                text=f"{location}今天是{random_weather}。",
            ),
        ]
    )




async def creating_react_agent() -> None:
    """创建一个 ReAct 智能体并运行一个简单任务。"""
    # 准备工具
    toolkit = Toolkit()
    # 
    toolkit.register_tool_function(get_weather)
    toolkit.register_tool_function(calc_plus)
    toolkit.register_tool_function(calc_minus)
    
    jarvis = ReActAgent(
        name="Jarvis",
        sys_prompt="你是一个名为 Jarvis 的助手",
        model=DashScopeChatModel(
            model_name="qwen3-max",
            api_key="key",
            stream=True,
        ),
        formatter=DashScopeChatFormatter(),
        toolkit=toolkit,
        memory=InMemoryMemory(),
    )

    msg = Msg(
        name="user1",
        content="北京天气",
        role="user",
    )

    streaming = await jarvis(msg)
    
asyncio.run(creating_react_agent())
```

**Expected behavior**
Here, multiple tool calls occur.
```
Jarvis: {
    "type": "tool_use",
    "id": "call_0ef073b5854945d28291749c",
    "name": "get_weather",
    "input": {
        "city": "北京"
    }
}
system: {
    "type": "tool_result",
    "id": "call_0ef073b5854945d28291749c",
    "name": "get_weather",
    "output": [
        {
            "type": "text",
            "text": "北京今天是雨天。"
        }
    ]
}
Jarvis: {
    "type": "tool_use",
    "id": "call_0110ba389bf4426eb8142e40",
    "name": "get_weather",
    "input": {
        "city": "北京"
    }
}
system: {
    "type": "tool_result",
    "id": "call_0110ba389bf4426eb8142e40",
    "name": "get_weather",
    "output": [
        {
            "type": "text",
            "text": "北京今天是晴天。"
        }
    ]
}
Jarvis: {
    "type": "tool_use",
    "id": "call_c269ad671e84471aa31f7828",
    "name": "get_weather",
    "input": {
        "city": "北京"
    }
}
system: {
    "type": "tool_result",
    "id": "call_c269ad671e84471aa31f7828",
    "name": "get_weather",
    "output": [
        {
            "type": "text",
            "text": "北京今天是多云。"
        }
    ]
}
Jarvis: {
    "type": "tool_use",
    "id": "call_f48cd8c5519f4b08ae6081f7",
    "name": "get_weather",
    "input": {
        "city": "北京"
    }
}
system: {
    "type": "tool_result",
    "id": "call_f48cd8c5519f4b08ae6081f7",
    "name": "get_weather",
    "output": [
        {
            "type": "text",
            "text": "北京今天是晴天。"
        }
    ]
}
Jarvis: {
    "type": "tool_use",
    "id": "call_56782dfd323946ae86889093",
    "name": "get_weather",
    "input": {
        "city": "北京"
    }
}
system: {
    "type": "tool_result",
    "id": "call_56782dfd323946ae86889093",
    "name": "get_weather",
    "output": [
        {
            "type": "text",
            "text": "北京今天是多云。"
        }
    ]
}
Jarvis: {
    "type": "tool_use",
    "id": "call_7cc6d8465829434e9c1fea5f",
    "name": "get_weather",
    "input": {
        "city": "北京"
    }
}
system: {
    "type": "tool_result",
    "id": "call_7cc6d8465829434e9c1fea5f",
    "name": "get_weather",
    "output": [
        {
            "type": "text",
            "text": "北京今天是多云。"
        }
    ]
}
Jarvis: {
    "type": "tool_use",
    "id": "call_159b021954904efaa0f1ba2d",
    "name": "get_weather",
    "input": {
        "city": "北京"
    }
}
system: {
    "type": "tool_result",
    "id": "call_159b021954904efaa0f1ba2d",
    "name": "get_weather",
    "output": [
        {
            "type": "text",
            "text": "北京今天是雨天。"
        }
    ]
}
Jarvis: {
    "type": "tool_use",
    "id": "call_98b43c3f3348484386c97bbd",
    "name": "get_weather",
    "input": {
        "city": "北京"
    }
}
system: {
    "type": "tool_result",
    "id": "call_98b43c3f3348484386c97bbd",
    "name": "get_weather",
    "output": [
        {
            "type": "text",
            "text": "北京今天是雨天。"
        }
    ]
}
Jarvis: {
    "type": "tool_use",
    "id": "call_55928296f12c4f2096f7c82d",
    "name": "get_weather",
    "input": {
        "city": "北京"
    }
}
system: {
    "type": "tool_result",
    "id": "call_55928296f12c4f2096f7c82d",
    "name": "get_weather",
    "output": [
        {
            "type": "text",
            "text": "北京今天是晴天。"
        }
    ]
}
Jarvis: {
    "type": "tool_use",
    "id": "call_a30c05d57f5f4821b1ebb093",
    "name": "get_weather",
    "input": {
        "city": "北京"
    }
}
system: {
    "type": "tool_result",
    "id": "call_a30c05d57f5f4821b1ebb093",
    "name": "get_weather",
    "output": [
        {
            "type": "text",
            "text": "北京今天是晴天。"
        }
    ]
}
Jarvis: 根据您提供的多条记录，北京近期的天气情况如下：  
- 晴天：5次  
- 多云：3次  
- 雨天：3次  

这表明北京最近天气以晴天为主，间有多云和降雨。不过，这些数据并非实时气象信息，仅反映您输入的历史记录。如需准确的当前天气，请查询权威天气预报服务。
```

**Environment (please complete the following information):**

- AgentScope Version: 1.0.12
- Python Version: 3.14.2
- OS: linux

**Additional context**
Add any other context about the problem here.



Patch:
diff --git a/src/agentscope/formatter/_dashscope_formatter.py b/src/agentscope/formatter/_dashscope_formatter.py
index ed33a99ee1..766876b99f 100644
--- a/src/agentscope/formatter/_dashscope_formatter.py
+++ b/src/agentscope/formatter/_dashscope_formatter.py
@@ -148,6 +148,24 @@ class DashScopeChatFormatter(TruncatedFormatterBase):
     """The DashScope formatter class for chatbot scenario, where only a user
     and an agent are involved. We use the `role` field to identify different
     entities in the conversation.
+
+    .. warning::
+        Known Issues with DashScope API:
+
+        1. **Missing content field**: When messages lack the 'content' field,
+           qwen-vl-max models will raise ``KeyError: 'content'``.
+
+        2. **None content value**: When content is ``None``, qwen-vl-max models
+           will raise ``TypeError: 'NoneType' object is not iterable``.
+
+        3. **Empty text in content**: When content contains
+           ``[{"text": None}]``, qwen3-max may repeatedly invoke tools
+           multiple times. Note that when qwen3-max initiates tool calls,
+           the returned message contains ``"content": ""``.
+
+        To avoid these issues, this formatter assigns content as an empty
+        list ``[]`` for messages without valid content blocks.
+
     """
 
     support_tools_api: bool = True
@@ -377,17 +395,13 @@ async def _format(
 
             msg_dashscope = {
                 "role": msg.role,
-                "content": content_blocks or [{"text": None}],
+                "content": content_blocks,
             }
 
             if tool_calls:
                 msg_dashscope["tool_calls"] = tool_calls
 
-            if msg_dashscope["content"] != [
-                {"text": None},
-            ] or msg_dashscope.get(
-                "tool_calls",
-            ):
+            if msg_dashscope["content"] or msg_dashscope.get("tool_calls"):
                 formatted_msgs.append(msg_dashscope)
 
             # Move to next message
diff --git a/src/agentscope/model/_dashscope_model.py b/src/agentscope/model/_dashscope_model.py
index c17a08e703..062642042a 100644
--- a/src/agentscope/model/_dashscope_model.py
+++ b/src/agentscope/model/_dashscope_model.py
@@ -152,15 +152,6 @@ async def __call__(
         """
         import dashscope
 
-        # For qvq and qwen-vl models, the content field cannot be `None` or
-        # `[{"text": None}]`, so we need to convert it to an empty list.
-        if self.model_name.startswith("qvq") or "-vl" in self.model_name:
-            for msg in messages:
-                if msg["content"] is None or msg["content"] == [
-                    {"text": None},
-                ]:
-                    msg["content"] = []
-
         kwargs = {
             "messages": messages,
             "model": self.model_name,


Test patch:
diff --git a/tests/formatter_dashscope_test.py b/tests/formatter_dashscope_test.py
index 61bb56c926..e52c8b57cb 100644
--- a/tests/formatter_dashscope_test.py
+++ b/tests/formatter_dashscope_test.py
@@ -257,7 +257,7 @@ async def asyncSetUp(self) -> None:
             },
             {
                 "role": "assistant",
-                "content": [{"text": None}],
+                "content": [],
                 "tool_calls": [
                     {
                         "id": "1",
@@ -319,11 +319,7 @@ async def asyncSetUp(self) -> None:
             },
             {
                 "role": "assistant",
-                "content": [
-                    {
-                        "text": None,
-                    },
-                ],
+                "content": [],
                 "tool_calls": [
                     {
                         "id": "1",
@@ -360,11 +356,7 @@ async def asyncSetUp(self) -> None:
             },
             {
                 "role": "assistant",
-                "content": [
-                    {
-                        "text": None,
-                    },
-                ],
+                "content": [],
                 "tool_calls": [
                     {
                         "id": "1",
@@ -429,11 +421,7 @@ async def asyncSetUp(self) -> None:
             },
             {
                 "role": "assistant",
-                "content": [
-                    {
-                        "text": None,
-                    },
-                ],
+                "content": [],
                 "tool_calls": [
                     {
                         "id": "1",
@@ -463,11 +451,7 @@ async def asyncSetUp(self) -> None:
             },
             {
                 "role": "assistant",
-                "content": [
-                    {
-                        "text": None,
-                    },
-                ],
+                "content": [],
                 "tool_calls": [
                     {
                         "id": "1",
@@ -628,7 +612,7 @@ async def test_chat_formatter_with_extract_media_blocks(
             },
             {
                 "role": "assistant",
-                "content": [{"text": None}],
+                "content": [],
                 "tool_calls": [
                     {
                         "id": "1",
@@ -852,11 +836,7 @@ async def test_multiagent_formatter_with_promote_media_tool_result(
             },
             {
                 "role": "assistant",
-                "content": [
-                    {
-                        "text": None,
-                    },
-                ],
+                "content": [],
                 "tool_calls": [
                     {
                         "id": "1",