packages feed

ollama-haskell-0.3.0.0: examples/AllFeatures.hs

{-# LANGUAGE OverloadedStrings #-}

{- |
Module      : Main
Copyright   : (c) 2024-2026 Tushar Adhatrao
License     : MIT
Maintainer  : tusharadhatrao@gmail.com
Stability   : stable
Portability : portable

Comprehensive example demonstrating all v1.0 features of ollama-haskell:
  * Client builder with env resolution, custom headers, & retry policies
  * Conduit-based response streaming
  * Thinking / reasoning model integration
  * Function / tool calling
  * Structured JSON schema output
  * Vector embeddings & token throughput metrics
  * Transactional STM conversation storage

@since 1.0.0.0
-}
module Main (main) where

import Data.List.NonEmpty (NonEmpty ((:|)))
import Data.Text qualified as T
import Data.Time (getCurrentTime)
import Ollama

fastOptions :: Maybe ModelOptions
fastOptions = Just (defaultOptions {optNumPredict = Just 20})

main :: IO ()
main = do
  putStrLn "=========================================================="
  putStrLn "   ollama-haskell v1.0 — All Features Showcase Example"
  putStrLn "=========================================================="

  -- 1. Client Builder with env vars, custom headers, & Exponential Retry Policy
  putStrLn "\n[1] Initializing client..."
  let customConfig =
        defaultConfig
          { configRetry = ExponentialRetry 3 1
          , configHeaders = [("X-Custom-Client", "ollama-haskell-v1.0")]
          }
  client <- newClient customConfig
  putStrLn "Client initialized with ExponentialRetry policy."

  -- 2. System Inspection
  putStrLn "\n[2] System Engine Version..."
  verRes <- getVersion client
  case verRes of
    Left err -> putStrLn $ "Error fetching version: " <> show err
    Right ver -> putStrLn $ "Ollama Engine Version: " <> T.unpack (unVersion ver)

  putStrLn "\n[3] Listing Local Models..."
  modelsRes <- listModels client
  case modelsRes of
    Left err -> putStrLn $ "Error listing models: " <> show err
    Right (ListResponse ms) -> do
      putStrLn $ "Found " <> show (length ms) <> " installed models:"
      mapM_ (\m -> putStrLn $ "  - " <> T.unpack (unModelName (miName m))) ms

  let model = "qwen3.5:2b"

  -- 4. Conduit-Based Streaming Chat
  putStrLn $ "\n[4] Streaming Chat with Conduit (" <> T.unpack (unModelName model) <> ")..."
  let streamReq =
        (chatRequest model (userMessage "Count from 1 to 5." :| []))
          { chatOptions = fastOptions
          , chatThink = Just ThinkDisabled
          }
  putStr "Response stream: "
  chunks <- collectStream (chatStream client streamReq)
  mapM_
    ( \c -> case crMessage c of
        Just msg -> putStr (T.unpack (messageContent msg))
        Nothing -> pure ()
    )
    chunks
  putStrLn ""

  -- 5. Non-Streaming Chat with Token Throughput Metrics
  putStrLn "\n[5] Non-Streaming Chat & Token Metrics..."
  let chatReq =
        ( chatRequest
            model
            (systemMessage "You are a concise assistant." :| [userMessage "Explain gravity in one sentence."])
        )
          { chatOptions = fastOptions
          , chatThink = Just ThinkDisabled
          }
  chatRes <- chat client chatReq
  case chatRes of
    Left err -> putStrLn $ "Chat error: " <> show err
    Right resp -> do
      case crMessage resp of
        Just msg -> putStrLn $ "Answer: " <> T.unpack (messageContent msg)
        Nothing -> putStrLn "No message returned."
      case chatEvalTokensPerSecond resp of
        Just tps -> putStrLn $ "Generation Speed: " <> show tps <> " tokens/sec"
        Nothing -> pure ()

  -- 6. Thinking / Reasoning Model Integration
  putStrLn "\n[6] Generation with Thinking Mode..."
  let thinkReq =
        (generateRequest model "What is 15 * 14?")
          { genThink = Just (ThinkLevel ThinkMedium)
          , genOptions = fastOptions
          }
  thinkRes <- generate client thinkReq
  case thinkRes of
    Left err -> putStrLn $ "Generate error: " <> show err
    Right resp -> putStrLn $ "Result: " <> T.unpack (grResponse resp)

  -- 7. Tool / Function Calling
  putStrLn "\n[7] Tool / Function Calling..."
  let calcTool =
        Tool
          { toolType = "function"
          , toolFunction =
              FunctionDef
                { fnName = "calculator"
                , fnDescription = Just "Perform basic math calculations"
                , fnParameters =
                    Just
                      FunctionParameters
                        { fpType = "object"
                        , fpProperties = Nothing
                        , fpRequired = Just ["expression"]
                        , fpAdditionalProperties = Nothing
                        , fpDescription = Nothing
                        , fpEnum = Nothing
                        }
                , fnStrict = Just True
                }
          }
      toolReq =
        (chatRequest model (userMessage "Calculate 42 * 8" :| []))
          { chatTools = Just [calcTool]
          , chatOptions = fastOptions
          , chatThink = Just ThinkDisabled
          }
  toolRes <- chat client toolReq
  case toolRes of
    Left err -> putStrLn $ "Tool chat error: " <> show err
    Right resp -> case crMessage resp of
      Just msg -> case messageToolCalls msg of
        Just calls -> putStrLn $ "Model requested tool execution: " <> show calls
        Nothing -> putStrLn $ "Response: " <> T.unpack (messageContent msg)
      Nothing -> putStrLn "No message returned."

  -- 8. Vector Embeddings
  putStrLn "\n[8] Vector Embeddings..."
  let embReq = embedRequest model ["Haskell AI development", "Ollama LLM client"]
  embRes <- embed client embReq
  case embRes of
    Left err -> putStrLn $ "Embed error: " <> show err
    Right resp -> putStrLn $ "Generated " <> show (length (erEmbeddings resp)) <> " embedding vectors."

  -- 9. Transactional Conversation Store
  putStrLn "\n[9] STM Conversation Store..."
  store <- initInMemoryStore
  now <- getCurrentTime
  let convId = "demo-session-42"
      session =
        Conversation
          convId
          [systemMessage "Context saved.", userMessage "Favorite language is Haskell."]
          model
          now
          now
  saveConversationInMemory store session
  retrieved <- loadConversationInMemory store convId
  case retrieved of
    Just c ->
      putStrLn $
        "Successfully loaded conversation ["
          <> T.unpack (conversationId c)
          <> "] with "
          <> show (length (messages c))
          <> " messages."
    Nothing -> putStrLn "Failed to load conversation."

  closeClient client
  putStrLn "\n=========================================================="
  putStrLn "   Showcase Completed Successfully!"
  putStrLn "=========================================================="