[GH-ISSUE #11800] gpt-oss:20b and gpt-oss:120b tool use throws unexpected error format in response (status code: 500) #54340

Closed
opened 2026-04-29 05:46:44 -05:00 by GiteaMirror · 12 comments
Owner

Originally created by @MarkWard0110 on GitHub (Aug 8, 2025).
Original GitHub issue: https://github.com/ollama/ollama/issues/11800

What is the issue?

What I am getting is the error thrown here
114c3f2265/server/routes.go (L373-L375)
Which then causes this error
114c3f2265/server/routes.go (L428-L429)
and that is the response I get in the client.

The gpt-oss is returning invalid JSON in the toolContent, and Ollama is failing to parse it, resulting in a 500 error being returned to my client. My client sees that error and retries the same message. This repeats until my client reaches max retries and aborts the call.

Could there be a graceful way to handle bad model output on the server side? Other models are not failing when they produce invalid tool calls. Their poorly generated tool calls are thrown into the message content and returned to the client.

The LLM's output should not be coupled to Ollama's HTTP status codes. Ollama could take the failed JSON and insert it into the message content. The current implementation provides zero feedback and information for the client to recover partial data from the invalid JSON. The lack of feedback means it is harder to prompt engineer a solution.

Relevant log output


OS

Windows

GPU

Nvidia RTX 3090

CPU

Intel Utral 9 285K 96GB RAM

Ollama version

0.11.0, 0.11.1, 0.11.3, 0.11.4

Originally created by @MarkWard0110 on GitHub (Aug 8, 2025). Original GitHub issue: https://github.com/ollama/ollama/issues/11800 ### What is the issue? What I am getting is the error thrown here https://github.com/ollama/ollama/blob/114c3f22657750cfb57f70c4a0d6e7389fb7a9fe/server/routes.go#L373-L375 Which then causes this error https://github.com/ollama/ollama/blob/114c3f22657750cfb57f70c4a0d6e7389fb7a9fe/server/routes.go#L428-L429 and that is the response I get in the client. The `gpt-oss` is returning invalid JSON in the `toolContent`, and Ollama is failing to parse it, resulting in a 500 error being returned to my client. My client sees that error and retries the same message. This repeats until my client reaches max retries and aborts the call. Could there be a graceful way to handle bad model output on the server side? Other models are not failing when they produce invalid tool calls. Their poorly generated tool calls are thrown into the message content and returned to the client. The LLM's output should not be coupled to Ollama's HTTP status codes. Ollama could take the failed JSON and insert it into the message content. The current implementation provides zero feedback and information for the client to recover partial data from the invalid JSON. The lack of feedback means it is harder to prompt engineer a solution. ### Relevant log output ```shell ``` ### OS Windows ### GPU Nvidia RTX 3090 ### CPU Intel Utral 9 285K 96GB RAM ### Ollama version 0.11.0, 0.11.1, 0.11.3, 0.11.4
GiteaMirror added the bug label 2026-04-29 05:46:44 -05:00
Author
Owner

@jvsteiner commented on GitHub (Aug 8, 2025):

I ran into something similar with the template too. In my case it was:

ResponseError('template: :108:130: executing "" at <index $prop.Type 0>: error calling index: reflect: slice index out of range')Traceback (most recent call last):

I installed the following corrected modelfile, and was able to run a langgraph agent locally:

apologize in advance for long post, file upload does not support modelfile type

FROM gpt-oss:latest
TEMPLATE """<|start|>system<|message|>You are ChatGPT, a large language model trained by OpenAI.
Knowledge cutoff: 2024-06
Current date: {{ currentDate }}
{{- if and .IsThinkSet .Think (ne .ThinkLevel "") }}

Reasoning: {{ .ThinkLevel }}
{{- else if or (not .IsThinkSet) (and .IsThinkSet .Think) }}

Reasoning: medium
{{- end }}

{{- $hasNonBuiltinTools := false }}
{{- if .Tools -}}
{{- $hasBrowserSearch := false }}
{{- $hasBrowserOpen := false }}
{{- $hasBrowserFind := false }}
{{- $hasPython := false }}
  {{- range .Tools }}
    {{- if eq .Function.Name "browser.search" -}}{{- $hasBrowserSearch = true -}}
    {{- else if eq .Function.Name "browser.open" -}}{{- $hasBrowserOpen = true -}}
    {{- else if eq .Function.Name "browser.find" -}}{{- $hasBrowserFind = true -}}
    {{- else if eq .Function.Name "python" -}}{{- $hasPython = true -}}
    {{- else }}{{ $hasNonBuiltinTools = true -}}
    {{- end }}
  {{- end }}
{{- if or $hasBrowserSearch $hasBrowserOpen $hasBrowserFind $hasPython }}

# Tools
{{- if or $hasBrowserSearch $hasBrowserOpen $hasBrowserFind }}

## browser

// Tool for browsing.
// The `cursor` appears in brackets before each browsing display: `[{cursor}]`.
// Cite information from the tool using the following format:
// `【{cursor}†L{line_start}(-L{line_end})?】`, for example: `【6†L9-L11】` or `【8†L3】`.
// Do not quote more than 10 words directly from the tool output.
// sources=web (default: web)
namespace browser {
{{- if $hasBrowserSearch }}

// Searches for information related to `query` and displays `topn` results.
type search = (_: {
query: string,
topn?: number, // default: 10
source?: string,
}) => any;
{{- end }}
{{- if $hasBrowserOpen }}

// Opens the link `id` from the page indicated by `cursor` starting at line number `loc`, showing `num_lines` lines.
// Valid link ids are displayed with the formatting: `【{id}†.*】`.
// If `cursor` is not provided, the most recent page is implied.
// If `id` is a string, it is treated as a fully qualified URL associated with `source`.
// If `loc` is not provided, the viewport will be positioned at the beginning of the document or centered on the most relevant passage, if available.
// Use this function without `id` to scroll to a new location of an opened page.
type open = (_: {
id?: number | string, // default: -1
cursor?: number, // default: -1
loc?: number, // default: -1
num_lines?: number, // default: -1
view_source?: boolean, // default: false
source?: string,
}) => any;
{{- end }}
{{- if $hasBrowserFind }}

// Finds exact matches of `pattern` in the current page, or the page given by `cursor`.
type find = (_: {
pattern: string,
cursor?: number, // default: -1
}) => any;
{{- end }}

} // namespace browser
{{- end }}{{/* end if has browser tools */}}
{{- if $hasPython }}

## python

Use this tool to execute Python code in your chain of thought. The code will not be shown to the user. This tool should be used for internal reasoning, but not for code that is intended to be visible to the user (e.g. when creating plots, tables, or files).

When you send a message containing Python code to python, it will be executed in a stateful Jupyter notebook environment. python will respond with the output of the execution or time out after 120.0 seconds. The drive at '/mnt/data' can be used to save and persist user files. Internet access for this session is UNKNOWN. Depends on the cluster.
{{- end }}{{/* end if hasPython */}}
{{- end }}{{/* end if has any built-in tools */}}
{{- end }}{{/* end if .Tools */}}

# Valid channels: analysis, commentary, final. Channel must be included for every message.{{ if $hasNonBuiltinTools }}
Calls to these tools must go to the commentary channel: 'functions'.
{{- end -}}<|end|>{{/* end of system */ -}}
{{- if or $hasNonBuiltinTools .System -}}
<|start|>developer<|message|>{{- if $hasNonBuiltinTools }}# Tools

## functions

namespace functions {
{{- range .Tools }}
{{- if not (or (eq .Function.Name "browser.search") (eq .Function.Name "browser.open") (eq .Function.Name "browser.find") (eq .Function.Name "python")) }}
{{if .Function.Description }}
// {{ .Function.Description }}
{{- end }}
{{- if and .Function.Parameters.Properties (gt (len .Function.Parameters.Properties) 0) }}
type {{ .Function.Name }} = (_: {
{{- range $name, $prop := .Function.Parameters.Properties }}
{{- if $prop.Description }}
  // {{ $prop.Description }}
{{- end }}
  {{ $name }}: {{ if and $prop.Type (gt (len $prop.Type) 0) }}{{ if gt (len $prop.Type) 1 }}{{ range $i, $t := $prop.Type }}{{ if $i }} | {{ end }}{{ $t }}{{ end }}{{ else }}{{ index $prop.Type 0 }}{{ end }}{{ else }}any{{ end }},
{{- end }}
}) => any;
{{- else }}
type {{ .Function.Name }} = () => any;
{{- end }}
{{- end }}{{/* end if not browser tool */}}
{{- end }}{{/* end of range .Tools */}}

} // namespace functions
{{- end }}{{/* end if hasNonBuiltinTools */}}
{{- if .System}}

# Instructions

{{ .System }}
{{- end -}}
<|end|>
{{- end -}}
{{- /* Find the index of the last user message */ -}}
{{- $lastUserIdx := -1 }}
{{- $prefillingContent := false }}
{{- $prefillingThinkingOnly := false }}
{{- range $i, $msg := .Messages }}
  {{- $last := eq (len (slice $.Messages $i)) 1 -}}
  {{- if eq $msg.Role "user" }}
    {{- $lastUserIdx = $i }}
  {{- end -}}
  {{- if and $last (eq $msg.Role "assistant") (gt (len $msg.Content) 0) }}
    {{- $prefillingContent = true }}
  {{- else if and $last (eq $msg.Role "assistant") (gt (len $msg.Thinking) 0) }}
    {{- $prefillingThinkingOnly = true }}
  {{- end }}
{{- end -}}
{{- /* Now render messages */ -}}
{{- range $i, $msg := .Messages }}
  {{- $last := eq (len (slice $.Messages $i)) 1 -}}
  {{- if (ne $msg.Role "system") -}}
    {{- if eq $msg.Role "tool" -}}
      {{- if or (eq $msg.ToolName "python") (eq $msg.ToolName "browser.search") (eq $msg.ToolName "browser.open") (eq $msg.ToolName "browser.find") -}}
        <|start|>{{ $msg.ToolName }} to=assistant<|message|>{{ $msg.Content }}<|end|>
      {{- else -}}
        <|start|>functions.{{ $msg.ToolName }} to=assistant<|message|>{{ $msg.Content }}<|end|>
      {{- end -}}
    {{- else if eq $msg.Role "assistant" -}}
      {{- if and $msg.Thinking (gt $i $lastUserIdx) -}}{{- /* Show thinking only after last user message */ -}}
      <|start|>assistant<|channel|>analysis<|message|>{{ $msg.Thinking }}{{- if not $prefillingThinkingOnly -}}<|end|>{{- end -}}
      {{- end -}}
      {{- if gt (len $msg.Content) 0 -}}
        <|start|>assistant<|channel|>final<|message|>{{ $msg.Content }}{{- if not $prefillingContent -}}<|end|>{{- end -}}
      {{- end -}}
      {{- if gt (len $msg.ToolCalls) 0 -}}
        {{- range $j, $toolCall := $msg.ToolCalls -}}
          {{- $isBuiltin := or (eq $toolCall.Function.Name "python") (eq $toolCall.Function.Name "browser.search") (eq $toolCall.Function.Name "browser.open") (eq $toolCall.Function.Name "browser.find") -}}
          <|start|>assistant<|channel|>{{ if $isBuiltin }}analysis{{ else }}commentary{{ end }} to={{ if not $isBuiltin}}functions.{{end}}{{ $toolCall.Function.Name }} <|constrain|>json<|message|>{{ $toolCall.Function.Arguments }}<|call|>
        {{- end -}}
      {{- end -}}
    {{- else if eq $msg.Role "user" -}}
      <|start|>{{ $msg.Role }}<|message|>{{ $msg.Content }}<|end|>
    {{- end }}
  {{- else }}
  {{- end }}
{{- end -}}
{{- if not (or $prefillingContent $prefillingThinkingOnly) -}}
<|start|>assistant
{{- end -}}"""
<!-- gh-comment-id:3167493042 --> @jvsteiner commented on GitHub (Aug 8, 2025): I ran into something similar with the template too. In my case it was: ``` ResponseError('template: :108:130: executing "" at <index $prop.Type 0>: error calling index: reflect: slice index out of range')Traceback (most recent call last): ``` I installed the following corrected modelfile, and was able to run a langgraph agent locally: apologize in advance for long post, file upload does not support modelfile type ``` FROM gpt-oss:latest TEMPLATE """<|start|>system<|message|>You are ChatGPT, a large language model trained by OpenAI. Knowledge cutoff: 2024-06 Current date: {{ currentDate }} {{- if and .IsThinkSet .Think (ne .ThinkLevel "") }} Reasoning: {{ .ThinkLevel }} {{- else if or (not .IsThinkSet) (and .IsThinkSet .Think) }} Reasoning: medium {{- end }} {{- $hasNonBuiltinTools := false }} {{- if .Tools -}} {{- $hasBrowserSearch := false }} {{- $hasBrowserOpen := false }} {{- $hasBrowserFind := false }} {{- $hasPython := false }} {{- range .Tools }} {{- if eq .Function.Name "browser.search" -}}{{- $hasBrowserSearch = true -}} {{- else if eq .Function.Name "browser.open" -}}{{- $hasBrowserOpen = true -}} {{- else if eq .Function.Name "browser.find" -}}{{- $hasBrowserFind = true -}} {{- else if eq .Function.Name "python" -}}{{- $hasPython = true -}} {{- else }}{{ $hasNonBuiltinTools = true -}} {{- end }} {{- end }} {{- if or $hasBrowserSearch $hasBrowserOpen $hasBrowserFind $hasPython }} # Tools {{- if or $hasBrowserSearch $hasBrowserOpen $hasBrowserFind }} ## browser // Tool for browsing. // The `cursor` appears in brackets before each browsing display: `[{cursor}]`. // Cite information from the tool using the following format: // `【{cursor}†L{line_start}(-L{line_end})?】`, for example: `【6†L9-L11】` or `【8†L3】`. // Do not quote more than 10 words directly from the tool output. // sources=web (default: web) namespace browser { {{- if $hasBrowserSearch }} // Searches for information related to `query` and displays `topn` results. type search = (_: { query: string, topn?: number, // default: 10 source?: string, }) => any; {{- end }} {{- if $hasBrowserOpen }} // Opens the link `id` from the page indicated by `cursor` starting at line number `loc`, showing `num_lines` lines. // Valid link ids are displayed with the formatting: `【{id}†.*】`. // If `cursor` is not provided, the most recent page is implied. // If `id` is a string, it is treated as a fully qualified URL associated with `source`. // If `loc` is not provided, the viewport will be positioned at the beginning of the document or centered on the most relevant passage, if available. // Use this function without `id` to scroll to a new location of an opened page. type open = (_: { id?: number | string, // default: -1 cursor?: number, // default: -1 loc?: number, // default: -1 num_lines?: number, // default: -1 view_source?: boolean, // default: false source?: string, }) => any; {{- end }} {{- if $hasBrowserFind }} // Finds exact matches of `pattern` in the current page, or the page given by `cursor`. type find = (_: { pattern: string, cursor?: number, // default: -1 }) => any; {{- end }} } // namespace browser {{- end }}{{/* end if has browser tools */}} {{- if $hasPython }} ## python Use this tool to execute Python code in your chain of thought. The code will not be shown to the user. This tool should be used for internal reasoning, but not for code that is intended to be visible to the user (e.g. when creating plots, tables, or files). When you send a message containing Python code to python, it will be executed in a stateful Jupyter notebook environment. python will respond with the output of the execution or time out after 120.0 seconds. The drive at '/mnt/data' can be used to save and persist user files. Internet access for this session is UNKNOWN. Depends on the cluster. {{- end }}{{/* end if hasPython */}} {{- end }}{{/* end if has any built-in tools */}} {{- end }}{{/* end if .Tools */}} # Valid channels: analysis, commentary, final. Channel must be included for every message.{{ if $hasNonBuiltinTools }} Calls to these tools must go to the commentary channel: 'functions'. {{- end -}}<|end|>{{/* end of system */ -}} {{- if or $hasNonBuiltinTools .System -}} <|start|>developer<|message|>{{- if $hasNonBuiltinTools }}# Tools ## functions namespace functions { {{- range .Tools }} {{- if not (or (eq .Function.Name "browser.search") (eq .Function.Name "browser.open") (eq .Function.Name "browser.find") (eq .Function.Name "python")) }} {{if .Function.Description }} // {{ .Function.Description }} {{- end }} {{- if and .Function.Parameters.Properties (gt (len .Function.Parameters.Properties) 0) }} type {{ .Function.Name }} = (_: { {{- range $name, $prop := .Function.Parameters.Properties }} {{- if $prop.Description }} // {{ $prop.Description }} {{- end }} {{ $name }}: {{ if and $prop.Type (gt (len $prop.Type) 0) }}{{ if gt (len $prop.Type) 1 }}{{ range $i, $t := $prop.Type }}{{ if $i }} | {{ end }}{{ $t }}{{ end }}{{ else }}{{ index $prop.Type 0 }}{{ end }}{{ else }}any{{ end }}, {{- end }} }) => any; {{- else }} type {{ .Function.Name }} = () => any; {{- end }} {{- end }}{{/* end if not browser tool */}} {{- end }}{{/* end of range .Tools */}} } // namespace functions {{- end }}{{/* end if hasNonBuiltinTools */}} {{- if .System}} # Instructions {{ .System }} {{- end -}} <|end|> {{- end -}} {{- /* Find the index of the last user message */ -}} {{- $lastUserIdx := -1 }} {{- $prefillingContent := false }} {{- $prefillingThinkingOnly := false }} {{- range $i, $msg := .Messages }} {{- $last := eq (len (slice $.Messages $i)) 1 -}} {{- if eq $msg.Role "user" }} {{- $lastUserIdx = $i }} {{- end -}} {{- if and $last (eq $msg.Role "assistant") (gt (len $msg.Content) 0) }} {{- $prefillingContent = true }} {{- else if and $last (eq $msg.Role "assistant") (gt (len $msg.Thinking) 0) }} {{- $prefillingThinkingOnly = true }} {{- end }} {{- end -}} {{- /* Now render messages */ -}} {{- range $i, $msg := .Messages }} {{- $last := eq (len (slice $.Messages $i)) 1 -}} {{- if (ne $msg.Role "system") -}} {{- if eq $msg.Role "tool" -}} {{- if or (eq $msg.ToolName "python") (eq $msg.ToolName "browser.search") (eq $msg.ToolName "browser.open") (eq $msg.ToolName "browser.find") -}} <|start|>{{ $msg.ToolName }} to=assistant<|message|>{{ $msg.Content }}<|end|> {{- else -}} <|start|>functions.{{ $msg.ToolName }} to=assistant<|message|>{{ $msg.Content }}<|end|> {{- end -}} {{- else if eq $msg.Role "assistant" -}} {{- if and $msg.Thinking (gt $i $lastUserIdx) -}}{{- /* Show thinking only after last user message */ -}} <|start|>assistant<|channel|>analysis<|message|>{{ $msg.Thinking }}{{- if not $prefillingThinkingOnly -}}<|end|>{{- end -}} {{- end -}} {{- if gt (len $msg.Content) 0 -}} <|start|>assistant<|channel|>final<|message|>{{ $msg.Content }}{{- if not $prefillingContent -}}<|end|>{{- end -}} {{- end -}} {{- if gt (len $msg.ToolCalls) 0 -}} {{- range $j, $toolCall := $msg.ToolCalls -}} {{- $isBuiltin := or (eq $toolCall.Function.Name "python") (eq $toolCall.Function.Name "browser.search") (eq $toolCall.Function.Name "browser.open") (eq $toolCall.Function.Name "browser.find") -}} <|start|>assistant<|channel|>{{ if $isBuiltin }}analysis{{ else }}commentary{{ end }} to={{ if not $isBuiltin}}functions.{{end}}{{ $toolCall.Function.Name }} <|constrain|>json<|message|>{{ $toolCall.Function.Arguments }}<|call|> {{- end -}} {{- end -}} {{- else if eq $msg.Role "user" -}} <|start|>{{ $msg.Role }}<|message|>{{ $msg.Content }}<|end|> {{- end }} {{- else }} {{- end }} {{- end -}} {{- if not (or $prefillingContent $prefillingThinkingOnly) -}} <|start|>assistant {{- end -}}""" ```
Author
Owner

@MarkWard0110 commented on GitHub (Aug 8, 2025):

I might have figured this one out, but early results indicate it has something to do with the "thinking" element.

Ollama + gpt-oss:20b/120b puts the model's thinking in the new thinking field in the message. I thought the new thinking feature would only do this if the chat request indicated the client supported the thinking field in the message.
My client was unaware of a "thinking" field on the Message, so it was creating the chat without the assistant's thinking. Most of the time, this was fine because the other thinking models incorporated the thinking into the content. Well, after upgrading my client with the "thinking" support, I am getting better results from the gpt-oss:20b

I am not getting the 500 response error from Ollama. This improves the model's benchmark scores because it can now attempt to complete the benchmarks it was previously failing on.

<!-- gh-comment-id:3169269420 --> @MarkWard0110 commented on GitHub (Aug 8, 2025): I might have figured this one out, but early results indicate it has something to do with the "thinking" element. Ollama + `gpt-oss:20b/120b` puts the model's thinking in the new `thinking` field in the `message`. I thought the new `thinking` feature would only do this if the chat request indicated the client supported the `thinking` field in the message. My client was unaware of a "thinking" field on the Message, so it was creating the chat without the assistant's thinking. Most of the time, this was fine because the other thinking models incorporated the thinking into the content. Well, after upgrading my client with the "thinking" support, I am getting better results from the `gpt-oss:20b` I am not getting the 500 response error from Ollama. This improves the model's benchmark scores because it can now attempt to complete the benchmarks it was previously failing on.
Author
Owner

@drifkin commented on GitHub (Aug 8, 2025):

ah yes, that sounds correct to me! for this model in particular thinking must be parsed because it is very particular about when thinking history should be provided and how it should be formatted (though to be fair, this increases quality on any thinking models). For non-streaming calls it will roundtrip (i.e., if you provide the same raw messages as history that were originally output by a previous call), but for streaming calls it's a little trickier and you'd need to handle it yourself since you're assembling messages (or the client you're using needs to)

