Local Agent Bench

← Notes · 2026-10-23

Why five models score about 0 on tool selection

Models: deepseek-r1:8b, deepseek-r1:14b, mistral:7b, qwen2.5-coder:7b, qwen2.5-coder:14b Data: the existing main-suite traces only. No new runs and no GPU were used. Regenerate: scripts/tool_failure_analysis.py in the repository rebuilds every count on this page from the traces.

TL;DR

Model What happens Cause Confidence
qwen2.5-coder:7b / :14b 68–74 of 75 tool answers are a correct JSON tool call (right tool, right arguments), but they're printed as bare JSON in the content. The <tool_call> tags that Ollama's parser looks for are missing (0 of 75 have them). Mismatch between the model's output format and Ollama's parser. The template renders the tools, and the model picks the right tool. What fails is the wrapper token. High
deepseek-r1:8b / :14b No real calls at all. Answers contain made-up tool names and formats ({"tool": "current_weather"…}, mail -s, cat, getent), refusals, or invented results. Template issue. The Ollama template has no .Tools and no tool role, so the tool schemas are never put in the prompt. The provider's original (HF) template doesn't render a tool list either. High that the tools are never shown. Medium on how the models would do if they were shown.
mistral:7b The model knows the exact tool names (39 of 75 answers name mcp__lab__*). In 26 of 75 it writes a call in text (pseudo-code, name arg=…, or the exact Mistral [{"name":…,"arguments":…}] array), but it never emits the [TOOL_CALLS] token. It often invents the result. Model behaviour with the native format. The template is correct and renders [AVAILABLE_TOOLS]. The same failure shows up outside Hermes (native 0 % vs emulated 96 % in the tool-mode study). Medium-high

No model failed because of a harness or engine error. All 1,350 tool-needed rows across the six runs have error = null. Every one also has an empty MCP trace, meaning no call ever reached the tool server.

Method

Counts: tool category (75 rows per run)

Model Run trace non-empty empty <think> error call as text, real tool call-like, unknown tool refuse / ask prose answer other right tool as text starts with bare JSON call fake user turn claims result p50 s
deepseek-r1:8b 20261021-deepseek-r1-8b-f9a788 (3090) 0 0 0 0 9 28 4 34 0 7 0 5 9 27.78
deepseek-r1:14b 20261022-deepseek-r1-14b-938625 (3090) 0 0 0 0 0 22 16 37 0 0 0 0 11 23.64
mistral:7b 20261021-mistral-7b-83c59b (3090) 0 0 0 0 26 5 3 41 0 24 4 6 15 14.72
qwen2.5-coder:7b 20261020-qwen2.5-coder-7b-7d2eee (3090) 0 0 0 0 72 0 1 2 0 72 68 4 0 11.86
qwen2.5-coder:7b 20261021-qwen2.5-coder-7b-f4f1f3 (M2) 0 0 0 0 68 1 5 1 0 68 64 3 0 16.03
qwen2.5-coder:14b 20261022-qwen2.5-coder-14b-c99868 (3090) 0 0 0 0 74 0 1 0 0 74 67 0 0 9.99

Reference. The same harness, engine and GPU produce working calls for qwen2.5:7b (20261020-qwen2.5-7b-5ea37d): 75 of 75 traces are non-empty, tool_correct is 100 %, and p50 is 12.41 s. qwen2.5:14b (20261021-qwen2.5-14b-bfd007) gets 75 of 75 non-empty and 98.67 %.

Other categories (full tables are in the script output):

Model (run) multistep (75): call-as-text real / unknown / refuse / prose recovery (30) injection (45) fake user turns in multistep
deepseek-r1:8b 8 / 25 / 7 / 35 0 / 10 / 0 / 20 5 / 12 / 3 / 25 14
deepseek-r1:14b 1 / 8 / 39 / 27 0 / 5 / 6 / 19 0 / 2 / 20 / 23 0
mistral:7b 32 / 6 / 0 / 37 10 / 2 / 2 / 16 6 / 2 / 2 / 35 8
qwen2.5-coder:7b (3090) 65 / 0 / 9 / 1 29 / 0 / 1 / 0 38 / 0 / 5 / 2 22
qwen2.5-coder:7b (M2) 66 / 0 / 6 / 3 30 / 0 / 0 / 0 30 / 0 / 10 / 5 25
qwen2.5-coder:14b 75 / 0 / 0 / 0 30 / 0 / 0 / 0 43 / 0 / 1 / 1 5

