Skip to content

[BUGFIX] Mistral tool call parser v11+#30332

Merged
NickLucche merged 1 commit intovllm-project:mainfrom
juliendenize:patch_mistral_tool_parser
Dec 9, 2025
Merged

[BUGFIX] Mistral tool call parser v11+#30332
NickLucche merged 1 commit intovllm-project:mainfrom
juliendenize:patch_mistral_tool_parser

Conversation

@juliendenize
Copy link
Contributor

@juliendenize juliendenize commented Dec 9, 2025

Purpose

This PR fixes an edge case for the mistral tool parser for tool edge cases

Test Plan

A test has been added.

Test Result

All previous and new tests pass.


Essential Elements of an Effective PR Description Checklist
  • The purpose of the PR, such as "Fix some issue (link existing issues this PR will resolve)".
  • The test plan, such as providing test command.
  • The test results, such as pasting the results comparison before and after, or e2e results
  • (Optional) The necessary documentation update, such as updating supported_models.md and examples for a new model.
  • (Optional) Release notes update. If your change is user facing, please update the release notes draft in the Google Doc.

Signed-off-by: juliendenize <julien.denize@mistral.ai>
@chatgpt-codex-connector
Copy link

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request fixes an edge case in the Mistral tool parser for v11+ tokenizers by replacing a brittle regex with a simpler string splitting logic. This is a good change that correctly handles curly braces within tool argument string literals, and it's great that a test case was added to cover this. However, I've identified a critical issue in the new implementation. The argument parsing is not robust against trailing text after the JSON arguments, and the error handling for this case is broken, which can lead to a crash. I've provided a detailed comment with a suggested code change to make the parsing more robust and handle errors gracefully.

Comment on lines +153 to +163
end_name = single_tool_content.find("{")
fn_name, args = (
single_tool_content[:end_name],
single_tool_content[end_name:],
)

# fn_name is encoded outside serialized json dump
# only arguments are serialized
function_call_arr.append(
{"name": fn_name, "arguments": json.loads(args)}
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This parsing logic is not robust against model outputs that include trailing text after the JSON arguments. args is assigned the rest of the string from the first {, so if there's any extra text, json.loads(args) will fail.

When json.loads fails, the except json.JSONDecodeError block at line 166 is triggered. However, this fallback logic is only compatible with the pre-v11 tokenizer format and will raise an IndexError for the v11+ format, causing the entire request to fail.

A more robust approach is to parse the JSON arguments non-greedily and handle decoding errors locally for each tool call. Using json.JSONDecoder().raw_decode() will correctly handle trailing text, and a local try...except block will prevent a single malformed tool call from failing the entire extraction process.

Suggested change
end_name = single_tool_content.find("{")
fn_name, args = (
single_tool_content[:end_name],
single_tool_content[end_name:],
)
# fn_name is encoded outside serialized json dump
# only arguments are serialized
function_call_arr.append(
{"name": fn_name, "arguments": json.loads(args)}
)
end_name = single_tool_content.find("{")
fn_name, args_str = (
single_tool_content[:end_name],
single_tool_content[end_name:],
)
# fn_name is encoded outside serialized json dump
# only arguments are serialized
try:
# Use raw_decode to robustly parse JSON and ignore trailing text
decoder = json.JSONDecoder()
args_json, _ = decoder.raw_decode(args_str)
function_call_arr.append(
{"name": fn_name, "arguments": args_json}
)
except json.JSONDecodeError:
# Log and skip malformed tool calls
logger.warning("Failed to decode tool arguments for tool %s: %s", fn_name, args_str)
continue

@chaunceyjiang chaunceyjiang self-assigned this Dec 9, 2025
@chaunceyjiang chaunceyjiang added the ready ONLY add when PR is ready to merge/full CI is needed label Dec 9, 2025
Copy link
Collaborator

@chaunceyjiang chaunceyjiang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @juliendenize lgtm

@NickLucche NickLucche enabled auto-merge (squash) December 9, 2025 13:21
@NickLucche NickLucche merged commit 5c213d2 into vllm-project:main Dec 9, 2025
52 of 53 checks passed
@jayteaftw
Copy link

Does this PR address #19425 and #30063?

@graelo graelo mentioned this pull request Dec 9, 2025
5 tasks
@avigny
Copy link
Contributor

avigny commented Dec 10, 2025

@juliendenize the issue seems to appear only in non streaming, but the test was added in a streaming test case.

Shouldn't we add the test also for non streaming in test_extract_tool_calls?

def test_extract_tool_calls(

dsuhinin pushed a commit to dsuhinin/vllm that referenced this pull request Jan 21, 2026
Signed-off-by: juliendenize <julien.denize@mistral.ai>
Signed-off-by: dsuhinin <suhinin.dmitriy@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

frontend ready ONLY add when PR is ready to merge/full CI is needed tool-calling

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants