Ollama supports tool use for models that are trained for it. This allows the model to request function calls that your code executes.
Overview
The tool use workflow:
- Define tools with JSON Schema
- Send chat with tool definitions
- Model returns tool_calls (if needed)
- Execute the functions
- Send results back to model
- Model generates final response
Defining Tools
Tools use JSON Schema format:
weather_tool = %{
type: "function",
function: %{
name: "get_weather",
description: "Get current weather for a location",
parameters: %{
type: "object",
properties: %{
location: %{
type: "string",
description: "City name, e.g., 'San Francisco'"
},
unit: %{
type: "string",
enum: ["celsius", "fahrenheit"],
description: "Temperature unit"
}
},
required: ["location"]
}
}
}Tool Helpers
You can define tools programmatically:
tool =
Ollixir.Tool.define(:get_weather,
description: "Get current weather for a location",
parameters: [
location: [type: :string, required: true, description: "City name"],
unit: [type: :string, enum: ["celsius", "fahrenheit"]]
]
)Or pass functions directly and let Ollama convert them:
defmodule WeatherTools do
@doc "Get weather for a city."
@spec get_weather(String.t(), String.t()) :: String.t()
def get_weather(city, unit), do: "#{city} in #{unit}: 72 degrees"
end
{:ok, response} = Ollixir.chat(client,
model: "llama3.2",
messages: [%{role: "user", content: "Weather in Tokyo?"}],
tools: [&WeatherTools.get_weather/2]
)Ollama also ships predefined tool definitions for web search and fetch:
tools = Ollixir.Web.Tools.all()Best Practices for Tool Definitions
- Clear descriptions - Help the model understand when to use the tool
- Specific parameter descriptions - Include examples and constraints
- Required fields - Mark truly required parameters
- Enum values - Use enums for constrained choices
Basic Workflow
defmodule WeatherAgent do
def run(question) do
client = Ollixir.init()
tools = [weather_tool()]
# Step 1: Initial request
{:ok, response} = Ollixir.chat(client,
model: "llama3.2",
messages: [%{role: "user", content: question}],
tools: tools
)
# Step 2: Check for tool calls
case get_in(response, ["message", "tool_calls"]) do
nil ->
# No tool call, return direct response
response["message"]["content"]
tool_calls ->
# Step 3: Execute tools
results = execute_tools(tool_calls)
# Step 4: Send results back
{:ok, final} = Ollixir.chat(client,
model: "llama3.2",
messages: [
%{role: "user", content: question},
%{role: "assistant", content: "", tool_calls: tool_calls},
%{role: "tool", content: results}
],
tools: tools
)
final["message"]["content"]
end
end
defp execute_tools(tool_calls) do
# Execute each tool and collect results
tool_calls
|> Enum.map(&execute_tool/1)
|> Enum.join("\n")
end
defp execute_tool(%{"function" => %{"name" => "get_weather", "arguments" => args}}) do
# Your actual weather API call here
location = args["location"]
"Weather in #{location}: 72°F, sunny"
end
endMulti-Turn Tool Use
For complex tasks, the model may need multiple tool calls:
defmodule Agent do
@max_iterations 5
def run(prompt, tools) do
client = Ollixir.init()
messages = [%{role: "user", content: prompt}]
loop(client, messages, tools, 0)
end
defp loop(_client, messages, _tools, @max_iterations) do
{:error, :max_iterations}
end
defp loop(client, messages, tools, iteration) do
{:ok, response} = Ollixir.chat(client,
model: "llama3.2",
messages: messages,
tools: tools
)
case get_in(response, ["message", "tool_calls"]) do
nil ->
{:ok, response["message"]["content"]}
tool_calls ->
results = execute_all(tool_calls)
messages = messages ++
[%{role: "assistant", content: "", tool_calls: tool_calls}] ++
Enum.map(results, &%{role: "tool", content: &1})
loop(client, messages, tools, iteration + 1)
end
end
endWeb Tools Integration
Use predefined web tools for search and fetch.
For an MCP (Model Context Protocol) stdio server example, see
examples/mcp/mcp_server.exs. This exposes web_search and web_fetch to
MCP clients like Cursor, Cline, and Open WebUI.
# Get all web tool definitions
tools = Ollixir.Web.Tools.all()
{:ok, response} = Ollixir.chat(client,
model: "llama3.2",
messages: [%{role: "user", content: "Search for Elixir news"}],
tools: tools
)
# Execute web tool calls
case get_in(response, ["message", "tool_calls"]) do
[%{"function" => %{"name" => "web_search", "arguments" => args}}] ->
{:ok, results} = Ollixir.web_search(client, query: args["query"])
# Continue with results...
[%{"function" => %{"name" => "web_fetch", "arguments" => args}}] ->
{:ok, page} = Ollixir.web_fetch(client, url: args["url"])
# Continue with page content...
_ ->
response["message"]["content"]
endThinking with Tools
Combine tool use with thinking mode (supported models only):
{:ok, response} = Ollixir.chat(client,
model: "gpt-oss:20b-cloud",
messages: [%{role: "user", content: "Calculate the weather impact on crops"}],
tools: [weather_tool, crop_tool],
think: true
)
# Access thinking process
IO.puts("Thinking: #{response["message"]["thinking"]}")
# Handle tool calls as usual
tool_calls = get_in(response, ["message", "tool_calls"])Tool Argument Handling
Tool arguments may arrive as a map or JSON string. Handle both:
defp parse_arguments(args) when is_map(args), do: args
defp parse_arguments(args) when is_binary(args) do
case Jason.decode(args) do
{:ok, parsed} -> parsed
{:error, _} -> %{}
end
end
defp execute_tool(%{"function" => %{"name" => name, "arguments" => args}}) do
parsed_args = parse_arguments(args)
# Use parsed_args...
endError Handling
Handle tool execution failures gracefully:
defp execute_tool_safely(tool_call) do
try do
result = execute_tool(tool_call)
Jason.encode!(%{success: true, result: result})
rescue
e ->
Jason.encode!(%{success: false, error: Exception.message(e)})
end
end
# In the agent loop, send error back to model
{:ok, response} = Ollixir.chat(client,
model: "llama3.2",
messages: messages ++ [
%{role: "assistant", content: "", tool_calls: tool_calls},
%{role: "tool", content: ~s({"success": false, "error": "API unavailable"})}
],
tools: tools
)Limitations
- No streaming with tools -
stream: trueis not supported when using tools - Model dependent - Not all models support tool use
- Tool arguments - Arguments may arrive as a JSON string or a map
- Non-deterministic - Tool calls may vary between runs
Compatible Models
| Model | Tool Support | Thinking + Tools |
|---|---|---|
| llama3.2 | Yes | No |
| mistral | Yes | No |
| mixtral | Yes | No |
| command-r | Yes | No |
| qwen2.5 | Yes | No |
| qwen3 | Yes | Yes |
| gpt-oss:20b-cloud | Yes | Yes |
| gpt-oss:120b-cloud | Yes | Yes |
See Also
- Cloud API Guide - Web search and fetch details
- Thinking Guide - Thinking mode with tools
- Examples - Working tool examples