<!-- gh-comment-id:3169348757 --> @drifkin commented on GitHub (Aug 8, 2025): ah yes, that sounds correct to me! for this model in particular thinking must be parsed because it is very particular about when thinking history should be provided and how it should be formatted (though to be fair, this increases quality on any thinking models). For non-streaming calls it will roundtrip (i.e., if you provide the same raw messages as history that were originally output by a previous call), but for streaming calls it's a little trickier and you'd need to handle it yourself since you're assembling messages (or the client you're using needs to)
Author
Owner

@drifkin commented on GitHub (Aug 8, 2025):

also @MarkWard0110, your point in the original message about us not providing useful feedback to the caller during a bad parse is a great one. I've opened #11821 to track some improvements I'd like to make there. I have noticed that generally these kinds of errors are happening from bad/incomplete history (e.g., missing function names or missing thinking contents), so I'd err on the side of looking at the rendered prompt (providing logs with OLLAMA_DEBUG=1 can be very useful for that), but I don't want us to hide anything from the user

<!-- gh-comment-id:3169359962 --> @drifkin commented on GitHub (Aug 8, 2025): also @MarkWard0110, your point in the original message about us not providing useful feedback to the caller during a bad parse is a great one. I've opened #11821 to track some improvements I'd like to make there. I have noticed that generally these kinds of errors are happening from bad/incomplete history (e.g., missing function names or missing thinking contents), so I'd err on the side of looking at the rendered prompt (providing logs with OLLAMA_DEBUG=1 can be very useful for that), but I don't want us to hide anything from the user
Author
Owner

