Skip to content

Instantly share code, notes, and snippets.

@yak1ex
Created August 12, 2026 17:07
Show Gist options
  • Select an option

  • Save yak1ex/11f48887a6cf1efcdd82cca36b8167ec to your computer and use it in GitHub Desktop.

Select an option

Save yak1ex/11f48887a6cf1efcdd82cca36b8167ec to your computer and use it in GitHub Desktop.
Analysis of Ollama tool calling by Codex

PARSER selects Ollama’s streaming output decoder. It separates raw generated tokens into:

  • message.content
  • message.thinking
  • message.tool_calls

It does not generally instruct the model; the matching RENDERER supplies the prompt instructions. Renderer and parser therefore need compatible conventions.

The current parser registry is defined in model/parsers/parsers.go.

Tool-capable parsers

PARSER value Expected model tool-call convention
qwen3 JSON inside <tool_call>...</tool_call>
qwen3-thinking Same JSON tool format, plus <think>...</think>
qwen3-coder Qwen pseudo-XML: <function=name><parameter=key>value</parameter></function> inside <tool_call>
qwen3.5, ornith Qwen3-Coder pseudo-XML, plus <think>...</think>
qwen3-vl-instruct JSON inside <tool_call>...</tool_call>
qwen3-vl-thinking Same JSON format, plus thinking
nemotron-3-nano, nemotron-3.5-nano Qwen-style pseudo-XML tool calls, plus thinking
ministral [TOOL_CALLS]name[ARGS]{JSON}
functiongemma <start_function_call>call:name{args}<end_function_call>
gemma4, gemma4-no-thinking <|tool_call>call:name{arguments}<tool_call|>
deepseek3 DeepSeek special-token envelope with function name, separator, and JSON arguments
cogito DeepSeek-like envelope, with a fenced JSON argument block
glm-4.7, glm-ocr GLM XML-like <tool_call>name<arg_key>…</arg_key><arg_value>…</arg_value></tool_call>
laguna, poolside-v1 <tool_call> with GLM-style argument tags or JSON; includes several fallback forms
lfm2, lfm2-thinking Python-style calls between <|tool_call_start|> and <|tool_call_end|>
olmo3 Python-style calls inside <function_calls>...</function_calls>
cohere JSON action array between <|START_ACTION|> and <|END_ACTION|>
harmony Harmony channel/recipient protocol with JSON arguments
glimmer Harmony-like message headers combined with ATEM XML-like function calls

Representative conventions

Qwen3: wrapped JSON

<tool_call>
{"name":"get_weather","arguments":{"location":"Tokyo"}}
</tool_call>

The body must be JSON with name and arguments (model/parsers/qwen3.go).

Qwen3-Coder / Qwen3.5: pseudo-XML

<tool_call>
<function=get_weather>
<parameter=location>
Tokyo
</parameter>
<parameter=units>
celsius
</parameter>
</function>
</tool_call>

This is not standard XML. Ollama converts <function=name> into an XML element with a name attribute before parsing it (model/parsers/qwen3coder.go).

Qwen3.5 additionally separates:

<think>
reasoning
</think>

and delegates the subsequent tool call to the Qwen3-Coder parser (model/parsers/qwen35.go).

Qwen3-VL: wrapped JSON

<tool_call>
{"name":"get_weather","arguments":{"location":"Tokyo"}}
</tool_call>

Although its wrapper resembles Qwen3.5, the contents are JSON rather than Qwen3.5 pseudo-XML (model/parsers/qwen3vl.go).

Ministral

[TOOL_CALLS]get_weather[ARGS]{"location":"Tokyo"}

Thinking-capable variants use:

[THINK]
reasoning
[/THINK]

See model/parsers/ministral.go.

DeepSeek 3

Conceptually:

<|tool▁calls▁begin|>
<|tool▁call▁begin|>
get_weather<|tool▁sep|>{"location":"Tokyo"}
<|tool▁call▁end|>
<|tool▁calls▁end|>

The unusual vertical bars and low-line characters are literal Unicode characters, not ordinary | and _. The arguments must be JSON (model/parsers/deepseek3.go).

Cogito

<|tool▁calls▁begin|>
<|tool▁call▁begin|>
function<|tool▁sep|>get_weather
```json
{"location":"Tokyo"}

<|tool▁call▁end|> <|tool▁calls▁end|>


Cogito specifically expects the JSON code fence as part of its format ([model/parsers/cogito.go](/C:/cygwin64/home/atarashi/work-git/ollama/model/parsers/cogito.go:309)).

#### FunctionGemma

```text
<start_function_call>
call:get_weather{location:"Tokyo"}
<end_function_call>

This uses a custom key:value argument syntax rather than strict JSON (model/parsers/functiongemma.go).

Gemma 4

<|tool_call>
call:get_weather{location:"Tokyo"}
<tool_call|>

Gemma 4 uses special asymmetric delimiters and a custom argument representation. Ollama also includes limited repair logic for malformed arguments (model/parsers/gemma4.go).

GLM 4.7

<tool_call>
get_weather
<arg_key>location</arg_key>
<arg_value>Tokyo</arg_value>
<arg_key>units</arg_key>
<arg_value>celsius</arg_value>
</tool_call>

Argument keys and values are paired by order. Their counts must match (model/parsers/glm46.go).

LFM2

<|tool_call_start|>
get_weather(location='Tokyo', units='celsius')
<|tool_call_end|>

It accepts Python-style calls and supports multiple calls:

<|tool_call_start|>
[get_weather(location='Tokyo'), get_time(zone='Asia/Tokyo')]
<|tool_call_end|>

It also has a fallback for unwrapped Python-style calls (model/parsers/lfm2.go).

OLMo 3

<function_calls>
get_weather(location="Tokyo")
get_time(zone="Asia/Tokyo")
</function_calls>

Each line is parsed as a Python-like function call (model/parsers/olmo3.go).

Cohere

<|START_ACTION|>
[
  {
    "tool_call_id":"0",
    "tool_name":"get_weather",
    "parameters":{"location":"Tokyo"}
  }
]
<|END_ACTION|>
<|END_OF_TURN_TOKEN|>

Normal content instead uses <|START_TEXT|>...<|END_TEXT|> (model/parsers/cohere.go).

Harmony

<|start|>assistant<|channel|>commentary to=functions.get_weather <|constrain|>json<|message|>
{"location":"Tokyo"}
<|call|>

The recipient identifies the function, and the message body contains its JSON arguments. Harmony also maps analysis to thinking and final to visible content.

Glimmer / ATEM

Conceptually:

<|start|>assistant to=get_weather<|message|>
<atem:function_calls>
<atem:invoke name="get_weather">
<atem:parameter name="location">Tokyo</atem:parameter>
</atem:invoke>
</atem:function_calls>
<|eot|>

The parser validates the recipient, invocation name, and declared tool before producing a call (model/parsers/glimmer.go).

Non-tool parsers

  • olmo3-think: extracts thinking terminated by </think> but does not parse tools.
  • passthrough: returns all generated text unchanged; no thinking or tool extraction.

Practical rule

A parser is not a generic “find any JSON that resembles a function call” mechanism. Each parser recognizes a particular model protocol:

matching RENDERER
    → teaches and frames the expected protocol

model output
    → must follow that protocol

matching PARSER
    → converts it into content, thinking, and API tool_calls

A mismatch can cause tool-call syntax to appear as ordinary text, be buffered indefinitely while streaming, or produce a parsing error. JSON alone is sufficient only for parser conventions that explicitly expect JSON at that position.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment