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 15, 04:45 UTC
[Bugfix] Fix Mistral tool-parser regex for nested JSON (#20093)
Signed-off-by: mgoin <mgoin64@gmail.com>
Michael Goin committed
Jun 26, 2025 at 10:01 UTC
754b00edb3fd2642da08c40363a07f1d60a54977
2 files changed
+53
-2
tests/models/language/generation/test_mistral.py
+51
@@ -10,6 +10,7 @@ import pytest
10
from vllm.entrypoints.openai.tool_parsers.mistral_tool_parser import (
11
MistralToolCall, MistralToolParser)
12
from vllm.sampling_params import GuidedDecodingParams, SamplingParams
13
+from vllm.transformers_utils.tokenizer import MistralTokenizer
14
15
from ...utils import check_logprobs_close
16
@@ -318,3 +319,53 @@ def test_mistral_guided_decoding(
319
schema=SAMPLE_JSON_SCHEMA)
320
except jsonschema.exceptions.ValidationError:
321
pytest.fail("Generated response is not valid with JSON schema")
322
+
323
+
324
+def test_mistral_function_call_nested_json():
325
+ """Ensure that the function-name regex captures the entire outer-most
326
+ JSON block, including nested braces."""
327
+
328
+ # Create a minimal stub tokenizer that provides the few attributes the
329
+ # parser accesses (`version` and `get_vocab`).
330
+ class _StubMistralTokenizer(MistralTokenizer):
331
+ version = 11 # Satisfy the version check
332
+
333
+ def __init__(self):
334
+ pass
335
+
336
+ @staticmethod
337
+ def get_vocab():
338
+ # Provide the special TOOL_CALLS token expected by the parser.
339
+ return {"[TOOL_CALLS]": 0}
340
+
341
+ tokenizer = _StubMistralTokenizer()
342
+ parser = MistralToolParser(tokenizer)
343
+
344
+ # Craft a model output featuring nested JSON inside the arguments.
345
+ args_dict = {
346
+ "city": "Dallas",
347
+ "state": "TX",
348
+ "unit": "fahrenheit",
349
+ "sub_dict": {
350
+ "foo": "bar",
351
+ "inner": {
352
+ "x": 1,
353
+ "y": 2
354
+ }
355
+ },
356
+ }
357
+
358
+ model_output = (
359
+ f"{parser.bot_token}get_current_weather{json.dumps(args_dict)}")
360
+
361
+ parsed = parser.extract_tool_calls(model_output, None)
362
+
363
+ # Assertions: the tool call is detected and the full nested JSON is parsed
364
+ # without truncation.
365
+ assert parsed.tools_called
366
+
367
+ assert MistralToolCall.is_valid_id(parsed.tool_calls[0].id)
368
+ assert parsed.tool_calls[0].function.name == "get_current_weather"
369
+ assert json.loads(parsed.tool_calls[0].function.arguments) == args_dict
370
+ # No additional content outside the tool call should be returned.
371
+ assert parsed.content is None
vllm/entrypoints/openai/tool_parsers/mistral_tool_parser.py
+2
-2
@@ -77,8 +77,8 @@ class MistralToolParser(ToolParser):
77
self.bot_token_id = self.vocab.get(self.bot_token)
78
self.tool_call_regex = re.compile(r"\[{.*}\]", re.DOTALL)
79
if _is_fn_name_regex_support(self.model_tokenizer):
80
- self.fn_name_regex = re.compile(r'([a-zA-Z0-9_-]+)(\{.*?\})',
81
- re.DOTALL)
80
+ self.fn_name_regex = re.compile(
81
+ r'([a-zA-Z0-9_-]+)(\{[\s\S]*?\})(?=\s*$|,|\s)', re.DOTALL)
82
else:
83
self.fn_name_regex = None
84