@drifkin commented on GitHub (Aug 8, 2025):

I ran into something similar with the template too. In my case it was:

ResponseError('template: :108:130: executing "" at <index $prop.Type 0>: error calling index: reflect: slice index out of range')Traceback (most recent call last):

I installed the following corrected modelfile, and was able to run a langgraph agent locally:

apologize in advance for long post, file upload does not support modelfile type

This one is likely fixed by https://github.com/ollama/ollama/pull/11705, but we still need to push out a new template later today and you'll need to ollama pull the models to get updated templates (the weights won't need to be redownloaded though)

If anyone wants to try out the new template ahead of it being pushed, I'm putting it below here. You can create a small Modelfile that contains this template (similar to https://github.com/ollama/ollama?tab=readme-ov-file#customize-a-prompt but changing TEMPLATE instead)

TEMPLATE """<|start|>system<|message|>You are ChatGPT, a large language model trained by OpenAI.
Knowledge cutoff: 2024-06
Current date: {{ currentDate }}
{{- if and .IsThinkSet .Think (ne .ThinkLevel "") }}

Reasoning: {{ .ThinkLevel }}
{{- else if or (not .IsThinkSet) (and .IsThinkSet .Think) }}

Reasoning: medium
{{- end }}

{{- $hasNonBuiltinTools := false }}
{{- if .Tools -}}
{{- $hasBrowserSearch := false }}
{{- $hasBrowserOpen := false }}
{{- $hasBrowserFind := false }}
{{- $hasPython := false }}
  {{- range .Tools }}
    {{- if eq .Function.Name "browser.search" -}}{{- $hasBrowserSearch = true -}}
    {{- else if eq .Function.Name "browser.open" -}}{{- $hasBrowserOpen = true -}}
    {{- else if eq .Function.Name "browser.find" -}}{{- $hasBrowserFind = true -}}
    {{- else if eq .Function.Name "python" -}}{{- $hasPython = true -}}
    {{- else }}{{ $hasNonBuiltinTools = true -}}
    {{- end }}
  {{- end }}
{{- if or $hasBrowserSearch $hasBrowserOpen $hasBrowserFind $hasPython }}

# Tools
{{- if or $hasBrowserSearch $hasBrowserOpen $hasBrowserFind }}

## browser

// Tool for browsing.
// The `cursor` appears in brackets before each browsing display: `[{cursor}]`.
// Cite information from the tool using the following format:
// `【{cursor}†L{line_start}(-L{line_end})?】`, for example: `【6†L9-L11】` or `【8†L3】`.
// Do not quote more than 10 words directly from the tool output.
// sources=web (default: web)
namespace browser {
{{- if $hasBrowserSearch }}

// Searches for information related to `query` and displays `topn` results.
type search = (_: {
query: string,
topn?: number, // default: 10
source?: string,
}) => any;
{{- end }}
{{- if $hasBrowserOpen }}

// Opens the link `id` from the page indicated by `cursor` starting at line number `loc`, showing `num_lines` lines.
// Valid link ids are displayed with the formatting: `【{id}†.*】`.
// If `cursor` is not provided, the most recent page is implied.
// If `id` is a string, it is treated as a fully qualified URL associated with `source`.
// If `loc` is not provided, the viewport will be positioned at the beginning of the document or centered on the most relevant passage, if available.
// Use this function without `id` to scroll to a new location of an opened page.
type open = (_: {
id?: number | string, // default: -1
cursor?: number, // default: -1
loc?: number, // default: -1
num_lines?: number, // default: -1
view_source?: boolean, // default: false
source?: string,
}) => any;
{{- end }}
{{- if $hasBrowserFind }}

// Finds exact matches of `pattern` in the current page, or the page given by `cursor`.
type find = (_: {
pattern: string,
cursor?: number, // default: -1
}) => any;
{{- end }}

} // namespace browser
{{- end }}{{/* end if has browser tools */}}
{{- if $hasPython }}

## python

Use this tool to execute Python code in your chain of thought. The code will not be shown to the user. This tool should be used for internal reasoning, but not for code that is intended to be visible to the user (e.g. when creating plots, tables, or files).

When you send a message containing Python code to python, it will be executed in a stateful Jupyter notebook environment. python will respond with the output of the execution or time out after 120.0 seconds. The drive at '/mnt/data' can be used to save and persist user files. Internet access for this session is UNKNOWN. Depends on the cluster.
{{- end }}{{/* end if hasPython */}}
{{- end }}{{/* end if has any built-in tools */}}
{{- end }}{{/* end if .Tools */}}

# Valid channels: analysis, commentary, final. Channel must be included for every message.{{ if $hasNonBuiltinTools }}
Calls to these tools must go to the commentary channel: 'functions'.
{{- end -}}<|end|>{{/* end of system */ -}}
{{- if or $hasNonBuiltinTools .System -}}
<|start|>developer<|message|>{{- if $hasNonBuiltinTools }}# Tools

## functions

namespace functions {
{{- range .Tools }}
{{- if not (or (eq .Function.Name "browser.search") (eq .Function.Name "browser.open") (eq .Function.Name "browser.find") (eq .Function.Name "python")) }}
{{if .Function.Description }}
// {{ .Function.Description }}
{{- end }}
{{- if and .Function.Parameters.Properties (gt (len .Function.Parameters.Properties) 0) }}
type {{ .Function.Name }} = (_: {
{{- range $name, $prop := .Function.Parameters.Properties }}
{{- if $prop.Description }}
  // {{ $prop.Description }}
{{- end }}
  {{ $name }}: {{ $prop | toTypeScriptType }},
{{- end }}
}) => any;
{{- else }}
type {{ .Function.Name }} = () => any;
{{- end }}
{{- end }}{{/* end if not browser tool */}}
{{- end }}{{/* end of range .Tools */}}

} // namespace functions
{{- end }}{{/* end if hasNonBuiltinTools */}}
{{- if .System}}

# Instructions

{{ .System }}
{{- end -}}
<|end|>
{{- end -}}
{{- /* Find the index of the last user message */ -}}
{{- $lastUserIdx := -1 }}
{{- $prefillingContent := false }}
{{- $prefillingThinkingOnly := false }}
{{- range $i, $msg := .Messages }}
  {{- $last := eq (len (slice $.Messages $i)) 1 -}}
  {{- if eq $msg.Role "user" }}
    {{- $lastUserIdx = $i }}
  {{- end -}}
  {{- if and $last (eq $msg.Role "assistant") (gt (len $msg.Content) 0) }}
    {{- $prefillingContent = true }}
  {{- else if and $last (eq $msg.Role "assistant") (gt (len $msg.Thinking) 0) }}
    {{- $prefillingThinkingOnly = true }}
  {{- end }}
{{- end -}}
{{- /* Now render messages */ -}}
{{- range $i, $msg := .Messages }}
  {{- $last := eq (len (slice $.Messages $i)) 1 -}}
  {{- if (ne $msg.Role "system") -}}
    {{- if eq $msg.Role "tool" -}}
      {{- if or (eq $msg.ToolName "python") (eq $msg.ToolName "browser.search") (eq $msg.ToolName "browser.open") (eq $msg.ToolName "browser.find") -}}
        <|start|>{{ $msg.ToolName }} to=assistant<|message|>{{ $msg.Content }}<|end|>
      {{- else -}}
        <|start|>functions.{{ $msg.ToolName }} to=assistant<|message|>{{ $msg.Content }}<|end|>
      {{- end -}}
    {{- else if eq $msg.Role "assistant" -}}
      {{- if and $msg.Thinking (gt $i $lastUserIdx) -}}{{- /* Show thinking only after last user message */ -}}
      <|start|>assistant<|channel|>analysis<|message|>{{ $msg.Thinking }}{{- if not $prefillingThinkingOnly -}}<|end|>{{- end -}}
      {{- end -}}
      {{- if gt (len $msg.Content) 0 -}}
        <|start|>assistant<|channel|>final<|message|>{{ $msg.Content }}{{- if not $prefillingContent -}}<|end|>{{- end -}}
      {{- end -}}
      {{- if gt (len $msg.ToolCalls) 0 -}}
        {{- range $j, $toolCall := $msg.ToolCalls -}}
          {{- $isBuiltin := or (eq $toolCall.Function.Name "python") (eq $toolCall.Function.Name "browser.search") (eq $toolCall.Function.Name "browser.open") (eq $toolCall.Function.Name "browser.find") -}}
          <|start|>assistant<|channel|>{{ if $isBuiltin }}analysis{{ else }}commentary{{ end }} to={{ if not $isBuiltin}}functions.{{end}}{{ $toolCall.Function.Name }} <|constrain|>json<|message|>{{ $toolCall.Function.Arguments }}<|call|>
        {{- end -}}
      {{- end -}}
    {{- else if eq $msg.Role "user" -}}
      <|start|>{{ $msg.Role }}<|message|>{{ $msg.Content }}<|end|>
    {{- end }}
  {{- else }}
  {{- end }}
{{- end -}}
{{- if not (or $prefillingContent $prefillingThinkingOnly) -}}
<|start|>assistant
{{- end -}}"""
<!-- gh-comment-id:3169366464 --> @drifkin commented on GitHub (Aug 8, 2025): > I ran into something similar with the template too. In my case it was: > > ``` > ResponseError('template: :108:130: executing "" at <index $prop.Type 0>: error calling index: reflect: slice index out of range')Traceback (most recent call last): > ``` > > I installed the following corrected modelfile, and was able to run a langgraph agent locally: > > apologize in advance for long post, file upload does not support modelfile type > This one is likely fixed by https://github.com/ollama/ollama/pull/11705, but we still need to push out a new template later today and you'll need to `ollama pull` the models to get updated templates (the weights won't need to be redownloaded though) If anyone wants to try out the new template ahead of it being pushed, I'm putting it below here. You can create a small Modelfile that contains this template (similar to https://github.com/ollama/ollama?tab=readme-ov-file#customize-a-prompt but changing TEMPLATE instead) <details> ``` TEMPLATE """<|start|>system<|message|>You are ChatGPT, a large language model trained by OpenAI. Knowledge cutoff: 2024-06 Current date: {{ currentDate }} {{- if and .IsThinkSet .Think (ne .ThinkLevel "") }} Reasoning: {{ .ThinkLevel }} {{- else if or (not .IsThinkSet) (and .IsThinkSet .Think) }} Reasoning: medium {{- end }} {{- $hasNonBuiltinTools := false }} {{- if .Tools -}} {{- $hasBrowserSearch := false }} {{- $hasBrowserOpen := false }} {{- $hasBrowserFind := false }} {{- $hasPython := false }} {{- range .Tools }} {{- if eq .Function.Name "browser.search" -}}{{- $hasBrowserSearch = true -}} {{- else if eq .Function.Name "browser.open" -}}{{- $hasBrowserOpen = true -}} {{- else if eq .Function.Name "browser.find" -}}{{- $hasBrowserFind = true -}} {{- else if eq .Function.Name "python" -}}{{- $hasPython = true -}} {{- else }}{{ $hasNonBuiltinTools = true -}} {{- end }} {{- end }} {{- if or $hasBrowserSearch $hasBrowserOpen $hasBrowserFind $hasPython }} # Tools {{- if or $hasBrowserSearch $hasBrowserOpen $hasBrowserFind }} ## browser // Tool for browsing. // The `cursor` appears in brackets before each browsing display: `[{cursor}]`. // Cite information from the tool using the following format: // `【{cursor}†L{line_start}(-L{line_end})?】`, for example: `【6†L9-L11】` or `【8†L3】`. // Do not quote more than 10 words directly from the tool output. // sources=web (default: web) namespace browser { {{- if $hasBrowserSearch }} // Searches for information related to `query` and displays `topn` results. type search = (_: { query: string, topn?: number, // default: 10 source?: string, }) => any; {{- end }} {{- if $hasBrowserOpen }} // Opens the link `id` from the page indicated by `cursor` starting at line number `loc`, showing `num_lines` lines. // Valid link ids are displayed with the formatting: `【{id}†.*】`. // If `cursor` is not provided, the most recent page is implied. // If `id` is a string, it is treated as a fully qualified URL associated with `source`. // If `loc` is not provided, the viewport will be positioned at the beginning of the document or centered on the most relevant passage, if available. // Use this function without `id` to scroll to a new location of an opened page. type open = (_: { id?: number | string, // default: -1 cursor?: number, // default: -1 loc?: number, // default: -1 num_lines?: number, // default: -1 view_source?: boolean, // default: false source?: string, }) => any; {{- end }} {{- if $hasBrowserFind }} // Finds exact matches of `pattern` in the current page, or the page given by `cursor`. type find = (_: { pattern: string, cursor?: number, // default: -1 }) => any; {{- end }} } // namespace browser {{- end }}{{/* end if has browser tools */}} {{- if $hasPython }} ## python Use this tool to execute Python code in your chain of thought. The code will not be shown to the user. This tool should be used for internal reasoning, but not for code that is intended to be visible to the user (e.g. when creating plots, tables, or files). When you send a message containing Python code to python, it will be executed in a stateful Jupyter notebook environment. python will respond with the output of the execution or time out after 120.0 seconds. The drive at '/mnt/data' can be used to save and persist user files. Internet access for this session is UNKNOWN. Depends on the cluster. {{- end }}{{/* end if hasPython */}} {{- end }}{{/* end if has any built-in tools */}} {{- end }}{{/* end if .Tools */}} # Valid channels: analysis, commentary, final. Channel must be included for every message.{{ if $hasNonBuiltinTools }} Calls to these tools must go to the commentary channel: 'functions'. {{- end -}}<|end|>{{/* end of system */ -}} {{- if or $hasNonBuiltinTools .System -}} <|start|>developer<|message|>{{- if $hasNonBuiltinTools }}# Tools ## functions namespace functions { {{- range .Tools }} {{- if not (or (eq .Function.Name "browser.search") (eq .Function.Name "browser.open") (eq .Function.Name "browser.find") (eq .Function.Name "python")) }} {{if .Function.Description }} // {{ .Function.Description }} {{- end }} {{- if and .Function.Parameters.Properties (gt (len .Function.Parameters.Properties) 0) }} type {{ .Function.Name }} = (_: { {{- range $name, $prop := .Function.Parameters.Properties }} {{- if $prop.Description }} // {{ $prop.Description }} {{- end }} {{ $name }}: {{ $prop | toTypeScriptType }}, {{- end }} }) => any; {{- else }} type {{ .Function.Name }} = () => any; {{- end }} {{- end }}{{/* end if not browser tool */}} {{- end }}{{/* end of range .Tools */}} } // namespace functions {{- end }}{{/* end if hasNonBuiltinTools */}} {{- if .System}} # Instructions {{ .System }} {{- end -}} <|end|> {{- end -}} {{- /* Find the index of the last user message */ -}} {{- $lastUserIdx := -1 }} {{- $prefillingContent := false }} {{- $prefillingThinkingOnly := false }} {{- range $i, $msg := .Messages }} {{- $last := eq (len (slice $.Messages $i)) 1 -}} {{- if eq $msg.Role "user" }} {{- $lastUserIdx = $i }} {{- end -}} {{- if and $last (eq $msg.Role "assistant") (gt (len $msg.Content) 0) }} {{- $prefillingContent = true }} {{- else if and $last (eq $msg.Role "assistant") (gt (len $msg.Thinking) 0) }} {{- $prefillingThinkingOnly = true }} {{- end }} {{- end -}} {{- /* Now render messages */ -}} {{- range $i, $msg := .Messages }} {{- $last := eq (len (slice $.Messages $i)) 1 -}} {{- if (ne $msg.Role "system") -}} {{- if eq $msg.Role "tool" -}} {{- if or (eq $msg.ToolName "python") (eq $msg.ToolName "browser.search") (eq $msg.ToolName "browser.open") (eq $msg.ToolName "browser.find") -}} <|start|>{{ $msg.ToolName }} to=assistant<|message|>{{ $msg.Content }}<|end|> {{- else -}} <|start|>functions.{{ $msg.ToolName }} to=assistant<|message|>{{ $msg.Content }}<|end|> {{- end -}} {{- else if eq $msg.Role "assistant" -}} {{- if and $msg.Thinking (gt $i $lastUserIdx) -}}{{- /* Show thinking only after last user message */ -}} <|start|>assistant<|channel|>analysis<|message|>{{ $msg.Thinking }}{{- if not $prefillingThinkingOnly -}}<|end|>{{- end -}} {{- end -}} {{- if gt (len $msg.Content) 0 -}} <|start|>assistant<|channel|>final<|message|>{{ $msg.Content }}{{- if not $prefillingContent -}}<|end|>{{- end -}} {{- end -}} {{- if gt (len $msg.ToolCalls) 0 -}} {{- range $j, $toolCall := $msg.ToolCalls -}} {{- $isBuiltin := or (eq $toolCall.Function.Name "python") (eq $toolCall.Function.Name "browser.search") (eq $toolCall.Function.Name "browser.open") (eq $toolCall.Function.Name "browser.find") -}} <|start|>assistant<|channel|>{{ if $isBuiltin }}analysis{{ else }}commentary{{ end }} to={{ if not $isBuiltin}}functions.{{end}}{{ $toolCall.Function.Name }} <|constrain|>json<|message|>{{ $toolCall.Function.Arguments }}<|call|> {{- end -}} {{- end -}} {{- else if eq $msg.Role "user" -}} <|start|>{{ $msg.Role }}<|message|>{{ $msg.Content }}<|end|> {{- end }} {{- else }} {{- end }} {{- end -}} {{- if not (or $prefillingContent $prefillingThinkingOnly) -}} <|start|>assistant {{- end -}}""" ``` </details>
Author
Owner

