ollama-haskell 0.2.0.0 → 0.2.1.0
raw patch · 35 files changed
+1147/−357 lines, 35 filesdep ~http-client-tlsPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: http-client-tls
API changes (from Hackage documentation)
- Data.Ollama.Chat: [chatModelName] :: ChatOps -> !Text
- Data.Ollama.Chat: instance GHC.Show.Show Data.Ollama.Chat.ChatOps
- Data.Ollama.Common.Config: instance GHC.Generics.Generic Data.Ollama.Common.Config.OllamaConfig
- Data.Ollama.Common.Error: instance GHC.Exception.Type.Exception Data.Ollama.Common.Error.OllamaError
- Data.Ollama.Common.Error: instance GHC.Generics.Generic Data.Ollama.Common.Error.OllamaError
- Data.Ollama.Common.Error: instance GHC.Show.Show Data.Ollama.Common.Error.OllamaError
- Data.Ollama.Common.SchemaBuilder: instance GHC.Generics.Generic Data.Ollama.Common.SchemaBuilder.JsonType
- Data.Ollama.Common.SchemaBuilder: instance GHC.Generics.Generic Data.Ollama.Common.SchemaBuilder.Property
- Data.Ollama.Common.SchemaBuilder: instance GHC.Generics.Generic Data.Ollama.Common.SchemaBuilder.Schema
- Data.Ollama.Common.SchemaBuilder: instance GHC.Show.Show Data.Ollama.Common.SchemaBuilder.JsonType
- Data.Ollama.Common.SchemaBuilder: instance GHC.Show.Show Data.Ollama.Common.SchemaBuilder.Property
- Data.Ollama.Common.SchemaBuilder: instance GHC.Show.Show Data.Ollama.Common.SchemaBuilder.Schema
- Data.Ollama.Common.SchemaBuilder: instance GHC.Show.Show Data.Ollama.Common.SchemaBuilder.SchemaBuilder
- Data.Ollama.Common.Types: instance GHC.Generics.Generic Data.Ollama.Common.Types.FunctionDef
- Data.Ollama.Common.Types: instance GHC.Generics.Generic Data.Ollama.Common.Types.InputTool
- Data.Ollama.Common.Types: instance GHC.Generics.Generic Data.Ollama.Common.Types.Message
- Data.Ollama.Common.Types: instance GHC.Show.Show Data.Ollama.Common.Types.ChatResponse
- Data.Ollama.Common.Types: instance GHC.Show.Show Data.Ollama.Common.Types.Format
- Data.Ollama.Common.Types: instance GHC.Show.Show Data.Ollama.Common.Types.FunctionDef
- Data.Ollama.Common.Types: instance GHC.Show.Show Data.Ollama.Common.Types.FunctionParameters
- Data.Ollama.Common.Types: instance GHC.Show.Show Data.Ollama.Common.Types.GenerateResponse
- Data.Ollama.Common.Types: instance GHC.Show.Show Data.Ollama.Common.Types.InputTool
- Data.Ollama.Common.Types: instance GHC.Show.Show Data.Ollama.Common.Types.Message
- Data.Ollama.Common.Types: instance GHC.Show.Show Data.Ollama.Common.Types.ModelDetails
- Data.Ollama.Common.Types: instance GHC.Show.Show Data.Ollama.Common.Types.ModelOptions
- Data.Ollama.Common.Types: instance GHC.Show.Show Data.Ollama.Common.Types.OutputFunction
- Data.Ollama.Common.Types: instance GHC.Show.Show Data.Ollama.Common.Types.Role
- Data.Ollama.Common.Types: instance GHC.Show.Show Data.Ollama.Common.Types.ToolCall
- Data.Ollama.Common.Types: instance GHC.Show.Show Data.Ollama.Common.Types.Version
- Data.Ollama.Conversation: instance Control.Monad.IO.Class.MonadIO Data.Ollama.Conversation.ConvoM
- Data.Ollama.Conversation: instance GHC.Base.Applicative Data.Ollama.Conversation.ConvoM
- Data.Ollama.Conversation: instance GHC.Base.Functor Data.Ollama.Conversation.ConvoM
- Data.Ollama.Conversation: instance GHC.Base.Monad Data.Ollama.Conversation.ConvoM
- Data.Ollama.Conversation: instance GHC.Generics.Generic Data.Ollama.Conversation.Conversation
- Data.Ollama.Conversation: instance GHC.Show.Show Data.Ollama.Conversation.Conversation
- Data.Ollama.Copy: instance GHC.Generics.Generic Data.Ollama.Copy.CopyModelOps
- Data.Ollama.Copy: instance GHC.Show.Show Data.Ollama.Copy.CopyModelOps
- Data.Ollama.Create: instance Data.Aeson.Types.FromJSON.FromJSON Data.Ollama.Create.CreateModelResp
- Data.Ollama.Create: instance Data.Aeson.Types.ToJSON.ToJSON Data.Ollama.Create.CreateModelOps
- Data.Ollama.Create: instance Data.Ollama.Common.Types.HasDone Data.Ollama.Create.CreateModelResp
- Data.Ollama.Create: instance GHC.Classes.Eq Data.Ollama.Create.CreateModelOps
- Data.Ollama.Create: instance GHC.Classes.Eq Data.Ollama.Create.CreateModelResp
- Data.Ollama.Create: instance GHC.Show.Show Data.Ollama.Create.CreateModelOps
- Data.Ollama.Create: instance GHC.Show.Show Data.Ollama.Create.CreateModelResp
- Data.Ollama.Delete: instance GHC.Show.Show Data.Ollama.Delete.DeleteModelReq
- Data.Ollama.Embeddings: [keepAliveEmbed] :: EmbeddingOps -> !Maybe Int
- Data.Ollama.Embeddings: [model] :: EmbeddingOps -> !Text
- Data.Ollama.Embeddings: instance GHC.Show.Show Data.Ollama.Embeddings.EmbeddingOps
- Data.Ollama.Embeddings: instance GHC.Show.Show Data.Ollama.Embeddings.EmbeddingResp
- Data.Ollama.Generate: instance GHC.Show.Show Data.Ollama.Generate.GenerateOps
- Data.Ollama.List: instance GHC.Show.Show Data.Ollama.List.ModelInfo
- Data.Ollama.List: instance GHC.Show.Show Data.Ollama.List.Models
- Data.Ollama.Ps: instance GHC.Show.Show Data.Ollama.Ps.RunningModel
- Data.Ollama.Ps: instance GHC.Show.Show Data.Ollama.Ps.RunningModels
- Data.Ollama.Pull: instance GHC.Generics.Generic Data.Ollama.Pull.PullOps
- Data.Ollama.Pull: instance GHC.Generics.Generic Data.Ollama.Pull.PullResp
- Data.Ollama.Pull: instance GHC.Show.Show Data.Ollama.Pull.PullOps
- Data.Ollama.Pull: instance GHC.Show.Show Data.Ollama.Pull.PullResp
- Data.Ollama.Push: instance GHC.Generics.Generic Data.Ollama.Push.PushOps
- Data.Ollama.Push: instance GHC.Generics.Generic Data.Ollama.Push.PushResp
- Data.Ollama.Push: instance GHC.Show.Show Data.Ollama.Push.PushOps
- Data.Ollama.Push: instance GHC.Show.Show Data.Ollama.Push.PushResp
- Data.Ollama.Show: instance GHC.Generics.Generic Data.Ollama.Show.ShowModelOps
- Data.Ollama.Show: instance GHC.Show.Show Data.Ollama.Show.ShowModelInfo
- Data.Ollama.Show: instance GHC.Show.Show Data.Ollama.Show.ShowModelOps
- Data.Ollama.Show: instance GHC.Show.Show Data.Ollama.Show.ShowModelResponse
- Ollama: [chatModelName] :: ChatOps -> !Text
- Ollama: [expiresAt] :: RunningModel -> !UTCTime
- Ollama: [input] :: EmbeddingOps -> ![Text]
- Ollama: [keepAliveEmbed] :: EmbeddingOps -> !Maybe Int
- Ollama: [modelDetails] :: RunningModel -> !ModelDetails
- Ollama: [modelDigest] :: RunningModel -> !Text
- Ollama: [modelOptions] :: EmbeddingOps -> !Maybe ModelOptions
- Ollama: [name_] :: RunningModel -> !Text
- Ollama: [sizeVRam] :: RunningModel -> !Int64
- Ollama: [size_] :: RunningModel -> !Int64
- Ollama: [truncateInput] :: EmbeddingOps -> !Maybe Bool
+ Data.Ollama.Blob: checkBlobExists :: Text -> Maybe OllamaConfig -> IO (Either OllamaError Bool)
+ Data.Ollama.Blob: createBlob :: FilePath -> Text -> Maybe OllamaConfig -> IO (Either OllamaError ())
+ Data.Ollama.Chat: [doneReason] :: ChatResponse -> !Maybe Text
+ Data.Ollama.Chat: [modelName] :: ChatOps -> !Text
+ Data.Ollama.Chat: instance GHC.Internal.Show.Show Data.Ollama.Chat.ChatOps
+ Data.Ollama.Common.Config: instance GHC.Internal.Generics.Generic Data.Ollama.Common.Config.OllamaConfig
+ Data.Ollama.Common.Error: instance GHC.Internal.Exception.Type.Exception Data.Ollama.Common.Error.OllamaError
+ Data.Ollama.Common.Error: instance GHC.Internal.Generics.Generic Data.Ollama.Common.Error.OllamaError
+ Data.Ollama.Common.Error: instance GHC.Internal.Show.Show Data.Ollama.Common.Error.OllamaError
+ Data.Ollama.Common.SchemaBuilder: instance GHC.Internal.Generics.Generic Data.Ollama.Common.SchemaBuilder.JsonType
+ Data.Ollama.Common.SchemaBuilder: instance GHC.Internal.Generics.Generic Data.Ollama.Common.SchemaBuilder.Property
+ Data.Ollama.Common.SchemaBuilder: instance GHC.Internal.Generics.Generic Data.Ollama.Common.SchemaBuilder.Schema
+ Data.Ollama.Common.SchemaBuilder: instance GHC.Internal.Show.Show Data.Ollama.Common.SchemaBuilder.JsonType
+ Data.Ollama.Common.SchemaBuilder: instance GHC.Internal.Show.Show Data.Ollama.Common.SchemaBuilder.Property
+ Data.Ollama.Common.SchemaBuilder: instance GHC.Internal.Show.Show Data.Ollama.Common.SchemaBuilder.Schema
+ Data.Ollama.Common.SchemaBuilder: instance GHC.Internal.Show.Show Data.Ollama.Common.SchemaBuilder.SchemaBuilder
+ Data.Ollama.Common.Types: [doneReason] :: ChatResponse -> !Maybe Text
+ Data.Ollama.Common.Types: instance GHC.Internal.Generics.Generic Data.Ollama.Common.Types.FunctionDef
+ Data.Ollama.Common.Types: instance GHC.Internal.Generics.Generic Data.Ollama.Common.Types.InputTool
+ Data.Ollama.Common.Types: instance GHC.Internal.Generics.Generic Data.Ollama.Common.Types.Message
+ Data.Ollama.Common.Types: instance GHC.Internal.Show.Show Data.Ollama.Common.Types.ChatResponse
+ Data.Ollama.Common.Types: instance GHC.Internal.Show.Show Data.Ollama.Common.Types.Format
+ Data.Ollama.Common.Types: instance GHC.Internal.Show.Show Data.Ollama.Common.Types.FunctionDef
+ Data.Ollama.Common.Types: instance GHC.Internal.Show.Show Data.Ollama.Common.Types.FunctionParameters
+ Data.Ollama.Common.Types: instance GHC.Internal.Show.Show Data.Ollama.Common.Types.GenerateResponse
+ Data.Ollama.Common.Types: instance GHC.Internal.Show.Show Data.Ollama.Common.Types.InputTool
+ Data.Ollama.Common.Types: instance GHC.Internal.Show.Show Data.Ollama.Common.Types.Message
+ Data.Ollama.Common.Types: instance GHC.Internal.Show.Show Data.Ollama.Common.Types.ModelDetails
+ Data.Ollama.Common.Types: instance GHC.Internal.Show.Show Data.Ollama.Common.Types.ModelOptions
+ Data.Ollama.Common.Types: instance GHC.Internal.Show.Show Data.Ollama.Common.Types.OutputFunction
+ Data.Ollama.Common.Types: instance GHC.Internal.Show.Show Data.Ollama.Common.Types.Role
+ Data.Ollama.Common.Types: instance GHC.Internal.Show.Show Data.Ollama.Common.Types.ToolCall
+ Data.Ollama.Common.Types: instance GHC.Internal.Show.Show Data.Ollama.Common.Types.Version
+ Data.Ollama.Conversation: instance GHC.Internal.Base.Applicative Data.Ollama.Conversation.ConvoM
+ Data.Ollama.Conversation: instance GHC.Internal.Base.Functor Data.Ollama.Conversation.ConvoM
+ Data.Ollama.Conversation: instance GHC.Internal.Base.Monad Data.Ollama.Conversation.ConvoM
+ Data.Ollama.Conversation: instance GHC.Internal.Control.Monad.IO.Class.MonadIO Data.Ollama.Conversation.ConvoM
+ Data.Ollama.Conversation: instance GHC.Internal.Generics.Generic Data.Ollama.Conversation.Conversation
+ Data.Ollama.Conversation: instance GHC.Internal.Show.Show Data.Ollama.Conversation.Conversation
+ Data.Ollama.Copy: instance GHC.Internal.Generics.Generic Data.Ollama.Copy.CopyModelOps
+ Data.Ollama.Copy: instance GHC.Internal.Show.Show Data.Ollama.Copy.CopyModelOps
+ Data.Ollama.Create: CreateOps :: !Text -> !Maybe Text -> !Maybe (Map Text Text) -> !Maybe (Map Text Text) -> !Maybe Text -> !Maybe [Text] -> !Maybe Text -> !Maybe ModelOptions -> !Maybe [Message] -> !Maybe Bool -> !Maybe QuantizationType -> CreateOps
+ Data.Ollama.Create: CreateResp :: !Text -> !Maybe Text -> !Maybe Int64 -> !Maybe Int64 -> CreateResp
+ Data.Ollama.Create: Q4_K_M :: QuantizationType
+ Data.Ollama.Create: Q4_K_S :: QuantizationType
+ Data.Ollama.Create: Q8_0 :: QuantizationType
+ Data.Ollama.Create: [adapters] :: CreateOps -> !Maybe (Map Text Text)
+ Data.Ollama.Create: [completed] :: CreateResp -> !Maybe Int64
+ Data.Ollama.Create: [digest] :: CreateResp -> !Maybe Text
+ Data.Ollama.Create: [files] :: CreateOps -> !Maybe (Map Text Text)
+ Data.Ollama.Create: [fromModel] :: CreateOps -> !Maybe Text
+ Data.Ollama.Create: [license] :: CreateOps -> !Maybe [Text]
+ Data.Ollama.Create: [messages] :: CreateOps -> !Maybe [Message]
+ Data.Ollama.Create: [modelName] :: CreateOps -> !Text
+ Data.Ollama.Create: [parameters] :: CreateOps -> !Maybe ModelOptions
+ Data.Ollama.Create: [quantizeType] :: CreateOps -> !Maybe QuantizationType
+ Data.Ollama.Create: [status] :: CreateResp -> !Text
+ Data.Ollama.Create: [stream] :: CreateOps -> !Maybe Bool
+ Data.Ollama.Create: [systemPrompt] :: CreateOps -> !Maybe Text
+ Data.Ollama.Create: [template] :: CreateOps -> !Maybe Text
+ Data.Ollama.Create: [total] :: CreateResp -> !Maybe Int64
+ Data.Ollama.Create: data CreateOps
+ Data.Ollama.Create: data CreateResp
+ Data.Ollama.Create: data QuantizationType
+ Data.Ollama.Create: defaultCreateOps :: Text -> CreateOps
+ Data.Ollama.Create: instance Data.Aeson.Types.FromJSON.FromJSON Data.Ollama.Create.CreateResp
+ Data.Ollama.Create: instance Data.Aeson.Types.FromJSON.FromJSON Data.Ollama.Create.QuantizationType
+ Data.Ollama.Create: instance Data.Aeson.Types.ToJSON.ToJSON Data.Ollama.Create.CreateOps
+ Data.Ollama.Create: instance Data.Aeson.Types.ToJSON.ToJSON Data.Ollama.Create.QuantizationType
+ Data.Ollama.Create: instance Data.Ollama.Common.Types.HasDone Data.Ollama.Create.CreateResp
+ Data.Ollama.Create: instance GHC.Classes.Eq Data.Ollama.Create.CreateOps
+ Data.Ollama.Create: instance GHC.Classes.Eq Data.Ollama.Create.CreateResp
+ Data.Ollama.Create: instance GHC.Classes.Eq Data.Ollama.Create.QuantizationType
+ Data.Ollama.Create: instance GHC.Internal.Generics.Generic Data.Ollama.Create.CreateOps
+ Data.Ollama.Create: instance GHC.Internal.Generics.Generic Data.Ollama.Create.CreateResp
+ Data.Ollama.Create: instance GHC.Internal.Generics.Generic Data.Ollama.Create.QuantizationType
+ Data.Ollama.Create: instance GHC.Internal.Show.Show Data.Ollama.Create.CreateOps
+ Data.Ollama.Create: instance GHC.Internal.Show.Show Data.Ollama.Create.CreateResp
+ Data.Ollama.Create: instance GHC.Internal.Show.Show Data.Ollama.Create.QuantizationType
+ Data.Ollama.Delete: instance GHC.Internal.Show.Show Data.Ollama.Delete.DeleteModelReq
+ Data.Ollama.Embeddings: [dimensions] :: EmbeddingOps -> !Maybe Int
+ Data.Ollama.Embeddings: [keepAlive] :: EmbeddingOps -> !Maybe Int
+ Data.Ollama.Embeddings: [modelName] :: EmbeddingOps -> !Text
+ Data.Ollama.Embeddings: instance GHC.Internal.Show.Show Data.Ollama.Embeddings.EmbeddingOps
+ Data.Ollama.Embeddings: instance GHC.Internal.Show.Show Data.Ollama.Embeddings.EmbeddingResp
+ Data.Ollama.Generate: [doneReason] :: GenerateResponse -> !Maybe Text
+ Data.Ollama.Generate: instance GHC.Internal.Show.Show Data.Ollama.Generate.GenerateOps
+ Data.Ollama.List: instance GHC.Internal.Show.Show Data.Ollama.List.ModelInfo
+ Data.Ollama.List: instance GHC.Internal.Show.Show Data.Ollama.List.Models
+ Data.Ollama.Ps: instance GHC.Internal.Show.Show Data.Ollama.Ps.RunningModel
+ Data.Ollama.Ps: instance GHC.Internal.Show.Show Data.Ollama.Ps.RunningModels
+ Data.Ollama.Pull: instance GHC.Internal.Generics.Generic Data.Ollama.Pull.PullOps
+ Data.Ollama.Pull: instance GHC.Internal.Generics.Generic Data.Ollama.Pull.PullResp
+ Data.Ollama.Pull: instance GHC.Internal.Show.Show Data.Ollama.Pull.PullOps
+ Data.Ollama.Pull: instance GHC.Internal.Show.Show Data.Ollama.Pull.PullResp
+ Data.Ollama.Push: instance GHC.Internal.Generics.Generic Data.Ollama.Push.PushOps
+ Data.Ollama.Push: instance GHC.Internal.Generics.Generic Data.Ollama.Push.PushResp
+ Data.Ollama.Push: instance GHC.Internal.Show.Show Data.Ollama.Push.PushOps
+ Data.Ollama.Push: instance GHC.Internal.Show.Show Data.Ollama.Push.PushResp
+ Data.Ollama.Show: instance GHC.Internal.Generics.Generic Data.Ollama.Show.ShowModelOps
+ Data.Ollama.Show: instance GHC.Internal.Show.Show Data.Ollama.Show.ShowModelInfo
+ Data.Ollama.Show: instance GHC.Internal.Show.Show Data.Ollama.Show.ShowModelOps
+ Data.Ollama.Show: instance GHC.Internal.Show.Show Data.Ollama.Show.ShowModelResponse
+ Ollama: [doneReason] :: ChatResponse -> !Maybe Text
+ Ollama: checkBlobExists :: Text -> Maybe OllamaConfig -> IO (Either OllamaError Bool)
+ Ollama: createBlob :: FilePath -> Text -> Maybe OllamaConfig -> IO (Either OllamaError ())
- Data.Ollama.Chat: ChatResponse :: !Text -> !UTCTime -> !Maybe Message -> !Bool -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> ChatResponse
+ Data.Ollama.Chat: ChatResponse :: !Text -> !UTCTime -> !Maybe Message -> !Bool -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Text -> ChatResponse
- Data.Ollama.Common.Types: ChatResponse :: !Text -> !UTCTime -> !Maybe Message -> !Bool -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> ChatResponse
+ Data.Ollama.Common.Types: ChatResponse :: !Text -> !UTCTime -> !Maybe Message -> !Bool -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Text -> ChatResponse
- Data.Ollama.Common.Types: GenerateResponse :: !Text -> !UTCTime -> !Text -> !Bool -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Text -> GenerateResponse
+ Data.Ollama.Common.Types: GenerateResponse :: !Text -> !UTCTime -> !Text -> !Bool -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Text -> !Maybe Text -> GenerateResponse
- Data.Ollama.Common.Utils: commonStreamHandler :: (HasDone a, FromJSON a) => (a -> IO ()) -> IO () -> Response BodyReader -> IO (Either OllamaError a)
+ Data.Ollama.Common.Utils: commonStreamHandler :: (HasDone a, FromJSON a) => (a -> IO (), IO ()) -> Response BodyReader -> IO (Either OllamaError a)
- Data.Ollama.Create: createModel :: Text -> Maybe Text -> Maybe Bool -> Maybe FilePath -> Maybe OllamaConfig -> IO ()
+ Data.Ollama.Create: createModel :: CreateOps -> Maybe OllamaConfig -> IO ()
- Data.Ollama.Create: createModelM :: MonadIO m => Text -> Maybe Text -> Maybe Bool -> Maybe FilePath -> Maybe OllamaConfig -> m ()
+ Data.Ollama.Create: createModelM :: MonadIO m => CreateOps -> Maybe OllamaConfig -> m ()
- Data.Ollama.Embeddings: EmbeddingOps :: !Text -> ![Text] -> !Maybe Bool -> !Maybe Int -> !Maybe ModelOptions -> EmbeddingOps
+ Data.Ollama.Embeddings: EmbeddingOps :: !Text -> ![Text] -> !Maybe Bool -> !Maybe Int -> !Maybe ModelOptions -> !Maybe Int -> EmbeddingOps
- Data.Ollama.Embeddings: embeddingOps :: Text -> [Text] -> Maybe Bool -> Maybe Int -> Maybe ModelOptions -> Maybe OllamaConfig -> IO (Either OllamaError EmbeddingResp)
+ Data.Ollama.Embeddings: embeddingOps :: Text -> [Text] -> Maybe Bool -> Maybe Int -> Maybe ModelOptions -> Maybe Int -> Maybe OllamaConfig -> IO (Either OllamaError EmbeddingResp)
- Data.Ollama.Embeddings: embeddingOpsM :: MonadIO m => Text -> [Text] -> Maybe Bool -> Maybe Int -> Maybe ModelOptions -> Maybe OllamaConfig -> m (Either OllamaError EmbeddingResp)
+ Data.Ollama.Embeddings: embeddingOpsM :: MonadIO m => Text -> [Text] -> Maybe Bool -> Maybe Int -> Maybe ModelOptions -> Maybe Int -> Maybe OllamaConfig -> m (Either OllamaError EmbeddingResp)
- Data.Ollama.Generate: GenerateResponse :: !Text -> !UTCTime -> !Text -> !Bool -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Text -> GenerateResponse
+ Data.Ollama.Generate: GenerateResponse :: !Text -> !UTCTime -> !Text -> !Bool -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Text -> !Maybe Text -> GenerateResponse
- Ollama: ChatResponse :: !Text -> !UTCTime -> !Maybe Message -> !Bool -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> ChatResponse
+ Ollama: ChatResponse :: !Text -> !UTCTime -> !Maybe Message -> !Bool -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Text -> ChatResponse
- Ollama: EmbeddingOps :: !Text -> ![Text] -> !Maybe Bool -> !Maybe Int -> !Maybe ModelOptions -> EmbeddingOps
+ Ollama: EmbeddingOps :: !Text -> ![Text] -> !Maybe Bool -> !Maybe Int -> !Maybe ModelOptions -> !Maybe Int -> EmbeddingOps
- Ollama: GenerateResponse :: !Text -> !UTCTime -> !Text -> !Bool -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Text -> GenerateResponse
+ Ollama: GenerateResponse :: !Text -> !UTCTime -> !Text -> !Bool -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Int64 -> !Maybe Text -> !Maybe Text -> GenerateResponse
- Ollama: [modelName] :: RunningModel -> !Text
+ Ollama: [modelName] :: ChatOps -> !Text
- Ollama: [model] :: EmbeddingOps -> !Text
+ Ollama: [model] :: ChatResponse -> !Text
- Ollama: createModel :: Text -> Maybe Text -> Maybe Bool -> Maybe FilePath -> Maybe OllamaConfig -> IO ()
+ Ollama: createModel :: CreateOps -> Maybe OllamaConfig -> IO ()
- Ollama: createModelM :: MonadIO m => Text -> Maybe Text -> Maybe Bool -> Maybe FilePath -> Maybe OllamaConfig -> m ()
+ Ollama: createModelM :: MonadIO m => CreateOps -> Maybe OllamaConfig -> m ()
- Ollama: embeddingOps :: Text -> [Text] -> Maybe Bool -> Maybe Int -> Maybe ModelOptions -> Maybe OllamaConfig -> IO (Either OllamaError EmbeddingResp)
+ Ollama: embeddingOps :: Text -> [Text] -> Maybe Bool -> Maybe Int -> Maybe ModelOptions -> Maybe Int -> Maybe OllamaConfig -> IO (Either OllamaError EmbeddingResp)
- Ollama: embeddingOpsM :: MonadIO m => Text -> [Text] -> Maybe Bool -> Maybe Int -> Maybe ModelOptions -> Maybe OllamaConfig -> m (Either OllamaError EmbeddingResp)
+ Ollama: embeddingOpsM :: MonadIO m => Text -> [Text] -> Maybe Bool -> Maybe Int -> Maybe ModelOptions -> Maybe Int -> Maybe OllamaConfig -> m (Either OllamaError EmbeddingResp)
Files
- CHANGELOG.md +10/−0
- README.md +4/−0
- ollama-haskell.cabal +10/−2
- src/Data/Ollama/Blob.hs +176/−0
- src/Data/Ollama/Chat.hs +17/−20
- src/Data/Ollama/Common/Config.hs +2/−2
- src/Data/Ollama/Common/Error.hs +4/−3
- src/Data/Ollama/Common/SchemaBuilder.hs +4/−4
- src/Data/Ollama/Common/Types.hs +77/−60
- src/Data/Ollama/Common/Utils.hs +8/−11
- src/Data/Ollama/Conversation.hs +2/−2
- src/Data/Ollama/Copy.hs +4/−4
- src/Data/Ollama/Create.hs +156/−83
- src/Data/Ollama/Delete.hs +4/−4
- src/Data/Ollama/Embeddings.hs +23/−14
- src/Data/Ollama/Generate.hs +9/−9
- src/Data/Ollama/List.hs +51/−49
- src/Data/Ollama/Load.hs +13/−13
- src/Data/Ollama/Ps.hs +3/−3
- src/Data/Ollama/Pull.hs +4/−7
- src/Data/Ollama/Push.hs +4/−7
- src/Data/Ollama/Show.hs +3/−3
- src/Ollama.hs +10/−5
- test/Main.hs +20/−3
- test/Test/Ollama/Blob.hs +50/−0
- test/Test/Ollama/Chat.hs +37/−8
- test/Test/Ollama/Common.hs +91/−0
- test/Test/Ollama/Copy.hs +45/−0
- test/Test/Ollama/Create.hs +79/−0
- test/Test/Ollama/Delete.hs +42/−0
- test/Test/Ollama/Embedding.hs +31/−24
- test/Test/Ollama/Generate.hs +46/−5
- test/Test/Ollama/List.hs +41/−0
- test/Test/Ollama/Load.hs +53/−0
- test/Test/Ollama/Show.hs +14/−12
CHANGELOG.md view
@@ -2,6 +2,16 @@ ## Unreleased +## 0.2.1.0 -- 2025-09-24++* Added blob module for managing binary large objects (GGUF files/safetensors).+* Breaking: Added `dimensions` option in embeddings for specifying embedding dimensions.+* Breaking: Added `doneReason` field in chat and generate responses.+* Improved test suite with more comprehensive test cases.+* Added `onComplete` callback function in stream handlers for better streaming control.+* Breaking: Standardized field names across the library for consistency.+* Applied fourmolu code formatting and added hlint for better code quality.+ ## 0.2.0.0 -- 2025-06-05 * Added stack matrix to ensure lib is buildable from lts-19.33
README.md view
@@ -1,5 +1,9 @@ # 🦙 Ollama Haskell +<div style="text-align: center;">+<img src="./examples/ollama_haskell.png" alt="logo image" height="200"/>+</div>+ **`ollama-haskell`** is an unofficial Haskell client for [Ollama](https://ollama.com), inspired by [`ollama-python`](https://github.com/ollama/ollama-python). It enables interaction with locally running LLMs through the Ollama HTTP API — directly from Haskell. ---
ollama-haskell.cabal view
@@ -1,11 +1,11 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.38.0.+-- This file has been generated from package.yaml by hpack version 0.38.1. -- -- see: https://github.com/sol/hpack name: ollama-haskell-version: 0.2.0.0+version: 0.2.1.0 synopsis: Haskell client for ollama. description: Ollama client for Haskell category: Web@@ -27,6 +27,7 @@ library exposed-modules:+ Data.Ollama.Blob Data.Ollama.Chat Data.Ollama.Common.Config Data.Ollama.Common.Error@@ -74,9 +75,16 @@ type: exitcode-stdio-1.0 main-is: Main.hs other-modules:+ Test.Ollama.Blob Test.Ollama.Chat+ Test.Ollama.Common+ Test.Ollama.Copy+ Test.Ollama.Create+ Test.Ollama.Delete Test.Ollama.Embedding Test.Ollama.Generate+ Test.Ollama.List+ Test.Ollama.Load Test.Ollama.Show Paths_ollama_haskell hs-source-dirs:
+ src/Data/Ollama/Blob.hs view
@@ -0,0 +1,176 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++{- |+Module : Data.Ollama.Blob+Copyright : (c) 2025 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental+Description : Functions for managing binary large objects (blobs) in the Ollama API.++This module provides functionality for checking the existence of blobs and uploading+files as blobs to the Ollama server. Blobs are used when creating models from+GGUF files or safetensors directories.++The blob operations interact with the @\/api\/blobs@ endpoints:++- 'checkBlobExists' sends a HEAD request to check if a blob exists+- 'createBlob' uploads a file as a blob using streaming POST++Example usage:++>>> let digest = "sha256:29fdb92e57cf0827ded04ae6461b5931d01fa595843f55d36f5b275a52087dd2"+>>> exists <- checkBlobExists digest Nothing+>>> case exists of+>>> Right True -> putStrLn "Blob exists"+>>> Right False -> putStrLn "Blob not found"+>>> Left err -> putStrLn $ "Error: " ++ show err++>>> result <- createBlob "model.gguf" digest Nothing+>>> case result of+>>> Right () -> putStrLn "Blob uploaded successfully"+>>> Left err -> putStrLn $ "Upload failed: " ++ show err+-}+module Data.Ollama.Blob+ ( -- * Blob Operations+ checkBlobExists+ , createBlob+ ) where++import Control.Exception (try)+import Data.ByteString qualified as BS+import Data.Maybe (fromMaybe)+import Data.Ollama.Common.Config (OllamaConfig (..), defaultOllamaConfig)+import Data.Ollama.Common.Error+import Data.Ollama.Common.Error qualified as Error+import Data.Text (Text)+import Data.Text qualified as T+import Data.Text.Encoding qualified as TE+import Network.HTTP.Client+import Network.HTTP.Client.TLS+import Network.HTTP.Types (Status (statusCode))+import System.Directory (doesFileExist)++{- | Check if a blob exists on the Ollama server.++Sends a HEAD request to @\/api\/blobs\/sha256:\<digest\>@ to check if a blob+with the given SHA256 digest exists on the server.++Returns:+- 'Right' 'True' if the blob exists (HTTP 200 OK)+- 'Right' 'False' if the blob does not exist (HTTP 404 Not Found)+- 'Left' 'OllamaError' for other errors (network issues, invalid digest, etc.)++The digest should be a SHA256 hash string without the "sha256:" prefix.+-}+checkBlobExists ::+ -- | SHA256 digest of the blob (without "sha256:" prefix)+ Text ->+ -- | Optional 'OllamaConfig' (defaults to 'defaultOllamaConfig' if 'Nothing')+ Maybe OllamaConfig ->+ IO (Either OllamaError Bool)+checkBlobExists digest mbOllamaConfig = do+ let OllamaConfig {..} = fromMaybe defaultOllamaConfig mbOllamaConfig+ endpoint = "/api/blobs/sha256:" <> digest+ fullUrl = T.unpack $ hostUrl <> endpoint+ timeoutMicros = timeout * 1000000++ manager <- case commonManager of+ Nothing ->+ newTlsManagerWith+ tlsManagerSettings {managerResponseTimeout = responseTimeoutMicro timeoutMicros}+ Just m -> pure m++ eRequest <- try $ parseRequest fullUrl+ case eRequest of+ Left ex -> return $ Left $ Error.HttpError ex+ Right req -> do+ let request = req {method = "HEAD"}+ eResponse <- try $ httpNoBody request manager+ case eResponse of+ Left ex -> return $ Left $ Error.HttpError ex+ Right response -> do+ let status = statusCode $ responseStatus response+ case status of+ 200 -> return $ Right True+ 404 -> return $ Right False+ _ ->+ return $+ Left $+ Error.ApiError $+ "Unexpected status code: " <> T.pack (show status)++{- | Upload a file as a blob to the Ollama server.++Streams the contents of the file at the given 'FilePath' to the Ollama server+via a POST request to @\/api\/blobs\/sha256:\<digest\>@. The server will verify+that the uploaded file matches the expected SHA256 digest.++Returns:+- 'Right' @()@ if the blob was uploaded successfully (HTTP 201 Created)+- 'Left' 'OllamaError' if the upload failed++The digest should be the expected SHA256 hash of the file without the "sha256:" prefix.+If the file content doesn't match the digest, the server will return an error.++Note: This function streams the file content, so it can handle large files efficiently+without loading them entirely into memory.+-}+createBlob ::+ -- | Path to the file to upload+ FilePath ->+ -- | Expected SHA256 digest of the file (without "sha256:" prefix)+ Text ->+ -- | Optional 'OllamaConfig' (defaults to 'defaultOllamaConfig' if 'Nothing')+ Maybe OllamaConfig ->+ IO (Either OllamaError ())+createBlob filePath digest mbOllamaConfig = do+ let OllamaConfig {..} = fromMaybe defaultOllamaConfig mbOllamaConfig+ endpoint = "/api/blobs/sha256:" <> digest+ fullUrl = T.unpack $ hostUrl <> endpoint+ timeoutMicros = timeout * 1000000++ manager <- case commonManager of+ Nothing ->+ newTlsManagerWith+ tlsManagerSettings {managerResponseTimeout = responseTimeoutMicro timeoutMicros}+ Just m -> pure m++ eRequest <- try $ parseRequest fullUrl+ case eRequest of+ Left ex -> return $ Left $ Error.HttpError ex+ Right req -> do+ -- check if file exists+ fileExists <- doesFileExist filePath+ if not fileExists+ then return $ Left $ Error.ApiError "File does not exist"+ else do+ -- Create a request body from the file+ fileContent <- BS.readFile filePath+ let request =+ req+ { method = "POST"+ , requestBody = RequestBodyBS fileContent+ }++ eResponse <- try $ withResponse request manager $ \response -> do+ let status = statusCode $ responseStatus response+ case status of+ 201 -> return $ Right ()+ 400 -> do+ bodyReader <- brRead $ responseBody response+ return $+ Left $+ Error.ApiError $+ "Bad request - digest mismatch: " <> TE.decodeUtf8 bodyReader+ _ -> do+ bodyReader <- brRead $ responseBody response+ return $+ Left $+ Error.ApiError $+ "Unexpected status code " <> T.pack (show status) <> ": " <> TE.decodeUtf8 bodyReader++ case eResponse of+ Left ex -> return $ Left $ Error.HttpError ex+ Right result -> return result
src/Data/Ollama/Chat.hs view
@@ -1,5 +1,4 @@ {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-} {- | Module : Data.Ollama.Chat@@ -112,7 +111,7 @@ Message {role = System, content = "You are a helpful assistant.", images = Nothing, tool_calls = Nothing, thinking = Nothing} -} systemMessage :: Text -> Message-systemMessage c = genMessage System c+systemMessage = genMessage System {- | Creates a 'Message' with the 'User' role. @@ -122,7 +121,7 @@ Message {role = User, content = "What's 2+2?", images = Nothing, tool_calls = Nothing, thinking = Nothing} -} userMessage :: Text -> Message-userMessage c = genMessage User c+userMessage = genMessage User {- | Creates a 'Message' with the 'Assistant' role. @@ -132,7 +131,7 @@ Message {role = Assistant, content = "2+2 equals 4.", images = Nothing, tool_calls = Nothing, thinking = Nothing} -} assistantMessage :: Text -> Message-assistantMessage c = genMessage Assistant c+assistantMessage = genMessage Assistant {- | Creates a 'Message' with the 'Tool' role. @@ -142,18 +141,18 @@ Message {role = Tool, content = "Tool output: success", images = Nothing, tool_calls = Nothing, thinking = Nothing} -} toolMessage :: Text -> Message-toolMessage c = genMessage Tool c+toolMessage = genMessage Tool {- | Validates 'ChatOps' to ensure required fields are non-empty. Checks that the 'chatModelName' is not empty and that no 'Message' in 'messages' has empty content. Returns 'Right' with the validated 'ChatOps' or 'Left' with an 'OllamaError' if validation fails.------ @since 0.2.0.0++@since 0.2.0.0 -} validateChatOps :: ChatOps -> Either OllamaError ChatOps validateChatOps ops- | T.null (chatModelName ops) = Left $ InvalidRequest "Chat model name cannot be empty"+ | T.null (modelName ops) = Left $ InvalidRequest "Chat model name cannot be empty" | any (T.null . content) (messages ops) = Left $ InvalidRequest "Messages cannot have empty content" | otherwise = Right ops@@ -164,7 +163,7 @@ for tools, response format, streaming, timeout, and model options. -} data ChatOps = ChatOps- { chatModelName :: !Text+ { modelName :: !Text -- ^ The name of the chat model to be used (e.g., "gemma3"). , messages :: !(NonEmpty Message) -- ^ A non-empty list of messages forming the conversation context.@@ -175,7 +174,7 @@ -- -- @since 0.1.3.0 , stream :: !(Maybe (ChatResponse -> IO (), IO ()))- -- ^ Optional streaming functions: the first handles each response chunk, the second flushes the stream.+ -- ^ Optional callback function to be called with each incoming response. , keepAlive :: !(Maybe Int) -- ^ Optional override for the response timeout in minutes (default: 15 minutes). , options :: !(Maybe ModelOptions)@@ -191,7 +190,7 @@ instance Show ChatOps where show ( ChatOps- { chatModelName = m+ { modelName = m , messages = ms , tools = t , format = f@@ -218,14 +217,14 @@ instance Eq ChatOps where (==) a b =- chatModelName a == chatModelName b+ modelName a == modelName b && messages a == messages b && tools a == tools b && format a == format b && keepAlive a == keepAlive b instance ToJSON ChatOps where- toJSON (ChatOps model_ messages_ tools_ format_ stream_ keepAlive_ options think_) =+ toJSON (ChatOps model_ messages_ tools_ format_ stream_ keepAlive_ options_ think_) = object [ "model" .= model_ , "messages" .= messages_@@ -233,7 +232,7 @@ , "format" .= format_ , "stream" .= if isNothing stream_ then Just False else Just True , "keep_alive" .= keepAlive_- , "options" .= options+ , "options" .= options_ , "think" .= think_ ] @@ -251,7 +250,7 @@ defaultChatOps :: ChatOps defaultChatOps = ChatOps- { chatModelName = "gemma3"+ { modelName = "gemma3" , messages = userMessage "What is 2+2?" :| [] , tools = Nothing , format = Nothing@@ -263,7 +262,7 @@ {- | Sends a chat request to the Ollama API. -Validates the 'ChatOps' configuration and sends a POST request to the "/api//chat" endpoint.+Validates the 'ChatOps' configuration and sends a POST request to the @\/api\/chat@ endpoint. Supports both streaming and non-streaming responses based on the 'stream' field in 'ChatOps'. Returns an 'Either' containing an 'OllamaError' on failure or a 'ChatResponse' on success. @@ -277,11 +276,9 @@ chat ops mbConfig = case validateChatOps ops of Left err -> return $ Left err- Right _ -> withOllamaRequest "/api//chat" "POST" (Just ops) mbConfig handler+ Right _ -> withOllamaRequest "/api/chat" "POST" (Just ops) mbConfig handler where- handler = case stream ops of- Nothing -> commonNonStreamingHandler- Just (sc, fl) -> commonStreamHandler sc fl+ handler = maybe commonNonStreamingHandler commonStreamHandler (stream ops) {- | MonadIO version of 'chat' for use in monadic contexts.
src/Data/Ollama/Common/Config.hs view
@@ -38,8 +38,8 @@ {- | Configuration for the Ollama client. Used across all requests to customize behavior such as timeouts, retries, custom HTTP manager, and lifecycle hooks.------ @since 0.2.0.0++@since 0.2.0.0 -} data OllamaConfig = OllamaConfig { hostUrl :: Text
src/Data/Ollama/Common/Error.hs view
@@ -34,9 +34,10 @@ -- | Type alias for the value that failed to decode. type DecodingFailedValue = String --- | Represents all possible errors that may occur when using the Ollama client.------ @since 0.2.0.0+{- | Represents all possible errors that may occur when using the Ollama client.++@since 0.2.0.0+-} data OllamaError = -- | Low-level HTTP exception (connection failure, etc.) HttpError HttpException
src/Data/Ollama/Common/SchemaBuilder.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE OverloadedStrings #-} @@ -106,9 +105,10 @@ toJSON (Property (JObject schema)) = toJSON schema toJSON (Property typ) = object ["type" .= typ] --- | Complete schema representation.------ @since 0.2.0.0+{- | Complete schema representation.++@since 0.2.0.0+-} data Schema = Schema { schemaProperties :: HM.Map Text Property , schemaRequired :: [Text]
src/Data/Ollama/Common/Types.hs view
@@ -129,6 +129,11 @@ -- ^ Thinking of reasoning models; if think is set to true -- -- @since 0.2.0.0+ , doneReason :: !(Maybe Text)+ -- ^ Reason why the generation process completed (e.g., "stop", "length", "cancel").+ -- Available when the Ollama server provides completion reason information.+ --+ -- @since 0.2.1.0 } deriving (Show, Eq) @@ -146,6 +151,7 @@ <*> v .:? "eval_count" <*> v .:? "eval_duration" <*> v .:? "thinking"+ <*> v .:? "done_reason" -- | Enumerated roles that can participate in a chat. data Role = System | User | Assistant | Tool@@ -205,6 +211,11 @@ -- ^ Optional count of evaluations during the chat process. , evalDuration :: !(Maybe Int64) -- ^ Optional duration in milliseconds for evaluations during the chat process.+ , doneReason :: !(Maybe Text)+ -- ^ Reason why the chat process completed (e.g., "stop", "length", "cancel").+ -- Available when the Ollama server provides completion reason information.+ --+ -- @since 0.2.1.0 } deriving (Show, Eq) @@ -221,6 +232,7 @@ <*> v .:? "prompt_eval_duration" <*> v .:? "eval_count" <*> v .:? "eval_duration"+ <*> v .:? "done_reason" -- | A workaround to use done field within commonStreamHandler class HasDone a where@@ -232,54 +244,54 @@ instance HasDone ChatResponse where getDone ChatResponse {..} = done --- | Optional model tuning parameters that influence generation behavior.------ @since 0.2.0.0+{- | Optional model tuning parameters that influence generation behavior.++@since 0.2.0.0+-} data ModelOptions = ModelOptions { numKeep :: Maybe Int- , -- \^ Number of tokens to keep from the previous context.- seed :: Maybe Int- , -- \^ Random seed for reproducibility.- numPredict :: Maybe Int- , -- \^ Maximum number of tokens to predict.- topK :: Maybe Int- , -- \^ Top-K sampling parameter.- topP :: Maybe Double- , -- \^ Top-P (nucleus) sampling parameter.- minP :: Maybe Double- , -- \^ Minimum probability for nucleus sampling.- typicalP :: Maybe Double- , -- \^ Typical sampling probability.- repeatLastN :: Maybe Int- , -- \^ Number of tokens to consider for repetition penalty.- temperature :: Maybe Double- , -- \^ Sampling temperature. Higher = more randomness.- repeatPenalty :: Maybe Double- , -- \^ Penalty for repeating the same tokens.- presencePenalty :: Maybe Double- , -- \^ Penalty for introducing new tokens.- frequencyPenalty :: Maybe Double- , -- \^ Penalty for frequent tokens.- penalizeNewline :: Maybe Bool- , -- \^ Whether to penalize newline tokens.- stop :: Maybe [Text]- , -- \^ List of stop sequences to end generation.- numa :: Maybe Bool- , -- \^ Whether to enable NUMA-aware optimizations.- numCtx :: Maybe Int- , -- \^ Number of context tokens.- numBatch :: Maybe Int- , -- \^ Batch size used during generation.- numGpu :: Maybe Int- , -- \^ Number of GPUs to use.- mainGpu :: Maybe Int- , -- \^ Index of the primary GPU to use.- useMmap :: Maybe Bool- , -- \^ Whether to memory-map the model.- numThread :: Maybe Int+ -- ^ Number of tokens to keep from the previous context.+ , seed :: Maybe Int+ -- ^ Random seed for reproducibility.+ , numPredict :: Maybe Int+ -- ^ Maximum number of tokens to predict.+ , topK :: Maybe Int+ -- ^ Top-K sampling parameter.+ , topP :: Maybe Double+ -- ^ Top-P (nucleus) sampling parameter.+ , minP :: Maybe Double+ -- ^ Minimum probability for nucleus sampling.+ , typicalP :: Maybe Double+ -- ^ Typical sampling probability.+ , repeatLastN :: Maybe Int+ -- ^ Number of tokens to consider for repetition penalty.+ , temperature :: Maybe Double+ -- ^ Sampling temperature. Higher = more randomness.+ , repeatPenalty :: Maybe Double+ -- ^ Penalty for repeating the same tokens.+ , presencePenalty :: Maybe Double+ -- ^ Penalty for introducing new tokens.+ , frequencyPenalty :: Maybe Double+ -- ^ Penalty for frequent tokens.+ , penalizeNewline :: Maybe Bool+ -- ^ Whether to penalize newline tokens.+ , stop :: Maybe [Text]+ -- ^ List of stop sequences to end generation.+ , numa :: Maybe Bool+ -- ^ Whether to enable NUMA-aware optimizations.+ , numCtx :: Maybe Int+ -- ^ Number of context tokens.+ , numBatch :: Maybe Int+ -- ^ Batch size used during generation.+ , numGpu :: Maybe Int+ -- ^ Number of GPUs to use.+ , mainGpu :: Maybe Int+ -- ^ Index of the primary GPU to use.+ , useMmap :: Maybe Bool+ -- ^ Whether to memory-map the model.+ , numThread :: Maybe Int+ -- ^ Number of threads to use for inference. }- -- \^ Number of threads to use for inference.- deriving (Show, Eq) -- | Custom ToJSON instance for Options@@ -318,9 +330,10 @@ parseJSON = withObject "version" $ \v -> do Version <$> v .: "version" --- | Represents a tool that can be used in the conversation.------ @since 0.2.0.0+{- | Represents a tool that can be used in the conversation.++@since 0.2.0.0+-} data InputTool = InputTool { toolType :: Text -- ^ The type of the tool@@ -342,9 +355,10 @@ <$> v .: "type" <*> v .: "function" --- | Represents a function that can be called by the model.------ @since 0.2.0.0+{- | Represents a function that can be called by the model.++@since 0.2.0.0+-} data FunctionDef = FunctionDef { functionName :: Text -- ^ The name of the function@@ -374,9 +388,10 @@ <*> v .:? "parameters" <*> v .:? "strict" --- | Parameters definition for a function call used in structured output or tool calls.------ @since 0.2.0.0+{- | Parameters definition for a function call used in structured output or tool calls.++@since 0.2.0.0+-} data FunctionParameters = FunctionParameters { parameterType :: Text -- ^ Type of the parameter (usually "object").@@ -406,18 +421,20 @@ <*> v .: "required" <*> v .: "additionalProperties" --- | A single tool call returned from the model, containing the function to be invoked.------ @since 0.2.0.0+{- | A single tool call returned from the model, containing the function to be invoked.++@since 0.2.0.0+-} newtype ToolCall = ToolCall { outputFunction :: OutputFunction -- ^ The function the model intends to call, with arguments. } deriving (Show, Eq) --- | Output representation of a function to be called, including its name and arguments.------ @since 0.2.0.0+{- | Output representation of a function to be called, including its name and arguments.++@since 0.2.0.0+-} data OutputFunction = OutputFunction { outputFunctionName :: Text -- ^ The name of the function to invoke.
src/Data/Ollama/Common/Utils.hs view
@@ -155,7 +155,7 @@ req { method = reqMethod , requestBody =- maybe mempty (\x -> RequestBodyLBS $ encode x) mbPayload+ maybe mempty (RequestBodyLBS . encode) mbPayload } retryCnt = fromMaybe 0 retryCount retryDelay_ = fromMaybe 1 retryDelay@@ -214,12 +214,10 @@ commonStreamHandler :: (HasDone a, FromJSON a) => -- | Function to handle each decoded chunk- (a -> IO ()) ->- -- | Function to flush after each chunk- IO () ->+ (a -> IO (), IO ()) -> Response BodyReader -> IO (Either OllamaError a)-commonStreamHandler sendChunk flush resp = go mempty+commonStreamHandler (sendChunk, onComplete) resp = go mempty where go acc = do bs <- brRead $ responseBody resp@@ -234,8 +232,7 @@ Left err -> return $ Left $ Error.DecodeError err (show acc) Right res -> do sendChunk res- flush- if getDone res then return (Right res) else go (acc <> bs)+ if getDone res then onComplete >> return (Right res) else go (acc <> bs) {- | Handles non-JSON API responses. @@ -287,19 +284,19 @@ {- | Retrieves the Ollama server version. -Sends a GET request to the "/api//version" endpoint and returns the server version+Sends a GET request to the @\/api\/version@ endpoint and returns the server version as a 'Version' wrapped in an 'Either' 'OllamaError'. Example: >>> getVersion------ @since 0.2.0.0++@since 0.2.0.0 -} getVersion :: IO (Either OllamaError Version) getVersion = do withOllamaRequest- "/api//version"+ "/api/version" "GET" (Nothing :: Maybe Value) Nothing
src/Data/Ollama/Conversation.hs view
@@ -78,8 +78,8 @@ {- | Typeclass defining operations for storing and managing conversations. Provides methods for saving, loading, listing, and deleting conversations in a monadic context.------ @since 0.2.0.0++@since 0.2.0.0 -} class Monad m => ConversationStore m where -- | Saves a conversation to the store.
src/Data/Ollama/Copy.hs view
@@ -13,7 +13,7 @@ This module provides functions to copy a model from a source name to a destination name using the Ollama API. It includes both an IO-based function ('copyModel') and a monadic version ('copyModelM') for use in-'MonadIO' contexts. The copy operation is performed via a POST request to the "/api//copy" endpoint.+'MonadIO' contexts. The copy operation is performed via a POST request to the @\/api\/copy@ endpoint. Example: @@ -45,7 +45,7 @@ {- | Copies a model from a source name to a destination name. -Sends a POST request to the "/api//copy" endpoint with the source and destination model names.+Sends a POST request to the @\/api\/copy@ endpoint with the source and destination model names. Returns 'Right ()' on success or 'Left' with an 'OllamaError' on failure. Example: @@ -66,11 +66,11 @@ mbConfig = do let reqBody = CopyModelOps {source = source_, destination = destination_} withOllamaRequest- "/api//copy"+ "/api/copy" "POST" (Just reqBody) mbConfig- (fmap ((const ()) <$>) . nonJsonHandler)+ (fmap (const () <$>) . nonJsonHandler) {- | MonadIO version of 'copyModel' for use in monadic contexts.
src/Data/Ollama/Create.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-}@@ -10,121 +11,196 @@ Stability : experimental Description : Functionality for creating new models in the Ollama client. -This module provides functions to create a new model in the Ollama API using either a model file-content or a file path. It includes both an IO-based function ('createModel') and a monadic version-('createModelM') for use in 'MonadIO' contexts. The create operation is performed via a POST request-to the "/api//pull" endpoint, with streaming support for progress updates.+This module provides functions to create new models in the Ollama API. Models can be created from: -Note: If both 'modelFile' and 'path' are provided, 'modelFile' takes precedence.+- Another existing model (using 'from' parameter)+- A safetensors directory (using 'files' parameter)+- A GGUF file (using 'files' parameter) -Example:+The create operation is performed via a POST request to the @\/api\/create@ endpoint, with streaming+support for progress updates. The module supports model quantization, custom templates, system prompts,+parameters, and LORA adapters. ->>> createModel "myModel" (Just "FROM llama3\nPARAMETER temperature 0.8") (Just True) Nothing Nothing+Example creating from existing model:++>>> let ops = defaultCreateOps { modelName = "mario", fromModel = Just "llama3.2", systemPrompt = Just "You are Mario from Super Mario Bros." }+>>> createModel ops Nothing Creating model...-Completed+Success++Example quantizing a model:++>>> let ops = defaultCreateOps { modelName = "llama3.2:quantized", fromModel = Just "llama3.2:3b-instruct-fp16", quantizeType = Just Q4_K_M }+>>> createModel ops Nothing+Quantizing model...+Success -} module Data.Ollama.Create ( -- * Create Model API createModel , createModelM++ -- * Configuration Types+ , CreateOps (..)+ , defaultCreateOps++ -- * Response Types+ , CreateResp (..)++ -- * Quantization Types+ , QuantizationType (..) ) where import Control.Monad (void) import Control.Monad.IO.Class (MonadIO (liftIO)) import Data.Aeson+import Data.Map.Strict (Map)+import Data.Maybe (catMaybes) import Data.Ollama.Common.Config (OllamaConfig)-import Data.Ollama.Common.Types (HasDone (getDone))+import Data.Ollama.Common.Types (HasDone (getDone), Message, ModelOptions) import Data.Ollama.Common.Utils as CU import Data.Text (Text)+import GHC.Generics (Generic)+import GHC.Int (Int64) +-- | Quantization types supported by Ollama.+data QuantizationType+ = -- | Recommended quantization type+ Q4_K_M+ | -- | Alternative quantization type+ Q4_K_S+ | -- | Recommended quantization type+ Q8_0+ deriving (Show, Eq, Generic)++instance ToJSON QuantizationType where+ toJSON Q4_K_M = String "q4_K_M"+ toJSON Q4_K_S = String "q4_K_S"+ toJSON Q8_0 = String "q8_0"++instance FromJSON QuantizationType where+ parseJSON = withText "QuantizationType" $ \t ->+ case t of+ "q4_K_M" -> pure Q4_K_M+ "q4_K_S" -> pure Q4_K_S+ "q8_0" -> pure Q8_0+ _ -> fail $ "Invalid QuantizationType: " <> show t+ -- | Configuration for creating a new model.-data CreateModelOps = CreateModelOps- { name :: !Text+data CreateOps = CreateOps+ { modelName :: !Text -- ^ The name of the model to create.- , modelFile :: !(Maybe Text)- -- ^ Optional model file content (e.g., Modelfile text). Takes precedence over 'path'.+ , fromModel :: !(Maybe Text)+ -- ^ Optional name of an existing model to create the new model from.+ , files :: !(Maybe (Map Text Text))+ -- ^ Optional dictionary of file names to SHA256 digests of blobs to create the model from.+ , adapters :: !(Maybe (Map Text Text))+ -- ^ Optional dictionary of file names to SHA256 digests of blobs for LORA adapters.+ , template :: !(Maybe Text)+ -- ^ Optional prompt template for the model.+ , license :: !(Maybe [Text])+ -- ^ Optional list of strings containing the license(s) for the model.+ , systemPrompt :: !(Maybe Text)+ -- ^ Optional system prompt for the model.+ , parameters :: !(Maybe ModelOptions)+ -- ^ Optional parameters for the model.+ , messages :: !(Maybe [Message])+ -- ^ Optional list of message objects used to create a conversation. , stream :: !(Maybe Bool) -- ^ Optional flag to enable streaming progress updates.- , path :: !(Maybe FilePath)- -- ^ Optional file path to a Modelfile.+ , quantizeType :: !(Maybe QuantizationType)+ -- ^ Optional quantization type for quantizing a non-quantized model. }- deriving (Show, Eq)+ deriving (Show, Eq, Generic) --- | Response type for model creation.-newtype CreateModelResp- = -- | The status of the create operation (e.g., "success").- CreateModelResp {status :: Text}- deriving (Show, Eq)+-- | Default configuration for creating a model.+defaultCreateOps :: Text -> CreateOps+defaultCreateOps name =+ CreateOps+ { modelName = name+ , fromModel = Nothing+ , files = Nothing+ , adapters = Nothing+ , template = Nothing+ , license = Nothing+ , systemPrompt = Nothing+ , parameters = Nothing+ , messages = Nothing+ , stream = Nothing+ , quantizeType = Nothing+ } -instance HasDone CreateModelResp where- getDone CreateModelResp {..} = status /= "success"+-- | Response type for model creation operations.+data CreateResp = CreateResp+ { status :: !Text+ -- ^ The status of the create operation (e.g., "success", "reading model metadata").+ , digest :: !(Maybe Text)+ -- ^ Optional digest (hash) of the model layer being processed.+ , total :: !(Maybe Int64)+ -- ^ Optional total size in bytes for quantization operations.+ , completed :: !(Maybe Int64)+ -- ^ Optional number of bytes completed for quantization operations.+ }+ deriving (Show, Eq, Generic) -instance ToJSON CreateModelOps where- toJSON- ( CreateModelOps- name_- modelFile_- stream_- path_- ) =- object- [ "name" .= name_- , "modelfile" .= modelFile_- , "stream" .= stream_- , "path" .= path_+instance HasDone CreateResp where+ getDone CreateResp {..} = status == "success"++instance ToJSON CreateOps where+ toJSON CreateOps {..} =+ object $+ catMaybes+ [ Just ("model" .= modelName)+ , ("from" .=) <$> fromModel+ , ("files" .=) <$> files+ , ("adapters" .=) <$> adapters+ , ("template" .=) <$> template+ , ("license" .=) <$> license+ , ("system" .=) <$> systemPrompt+ , ("parameters" .=) <$> parameters+ , ("messages" .=) <$> messages+ , ("stream" .=) <$> stream+ , ("quantize" .=) <$> quantizeType ] -instance FromJSON CreateModelResp where- parseJSON = withObject "CreateModelResp" $ \v ->- CreateModelResp+instance FromJSON CreateResp where+ parseJSON = withObject "CreateResp" $ \v ->+ CreateResp <$> v .: "status"+ <*> v .:? "digest"+ <*> v .:? "total"+ <*> v .:? "completed" -{- | Creates a new model using either model file content or a file path.+{- | Creates a new model according to the provided configuration. -Sends a POST request to the "/api//pull" endpoint to create a model with the specified name.-The model can be defined either by 'modelFile' (Modelfile content as text) or 'path' (file path to a Modelfile).-If both are provided, 'modelFile' is used. Supports streaming progress updates if 'stream' is 'Just True'.+Sends a POST request to the @\/api\/create@ endpoint to create a model. The model can be created from:++- Another existing model (specify 'fromModel')+- A safetensors directory (specify 'files' with file name to SHA256 digest mappings)+- A GGUF file (specify 'files' with file name to SHA256 digest mapping)++Supports quantization, custom templates, system prompts, parameters, and LORA adapters. Prints progress messages to the console during creation. -} createModel ::- -- | Model name- Text ->- -- | Optional model file content- Maybe Text ->- -- | Optional streaming flag- Maybe Bool ->- -- | Optional file path to a Modelfile- Maybe FilePath ->+ -- | Model creation configuration+ CreateOps -> -- | Optional 'OllamaConfig' (defaults to 'defaultOllamaConfig' if 'Nothing') Maybe OllamaConfig -> IO ()-createModel- modelName- modelFile_- stream_- path_- mbConfig =- void $- withOllamaRequest- "/api//pull"- "POST"- ( Just $- CreateModelOps- { name = modelName- , modelFile = modelFile_- , stream = stream_- , path = path_- }- )- mbConfig- (commonStreamHandler onToken onComplete)- where- onToken :: CreateModelResp -> IO ()- onToken _ = do- putStrLn "Creating model..."-- onComplete :: IO ()- onComplete = putStrLn "Completed"+createModel createOps mbConfig =+ void $+ withOllamaRequest+ "/api/create"+ "POST"+ (Just createOps)+ mbConfig+ (commonStreamHandler (onToken, pure ()))+ where+ onToken :: CreateResp -> IO ()+ onToken CreateResp {..} = case (total, completed) of+ (Just t, Just c) -> putStrLn $ "Progress: " <> show c <> "/" <> show t <> " bytes"+ _ -> putStrLn $ "Status: " <> show status {- | MonadIO version of 'createModel' for use in monadic contexts. @@ -132,10 +208,7 @@ -} createModelM :: MonadIO m =>- Text ->- Maybe Text ->- Maybe Bool ->- Maybe FilePath ->+ CreateOps -> Maybe OllamaConfig -> m ()-createModelM m mf s p mbCfg = liftIO $ createModel m mf s p mbCfg+createModelM createOps mbCfg = liftIO $ createModel createOps mbCfg
src/Data/Ollama/Delete.hs view
@@ -13,7 +13,7 @@ This module provides functions to delete a model from the Ollama server using its name. It includes both an IO-based function ('deleteModel') and a monadic version ('deleteModelM') for use in-'MonadIO' contexts. The delete operation is performed via a DELETE request to the "/api//delete" endpoint.+'MonadIO' contexts. The delete operation is performed via a DELETE request to the @\/api\/delete@ endpoint. Example: @@ -44,7 +44,7 @@ {- | Deletes a model from the Ollama server. -Sends a DELETE request to the "/api//delete" endpoint with the specified model name.+Sends a DELETE request to the "/api/delete" endpoint with the specified model name. Returns 'Right ()' on success or 'Left' with an 'OllamaError' on failure. -} deleteModel ::@@ -56,11 +56,11 @@ deleteModel modelName mbConfig = do let reqBody = DeleteModelReq {name = modelName} withOllamaRequest- "/api//delete"+ "/api/delete" "DELETE" (Just reqBody) mbConfig- (fmap ((const ()) <$>) . nonJsonHandler)+ (fmap (const () <$>) . nonJsonHandler) {- | MonadIO version of 'deleteModel' for use in monadic contexts.
src/Data/Ollama/Embeddings.hs view
@@ -56,27 +56,32 @@ defaultEmbeddingOps :: EmbeddingOps defaultEmbeddingOps = EmbeddingOps- { model = "llama3.2"+ { modelName = "nomic-embed-text" , input = [] , truncateInput = Nothing- , keepAliveEmbed = Nothing+ , keepAlive = Nothing , modelOptions = Nothing+ , dimensions = Nothing } -- | Configuration for an embedding request. data EmbeddingOps = EmbeddingOps- { model :: !Text+ { modelName :: !Text -- ^ The name of the model to use for generating embeddings (e.g., "llama3.2"). , input :: ![Text] -- ^ List of input texts to generate embeddings for. , truncateInput :: !(Maybe Bool) -- ^ Optional flag to truncate input if it exceeds model limits.- , keepAliveEmbed :: !(Maybe Int)+ , keepAlive :: !(Maybe Int) -- ^ Optional override for the keep-alive timeout in minutes. , modelOptions :: !(Maybe ModelOptions) -- ^ Optional model parameters (e.g., temperature) as specified in the Modelfile. -- -- @since 0.2.0.0+ , dimensions :: !(Maybe Int)+ -- ^ number of dimensions for the embedding+ --+ -- @since 0.2.1.0 } deriving (Show, Eq) @@ -96,18 +101,19 @@ <*> v .: "embeddings" instance ToJSON EmbeddingOps where- toJSON (EmbeddingOps model_ input_ truncate' keepAlive_ ops) =+ toJSON (EmbeddingOps model_ input_ truncate' keepAlive_ ops dimensions_) = object [ "model" .= model_ , "input" .= input_ , "truncate" .= truncate' , "keep_alive" .= keepAlive_ , "options" .= ops+ , "dimensions" .= dimensions_ ] {- | Generates embeddings for a list of input texts with full configuration. -Sends a POST request to the "/api//embed" endpoint to generate embeddings for the provided inputs.+Sends a POST request to the @\/api\/embed@ endpoint to generate embeddings for the provided inputs. Allows customization of truncation, keep-alive settings, model options, and Ollama configuration. Returns 'Right' with an 'EmbeddingResp' on success or 'Left' with an 'OllamaError' on failure. -}@@ -123,19 +129,21 @@ -- | Optional model options Maybe ModelOptions -> -- | Optional 'OllamaConfig' (defaults to 'defaultOllamaConfig' if 'Nothing')+ Maybe Int -> Maybe OllamaConfig -> IO (Either OllamaError EmbeddingResp)-embeddingOps modelName input_ mTruncate mKeepAlive mbOptions mbConfig = do+embeddingOps modelName_ input_ mTruncate mKeepAlive mbOptions mbDimensions mbConfig = do withOllamaRequest- "/api//embed"+ "/api/embed" "POST" ( Just $ EmbeddingOps- { model = modelName+ { modelName = modelName_ , input = input_ , truncateInput = mTruncate- , keepAliveEmbed = mKeepAlive+ , keepAlive = mKeepAlive , modelOptions = mbOptions+ , dimensions = mbDimensions } ) mbConfig@@ -152,8 +160,8 @@ -- | List of input texts [Text] -> IO (Either OllamaError EmbeddingResp)-embedding modelName input_ =- embeddingOps modelName input_ Nothing Nothing Nothing Nothing+embedding modelName_ input_ =+ embeddingOps modelName_ input_ Nothing Nothing Nothing Nothing Nothing {- | MonadIO version of 'embedding' for use in monadic contexts. @@ -174,7 +182,8 @@ Maybe Bool -> Maybe Int -> Maybe ModelOptions ->+ Maybe Int -> Maybe OllamaConfig -> m (Either OllamaError EmbeddingResp)-embeddingOpsM m ip mbTruncate mbKeepAlive mbOptions mbCfg =- liftIO $ embeddingOps m ip mbTruncate mbKeepAlive mbOptions mbCfg+embeddingOpsM m ip mbTruncate mbKeepAlive mbOptions mbDimensions mbCfg =+ liftIO $ embeddingOps m ip mbTruncate mbKeepAlive mbOptions mbDimensions mbCfg
src/Data/Ollama/Generate.hs view
@@ -66,8 +66,8 @@ >>> validateGenerateOps defaultGenerateOps Left (InvalidRequest "Prompt cannot be empty")------ @since 0.2.0.0++@since 0.2.0.0 -} validateGenerateOps :: GenerateOps -> Either OllamaError GenerateOps validateGenerateOps ops@@ -94,7 +94,7 @@ , template :: !(Maybe Text) -- ^ Optional template to format the response. , stream :: !(Maybe (GenerateResponse -> IO (), IO ()))- -- ^ Optional streaming functions: the first handles each response chunk, the second flushes the stream.+ -- ^ Optional callback function to be called with each incoming response. , raw :: !(Maybe Bool) -- ^ Optional flag to return the raw response. , keepAlive :: !(Maybe Int)@@ -211,7 +211,7 @@ {- | Generates text using the specified model and configuration. -Validates the 'GenerateOps' configuration and sends a POST request to the "/api//generate" endpoint.+Validates the 'GenerateOps' configuration and sends a POST request to the @\/api\/generate@ endpoint. Supports both streaming and non-streaming responses based on the 'stream' field in 'GenerateOps'. Returns 'Right' with a 'GenerateResponse' on success or 'Left' with an 'OllamaError' on failure. @@ -225,11 +225,9 @@ generate ops mbConfig = case validateGenerateOps ops of Left err -> pure $ Left err- Right _ -> withOllamaRequest "/api//generate" "POST" (Just ops) mbConfig handler+ Right _ -> withOllamaRequest "/api/generate" "POST" (Just ops) mbConfig handler where- handler = case stream ops of- Nothing -> commonNonStreamingHandler- Just (sc, fl) -> commonStreamHandler sc fl+ handler = maybe commonNonStreamingHandler commonStreamHandler (stream ops) {- | MonadIO version of 'generate' for use in monadic contexts. @@ -244,5 +242,7 @@ -} generateM :: MonadIO m =>- GenerateOps -> Maybe OllamaConfig -> m (Either OllamaError GenerateResponse)+ GenerateOps ->+ Maybe OllamaConfig ->+ m (Either OllamaError GenerateResponse) generateM ops mbCfg = liftIO $ generate ops mbCfg
src/Data/Ollama/List.hs view
@@ -1,31 +1,31 @@--- |--- Module : Data.Ollama.List--- Copyright : (c) 2025 Tushar Adhatrao--- License : MIT--- Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>--- Stability : experimental--- Description : Functionality for listing available models in the Ollama client.------ This module provides functions to retrieve a list of models available on the Ollama server.--- It includes both an IO-based function ('list') and a monadic version ('listM') for use in--- 'MonadIO' contexts. The list operation is performed via a GET request to the "/api//tags" endpoint,--- returning a 'Models' type containing a list of 'ModelInfo' records with details about each model.------ Example:------ >>> list Nothing--- Right (Models [ModelInfo ...])--- {-# LANGUAGE OverloadedStrings #-} +{- |+Module : Data.Ollama.List+Copyright : (c) 2025 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental+Description : Functionality for listing available models in the Ollama client.++This module provides functions to retrieve a list of models available on the Ollama server.+It includes both an IO-based function ('list') and a monadic version ('listM') for use in+'MonadIO' contexts. The list operation is performed via a GET request to the @\/api\/tags@ endpoint,+returning a 'Models' type containing a list of 'ModelInfo' records with details about each model.++Example:++>>> list Nothing+Right (Models [ModelInfo ...])+-} module Data.Ollama.List ( -- * List Models API- list,- listM,+ list+ , listM -- * Model Types- Models (..),- ModelInfo (..)+ , Models (..)+ , ModelInfo (..) ) where import Control.Monad.IO.Class (MonadIO (liftIO))@@ -39,25 +39,25 @@ import GHC.Int (Int64) -- | A wrapper type containing a list of available models.----newtype Models = Models [ModelInfo]- -- ^ List of 'ModelInfo' records describing available models.+newtype Models+ = -- | List of 'ModelInfo' records describing available models.+ Models [ModelInfo] deriving (Eq, Show) -- | Details about a specific model.--- data ModelInfo = ModelInfo { name :: !Text- -- ^ The name of the model.+ -- ^ The name of the model. , modifiedAt :: !UTCTime- -- ^ The timestamp when the model was last modified.+ -- ^ The timestamp when the model was last modified. , size :: !Int64- -- ^ The size of the model in bytes.+ -- ^ The size of the model in bytes. , digest :: !Text- -- ^ The digest (hash) of the model.+ -- ^ The digest (hash) of the model. , details :: !ModelDetails- -- ^ Additional details about the model (e.g., format, family, parameters).- } deriving (Eq, Show)+ -- ^ Additional details about the model (e.g., format, family, parameters).+ }+ deriving (Eq, Show) -- | JSON parsing instance for 'Models'. instance FromJSON Models where@@ -73,31 +73,33 @@ <*> v .: "digest" <*> v .: "details" --- | Retrieves a list of available models from the Ollama server.------ Sends a GET request to the "/api//tags" endpoint to fetch the list of models.--- Returns 'Right' with a 'Models' containing the list of 'ModelInfo' on success,--- or 'Left' with an 'OllamaError' on failure.+{- | Retrieves a list of available models from the Ollama server.++Sends a GET request to the @\/api\/tags@ endpoint to fetch the list of models.+Returns 'Right' with a 'Models' containing the list of 'ModelInfo' on success,+or 'Left' with an 'OllamaError' on failure.+-} list ::- Maybe OllamaConfig -> -- ^ Optional 'OllamaConfig' (defaults to 'defaultOllamaConfig' if 'Nothing')+ -- | Optional 'OllamaConfig' (defaults to 'defaultOllamaConfig' if 'Nothing')+ Maybe OllamaConfig -> IO (Either OllamaError Models) list mbConfig = do withOllamaRequest- "/api//tags"+ "/api/tags" "GET" (Nothing :: Maybe Value) mbConfig commonNonStreamingHandler --- | MonadIO version of 'list' for use in monadic contexts.------ Lifts the 'list' function into a 'MonadIO' context, allowing it to be used in monadic computations.------ Example:------ >>> import Control.Monad.IO.Class--- >>> runReaderT (listM Nothing) someContext--- Right (Models [ModelInfo ...])---+{- | MonadIO version of 'list' for use in monadic contexts.++Lifts the 'list' function into a 'MonadIO' context, allowing it to be used in monadic computations.++Example:++>>> import Control.Monad.IO.Class+>>> runReaderT (listM Nothing) someContext+Right (Models [ModelInfo ...])+-} listM :: MonadIO m => Maybe OllamaConfig -> m (Either OllamaError Models) listM mbCfg = liftIO $ list mbCfg
src/Data/Ollama/Load.hs view
@@ -11,7 +11,7 @@ This module provides functions to load and unload generative models in the Ollama server. It includes both IO-based functions ('loadGenModel', 'unloadGenModel') and monadic versions ('loadGenModelM', 'unloadGenModelM') for use in 'MonadIO' contexts. The operations are-performed via POST requests to the "/api//generate" endpoint, leveraging the 'GenerateOps'+performed via POST requests to the @\/api\/generate@ endpoint, leveraging the 'GenerateOps' configuration from the 'Data.Ollama.Generate' module. Loading a model keeps it in memory for faster subsequent requests, while unloading frees@@ -40,11 +40,11 @@ {- | Loads a generative model into memory. -Sends a POST request to the "/api//generate" endpoint to load the specified model into+Sends a POST request to the @\/api\/generate@ endpoint to load the specified model into memory, ensuring faster response times for subsequent requests. Returns 'Right ()' on success or 'Left' with an 'OllamaError' on failure.------ @since 0.2.0.0++@since 0.2.0.0 -} loadGenModel :: -- | Model name (e.g., "gemma3")@@ -52,15 +52,15 @@ IO (Either OllamaError ()) loadGenModel m = do let ops = Gen.defaultGenerateOps {Gen.modelName = m}- withOllamaRequest "/api//generate" "POST" (Just ops) Nothing commonNonStreamingHandler+ withOllamaRequest "/api/generate" "POST" (Just ops) Nothing commonNonStreamingHandler {- | Unloads a generative model from memory. -Sends a POST request to the "/api//generate" endpoint with a keep-alive duration of zero+Sends a POST request to the @\/api\/generate@ endpoint with a keep-alive duration of zero to unload the specified model from memory, freeing up resources. Returns 'Right ()' on success or 'Left' with an 'OllamaError' on failure.------ @since 0.2.0.0++@since 0.2.0.0 -} unloadGenModel :: -- | Model name (e.g., "gemma3")@@ -68,7 +68,7 @@ IO (Either OllamaError ()) unloadGenModel m = do let ops = Gen.defaultGenerateOps {Gen.modelName = m, Gen.keepAlive = Just 0}- withOllamaRequest "/api//generate" "POST" (Just ops) Nothing commonNonStreamingHandler+ withOllamaRequest "/api/generate" "POST" (Just ops) Nothing commonNonStreamingHandler {- | MonadIO version of 'loadGenModel' for use in monadic contexts. @@ -80,8 +80,8 @@ >>> import Control.Monad.IO.Class >>> runReaderT (loadGenModelM "gemma3") someContext Right ()------ @since 0.2.0.0++@since 0.2.0.0 -} loadGenModelM :: MonadIO m => Text -> m (Either OllamaError ()) loadGenModelM t = liftIO $ loadGenModel t@@ -96,8 +96,8 @@ >>> import Control.Monad.IO.Class >>> runReaderT (unloadGenModelM "gemma3") someContext Right ()------ @since 0.2.0.0++@since 0.2.0.0 -} unloadGenModelM :: MonadIO m => Text -> m (Either OllamaError ()) unloadGenModelM t = liftIO $ unloadGenModel t
src/Data/Ollama/Ps.hs view
@@ -10,7 +10,7 @@ This module provides functions to retrieve a list of models currently running on the Ollama server. It includes both an IO-based function ('ps') and a monadic version ('psM') for use in 'MonadIO'-contexts. The operation is performed via a GET request to the "/api//ps" endpoint, returning a+contexts. The operation is performed via a GET request to the @\/api\/ps@ endpoint, returning a 'RunningModels' type containing a list of 'RunningModel' records with details about each running model. Example:@@ -81,7 +81,7 @@ {- | Retrieves a list of currently running models from the Ollama server. -Sends a GET request to the "/api//ps" endpoint to fetch the list of running models.+Sends a GET request to the @\/api\/ps@ endpoint to fetch the list of running models. Returns 'Right' with a 'RunningModels' containing the list of 'RunningModel' on success, or 'Left' with an 'OllamaError' on failure. Example:@@ -95,7 +95,7 @@ IO (Either OllamaError RunningModels) ps mbConfig = do withOllamaRequest- "/api//ps"+ "/api/ps" "GET" (Nothing :: Maybe Value) mbConfig
src/Data/Ollama/Pull.hs view
@@ -17,7 +17,7 @@ support for streaming progress updates and insecure connections. The 'PullOps' type configures the pull request, and 'PullResp' represents the response containing the status and progress details. -The pull operation is performed via a POST request to the "/api//pull" endpoint. Streaming mode,+The pull operation is performed via a POST request to the @\/api\/pull@ endpoint. Streaming mode, when enabled, provides real-time progress updates by printing the remaining bytes to the console. Example:@@ -76,7 +76,7 @@ {- | Pulls a model with full configuration. -Sends a POST request to the "/api//pull" endpoint to download the specified model. Supports+Sends a POST request to the @\/api\/pull@ endpoint to download the specified model. Supports streaming progress updates (if 'stream' is 'Just True') and insecure connections (if 'insecure' is 'Just True'). Prints remaining bytes during streaming and "Completed" when finished. Returns 'Right' with a 'PullResp' on success or 'Left' with an 'OllamaError' on failure.@@ -93,20 +93,17 @@ IO (Either OllamaError PullResp) pullOps modelName mInsecure mStream mbConfig = do withOllamaRequest- "/api//pull"+ "/api/pull" "POST" (Just $ PullOps {name = modelName, insecure = mInsecure, stream = mStream}) mbConfig- (commonStreamHandler onToken onComplete)+ (commonStreamHandler (onToken, pure ())) where onToken :: PullResp -> IO () onToken res = do let completed' = fromMaybe 0 (completed res) let total' = fromMaybe 0 (total res) putStrLn $ "Remaining bytes: " <> show (total' - completed')-- onComplete :: IO ()- onComplete = putStrLn "Completed" {- | Simplified API for pulling a model.
src/Data/Ollama/Push.hs view
@@ -14,7 +14,7 @@ This module provides functions to push (upload) a model to the Ollama server. It includes both an IO-based function ('push') and a monadic version ('pushM') for use in 'MonadIO'-contexts. The push operation is performed via a POST request to the "/api//pull" endpoint,+contexts. The push operation is performed via a POST request to the @\/api\/pull@ endpoint, with support for streaming progress updates and insecure connections. The 'PushOps' type configures the push request, and 'PushResp' represents the response@@ -72,7 +72,7 @@ {- | Pushes a model to the Ollama server with specified options. -Sends a POST request to the "/api//pull" endpoint to upload the specified model. Supports+Sends a POST request to the @\/api\/pull@ endpoint to upload the specified model. Supports streaming progress updates (if 'stream' is 'Just True') and insecure connections (if 'insecure' is 'Just True'). Prints "Pushing..." during streaming and "Completed" when finished. Returns '()' on completion.@@ -90,17 +90,14 @@ push modelName mInsecure mStream mbConfig = do void $ withOllamaRequest- "/api//push"+ "/api/push" "POST" (Just $ PushOps {name = modelName, insecure = mInsecure, stream = mStream}) mbConfig- (commonStreamHandler onToken onComplete)+ (commonStreamHandler (onToken, pure ())) where onToken :: PushResp -> IO () onToken _ = putStrLn "Pushing... "-- onComplete :: IO ()- onComplete = putStrLn "Completed" {- | MonadIO version of 'push' for use in monadic contexts.
src/Data/Ollama/Show.hs view
@@ -14,7 +14,7 @@ This module provides functions to fetch detailed information about a specific model on the Ollama server. It includes both high-level ('showModel', 'showModelM') and low-level ('showModelOps', 'showModelOpsM') APIs for retrieving model details, with support for verbose output. The operation is performed via a POST request-to the "/api//show" endpoint, returning a 'ShowModelResponse' containing comprehensive model metadata.+to the @\/api\/show@ endpoint, returning a 'ShowModelResponse' containing comprehensive model metadata. The 'ShowModelOps' type configures the request, and 'ShowModelResponse' and 'ShowModelInfo' represent the response structure. The module also re-exports 'CT.ModelDetails' for completeness.@@ -170,7 +170,7 @@ {- | Retrieves model information with configuration options. -Sends a POST request to the "/api//show" endpoint to fetch detailed information about+Sends a POST request to the @\/api\/show@ endpoint to fetch detailed information about the specified model. Supports verbose output if 'verbose' is 'Just True' (though verbose mode parsing is currently incomplete). Returns 'Right' with a 'ShowModelResponse' on success or 'Left' with an 'OllamaError' on failure.@@ -185,7 +185,7 @@ IO (Either OllamaError ShowModelResponse) showModelOps modelName verbose_ mbConfig = do withOllamaRequest- "/api//show"+ "/api/show" "POST" ( Just $ ShowModelOps
src/Ollama.hs view
@@ -10,7 +10,7 @@ == Ollama Haskell -This module provides a high-level Haskell interface to the [Ollama](https://ollama.com) API +This module provides a high-level Haskell interface to the [Ollama](https://ollama.com) API for interacting with local LLMs. It includes support for: - Text generation (sync/streaming)@@ -20,7 +20,7 @@ - Structured outputs - Custom configuration and model options -Inspired by @ollama-python@, this library is built to offer idiomatic Haskell bindings +Inspired by @ollama-python@, this library is built to offer idiomatic Haskell bindings over Ollama’s HTTP API. == 🔧 Usage@@ -31,7 +31,7 @@ import Ollama @ -All functions return @Either OllamaError a@ or can be used in a Monad stack using +All functions return @Either OllamaError a@ or can be used in a Monad stack using their @\*M@ variants. == 🔑 Main APIs@@ -144,6 +144,10 @@ , showModelM , showModelOpsM + -- ** Blob Operations+ , checkBlobExists+ , createBlob+ -- * Ollama config , defaultOllamaConfig , withOnModelStart@@ -177,6 +181,7 @@ ) where +import Data.Ollama.Blob import Data.Ollama.Chat import Data.Ollama.Common.Config import Data.Ollama.Common.Types@@ -184,11 +189,11 @@ import Data.Ollama.Copy import Data.Ollama.Create import Data.Ollama.Delete-import Data.Ollama.Embeddings+import Data.Ollama.Embeddings hiding (keepAlive, modelName) import Data.Ollama.Generate import Data.Ollama.List import Data.Ollama.Load-import Data.Ollama.Ps+import Data.Ollama.Ps hiding (modelName) import Data.Ollama.Pull import Data.Ollama.Push import Data.Ollama.Show
test/Main.hs view
@@ -4,9 +4,16 @@ import Data.Text (unpack) import Ollama (Version (..), getVersion)+import Test.Ollama.Blob qualified as Blob import Test.Ollama.Chat qualified as Chat+import Test.Ollama.Common qualified as Common+import Test.Ollama.Copy qualified as Copy+import Test.Ollama.Create qualified as Create+import Test.Ollama.Delete qualified as Delete import Test.Ollama.Embedding qualified as Embeddings import Test.Ollama.Generate qualified as Generate+import Test.Ollama.List qualified as List+import Test.Ollama.Load qualified as Load import Test.Ollama.Show qualified as Show import Test.Tasty @@ -14,17 +21,27 @@ tests = testGroup "Tests"- [ Generate.tests+ [ -- Core functionality tests+ Generate.tests , Chat.tests- , Show.tests , Embeddings.tests+ , -- Model management tests+ Show.tests+ , List.tests+ , Copy.tests+ , Create.tests+ , Delete.tests+ , Load.tests+ , -- Utility and blob tests+ Blob.tests+ , Common.tests ] main :: IO () main = do eRes <- getVersion case eRes of- Left err -> putStrLn $ show err+ Left err -> print err Right (Version r) -> do putStrLn $ "Ollama client version: " <> unpack r defaultMain tests
+ test/Test/Ollama/Blob.hs view
@@ -0,0 +1,50 @@+{-# LANGUAGE OverloadedStrings #-}++module Test.Ollama.Blob (tests) where++import Data.Ollama.Blob+import Data.Ollama.Common.Config (OllamaConfig (..), defaultOllamaConfig)+import Test.Tasty+import Test.Tasty.HUnit++testCheckBlobExistsInvalid :: TestTree+testCheckBlobExistsInvalid = testCase "Check blob exists: invalid digest should fail" $ do+ res <- checkBlobExists "invalid-digest" Nothing+ case res of+ Left _ -> assertBool "Should fail with invalid digest" True+ Right _ -> assertBool "Or return False for invalid digest" True++testCheckBlobExistsNonExistent :: TestTree+testCheckBlobExistsNonExistent = testCase "Check blob exists: non-existent blob" $ do+ let fakeDigest = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"+ res <- checkBlobExists fakeDigest Nothing+ case res of+ Left _ -> return () -- Network error is acceptable+ Right exists -> assertBool "Non-existent blob should return False" (not exists)++testCheckBlobExistsWithConfig :: TestTree+testCheckBlobExistsWithConfig = testCase "Check blob exists: with custom config" $ do+ let config = Just $ defaultOllamaConfig {timeout = 5}+ fakeDigest = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"+ res <- checkBlobExists fakeDigest config+ case res of+ Left _ -> return () -- Network error is acceptable+ Right _ -> assertBool "Should handle custom config" True++testCreateBlobInvalidFile :: TestTree+testCreateBlobInvalidFile = testCase "Create blob: invalid file path should fail" $ do+ let fakeDigest = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"+ res <- createBlob "/nonexistent/file/path" fakeDigest Nothing+ case res of+ Left _ -> assertBool "Should fail with invalid file path" True+ Right () -> assertFailure "Should not succeed with invalid file path"++tests :: TestTree+tests =+ testGroup+ "Blob tests"+ [ testCheckBlobExistsInvalid+ , testCheckBlobExistsNonExistent+ , testCheckBlobExistsWithConfig+ , testCreateBlobInvalidFile+ ]
test/Test/Ollama/Chat.hs view
@@ -37,8 +37,9 @@ let config = Just $ defaultOllamaConfig {timeout = 1} eRes <- chat defaultChatOps config case eRes of+ Right _ -> assertFailure "The model responded before timeout" Left (TimeoutError _) -> pure ()- _ -> assertFailure "Expected timeout error"+ Left other -> assertFailure $ "Expected timeout error, got " ++ show other -- | Test model lifecycle hooks on failure hooksFailTest :: TestTree@@ -160,15 +161,14 @@ Just msg -> do let responseText = content msg let decoded = Aeson.decode (BSL.pack $ T.unpack responseText) :: Maybe Aeson.Value- assertBool "Expected valid JSON object in response" (decoded /= Nothing)+ assertBool "Expected valid JSON object in response" (isJust decoded) -- | Test streaming response streamingTest :: TestTree streamingTest = testCase "Should handle streaming response" $ do chunksRef <- newIORef [] let streamHandler chunk = modifyIORef chunksRef (++ [message chunk])- flushHandler = pure ()- ops = defaultChatOps {stream = Just (streamHandler, flushHandler)}+ ops = defaultChatOps {stream = Just (streamHandler, pure ())} eRes <- chat ops Nothing chunks <- readIORef chunksRef let fullOutput = T.concat (map (maybe "" content) chunks)@@ -193,8 +193,8 @@ Left err -> assertFailure $ "Expected success, got error: " ++ show err Right r -> assertBool "Expected a response message" (isJust (message r)) -testToolCall_addTwoNumbers :: TestTree-testToolCall_addTwoNumbers = testCase "Tool call: addTwoNumbers(23, 46)" $ do+testToolCallAddTwoNumbers :: TestTree+testToolCallAddTwoNumbers = testCase "Tool call: addTwoNumbers(23, 46)" $ do let messageList = NE.singleton $ userMessage "What is 23 + 46? (Use tool)" paramProps = HM.fromList@@ -222,7 +222,7 @@ } ops = defaultChatOps- { chatModelName = "qwen3:0.6b"+ { modelName = "qwen3:0.6b" , messages = messageList , tools = Just [tool] }@@ -258,6 +258,33 @@ convertToNumber (Aeson.Number n) = toBoundedInteger n convertToNumber _ = Nothing +-- | Test conversation with multiple messages+multiMessageConversationTest :: TestTree+multiMessageConversationTest = testCase "Multi-message conversation should work" $ do+ let msgs =+ fromList+ [ systemMessage "You are a helpful assistant."+ , userMessage "What is 2+2?"+ , assistantMessage "2+2 equals 4."+ , userMessage "What about 3+3?"+ ]+ ops = defaultChatOps {messages = msgs}+ eRes <- chat ops Nothing+ case eRes of+ Left _ -> assertFailure "Expected success, got error"+ Right r -> case message r of+ Nothing -> assertFailure "Expected a message in response"+ Just msg -> assertBool "Should contain '6'" (T.isInfixOf "6" (content msg))++-- | Test invalid model name+invalidModelChatTest :: TestTree+invalidModelChatTest = testCase "Invalid model name should fail" $ do+ let ops = defaultChatOps {modelName = "nonexistent-chat-model"}+ eRes <- chat ops Nothing+ case eRes of+ Left _ -> assertBool "Should fail with invalid model" True+ Right _ -> assertFailure "Should not succeed with invalid model"+ -- | Group all tests tests :: TestTree tests =@@ -274,5 +301,7 @@ , jsonFormatTest , streamingTest , modelOptionsTest- , testToolCall_addTwoNumbers+ , testToolCallAddTwoNumbers+ , multiMessageConversationTest+ , invalidModelChatTest ]
+ test/Test/Ollama/Common.hs view
@@ -0,0 +1,91 @@+{-# LANGUAGE OverloadedStrings #-}++module Test.Ollama.Common (tests) where++import Data.Ollama.Common.SchemaBuilder+import Data.Ollama.Common.Utils (encodeImage)+import Data.Text qualified as T+import Test.Tasty+import Test.Tasty.HUnit++-- Test SchemaBuilder functionality+testSchemaBuilderBasic :: TestTree+testSchemaBuilderBasic = testCase "SchemaBuilder: basic object construction" $ do+ let schema =+ buildSchema $+ emptyObject+ |+ ("name", JString)+ |+ ("age", JNumber)+ |! "name"+ |! "age"++ -- Check that schema is not null/empty+ let schemaText = T.pack $ show schema+ assertBool "Schema should not be empty" (not $ T.null schemaText)+ assertBool "Schema should contain 'name'" ("name" `T.isInfixOf` schemaText)+ assertBool "Schema should contain 'age'" ("age" `T.isInfixOf` schemaText)++testSchemaBuilderArray :: TestTree+testSchemaBuilderArray = testCase "SchemaBuilder: array types" $ do+ let arraySchema =+ buildSchema $+ emptyObject+ |+ ("items", JArray JString)+ |+ ("numbers", JArray JNumber)+ |+ ("flags", JArray JBoolean)+ |! "items"++ let schemaText = T.pack $ show arraySchema+ assertBool "Array schema should not be empty" (not $ T.null schemaText)+ assertBool "Should contain items field" ("items" `T.isInfixOf` schemaText)++testSchemaBuilderOptionalFields :: TestTree+testSchemaBuilderOptionalFields = testCase "SchemaBuilder: optional vs required fields" $ do+ let schema =+ buildSchema $+ emptyObject+ |+ ("required_field", JString)+ |+ ("optional_field", JString)+ |! "required_field"++ let schemaText = T.pack $ show schema+ assertBool "Schema should contain both fields" $+ "required_field" `T.isInfixOf` schemaText+ && "optional_field" `T.isInfixOf` schemaText++-- Test image encoding utility+testEncodeImageValidFile :: TestTree+testEncodeImageValidFile = testCase "EncodeImage: should handle existing image file" $ do+ -- Test with the sample image from the examples+ result <- encodeImage "./examples/sample.png"+ case result of+ Nothing -> return () -- File might not exist in test environment+ Just encoded -> do+ assertBool "Encoded image should not be empty" (not $ T.null encoded)+ assertBool "Should be base64-like content" (T.length encoded > 10)++testEncodeImageNonExistentFile :: TestTree+testEncodeImageNonExistentFile = testCase "EncodeImage: should return Nothing for non-existent file" $ do+ result <- encodeImage "./nonexistent/file.png"+ case result of+ Nothing -> assertBool "Should return Nothing for non-existent file" True+ Just _ -> assertFailure "Should not encode non-existent file"++testEncodeImageInvalidFormat :: TestTree+testEncodeImageInvalidFormat = testCase "EncodeImage: should handle invalid file format" $ do+ result <- encodeImage "./test/Main.hs" -- Text file, not an image+ case result of+ Nothing -> assertBool "Should return Nothing for invalid format" True+ Just _ -> assertFailure "Should not encode invalid format"++tests :: TestTree+tests =+ testGroup+ "Common utilities tests"+ [ testSchemaBuilderBasic+ , testSchemaBuilderArray+ , testSchemaBuilderOptionalFields+ , testEncodeImageValidFile+ , testEncodeImageNonExistentFile+ , testEncodeImageInvalidFormat+ ]
+ test/Test/Ollama/Copy.hs view
@@ -0,0 +1,45 @@+{-# LANGUAGE OverloadedStrings #-}++module Test.Ollama.Copy (tests) where++import Control.Monad (void)+import Data.Ollama.Common.Config (OllamaConfig (..), defaultOllamaConfig)+import Data.Ollama.Copy+import Data.Ollama.Delete (deleteModel)+import Test.Tasty+import Test.Tasty.HUnit++testCopyModelBasic :: TestTree+testCopyModelBasic = testCase "Copy model: basic functionality" $ do+ res <- copyModel "gemma3" "gemma3-test-copy" Nothing+ case res of+ Left _ -> return () -- Allow failure if source model doesn't exist+ Right () -> do+ assertBool "Copy should succeed" True+ void $ deleteModel "gemma3-test-copy" Nothing++testCopyModelWithConfig :: TestTree+testCopyModelWithConfig = testCase "Copy model: with custom config" $ do+ let config = Just $ defaultOllamaConfig {timeout = 30}+ res <- copyModel "gemma3" "gemma3-test-copy-config" config+ case res of+ Left _ -> return () -- Allow failure if source doesn't exist+ Right () -> do+ assertBool "Copy with config should work" True+ void $ deleteModel "gemma3-test-copy-config" Nothing++testCopyModelInvalidSource :: TestTree+testCopyModelInvalidSource = testCase "Copy model: invalid source should fail" $ do+ res <- copyModel "nonexistent-model-12345" "some-destination" Nothing+ case res of+ Left _ -> assertBool "Should fail with invalid source" True+ Right () -> assertFailure "Should not succeed with invalid source"++tests :: TestTree+tests =+ testGroup+ "Copy tests"+ [ testCopyModelBasic+ , testCopyModelWithConfig+ , testCopyModelInvalidSource+ ]
+ test/Test/Ollama/Create.hs view
@@ -0,0 +1,79 @@+{-# LANGUAGE OverloadedStrings #-}++module Test.Ollama.Create (tests) where++import Control.Monad (void)+import Data.Maybe (isNothing)+import Data.Ollama.Common.Types (ModelOptions (..))+import Data.Ollama.Common.Utils (defaultModelOptions)+import Data.Ollama.Create+import Data.Ollama.Delete (deleteModel)+import Test.Tasty+import Test.Tasty.HUnit++testCreateModelBasic :: TestTree+testCreateModelBasic = testCase "Create model: basic from existing model" $ do+ let ops =+ (defaultCreateOps "test-model-basic")+ { fromModel = Just "gemma3"+ , systemPrompt = Just "You are a helpful assistant."+ }+ createModel ops Nothing+ assertBool "Create should complete without error" True+ void $ deleteModel "test-model-basic" Nothing++testCreateModelWithQuantization :: TestTree+testCreateModelWithQuantization = testCase "Create model: with quantization" $ do+ let ops =+ (defaultCreateOps "test-model-quantized")+ { fromModel = Just "gemma3"+ , quantizeType = Just Q4_K_M+ }+ createModel ops Nothing+ assertBool "Quantization request should complete" True+ void $ deleteModel "test-model-quantized" Nothing++testCreateModelWithParameters :: TestTree+testCreateModelWithParameters = testCase "Create model: with custom parameters" $ do+ let modelOpts =+ defaultModelOptions+ { temperature = Just 0.7+ , topP = Just 0.9+ , topK = Just 40+ }+ ops =+ (defaultCreateOps "test-model-params")+ { fromModel = Just "gemma3"+ , parameters = Just modelOpts+ , template = Just "Custom template: {{.Prompt}}"+ }+ createModel ops Nothing+ assertBool "Custom parameters should be handled" True+ void $ deleteModel "test-model-params" Nothing++testQuantizationTypeValues :: TestTree+testQuantizationTypeValues = testCase "Quantization types: should have valid values" $ do+ let q1 = Q4_K_M+ q2 = Q4_K_S+ q3 = Q8_0+ assertBool "Q4_K_M should be valid" (show q1 == "Q4_K_M")+ assertBool "Q4_K_S should be valid" (show q2 == "Q4_K_S")+ assertBool "Q8_0 should be valid" (show q3 == "Q8_0")++testCreateModelFieldAccess :: TestTree+testCreateModelFieldAccess = testCase "CreateOps: field access should work" $ do+ let ops = defaultCreateOps "test-model"+ assertBool "modelName should be accessible" (modelName ops == "test-model")+ assertBool "fromModel should be Nothing by default" (isNothing (fromModel ops))+ assertBool "files should be Nothing by default" (isNothing (files ops))++tests :: TestTree+tests =+ testGroup+ "Create tests"+ [ testCreateModelBasic+ , testCreateModelWithQuantization+ , testCreateModelWithParameters+ , testQuantizationTypeValues+ , testCreateModelFieldAccess+ ]
+ test/Test/Ollama/Delete.hs view
@@ -0,0 +1,42 @@+{-# LANGUAGE OverloadedStrings #-}++module Test.Ollama.Delete (tests) where++import Data.Ollama.Common.Config (OllamaConfig (..), defaultOllamaConfig)+import Data.Ollama.Delete+import Test.Tasty+import Test.Tasty.HUnit++testDeleteModelNonExistent :: TestTree+testDeleteModelNonExistent = testCase "Delete non-existent model should fail gracefully" $ do+ res <- deleteModel "nonexistent-model-12345" Nothing+ case res of+ Left _ -> assertBool "Should fail when deleting non-existent model" True+ Right () -> assertBool "Or succeed if server handles gracefully" True++testDeleteModelWithConfig :: TestTree+testDeleteModelWithConfig = testCase "Delete model: with custom config" $ do+ let config = Just $ defaultOllamaConfig {timeout = 30}+ res <- deleteModel "nonexistent-test-model" config+ case res of+ Left _ -> assertBool "Should handle custom config" True+ Right () -> assertBool "Or succeed if server handles gracefully" True++testDeleteModelEmptyName :: TestTree+testDeleteModelEmptyName = testCase "Delete model: empty name should fail" $ do+ res <- deleteModel "" Nothing+ case res of+ Left _ -> assertBool "Should fail with empty name" True+ Right () -> assertFailure "Should not succeed with empty name"++-- Note: We avoid testing actual deletion of existing models in unit tests+-- as it would be destructive. Integration tests could cover this.++tests :: TestTree+tests =+ testGroup+ "Delete tests"+ [ testDeleteModelNonExistent+ , testDeleteModelWithConfig+ , testDeleteModelEmptyName+ ]
test/Test/Ollama/Embedding.hs view
@@ -1,34 +1,43 @@ {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-} module Test.Ollama.Embedding (tests) where -import Data.Maybe (listToMaybe) import Data.Ollama.Embeddings import Test.Tasty import Test.Tasty.HUnit -testEmbeddingBasic :: TestTree-testEmbeddingBasic = testCase "Basic embedding with qwen3" $ do- res <- embedding "qwen3:0.6b" ["The sky is blue.", "Cats are independent."]- case res of- Left err -> assertFailure $ "Expected success, got error: " ++ show err- Right EmbeddingResp {..} -> do- assertEqual "Should return two embeddings" 2 (length respondedEmbeddings)- assertBool "Embeddings should not be empty" (all (not . null) respondedEmbeddings)+-- Qwen doesn't support embeddings yet+-- TODO: Add embeddings supporting model. -testEmbeddingWithOptions :: TestTree-testEmbeddingWithOptions = testCase "Embedding with truncate and keepAlive" $ do- let opts = defaultModelOptions {numKeep = Just 5, seed = Just 42}- res <- embeddingOps "qwen3:0.6b" ["Hello world"] (Just True) (Just 30) (Just opts) Nothing- case res of- Left err -> assertFailure $ "Unexpected error: " ++ show err- Right EmbeddingResp {..} -> do- assertEqual "Should return one embedding" 1 (length respondedEmbeddings)- assertBool- "Embedding vector should not be empty"- (not . null $ listToMaybe respondedEmbeddings)+-- testEmbeddingBasic :: TestTree+-- testEmbeddingBasic = testCase "Basic embedding with qwen3" $ do+-- res <- embedding "qwen3:0.6b" ["The sky is blue.", "Cats are independent."]+-- case res of+-- Left err -> assertFailure $ "Expected success, got error: " ++ show err+-- Right EmbeddingResp {..} -> do+-- assertEqual "Should return two embeddings" 2 (length respondedEmbeddings)+-- assertBool "Embeddings should not be empty" (not (any null respondedEmbeddings)) +-- testEmbeddingWithOptions :: TestTree+-- testEmbeddingWithOptions = testCase "Embedding with truncate and keepAlive" $ do+-- let opts = defaultModelOptions {numKeep = Just 5, seed = Just 42}+-- res <-+-- embeddingOps+-- "qwen3:0.6b"+-- ["Hello world"]+-- (Just True)+-- (Just 30)+-- (Just opts)+-- Nothing+-- Nothing+-- case res of+-- Left err -> assertFailure $ "Unexpected error: " ++ show err+-- Right EmbeddingResp {..} -> do+-- assertEqual "Should return one embedding" 1 (length respondedEmbeddings)+-- assertBool+-- "Embedding vector should not be empty"+-- (not . null $ listToMaybe respondedEmbeddings)+ testEmbeddingInvalidModel :: TestTree testEmbeddingInvalidModel = testCase "Embedding with invalid model name" $ do res <- embedding "nonexistent-model" ["This should fail."]@@ -40,7 +49,5 @@ tests = testGroup "Embeddings tests"- [ testEmbeddingBasic- , testEmbeddingWithOptions- , testEmbeddingInvalidModel+ [ testEmbeddingInvalidModel ]
test/Test/Ollama/Generate.hs view
@@ -9,6 +9,7 @@ import Data.Aeson qualified as Aeson import Data.ByteString.Lazy.Char8 qualified as BSL import Data.IORef (modifyIORef, newIORef, readIORef, writeIORef)+import Data.Maybe (isJust) import Data.Ollama.Common.SchemaBuilder import Data.Ollama.Common.Utils (encodeImage) import Data.Ollama.Generate@@ -34,7 +35,10 @@ , testCase "Setting timeout" $ do eRes <- generate- defaultGenerateOps {modelName = "gemma3", prompt = "Write a poem about French revolution"}+ defaultGenerateOps+ { modelName = "gemma3"+ , prompt = "Write a poem about French revolution"+ } (Just $ defaultOllamaConfig {timeout = 1}) case eRes of Left (TimeoutError _) -> pure ()@@ -201,8 +205,10 @@ Left err -> assertFailure $ "Expected success, got error: " ++ show err Right r -> do let responseText = genResponse r- let decoded = Aeson.decode (BSL.pack $ T.unpack responseText) :: Maybe Aeson.Value- assertBool "Expected valid JSON object in response" (decoded /= Nothing)+ let decoded =+ Aeson.decode (BSL.pack $ T.unpack responseText) ::+ Maybe Aeson.Value+ assertBool "Expected valid JSON object in response" (isJust decoded) testFormatSchemaFormat :: TestTree testFormatSchemaFormat = testCase "Should include SchemaFormat in the request" $ do@@ -235,6 +241,10 @@ case maybeImg of Nothing -> assertFailure "Image encoding failed (unsupported format or missing file)" Just imgData -> do+ -- Validate the encoded image data+ assertBool "Encoded image should not be empty" (not $ T.null imgData)+ assertBool "Encoded image should be reasonable length" (T.length imgData > 100)+ let ops = defaultGenerateOps { modelName = "gemma3"@@ -248,6 +258,7 @@ Left err -> assertFailure $ "Expected success, got error: " ++ show err Right r -> do let response = T.toLower (genResponse r)+ assertBool "Response should not be empty" (not $ T.null response) assertBool "Expected image-related description in response" $ T.isInfixOf "i love haskell" response @@ -257,12 +268,11 @@ chunksRef <- newIORef [] -- Define the stream handler: accumulate responses let streamHandler chunk = modifyIORef chunksRef (++ [genResponse chunk])- flushHandler = pure () ops = defaultGenerateOps { modelName = "gemma3" , prompt = "Write few words about Haskell."- , stream = Just (streamHandler, flushHandler)+ , stream = Just (streamHandler, pure ()) } eRes <- generate ops Nothing -- Collect streamed chunks from IORef@@ -299,6 +309,35 @@ Left err -> assertFailure $ "Expected success, got: " ++ show err Right r -> assertBool "Response should not be empty" (not . T.null $ genResponse r) +testModelOptionsEdgeCases :: TestTree+testModelOptionsEdgeCases = testCase "ModelOptions: edge case values" $ do+ let opts =+ Just $+ defaultModelOptions+ { temperature = Just 0.0 -- Minimum temperature+ , topP = Just 1.0 -- Maximum topP+ , topK = Just 1 -- Minimum topK+ , numPredict = Just 1 -- Minimum prediction+ }+ eRes <-+ generate+ defaultGenerateOps+ { modelName = "gemma3"+ , prompt = "Hi"+ , options = opts+ }+ Nothing+ case eRes of+ Left _ -> assertFailure "Expected success, got error"+ Right _ -> assertBool "Should handle edge case model options" True++testInvalidModelName :: TestTree+testInvalidModelName = testCase "Invalid model name should fail gracefully" $ do+ eRes <- generate defaultGenerateOps {modelName = "invalid-model-xyz"} Nothing+ case eRes of+ Left _ -> assertBool "Should fail with invalid model name" True+ Right _ -> assertFailure "Should not succeed with invalid model name"+ tests :: TestTree tests = sequentialTestGroup@@ -316,4 +355,6 @@ , testImageInput , testStreamingHandler , testModelOptionsBasic+ , testModelOptionsEdgeCases+ , testInvalidModelName ]
+ test/Test/Ollama/List.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE OverloadedStrings #-}++module Test.Ollama.List (tests) where++import Data.Ollama.Common.Config (OllamaConfig (..), defaultOllamaConfig)+import Data.Ollama.List+import Data.Text qualified as T+import Test.Tasty+import Test.Tasty.HUnit++testListBasic :: TestTree+testListBasic = testCase "List models: basic call should return Models" $ do+ res <- list Nothing+ case res of+ Left err -> assertFailure $ "Expected success, got error: " ++ show err+ Right (Models modelList) -> do+ assertBool "Should return a list (possibly empty)" True+ -- Additional checks if models are present+ case modelList of+ [] -> return () -- Empty list is acceptable+ (model : _) -> do+ assertBool "Model name should not be empty" (not $ T.null $ name model)+ assertBool "Model digest should not be empty" (not $ T.null $ digest model)+ assertBool "Model size should be positive" (size model > 0)+ assertBool "details should be present" True -- ModelDetails is always present++testListWithConfig :: TestTree+testListWithConfig = testCase "List with custom config should work" $ do+ let config = Just $ defaultOllamaConfig {timeout = 10}+ res <- list config+ case res of+ Left _ -> assertFailure "Expected success, got error"+ Right (Models _) -> assertBool "Should return Models type" True++tests :: TestTree+tests =+ testGroup+ "List tests"+ [ testListBasic+ , testListWithConfig+ ]
+ test/Test/Ollama/Load.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE OverloadedStrings #-}++module Test.Ollama.Load (tests) where++import Data.Ollama.Load+import Test.Tasty+import Test.Tasty.HUnit++testLoadGenModelBasic :: TestTree+testLoadGenModelBasic = testCase "Load generation model: basic functionality" $ do+ res <- loadGenModel "gemma3"+ case res of+ Left _ -> assertFailure "Expected success, got error"+ Right () -> assertBool "Load should succeed" True++testUnloadGenModelBasic :: TestTree+testUnloadGenModelBasic = testCase "Unload generation model: basic functionality" $ do+ res <- unloadGenModel "gemma3"+ case res of+ Left _ -> assertFailure "Expected success, got error"+ Right () -> assertBool "Unload should succeed" True++testLoadInvalidModel :: TestTree+testLoadInvalidModel = testCase "Load invalid model should fail" $ do+ res <- loadGenModel "nonexistent-model-12345"+ case res of+ Left _ -> assertBool "Should fail with invalid model" True+ Right () -> assertFailure "Should not succeed with invalid model"++testUnloadInvalidModel :: TestTree+testUnloadInvalidModel = testCase "Unload invalid model should handle gracefully" $ do+ res <- unloadGenModel "nonexistent-model-12345"+ case res of+ Left _ -> assertBool "Should fail or handle gracefully" True+ Right () -> assertBool "Or succeed if server handles gracefully" True++testLoadEmptyModelName :: TestTree+testLoadEmptyModelName = testCase "Load model: empty name should fail" $ do+ res <- loadGenModel ""+ case res of+ Left _ -> assertBool "Should fail with empty name" True+ Right () -> assertFailure "Should not succeed with empty name"++tests :: TestTree+tests =+ testGroup+ "Load tests"+ [ testLoadGenModelBasic+ , testUnloadGenModelBasic+ , testLoadInvalidModel+ , testUnloadInvalidModel+ , testLoadEmptyModelName+ ]
test/Test/Ollama/Show.hs view
@@ -3,12 +3,12 @@ module Test.Ollama.Show (tests) where -import Test.Tasty-import Test.Tasty.HUnit-import Data.Ollama.Show+import Data.Maybe (isJust) import Data.Ollama.Ps+import Data.Ollama.Show import Data.Text qualified as T-import Data.Maybe (isJust)+import Test.Tasty+import Test.Tasty.HUnit testShowModelBasic :: TestTree testShowModelBasic = testCase "Show model info: basic call" $ do@@ -23,7 +23,7 @@ testShowModelVerbose = testCase "Show model info: verbose enabled" $ do res <- showModelOps "qwen3:0.6b" (Just True) Nothing case res of- Left _ -> pure () --assertFailure $ "Expected success, got error: " ++ show err+ Left _ -> pure () -- assertFailure $ "Expected success, got error: " ++ show err Right ShowModelResponse {template, parameters} -> do -- Verbose should yield more details like parameters/template assertBool "Should have a template if verbose" (isJust template)@@ -42,7 +42,7 @@ res <- ps Nothing case res of Left _ -> return () -- Allow failure if no models are running- Right (RunningModels (m:_)) -> do+ Right (RunningModels (m : _)) -> do assertBool "name_ should not be empty" (not $ T.null $ name_ m) assertBool "modelName should not be empty" (not $ T.null $ modelName m) assertBool "modelDigest should not be empty" (not $ T.null $ modelDigest m)@@ -51,9 +51,11 @@ Right _ -> return () -- If empty, that's acceptable tests :: TestTree-tests = testGroup "show model tests" [- testShowModelBasic - , testShowModelVerbose- , testPsBasic- , testPsModelFields- ]+tests =+ testGroup+ "show model tests"+ [ testShowModelBasic+ , testShowModelVerbose+ , testPsBasic+ , testPsModelFields+ ]