ollama - ✅(Solved) Fix Qwen3 tool calling via /api/chat tools parameter: malformed tool definitions (and soft switch thinking issue) [2 pull requests, 4 comments, 3 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
ollama/ollama#14601Fetched 2026-04-08 00:33:54
View on GitHub
Comments
4
Participants
3
Timeline
16
Reactions
2
Author
Timeline (top)
cross-referenced ×6commented ×4renamed ×2subscribed ×2

Root Cause

When the tools parameter is used, tool definitions in the model prompt are malformed:

{"type": "function", "function": {get_weather Get the current weather for a city {object [city] {"city":{"type":"string","description":"The name of the city"}}}}

The correct format per the official Qwen3 HuggingFace chat template:

{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather for a city", "parameters": {"type": "object", "properties": {"city": {"type": "string", "description": "The name of the city"}}, "required": ["city"]}}}

The root cause is visible in the Qwen3 modelfile template:

{"type": "function", "function": {{ .Function }}}

The .Function variable is rendered using its default Go struct string representation rather than being serialised as JSON. There is no toJson or equivalent function available in Ollama's template engine to fix this at the modelfile level — it requires a code change in Ollama's Go source.

Fix Action

Fix / Workaround

Unlike Bugs 1 and 2, this can be fixed at the modelfile level by removing the following lines from the template:

{{- if and $.IsThinkSet (eq $i $lastUserIdx) }}
   {{- if $.Think -}}
      {{- " "}}/think
   {{- else -}}
      {{- " "}}/no_think
   {{- end -}}
{{- end }}

Workaround Both bugs can be avoided by bypassing the tools parameter and embedding tool definitions directly in the system prompt as JSON strings in the Hermes format, matching the official Qwen3 chat template. Apply the /think//no_think fix via a custom modelfile by removing the redundant text instructions from the template

PR fix notes

PR #14695: template: add JSON String() methods for tool struct types

Description (problem / solution / changelog)

Summary

Adds String() methods to templateTool, templateToolFunction, and templateToolFunctionParameters so they serialize as valid JSON when referenced directly in Go templates.

Problem

When a model template uses {{ .Function }} to render a tool's function object (as Qwen3's template does), the output is Go's default struct format:

{get_weather Get the current weather for a city {object [city] {"city":{"type":"string",...}}}}

Instead of valid JSON:

{"name":"get_weather","description":"Get the current weather for a city","parameters":{"type":"object","required":["city"],"properties":{"city":{"type":"string",...}}}}

This breaks tool calling for any model whose template renders tool structs directly rather than accessing individual fields.

Fix

The existing map types (templateArgs, templateProperties) and slice type (templateTools) already implement String() with JSON marshaling. This PR extends the same pattern to the three struct types that were missing it:

  • templateTool — the full tool object ({{ . }} in a tools range)
  • templateToolFunction — the function sub-object ({{ .Function }})
  • templateToolFunctionParameters — the parameters sub-object ({{ .Function.Parameters }})

Tests

Added three new test cases:

  • TestTemplateToolFunctionJSON — verifies {{ .Function }} outputs valid JSON
  • TestTemplateToolJSON — verifies {{ . }} on a tool outputs valid JSON
  • TestTemplateToolParametersJSON — verifies {{ .Function.Parameters }} outputs valid JSON

All existing tests continue to pass.

Fixes #14601

Changed files

  • template/template.go (modified, +15/-0)
  • template/template_test.go (modified, +138/-0)

PR #15022: model/parsers: Close think block if tool block starts in Qwen3.5

Description (problem / solution / changelog)

This change fixes https://github.com/ollama/ollama/issues/14745 when the model starts a tool_call block without closing the think block:

Thinking: I need to mock Valkey for tests. Let me check how the app initializes Valkey and update TESTING.md with this information.
<tool_call>
<function=bash>
<parameter=command>
grep "redacted" | head -20
</parameter>
<parameter=description>
Find Valkey initialization
</parameter>
</function>
</tool_call>

I saw this behavior while using Opencode + Ollama 0.18.2 + qwen3.5:9b. I have been working for a couple of days with the same setup and this patch without problems.

Issues that can be fixed with this PR as well:

external:

Regards, and thank you all for this great tool.

Changed files

  • model/parsers/qwen35.go (modified, +9/-0)
  • model/parsers/qwen35_test.go (modified, +14/-8)

Code Example

{"type": "function", "function": {get_weather Get the current weather for a city {object [city] {"city":{"type":"string","description":"The name of the city"}}}}

---

{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather for a city", "parameters": {"type": "object", "properties": {"city": {"type": "string", "description": "The name of the city"}}, "required": ["city"]}}}

---

{"type": "function", "function": {{ .Function }}}

---

<|im_start|>assistant
<tool_call>
{"name": "get_weather", "arguments": {"city": "Paris"}}
</tool_call><|im_end|>
<|im_start|>user
<tool_response>
{"temperature": "14C", "condition": "Clear"}
</tool_response><|im_end|>

---

<|im_start|>assistant
<|im_end|>
<|im_start|>user
<tool_response>
{"temperature": "14C", "condition": "Clear"}
</tool_response><|im_end|>

---

{{- if and $.IsThinkSet (eq $i $lastUserIdx) }}
   {{- if $.Think -}}
      {{- " "}}/think
   {{- else -}}
      {{- " "}}/no_think
   {{- end -}}
{{- end }}

---
RAW_BUFFERClick to expand / collapse

What is the issue?

Description:

There are two bugs in how Ollama constructs prompts for Qwen3 when tools are passed via the /api/chat tools parameter. Both bugs are absent when tools are embedded directly in the system prompt with the tools parameter omitted. The bugs are visible in the prompt sent by Ollama to the model runner, which is logged when OLLAMA_DEBUG=2 (the prompt can also be seen by monitoring the runner's TCP socket via tcpdump.)

Bug 1: Tool definitions serialised as Go structs rather than valid JSON

When the tools parameter is used, tool definitions in the model prompt are malformed:

{"type": "function", "function": {get_weather Get the current weather for a city {object [city] {"city":{"type":"string","description":"The name of the city"}}}}

The correct format per the official Qwen3 HuggingFace chat template:

{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather for a city", "parameters": {"type": "object", "properties": {"city": {"type": "string", "description": "The name of the city"}}, "required": ["city"]}}}

The root cause is visible in the Qwen3 modelfile template:

{"type": "function", "function": {{ .Function }}}

The .Function variable is rendered using its default Go struct string representation rather than being serialised as JSON. There is no toJson or equivalent function available in Ollama's template engine to fix this at the modelfile level — it requires a code change in Ollama's Go source.

Bug 2: Assistant tool call content stripped from conversation history

When the tools parameter is used and a conversation history containing previous tool calls is passed to Ollama, those tool calls are automatically stripped from assistant turns before the prompt is sent to the runner.

For example, attempting to pass this conversation history:

<|im_start|>assistant
<tool_call>
{"name": "get_weather", "arguments": {"city": "Paris"}}
</tool_call><|im_end|>
<|im_start|>user
<tool_response>
{"temperature": "14C", "condition": "Clear"}
</tool_response><|im_end|>

Results in the model receiving:

<|im_start|>assistant
<|im_end|>
<|im_start|>user
<tool_response>
{"temperature": "14C", "condition": "Clear"}
</tool_response><|im_end|>

The model therefore has no visibility of its own previous tool calls, only the responses. The stripping occurs upstream of the template renderer in Ollama's context building code and cannot be fixed via the modelfile.

Update: This was a client-side issue — see comments below.

Additional finding: redundant /think//no_think text instructions

Ollama appends /think or /no_think to the model prompt when the think parameter is set. This leaks into conversation history as visible text and the model may comment on it. Per the official Qwen3 chat template, thinking mode is on by default and is switched off by appending <think>\n</think> to the prompt — which Ollama already does correctly. The /think//no_think instructions are irrelevant.

Unlike Bugs 1 and 2, this can be fixed at the modelfile level by removing the following lines from the template:

{{- if and $.IsThinkSet (eq $i $lastUserIdx) }}
   {{- if $.Think -}}
      {{- " "}}/think
   {{- else -}}
      {{- " "}}/no_think
   {{- end -}}
{{- end }}

Workaround Both bugs can be avoided by bypassing the tools parameter and embedding tool definitions directly in the system prompt as JSON strings in the Hermes format, matching the official Qwen3 chat template. Apply the /think//no_think fix via a custom modelfile by removing the redundant text instructions from the template

Relevant log output

OS

Linux

GPU

Nvidia

CPU

Intel

Ollama version

0.17.5

Model

qwen3:8b

extent analysis

Fix Plan

To fix the issues, we need to make the following changes:

  • Bug 1: Tool definitions serialization
    • Modify the Ollama source code to serialize the .Function variable as JSON.
    • Use the encoding/json package in Go to marshal the struct into a JSON string.
  • Remove redundant /think//no_think text instructions
    • Update the modelfile template by removing the lines that append /think or /no_think to the model prompt.

Code Changes

// In Ollama's source code, replace the line that renders the .Function variable
// with the following code to serialize it as JSON:
functionJson, err := json.Marshal(.Function)
if err != nil {
    // Handle the error
}
// Use the functionJson string in the template

// In the modelfile template, remove the following lines:
{{- if and $.IsThinkSet (eq $i $lastUserIdx) }}
   {{- if $.Think -}}
      {{- " "}}/think
   {{- else -}}
      {{- " "}}/no_think
   {{- end -}}
{{- end }}

Verification

To verify that the fixes work:

  1. Run Ollama with the updated source code and modelfile template.
  2. Pass tool definitions via the /api/chat tools parameter.
  3. Check the model prompt sent to the runner (logged when OLLLAMA_DEBUG=2) to ensure that tool definitions are serialized correctly as JSON.
  4. Verify that the /think//no_think text instructions are no longer appended to the model prompt.

Extra Tips

  • Make sure to test the changes thoroughly to ensure that they do not introduce any new issues.
  • Consider adding unit tests to cover the serialization of tool definitions and the removal of redundant text instructions.
  • If you encounter any issues with the encoding/json package, refer to the Go documentation for troubleshooting tips.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING