packages feed

genai-lib-1.3: src/lib/GenAILib/Ollama.hs

{-# LANGUAGE DuplicateRecordFields #-}

module GenAILib.Ollama
  ( Host (..)
  , OllamaRequest (..)
  , Stream (..)
  , System (..)
  , defaultHost
  , hostFromString
  )
  where

import Data.Aeson (ToJSON, Value, defaultOptions, genericToEncoding,
  genericToJSON, omitNothingFields, toEncoding, toJSON)
import Data.Aeson qualified as Aeson
import Data.String.Conv (toS)
import Data.Text.Lazy qualified as TL
import Formatting ((%), formatToString, int, text)
import GHC.Generics (Generic)
import Text.Read (readMaybe)

import GenAILib.Common (Model, Prompt, splitAtColon)


newtype Stream = Stream Bool
  deriving Generic

instance ToJSON Stream


{- ^ This data structure is used to construct the POST body for an Ollama REST call
-}
data OllamaRequest = OllamaRequest
  { model :: Model
  , system :: Maybe System
  , prompt :: Prompt
  , stream :: Stream
  , options :: Maybe Value
  }
  deriving Generic

customOptions :: Aeson.Options
customOptions = defaultOptions { omitNothingFields = True }

instance ToJSON OllamaRequest where
  toJSON     = genericToJSON customOptions
  toEncoding = genericToEncoding customOptions


data Host = Host TL.Text Int

-- I don't normally do this because it harms debugging when we want to see the
-- actual Haskell data structure serialized BUT this plays nicer with
-- optparse-applicative's showDefault
instance Show Host where
  show (Host hostName port) = formatToString (text % ":" % int) hostName port


defaultHost :: Host
defaultHost = Host "localhost" 11434


hostFromString :: String -> Maybe Host
hostFromString combinedStr = do
  (hostName, portStr) <- splitAtColon combinedStr
  Host (toS hostName) <$> readMaybe portStr


newtype System = System TL.Text
  deriving Generic

instance ToJSON System