langchain-hs-0.0.3.0: src/Langchain/LLM/Core.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeFamilies #-}
{- |
Module: Langchain.LLM.Core
Copyright: (c) 2025 Tushar Adhatrao
License: MIT
Description: Core implementation of langchain LLMs
Maintainer: Tushar Adhatrao <tusharadhatrao@gmail.com>
Stability: experimental
This module provides the core types and typeclasses for the Langchain library in Haskell,
which is designed to facilitate interaction with language models (LLMs).
It defines a standardized interface that allows different LLM implementations
to be used interchangeably, promoting code reuse and modularity.
The main components include:
* The 'LLM' typeclass, which defines the interface for language models.
* Data types such as 'Message' for conversation messages,
and 'StreamHandler' for handling streaming responses.
* Default values like 'defaultParams' and 'defaultMessageData' for convenience.
This module is intended to be used as the foundation for building applications that interact with LLMs,
providing a consistent API across different model implementations.
-}
module Langchain.LLM.Core
( -- * LLM Typeclass
LLM (..)
-- * Parameters
, Message (..)
, Role (..)
, ChatHistory
, MessageData (..)
, ToolCall (..)
, ToolFunction (..)
, StreamHandler (..)
, MessageConvertible (..)
-- * Default Values
, defaultMessage
, defaultMessageData
) where
import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.Aeson
import qualified Data.Aeson.KeyMap as KM
import Data.List.NonEmpty
import qualified Data.Map as HM
import Data.Text (Text)
import Data.Text.Encoding (encodeUtf8)
import GHC.Generics
import Langchain.Error (LangchainResult)
{- | Callbacks for handling streaming responses from a language model.
This allows real-time processing of tokens as they are generated and an action
upon completion.
@
printHandler :: StreamHandler
printHandler = StreamHandler
{ onToken = putStrLn . ("Token: " ++)
, onComplete = putStrLn "Streaming complete"
}
@
-}
data StreamHandler tokenType = StreamHandler
{ onToken :: tokenType -> IO ()
-- ^ Action to perform for each token received
, onComplete :: IO ()
-- ^ Action to perform when streaming is complete
}
-- | Enumeration of possible roles in a conversation.
data Role
= -- | System role, typically for instructions or context
System
| -- | User role, for user inputs
User
| -- | Assistant role, for model responses
Assistant
| -- | Tool role, for tool outputs or interactions
Tool
| -- | Developer role for developer messages. Specific to only some integrations
Developer
| -- | Function role for function call messages. Specific to only some integrations
Function
deriving
( Eq
, Show
, Generic
, ToJSON
, FromJSON
)
{- | Represents a message in a conversation, including the sender's role, content,
and additional metadata.
https://python.langchain.com/docs/concepts/messages/
@
userMsg :: Message
userMsg = Message
{ role = User
, content = "Explain functional programming"
, messageData = defaultMessageData
}
@
-}
data Message = Message
{ role :: Role
-- ^ The role of the message sender
, content :: Text
-- ^ The content of the message
, messageData :: MessageData
-- ^ Additional data associated with the message
}
deriving (Eq, Show)
-- Function call details
data ToolFunction = ToolFunction
{ toolFunctionName :: Text
, toolFunctionArguments :: HM.Map Text Value
}
deriving (Show, Eq)
-- Main tool call structure
data ToolCall = ToolCall
{ toolCallId :: Text
, toolCallType :: Text
, toolCallFunction :: ToolFunction
}
deriving (Show, Eq)
-- ToJSON instance for ToolFunction
instance ToJSON ToolFunction where
toJSON (ToolFunction name args) =
object
[ "name" .= name
, "arguments" .= args
]
-- FromJSON instance for ToolFunction
instance FromJSON ToolFunction where
parseJSON = withObject "ToolFunction" $ \obj -> do
name <- obj .: "name"
argsVal <- obj .: "arguments"
args <- case argsVal of
Object o -> pure $ KM.toMapText o
String s -> case decodeStrict (encodeUtf8 s) of
Just (Object o) -> pure $ KM.toMapText o
_ -> fail "ToolFunction.arguments: expected object or JSON-encoded object string"
_ -> fail "ToolFunction.arguments: expected object or string"
return $ ToolFunction name args
-- ToJSON instance for ToolCall
instance ToJSON ToolCall where
toJSON (ToolCall callId callType func) =
object
[ "id" .= callId
, "type" .= callType
, "function" .= func
]
-- FromJSON instance for ToolCall
instance FromJSON ToolCall where
parseJSON = withObject "ToolCall" $ \obj -> do
callId <- obj .: "id"
callType <- obj .: "type"
func <- obj .: "function"
return $ ToolCall callId callType func
{- | Additional data for a message, such as a name or tool calls.
This type is designed for extensibility, allowing new fields to be added without
breaking changes. Use 'defaultMessageData' for typical usage.
-}
data MessageData = MessageData
{ name :: Maybe Text
-- ^ Optional name associated with the message
, toolCalls :: Maybe [ToolCall]
-- ^ Optional list of tool calls invoked by the message
, messageImages :: Maybe [Text]
-- ^ Base64 encoded image data list
, thinking :: Maybe Text
-- ^ Thinking
}
deriving (Eq, Show)
-- | JSON serialization for MessageData.
instance ToJSON MessageData where
toJSON MessageData {..} =
object
[ "name" .= name
, "tool_calls" .= toolCalls
, "images" .= messageImages
, "thinking" .= thinking
-- Add more fields as they are added
]
-- | JSON deserialization for MessageData.
instance FromJSON MessageData where
parseJSON = withObject "MessageData" $ \v ->
MessageData
<$> v .:? "name"
<*> v .:? "tool_calls"
<*> v .:? "images"
<*> v .:? "thinking"
-- | Type alias for NonEmpty Message
type ChatHistory = NonEmpty Message
-- | Default message with User role and no content.
defaultMessage :: Message
defaultMessage =
Message
{ role = User
, content = ""
, messageData = defaultMessageData
}
{- | Default message data with all fields set to Nothing.
Use this for standard messages without additional metadata
-}
defaultMessageData :: MessageData
defaultMessageData =
MessageData
{ name = Nothing
, toolCalls = Nothing
, messageImages = Nothing
, thinking = Nothing
}
-- | Typeclass that all ChatModels should interface with
class LLM llm where
-- | Define the Parameter type for your LLM model.
type LLMStreamTokenType llm
type LLMParams llm
{- | Invoke the language model with a single prompt.
Suitable for simple queries; returns either an error or generated text.
-}
generate ::
-- | The type of the language model instance.
llm ->
-- | The prompt to send to the model.
Text ->
-- | Optional configuration parameters.
Maybe (LLMParams llm) ->
IO (LangchainResult Text)
{- | Chat with the language model using a sequence of messages.
Suitable for multi-turn conversations; returns either an error or the response.
-}
chat ::
-- | The type of the language model instance.
llm ->
-- | A non-empty list of messages to send to the model.
ChatHistory ->
-- | Optional configuration parameters.
Maybe (LLMParams llm) ->
-- | The result of the chat, either an error or the response text.
IO (LangchainResult Message)
{- | Stream responses from the language model for a sequence of messages.
Uses callbacks to process tokens in real-time; returns either an error or unit.
-}
stream ::
llm ->
ChatHistory ->
StreamHandler (LLMStreamTokenType llm) ->
Maybe (LLMParams llm) ->
IO (LangchainResult ())
-- Default implementations
-- | MonadIO version of generate
generateM ::
MonadIO m =>
-- | The type of the language model instance.
llm ->
-- | The prompt to send to the model.
Text ->
-- | Optional configuration parameters.
Maybe (LLMParams llm) ->
m (LangchainResult Text)
generateM llm prompt mbParams = liftIO $ generate llm prompt mbParams
-- | MonadIO version of chat
chatM ::
MonadIO m =>
-- | The type of the language model instance.
llm ->
-- | A non-empty list of messages to send to the model.
ChatHistory ->
-- | Optional configuration parameters.
Maybe (LLMParams llm) ->
-- | The result of the chat, either an error or the response text.
m (LangchainResult Message)
chatM llm chatHistory mbParams = liftIO $ chat llm chatHistory mbParams
-- | MonadIO version of stream
streamM ::
MonadIO m =>
llm ->
ChatHistory ->
StreamHandler (LLMStreamTokenType llm) ->
Maybe (LLMParams llm) ->
m (LangchainResult ())
streamM llm chatHistory sHandler mbParams =
liftIO $ stream llm chatHistory sHandler mbParams
class MessageConvertible a where
to :: Message -> a
from :: a -> Message