Tool Calling With Local Models: Making Agents Stop Breaking on Malformed JSON

Written by Jakub Rusinowski · Last updated 2026-08-04 · Hardware figures computed by our VRAM engine

Tool calling is where local coding agents break most often — not because the model cannot reason, but because it emits JSON the harness cannot parse. Three fixes, in order of impact: use a model whose chat template has native tool-call support, constrain decoding with a JSON-schema grammar so invalid output is impossible by construction, and keep the tool surface small. Together these turn a flaky agent into a dependable one without changing the model tier.

An agent is a model that can act. The mechanism is unglamorous: the model emits a structured call — a name and a JSON argument object — and the harness runs it. Every capability you care about, from reading a file to running your test suite, passes through that one narrow interface.

Frontier models make it look trivial. Local models expose how narrow it really is. A 8–32B model asked to produce a tool call will, at some rate, wrap the JSON in an explanation, use file where the schema said path, emit two calls where one was allowed, or start a call and drift into prose. The harness sees an unparseable action, and your agent stalls, retries, or dies. This is not a reasoning failure and a bigger model is not the only fix.

Three ways a local model can call a tool

Knowing which mechanism your stack uses explains most of its failures.

1 · Native tool calling via the chat template. The model was fine-tuned to emit calls in a specific format, and the GGUF's chat template encodes it. The server parses that format and returns a structured tool_calls field in the OpenAI-compatible response. This is the reliable path — and it is why models advertised as *agentic* or *coder* variants behave so much better in agents than general chat models of the same size.

2 · Prompted JSON. The harness describes the tools in the system prompt and asks for JSON back, parsing it itself. Works with any model; fails at a much higher rate, because nothing enforces the shape. Most "why did my agent stop?" reports live here.

3 · Edit formats instead of tool calls. Aider's approach: rather than a tool schema, ask for a diff or a search/replace block. It is a *text* format, so weaker models handle it better than nested JSON, and it fails visibly (the patch does not apply) rather than silently. This is a genuine reason Aider punches above its weight on small models.

Prefer 1, fall back to 3, treat 2 as the thing to constrain.

Grammar-constrained decoding: the fix most people skip

The strongest available fix does not ask the model to behave — it makes misbehaviour impossible. At each token, constrained decoding masks out every token that could not continue a valid output under a formal grammar. Invalid JSON is not corrected after the fact; it is never generated.

llama.cpp uses GBNF (its BNF-style grammar format) and converts a JSON Schema to a grammar automatically, so in practice you hand it the same schema you would give the tool definition. The OpenAI-compatible servers built on it expose this as structured output / JSON-schema response formats; vLLM and SGLang have equivalents.

# llama.cpp server: force every response to match a tool-call schema
llama-server -m qwen3-coder-8b-q4_k_m.gguf --port 8080 --ctx-size 16384

curl http://localhost:8080/v1/chat/completions -H "Content-Type: application/json" -d '{
  "messages": [{"role":"user","content":"Read src/auth.py"}],
  "response_format": {
    "type": "json_schema",
    "json_schema": {"name":"tool_call","schema":{
      "type":"object",
      "properties":{
        "tool":{"type":"string","enum":["read_file","edit_file","run_tests"]},
        "args":{"type":"object"}
      },
      "required":["tool","args"],
      "additionalProperties": false
    }}
  }
}'

Two caveats worth knowing before you turn it on everywhere:

Pick a model that was trained for this

Tool-call reliability is a training property, not an emergent one. Two models of identical size can differ enormously, and the difference tracks whether the model was post-trained on agentic traces and diverse tool-call formats.

What to look for, in order:

1. An explicit agentic/coder tune. Devstral-2 22B is trained for agentic software engineering; the Qwen3-Coder line is trained for coding-agent use and tool calling. Both behave far better in a harness than a general chat model of the same parameter count. 2. A chat template with tool support. Check the GGUF actually ships one — ollama show --modelfile <tag> reveals the template. No tool tokens in the template means you are on the prompted-JSON path whether you meant to be or not. 3. Recency over size, within a tier. Tool-calling quality has improved much faster than raw reasoning across 2025–2026. A current 22B usually beats a two-year-old 34B inside an agent loop.

The per-tier picks and exact VRAM figures are in best local coding models. The short version for agents: 8B works with constraints and a small tool set; 22B is the first comfortable tier; 27–32B is where you stop thinking about it.

Models that behave in an agent loop

The tiers where tool calling is dependable enough to leave running. VRAM figures are Q4_K_M weights from the site’s compute engine — add KV cache for your context on top.

