Chat template + tool calling fixes

#3
by shimmyshimmer - opened
Unsloth AI org

For the fixes we did for Mistral 3.2 tool calling & Chat template for GGUF / transformers.

Should largely increase accuracy of the model especially for tool calling. Fixes:

  1. 3.2 tool calling is different from 3.1
  2. timedelta(days=1) (yesterday) changed with a if-else - supports 2024 to 2028 dates - so now word for word same sys prompt!
  3. Made experimental FP8 quant as well: https://huggingface.co/unsloth/Mistral-Small-3.2-24B-Instruct-2506-FP8

Keep in mind that these fixes are specifically for GGUF files. The original Mistral models e.g. Safetensors have nothing wrong with them so only if you're using GGUF's, please use our quants for the best results.

shimmyshimmer pinned discussion

So basically what they talk about here: https://github.com/lmstudio-ai/lmstudio-bug-tracker/issues/710 you have already implemented?

Thank you for putting the effort in, it is really appreciated!

Unsloth AI org

So basically what they talk about here: https://github.com/lmstudio-ai/lmstudio-bug-tracker/issues/710 you have already implemented?

Thank you for putting the effort in, it is really appreciated!

Yes correct

Unsloth AI org

@simonw You might be interested in these quants where we managed to add tool calling support and also use the exact system prompt as suggested by Mistral!

In llama.cpp it appears to be failing tool calls, not sure if im doing something wrong here

"Failed to infer a tool call example (possible template bug)"

Model: unsloth_Mistral-Small-3.2-24B-Instruct-2506-GGUF_Mistral-Small-3.2-24B-Instruct-2506-UD-Q8_K_XL.gguf

When used for tool calling, I always see this output from Mistral-Small-3.2-24B-Instruct-2506-UD-Q6_K_XL.gguf.
I am trying this with Jan Beta and aichat.

In Tekken 7, Mistral is a powerful character known for her agility and versatility. She is a French fighter who uses a mix of martial arts, including Muay Thai and Capoeira, to overwhelm her opponents. Mistral's moveset includes a variety of kicks and spins, making her a formidable opponent in both offense and defense. Her signature moves, such as the 'Mistral Kick' and 'French Kiss,' are particularly effective in close combat. Additionally, her ability to quickly change directions and evade attacks makes her a challenging character to counter. Overall, Mistral is a dynamic and exciting character to play in Tekken 7, offering a unique blend of speed, power, and technique.

Unsloth AI org

@qikchen Interesting - you're right - interestingly normal hugging face inference works fine for "What's the temperature in San Francisco now? How about tomorrow?". We get "[TOOL_CALLS]get_current_temperature[ARGS]{"location": "San Francisco, California, USA", "unit": "celsius"}[TOOL_CALLS]get_temperature_date[ARGS]{"location": "San Francisco, California, USA", "date": "2025-06-24", "unit": "celsius"}</s>"

Python code to do Hugging Face inference with tool calling
#  pip install unsloth
from unsloth import FastLanguageModel
import torch
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = "unsloth/Mistral-Small-3.2-24B-Instruct-2506",
    max_seq_length = 2048,
    load_in_4bit = True,
)
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_current_temperature",
            "description": "Get current temperature at a location.",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": 'The location to get the temperature for, in the format "City, State, Country".',
                    },
                    "unit": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "description": 'The unit to return the temperature in. Defaults to "celsius".',
                    },
                },
                "required": ["location"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "get_temperature_date",
            "description": "Get temperature at a location and date.",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": 'The location to get the temperature for, in the format "City, State, Country".',
                    },
                    "date": {
                        "type": "string",
                        "description": 'The date to get the temperature for, in the format "Year-Month-Day".',
                    },
                    "unit": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "description": 'The unit to return the temperature in. Defaults to "celsius".',
                    },
                },
                "required": ["location", "date"],
            },
        },
    },
]
messages = [
    {"role": "user", "content": "What's the temperature in San Francisco now? How about tomorrow?"},
]

inputs = tokenizer.tokenizer.apply_chat_template(messages, tools = tools, return_tensors = "pt").to("cuda")
from transformers import TextStreamer
text_streamer = TextStreamer(tokenizer, skip_prompt = True)
_ = model.generate(input_ids = inputs, streamer = text_streamer, max_new_tokens = 128, temperature = 0, do_sample = False)

For llama.cpp, doing the below works as well, we get "get_current_temperature{"location": "San Francisco, California, USA"}"

