ollama-haskell-0.3.0.0: src/Ollama/Types/Common.hs
{- |
Module : Ollama.Types.Common
Copyright : (c) 2024-2026 Tushar Adhatrao
License : MIT
Maintainer : tusharadhatrao@gmail.com
Stability : stable
Portability : portable
Common newtypes and domain primitives for the Ollama API.
@since 3.0.0.0
-}
module Ollama.Types.Common (
ModelName (..),
mkModelName,
Digest (..),
Base64Image (..),
Duration (..),
durationToSeconds,
durationToMillis,
tokensPerSecond,
Version (..),
Think (..),
ThinkingLevel (..),
) where
import Data.Aeson (FromJSON (..), ToJSON (..), Value (..), object, withText, (.:), (.=))
import Data.Aeson.Types (typeMismatch)
import Data.Hashable (Hashable)
import Data.Int (Int64)
import Data.String (IsString)
import Data.Text (Text)
import Data.Text qualified as T
import GHC.Generics (Generic)
{- | Model name following the @model:tag@ format.
@since 3.0.0.0
-}
newtype ModelName = ModelName {unModelName :: Text}
deriving newtype (Eq, Ord, Show, IsString, ToJSON, FromJSON, Hashable)
{- | Smart constructor that validates that a model name is non-empty.
@since 3.0.0.0
-}
mkModelName :: Text -> Either Text ModelName
mkModelName t
| T.null t = Left "Model name cannot be empty"
| otherwise = Right (ModelName t)
{- | SHA256 digest of a layer blob.
@since 3.0.0.0
-}
newtype Digest = Digest {unDigest :: Text}
deriving newtype (Eq, Ord, Show, ToJSON, FromJSON, Hashable)
{- | Base64-encoded image data for multimodal inputs.
@since 3.0.0.0
-}
newtype Base64Image = Base64Image {unBase64Image :: Text}
deriving newtype (Eq, Show, ToJSON, FromJSON)
{- | Duration in nanoseconds as returned by the Ollama API.
@since 3.0.0.0
-}
newtype Duration = Duration {durationNanos :: Int64}
deriving newtype (Eq, Ord, Show, ToJSON, FromJSON, Num)
{- | Convert duration nanoseconds to seconds.
@since 3.0.0.0
-}
durationToSeconds :: Duration -> Double
durationToSeconds (Duration ns) = fromIntegral ns / 1e9
{- | Convert duration nanoseconds to milliseconds.
@since 1.0.0.0
-}
durationToMillis :: Duration -> Double
durationToMillis (Duration ns) = fromIntegral ns / 1e6
{- | Calculate tokens per second (tokens\/s) given a token count and a 'Duration'.
@since 1.0.0.0
-}
tokensPerSecond :: Int -> Duration -> Double
tokensPerSecond count dur =
let secs = durationToSeconds dur
in if secs <= 0 then 0.0 else fromIntegral count / secs
{- | Ollama server engine version string.
@since 3.0.0.0
-}
newtype Version = Version {unVersion :: Text}
deriving stock (Eq, Show, Generic)
instance FromJSON Version where
parseJSON = \case
String s -> pure $ Version s
Object v -> Version <$> v .: "version"
v -> typeMismatch "Version" v
instance ToJSON Version where
toJSON (Version s) = object ["version" .= s]
{- | Thinking level settings for reasoning models.
@since 3.0.0.0
-}
data ThinkingLevel = ThinkLow | ThinkMedium | ThinkHigh | ThinkMax
deriving stock (Eq, Show, Bounded, Enum, Generic)
instance ToJSON ThinkingLevel where
toJSON ThinkLow = String "low"
toJSON ThinkMedium = String "medium"
toJSON ThinkHigh = String "high"
toJSON ThinkMax = String "max"
instance FromJSON ThinkingLevel where
parseJSON = withText "ThinkingLevel" $ \case
"low" -> pure ThinkLow
"medium" -> pure ThinkMedium
"high" -> pure ThinkHigh
"max" -> pure ThinkMax
other -> fail $ "Unknown thinking level: " <> T.unpack other
{- | Controls whether a thinking/reasoning model outputs its thoughts.
@since 3.0.0.0
-}
data Think
= ThinkEnabled
| ThinkDisabled
| ThinkLevel !ThinkingLevel
deriving stock (Eq, Show, Generic)
instance ToJSON Think where
toJSON ThinkEnabled = Bool True
toJSON ThinkDisabled = Bool False
toJSON (ThinkLevel lvl) = toJSON lvl
instance FromJSON Think where
parseJSON (Bool True) = pure ThinkEnabled
parseJSON (Bool False) = pure ThinkDisabled
parseJSON (String s) = ThinkLevel <$> parseJSON (String s)
parseJSON v = typeMismatch "Think" v