Having an AI model is cool, but have you ever wondered what would happen if your local AI could run your script and use the output to solve a problem? Probably you didn’t, but if you are reading this article, that’s the main topic here.
Local AI is good for chatting, coding, and more, but it’s perfectly normal to want to extend your model with tools you created. Tool calling lets you do exactly that. You give your AI model access to tools that it can request when it needs to solve a problem.
Tools are external functions that we make available to our model. Yes, technically even a simple multiplication function can be considered a tool.
However, the AI doesn’t run those tools by itself, as we might imagine. Instead, it decides which tool should be used and what information should be sent to it. That process is called tool calling, sometimes also called function calling.
Tool
Imagine you wrote a weather function. It calls an API, gathers today’s weather data, and returns the result. You also have a local AI model with no internet access, and you want it to know today’s weather, so you connect your function to the application running your model. The function now becomes a tool available to your AI model.
To avoid confusion: the JSON you will see in this article is not a file stored on your computer. It is just structured data passed between your app and the model, and your Python function never has to read or write a JSON file.
Plugin
There’s one more thing worth knowing next to tools: plugins. A plugin is like a package that adds new features to your application, and it can include one or more tools.
Think about the weather example again. You wrote the function yourself and connected it to your app, but most people don’t want to write code for every tool they need. That’s where plugins come in.
Someone else can package that same weather function as a plugin. You install it in your app, and your model can use the weather tool without you having to build everything yourself.
| Tool | Plugin |
|---|---|
| One function the model can use | A package that adds one or more tools |
| You or someone else | Usually another developer |
get_weather | A weather plugin with get_weather inside |
A plugin can also add other things beyond tools, depending on the app, such as settings or interface changes.
The naming can get confusing because different apps use different words for similar things. Some call them plugins, while others call them tools or functions. In some applications, the word plugin is used as a broader term. For example, Open WebUI has a plugin system that includes both Tools and Functions, with each serving a different purpose.
How Ollama Connects Your Model to Tools
Local AI models and tools communicate through the application running the model. The first thing to understand is that the model never runs your function itself. It only decides when the function should be used.
The application first tells the model which tools are available. It sends a structured description of each tool along with the conversation. This description includes the tool’s name, what it does, and which arguments it requires.
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "Name of the city"
}
},
"required": ["city"]
}
}
}
With Ollama, your app sends your message and the available tool descriptions to the Ollama server running on your own computer. The communication between your app and the local model is usually handled through HTTP requests.
Ollama then gives both the conversation and the tool information to the model at the same time. This allows the model to decide whether it should answer normally or request one of the available tools.
If you say something like “What’s the weather in Hong Kong today?”, your model may decide to use the tool. It then returns a structured tool call:
{
"name": "get_weather",
"arguments": {
"city": "Hong Kong"
}
}
The real response contains a list of these calls under `tool_calls`, and the example above is heavily simplified.
Your application reads that request, runs the real `get_weather` function, and gets a result:
{
"city": "Hong Kong",
"temperature_c": 21,
"condition": "Sunny"
}
The app then sends the result back to the model. The model turns it into a normal reply, such as: “It’s 21°C and sunny in Hong Kong right now.”
Tool Calling With Ollama’s Python Library
Ollama’s Python library handles most of the process under the hood. If you already have a custom function, the library gives you everything you need to turn it into a tool and pass it to the model.
In this example the `llama3.1` model is used, but keep in mind that not every local AI model supports tool calling.
import ollama
def add(a: int, b: int):
return a + b
messages = [
{"role": "user", "content": "What is 17 plus 25?"}
]
response = ollama.chat(
model="llama3.1",
messages=messages,
tools=[add]
)
messages.append(response.message)
if response.message.tool_calls:
call = response.message.tool_calls[0]
result = add(**call.function.arguments)
messages.append({
"role": "tool",
"tool_name": "add",
"content": str(result)
})
response = ollama.chat(
model="llama3.1",
messages=messages
)
print(response.message.content)
Below the function, the code will look almost the same every time you add another tool. The custom function is the part you will usually change, depending on what you want the tool to do.
Declaring types in the function parameters is good practice because Ollama’s Python library uses them to build the tool schema that tells the model which arguments the function expects.
One small thing to keep in mind is that tool arguments may not always perfectly match the type you expect. For example, Ollama’s official example converts the values with int(a) and int(b) before adding them.
The `messages` list is the conversation history of your application. It contains the user’s input and, later, the responses that come from the model.
After we send `messages` to the model, we get a response. We then append `response.message` back into `messages` so the conversation history stays complete.
Next, we check `response.message.tool_calls` to see if the model requested one of the tools. If it did, we take the arguments from that tool call and use them to run the actual function.
The result of the function is then added to `messages`. Finally, we send the updated `messages` back to the model so it can read the tool result and turn it into a normal response for the user.
Adding More Tools
Adding another tool is simple. You create one more function next to your existing one, then add an `if` or `elif` statement to check the name of the function the model requested.
Based on the function name inside the tool call, your application decides which function should actually run.
import ollama
def add(a: int, b: int):
return a + b
def subtract(a: int, b: int):
return a - b
messages = [
{"role": "user", "content": "What is 50 minus 8?"}
]
response = ollama.chat(
model="llama3.1",
messages=messages,
tools=[add, subtract]
)
messages.append(response.message)
if response.message.tool_calls:
call = response.message.tool_calls[0]
if call.function.name == "add":
result = add(**call.function.arguments)
elif call.function.name == "subtract":
result = subtract(**call.function.arguments)
else:
result = f"Unknown tool: {call.function.name}"
messages.append({
"role": "tool",
"tool_name": call.function.name,
"content": str(result)
})
response = ollama.chat(
model="llama3.1",
messages=messages
)
print(response.message.content)
Handling Multiple Tool Calls
If your model decides to go berserk and tries to use two or three different tools at once, the script we created only handles the first one. To handle multiple tool calls, you need to loop through the tools the model requested and run each one.
import ollama
def add(a: int, b: int):
return a + b
def multiply(a: int, b: int):
return a * b
tools = {
"add": add,
"multiply": multiply
}
messages = [
{"role": "user", "content": "What is 3 plus 4, and what is 6 times 7?"}
]
response = ollama.chat(
model="llama3.1",
messages=messages,
tools=[add, multiply]
)
messages.append(response.message)
if response.message.tool_calls:
for call in response.message.tool_calls:
function = tools[call.function.name]
result = function(**call.function.arguments)
messages.append({
"role": "tool",
"tool_name": call.function.name,
"content": str(result)
})
response = ollama.chat(
model="llama3.1",
messages=messages
)
print(response.message.content)
It’s pretty simple. `tool_calls` is iterable, so you can loop through it and run each function the model selected from your available tools. It’s the same as looping through names in a list and checking which one matches the function name.
Handling Tool Errors
All the scripts above assume everything goes perfectly, but as we know, there’s always a chance something fails, so handling errors is essential.
The most basic way is to send the error back as the tool result when the tool fails. This way, the model can see what went wrong and react to it.
import ollama
def add(a: int, b: int):
return a + b
tools = {
"add": add
}
messages = [
{"role": "user", "content": "What is 17 plus 25?"}
]
response = ollama.chat(
model="llama3.1",
messages=messages,
tools=[add]
)
messages.append(response.message)
if response.message.tool_calls:
for call in response.message.tool_calls:
try:
function = tools[call.function.name]
result = function(**call.function.arguments)
except Exception as error:
result = f"Tool error: {error}"
messages.append({
"role": "tool",
"tool_name": call.function.name,
"content": str(result)
})
response = ollama.chat(
model="llama3.1",
messages=messages
)
print(response.message.content)
There are many ways to catch errors, and you can handle specific errors differently depending on what went wrong. In a larger application, you might even build a separate error-handling system for different types of tool failures.
For now, it’s enough to know that handling tool errors works pretty much the same way as handling errors in any other script.
Conclusion
Tools are a great way to extend the capabilities of your models, and they are easy to implement. There are many available plugins, so coding may not even be required. If it is, AI can help write a custom function in under 10 minutes.
The flow is simple. The article touched on some under-the-hood details for those who wonder, but it all comes down to four steps: create the tool, add more tools, handle multiple tool calls, and handle tool errors. That’s all you need to start calling tools.