./llama.cpp/llama-cli     --model Mistral-Small-3.2-24B-Instruct-2506-GGUF/Mistral-Small-3.2-24B-Instruct-2506-Q8_0.gguf     -ngl 99     --temp 0.0     --device CUDA0     -no-cnv     --prompt "[SYSTEM_PROMPT]You are Mistral-Small-3.2-24B-Instruct-2506, a Large Language Model (LLM) created by Mistral AI, a French startup headquartered in Paris.\nYou power an AI assistant called Le Chat.\nYour knowledge base was last updated on 2023-10-01.\nThe current date is 2025-06-23.\n\nWhen you\'re not sure about some information or when the user\'s request requires up-to-date or specific data, you must use the available tools to fetch the information. Do not hesitate to use tools whenever they can provide a more accurate or complete response. If no relevant tools are available, then clearly state that you don\'t have the information and avoid making up anything.\nIf the user\'s question is not clear, ambiguous, or does not provide enough context for you to accurately answer the question, you do not try to answer it right away and you rather ask the user to clarify their request (e.g. \"What are some good restaurants around me?\" => \"Where are you?\" or \"When is the next flight to Tokyo\" => \"Where do you travel from?\").\nYou are always very attentive to dates, in particular you try to resolve dates (e.g. \"yesterday\" is 2025-06-22) and when asked about information at specific dates, you discard information that is at another date.\nYou follow these instructions in all languages, and always respond to the user in the language they use or request.\nNext sections describe the capabilities that you have.\n\n# WEB BROWSING INSTRUCTIONS\n\nYou cannot perform any web search or access internet to open URLs, links etc. If it seems like the user is expecting you to do so, you clarify the situation and ask the user to copy paste the text directly in the chat.\n\n# MULTI-MODAL INSTRUCTIONS\n\nYou have the ability to read images, but you cannot generate images. You also cannot transcribe audio files or videos.\nYou cannot read nor transcribe audio files or videos.\n\n# TOOL CALLING INSTRUCTIONS\n\nYou may have access to tools that you can use to fetch information or perform actions. You must use these tools in the following situations:\n\n1. When the request requires up-to-date information.\n2. When the request requires specific data that you do not have in your knowledge base.\n3. When the request involves actions that you cannot perform without tools.\n\nAlways prioritize using tools to provide the most accurate and helpful response. If tools are not available, inform the user that you cannot perform the requested action at the moment.[/SYSTEM_PROMPT][AVAILABLE_TOOLS][{\"type\": \"function\", \"function\": {\"name\": \"get_current_temperature\", \"description\": \"Get current temperature at a location.\", \"parameters\": {\"type\": \"object\", \"properties\": {\"location\": {\"type\": \"string\", \"description\": \"The location to get the temperature for, in the format \\\"City, State, Country\\\".\"}, \"unit\": {\"type\": \"string\", \"enum\": [\"celsius\", \"fahrenheit\"], \"description\": \"The unit to return the temperature in. Defaults to \\\"celsius\\\".\"}}, \"required\": [\"location\"]}}}, {\"type\": \"function\", \"function\": {\"name\": \"get_temperature_date\", \"description\": \"Get temperature at a location and date.\", \"parameters\": {\"type\": \"object\", \"properties\": {\"location\": {\"type\": \"string\", \"description\": \"The location to get the temperature for, in the format \\\"City, State, Country\\\".\"}, \"date\": {\"type\": \"string\", \"description\": \"The date to get the temperature for, in the format \\\"Year-Month-Day\\\".\"}, \"unit\": {\"type\": \"string\", \"enum\": [\"celsius\", \"fahrenheit\"], \"description\": \"The unit to return the temperature in. Defaults to \\\"celsius\\\".\"}}, \"required\": [\"location\", \"date\"]}}}][/AVAILABLE_TOOLS][INST]What\'s the temperature in San Francisco now? How about tomorrow?[/INST]"

Using --verbose, we see the tokens are produced correctly:

[ '[TOOL_CALLS]':9 ]
[ 'get':1689 ]
 [ '_current':45971 ]    
 [ '_t':2480 ]
 [ 'emperature':32294 ]                                                                 
 [ '[ARGS]':32 ]
[ '{"':19227 ]         
[ 'location':17611 ]                            
 [ '":':2811 ]                    
 [ ' "':1429 ]                    
