How Tool Calling Works
- You declare tools. Each tool is a name, a description, and a JSON Schema for its arguments.
- The model decides. When a tool would help, the model returns a
tool_callsmessage instead of a plain answer — e.g.search_kb(query="leave policy", limit=3). - You execute. Your code dispatches to the real function, with validation.
- You feed back the result. The tool output is appended as a
toolmessage, and the model answers (or calls again).
The key mental model: the model proposes; your code disposes. Everything that actually happens — file writes, API calls, DB queries — runs through code you control. That's the safety property that makes local tool calling safe.
Which Local Models Support It
| Model | Tool calling | Notes |
|---|---|---|
| Qwen3-8B / Qwen3.5-9B | Excellent | The 2026 default — multi-call, parallel calls, reliable JSON |
| GLM-4-9B | Very good | Strong schema adherence, good on CPU |
| Gemma 4 12B | Very good | Multimodal + tools; needs ~10GB VRAM |
| Llama 3.1/3.2 8B | Good | Reliable single calls; parallel calls weaker |
| Qwen3.5-4B | Decent | Tiny-hardware option; keep tool count low |
| Phi-4-mini | Weak | Great at math/code, not tool orchestration |
Full rankings with downloads: sub-12B by power and sub-12B for coding.
The Minimal Ollama Tool Loop
import json, requests
# 1. Declare tools (JSON Schema)
tools = [{
"type": "function",
"function": {
"name": "search_kb",
"description": "Search the internal knowledge base for documents about a topic.",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Search phrase"},
"limit": {"type": "integer", "minimum": 1, "maximum": 10}
},
"required": ["query"]
}
}
}]
# 2. The real function behind the tool
def search_kb(query: str, limit: int = 3) -> str:
# In production: query ChromaDB (see the 30-min RAG guide)
return f"[KB] Top {limit} results for '{query}'"
messages = [{"role": "user", "content": "What's our leave policy?"}]
for _ in range(5): # iteration guard
r = requests.post("http://localhost:11434/api/chat", json={
"model": "qwen3:8b",
"messages": messages,
"tools": tools,
})
msg = r.json()["message"]
messages.append({"role": "assistant", "content": msg.get("content") or ""})
if msg.get("tool_calls"):
for call in msg["tool_calls"]:
fn = call["function"]
if fn["name"] == "search_kb":
result = search_kb(**fn["arguments"])
messages.append({"role": "tool",
"name": fn["name"],
"content": result})
continue
print(msg["content"]) # final grounded answer
break
That's the entire loop: declare tools → send → execute → feed back → repeat. requests and the Ollama API — no framework required. For graph control (loops, interrupts, persistence), layer LangGraph on top — see Build an AI Agent with LangGraph + Ollama.
Failure Modes and Fixes
| Symptom | Cause | Fix |
|---|---|---|
| Model never calls tools | Descriptions too vague; task doesn't need tools | Write descriptions as triggers ("Call when the user asks about...") |
| Wrong arguments (string for number) | Schema unclear; context leak | Add examples to descriptions; enforce types in code |
| Calls wrong tool among many | Tool count too high | Keep ≤10 tools; merge related ones; use a router tool |
| Loops forever | Tool result doesn't satisfy the task | Iteration guard (see for _ in range(5)); improve tool output quality |
| Malformed JSON | Model without native support | Switch model or use prompt-based JSON protocol |
The Safety Boundary
- Validate arguments against the schema again in code — the model's JSON is a suggestion, not a contract.
- Whitelist operations — a "run_shell" tool should reject anything not on the allowlist.
- Require confirmation for destructive or expensive tools (email, writes, payments).
- Log every call — tool logs are your audit trail (GDPR-relevant and AI-Act-relevant).
- Local means contained — with a local model and local tools, no tool call ever touches the network. See the offline workspace guide.
Where Tool Calling Takes You
- Agents: the loop above, with a stop condition — LangGraph + Ollama agent tutorial.
- MCP: expose your tools as MCP servers and every client can use them.
- RAG: make retrieval a tool so the model decides when to search — agentic RAG.
- Multi-agent: route between specialist agents — when one agent isn't enough.
🚀 In production
Lawyer Assistant uses exactly this pattern: the local model calls a retrieval tool over a ChromaDB index of legal documents, and every answer is grounded in what the tool returned. No cloud, no prompt leakage — just a model, a tool, and a loop.
Frequently Asked Questions (FAQ)
What is tool calling (function calling)?
Tool calling is when an LLM, instead of answering directly, emits a structured request to call a function you defined — with arguments in JSON. Your code runs the function and returns the result, and the model continues from there. It's the mechanism behind agents, RAG lookups, and API integrations.
Which local models support tool calling well?
The reliable 2026 picks: Qwen3 and Qwen3.5 (excellent), GLM-4-9B, Gemma 4, and Llama 3.1+ derivatives. Check each model card for tool-calling support — some small models only handle single-call, not multi-call parallel invocations.
How do I implement tool calling with Ollama?
Ollama's API accepts a tools array of JSON schemas alongside the model name. The response contains a message with tool_calls, which your code executes and appends as tool messages before calling again. The example in this post is a complete minimal loop.
Why does my local model call the wrong tool or wrong arguments?
Usually one of: tool descriptions too vague (the model doesn't know when to use them), too many tools in one call (models degrade past ~10), arguments colliding with context, or a model without proper function-calling fine-tuning. Fix the schema and descriptions before blaming the model.
Do I need a special model for tool calling?
Native tool calling needs a model trained for it — a JSON output in a specific format. Models without it can be coerced with prompt-based protocols (asking for JSON output), which is more fragile. Prefer natively supported models; they're common in 2026.
Is tool calling safe with local models?
The model is a request generator — your code decides what actually runs. That's the safety boundary: validate arguments, whitelist operations, require confirmation for destructive tools, and log every call. Local execution also means no data leaves the machine for any tool call.
Sources & Further Reading
- How to Build an AI Agent with LangGraph + Ollama
- MCP Explained: Model Context Protocol for Local AI
- Agentic RAG: Combining Agents with Retrieval
- How to Build a RAG System in 30 Minutes (Local, Free)
- Top 10 AI Models Under 12B Parameters
- Lawyer Assistant: privacy-first legal RAG
- Ollama OpenAI-compatible API docs (tools)