diff --git a/tensorrt_llm/serve/tool_parser/minimax_m2_parser.py b/tensorrt_llm/serve/tool_parser/minimax_m2_parser.py
index 8201138f09e3..1479dc9a0286 100644
--- a/tensorrt_llm/serve/tool_parser/minimax_m2_parser.py
+++ b/tensorrt_llm/serve/tool_parser/minimax_m2_parser.py
@@ -93,10 +93,10 @@ def __init__(self):
r"(.*?)", re.DOTALL
)
self.invoke_regex = re.compile(
- r'(.*?)', re.DOTALL
+ r']*?)["\']?\s*>(.*?)', re.DOTALL
)
self.param_regex = re.compile(
- r'(.*?)', re.DOTALL
+ r']*?)["\']?\s*>(.*?)', re.DOTALL
)
# Streaming state
@@ -191,7 +191,7 @@ def parse_streaming_increment(self, new_text: str, tools: List[Tool]) -> Streami
complete_invocations = list(self.invoke_regex.finditer(inner_text))
# Check for partial invoke (started but not closed)
partial_invoke = re.search(
- r'((?:(?!).)*?)$',
+ r']*?)["\']?\s*>((?:(?!).)*?)$',
inner_text,
re.DOTALL,
)
diff --git a/tests/unittest/llmapi/apps/test_tool_parsers.py b/tests/unittest/llmapi/apps/test_tool_parsers.py
index 24b8e77cd0a8..72a0c9b91ca4 100644
--- a/tests/unittest/llmapi/apps/test_tool_parsers.py
+++ b/tests/unittest/llmapi/apps/test_tool_parsers.py
@@ -4298,6 +4298,53 @@ def test_streaming_preserves_prefix_in_same_chunk(self, sample_tools):
names = [c.name for c in result.calls if c.name]
assert "get_weather" in names
+ def test_parameter_value_with_angle_brackets_streams_within_time_bound(
+ self, sample_tools):
+ """Stream a long parameter value full of `>` within the time bound.
+
+ Code values such as `if (a > b) { p->x = q; }` hold many `>`, four per
+ increment here. The bound guards against regex work that grows with
+ every `>` seen on each increment.
+ """
+ value = "if (a > b) { p->x = q; }\n" * 160 + "return;"
+ text = (''
+ ''
+ f'{value}'
+ ''
+ '')
+ parser = MiniMaxM2ToolParser()
+ calls = []
+ start = time.monotonic()
+
+ for i in range(0, len(text), 4):
+ calls.extend(
+ parser.parse_streaming_increment(text[i:i + 4],
+ sample_tools).calls)
+ calls.extend(parser.finish(sample_tools).calls)
+
+ assert time.monotonic() - start < 10
+ first_call = [c for c in calls if c.tool_index == 0]
+ assert first_call[0].name == "get_weather"
+ arguments = "".join(c.parameters for c in first_call)
+ assert json.loads(arguments) == {"location": value}
+
+ def test_detect_and_parse_parameter_value_with_angle_brackets(
+ self, sample_tools, parser):
+ """Names stop at the first `>`, values keep every `>` they hold."""
+ text = (''
+ ''
+ 'a > b -> c'
+ ''
+ '')
+
+ result = parser.detect_and_parse(text, sample_tools)
+
+ assert len(result.calls) == 1
+ assert result.calls[0].name == "get_weather"
+ assert json.loads(result.calls[0].parameters) == {
+ "location": "a > b -> c"
+ }
+
# ============================================================================
# Reasoning Parser Tests for Interleaved Thinking