You set up OpenClaw with Ollama, tested it, and it seemed to work. Then you asked it to do something multi-step — search a file, then summarize the result, then write it somewhere — and things got weird. The agent described what it would do instead of doing it. Or it called the right tool with obviously wrong parameters. Or it just stopped mid-chain.
This is a known bug in OpenClaw's Ollama native provider (GitHub issue #57103): tool call arguments are incorrectly stringified as a string instead of passed as a JSON object. In single-turn interactions it often goes unnoticed. In multi-turn tool chains, it compounds into silent failures and corrupted parameters.
Affected: Any setup using Ollama as the provider via the native Ollama API (api: "ollama-completions" or similar). The OpenAI-compatible endpoint workaround (below) does not have this bug.
How to Tell If This Is Your Problem
- 🔴Agent says "I'll now use the search tool…" and then either does nothing or the tool call fails silently
- 🔴Tool calls in turn 1 work fine; tool calls in turn 3+ start failing or receiving garbage parameters
- 🔴You see stringified JSON in tool arguments — e.g.,
"{\"path\":\"/home/user/file.txt\"}"instead of{"path": "/home/user/file.txt"} - 🟡Agent completes simple one-tool tasks but fails on anything that requires chaining two or more tools
- 🟡Verbose logs show the tool being "called" but with parameters that don't match what you asked for
You can confirm by checking your agent logs. Run OpenClaw with verbose output:
openclaw gateway start --verbose 2>&1 | grep -i "tool"
If you see tool arguments wrapped in extra string quotes ("{...}" instead of {...}), you've confirmed the bug.
What's Actually Happening
When Ollama returns a tool call from a local model, it sends back the arguments as a JSON object. OpenClaw's Ollama native provider then takes those arguments and — due to a serialization bug — wraps them in an extra JSON.stringify() call. The result:
Expected (correct)
{
"name": "read_file",
"arguments": {
"path": "/workspace/notes.md"
}
}
What Ollama provider sends
{
"name": "read_file",
"arguments": "{\"path\":\"/workspace/notes.md\"}"
}
OpenClaw's tool dispatcher then tries to parse the arguments but receives a string instead of an object. In turn 1, the model often recovers. By turn 2 or 3, the conversation history contains malformed tool results, and the model starts hallucinating what tools returned or stops calling them entirely.
The Fix — Switch to OpenAI-Compatible Endpoint
Ollama exposes an OpenAI-compatible API at http://localhost:11434/v1. This endpoint does not have the stringification bug because it uses a different serialization path. Switching to it fixes multi-turn tool calling completely.
In your ~/.openclaw/openclaw.json, change your Ollama provider config:
# Before (Ollama native — has the bug)
"providers": {
"ollama": {
"baseUrl": "http://127.0.0.1:11434",
"api": "ollama-completions"
}
}
# After (OpenAI-compatible — no bug)
"providers": {
"ollama": {
"baseUrl": "http://127.0.0.1:11434/v1",
"apiKey": "ollama",
"api": "openai-completions"
}
}
The key changes:
- Add
/v1to the base URL - Set
"api": "openai-completions"instead of"ollama-completions" - Add
"apiKey": "ollama"(required by the OpenAI-compatible endpoint — the value doesn't matter, just needs to be present)
Then restart:
openclaw gateway restart
This works with all Ollama models — Qwen, Llama, Gemma, Mistral, etc. The OpenAI-compatible endpoint supports the same models, just through a different API path that handles tool calling correctly.
If You Want to Keep Using the Native Provider
The native provider has one small advantage: it supports some Ollama-specific parameters (like num_ctx for context window size) that the OpenAI-compatible endpoint doesn't always honor. If that matters to you, there's a partial workaround:
Set injectNumCtxForOpenAICompat: true in your provider config. OpenClaw will inject the context window size as a custom parameter even through the OpenAI-compatible endpoint:
"providers": {
"ollama": {
"baseUrl": "http://127.0.0.1:11434/v1",
"apiKey": "ollama",
"api": "openai-completions",
"injectNumCtxForOpenAICompat": true,
"models": [
{
"id": "qwen3.5:9b",
"contextWindow": 32768
}
]
}
}
The native provider bug is tracked at GitHub issue #57103. A fix is in review. Once it lands (likely in a future 4.x release), you can switch back to the native provider if you prefer — but the OpenAI-compatible path works great and there's no real reason to switch back.
Which Local Models Actually Work Well for Tool Calling?
Even with the provider bug fixed, model capability matters. Not all local models handle tool calling reliably:
- Qwen 2.5 7B / Qwen 3.5 9B — Best tool calling in the sub-10B range. Reliable for single and multi-turn. Good choice for most OpenClaw setups.
- Llama 3.1 8B — Decent tool calling, occasional hallucinations on complex parameter schemas.
- Gemma 4 27B (MoE) — Excellent (85%+ tool accuracy) but needs more RAM. Overkill for simple setups.
- Mistral 7B — Unreliable tool calling. Works for simple cases, breaks on complex schemas or multi-turn chains.
- Models under 4B parameters — Generally not recommended for tool-heavy OpenClaw use. They'll describe using tools but often fail to format the calls correctly.
For a NUC or mini-PC with 14–16GB RAM, Qwen 3.5 9B via the OpenAI-compatible endpoint is the sweet spot: free, fast enough, and reliable tool calling.