@MarkWard0110 commented on GitHub (Aug 9, 2025):

I have not updated the model file. I have been testing with a client that provides the assistant "thinking" field back into the chat, and I have not been able to reproduce the issue since.

<!-- gh-comment-id:3169547460 --> @MarkWard0110 commented on GitHub (Aug 9, 2025): I have not updated the model file. I have been testing with a client that provides the assistant "thinking" field back into the chat, and I have not been able to reproduce the issue since.
Author
Owner

@abcbarryn commented on GitHub (Nov 2, 2025):

What was/is the change or pull request that fixes this?

<!-- gh-comment-id:3478450805 --> @abcbarryn commented on GitHub (Nov 2, 2025): What was/is the change or pull request that fixes this?
Author
Owner

@Ryzhtus commented on GitHub (Jan 13, 2026):

Hi, I've faced the same error today 'template: :108:130: executing "" at <index $prop.Type 0>: error calling index: reflect: slice index out of range') using gpt-oss:120b in Ollama locally connected to OpenWebUI

<!-- gh-comment-id:3744918010 --> @Ryzhtus commented on GitHub (Jan 13, 2026): Hi, I've faced the same error today 'template: :108:130: executing "" at <index $prop.Type 0>: error calling index: reflect: slice index out of range') using gpt-oss:120b in Ollama locally connected to OpenWebUI
Author
Owner

