A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 11, 18:45 UTC
[BUGFIX] Mistral tool call parser v11+ (#30332)
Signed-off-by: juliendenize <julien.denize@mistral.ai>
Julien Denize committed
Dec 9, 2025 at 15:55 UTC
5c213d2899f5a2d439c8d771a0abc156a5412a2b
2 files changed
+32
-20
tests/tool_use/test_mistral_tool_parser.py
+16
@@ -615,6 +615,7 @@ def test_extract_tool_calls_streaming(
615
"single_tool_weather",
616
"multiple_tool_calls",
617
"content_before_tool",
618
+ "complex",
619
],
620
argnames=["model_output", "expected_tool_calls", "expected_content"],
621
argvalues=[
@@ -673,6 +674,21 @@ def test_extract_tool_calls_streaming(
674
],
675
"bla",
676
),
677
+ (
678
+ # Complex
679
+ """[TOOL_CALLS]bash{"command": "print(\\"hello world!\\")\\nre.compile(r\'{}\')"}""", # noqa: E501
680
+ [
681
+ ToolCall(
682
+ function=FunctionCall(
683
+ name="bash",
684
+ arguments=json.dumps(
685
+ {"command": "print(\"hello world!\")\nre.compile(r'{}')"}
686
+ ),
687
+ )
688
+ )
689
+ ],
690
+ "",
691
+ ),
692
],
693
)
694
def test_extract_tool_calls_streaming_one_chunk(
vllm/entrypoints/openai/tool_parsers/mistral_tool_parser.py
+16
-20
@@ -99,12 +99,7 @@ class MistralToolParser(ToolParser):
99
self.bot_token = "[TOOL_CALLS]"
100
self.bot_token_id = self.vocab.get(self.bot_token)
101
self.tool_call_regex = re.compile(r"\[{.*}\]", re.DOTALL)
102
- if not _is_pre_v11_tokeniser(self.model_tokenizer):
103
- self.fn_name_regex = re.compile(
104
- r"([a-zA-Z0-9_-]+)(\{[\s\S]*?\}+)", re.DOTALL
105
- )
106
- else:
107
- self.fn_name_regex = None
102
+ self._is_pre_v11 = _is_pre_v11_tokeniser(self.model_tokenizer)
103
104
if self.bot_token_id is None:
105
raise RuntimeError(
@@ -148,23 +143,24 @@ class MistralToolParser(ToolParser):
143
tool_content = model_output.replace(self.bot_token, "").strip()
144
145
try:
151
- # we first try to directly load the json as parsing very nested
152
- # jsons is difficult
146
try:
154
- if self.fn_name_regex:
147
+ if not self._is_pre_v11:
148
function_call_arr = []
149
for single_tool_content in model_output.split(self.bot_token):
157
- matches = self.fn_name_regex.findall(single_tool_content)
158
-
159
- for match in matches:
160
- fn_name = match[0]
161
- args = match[1]
162
-
163
- # fn_name is encoded outside serialized json dump
164
- # only arguments are serialized
165
- function_call_arr.append(
166
- {"name": fn_name, "arguments": json.loads(args)}
167
- )
150
+ if "{" not in single_tool_content:
151
+ continue
152
+
153
+ end_name = single_tool_content.find("{")
154
+ fn_name, args = (
155
+ single_tool_content[:end_name],
156
+ single_tool_content[end_name:],
157
+ )
158
+
159
+ # fn_name is encoded outside serialized json dump
160
+ # only arguments are serialized
161
+ function_call_arr.append(
162
+ {"name": fn_name, "arguments": json.loads(args)}
163
+ )
164
else:
165
function_call_arr = json.loads(tool_content)
166
except json.JSONDecodeError: