diff --git a/tensorrt_llm/serve/tool_parser/qwen3_coder_parser.py b/tensorrt_llm/serve/tool_parser/qwen3_coder_parser.py
index 97447695ac86..d655994542a4 100644
--- a/tensorrt_llm/serve/tool_parser/qwen3_coder_parser.py
+++ b/tensorrt_llm/serve/tool_parser/qwen3_coder_parser.py
@@ -153,23 +153,18 @@ def parse_streaming_increment(self, new_text: str, tools: List[Tool]) -> Streami
if self.tool_call_end_token in self._buf:
end_pos = self._buf.find(self.tool_call_end_token)
- # Add closing brace to complete the JSON object
+ # Streamed fragments never carry the outer closing brace, so the
+ # arguments are completed here: "{}" when nothing was streamed.
current_streamed = self.streamed_args_for_tool[self.current_tool_id]
- if current_streamed:
- # Count opening and closing braces to check if JSON is complete
- open_braces = current_streamed.count("{")
- close_braces = current_streamed.count("}")
- if open_braces > close_braces:
- calls.append(
- ToolCallItem(
- tool_index=self.current_tool_id,
- name=None,
- parameters="}",
- )
- )
- self.streamed_args_for_tool[self.current_tool_id] = (
- current_streamed + "}"
- )
+ closing = "}" if current_streamed else "{}"
+ calls.append(
+ ToolCallItem(
+ tool_index=self.current_tool_id,
+ name=None,
+ parameters=closing,
+ )
+ )
+ self.streamed_args_for_tool[self.current_tool_id] = current_streamed + closing
# Complete the tool call
self._buf = self._buf[end_pos + len(self.tool_call_end_token) :]
diff --git a/tests/unittest/llmapi/apps/test_tool_parsers.py b/tests/unittest/llmapi/apps/test_tool_parsers.py
index ccc82e9adc7e..f42c4c2fb80e 100644
--- a/tests/unittest/llmapi/apps/test_tool_parsers.py
+++ b/tests/unittest/llmapi/apps/test_tool_parsers.py
@@ -1226,6 +1226,62 @@ def test_parse_streaming_increment_end_token_handling(
assert parser._buf == ""
assert parser._in_tool_call is False
+ @pytest.mark.parametrize("chunked", [False, True],
+ ids=["one_delta", "chunked"])
+ def test_streaming_zero_arg_tool(self, parser, chunked):
+ """A call without parameter blocks streams "{}" as its arguments."""
+ tools = [
+ ChatCompletionToolsParam(
+ type="function",
+ function=FunctionDefinition(
+ name="get_time",
+ description="Get current time",
+ parameters={
+ "type": "object",
+ "properties": {},
+ },
+ ),
+ )
+ ]
+ deltas = [
+ "\n", "\n", "\n",
+ ""
+ ]
+ if not chunked:
+ deltas = ["".join(deltas)]
+
+ calls = [
+ call for delta in deltas
+ for call in parser.parse_streaming_increment(delta, tools).calls
+ ]
+
+ names = [c.name for c in calls if c.name]
+ assert names == ["get_time"]
+ params = "".join(c.parameters for c in calls)
+ assert params == "{}", f"Expected '{{}}', got {params!r}"
+ assert params == self.make_parser().detect_and_parse(
+ "".join(deltas), tools).calls[0].parameters
+
+ def test_streaming_closes_arguments_with_brace_in_value(
+ self, sample_tools, parser):
+ """The closing brace is appended regardless of the streamed text."""
+ deltas = [
+ "\n\n",
+ "a}b\n",
+ "\n",
+ ]
+
+ calls = [
+ call
+ for delta in deltas for call in parser.parse_streaming_increment(
+ delta, sample_tools).calls
+ ]
+
+ params = "".join(c.parameters for c in calls)
+ assert json.loads(params) == {"location": "a}b"}
+ assert params == self.make_parser().detect_and_parse(
+ "".join(deltas), sample_tools).calls[0].parameters
+
def test_parse_streaming_increment_multiple_tools_streaming(
self, sample_tools, parser):
"""Test streaming parser handles multiple tool calls."""