Ollama Tools/Function Calling in Racket
One of the most powerful features of modern LLMs is their ability to call external functions (tools) during a conversation. This allows the model to perform actions beyond just generating text — it can fetch live data, interact with files, call APIs, and more.
Ollama supports tool/function calling through its chat API. When you provide a list of available tools with their schemas, the model can decide to call one or more tools, and your code executes them and returns the results back to the model.
The examples for this chapter are in the directory Racket-AI-book/source-code/ollama_tools.
How Tool Calling Works
The flow is:
- You define tools — functions with JSON schemas describing their parameters
- Send request to Ollama — include the tool definitions and user prompt
- Model decides — if it needs a tool, it returns a
tool_callsarray - You execute the tool — call your Racket function with the arguments
- Return result — add the tool result to the message history
- Model responds — uses the tool output to generate its final answer
This creates a conversation loop where the LLM can request information it doesn’t have intrinsically from its training data.
A Racket Tools Library
The following code defines a reusable library for Ollama tool calling. It provides:
- A tool registry to register functions with their schemas
- Built-in tools for common operations (weather, files, Wikipedia)
- API communication to call Ollama and handle tool responses
This example demonstrates how to bridge the gap between Large Language Models and local system capabilities by implementing a tool-calling framework in Racket. The code provides a structured way to register Racket functions as “tools” that Ollama-hosted models can invoke to perform real-world tasks such as fetching live weather data, searching Wikipedia, or interacting with the local file system. By defining a clear registry system and using JSON schema for parameter validation, the module automates the complex loop of sending prompts to the LLM, parsing its request for a function call, executing the corresponding Racket code, and returning the results back to the model for a final synthesis. This pattern is essential for building “agentic” applications where the AI is not just a chatbot, but a functional interface capable of executing logic and retrieving dynamic data.
The following file tools.rkt contains both the library code for creating and using tools and also example tool implementations:
1 #lang racket
2
3 ;;; Copyright (C) 2026 Mark Watson <markw@markwatson.com>
4 ;;; Apache 2 License
5 ;;;
6 ;;; Ollama Tools/Function Calling Example for Racket
7 ;;;
8 ;;; This module demonstrates how to use Ollama's tool/function calling
9 ;;; capability from Racket. It defines tools (functions) that the LLM
10 ;;; can call, registers them, and handles the tool call flow.
11
12 (require net/http-easy)
13 (require json)
14 (require racket/date)
15 (require net/uri-codec)
16
17 (provide register-tool
18 get-tool
19 call-ollama-with-tools
20 get-current-datetime
21 get-weather
22 list-directory
23 read-file-contents
24 *available-tools*
25 *ollama-host*
26 *default-model*)
27
28 ;;; -----------------------------------------------------------------------------
29 ;;; Configuration
30
31 (define *default-model* (make-parameter (or (getenv "OLLAMA_MODEL") "qwen3:1.7b")))
32 (define *ollama-host* (make-parameter (or (getenv "OLLAMA_HOST") "http://localhost:11434")))
33
34 ;;; -----------------------------------------------------------------------------
35 ;;; Tool Registry
36
37 (define *available-tools* (make-hash))
38
39 (define (register-tool name description parameters handler)
40 "Register a tool that can be called by the LLM.
41 NAME: string - the tool name
42 DESCRIPTION: string - what the tool does
43 PARAMETERS: hash - JSON schema for parameters
44 HANDLER: function - Racket function to execute the tool"
45 (hash-set! *available-tools* name
46 (hash 'name name
47 'description description
48 'parameters parameters
49 'handler handler)))
50
51 (define (get-tool name)
52 "Get a registered tool by name."
53 (hash-ref *available-tools* name #f))
54
55 ;;; -----------------------------------------------------------------------------
56 ;;; Tool Implementations
57
58 (define (get-current-datetime args)
59 "Returns the current date and time as a string."
60 (date->string (current-date) "~Y-~m-~d ~H:~M:~S"))
61
62 (define (get-weather args)
63 "Fetches current weather for a location using wttr.in.
64 ARGS should contain 'location' key."
65 (let ([location (hash-ref args 'location "unknown")])
66 (with-handlers ([exn:fail? (lambda (e)
67 (format "Error fetching weather: ~a" (exn-message e)))])
68 (let* ([url (format "https://wttr.in/~a?format=3"
69 (string-replace location " " "+"))]
70 [response (get url)]
71 [body (response-body response)])
72 (string-trim (bytes->string/utf-8 body))))))
73
74 (define (list-directory args)
75 "Lists files in the current directory or specified directory.
76 ARGS: optional 'dir_path'"
77 (let* ([dir-path (hash-ref args 'dir_path (current-directory))]
78 [resolved-dir (simplify-path (path->complete-path dir-path))]
79 [resolved-sandbox (simplify-path (path->complete-path (current-directory)))])
80 (if (string-prefix? (path->string resolved-sandbox) (path->string resolved-dir))
81 (if (directory-exists? resolved-dir)
82 (let ([files (directory-list resolved-dir)])
83 (format "Files in ~a: ~a"
84 resolved-dir
85 (string-join (map path->string files) ", ")))
86 (format "Directory not found: ~a" dir-path))
87 (format "Access denied: ~a is outside the sandbox directory" dir-path))))
88
89 (define (read-file-contents args)
90 "Reads contents of a file.
91 ARGS should contain 'file_path' key."
92 (let* ([file-path (hash-ref args 'file_path #f)]
93 [resolved-path (and file-path (simplify-path (path->complete-path file-path)))]
94 [resolved-sandbox (simplify-path (path->complete-path (current-directory)))])
95 (if (and resolved-path (string-prefix? (path->string resolved-sandbox) (path->string resolved-path)))
96 (if (file-exists? resolved-path)
97 (with-handlers ([exn:fail? (lambda (e)
98 (format "Error reading file: ~a" (exn-message e)))])
99 (file->string resolved-path))
100 (format "File not found: ~a" file-path))
101 (format "Access denied: file path is invalid or outside the sandbox directory"))))
102
103 (define (search-wikipedia args)
104 "Searches Wikipedia for a query and returns summary.
105 ARGS should contain 'query' key."
106 (let ([query (hash-ref args 'query #f)])
107 (if query
108 (with-handlers ([exn:fail? (lambda (e)
109 (format "Error searching Wikipedia: ~a" (exn-message e)))])
110 (let* ([url (format "https://en.wikipedia.org/api/rest_v1/page/summary/~a"
111 (uri-encode (string-replace query " " "_")))]
112 [response (get url
113 #:headers (hash 'user-agent "RacketOllamaTools/1.0"))]
114 [data (response-json response)])
115 (hash-ref data 'extract "No summary available")))
116 "No query provided")))
117
118 ;;; -----------------------------------------------------------------------------
119 ;;; Register Default Tools
120
121 (register-tool
122 "get_current_datetime"
123 "Get the current date and time"
124 (hash 'type "object"
125 'properties (hash)
126 'required '())
127 get-current-datetime)
128
129 (register-tool
130 "get_weather"
131 "Get the current weather for a location"
132 (hash 'type "object"
133 'properties (hash 'location (hash 'type "string"
134 'description "City name, e.g., 'London' or 'New York'"))
135 'required '("location"))
136 get-weather)
137
138 (register-tool
139 "list_directory"
140 "List files in the current directory"
141 (hash 'type "object"
142 'properties (hash)
143 'required '())
144 list-directory)
145
146 (register-tool
147 "read_file_contents"
148 "Read the contents of a file"
149 (hash 'type "object"
150 'properties (hash 'file_path (hash 'type "string"
151 'description "Path to the file to read"))
152 'required '("file_path"))
153 read-file-contents)
154
155 (register-tool
156 "search_wikipedia"
157 "Search Wikipedia and return a summary"
158 (hash 'type "object"
159 'properties (hash 'query (hash 'type "string"
160 'description "Search query"))
161 'required '("query"))
162 search-wikipedia)
163
164 ;;; -----------------------------------------------------------------------------
165 ;;; Ollama API Communication
166
167 (define (make-tool-schemas tool-names)
168 "Build tool schemas for the Ollama API request."
169 (for/list ([name tool-names])
170 (let ([tool (get-tool name)])
171 (if tool
172 (hash 'type "function"
173 'function (hash 'name (hash-ref tool 'name)
174 'description (hash-ref tool 'description)
175 'parameters (hash-ref tool 'parameters)))
176 (error (format "Unknown tool: ~a" name))))))
177
178 (define (call-ollama-api messages tools)
179 "Call the Ollama chat API with tools.
180 MESSAGES: list of message hashes with 'role and 'content
181 TOOLS: list of tool schemas"
182 (let* ([data (hash 'model (*default-model*)
183 'messages messages
184 'tools tools
185 'stream #f)]
186 [json-data (jsexpr->string data)]
187 [response (post (string-append (*ollama-host*) "/api/chat")
188 #:data json-data
189 #:headers (hash 'content-type "application/json"))]
190 [result (response-json response)])
191 result))
192
193 (define (handle-tool-call tool-call)
194 "Execute a tool call from the LLM response."
195 (with-handlers ([exn:fail? (lambda (e)
196 (hash 'role "tool"
197 'content (format "Error processing tool call: ~a" (exn-message e))))])
198 (let* ([name (hash-ref tool-call 'function (hash))]
199 [func-name (hash-ref name 'name #f)]
200 [args-str (hash-ref name 'arguments "{}")]
201 [args (cond
202 [(hash? args-str) args-str]
203 [(string? args-str) (string->jsexpr args-str)]
204 [else (hash)])]
205 [tool (get-tool func-name)])
206 (if tool
207 (let ([handler (hash-ref tool 'handler #f)])
208 (if handler
209 (let ([result (handler args)])
210 (hash 'role "tool"
211 'content result))
212 (hash 'role "tool"
213 'content (format "No handler for tool: ~a" func-name))))
214 (hash 'role "tool"
215 'content (format "Unknown tool: ~a" func-name))))))
216
217 (define (call-ollama-with-tools prompt tool-names #:model [model (*default-model*)])
218 "Call Ollama with tools and handle the tool calling loop.
219 PROMPT: the user's prompt
220 TOOL-NAMES: list of tool names to make available
221 MODEL: optional model override
222
223 Returns the final response text after any tool calls are processed."
224 (parameterize ([*default-model* model])
225 (let* ([tools (make-tool-schemas tool-names)]
226 [messages (list (hash 'role "user" 'content prompt))])
227 (let loop ([msgs messages]
228 [max-iterations 10])
229 (if (<= max-iterations 0)
230 "Max iterations reached"
231 (let* ([response (call-ollama-api msgs tools)]
232 [message (hash-ref response 'message (hash))]
233 [tool-calls (hash-ref message 'tool_calls #f)])
234 (if tool-calls
235 ;; Process tool calls and continue
236 (let* ([tool-results (for/list ([tc tool-calls])
237 (handle-tool-call tc))]
238 [assistant-msg (hash 'role "assistant"
239 'content (hash-ref message 'content #f)
240 'tool_calls tool-calls)]
241 [new-msgs (append msgs (list assistant-msg)
242 tool-results)])
243 (loop new-msgs (- max-iterations 1)))
244 ;; No tool calls, return the content
245 (hash-ref message 'content "No response"))))))))
246
247 ;;; -----------------------------------------------------------------------------
248 ;;; Example Usage (commented out for library use)
249
250 #|
251 (require "tools.rkt")
252
253 ;; Example 1: Get current date/time
254 (displayln (call-ollama-with-tools
255 "What is the current date and time?"
256 '("get_current_datetime")))
257
258 ;; Example 2: Get weather
259 (displayln (call-ollama-with-tools
260 "What is the weather in Phoenix Arizona?"
261 '("get_weather")))
262
263 ;; Example 3: Multiple tools available
264 (displayln (call-ollama-with-tools
265 "Tell me about the Eiffel Tower"
266 '("get_weather" "search_wikipedia" "get_current_datetime")))
267
268 ;; Example 4: List files
269 (displayln (call-ollama-with-tools
270 "What files are in the current directory?"
271 '("list_directory")))
272 |#
This tool use implementation relies on a central registry, available-tools which stores tool metadata and their associated handler functions. When a user sends a prompt, the call-ollama-with-tools function packages the available tool definitions into the format expected by the Ollama API. The model then decides whether to answer the query directly or request a tool execution. If the model provides a tool_calls object, the Racket handler dynamically dispatches the request to the local function, processes the output, and feeds it back into the conversation history.
A key technical highlight is the use of the net/http-easy and json libraries to manage the RESTful communication with the Ollama service. The recursive loop within call-ollama-with-tools ensures that the system can handle multi-step reasoning where a model might need to call one tool to get a piece of information before calling another to complete the task. This robust structure allows developers to expand the LLM’s capabilities indefinitely by simply registering new Racket functions to the registry.
Complete Example Using the Tools Library and Example Tools
Here we use the example tool that we previously saw implemented in the file tools.rkt.
The file main.rkt in the ollama_tools directory provides an interactive menu for testing the tools:
1 #lang racket
2
3 ;;; Copyright (C) 2026 Mark Watson <markw@markwatson.com>
4 ;;; Apache 2 License
5 ;;;
6 ;;; Ollama Tools Example - Interactive Demo
7 ;;;
8 ;;; Run with: racket main.rkt
9
10 (require "tools.rkt")
11
12 (define (display-menu)
13 (displayln "\n=== Ollama Tools Demo ===")
14 (displayln "1. Get current date and time")
15 (displayln "2. Get weather for a location")
16 (displayln "3. List files in current directory")
17 (displayln "4. Read a file")
18 (displayln "5. Search Wikipedia")
19 (displayln "6. Custom prompt (all tools available)")
20 (displayln "7. Exit")
21 (display "Select option: "))
22
23 (define (run-demo)
24 (displayln (format "Using model: ~a" (*default-model*)))
25 (displayln (format "Ollama host: ~a" (*ollama-host*)))
26 (displayln "Make sure Ollama is running and the model is pulled.")
27 (newline)
28
29 (let loop ()
30 (display-menu)
31 (let ([choice (read-line)])
32 (cond
33 [(string=? choice "1")
34 (displayln "\n>>> Calling get_current_datetime...")
35 (displayln (call-ollama-with-tools
36 "What is the current date and time?"
37 '("get_current_datetime")))
38 (loop)]
39
40 [(string=? choice "2")
41 (display "Enter location: ")
42 (let ([location (read-line)])
43 (displayln (format "\n>>> Getting weather for ~a..." location))
44 (displayln (call-ollama-with-tools
45 (format "What is the weather in ~a?" location)
46 '("get_weather"))))
47 (loop)]
48
49 [(string=? choice "3")
50 (displayln "\n>>> Listing directory...")
51 (displayln (call-ollama-with-tools
52 "What files are in the current directory?"
53 '("list_directory")))
54 (loop)]
55
56 [(string=? choice "4")
57 (display "Enter file path: ")
58 (let ([filepath (read-line)])
59 (displayln (format "\n>>> Reading ~a..." filepath))
60 (displayln (call-ollama-with-tools
61 (format "Read the contents of ~a and summarize it" filepath)
62 '("read_file_contents"))))
63 (loop)]
64
65 [(string=? choice "5")
66 (display "Enter search query: ")
67 (let ([query (read-line)])
68 (displayln (format "\n>>> Searching Wikipedia for ~a..." query))
69 (displayln (call-ollama-with-tools
70 (format "Tell me about ~a" query)
71 '("search_wikipedia"))))
72 (loop)]
73
74 [(string=? choice "6")
75 (display "Enter your prompt: ")
76 (let ([prompt (read-line)])
77 (displayln "\n>>> Processing with all tools...")
78 (displayln (call-ollama-with-tools
79 prompt
80 '("get_current_datetime" "get_weather"
81 "list_directory" "read_file_contents"
82 "search_wikipedia"))))
83 (loop)]
84
85 [(string=? choice "7")
86 (displayln "Goodbye!")]
87
88 [else
89 (displayln "Invalid choice, try again.")
90 (loop)]))))
91
92 (run-demo)
Here is some example output:
1 $ racket main.rkt
2 Using model: qwen3:1.7b
3 Ollama host: http://localhost:11434
4 Make sure Ollama is running and the model is pulled.
5
6
7 === Ollama Tools Demo ===
8 1. Get current date and time
9 2. Get weather for a location
10 3. List files in current directory
11 4. Read a file
12 5. Search Wikipedia
13 6. Custom prompt (all tools available)
14 7. Exit
15 Select option: 1
16
17 >>> Calling get_current_datetime...
18 The current date and time is **Wednesday, April 8th, 2026 11:28:40am**.
19
20 === Ollama Tools Demo ===
21 1. Get current date and time
22 2. Get weather for a location
23 3. List files in current directory
24 4. Read a file
25 5. Search Wikipedia
26 6. Custom prompt (all tools available)
27 7. Exit
28 Select option: 3
29
30 >>> Listing directory...
31 The current directory contains the following files:
32
33 - `README.md`
34 - `compiled`
35 - `main.rkt`
36 - `main.rkt~` (modified)
37 - `tools.rkt`
38 - `tools.rkt~` (modified)
39
40 These files are located in the directory `/Users/markwatson/GITHUB/Racket-AI-book/source-code/ollama_tools/`. The ~ symbols indicate modified files.
41
42 === Ollama Tools Demo ===
43 1. Get current date and time
44 2. Get weather for a location
45 3. List files in current directory
46 4. Read a file
47 5. Search Wikipedia
48 6. Custom prompt (all tools available)
49 7. Exit
50 Select option: 5
51 Enter search query: Flagstaff Arizona
52
53 >>> Searching Wikipedia for Flagstaff Arizona...
54 Flagstaff, Arizona, is a city located in the Phoenix metropolitan area, known for its scenic beauty, historical landmarks, and outdoor activities. It is part of the Grand Canyon Railway system and is home to the Grand Canyon Railway Museum. The city also features the historic Flagstaff Historical Society and the Flagstaff Art Center. Flagstaff is situated near the Colorado River and is a popular destination for outdoor recreation, including hiking, camping, and visiting the Grand Canyon. While specific Wikipedia summaries may not be available, Flagstaff is recognized for its natural beauty, cultural heritage, and community spirit.
55
56 === Ollama Tools Demo ===
57 1. Get current date and time
58 2. Get weather for a location
59 3. List files in current directory
60 4. Read a file
61 5. Search Wikipedia
62 6. Custom prompt (all tools available)
63 7. Exit
64 Select option:
The following diagram shows the high-level architecture of the Ollama tool-calling framework developed in this chapter:
Summary
Tool calling transforms LLMs from passive text generators into active agents that can:
- Access live data — weather, news, stock prices
- Interact with the system — read/write files, run commands
- Call external APIs — databases, web services
- Chain operations — multiple tools in sequence
This is foundational for building AI agents and assistants. In the next chapter on agents, we’ll see how tools enable more complex autonomous behavior.
Optional Practice Problems
- Implement a Calculator Tool: Create and register a new tool named
calculatethat accepts a mathematical expression (e.g.,"125 * 8"or"2^10") and safely evaluates the expression in Racket, returning the result to the model. - Execute Multiple Tool Calls: The current implementation handles a single tool call returned by the model. Refactor the execution loop in
call-ollama-with-toolsto support processing multiple tool calls in a single turn when the LLM returns an array of tool requests. - Schema Validation: Write a Racket function to validate the tool arguments received from the LLM against the parameters’ JSON schema defined in
register-toolbefore executing the tool’s handler. Return a schema error response to the LLM if validation fails.