ModelVRAM (Q4)Runs onContextLicense
Qwen3-Coder 8B
Entry — constrain it — Usable in a loop with a small tool set and grammar constraints. Keep tasks single-file.
ollama pull qwen3-coder:8b
5.6 GB8 GB GPU (RTX 3060/4060)
Mac: 16 GB unified
125KApache-2.0
Devstral-2 22B
First comfortable tier — Explicitly agent-tuned for software engineering; follows edit formats and tool schemas reliably.
ollama pull devstral:22b
14.1 GB16 GB GPU (RTX 4060 Ti 16GB / 5060 Ti)
Mac: 24 GB unified
125KApache-2.0
Qwen 3.6 27B
Dependable autonomy — The tier where malformed calls stop being something you think about day to day.
ollama pull qwen3.6:27b
17.6 GB24 GB GPU (RTX 3090/4090)
Mac: 24 GB unified
256KApache-2.0
Qwen3-Coder 80B-A3B (MoE)
Workstation / 96 GB+ Mac — Fast MoE decode plus strong tool-call training — the local ceiling for long agent runs.
ollama pull qwen3-coder:80b-a3b-q4
49.1 GB2×48 GB GPUs / big unified memory
Mac: 96 GB unified
125KApache-2.0

Keep the tool surface small

Every tool you expose is described in every request, and every extra option is another chance for a weak model to choose wrong. Both costs are worse locally: tokens are scarce because the window is small, and selection accuracy is lower to begin with.

Repair strategies for when it still breaks

Even with constraints, occasional calls fail. Handle them in the harness rather than escalating to a human:

1. Parse-repair retry. On a parse failure, re-prompt with the raw output and the schema, asking only for corrected JSON. This usually succeeds first try and should *not* count against the loop's iteration cap — it is a formatting error, not a failed attempt. 2. Validate arguments, don't just parse. Valid JSON with a hallucinated path is worse than a parse error, because it executes. Check the path exists and is inside the allowed root, that enums are members, and that required fields are present — then return the error message to the model as a tool result. Models correct on specific feedback remarkably well. 3. Lower temperature for action turns. Structured output wants near-greedy decoding. 0.0–0.2 for tool-calling turns; save the higher temperature for prose. 4. Log every malformed call. A rate above roughly one in twenty is a configuration problem — wrong template, unconstrained decoding, too many tools — not bad luck. The log tells you which. 5. Cap the repair loop. Two failed repairs means stop and surface it. Three models arguing with a schema is a wasted evening.

Diagnosing the five common breakages

SymptomCauseFix
tool_calls always empty, model narrates its plan insteadChat template has no tool support, or the harness is on the prompted-JSON pathSwitch to an agentic/coder tune with tool tokens in its template; verify with ollama show --modelfile
JSON valid, argument names wrongSchema not enforced; the model is guessing from the descriptionConstrain with the JSON schema; make descriptions state field names explicitly
Call is truncated mid-JSONResponse token limit too low, or the window filled and the reply had no roomReserve reply space in your context budget; raise max tokens
Correct call, wrong file pathNot a tool-calling problem — the model lacks repo contextRepo map or a search step before the edit (context engineering)
Works alone, breaks after 20 turnsContext degradation, not formattingCompact earlier or restart the session

The pattern worth internalising: formatting failures are configuration bugs, and configuration bugs are cheap to fix. Only after the tool-call rate is clean does a model-tier upgrade tell you anything real.

Frequently asked questions

Why do local models produce broken tool calls?
Usually one of three configuration causes: the model has no native tool-call support in its chat template, so the harness is parsing free-form JSON out of prose; decoding is unconstrained, so nothing prevents invalid output; or too many tools are exposed and the model picks wrong. All three are fixable without changing model tier — constrained decoding plus an agent-tuned model removes most of the class.
What is grammar-constrained decoding?
A decoding technique that masks out any token which could not continue a valid output under a formal grammar, so malformed output is impossible rather than corrected afterwards. llama.cpp uses GBNF and converts JSON Schema to a grammar automatically; vLLM and SGLang have equivalents. It costs a few percent in throughput and removes the validate-retry cycle entirely.
Which local models are best at tool calling?
Models post-trained for agentic coding rather than general chat. Devstral-2 22B is explicitly trained for agentic software engineering and is the first comfortable tier; the Qwen3-Coder family is trained for coding-agent use; Qwen 3.6 27B is where malformed calls stop being a daily concern. Within a tier, prefer the more recent model — tool-calling quality has improved faster than raw reasoning.
How many tools should I give a local coding agent?
Six to eight is enough for coding: read, edit, list, search, run command, run tests. Every definition is tokens in every request and another chance to choose wrong, and both costs hit harder on a small window and a smaller model. Connected MCP servers count — several of them can consume more of a local window than the task itself.
Does constrained decoding hurt model quality?
It can, if you over-apply it. Forcing rigid structure at every token suppresses the free-form reasoning many models do before acting. The practical compromise is to let the model reason in text and constrain only the final action block, or to include an explicit reasoning field in the schema ahead of the tool field, so the structure permits thinking.
Should the harness retry a malformed tool call?
Yes — once or twice, re-prompting with the raw output and the schema and asking only for corrected JSON. Treat it as a formatting repair, not a failed attempt, so it does not consume the loop’s iteration budget. Also validate arguments rather than only parsing them: a well-formed call with a hallucinated path executes, which is worse than a parse error.

Keep going