[ 'San':24619 ]                 
[ ' Fran

Yes, I see the correct tool call happening with llama-cli on UD-Q6_K_XL as well. Perhaps the system prompts from aichat and Jan Beta aren't working?
I guess I can try llm by Simon Willison later.

When used for tool calling, I always see this output from Mistral-Small-3.2-24B-Instruct-2506-UD-Q6_K_XL.gguf.
I am trying this with Jan Beta and aichat.

In Tekken 7, Mistral is a powerful character known for her agility and versatility. She is a French fighter who uses a mix of martial arts, including Muay Thai and Capoeira, to overwhelm her opponents. Mistral's moveset includes a variety of kicks and spins, making her a formidable opponent in both offense and defense. Her signature moves, such as the 'Mistral Kick' and 'French Kiss,' are particularly effective in close combat. Additionally, her ability to quickly change directions and evade attacks makes her a challenging character to counter. Overall, Mistral is a dynamic and exciting character to play in Tekken 7, offering a unique blend of speed, power, and technique.

This is the same as what Im seeing as well with llama.cpp and owui

The Mistral AI 7 large language model (LLM) is a powerful tool for natural language processing tasks. It is designed to understand and generate human-like text based on the input it receives. The model is trained on a diverse range of internet text, which allows it to have a broad knowledge base and the ability to

Unsloth AI org

@qikchen @mumblerit I found a solution! The culprit was llama.cpp was adding 1970 to the date, in which I did not consider - so I added a fallback - I confirmed in llama.cpp it should work as expected now! :) Please retry them if you guys have time! Hope it works!

@Dampfinchen Also best to redownload them to get fixed tool calling for llama.cpp!

