Tools & Function Calling
Type-safe function definitions, JSON parameter schemas, and dynamic tool execution.
Type-safe function definitions, JSON parameter schemas, and dynamic tool execution.
Key Concepts
- Typed Tool Definitions: Define function names, descriptions, and JSON Schema parameters that models can inspect and invoke.
- Effect-Polymorphic Execution: Tool execution functions operate cleanly in
IOor custom monads with typed arguments and string results. - Tool Dispatching: Automated mapping between LLM function call responses and Haskell handlers.
Working Code Example
Compare local execution via Ollama and cloud API execution via OpenAI / OpenRouter. Use the toggle tabs or the global provider switcher in the header to switch:
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
module Ollama.Tool (runApp) where
import Control.Monad.IO.Class (liftIO)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Langchain.Prelude
import Langchain.Tool.Calculator (calculatorTool)
inputPrompt :: T.Text
inputPrompt = "What is 15 * 4? Please use the calculator tool to find the answer."
runApp :: IO ()
runApp = do
o <- newOllama "qwen3.5:2b" defaultConfig
let promptMsgs = [userMessage inputPrompt]
chatReq = withTools [calculatorTool @IO] (chatRequestFor o promptMsgs)
res <- runLangchainT () $ do
respMsg <- invoke o promptMsgs (Just chatReq)
case messageToolCalls respMsg of
Nothing -> do
liftIO $ T.putStrLn "No tool called, direct answer:"
liftIO $ T.putStrLn $ extractMessageText respMsg
Just [] -> do
liftIO $ T.putStrLn "No tool called, direct answer:"
liftIO $ T.putStrLn $ extractMessageText respMsg
Just (tCall : _) -> do
liftIO $ T.putStrLn $ "Tool called: " <> toolCallName tCall
liftIO $ T.putStrLn $ "Arguments: " <> T.pack (show (toolCallArguments tCall))
eExec <- liftIO $ toolExecute (calculatorTool @IO) (toolCallArguments tCall)
toolResult <- case eExec of
Left err -> pure $ "Tool execution error: " <> errorMessage err
Right out -> pure out
liftIO $ T.putStrLn $ "Tool execution result: " <> toolResult
let toolMsg =
(toolMessage toolResult)
{ messageName = Just (toolCallName tCall)
}
conversation = promptMsgs ++ [respMsg, toolMsg]
followUpReq = withTools [calculatorTool @IO] (chatRequestFor o conversation)
finalMsg <- invoke o conversation (Just followUpReq)
liftIO $ T.putStrLn "\nFinal Assistant Answer:"
liftIO $ T.putStrLn $ extractMessageText finalMsg
case res of
Left err -> T.putStrLn $ "Error: " <> errorMessage err
Right () -> pure (){-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
module OpenAI.Tool (runApp) where
import Control.Monad.IO.Class (liftIO)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Langchain.Prelude
import Langchain.Tool.Calculator (calculatorTool)
import OpenAI.Common (defaultModelName, getOpenRouterModel)
inputPrompt :: T.Text
inputPrompt = "What is 15 * 4? Please use the calculator tool to find the answer."
runApp :: IO ()
runApp = do
o <- getOpenRouterModel defaultModelName
let promptMsgs = [userMessage inputPrompt]
tools = [calculatorTool @IO]
chatReq = bindToolsConfig @OpenAI tools Nothing
res <- runLangchainT () $ do
respMsg <- invoke o promptMsgs chatReq
case messageToolCalls respMsg of
Nothing -> do
liftIO $ T.putStrLn "No tool called, direct answer:"
liftIO $ T.putStrLn $ extractMessageText respMsg
Just [] -> do
liftIO $ T.putStrLn "No tool called, direct answer:"
liftIO $ T.putStrLn $ extractMessageText respMsg
Just (tCall : _) -> do
liftIO $ T.putStrLn $ "Tool called: " <> toolCallName tCall
liftIO $ T.putStrLn $ "Arguments: " <> T.pack (show (toolCallArguments tCall))
eExec <- liftIO $ toolExecute (calculatorTool @IO) (toolCallArguments tCall)
toolResult <- case eExec of
Left err -> pure $ "Tool execution error: " <> errorMessage err
Right out -> pure out
liftIO $ T.putStrLn $ "Tool execution result: " <> toolResult
let toolMsg =
(toolMessage toolResult)
{ messageName = Just (toolCallName tCall)
, messageToolId = Just (toolCallId tCall)
}
conversation = promptMsgs ++ [respMsg, toolMsg]
followUpReq = bindToolsConfig @OpenAI tools Nothing
finalMsg <- invoke o conversation followUpReq
liftIO $ T.putStrLn "\nFinal Assistant Answer:"
liftIO $ T.putStrLn $ extractMessageText finalMsg
case res of
Left err -> T.putStrLn $ "Error: " <> errorMessage err
Right () -> pure ()Core Types & Functions
data Tool = Tool { toolName :: Text, toolDescription :: Text, toolExecute :: Value -> IO (Either Text Value) }data FunctionDefinition = FunctionDefinition { funcName :: Text, funcDescription :: Text, funcParameters :: Value }Running This Example
Local Ollama
Ensure your Ollama daemon is running locally with the target model:
ollama run gemma3 # or your desired model
stack run toolollamaOpenAI / OpenRouter
Ensure your OPENROUTER_API_KEY or OPENAI_API_KEY is exported:
export OPENROUTER_API_KEY="your-api-key"
stack run toolopenai