@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 754b00edb3
Fork 0

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