[GH-ISSUE #11296] devstral template doesn't handle multiple tool calls #85135

Closed
opened 2026-05-09 22:36:48 -05:00 by GiteaMirror · 1 comment
Owner

Originally created by @rick-github on GitHub (Jul 4, 2025).
Original GitHub issue: https://github.com/ollama/ollama/issues/11296

What is the issue?

This line in the template:

{{- if and (le (len (slice $.Messages $index)) 2) $.Tools }}[AVAILABLE_TOOLS]{{ $.Tools }}[/AVAILABLE_TOOLS]

adds tools to the user message, when the user message is last or second to last message in the message list. So for a completion call with

[
  system message (s)
  user message (U1)
  assistant message (A1)
  user message (U2)
]

the tools get attached to U2 and the model will generate a message (A2) with one or more tool calls. In the process of completing the tools calls, the client will update the message list with A2 and the results of the tool call(s) with a tool message.

[ S U1 A1 U2 A2 T1 ]

Since U2 is no longer the second to last or last message, the tools are not attached. If the model thinks it needs to make another tool call in order to satisfy the instruction in U2, it cannot. As a concrete example:

[
    {
        "role": "user",
        "content": "I have some files in /home/user -- first list the directory, secondly read EVERY python file and summarize its top-level definitions"
    },
    {
        "role": "assistant",
        "content": "",
        "tool_calls": [
            "function=Function(name='ls', arguments={'path': '/home/user'})"
        ]
    },
    {
        "role": "tool",
        "content": "['.git', '.gitignore', 'compiled', 'main.py', 'main.py~', 'ollama.py', 'tools.py', 'tooldesc.py', 'utils.py']"
    },
    {
        "role": "assistant",
        "content": "Now I'll read all Python files in the directory to summarize their top-level definitions:\n\n1. **main.py**\n2. **ollama.py**\n3. **tools.py**\n4. **tooldesc.py**\n5. **utils.py**\n\nLet's start with `main.py`:\n\n",
        "tool_calls": null
    }
]

In contrast, if the user message has tools attached:

[
    {
        "role": "user",
        "content": "I have some files in /home/user -- first list the directory, secondly read EVERY python file and summarize its top-level definitions"
    },
    {
        "role": "assistant",
        "content": "",
        "tool_calls": [
            "function=Function(name='ls', arguments={'path': '/home/user'})"
        ]
    },
    {
        "role": "tool",
        "content": "['.git', '.gitignore', 'compiled', 'main.py', 'main.py~', 'ollama.py', 'tools.py', 'tooldesc.py', 'utils.py']"
    },
    {
        "role": "assistant",
        "content": "",
        "tool_calls": [
            "function=Function(name='read_file', arguments={'end': -1, 'path': '/home/user/main.py', 'start': 0})",
            "function=Function(name='read_file', arguments={'end': -1, 'path': '/home/user/ollama.py', 'start': 0})",
            "function=Function(name='read_file', arguments={'end': -1, 'path': '/home/user/tools.py', 'start': 0})",
            "function=Function(name='read_file', arguments={'end': -1, 'path': '/home/user/tooldesc.py', 'start': 0})",
            "function=Function(name='read_file', arguments={'end': -1, 'path': '/home/user/utils.py', 'start': 0})"
        ]
    },
    {
        "role": "tool",
        "content": "#!usr/bin/env python3\n\nprint(\"Hello, World!\")\n"
    },
    {
        "role": "tool",
        "content": "#!usr/bin/env python3\n\nprint(\"Hello, World!\")\n"
    },
    {
        "role": "tool",
        "content": "#!usr/bin/env python3\n\nprint(\"Hello, World!\")\n"
    },
    {
        "role": "tool",
        "content": "#!usr/bin/env python3\n\nprint(\"Hello, World!\")\n"
    },
    {
        "role": "tool",
        "content": "#!usr/bin/env python3\n\nprint(\"Hello, World!\")\n"
    },
    {
        "role": "assistant",
        "content": "Here are the Python files in /home/user:\n\n1. `main.py`\n2. `ollama.py`\n3. `tools.py`\n4. `tooldesc.py`\n5. `utils.py`\n\nAll these files contain a single top-level definition: a \"Hello, World!\" print statement in Python 3.\n\nWould you like to see the contents of any specific file or make modifications?",
        "tool_calls": null
    }
]

Suggested change to enable devstral to handle multiple tool calls:

--- Modelfile.orig	2025-07-04 00:18:11.192560198 +0000
+++ Modelfile	2025-07-04 08:20:33.028014363 +0000
@@ -2,10 +2,14 @@
 # To build a new Modelfile based on this, replace FROM with:
 FROM devstral:24b-small-2505-q8_0
 
-TEMPLATE """{{- range $index, $_ := .Messages }}
+TEMPLATE """{{- $lastUserIndex := -1 }}
+{{- range $index, $_ := .Messages }}
+{{- if eq .Role "user" }}{{ $lastUserIndex = $index }}{{ end }}
+{{- end }}
+{{- range $index, $_ := .Messages }}
 {{- if eq .Role "system" }}[SYSTEM_PROMPT]{{ .Content }}[/SYSTEM_PROMPT]
 {{- else if eq .Role "user" }}
-{{- if and (le (len (slice $.Messages $index)) 2) $.Tools }}[AVAILABLE_TOOLS]{{ $.Tools }}[/AVAILABLE_TOOLS]
+{{- if and (eq $lastUserIndex $index) $.Tools }}[AVAILABLE_TOOLS]{{ $.Tools }}[/AVAILABLE_TOOLS]
 {{- end }}[INST]{{ .Content }}[/INST]
 {{- else if eq .Role "assistant" }}
 {{- if .Content }}{{ .Content }}

Relevant log output


OS

Linux, Docker

GPU

Intel, Nvidia

CPU

Intel

Ollama version

0.9.3

Originally created by @rick-github on GitHub (Jul 4, 2025). Original GitHub issue: https://github.com/ollama/ollama/issues/11296 ### What is the issue? This line in the template: ``` {{- if and (le (len (slice $.Messages $index)) 2) $.Tools }}[AVAILABLE_TOOLS]{{ $.Tools }}[/AVAILABLE_TOOLS] ``` adds tools to the user message, when the user message is last or second to last message in the message list. So for a completion call with ``` [ system message (s) user message (U1) assistant message (A1) user message (U2) ] ``` the tools get attached to U2 and the model will generate a message (A2) with one or more tool calls. In the process of completing the tools calls, the client will update the message list with A2 and the results of the tool call(s) with a `tool` message. ``` [ S U1 A1 U2 A2 T1 ] ``` Since U2 is no longer the second to last or last message, the tools are not attached. If the model thinks it needs to make another tool call in order to satisfy the instruction in U2, it cannot. As a concrete example: ``` [ { "role": "user", "content": "I have some files in /home/user -- first list the directory, secondly read EVERY python file and summarize its top-level definitions" }, { "role": "assistant", "content": "", "tool_calls": [ "function=Function(name='ls', arguments={'path': '/home/user'})" ] }, { "role": "tool", "content": "['.git', '.gitignore', 'compiled', 'main.py', 'main.py~', 'ollama.py', 'tools.py', 'tooldesc.py', 'utils.py']" }, { "role": "assistant", "content": "Now I'll read all Python files in the directory to summarize their top-level definitions:\n\n1. **main.py**\n2. **ollama.py**\n3. **tools.py**\n4. **tooldesc.py**\n5. **utils.py**\n\nLet's start with `main.py`:\n\n", "tool_calls": null } ] ``` In contrast, if the user message has tools attached: ``` [ { "role": "user", "content": "I have some files in /home/user -- first list the directory, secondly read EVERY python file and summarize its top-level definitions" }, { "role": "assistant", "content": "", "tool_calls": [ "function=Function(name='ls', arguments={'path': '/home/user'})" ] }, { "role": "tool", "content": "['.git', '.gitignore', 'compiled', 'main.py', 'main.py~', 'ollama.py', 'tools.py', 'tooldesc.py', 'utils.py']" }, { "role": "assistant", "content": "", "tool_calls": [ "function=Function(name='read_file', arguments={'end': -1, 'path': '/home/user/main.py', 'start': 0})", "function=Function(name='read_file', arguments={'end': -1, 'path': '/home/user/ollama.py', 'start': 0})", "function=Function(name='read_file', arguments={'end': -1, 'path': '/home/user/tools.py', 'start': 0})", "function=Function(name='read_file', arguments={'end': -1, 'path': '/home/user/tooldesc.py', 'start': 0})", "function=Function(name='read_file', arguments={'end': -1, 'path': '/home/user/utils.py', 'start': 0})" ] }, { "role": "tool", "content": "#!usr/bin/env python3\n\nprint(\"Hello, World!\")\n" }, { "role": "tool", "content": "#!usr/bin/env python3\n\nprint(\"Hello, World!\")\n" }, { "role": "tool", "content": "#!usr/bin/env python3\n\nprint(\"Hello, World!\")\n" }, { "role": "tool", "content": "#!usr/bin/env python3\n\nprint(\"Hello, World!\")\n" }, { "role": "tool", "content": "#!usr/bin/env python3\n\nprint(\"Hello, World!\")\n" }, { "role": "assistant", "content": "Here are the Python files in /home/user:\n\n1. `main.py`\n2. `ollama.py`\n3. `tools.py`\n4. `tooldesc.py`\n5. `utils.py`\n\nAll these files contain a single top-level definition: a \"Hello, World!\" print statement in Python 3.\n\nWould you like to see the contents of any specific file or make modifications?", "tool_calls": null } ] ``` Suggested change to enable devstral to handle multiple tool calls: ```diff --- Modelfile.orig 2025-07-04 00:18:11.192560198 +0000 +++ Modelfile 2025-07-04 08:20:33.028014363 +0000 @@ -2,10 +2,14 @@ # To build a new Modelfile based on this, replace FROM with: FROM devstral:24b-small-2505-q8_0 -TEMPLATE """{{- range $index, $_ := .Messages }} +TEMPLATE """{{- $lastUserIndex := -1 }} +{{- range $index, $_ := .Messages }} +{{- if eq .Role "user" }}{{ $lastUserIndex = $index }}{{ end }} +{{- end }} +{{- range $index, $_ := .Messages }} {{- if eq .Role "system" }}[SYSTEM_PROMPT]{{ .Content }}[/SYSTEM_PROMPT] {{- else if eq .Role "user" }} -{{- if and (le (len (slice $.Messages $index)) 2) $.Tools }}[AVAILABLE_TOOLS]{{ $.Tools }}[/AVAILABLE_TOOLS] +{{- if and (eq $lastUserIndex $index) $.Tools }}[AVAILABLE_TOOLS]{{ $.Tools }}[/AVAILABLE_TOOLS] {{- end }}[INST]{{ .Content }}[/INST] {{- else if eq .Role "assistant" }} {{- if .Content }}{{ .Content }} ``` ### Relevant log output ```shell ``` ### OS Linux, Docker ### GPU Intel, Nvidia ### CPU Intel ### Ollama version 0.9.3
GiteaMirror added the bug label 2026-05-09 22:36:48 -05:00
Author
Owner

@jmorganca commented on GitHub (Jul 4, 2025):

@rick-github thank you so much for spotting this! And sorry for the issue. It should be fixed now:

ollama pull devstral
<!-- gh-comment-id:3037034268 --> @jmorganca commented on GitHub (Jul 4, 2025): @rick-github thank you so much for spotting this! And sorry for the issue. It should be fixed now: ``` ollama pull devstral ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/ollama#85135