Discussion Draft: NVIDIA Nemotron 3 Nano - Tool Calling Template Bug
Summary
Nemotron 3 Nano's chat_template.jinja crashes with:
TypeError: Can only get item pairs from a mapping
when rendering assistant messages containing tool_calls where tool_calls[].function.arguments is provided as a JSON-encoded string (a common format used by OpenAI-style tool calling and supported by transformers).
The template assumes arguments is a dict/mapping and applies |items directly (line 150).
Reproduction
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained(
"nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16",
trust_remote_code=True,
)
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather in a city",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"}
},
"required": ["city"]
}
}
}]
# β FAILS β arguments as JSON string
messages_string = [
{"role": "user", "content": "What's the weather in Seoul?"},
{
"role": "assistant",
"content": "",
"tool_calls": [{
"id": "call_1",
"type": "function",
"function": {
"name": "get_weather",
"arguments": '{"city": "Seoul"}' # JSON string
}
}]
},
{"role": "tool", "tool_call_id": "call_1", "content": '{"temperature": "5Β°C"}'},
]
tokenizer.apply_chat_template(
messages_string, tokenize=False, add_generation_prompt=True, tools=tools
)
# TypeError: Can only get item pairs from a mapping.
# β
PASSES β arguments as dict
messages_dict = [
{"role": "user", "content": "What's the weather in Seoul?"},
{
"role": "assistant",
"content": "",
"tool_calls": [{
"id": "call_1",
"type": "function",
"function": {
"name": "get_weather",
"arguments": {"city": "Seoul"} # dict
}
}]
},
{"role": "tool", "tool_call_id": "call_1", "content": '{"temperature": "5Β°C"}'},
]
tokenizer.apply_chat_template(
messages_dict, tokenize=False, add_generation_prompt=True, tools=tools
)
# Works without error
Environment
transformers: 5.0.0- Python: 3.13
- OS: macOS (Apple Silicon)
Error Traceback (key part)
File "<template>", line 150, in top-level template code
File ".../jinja2/filters.py", line 249, in do_items
raise TypeError("Can only get item pairs from a mapping.")
TypeError: Can only get item pairs from a mapping.
Cause
In chat_template.jinja (line 150):
{%- for args_name, args_value in tool_call.arguments|items %}
The Jinja |items filter requires a dict/mapping, but tool_call.arguments may be a JSON string in tool-calling message formats. When it is a string, |items raises the TypeError above.
Suggested Fix
Parse JSON strings before iterating, while keeping dict support:
{%- if tool_call.arguments is defined %}
{%- if tool_call.arguments is string %}
{%- set parsed_args = tool_call.arguments | fromjson %}
{%- else %}
{%- set parsed_args = tool_call.arguments %}
{%- endif %}
{%- for args_name, args_value in parsed_args | items %}
...
{%- endfor %}
{%- endif %}
Additional Note
The template appears to render tool outputs using <tool_response> rather than <tool_result>. Some external stacks may label tool output differently, so it might be worth confirming whether multiple tool-output tags should be handled.
Notes
- The model README indicates tool calling is supported
- Tested on the BF16 variant; the same
chat_template.jinjaalso appears in other variants - Happy to submit a PR with this change if it would be welcome
Hello,
It seems that the Qwen3Coder Tool Parser that we use does not accept arguments in JSON string format, and requires the user to pass in function call arguments as valid dictionaries.
e.g., if the PR is adopted, while this below will compile, it will end up parsing the tool call wrong:
{
"role": "assistant",
"content": "",
"tool_calls": [{
"id": "call_1",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\": \"Seoul\"}"
}
}]
},
parsed_tool_calls = [{'function': {'name': 'get_weather', 'arguments': {}}}] <-- arguments is emptyI presume this is because the Qwen3Coder Tool Parser operates in XML instead of JSON.
I recommend that users parse all the argument strings in their data by calling json.loads on them. It is the most straightforward way to use our tool calling capabilities without error.
Thanks for the explanation! I've addressed this in detail in PR #52.
TL;DR: The fromjson filter isn't available in the default Jinja2 sandbox, so the current PR uses an is mapping guard to prevent the crash. Full <parameter> tag support for string arguments would require registering a custom filter in the tokenizer environment.
Regarding the recommendation to have users call json.loads on their side β that's a reasonable workaround, but it does mean the template silently produces malformed output (missing <parameter> tags) instead of failing loudly when string arguments are passed. The PR at least makes the failure mode graceful rather than a raw traceback.
Hey @Bias92 !
Thanks so much for raising this - we're going to stick to the recommendation above for this model - but will use this to inform future decisions! Really appreciate the feedback!
