Docs / Components / Tools & Function Calling

Tools & Function Calling

Type-safe function definitions, JSON parameter schemas, and dynamic tool execution.

Langchain.Core.Tool Langchain.Core.Class

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 IO or 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 toolollama

OpenAI / OpenRouter

Ensure your OPENROUTER_API_KEY or OPENAI_API_KEY is exported:

export OPENROUTER_API_KEY="your-api-key"
stack run toolopenai
ESC