Function & Tool Calling

Give local LLMs access to custom Haskell functions and external APIs with structured tool calling.

What is Tool Calling?

Tool calling enables the language model to invoke your custom Haskell functions when it needs external data (such as querying a database, calculating math, or checking the current weather).

The model does not run the code itself; instead, it returns a structured ToolCall containing the function name and arguments. Your Haskell program executes the function and passes the result back to the model.


1. Define a Tool in Haskell

Use the ergonomic smart constructors toolDef, objectParams, and stringParam (or numberParam, intParam, boolParam, enumParam):

{-# LANGUAGE OverloadedStrings #-}
module Main where

import Data.List.NonEmpty (NonEmpty ((:|)))
import qualified Data.Map.Strict as Map
import Data.Text (Text)
import Ollama

-- 1. Define the Stock Price Tool using smart constructors
stockTool :: Tool
stockTool =
  toolDef "get_stock_price" "Fetch the current stock price for a given ticker symbol." $
    objectParams
      [ ("symbol", stringParam (Just "Stock ticker symbol (e.g. AAPL, GOOGL)")) ]
      ["symbol"]

2. Multi-Turn Tool Execution Loop

Pass the tool in chatTools and inspect the model’s response:

main :: IO ()
main = do
  client <- defaultClient

  let userMsg = userMessage "What is the stock price of AAPL?"
      req = (chatRequest "llama3.2:latest" (userMsg :| []))
        { chatTools = Just [stockTool] }

  putStrLn "Sending user prompt to model..."
  res <- chat client req

  case res of
    Left err -> print err
    Right resp -> case crMessage resp of
      Just assistantMsg -> do
        case messageToolCalls assistantMsg of
          Just (toolCall : _) -> do
            let fn = tcFunction toolCall
                callId = tcId toolCall
            putStrLn $ "LLM requested tool call: " <> show (tcfName fn)
            putStrLn $ "Call ID: " <> show callId
            putStrLn $ "With arguments: " <> show (tcfArguments fn)

            -- 3. Execute custom Haskell logic
            let resultText = "AAPL is currently trading at $234.50 USD (+1.8%)"

            -- 4. Send tool result back to the model (with optional call ID)
            let toolRespMsg = case callId of
                  Just cid -> toolResultMessageWithId resultText (tcfName fn) cid
                  Nothing  -> toolResultMessage resultText (tcfName fn)
                followUpHistory = userMsg :| [assistantMsg, toolRespMsg]
                followUpReq = (chatRequest "llama3.2:latest" followUpHistory)
                  { chatTools = Just [stockTool] }

            finalRes <- chat client followUpReq
            case finalRes of
              Right finalResp -> mapM_ (print . messageFullText) (crMessage finalResp)
              Left err        -> print err

          _ ->
            putStrLn $ "Model responded directly: " <> show (messageFullText assistantMsg)
      Nothing -> putStrLn "No message returned"