Ollama client for Haskell
54
stars
188
commits
Haskell
primary language
Aug 24, 2026
updated
Modern Haskell client library for the Ollama local LLM engine.
OllamaClient handle with connection pooling and resource management (newClient, defaultClient, clientFromEnv, withClient).conduit-based response streaming (chatStream, generateStream, pullStream, pushStream, createModelStream).mcp-server for converting between Ollama tools and MCP tools, running MCP servers via stdio or HTTP (Ollama.MCP).GHC.Generics with ToSchema and formatFor.SchemaBuilder DSL (|+, |++, |!, |!!) for type-safe JSON Schema structured responses.Tool), tool calls (ToolCall), and execution results (toolResultMessage).qwen3.5, deepseek-r1) with Think / ThinkingLevel types.OLLAMA_HOST and bearer token support for OLLAMA_API_KEY.NoRetry, ConstantRetry, ExponentialRetry), custom timeouts, lifecycle callbacks, and structured logging.InMemoryStore and ConversationStore typeclass for managing multi-turn chat sessions.Add ollama-haskell to your .cabal file:
build-depends:
base >= 4.17 && < 5
, ollama-haskell >= 0.4.1.0
Or using Stack in package.yaml:
dependencies:
- ollama-haskell >= 0.4.1.0
import Data.List.NonEmpty (NonEmpty ((:|)))
import Data.Text.IO qualified as TIO
import Ollama
main :: IO ()
main = do
client <- defaultClient
res <- chat client $ chatRequest "qwen3.5:2b" (userMessage "Why is the sky blue?" :| [])
case res of
Left err -> print err
Right resp -> mapM_ (TIO.putStrLn . messageContent) (crMessage resp)
Stream LLM responses token-by-token in real time:
import Conduit (mapM_C, runConduit, (.|))
import Control.Monad.IO.Class (liftIO)
import Data.List.NonEmpty (NonEmpty ((:|)))
import Data.Text.IO qualified as TIO
import Ollama
import System.IO (hFlush, stdout)
main :: IO ()
main = do
client <- defaultClient
let req = chatRequest "qwen3.5:2b" (userMessage "Count from 1 to 5." :| [])
-- Stream tokens to stdout as they arrive
runConduit $
chatStream client req .| mapM_C (\chunk -> liftIO $ do
mapM_ (TIO.putStr . messageContent) (crMessage chunk)
hFlush stdout
)
putStrLn ""
You can also accumulate all chunks at once with collectStream, or fold text with foldStream:
-- Collect all chunks:
chunks <- collectStream (chatStream client req)
-- Or fold into a single Text value:
fullText <- foldStream (\acc c -> acc <> maybe "" messageContent (crMessage c)) "" (chatStream client req)
Define function signatures and let the LLM execute structured tool calls:
import Data.List.NonEmpty (NonEmpty ((:|)))
import Ollama
calculatorTool :: Tool
calculatorTool = Tool "function" $ FunctionDef
{ fnName = "add"
, fnDescription = Just "Add two numbers"
, fnParameters = Just (FunctionParameters "object" Nothing (Just ["a", "b"]) Nothing Nothing Nothing)
, fnStrict = Just True
}
main :: IO ()
main = do
client <- defaultClient
let req = (chatRequest "qwen3.5:2b" (userMessage "What is 40 + 2?" :| []))
{ chatTools = Just [calculatorTool] }
res <- chat client req
case res of
Left err -> print err
Right resp -> print (crMessage resp)
Enforce structured JSON output formats using SchemaBuilder (re-exported directly from Ollama):
import Data.Text.IO qualified as TIO
import Ollama
personSchema :: Schema
personSchema = buildSchema $ emptyObject
|+ ("name", JString)
|+ ("age", JInteger)
|! "name"
main :: IO ()
main = do
client <- defaultClient
let req = (generateRequest "qwen3.5:2b" "Generate a person profile.")
{ genFormat = Just (SchemaFormat personSchema) }
res <- generate client req
case res of
Left err -> print err
Right resp -> TIO.putStrLn (grResponse resp)
Construct a client using environment variables (OLLAMA_HOST, OLLAMA_API_KEY):
main :: IO ()
main = do
client <- clientFromEnv
-- Automatically connects to OLLAMA_HOST with optional Authorization: Bearer header
...
Or configure custom retry policies and loggers:
customConfig :: OllamaClientConfig
customConfig = defaultConfig
{ configBaseUrl = "http://my-ollama-server:11434"
, configTimeout = 120
, configRetry = ExponentialRetry 3 1000000 -- 3 retries with exponential backoff
, configLogger = Just (\level msg -> putStrLn $ "[" <> show level <> "] " <> show msg)
}
main :: IO ()
main = withClient customConfig $ \client -> do
...
| Feature | Haskell (ollama-haskell) | Official Python (ollama-python) | Official JS/TS (ollama-js) | Community Go (ollama/ollama) |
|---|---|---|---|---|
| Strict Type Safety | ✅ Compile-time (PVP, Smart Constructors) | ⚠️ Type hints (Runtime) | ⚠️ TypeScript (Erased at runtime) | ✅ Go Structs |
| Response Streaming | ✅ conduit ($O(1)$ constant memory) | ⚠️ Python Generator | ⚠️ Async Iterator | ⚠️ Go Channels |
| Structured Output Derivation | ✅ GHC.Generics (ToSchema) | ⚠️ Pydantic BaseModel | ⚠️ Zod / JSON Schema | ⚠️ Manual JSON Schema |
| Model Context Protocol (MCP) | ✅ Native mcp-server Bridge | ❌ Manual | ❌ Manual | ❌ Manual |
| Thinking / Reasoning Models | ✅ Dedicated Think ADT | ⚠️ Dict parameters | ⚠️ Object properties | ⚠️ Raw parameters |
| Transactional Chat Store | ✅ STM InMemoryStore | ❌ None | ❌ None | ❌ None |
| Built-in Mock Testing | ✅ Ollama.Testing (Pure) | ❌ None | ❌ None | ❌ None |
| Configurable Retry & Backoff | ✅ Exponential & Constant ADT | ❌ Manual | ❌ Manual | ❌ Manual |
| Token Throughput Metrics | ✅ Native Calculation Helpers | ⚠️ Raw nanoseconds | ⚠️ Raw nanoseconds | ⚠️ Raw nanoseconds |
| Environment Auto-Discovery | ✅ clientFromEnv | ✅ Default client | ✅ Default client | ✅ Default client |
MIT © 2024–2026 Tushar Adhatrao
Haskell
98.9%
Makefile
1.1%
Ollama client for Haskell
54
stars
188
commits
Haskell
primary language
Aug 24, 2026
updated
Modern Haskell client library for the Ollama local LLM engine.
OllamaClient handle with connection pooling and resource management (newClient, defaultClient, clientFromEnv, withClient).conduit-based response streaming (chatStream, generateStream, pullStream, pushStream, createModelStream).mcp-server for converting between Ollama tools and MCP tools, running MCP servers via stdio or HTTP (Ollama.MCP).GHC.Generics with ToSchema and formatFor.SchemaBuilder DSL (|+, |++, |!, |!!) for type-safe JSON Schema structured responses.Tool), tool calls (ToolCall), and execution results (toolResultMessage).qwen3.5, deepseek-r1) with Think / ThinkingLevel types.OLLAMA_HOST and bearer token support for OLLAMA_API_KEY.NoRetry, ConstantRetry, ExponentialRetry), custom timeouts, lifecycle callbacks, and structured logging.InMemoryStore and ConversationStore typeclass for managing multi-turn chat sessions.Add ollama-haskell to your .cabal file:
build-depends:
base >= 4.17 && < 5
, ollama-haskell >= 0.4.1.0
Or using Stack in package.yaml:
dependencies:
- ollama-haskell >= 0.4.1.0
import Data.List.NonEmpty (NonEmpty ((:|)))
import Data.Text.IO qualified as TIO
import Ollama
main :: IO ()
main = do
client <- defaultClient
res <- chat client $ chatRequest "qwen3.5:2b" (userMessage "Why is the sky blue?" :| [])
case res of
Left err -> print err
Right resp -> mapM_ (TIO.putStrLn . messageContent) (crMessage resp)
Stream LLM responses token-by-token in real time:
import Conduit (mapM_C, runConduit, (.|))
import Control.Monad.IO.Class (liftIO)
import Data.List.NonEmpty (NonEmpty ((:|)))
import Data.Text.IO qualified as TIO
import Ollama
import System.IO (hFlush, stdout)
main :: IO ()
main = do
client <- defaultClient
let req = chatRequest "qwen3.5:2b" (userMessage "Count from 1 to 5." :| [])
-- Stream tokens to stdout as they arrive
runConduit $
chatStream client req .| mapM_C (\chunk -> liftIO $ do
mapM_ (TIO.putStr . messageContent) (crMessage chunk)
hFlush stdout
)
putStrLn ""
You can also accumulate all chunks at once with collectStream, or fold text with foldStream:
-- Collect all chunks:
chunks <- collectStream (chatStream client req)
-- Or fold into a single Text value:
fullText <- foldStream (\acc c -> acc <> maybe "" messageContent (crMessage c)) "" (chatStream client req)
Define function signatures and let the LLM execute structured tool calls:
import Data.List.NonEmpty (NonEmpty ((:|)))
import Ollama
calculatorTool :: Tool
calculatorTool = Tool "function" $ FunctionDef
{ fnName = "add"
, fnDescription = Just "Add two numbers"
, fnParameters = Just (FunctionParameters "object" Nothing (Just ["a", "b"]) Nothing Nothing Nothing)
, fnStrict = Just True
}
main :: IO ()
main = do
client <- defaultClient
let req = (chatRequest "qwen3.5:2b" (userMessage "What is 40 + 2?" :| []))
{ chatTools = Just [calculatorTool] }
res <- chat client req
case res of
Left err -> print err
Right resp -> print (crMessage resp)
Enforce structured JSON output formats using SchemaBuilder (re-exported directly from Ollama):
import Data.Text.IO qualified as TIO
import Ollama
personSchema :: Schema
personSchema = buildSchema $ emptyObject
|+ ("name", JString)
|+ ("age", JInteger)
|! "name"
main :: IO ()
main = do
client <- defaultClient
let req = (generateRequest "qwen3.5:2b" "Generate a person profile.")
{ genFormat = Just (SchemaFormat personSchema) }
res <- generate client req
case res of
Left err -> print err
Right resp -> TIO.putStrLn (grResponse resp)
Construct a client using environment variables (OLLAMA_HOST, OLLAMA_API_KEY):
main :: IO ()
main = do
client <- clientFromEnv
-- Automatically connects to OLLAMA_HOST with optional Authorization: Bearer header
...
Or configure custom retry policies and loggers:
customConfig :: OllamaClientConfig
customConfig = defaultConfig
{ configBaseUrl = "http://my-ollama-server:11434"
, configTimeout = 120
, configRetry = ExponentialRetry 3 1000000 -- 3 retries with exponential backoff
, configLogger = Just (\level msg -> putStrLn $ "[" <> show level <> "] " <> show msg)
}
main :: IO ()
main = withClient customConfig $ \client -> do
...
| Feature | Haskell (ollama-haskell) | Official Python (ollama-python) | Official JS/TS (ollama-js) | Community Go (ollama/ollama) |
|---|---|---|---|---|
| Strict Type Safety | ✅ Compile-time (PVP, Smart Constructors) | ⚠️ Type hints (Runtime) | ⚠️ TypeScript (Erased at runtime) | ✅ Go Structs |
| Response Streaming | ✅ conduit ($O(1)$ constant memory) | ⚠️ Python Generator | ⚠️ Async Iterator | ⚠️ Go Channels |
| Structured Output Derivation | ✅ GHC.Generics (ToSchema) | ⚠️ Pydantic BaseModel | ⚠️ Zod / JSON Schema | ⚠️ Manual JSON Schema |
| Model Context Protocol (MCP) | ✅ Native mcp-server Bridge | ❌ Manual | ❌ Manual | ❌ Manual |
| Thinking / Reasoning Models | ✅ Dedicated Think ADT | ⚠️ Dict parameters | ⚠️ Object properties | ⚠️ Raw parameters |
| Transactional Chat Store | ✅ STM InMemoryStore | ❌ None | ❌ None | ❌ None |
| Built-in Mock Testing | ✅ Ollama.Testing (Pure) | ❌ None | ❌ None | ❌ None |
| Configurable Retry & Backoff | ✅ Exponential & Constant ADT | ❌ Manual | ❌ Manual | ❌ Manual |
| Token Throughput Metrics | ✅ Native Calculation Helpers | ⚠️ Raw nanoseconds | ⚠️ Raw nanoseconds | ⚠️ Raw nanoseconds |
| Environment Auto-Discovery | ✅ clientFromEnv | ✅ Default client | ✅ Default client | ✅ Default client |
MIT © 2024–2026 Tushar Adhatrao
Haskell
98.9%
Makefile
1.1%