Reference Component Agents

Ollama Python Tool Calling

A minimal local tool-calling loop for Ollama: register Python functions, pass schemas to the model, execute selected calls, and feed tool results back into chat.

Problem Local LLM applications need a controlled way to call real Python functions instead of relying on text-only responses.
Outcome A small function registry and execution loop for Ollama tool calls.
Implementation evidence

The solution is backed by inspectable code

This solves the local tool-use loop: define trusted Python functions, expose them to Ollama, run only registered calls, and return the function output to the model.

Code

from ollama import chat


def add_two_numbers(a: int, b: int) -> int:
    """Add two numbers."""
    return int(a) + int(b)


def subtract_two_numbers(a: int, b: int) -> int:
    """Subtract two numbers."""
    return int(a) - int(b)


AVAILABLE_FUNCTIONS = {
    "add_two_numbers": add_two_numbers,
    "subtract_two_numbers": subtract_two_numbers,
}


def run_tool_chat(user_message, model="llama3.2"):
    messages = [{"role": "user", "content": user_message}]

    response = chat(
        model,
        messages=messages,
        tools=[add_two_numbers, subtract_two_numbers],
    )

    if not response.message.tool_calls:
        return response.message.content

    messages.append(response.message)

    for tool_call in response.message.tool_calls:
        function_to_call = AVAILABLE_FUNCTIONS.get(tool_call.function.name)
        if function_to_call is None:
            messages.append({
                "role": "tool",
                "name": tool_call.function.name,
                "content": f"Function {tool_call.function.name} is not registered.",
            })
            continue

        output = function_to_call(**tool_call.function.arguments)
        messages.append({
            "role": "tool",
            "name": tool_call.function.name,
            "content": str(output),
        })

    final_response = chat(model, messages=messages)
    return final_response.message.content

Usage

print(run_tool_chat("What is 30 plus 12?"))

For HTTP-facing tools, keep a registry rather than dispatching arbitrary function names:

FUNCTIONS = {}


def register_function(func):
    FUNCTIONS[func.__name__] = func
    return func


@register_function
def square(x: int) -> int:
    return int(x) * int(x)


def call_registered_function(function_name, arguments):
    if function_name not in FUNCTIONS:
        return {"error": f"Function {function_name!r} not found"}, 404
    result = FUNCTIONS[function_name](*arguments)
    return {"result": result}, 200

Requirements

Install ollama and run a local model that supports tool calls, for example ollama pull llama3.2.

Source

The article points to working examples in ernanhughes/ollama-functions.

Full explanation

For the Flask version, external API examples, and security considerations, read: Beyond Text Generation: Coding Ollama Function Calls and Tools.

The publishing loop Research → book → capstone → solution → real use → new evidence
Browse all solutions →