I assume this was your change?

    {%- elif yesterday_year == '1970' %}
        {#- Stop llama_cpp from erroring out #}
        {%- set yesterday_day = '29' %}
    {%- else %}
        {{- raise_exception('Unsloth custom template does not support years > 2032. Error year = [' + yesterday_year + ']') }}

Im still seeing the same output when attempting a tool call

In Tekken 7, Mistral is a character who is a powerful and agile fighter. She is known for her speed and precision, making her a formidable opponent in the game. Mistral's moveset includes a variety of kicks and punches, as well as some unique special moves that can catch opponents off guard. Her playstyle is often described as aggressive and unpredictable, as she can quickly switch between offense and defense. Mistral's design is inspired by French culture, and she often wears a beret and a striped shirt in her outfits. Overall, her character is a popular choice among Tekken players due to her dynamic fighting style and stylish appearance.

@danielhanchen Can you share just the template? Would like to try it out before a new large download.

Unsloth AI org

@qikchen Oh click on chat template on the model page and copy paste it into a new file called chat_template.jinja, then call llama-server --chat-template-file chat_template.jinja --jinja

@mumblerit Oh there's 2 changes so if it's just that one there's one more

@danielhanchen Thanks! It worked.

confirm specifying using the chat template manually fixes it

@qikchen Oh click on chat template on the model page and copy paste it into a new file called chat_template.jinja, then call llama-server --chat-template-file chat_template.jinja --jinja

@mumblerit Oh there's 2 changes so if it's just that one there's one more

Sorry I need your help
I copy the exact content of this file https://huggingface.co/unsloth/Mistral-Small-3.2-24B-Instruct-2506-GGUF/blob/main/template
and create a new one with the file name chat_template.jinja. Than, as you wrote, point Llama to it, right?

Edit:
I tried it that way but llama.cpp fails to take this chat template.
I redownloaded the models and suddenly function calling working now (without that tekken 7 stuff)
but what is very strange, using the model in open webui, every response is finished with following "<|im_end|>"
why its writing <|im_end|> at the end?

@Bennet85 that’s not the file you copy, but as @danielhanchen shared, it’s on the model card (https://huggingface.co/unsloth/Mistral-Small-3.2-24B-Instruct-2506-GGUF) - on the right-hand side just below the downloads chart.

Thank you for clarification. I must say, using the "wrong" template which i mentioned before, worked much better, than the "real" template.
I use mistral in Home Assistant as my assist to control my house and with the wrong one, I get devices controlled ( https://huggingface.co/unsloth/Mistral-Small-3.2-24B-Instruct-2506-GGUF/blob/main/template) compared to the one you pointed me to. crazy stuff

Hello.

The template gives me an index error using it with LangGraph.

The error:

mistral2-1  | got exception: {"code":500,"message":"Trying to access property '0' on null! at row 243, column 16:\n        {%- else %}\n            {{- message['content'][0]['text'] }}\n               ^\n        {%- endif %}\n at row 243, column 16:\n        {%- else %}\n            {{- message['content'][0]['text'] }}\n               ^\n        {%- endif %}\n at row 243, column 13:\n        {%- else %}\n            {{- message['content'][0]['text'] }}\n            ^\n        {%- endif %}\n at row 242, column 20:\n            {{- message['content'] }}\n        {%- else %}\n                   ^\n            {{- message['content'][0]['text'] }}\n at row 240, column 9:\n    {%- elif message['role'] == 'assistant' %}\n        {%- if message['content'] is string %}\n        ^\n            {{- message['content'] }}\n at row 239, column 47:\n\n    {%- elif message['role'] == 'assistant' %}\n                                              ^\n        {%- if message['content'] is string %}\n at row 197, column 5:\n{%- for message in loop_messages %}\n    {%- if message['role'] == 'user' %}\n    ^\n\n at row 196, column 36:\n\n{%- for message in loop_messages %}\n                                   ^\n    {%- if message['role'] == 'user' %}\n at row 196, column 1:\n\n{%- for message in loop_messages %}\n^\n    {%- if message['role'] == 'user' %}\n at row 1, column 69:\n{#- Copyright 2025-present the Unsloth team. All rights reserved. #}\n                                                                    ^\n{#- Licensed under the Apache License, Version 2.0 (the \"License\") #}\n","type":"server_error"}
mistral2-1  | srv  log_server_r: request: POST /v1/chat/completions 172.19.0.7 500

I've solved it by adding a simple check, replacin this:

    {%- elif message['role'] == 'assistant' %}
        {%- if message['content'] is string %}
            {{- message['content'] }}
        {%- else %}
            {{- message['content'][0]['text'] }}
        {%- endif %}

with this:

    {%- elif message['role'] == 'assistant' %}
        {%- if message['content'] is string %}
            {{- message['content'] }}
        {%- elif message['content'] %}  ## this change
            {{- message['content'][0]['text'] }}
        {%- endif %}

Hello.

The template gives me an index error using it with LangGraph.

The error:

mistral2-1  | got exception: {"code":500,"message":"Trying to access property '0' on null! at row 243, column 16:\n        {%- else %}\n            {{- message['content'][0]['text'] }}\n               ^\n        {%- endif %}\n at row 243, column 16:\n        {%- else %}\n            {{- message['content'][0]['text'] }}\n               ^\n        {%- endif %}\n at row 243, column 13:\n        {%- else %}\n            {{- message['content'][0]['text'] }}\n            ^\n        {%- endif %}\n at row 242, column 20:\n            {{- message['content'] }}\n        {%- else %}\n                   ^\n            {{- message['content'][0]['text'] }}\n at row 240, column 9:\n    {%- elif message['role'] == 'assistant' %}\n        {%- if message['content'] is string %}\n        ^\n            {{- message['content'] }}\n at row 239, column 47:\n\n    {%- elif message['role'] == 'assistant' %}\n                                              ^\n        {%- if message['content'] is string %}\n at row 197, column 5:\n{%- for message in loop_messages %}\n    {%- if message['role'] == 'user' %}\n    ^\n\n at row 196, column 36:\n\n{%- for message in loop_messages %}\n                                   ^\n    {%- if message['role'] == 'user' %}\n at row 196, column 1:\n\n{%- for message in loop_messages %}\n^\n    {%- if message['role'] == 'user' %}\n at row 1, column 69:\n{#- Copyright 2025-present the Unsloth team. All rights reserved. #}\n                                                                    ^\n{#- Licensed under the Apache License, Version 2.0 (the \"License\") #}\n","type":"server_error"}
mistral2-1  | srv  log_server_r: request: POST /v1/chat/completions 172.19.0.7 500

I've solved it by adding a simple check, replacin this:

    {%- elif message['role'] == 'assistant' %}
        {%- if message['content'] is string %}
            {{- message['content'] }}
        {%- else %}
            {{- message['content'][0]['text'] }}
        {%- endif %}

with this:

    {%- elif message['role'] == 'assistant' %}
        {%- if message['content'] is string %}
            {{- message['content'] }}
        {%- elif message['content'] %}  ## this change
            {{- message['content'][0]['text'] }}
        {%- endif %}

This fixed some tool calls i had issues with involving json

Sign up or log in to comment