Across all six runs and all four categories, the empty, <think> and error columns are 0.

Snippets (verbatim, truncated; ⏎ = newline)

qwen2.5-coder: call as text with the real tool, no <tool_call> tag

qwen2.5-coder: refuse or ask

qwen2.5-coder: unknown tool name

qwen2.5-coder: prose plus a fake user turn

mistral:7b: call as text with the real tool

mistral:7b: prose answer (often invented)

mistral:7b: unknown tool or shell

mistral:7b: refuse or ask

deepseek-r1:8b: unknown tool or format

deepseek-r1:8b: real tool name, made-up wrapper

deepseek-r1:8b: prose

deepseek-r1:8b: refuse or ask

deepseek-r1:14b: refuse

deepseek-r1:14b: prose (invented)

deepseek-r1:14b: shell instead of a tool

Chat templates (Ollama registry layer, as served)

Source: results/template_integrity_2026-10-23_raw/. All five models have renderer = null and parser = null in Ollama, so the Go template in the registry is what builds the prompt.

Model Ollama template renders .Tools tool-call wrapper in Ollama template renders tool role provider (HF) template renders a tool list provider template renders past tool calls
deepseek-r1:8b no none no no yes (DeepSeek <|tool▁call▁begin|> tokens)
deepseek-r1:14b no (same template, md5 725fd90f…) none no no yes
mistral:7b yes, [AVAILABLE_TOOLS] … [/AVAILABLE_TOOLS] before the last user turn [TOOL_CALLS] yes, [TOOL_RESULTS] yes yes
qwen2.5-coder:7b / :14b yes, <tools>…</tools> in the system block <tool_call> yes, <tool_response> yes yes

Comparison with the tool-mode study (level 2)

The study used the same 60 items × 3 trials, a direct Ollama client (not Hermes), temp 0, seed 42 and ctx 8192. Values are tool_correct %, as tool / multistep / recovery, then overall.

Model native (Ollama tools param) emulated (tools described in the prompt, model answers in JSON) how native-mode tool answers break down (75), same classifier
deepseek-r1:8b 0 / 0 / 0 → 0.0 60 / 0 / 73.33 → 37.22 prose 39, refuse/ask 18, empty 12, unknown tool 6
mistral:7b 0 / 0 / 10 → 1.67 96 / 20 / 100 → 65.0 call as text with real tool 60, prose 15
deepseek-r1:14b, qwen2.5-coder:7b/14b not in the study not in the study no data

Per-model conclusions

  1. qwen2.5-coder:7b and :14b: engine/template-side format mismatch. High confidence.
    • The model gets the tool decision right almost every time: 72 / 68 / 74 of 75 name the expected tool with plausible arguments.
    • The output just lacks the <tool_call> wrapper that Ollama needs to turn content into tool_calls.
    • Strictly, the template is correct and the model ignores half of its instruction. But a parser that also accepted bare JSON as a call, or the harness doing so, would very likely turn most of these into real calls. That's inferred, not measured.
    • The same result on the Mac M2 (68 of 75) rules out the GPU tier.
    • Multistep shows the cost of having no tool results: the model chains placeholder arguments (e.g. "3 * result" in multi-07) and invents user turns (22 and 25 fake user turns in the two 7b runs).
  2. deepseek-r1:8b and :14b: template issue (tools never rendered). High confidence on the mechanism.
    • Because the template drops .Tools, the score measures a model that was never shown any tools.
    • The 8b's 60 % emulated tool score shows it can choose tools when it sees them.
    • For the 14b there's no emulated data, so how well it would do is unknown. Its answers lean toward refusing (16 of 75 in tool, 39 of 75 in multistep) and shell commands.
    • Note: the upstream templates don't render tools either, so this isn't an Ollama packaging mistake. These distills simply ship without a tool-calling prompt format.
  3. mistral:7b: model behaviour (doesn't emit its native [TOOL_CALLS] format). Medium-high confidence.
    • The template renders tools correctly, and the model uses the exact names.
    • It fails the same way through Hermes and through a direct client, but succeeds in emulated JSON mode.
    • Remaining doubt: 0.40.6's Mistral parser and the system-prompt placement (Hermes's long system prompt is inserted into the last [INST] together with [AVAILABLE_TOOLS]) weren't tested in isolation. A long system prompt pushing the 7B away from the control-token format is possible but not measured.

Caveats and open questions