@abcbarryn commented on GitHub (Jan 14, 2026):

@Ryzhtus, are you sure you are running Ollama version 0.12.10 or later? I reported this issue originally but I have not seen it on newer versions of Ollama since @guybrush1984 added his commit to fix it.

<!-- gh-comment-id:3747593886 --> @abcbarryn commented on GitHub (Jan 14, 2026): @Ryzhtus, are you sure you are running Ollama version 0.12.10 or later? I reported this issue originally but I have not seen it on newer versions of Ollama since @guybrush1984 added his commit to fix it.
Author
Owner

@ImanolFotia commented on GitHub (Jan 14, 2026):

Having the exact same issue as @Ryzhtus

$ ollama --version
Warning: client version is 0.14.0

<!-- gh-comment-id:3751167583 --> @ImanolFotia commented on GitHub (Jan 14, 2026): Having the exact same issue as @Ryzhtus $ ollama --version Warning: client version is 0.14.0
Author
Owner

@leppa commented on GitHub (Jan 15, 2026):

@Ryzhtus, @ImanolFotia, I got exactly the same issue today (Ollama v0.14.1). After I updated the model, the error has disappeared.
Try doing ollama pull gpt-oss:<version>.

<!-- gh-comment-id:3756434016 --> @leppa commented on GitHub (Jan 15, 2026): @Ryzhtus, @ImanolFotia, I got exactly the same issue today (Ollama v0.14.1). After I updated the model, the error has disappeared. Try doing `ollama pull gpt-oss:<version>`.
Author
Owner

@ImanolFotia commented on GitHub (Jan 16, 2026):

Yep, that did it!
Thanks @leppa!

<!-- gh-comment-id:3760089589 --> @ImanolFotia commented on GitHub (Jan 16, 2026): Yep, that did it! Thanks @leppa!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/ollama#54340