langchain-hs 0.0.2.0 → 0.0.3.0
raw patch · 69 files changed
+4232/−2887 lines, 69 filesdep +base64-bytestringdep +openaidep +timedep ~ollama-haskellsetup-changedPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: base64-bytestring, openai, time, transformers
Dependency ranges changed: ollama-haskell
API changes (from Hackage documentation)
- Langchain.Agents.Core: AgentAction :: Text -> Text -> Text -> AgentAction
- Langchain.Agents.Core: AgentFinish :: Map Text Text -> Text -> AgentFinish
- Langchain.Agents.Core: AgentState :: m -> [(Text, Text)] -> [AgentAction] -> AgentState m
- Langchain.Agents.Core: AnyTool :: a -> (Text -> Input a) -> (Output a -> Text) -> AnyTool
- Langchain.Agents.Core: Continue :: AgentAction -> AgentStep
- Langchain.Agents.Core: Finish :: AgentFinish -> AgentStep
- Langchain.Agents.Core: [actionInput] :: AgentAction -> Text
- Langchain.Agents.Core: [actionLog] :: AgentAction -> Text
- Langchain.Agents.Core: [actionToolName] :: AgentAction -> Text
- Langchain.Agents.Core: [agentMemory] :: AgentState m -> m
- Langchain.Agents.Core: [agentSteps] :: AgentState m -> [AgentAction]
- Langchain.Agents.Core: [agentToolResults] :: AgentState m -> [(Text, Text)]
- Langchain.Agents.Core: [anyTool] :: AnyTool -> a
- Langchain.Agents.Core: [finishLog] :: AgentFinish -> Text
- Langchain.Agents.Core: [outputToText] :: AnyTool -> Output a -> Text
- Langchain.Agents.Core: [returnValues] :: AgentFinish -> Map Text Text
- Langchain.Agents.Core: [textToInput] :: AnyTool -> Text -> Input a
- Langchain.Agents.Core: agentPrompt :: Agent a => a -> IO PromptTemplate
- Langchain.Agents.Core: agentTools :: Agent a => a -> IO [AnyTool]
- Langchain.Agents.Core: class Agent a
- Langchain.Agents.Core: customAnyTool :: Tool a => a -> (Text -> Input a) -> (Output a -> Text) -> AnyTool
- Langchain.Agents.Core: data AgentAction
- Langchain.Agents.Core: data AgentFinish
- Langchain.Agents.Core: data BaseMemory m => AgentState m
- Langchain.Agents.Core: data AgentStep
- Langchain.Agents.Core: data AnyTool
- Langchain.Agents.Core: executeTool :: [AnyTool] -> Text -> Text -> IO (Either String Text)
- Langchain.Agents.Core: instance (Langchain.Memory.Core.BaseMemory m, GHC.Classes.Eq m) => GHC.Classes.Eq (Langchain.Agents.Core.AgentState m)
- Langchain.Agents.Core: instance (Langchain.Memory.Core.BaseMemory m, GHC.Internal.Show.Show m) => GHC.Internal.Show.Show (Langchain.Agents.Core.AgentState m)
- Langchain.Agents.Core: instance GHC.Classes.Eq Langchain.Agents.Core.AgentAction
- Langchain.Agents.Core: instance GHC.Classes.Eq Langchain.Agents.Core.AgentFinish
- Langchain.Agents.Core: instance GHC.Classes.Eq Langchain.Agents.Core.AgentStep
- Langchain.Agents.Core: instance GHC.Internal.Show.Show Langchain.Agents.Core.AgentAction
- Langchain.Agents.Core: instance GHC.Internal.Show.Show Langchain.Agents.Core.AgentFinish
- Langchain.Agents.Core: instance GHC.Internal.Show.Show Langchain.Agents.Core.AgentStep
- Langchain.Agents.Core: planNextAction :: (Agent a, BaseMemory m) => a -> AgentState m -> IO (Either String AgentStep)
- Langchain.Agents.Core: runAgent :: (Agent a, BaseMemory m) => a -> AgentState m -> Text -> IO (Either String AgentFinish)
- Langchain.Agents.Core: runAgentLoop :: (Agent a, BaseMemory m) => a -> AgentState m -> Int -> Int -> IO (Either String AgentFinish)
- Langchain.Agents.Core: runSingleStep :: (Agent a, BaseMemory m) => a -> AgentState m -> IO (Either String AgentStep)
- Langchain.Agents.React: ReactAgent :: llm -> Maybe (LLMParams llm) -> [AnyTool] -> ReactAgent llm
- Langchain.Agents.React: [reactLLMParams] :: ReactAgent llm -> Maybe (LLMParams llm)
- Langchain.Agents.React: [reactLLM] :: ReactAgent llm -> llm
- Langchain.Agents.React: [reactToolList] :: ReactAgent llm -> [AnyTool]
- Langchain.Agents.React: data LLM llm => ReactAgent llm
- Langchain.Agents.React: defaultReactPromptTemplate :: PromptTemplate
- Langchain.Agents.React: instance Langchain.LLM.Core.LLM llm => Langchain.Agents.Core.Agent (Langchain.Agents.React.ReactAgent llm)
- Langchain.Agents.React: instance Langchain.OutputParser.Core.OutputParser Langchain.Agents.React.ReactAgentOutputParser
- Langchain.Agents.React: runReactAgent :: LLM llm => llm -> Maybe (LLMParams llm) -> [AnyTool] -> Text -> IO (Either String AgentFinish)
- Langchain.DocumentLoader.FileLoader: data FileLoader
- Langchain.DocumentLoader.PdfLoader: data PdfLoader
- Langchain.Embeddings.OpenAI: [embeddingsUser] :: OpenAIEmbeddings -> Maybe Text
- Langchain.LLM.Core: type ChatMessage = NonEmpty Message
- Langchain.LLM.Huggingface: defaultMessage :: Message
- Langchain.LLM.Internal.Huggingface: data Delta
- Langchain.LLM.Internal.Huggingface: data ImageUrl
- Langchain.LLM.Internal.Huggingface: data SpecificToolChoice
- Langchain.LLM.Internal.Huggingface: data StreamOptions
- Langchain.LLM.Internal.Huggingface: defaultMessage :: Message
- Langchain.LLM.Internal.OpenAI: ApproximateLocation :: Text -> ApproximateLocation
- Langchain.LLM.Internal.OpenAI: Assistant :: Role
- Langchain.LLM.Internal.OpenAI: AudioConfig :: Text -> Text -> AudioConfig
- Langchain.LLM.Internal.OpenAI: AudioModality :: Modality
- Langchain.LLM.Internal.OpenAI: AudioResponse :: Text -> Integer -> Text -> Text -> AudioResponse
- Langchain.LLM.Internal.OpenAI: Auto :: ToolChoice
- Langchain.LLM.Internal.OpenAI: ChatCompletionChunk :: [ChunkChoice] -> ChatCompletionChunk
- Langchain.LLM.Internal.OpenAI: ChatCompletionRequest :: [Message] -> Text -> Maybe Int -> Maybe Double -> Maybe (Map Text Double) -> Maybe Bool -> Maybe Int -> Maybe Int -> Maybe (Map Text Text) -> Maybe [Modality] -> Maybe Int -> Maybe Bool -> Maybe PredictionOutput -> Maybe Double -> Maybe ReasoningEffort -> Maybe ResponseFormat -> Maybe Int -> Maybe Text -> Maybe (Either Text [Text]) -> Maybe Bool -> Maybe Bool -> Maybe StreamOptions -> Maybe Double -> Maybe ToolChoice -> Maybe [Tool_] -> Maybe Int -> Maybe Double -> Maybe Text -> Maybe WebSearchOptions -> Maybe AudioConfig -> ChatCompletionRequest
- Langchain.LLM.Internal.OpenAI: ChatCompletionResponse :: [Choice] -> Integer -> Text -> Text -> Text -> Maybe Text -> Text -> Usage -> ChatCompletionResponse
- Langchain.LLM.Internal.OpenAI: Choice :: Maybe FinishReason -> Int -> Maybe LogProbs -> Message -> Choice
- Langchain.LLM.Internal.OpenAI: ChunkChoice :: Delta -> Maybe FinishReason -> ChunkChoice
- Langchain.LLM.Internal.OpenAI: CompletionTokensDetails :: Int -> Int -> Int -> Int -> CompletionTokensDetails
- Langchain.LLM.Internal.OpenAI: ContentFilter :: FinishReason
- Langchain.LLM.Internal.OpenAI: ContentParts :: [TextContent] -> MessageContent
- Langchain.LLM.Internal.OpenAI: Delta :: Maybe Text -> Delta
- Langchain.LLM.Internal.OpenAI: Developer :: Role
- Langchain.LLM.Internal.OpenAI: Function :: Role
- Langchain.LLM.Internal.OpenAI: FunctionCall :: FinishReason
- Langchain.LLM.Internal.OpenAI: FunctionCall_ :: Text -> Text -> FunctionCall_
- Langchain.LLM.Internal.OpenAI: Function_ :: Text -> Maybe Text -> Maybe Value -> Maybe Bool -> Function_
- Langchain.LLM.Internal.OpenAI: High :: ReasoningEffort
- Langchain.LLM.Internal.OpenAI: JsonObjectFormat :: ResponseFormat
- Langchain.LLM.Internal.OpenAI: JsonSchemaFormat :: Value -> ResponseFormat
- Langchain.LLM.Internal.OpenAI: Length :: FinishReason
- Langchain.LLM.Internal.OpenAI: LogProbContent :: Maybe [Int] -> Double -> Text -> [TopLogProb] -> LogProbContent
- Langchain.LLM.Internal.OpenAI: LogProbs :: Maybe [LogProbContent] -> Maybe [LogProbContent] -> LogProbs
- Langchain.LLM.Internal.OpenAI: Low :: ReasoningEffort
- Langchain.LLM.Internal.OpenAI: Medium :: ReasoningEffort
- Langchain.LLM.Internal.OpenAI: Message :: Role -> Maybe MessageContent -> Maybe Text -> Maybe FunctionCall_ -> Maybe [ToolCall] -> Maybe Text -> Maybe AudioResponse -> Maybe Text -> Message
- Langchain.LLM.Internal.OpenAI: None :: ToolChoice
- Langchain.LLM.Internal.OpenAI: OpenAIStreamHandler :: (ChatCompletionChunk -> IO ()) -> IO () -> OpenAIStreamHandler
- Langchain.LLM.Internal.OpenAI: PredictionContent :: MessageContent -> Text -> PredictionContent
- Langchain.LLM.Internal.OpenAI: PredictionOutput :: Text -> MessageContent -> PredictionOutput
- Langchain.LLM.Internal.OpenAI: PromptTokensDetails :: Int -> Int -> PromptTokensDetails
- Langchain.LLM.Internal.OpenAI: Required :: ToolChoice
- Langchain.LLM.Internal.OpenAI: SpecificTool :: SpecificToolChoice -> ToolChoice
- Langchain.LLM.Internal.OpenAI: SpecificToolChoice :: Text -> Value -> SpecificToolChoice
- Langchain.LLM.Internal.OpenAI: Stop :: FinishReason
- Langchain.LLM.Internal.OpenAI: StreamOptions :: Bool -> StreamOptions
- Langchain.LLM.Internal.OpenAI: StringContent :: Text -> MessageContent
- Langchain.LLM.Internal.OpenAI: System :: Role
- Langchain.LLM.Internal.OpenAI: TextContent :: Text -> Text -> TextContent
- Langchain.LLM.Internal.OpenAI: TextModality :: Modality
- Langchain.LLM.Internal.OpenAI: Tool :: Role
- Langchain.LLM.Internal.OpenAI: ToolCall :: Text -> Text -> FunctionCall_ -> ToolCall
- Langchain.LLM.Internal.OpenAI: ToolCalls :: FinishReason
- Langchain.LLM.Internal.OpenAI: Tool_ :: Text -> Function_ -> Tool_
- Langchain.LLM.Internal.OpenAI: TopLogProb :: Maybe [Int] -> Double -> Text -> TopLogProb
- Langchain.LLM.Internal.OpenAI: Usage :: Int -> Int -> Int -> Maybe CompletionTokensDetails -> Maybe PromptTokensDetails -> Usage
- Langchain.LLM.Internal.OpenAI: User :: Role
- Langchain.LLM.Internal.OpenAI: UserLocation :: ApproximateLocation -> UserLocation
- Langchain.LLM.Internal.OpenAI: WebSearchOptions :: Maybe Text -> Maybe UserLocation -> WebSearchOptions
- Langchain.LLM.Internal.OpenAI: [acceptedPredictionTokens] :: CompletionTokensDetails -> Int
- Langchain.LLM.Internal.OpenAI: [approximate] :: UserLocation -> ApproximateLocation
- Langchain.LLM.Internal.OpenAI: [arguments] :: FunctionCall_ -> Text
- Langchain.LLM.Internal.OpenAI: [audioResponseData] :: AudioResponse -> Text
- Langchain.LLM.Internal.OpenAI: [audioResponseId] :: AudioResponse -> Text
- Langchain.LLM.Internal.OpenAI: [audioTokens] :: CompletionTokensDetails -> Int
- Langchain.LLM.Internal.OpenAI: [audio] :: ChatCompletionRequest -> Maybe AudioConfig
- Langchain.LLM.Internal.OpenAI: [bytes] :: LogProbContent -> Maybe [Int]
- Langchain.LLM.Internal.OpenAI: [cachedTokens] :: PromptTokensDetails -> Int
- Langchain.LLM.Internal.OpenAI: [choiceFinishReason] :: Choice -> Maybe FinishReason
- Langchain.LLM.Internal.OpenAI: [choiceLogprobs] :: Choice -> Maybe LogProbs
- Langchain.LLM.Internal.OpenAI: [choices] :: ChatCompletionResponse -> [Choice]
- Langchain.LLM.Internal.OpenAI: [chunkChoices] :: ChatCompletionChunk -> [ChunkChoice]
- Langchain.LLM.Internal.OpenAI: [completionTokensDetails] :: Usage -> Maybe CompletionTokensDetails
- Langchain.LLM.Internal.OpenAI: [completionTokens] :: Usage -> Int
- Langchain.LLM.Internal.OpenAI: [contentForDelta] :: Delta -> Maybe Text
- Langchain.LLM.Internal.OpenAI: [contentForLogProbs] :: LogProbs -> Maybe [LogProbContent]
- Langchain.LLM.Internal.OpenAI: [contentForPredictionContent] :: PredictionContent -> MessageContent
- Langchain.LLM.Internal.OpenAI: [contentForPredictionOutput] :: PredictionOutput -> MessageContent
- Langchain.LLM.Internal.OpenAI: [contentType] :: TextContent -> Text
- Langchain.LLM.Internal.OpenAI: [content] :: Message -> Maybe MessageContent
- Langchain.LLM.Internal.OpenAI: [created] :: ChatCompletionResponse -> Integer
- Langchain.LLM.Internal.OpenAI: [delta] :: ChunkChoice -> Delta
- Langchain.LLM.Internal.OpenAI: [description] :: Function_ -> Maybe Text
- Langchain.LLM.Internal.OpenAI: [expiresAt] :: AudioResponse -> Integer
- Langchain.LLM.Internal.OpenAI: [finishReason] :: ChunkChoice -> Maybe FinishReason
- Langchain.LLM.Internal.OpenAI: [format] :: AudioConfig -> Text
- Langchain.LLM.Internal.OpenAI: [frequencyPenalty] :: ChatCompletionRequest -> Maybe Double
- Langchain.LLM.Internal.OpenAI: [functionCallName] :: FunctionCall_ -> Text
- Langchain.LLM.Internal.OpenAI: [functionCall] :: Message -> Maybe FunctionCall_
- Langchain.LLM.Internal.OpenAI: [functionName] :: Function_ -> Text
- Langchain.LLM.Internal.OpenAI: [function] :: Tool_ -> Function_
- Langchain.LLM.Internal.OpenAI: [id_] :: ChatCompletionResponse -> Text
- Langchain.LLM.Internal.OpenAI: [includeUsage] :: StreamOptions -> Bool
- Langchain.LLM.Internal.OpenAI: [index] :: Choice -> Int
- Langchain.LLM.Internal.OpenAI: [locationType] :: ApproximateLocation -> Text
- Langchain.LLM.Internal.OpenAI: [logProbContentTopLogprobs] :: LogProbContent -> [TopLogProb]
- Langchain.LLM.Internal.OpenAI: [logProbsRefusal] :: LogProbs -> Maybe [LogProbContent]
- Langchain.LLM.Internal.OpenAI: [logitBias] :: ChatCompletionRequest -> Maybe (Map Text Double)
- Langchain.LLM.Internal.OpenAI: [logprob] :: LogProbContent -> Double
- Langchain.LLM.Internal.OpenAI: [logprobs] :: ChatCompletionRequest -> Maybe Bool
- Langchain.LLM.Internal.OpenAI: [maxCompletionTokens] :: ChatCompletionRequest -> Maybe Int
- Langchain.LLM.Internal.OpenAI: [maxTokens] :: ChatCompletionRequest -> Maybe Int
- Langchain.LLM.Internal.OpenAI: [messageAudio] :: Message -> Maybe AudioResponse
- Langchain.LLM.Internal.OpenAI: [messageToolCallId] :: Message -> Maybe Text
- Langchain.LLM.Internal.OpenAI: [message] :: Choice -> Message
- Langchain.LLM.Internal.OpenAI: [messages] :: ChatCompletionRequest -> [Message]
- Langchain.LLM.Internal.OpenAI: [metadata] :: ChatCompletionRequest -> Maybe (Map Text Text)
- Langchain.LLM.Internal.OpenAI: [modalities] :: ChatCompletionRequest -> Maybe [Modality]
- Langchain.LLM.Internal.OpenAI: [model] :: ChatCompletionRequest -> Text
- Langchain.LLM.Internal.OpenAI: [n] :: ChatCompletionRequest -> Maybe Int
- Langchain.LLM.Internal.OpenAI: [name] :: Message -> Maybe Text
- Langchain.LLM.Internal.OpenAI: [object_] :: ChatCompletionResponse -> Text
- Langchain.LLM.Internal.OpenAI: [onComplete] :: OpenAIStreamHandler -> IO ()
- Langchain.LLM.Internal.OpenAI: [onToken] :: OpenAIStreamHandler -> ChatCompletionChunk -> IO ()
- Langchain.LLM.Internal.OpenAI: [parallelToolCalls] :: ChatCompletionRequest -> Maybe Bool
- Langchain.LLM.Internal.OpenAI: [parameters] :: Function_ -> Maybe Value
- Langchain.LLM.Internal.OpenAI: [predictionContentType] :: PredictionContent -> Text
- Langchain.LLM.Internal.OpenAI: [predictionType] :: PredictionOutput -> Text
- Langchain.LLM.Internal.OpenAI: [prediction] :: ChatCompletionRequest -> Maybe PredictionOutput
- Langchain.LLM.Internal.OpenAI: [presencePenalty] :: ChatCompletionRequest -> Maybe Double
- Langchain.LLM.Internal.OpenAI: [promptTokenDetailsAudioTokens] :: PromptTokensDetails -> Int
- Langchain.LLM.Internal.OpenAI: [promptTokensDetails] :: Usage -> Maybe PromptTokensDetails
- Langchain.LLM.Internal.OpenAI: [promptTokens] :: Usage -> Int
- Langchain.LLM.Internal.OpenAI: [reasoningEffort] :: ChatCompletionRequest -> Maybe ReasoningEffort
- Langchain.LLM.Internal.OpenAI: [reasoningTokens] :: CompletionTokensDetails -> Int
- Langchain.LLM.Internal.OpenAI: [refusal] :: Message -> Maybe Text
- Langchain.LLM.Internal.OpenAI: [rejectedPredictionTokens] :: CompletionTokensDetails -> Int
- Langchain.LLM.Internal.OpenAI: [responseFormat] :: ChatCompletionRequest -> Maybe ResponseFormat
- Langchain.LLM.Internal.OpenAI: [responseModel] :: ChatCompletionResponse -> Text
- Langchain.LLM.Internal.OpenAI: [responseServiceTier] :: ChatCompletionResponse -> Maybe Text
- Langchain.LLM.Internal.OpenAI: [role] :: Message -> Role
- Langchain.LLM.Internal.OpenAI: [searchContextSize] :: WebSearchOptions -> Maybe Text
- Langchain.LLM.Internal.OpenAI: [seed] :: ChatCompletionRequest -> Maybe Int
- Langchain.LLM.Internal.OpenAI: [serviceTier] :: ChatCompletionRequest -> Maybe Text
- Langchain.LLM.Internal.OpenAI: [specificToolChoiceFunction] :: SpecificToolChoice -> Value
- Langchain.LLM.Internal.OpenAI: [specificToolChoiceToolType] :: SpecificToolChoice -> Text
- Langchain.LLM.Internal.OpenAI: [stop] :: ChatCompletionRequest -> Maybe (Either Text [Text])
- Langchain.LLM.Internal.OpenAI: [store] :: ChatCompletionRequest -> Maybe Bool
- Langchain.LLM.Internal.OpenAI: [streamOptions] :: ChatCompletionRequest -> Maybe StreamOptions
- Langchain.LLM.Internal.OpenAI: [stream] :: ChatCompletionRequest -> Maybe Bool
- Langchain.LLM.Internal.OpenAI: [strict] :: Function_ -> Maybe Bool
- Langchain.LLM.Internal.OpenAI: [systemFingerprint] :: ChatCompletionResponse -> Text
- Langchain.LLM.Internal.OpenAI: [temperature] :: ChatCompletionRequest -> Maybe Double
- Langchain.LLM.Internal.OpenAI: [text_] :: TextContent -> Text
- Langchain.LLM.Internal.OpenAI: [timeout] :: ChatCompletionRequest -> Maybe Int
- Langchain.LLM.Internal.OpenAI: [token] :: LogProbContent -> Text
- Langchain.LLM.Internal.OpenAI: [toolCallId] :: ToolCall -> Text
- Langchain.LLM.Internal.OpenAI: [toolCallToolType] :: ToolCall -> Text
- Langchain.LLM.Internal.OpenAI: [toolCallfunction] :: ToolCall -> FunctionCall_
- Langchain.LLM.Internal.OpenAI: [toolCalls] :: Message -> Maybe [ToolCall]
- Langchain.LLM.Internal.OpenAI: [toolChoice] :: ChatCompletionRequest -> Maybe ToolChoice
- Langchain.LLM.Internal.OpenAI: [toolType] :: Tool_ -> Text
- Langchain.LLM.Internal.OpenAI: [tools] :: ChatCompletionRequest -> Maybe [Tool_]
- Langchain.LLM.Internal.OpenAI: [topLogProbBytes] :: TopLogProb -> Maybe [Int]
- Langchain.LLM.Internal.OpenAI: [topLogProbLogprob] :: TopLogProb -> Double
- Langchain.LLM.Internal.OpenAI: [topLogProbToken] :: TopLogProb -> Text
- Langchain.LLM.Internal.OpenAI: [topLogprobs] :: ChatCompletionRequest -> Maybe Int
- Langchain.LLM.Internal.OpenAI: [topP] :: ChatCompletionRequest -> Maybe Double
- Langchain.LLM.Internal.OpenAI: [totalTokens] :: Usage -> Int
- Langchain.LLM.Internal.OpenAI: [transcript] :: AudioResponse -> Text
- Langchain.LLM.Internal.OpenAI: [usage] :: ChatCompletionResponse -> Usage
- Langchain.LLM.Internal.OpenAI: [userLocation] :: WebSearchOptions -> Maybe UserLocation
- Langchain.LLM.Internal.OpenAI: [user] :: ChatCompletionRequest -> Maybe Text
- Langchain.LLM.Internal.OpenAI: [voice] :: AudioConfig -> Text
- Langchain.LLM.Internal.OpenAI: [webSearchOptions] :: ChatCompletionRequest -> Maybe WebSearchOptions
- Langchain.LLM.Internal.OpenAI: createChatCompletion :: Text -> ChatCompletionRequest -> IO (Either String ChatCompletionResponse)
- Langchain.LLM.Internal.OpenAI: createChatCompletionStream :: Text -> ChatCompletionRequest -> OpenAIStreamHandler -> IO (Either String ())
- Langchain.LLM.Internal.OpenAI: data ApproximateLocation
- Langchain.LLM.Internal.OpenAI: data AudioConfig
- Langchain.LLM.Internal.OpenAI: data AudioResponse
- Langchain.LLM.Internal.OpenAI: data ChatCompletionChunk
- Langchain.LLM.Internal.OpenAI: data ChatCompletionRequest
- Langchain.LLM.Internal.OpenAI: data ChatCompletionResponse
- Langchain.LLM.Internal.OpenAI: data Choice
- Langchain.LLM.Internal.OpenAI: data ChunkChoice
- Langchain.LLM.Internal.OpenAI: data CompletionTokensDetails
- Langchain.LLM.Internal.OpenAI: data Delta
- Langchain.LLM.Internal.OpenAI: data FinishReason
- Langchain.LLM.Internal.OpenAI: data FunctionCall_
- Langchain.LLM.Internal.OpenAI: data Function_
- Langchain.LLM.Internal.OpenAI: data LogProbContent
- Langchain.LLM.Internal.OpenAI: data LogProbs
- Langchain.LLM.Internal.OpenAI: data Message
- Langchain.LLM.Internal.OpenAI: data MessageContent
- Langchain.LLM.Internal.OpenAI: data Modality
- Langchain.LLM.Internal.OpenAI: data OpenAIStreamHandler
- Langchain.LLM.Internal.OpenAI: data PredictionContent
- Langchain.LLM.Internal.OpenAI: data PredictionOutput
- Langchain.LLM.Internal.OpenAI: data PromptTokensDetails
- Langchain.LLM.Internal.OpenAI: data ReasoningEffort
- Langchain.LLM.Internal.OpenAI: data ResponseFormat
- Langchain.LLM.Internal.OpenAI: data Role
- Langchain.LLM.Internal.OpenAI: data SpecificToolChoice
- Langchain.LLM.Internal.OpenAI: data StreamOptions
- Langchain.LLM.Internal.OpenAI: data TextContent
- Langchain.LLM.Internal.OpenAI: data ToolCall
- Langchain.LLM.Internal.OpenAI: data ToolChoice
- Langchain.LLM.Internal.OpenAI: data Tool_
- Langchain.LLM.Internal.OpenAI: data TopLogProb
- Langchain.LLM.Internal.OpenAI: data Usage
- Langchain.LLM.Internal.OpenAI: data UserLocation
- Langchain.LLM.Internal.OpenAI: data WebSearchOptions
- Langchain.LLM.Internal.OpenAI: defaultAudioConfig :: AudioConfig
- Langchain.LLM.Internal.OpenAI: defaultChatCompletionRequest :: ChatCompletionRequest
- Langchain.LLM.Internal.OpenAI: defaultFunction :: Function_
- Langchain.LLM.Internal.OpenAI: defaultMessage :: Message
- Langchain.LLM.Internal.OpenAI: defaultPredictionOutput :: PredictionOutput
- Langchain.LLM.Internal.OpenAI: defaultReasoningEffort :: ReasoningEffort
- Langchain.LLM.Internal.OpenAI: defaultResponseFormat :: ResponseFormat
- Langchain.LLM.Internal.OpenAI: defaultSpecificToolChoice :: SpecificToolChoice
- Langchain.LLM.Internal.OpenAI: defaultStreamOptions :: StreamOptions
- Langchain.LLM.Internal.OpenAI: defaultToolChoice :: ToolChoice
- Langchain.LLM.Internal.OpenAI: defaultUserLocation :: UserLocation
- Langchain.LLM.Internal.OpenAI: defaultWebSearchOptions :: WebSearchOptions
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.ApproximateLocation
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.AudioConfig
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.AudioResponse
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.ChatCompletionChunk
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.ChatCompletionResponse
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.Choice
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.ChunkChoice
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.CompletionTokensDetails
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.Delta
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.FinishReason
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.FunctionCall_
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.Function_
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.LogProbContent
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.LogProbs
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.Message
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.MessageContent
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.Modality
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.PredictionContent
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.PredictionOutput
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.PromptTokensDetails
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.ReasoningEffort
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.ResponseFormat
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.Role
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.SpecificToolChoice
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.StreamOptions
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.TextContent
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.ToolCall
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.ToolChoice
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.Tool_
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.TopLogProb
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.Usage
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.UserLocation
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Internal.OpenAI.WebSearchOptions
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.LLM.Internal.OpenAI.ApproximateLocation
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.LLM.Internal.OpenAI.AudioConfig
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.LLM.Internal.OpenAI.AudioResponse
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.LLM.Internal.OpenAI.ChatCompletionRequest
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.LLM.Internal.OpenAI.FunctionCall_
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.LLM.Internal.OpenAI.Function_
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.LLM.Internal.OpenAI.Message
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.LLM.Internal.OpenAI.MessageContent
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.LLM.Internal.OpenAI.Modality
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.LLM.Internal.OpenAI.PredictionContent
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.LLM.Internal.OpenAI.PredictionOutput
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.LLM.Internal.OpenAI.ReasoningEffort
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.LLM.Internal.OpenAI.ResponseFormat
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.LLM.Internal.OpenAI.Role
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.LLM.Internal.OpenAI.SpecificToolChoice
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.LLM.Internal.OpenAI.StreamOptions
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.LLM.Internal.OpenAI.TextContent
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.LLM.Internal.OpenAI.ToolCall
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.LLM.Internal.OpenAI.ToolChoice
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.LLM.Internal.OpenAI.Tool_
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.LLM.Internal.OpenAI.UserLocation
- Langchain.LLM.Internal.OpenAI: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.LLM.Internal.OpenAI.WebSearchOptions
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.ApproximateLocation
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.AudioConfig
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.AudioResponse
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.ChatCompletionRequest
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.ChatCompletionResponse
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.Choice
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.CompletionTokensDetails
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.FinishReason
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.FunctionCall_
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.Function_
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.LogProbContent
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.LogProbs
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.Message
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.MessageContent
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.Modality
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.PredictionContent
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.PredictionOutput
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.PromptTokensDetails
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.ReasoningEffort
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.ResponseFormat
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.Role
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.SpecificToolChoice
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.StreamOptions
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.TextContent
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.ToolCall
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.ToolChoice
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.Tool_
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.TopLogProb
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.Usage
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.UserLocation
- Langchain.LLM.Internal.OpenAI: instance GHC.Classes.Eq Langchain.LLM.Internal.OpenAI.WebSearchOptions
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.ApproximateLocation
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.AudioConfig
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.AudioResponse
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.ChatCompletionRequest
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.ChatCompletionResponse
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.Choice
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.CompletionTokensDetails
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.FinishReason
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.FunctionCall_
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.Function_
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.LogProbContent
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.LogProbs
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.Message
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.MessageContent
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.Modality
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.PredictionContent
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.PredictionOutput
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.PromptTokensDetails
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.ReasoningEffort
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.ResponseFormat
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.Role
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.SpecificToolChoice
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.StreamOptions
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.TextContent
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.ToolCall
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.ToolChoice
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.Tool_
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.TopLogProb
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.Usage
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.UserLocation
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Generics.Generic Langchain.LLM.Internal.OpenAI.WebSearchOptions
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.ApproximateLocation
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.AudioConfig
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.AudioResponse
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.ChatCompletionChunk
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.ChatCompletionRequest
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.ChatCompletionResponse
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.Choice
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.ChunkChoice
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.CompletionTokensDetails
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.Delta
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.FinishReason
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.FunctionCall_
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.Function_
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.LogProbContent
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.LogProbs
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.Message
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.MessageContent
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.Modality
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.PredictionContent
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.PredictionOutput
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.PromptTokensDetails
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.ReasoningEffort
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.ResponseFormat
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.Role
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.SpecificToolChoice
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.StreamOptions
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.TextContent
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.ToolCall
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.ToolChoice
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.Tool_
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.TopLogProb
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.Usage
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.UserLocation
- Langchain.LLM.Internal.OpenAI: instance GHC.Internal.Show.Show Langchain.LLM.Internal.OpenAI.WebSearchOptions
- Langchain.LLM.Ollama: OllamaParams :: Maybe Text -> Maybe [Text] -> Maybe Format -> Maybe Text -> Maybe Text -> Maybe Bool -> Maybe Text -> Maybe Text -> Maybe Int -> Maybe Value -> Maybe [Value] -> OllamaParams
- Langchain.LLM.Ollama: [format] :: OllamaParams -> Maybe Format
- Langchain.LLM.Ollama: [hostUrl] :: OllamaParams -> Maybe Text
- Langchain.LLM.Ollama: [images] :: OllamaParams -> Maybe [Text]
- Langchain.LLM.Ollama: [keepAlive] :: OllamaParams -> Maybe Text
- Langchain.LLM.Ollama: [options] :: OllamaParams -> Maybe Value
- Langchain.LLM.Ollama: [raw] :: OllamaParams -> Maybe Bool
- Langchain.LLM.Ollama: [responseTimeOut] :: OllamaParams -> Maybe Int
- Langchain.LLM.Ollama: [suffix] :: OllamaParams -> Maybe Text
- Langchain.LLM.Ollama: [system] :: OllamaParams -> Maybe Text
- Langchain.LLM.Ollama: [template] :: OllamaParams -> Maybe Text
- Langchain.LLM.Ollama: [tools] :: OllamaParams -> Maybe [Value]
- Langchain.LLM.Ollama: data OllamaParams
- Langchain.LLM.Ollama: defaultOllamaParams :: OllamaParams
- Langchain.LLM.Ollama: instance GHC.Classes.Eq Langchain.LLM.Ollama.OllamaParams
- Langchain.LLM.Ollama: instance GHC.Internal.Show.Show Langchain.LLM.Ollama.OllamaParams
- Langchain.LLM.OpenAI: OpenAIParams :: Maybe Int -> Maybe Double -> Maybe (Map Text Double) -> Maybe Bool -> Maybe Int -> Maybe Int -> Maybe (Map Text Text) -> Maybe [Modality] -> Maybe Int -> Maybe Bool -> Maybe PredictionOutput -> Maybe Double -> Maybe ReasoningEffort -> Maybe ResponseFormat -> Maybe Int -> Maybe Text -> Maybe (Either Text [Text]) -> Maybe Bool -> Maybe Double -> Maybe ToolChoice -> Maybe [Tool_] -> Maybe Int -> Maybe Double -> Maybe Text -> Maybe WebSearchOptions -> Maybe AudioConfig -> OpenAIParams
- Langchain.LLM.OpenAI: [audio] :: OpenAIParams -> Maybe AudioConfig
- Langchain.LLM.OpenAI: [frequencyPenalty] :: OpenAIParams -> Maybe Double
- Langchain.LLM.OpenAI: [logitBias] :: OpenAIParams -> Maybe (Map Text Double)
- Langchain.LLM.OpenAI: [logprobs] :: OpenAIParams -> Maybe Bool
- Langchain.LLM.OpenAI: [maxCompletionTokens] :: OpenAIParams -> Maybe Int
- Langchain.LLM.OpenAI: [maxTokens] :: OpenAIParams -> Maybe Int
- Langchain.LLM.OpenAI: [metadata] :: OpenAIParams -> Maybe (Map Text Text)
- Langchain.LLM.OpenAI: [modalities] :: OpenAIParams -> Maybe [Modality]
- Langchain.LLM.OpenAI: [n] :: OpenAIParams -> Maybe Int
- Langchain.LLM.OpenAI: [openAIModelName] :: OpenAI -> Text
- Langchain.LLM.OpenAI: [parallelToolCalls] :: OpenAIParams -> Maybe Bool
- Langchain.LLM.OpenAI: [prediction] :: OpenAIParams -> Maybe PredictionOutput
- Langchain.LLM.OpenAI: [presencePenalty] :: OpenAIParams -> Maybe Double
- Langchain.LLM.OpenAI: [reasoningEffort] :: OpenAIParams -> Maybe ReasoningEffort
- Langchain.LLM.OpenAI: [responseFormat] :: OpenAIParams -> Maybe ResponseFormat
- Langchain.LLM.OpenAI: [seed] :: OpenAIParams -> Maybe Int
- Langchain.LLM.OpenAI: [serviceTier] :: OpenAIParams -> Maybe Text
- Langchain.LLM.OpenAI: [stop] :: OpenAIParams -> Maybe (Either Text [Text])
- Langchain.LLM.OpenAI: [store] :: OpenAIParams -> Maybe Bool
- Langchain.LLM.OpenAI: [temperature] :: OpenAIParams -> Maybe Double
- Langchain.LLM.OpenAI: [timeout] :: OpenAIParams -> Maybe Int
- Langchain.LLM.OpenAI: [toolChoice] :: OpenAIParams -> Maybe ToolChoice
- Langchain.LLM.OpenAI: [tools] :: OpenAIParams -> Maybe [Tool_]
- Langchain.LLM.OpenAI: [topLogprobs] :: OpenAIParams -> Maybe Int
- Langchain.LLM.OpenAI: [topP] :: OpenAIParams -> Maybe Double
- Langchain.LLM.OpenAI: [user] :: OpenAIParams -> Maybe Text
- Langchain.LLM.OpenAI: [webSearchOptions] :: OpenAIParams -> Maybe WebSearchOptions
- Langchain.LLM.OpenAI: data OpenAIParams
- Langchain.LLM.OpenAI: defaultOpenAIParams :: OpenAIParams
- Langchain.Tool.WikipediaTool: data PageResponse
- Langchain.Tool.WikipediaTool: data Pages
- Langchain.Tool.WikipediaTool: data SearchQuery
- Langchain.Tool.WikipediaTool: data SearchResponse
+ Langchain.Agent.Core: AgentAction :: [ToolCall] -> Text -> Map Text Text -> AgentAction
+ Langchain.Agent.Core: AgentCallbacks :: (Text -> IO ()) -> (AgentAction -> IO ()) -> (Text -> IO ()) -> (AgentFinish -> IO ()) -> (AgentStep -> IO ()) -> AgentCallbacks
+ Langchain.Agent.Core: AgentConfig :: Int -> Maybe Int -> Bool -> Maybe SomeMemory -> AgentConfig
+ Langchain.Agent.Core: AgentFinish :: Text -> Map Text Text -> Text -> AgentFinish
+ Langchain.Agent.Core: AgentState :: SomeMemory -> Text -> Int -> AgentState
+ Langchain.Agent.Core: AgentStep :: AgentAction -> Text -> UTCTime -> AgentStep
+ Langchain.Agent.Core: Continue :: AgentAction -> PlanResult
+ Langchain.Agent.Core: Done :: AgentFinish -> PlanResult
+ Langchain.Agent.Core: [SomeMemory] :: forall m. BaseMemory m => m -> SomeMemory
+ Langchain.Agent.Core: [ToolAcceptingToolCall] :: forall t. (Tool t, Input t ~ ToolCall, Output t ~ Text) => t -> ToolAcceptingToolCall
+ Langchain.Agent.Core: [actionLog] :: AgentAction -> Text
+ Langchain.Agent.Core: [actionMetadata] :: AgentAction -> Map Text Text
+ Langchain.Agent.Core: [actionToolCall] :: AgentAction -> [ToolCall]
+ Langchain.Agent.Core: [agentInput] :: AgentState -> Text
+ Langchain.Agent.Core: [agentIterations] :: AgentState -> Int
+ Langchain.Agent.Core: [agentMemory] :: AgentState -> SomeMemory
+ Langchain.Agent.Core: [agentOutput] :: AgentFinish -> Text
+ Langchain.Agent.Core: [finishLog] :: AgentFinish -> Text
+ Langchain.Agent.Core: [finishMetadata] :: AgentFinish -> Map Text Text
+ Langchain.Agent.Core: [maxExecutionTime] :: AgentConfig -> Maybe Int
+ Langchain.Agent.Core: [maxIterations] :: AgentConfig -> Int
+ Langchain.Agent.Core: [onAgentAction] :: AgentCallbacks -> AgentAction -> IO ()
+ Langchain.Agent.Core: [onAgentFinish] :: AgentCallbacks -> AgentFinish -> IO ()
+ Langchain.Agent.Core: [onAgentObservation] :: AgentCallbacks -> Text -> IO ()
+ Langchain.Agent.Core: [onAgentStart] :: AgentCallbacks -> Text -> IO ()
+ Langchain.Agent.Core: [onAgentStep] :: AgentCallbacks -> AgentStep -> IO ()
+ Langchain.Agent.Core: [stateMemory] :: AgentConfig -> Maybe SomeMemory
+ Langchain.Agent.Core: [stepAction] :: AgentStep -> AgentAction
+ Langchain.Agent.Core: [stepObservation] :: AgentStep -> Text
+ Langchain.Agent.Core: [stepTimestamp] :: AgentStep -> UTCTime
+ Langchain.Agent.Core: [verboseLogging] :: AgentConfig -> Bool
+ Langchain.Agent.Core: class Agent a
+ Langchain.Agent.Core: data AgentAction
+ Langchain.Agent.Core: data AgentCallbacks
+ Langchain.Agent.Core: data AgentConfig
+ Langchain.Agent.Core: data AgentFinish
+ Langchain.Agent.Core: data AgentState
+ Langchain.Agent.Core: data AgentStep
+ Langchain.Agent.Core: data PlanResult
+ Langchain.Agent.Core: data SomeMemory
+ Langchain.Agent.Core: data ToolAcceptingToolCall
+ Langchain.Agent.Core: defaultAgentCallbacks :: AgentCallbacks
+ Langchain.Agent.Core: defaultAgentConfig :: AgentConfig
+ Langchain.Agent.Core: executeTool :: Agent a => a -> ToolCall -> IO (LangchainResult Text)
+ Langchain.Agent.Core: executeToolM :: (Agent a, MonadIO m) => a -> ToolCall -> m (LangchainResult Text)
+ Langchain.Agent.Core: finalize :: Agent a => a -> AgentState -> IO ()
+ Langchain.Agent.Core: finalizeM :: (Agent a, MonadIO m) => a -> AgentState -> m ()
+ Langchain.Agent.Core: getTools :: Agent a => a -> [ToolAcceptingToolCall]
+ Langchain.Agent.Core: initialize :: Agent a => a -> AgentState -> IO (LangchainResult AgentState)
+ Langchain.Agent.Core: initializeM :: (Agent a, MonadIO m) => a -> AgentState -> m (LangchainResult AgentState)
+ Langchain.Agent.Core: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Agent.Core.AgentFinish
+ Langchain.Agent.Core: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Agent.Core.AgentFinish
+ Langchain.Agent.Core: instance GHC.Classes.Eq Langchain.Agent.Core.AgentAction
+ Langchain.Agent.Core: instance GHC.Classes.Eq Langchain.Agent.Core.AgentFinish
+ Langchain.Agent.Core: instance GHC.Classes.Eq Langchain.Agent.Core.AgentStep
+ Langchain.Agent.Core: instance GHC.Classes.Eq Langchain.Agent.Core.PlanResult
+ Langchain.Agent.Core: instance GHC.Classes.Eq Langchain.Agent.Core.ToolAcceptingToolCall
+ Langchain.Agent.Core: instance GHC.Internal.Generics.Generic Langchain.Agent.Core.AgentFinish
+ Langchain.Agent.Core: instance GHC.Internal.Show.Show Langchain.Agent.Core.AgentAction
+ Langchain.Agent.Core: instance GHC.Internal.Show.Show Langchain.Agent.Core.AgentConfig
+ Langchain.Agent.Core: instance GHC.Internal.Show.Show Langchain.Agent.Core.AgentFinish
+ Langchain.Agent.Core: instance GHC.Internal.Show.Show Langchain.Agent.Core.AgentState
+ Langchain.Agent.Core: instance GHC.Internal.Show.Show Langchain.Agent.Core.AgentStep
+ Langchain.Agent.Core: instance GHC.Internal.Show.Show Langchain.Agent.Core.PlanResult
+ Langchain.Agent.Core: instance GHC.Internal.Show.Show Langchain.Agent.Core.SomeMemory
+ Langchain.Agent.Core: instance GHC.Internal.Show.Show Langchain.Agent.Core.ToolAcceptingToolCall
+ Langchain.Agent.Core: plan :: Agent a => a -> AgentState -> IO (LangchainResult PlanResult)
+ Langchain.Agent.Core: planM :: (Agent a, MonadIO m) => a -> AgentState -> m (LangchainResult PlanResult)
+ Langchain.Agent.Executor: AgentExecutionResult :: AgentFinish -> [AgentStep] -> ExecutionMetrics -> AgentExecutionResult
+ Langchain.Agent.Executor: ExecutionMetrics :: Int -> Double -> Int -> Bool -> ExecutionMetrics
+ Langchain.Agent.Executor: [executionFinish] :: AgentExecutionResult -> AgentFinish
+ Langchain.Agent.Executor: [executionMetrics] :: AgentExecutionResult -> ExecutionMetrics
+ Langchain.Agent.Executor: [executionSteps] :: AgentExecutionResult -> [AgentStep]
+ Langchain.Agent.Executor: [metricsExecutionTime] :: ExecutionMetrics -> Double
+ Langchain.Agent.Executor: [metricsIterations] :: ExecutionMetrics -> Int
+ Langchain.Agent.Executor: [metricsSuccess] :: ExecutionMetrics -> Bool
+ Langchain.Agent.Executor: [metricsToolCalls] :: ExecutionMetrics -> Int
+ Langchain.Agent.Executor: createInitialState :: Maybe SomeMemory -> Text -> AgentState
+ Langchain.Agent.Executor: data AgentExecutionResult
+ Langchain.Agent.Executor: data ExecutionMetrics
+ Langchain.Agent.Executor: instance GHC.Classes.Eq Langchain.Agent.Executor.AgentExecutionResult
+ Langchain.Agent.Executor: instance GHC.Classes.Eq Langchain.Agent.Executor.ExecutionMetrics
+ Langchain.Agent.Executor: instance GHC.Internal.Show.Show Langchain.Agent.Executor.AgentExecutionResult
+ Langchain.Agent.Executor: instance GHC.Internal.Show.Show Langchain.Agent.Executor.ExecutionMetrics
+ Langchain.Agent.Executor: runAgentExecutor :: Agent a => a -> AgentConfig -> AgentCallbacks -> [AgentMiddleware a] -> Text -> IO (LangchainResult AgentExecutionResult)
+ Langchain.Agent.Middleware: AgentMiddleware :: ((AgentState, a) -> IO (LangchainResult (AgentState, a))) -> ((AgentState, a) -> IO (LangchainResult (AgentState, a))) -> ((AgentState, a) -> IO (LangchainResult (AgentState, a))) -> ((AgentState, a) -> IO (LangchainResult (AgentState, a))) -> ((AgentState, a) -> IO (LangchainResult (AgentState, a))) -> ((AgentState, a) -> IO (LangchainResult (AgentState, a))) -> AgentMiddleware a
+ Langchain.Agent.Middleware: [afterAgent] :: AgentMiddleware a -> (AgentState, a) -> IO (LangchainResult (AgentState, a))
+ Langchain.Agent.Middleware: [afterModelCall] :: AgentMiddleware a -> (AgentState, a) -> IO (LangchainResult (AgentState, a))
+ Langchain.Agent.Middleware: [afterToolCall] :: AgentMiddleware a -> (AgentState, a) -> IO (LangchainResult (AgentState, a))
+ Langchain.Agent.Middleware: [beforeAgent] :: AgentMiddleware a -> (AgentState, a) -> IO (LangchainResult (AgentState, a))
+ Langchain.Agent.Middleware: [beforeModelCall] :: AgentMiddleware a -> (AgentState, a) -> IO (LangchainResult (AgentState, a))
+ Langchain.Agent.Middleware: [beforeToolCall] :: AgentMiddleware a -> (AgentState, a) -> IO (LangchainResult (AgentState, a))
+ Langchain.Agent.Middleware: applyMiddlewares :: (AgentMiddleware a -> (AgentState, a) -> IO (LangchainResult (AgentState, a))) -> [AgentMiddleware a] -> (AgentState, a) -> IO (LangchainResult (AgentState, a))
+ Langchain.Agent.Middleware: data Agent a => AgentMiddleware a
+ Langchain.Agent.Middleware: defaultMiddleware :: Agent a => AgentMiddleware a
+ Langchain.Agent.Middleware: humanInLoopMiddleware :: Agent a => AgentMiddleware a
+ Langchain.Agent.Middleware: toolCallLimitMiddleware :: Agent a => Int -> IO (AgentMiddleware a)
+ Langchain.Agent.ReAct: ReActAgent :: llm -> Maybe (LLMParams llm) -> Text -> Int -> [ToolAcceptingToolCall] -> ReActAgent llm
+ Langchain.Agent.ReAct: [reactLLMParams] :: ReActAgent llm -> Maybe (LLMParams llm)
+ Langchain.Agent.ReAct: [reactLLM] :: ReActAgent llm -> llm
+ Langchain.Agent.ReAct: [reactMaxThinkingSteps] :: ReActAgent llm -> Int
+ Langchain.Agent.ReAct: [reactSystemPrompt] :: ReActAgent llm -> Text
+ Langchain.Agent.ReAct: [reactTools] :: ReActAgent llm -> [ToolAcceptingToolCall]
+ Langchain.Agent.ReAct: createReActAgent :: llm -> Maybe (LLMParams llm) -> [ToolAcceptingToolCall] -> ReActAgent llm
+ Langchain.Agent.ReAct: createReActAgentWithPrompt :: llm -> Maybe (LLMParams llm) -> [ToolAcceptingToolCall] -> Text -> ReActAgent llm
+ Langchain.Agent.ReAct: data ReActAgent llm
+ Langchain.Agent.ReAct: instance Langchain.LLM.Core.LLM llm => Langchain.Agent.Core.Agent (Langchain.Agent.ReAct.ReActAgent llm)
+ Langchain.Agent.ReAct: reActSystemPrompt :: Text
+ Langchain.DocumentLoader.Core: loadAndSplitM :: (BaseLoader loader, MonadIO m) => loader -> m (LangchainResult [Text])
+ Langchain.DocumentLoader.Core: loadM :: (BaseLoader loader, MonadIO m) => loader -> m (LangchainResult [Document])
+ Langchain.DocumentLoader.FileLoader: newtype FileLoader
+ Langchain.DocumentLoader.PdfLoader: newtype PdfLoader
+ Langchain.Embeddings.Core: embedDocumentsM :: (Embeddings embed, MonadIO m) => embed -> [Document] -> m (LangchainResult [[Float]])
+ Langchain.Embeddings.Core: embedQueryM :: (Embeddings embed, MonadIO m) => embed -> Text -> m (LangchainResult [Float])
+ Langchain.Embeddings.Gemini: GeminiEmbeddings :: Text -> Maybe String -> Text -> Maybe Int -> Maybe EncodingFormat -> Maybe Text -> Maybe Int -> GeminiEmbeddings
+ Langchain.Embeddings.Gemini: [apiKey] :: GeminiEmbeddings -> Text
+ Langchain.Embeddings.Gemini: [baseUrl] :: GeminiEmbeddings -> Maybe String
+ Langchain.Embeddings.Gemini: [dimensions] :: GeminiEmbeddings -> Maybe Int
+ Langchain.Embeddings.Gemini: [embeddingsUser] :: GeminiEmbeddings -> Maybe Text
+ Langchain.Embeddings.Gemini: [encodingFormat] :: GeminiEmbeddings -> Maybe EncodingFormat
+ Langchain.Embeddings.Gemini: [model] :: GeminiEmbeddings -> Text
+ Langchain.Embeddings.Gemini: [timeout] :: GeminiEmbeddings -> Maybe Int
+ Langchain.Embeddings.Gemini: data GeminiEmbeddings
+ Langchain.Embeddings.Gemini: defaultGeminiEmbeddings :: GeminiEmbeddings
+ Langchain.Embeddings.Gemini: instance GHC.Classes.Eq Langchain.Embeddings.Gemini.GeminiEmbeddings
+ Langchain.Embeddings.Gemini: instance GHC.Internal.Generics.Generic Langchain.Embeddings.Gemini.GeminiEmbeddings
+ Langchain.Embeddings.Gemini: instance GHC.Internal.Show.Show Langchain.Embeddings.Gemini.GeminiEmbeddings
+ Langchain.Embeddings.Gemini: instance Langchain.Embeddings.Core.Embeddings Langchain.Embeddings.Gemini.GeminiEmbeddings
+ Langchain.Embeddings.Ollama: [modelOptions] :: OllamaEmbeddings -> Maybe ModelOptions
+ Langchain.Embeddings.OpenAI: Base64Format :: EncodingFormat
+ Langchain.Embeddings.OpenAI: FloatFormat :: EncodingFormat
+ Langchain.Embeddings.OpenAI: [baseUrl] :: OpenAIEmbeddings -> Maybe String
+ Langchain.Embeddings.OpenAI: data EncodingFormat
+ Langchain.Error: AgentError :: ErrorCategory
+ Langchain.Error: ConfigurationError :: ErrorCategory
+ Langchain.Error: Critical :: ErrorSeverity
+ Langchain.Error: DocumentLoaderError :: ErrorCategory
+ Langchain.Error: EmbeddingError :: ErrorCategory
+ Langchain.Error: ErrorContext :: Maybe Text -> Maybe Text -> Maybe Text -> [(Text, Text)] -> UTCTime -> ErrorContext
+ Langchain.Error: High :: ErrorSeverity
+ Langchain.Error: Info :: ErrorSeverity
+ Langchain.Error: InternalError :: ErrorCategory
+ Langchain.Error: LLMError :: ErrorCategory
+ Langchain.Error: LangchainError :: Text -> ErrorSeverity -> ErrorCategory -> Maybe ErrorContext -> Maybe LangchainError -> Maybe Text -> LangchainError
+ Langchain.Error: Low :: ErrorSeverity
+ Langchain.Error: Medium :: ErrorSeverity
+ Langchain.Error: MemoryError :: ErrorCategory
+ Langchain.Error: NetworkError :: ErrorCategory
+ Langchain.Error: ParsingError :: ErrorCategory
+ Langchain.Error: RunnableError :: ErrorCategory
+ Langchain.Error: ToolError :: ErrorCategory
+ Langchain.Error: ValidationError :: ErrorCategory
+ Langchain.Error: VectorStoreError :: ErrorCategory
+ Langchain.Error: [contextComponent] :: ErrorContext -> Maybe Text
+ Langchain.Error: [contextInput] :: ErrorContext -> Maybe Text
+ Langchain.Error: [contextMetadata] :: ErrorContext -> [(Text, Text)]
+ Langchain.Error: [contextOperation] :: ErrorContext -> Maybe Text
+ Langchain.Error: [contextTimestamp] :: ErrorContext -> UTCTime
+ Langchain.Error: [errorCategory] :: LangchainError -> ErrorCategory
+ Langchain.Error: [errorCause] :: LangchainError -> Maybe LangchainError
+ Langchain.Error: [errorCode] :: LangchainError -> Maybe Text
+ Langchain.Error: [errorContext] :: LangchainError -> Maybe ErrorContext
+ Langchain.Error: [errorMessage] :: LangchainError -> Text
+ Langchain.Error: [errorSeverity] :: LangchainError -> ErrorSeverity
+ Langchain.Error: addContext :: ErrorContext -> LangchainError -> LangchainError
+ Langchain.Error: agentError :: Text -> Maybe Text -> Maybe Text -> LangchainError
+ Langchain.Error: agentErrorWithContext :: Text -> Maybe Text -> Maybe Text -> ErrorContext -> LangchainError
+ Langchain.Error: catchToLangchainError :: IO a -> IO (LangchainResult a)
+ Langchain.Error: chainError :: Text -> LangchainError -> LangchainError
+ Langchain.Error: class (Typeable e, Show e) => Exception e
+ Langchain.Error: configurationError :: Text -> Maybe Text -> Maybe Text -> LangchainError
+ Langchain.Error: configurationErrorWithContext :: Text -> Maybe Text -> Maybe Text -> ErrorContext -> LangchainError
+ Langchain.Error: data ErrorCategory
+ Langchain.Error: data ErrorContext
+ Langchain.Error: data ErrorSeverity
+ Langchain.Error: data LangchainError
+ Langchain.Error: data SomeException
+ Langchain.Error: displayException :: Exception e => e -> String
+ Langchain.Error: documentLoaderError :: Text -> Maybe Text -> Maybe Text -> LangchainError
+ Langchain.Error: documentLoaderErrorWithContext :: Text -> Maybe Text -> Maybe Text -> ErrorContext -> LangchainError
+ Langchain.Error: embeddingError :: Text -> Maybe Text -> Maybe Text -> LangchainError
+ Langchain.Error: embeddingErrorWithContext :: Text -> Maybe Text -> Maybe Text -> ErrorContext -> LangchainError
+ Langchain.Error: fromException :: SomeException -> LangchainError
+ Langchain.Error: fromString :: String -> LangchainError
+ Langchain.Error: fromStringError :: String -> LangchainError
+ Langchain.Error: getCategory :: LangchainError -> ErrorCategory
+ Langchain.Error: getSeverity :: LangchainError -> ErrorSeverity
+ Langchain.Error: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Error.ErrorCategory
+ Langchain.Error: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Error.ErrorContext
+ Langchain.Error: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Error.ErrorSeverity
+ Langchain.Error: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Error.LangchainError
+ Langchain.Error: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Error.ErrorCategory
+ Langchain.Error: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Error.ErrorContext
+ Langchain.Error: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Error.ErrorSeverity
+ Langchain.Error: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Error.LangchainError
+ Langchain.Error: instance GHC.Classes.Eq Langchain.Error.ErrorCategory
+ Langchain.Error: instance GHC.Classes.Eq Langchain.Error.ErrorContext
+ Langchain.Error: instance GHC.Classes.Eq Langchain.Error.ErrorSeverity
+ Langchain.Error: instance GHC.Classes.Eq Langchain.Error.LangchainError
+ Langchain.Error: instance GHC.Classes.Ord Langchain.Error.ErrorSeverity
+ Langchain.Error: instance GHC.Internal.Exception.Type.Exception Langchain.Error.LangchainError
+ Langchain.Error: instance GHC.Internal.Generics.Generic Langchain.Error.ErrorCategory
+ Langchain.Error: instance GHC.Internal.Generics.Generic Langchain.Error.ErrorContext
+ Langchain.Error: instance GHC.Internal.Generics.Generic Langchain.Error.ErrorSeverity
+ Langchain.Error: instance GHC.Internal.Generics.Generic Langchain.Error.LangchainError
+ Langchain.Error: instance GHC.Internal.Show.Show Langchain.Error.ErrorCategory
+ Langchain.Error: instance GHC.Internal.Show.Show Langchain.Error.ErrorContext
+ Langchain.Error: instance GHC.Internal.Show.Show Langchain.Error.ErrorSeverity
+ Langchain.Error: instance GHC.Internal.Show.Show Langchain.Error.LangchainError
+ Langchain.Error: internalError :: Text -> Maybe Text -> Maybe Text -> LangchainError
+ Langchain.Error: internalErrorWithContext :: Text -> Maybe Text -> Maybe Text -> ErrorContext -> LangchainError
+ Langchain.Error: isRetryable :: LangchainError -> Bool
+ Langchain.Error: liftStringError :: Either String a -> LangchainResult a
+ Langchain.Error: llmError :: Text -> Maybe Text -> Maybe Text -> LangchainError
+ Langchain.Error: llmErrorWithContext :: Text -> Maybe Text -> Maybe Text -> ErrorContext -> LangchainError
+ Langchain.Error: logError :: MonadIO m => LangchainError -> m ()
+ Langchain.Error: mapError :: (LangchainError -> LangchainError) -> LangchainResult a -> LangchainResult a
+ Langchain.Error: memoryError :: Text -> Maybe Text -> Maybe Text -> LangchainError
+ Langchain.Error: memoryErrorWithContext :: Text -> Maybe Text -> Maybe Text -> ErrorContext -> LangchainError
+ Langchain.Error: networkError :: Text -> Maybe Text -> Maybe Text -> LangchainError
+ Langchain.Error: networkErrorWithContext :: Text -> Maybe Text -> Maybe Text -> ErrorContext -> LangchainError
+ Langchain.Error: parsingError :: Text -> Maybe Text -> Maybe Text -> LangchainError
+ Langchain.Error: parsingErrorWithContext :: Text -> Maybe Text -> Maybe Text -> ErrorContext -> LangchainError
+ Langchain.Error: runnableError :: Text -> Maybe Text -> Maybe Text -> LangchainError
+ Langchain.Error: runnableErrorWithContext :: Text -> Maybe Text -> Maybe Text -> ErrorContext -> LangchainError
+ Langchain.Error: simpleError :: Text -> LangchainError
+ Langchain.Error: toString :: LangchainError -> String
+ Langchain.Error: toText :: LangchainError -> Text
+ Langchain.Error: toolError :: Text -> Maybe Text -> Maybe Text -> LangchainError
+ Langchain.Error: toolErrorWithContext :: Text -> Maybe Text -> Maybe Text -> ErrorContext -> LangchainError
+ Langchain.Error: try :: Exception e => IO a -> IO (Either e a)
+ Langchain.Error: type LangchainIO a = IO LangchainResult a
+ Langchain.Error: type LangchainResult a = Either LangchainError a
+ Langchain.Error: validationError :: Text -> Maybe Text -> Maybe Text -> LangchainError
+ Langchain.Error: validationErrorWithContext :: Text -> Maybe Text -> Maybe Text -> ErrorContext -> LangchainError
+ Langchain.Error: vectorStoreError :: Text -> Maybe Text -> Maybe Text -> LangchainError
+ Langchain.Error: vectorStoreErrorWithContext :: Text -> Maybe Text -> Maybe Text -> ErrorContext -> LangchainError
+ Langchain.Error: withContext :: Text -> Text -> LangchainResult a -> LangchainResult a
+ Langchain.Error: withContextIO :: MonadIO m => Text -> Text -> LangchainResult a -> m (LangchainResult a)
+ Langchain.Error: withErrorContext :: MonadIO m => ErrorContext -> LangchainIO a -> m (LangchainResult a)
+ Langchain.LLM.Core: ToolCall :: Text -> Text -> ToolFunction -> ToolCall
+ Langchain.LLM.Core: ToolFunction :: Text -> Map Text Value -> ToolFunction
+ Langchain.LLM.Core: [messageImages] :: MessageData -> Maybe [Text]
+ Langchain.LLM.Core: [thinking] :: MessageData -> Maybe Text
+ Langchain.LLM.Core: [toolCallFunction] :: ToolCall -> ToolFunction
+ Langchain.LLM.Core: [toolCallId] :: ToolCall -> Text
+ Langchain.LLM.Core: [toolCallType] :: ToolCall -> Text
+ Langchain.LLM.Core: [toolFunctionArguments] :: ToolFunction -> Map Text Value
+ Langchain.LLM.Core: [toolFunctionName] :: ToolFunction -> Text
+ Langchain.LLM.Core: chatM :: (LLM llm, MonadIO m) => llm -> ChatHistory -> Maybe (LLMParams llm) -> m (LangchainResult Message)
+ Langchain.LLM.Core: class MessageConvertible a
+ Langchain.LLM.Core: data ToolCall
+ Langchain.LLM.Core: data ToolFunction
+ Langchain.LLM.Core: defaultMessage :: Message
+ Langchain.LLM.Core: from :: MessageConvertible a => a -> Message
+ Langchain.LLM.Core: generateM :: (LLM llm, MonadIO m) => llm -> Text -> Maybe (LLMParams llm) -> m (LangchainResult Text)
+ Langchain.LLM.Core: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Core.ToolCall
+ Langchain.LLM.Core: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.LLM.Core.ToolFunction
+ Langchain.LLM.Core: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.LLM.Core.ToolCall
+ Langchain.LLM.Core: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.LLM.Core.ToolFunction
+ Langchain.LLM.Core: instance GHC.Classes.Eq Langchain.LLM.Core.ToolCall
+ Langchain.LLM.Core: instance GHC.Classes.Eq Langchain.LLM.Core.ToolFunction
+ Langchain.LLM.Core: instance GHC.Internal.Show.Show Langchain.LLM.Core.ToolCall
+ Langchain.LLM.Core: instance GHC.Internal.Show.Show Langchain.LLM.Core.ToolFunction
+ Langchain.LLM.Core: streamM :: (LLM llm, MonadIO m) => llm -> ChatHistory -> StreamHandler (LLMStreamTokenType llm) -> Maybe (LLMParams llm) -> m (LangchainResult ())
+ Langchain.LLM.Core: to :: MessageConvertible a => Message -> a
+ Langchain.LLM.Core: type ChatHistory = NonEmpty Message
+ Langchain.LLM.Core: type LLMStreamTokenType llm;
+ Langchain.LLM.Deepseek: Deepseek :: Text -> [Callback] -> Maybe String -> Deepseek
+ Langchain.LLM.Deepseek: [apiKey] :: Deepseek -> Text
+ Langchain.LLM.Deepseek: [baseUrl] :: Deepseek -> Maybe String
+ Langchain.LLM.Deepseek: [callbacks] :: Deepseek -> [Callback]
+ Langchain.LLM.Deepseek: data Deepseek
+ Langchain.LLM.Deepseek: instance GHC.Internal.Show.Show Langchain.LLM.Deepseek.Deepseek
+ Langchain.LLM.Deepseek: instance Langchain.LLM.Core.LLM Langchain.LLM.Deepseek.Deepseek
+ Langchain.LLM.Deepseek: instance Langchain.Runnable.Core.Runnable Langchain.LLM.Deepseek.Deepseek
+ Langchain.LLM.Gemini: Gemini :: Text -> [Callback] -> Maybe String -> Gemini
+ Langchain.LLM.Gemini: [apiKey] :: Gemini -> Text
+ Langchain.LLM.Gemini: [baseUrl] :: Gemini -> Maybe String
+ Langchain.LLM.Gemini: [callbacks] :: Gemini -> [Callback]
+ Langchain.LLM.Gemini: data Gemini
+ Langchain.LLM.Gemini: defaultGemini :: Gemini
+ Langchain.LLM.Gemini: instance GHC.Internal.Show.Show Langchain.LLM.Gemini.Gemini
+ Langchain.LLM.Gemini: instance Langchain.LLM.Core.LLM Langchain.LLM.Gemini.Gemini
+ Langchain.LLM.Gemini: instance Langchain.Runnable.Core.Runnable Langchain.LLM.Gemini.Gemini
+ Langchain.LLM.Huggingface: defaultHugginfaceMessage :: Message
+ Langchain.LLM.Internal.Huggingface: defaultHugginfaceMessage :: Message
+ Langchain.LLM.Internal.Huggingface: instance Langchain.LLM.Core.MessageConvertible Langchain.LLM.Internal.Huggingface.Message
+ Langchain.LLM.Internal.Huggingface: newtype Delta
+ Langchain.LLM.Internal.Huggingface: newtype ImageUrl
+ Langchain.LLM.Internal.Huggingface: newtype SpecificToolChoice
+ Langchain.LLM.Internal.Huggingface: newtype StreamOptions
+ Langchain.LLM.Ollama: defaultOllama :: Ollama
+ Langchain.LLM.Ollama: instance Langchain.LLM.Core.MessageConvertible Data.Ollama.Common.Types.Message
+ Langchain.LLM.OpenAI: [baseUrl] :: OpenAI -> Maybe String
+ Langchain.LLM.OpenAI: defaultOpenAI :: OpenAI
+ Langchain.LLM.OpenAICompatible: OpenAICompatible :: Text -> [Callback] -> Maybe String -> Text -> OpenAICompatible
+ Langchain.LLM.OpenAICompatible: [apiKey] :: OpenAICompatible -> Text
+ Langchain.LLM.OpenAICompatible: [baseUrl] :: OpenAICompatible -> Maybe String
+ Langchain.LLM.OpenAICompatible: [callbacks] :: OpenAICompatible -> [Callback]
+ Langchain.LLM.OpenAICompatible: [providerName] :: OpenAICompatible -> Text
+ Langchain.LLM.OpenAICompatible: data OpenAICompatible
+ Langchain.LLM.OpenAICompatible: instance GHC.Internal.Show.Show Langchain.LLM.OpenAICompatible.OpenAICompatible
+ Langchain.LLM.OpenAICompatible: instance Langchain.LLM.Core.LLM Langchain.LLM.OpenAICompatible.OpenAICompatible
+ Langchain.LLM.OpenAICompatible: instance Langchain.LLM.Core.MessageConvertible (OpenAI.V1.Chat.Completions.Message (Data.Vector.Vector OpenAI.V1.Chat.Completions.Content))
+ Langchain.LLM.OpenAICompatible: instance Langchain.LLM.Core.MessageConvertible (OpenAI.V1.Chat.Completions.Message Data.Text.Internal.Text)
+ Langchain.LLM.OpenAICompatible: instance Langchain.Runnable.Core.Runnable Langchain.LLM.OpenAICompatible.OpenAICompatible
+ Langchain.LLM.OpenAICompatible: mkOpenRouter :: [Callback] -> Maybe String -> Text -> OpenAICompatible
+ Langchain.Memory.Core: addAiMessageM :: (BaseMemory mem, MonadIO m) => mem -> Text -> m (LangchainResult mem)
+ Langchain.Memory.Core: addMessageM :: (BaseMemory mem, MonadIO m) => mem -> Message -> m (LangchainResult mem)
+ Langchain.Memory.Core: addUserMessageM :: (BaseMemory mem, MonadIO m) => mem -> Text -> m (LangchainResult mem)
+ Langchain.Memory.Core: clearM :: (BaseMemory mem, MonadIO m) => mem -> m (LangchainResult mem)
+ Langchain.Memory.Core: messagesM :: (BaseMemory mem, MonadIO m) => mem -> m (LangchainResult ChatHistory)
+ Langchain.Retriever.Core: _get_relevant_documentsM :: (Retriever a, MonadIO m) => a -> Text -> m (LangchainResult [Document])
+ Langchain.Runnable.Core: batchM :: (Runnable r, MonadIO m) => r -> [RunnableInput r] -> m (LangchainResult [RunnableOutput r])
+ Langchain.Runnable.Core: invokeM :: (Runnable r, MonadIO m) => r -> RunnableInput r -> m (LangchainResult (RunnableOutput r))
+ Langchain.Runnable.Core: streamM :: (Runnable r, MonadIO m) => r -> RunnableInput r -> (RunnableOutput r -> IO ()) -> m (LangchainResult ())
+ Langchain.Tool.Core: runToolM :: (Tool a, MonadIO m) => a -> Input a -> m (Output a)
+ Langchain.Tool.DuckDuckGo: DuckDuckGo :: DuckDuckGo
+ Langchain.Tool.DuckDuckGo: data DuckDuckGo
+ Langchain.Tool.DuckDuckGo: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Tool.DuckDuckGo.DuckDuckGoResponse
+ Langchain.Tool.DuckDuckGo: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Tool.DuckDuckGo.Icon
+ Langchain.Tool.DuckDuckGo: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Tool.DuckDuckGo.Meta
+ Langchain.Tool.DuckDuckGo: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Tool.DuckDuckGo.MetaDeveloper
+ Langchain.Tool.DuckDuckGo: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Tool.DuckDuckGo.MetaSrcOptions
+ Langchain.Tool.DuckDuckGo: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Tool.DuckDuckGo.RelatedTopic
+ Langchain.Tool.DuckDuckGo: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Tool.DuckDuckGo.DuckDuckGoQuery
+ Langchain.Tool.DuckDuckGo: instance GHC.Classes.Eq Langchain.Tool.DuckDuckGo.DuckDuckGo
+ Langchain.Tool.DuckDuckGo: instance GHC.Classes.Eq Langchain.Tool.DuckDuckGo.DuckDuckGoQuery
+ Langchain.Tool.DuckDuckGo: instance GHC.Classes.Eq Langchain.Tool.DuckDuckGo.DuckDuckGoResponse
+ Langchain.Tool.DuckDuckGo: instance GHC.Classes.Eq Langchain.Tool.DuckDuckGo.Icon
+ Langchain.Tool.DuckDuckGo: instance GHC.Classes.Eq Langchain.Tool.DuckDuckGo.Meta
+ Langchain.Tool.DuckDuckGo: instance GHC.Classes.Eq Langchain.Tool.DuckDuckGo.MetaDeveloper
+ Langchain.Tool.DuckDuckGo: instance GHC.Classes.Eq Langchain.Tool.DuckDuckGo.MetaSrcOptions
+ Langchain.Tool.DuckDuckGo: instance GHC.Classes.Eq Langchain.Tool.DuckDuckGo.RelatedTopic
+ Langchain.Tool.DuckDuckGo: instance GHC.Internal.Generics.Generic Langchain.Tool.DuckDuckGo.DuckDuckGoQuery
+ Langchain.Tool.DuckDuckGo: instance GHC.Internal.Generics.Generic Langchain.Tool.DuckDuckGo.DuckDuckGoResponse
+ Langchain.Tool.DuckDuckGo: instance GHC.Internal.Generics.Generic Langchain.Tool.DuckDuckGo.Icon
+ Langchain.Tool.DuckDuckGo: instance GHC.Internal.Generics.Generic Langchain.Tool.DuckDuckGo.Meta
+ Langchain.Tool.DuckDuckGo: instance GHC.Internal.Generics.Generic Langchain.Tool.DuckDuckGo.MetaDeveloper
+ Langchain.Tool.DuckDuckGo: instance GHC.Internal.Generics.Generic Langchain.Tool.DuckDuckGo.MetaSrcOptions
+ Langchain.Tool.DuckDuckGo: instance GHC.Internal.Generics.Generic Langchain.Tool.DuckDuckGo.RelatedTopic
+ Langchain.Tool.DuckDuckGo: instance GHC.Internal.Show.Show Langchain.Tool.DuckDuckGo.DuckDuckGo
+ Langchain.Tool.DuckDuckGo: instance GHC.Internal.Show.Show Langchain.Tool.DuckDuckGo.DuckDuckGoQuery
+ Langchain.Tool.DuckDuckGo: instance GHC.Internal.Show.Show Langchain.Tool.DuckDuckGo.DuckDuckGoResponse
+ Langchain.Tool.DuckDuckGo: instance GHC.Internal.Show.Show Langchain.Tool.DuckDuckGo.Icon
+ Langchain.Tool.DuckDuckGo: instance GHC.Internal.Show.Show Langchain.Tool.DuckDuckGo.Meta
+ Langchain.Tool.DuckDuckGo: instance GHC.Internal.Show.Show Langchain.Tool.DuckDuckGo.MetaDeveloper
+ Langchain.Tool.DuckDuckGo: instance GHC.Internal.Show.Show Langchain.Tool.DuckDuckGo.MetaSrcOptions
+ Langchain.Tool.DuckDuckGo: instance GHC.Internal.Show.Show Langchain.Tool.DuckDuckGo.RelatedTopic
+ Langchain.Tool.DuckDuckGo: instance Langchain.Tool.Core.Tool Langchain.Tool.DuckDuckGo.DuckDuckGo
+ Langchain.Tool.WikipediaTool: newtype PageResponse
+ Langchain.Tool.WikipediaTool: newtype Pages
+ Langchain.Tool.WikipediaTool: newtype SearchQuery
+ Langchain.Tool.WikipediaTool: newtype SearchResponse
+ Langchain.Utils: showText :: Show a => a -> Text
+ Langchain.VectorStore.Core: addDocumentsM :: (VectorStore vs, MonadIO m) => vs -> [Document] -> m (LangchainResult vs)
+ Langchain.VectorStore.Core: deleteM :: (VectorStore vs, MonadIO m) => vs -> [Int64] -> m (LangchainResult vs)
+ Langchain.VectorStore.Core: similaritySearchByVectorM :: (VectorStore vs, MonadIO m) => vs -> [Float] -> Int -> m (LangchainResult [Document])
+ Langchain.VectorStore.Core: similaritySearchM :: (VectorStore vs, MonadIO m) => vs -> Text -> Int -> m (LangchainResult [Document])
- Langchain.DocumentLoader.Core: class BaseLoader m
+ Langchain.DocumentLoader.Core: class BaseLoader loader
- Langchain.DocumentLoader.Core: load :: BaseLoader m => m -> IO (Either String [Document])
+ Langchain.DocumentLoader.Core: load :: BaseLoader loader => loader -> IO (LangchainResult [Document])
- Langchain.DocumentLoader.Core: loadAndSplit :: BaseLoader m => m -> IO (Either String [Text])
+ Langchain.DocumentLoader.Core: loadAndSplit :: BaseLoader loader => loader -> IO (LangchainResult [Text])
- Langchain.Embeddings.Core: class Embeddings m
+ Langchain.Embeddings.Core: class Embeddings embed
- Langchain.Embeddings.Core: embedDocuments :: Embeddings m => m -> [Document] -> IO (Either String [[Float]])
+ Langchain.Embeddings.Core: embedDocuments :: Embeddings embed => embed -> [Document] -> IO (LangchainResult [[Float]])
- Langchain.Embeddings.Core: embedQuery :: Embeddings m => m -> Text -> IO (Either String [Float])
+ Langchain.Embeddings.Core: embedQuery :: Embeddings embed => embed -> Text -> IO (LangchainResult [Float])
- Langchain.Embeddings.Ollama: OllamaEmbeddings :: Text -> Maybe Bool -> Maybe Text -> OllamaEmbeddings
+ Langchain.Embeddings.Ollama: OllamaEmbeddings :: Text -> Maybe Bool -> Maybe Int -> Maybe ModelOptions -> OllamaEmbeddings
- Langchain.Embeddings.Ollama: [defaultKeepAlive] :: OllamaEmbeddings -> Maybe Text
+ Langchain.Embeddings.Ollama: [defaultKeepAlive] :: OllamaEmbeddings -> Maybe Int
- Langchain.Embeddings.OpenAI: OpenAIEmbeddings :: Text -> Text -> Maybe Int -> Maybe EncodingFormat -> Maybe Text -> Maybe Int -> OpenAIEmbeddings
+ Langchain.Embeddings.OpenAI: OpenAIEmbeddings :: Text -> Maybe String -> Text -> Maybe Int -> Maybe EncodingFormat -> Maybe Int -> OpenAIEmbeddings
- Langchain.LLM.Core: MessageData :: Maybe Text -> Maybe [Text] -> MessageData
+ Langchain.LLM.Core: MessageData :: Maybe Text -> Maybe [ToolCall] -> Maybe [Text] -> Maybe Text -> MessageData
- Langchain.LLM.Core: StreamHandler :: (Text -> IO ()) -> IO () -> StreamHandler
+ Langchain.LLM.Core: StreamHandler :: (tokenType -> IO ()) -> IO () -> StreamHandler tokenType
- Langchain.LLM.Core: [onComplete] :: StreamHandler -> IO ()
+ Langchain.LLM.Core: [onComplete] :: StreamHandler tokenType -> IO ()
- Langchain.LLM.Core: [onToken] :: StreamHandler -> Text -> IO ()
+ Langchain.LLM.Core: [onToken] :: StreamHandler tokenType -> tokenType -> IO ()
- Langchain.LLM.Core: [toolCalls] :: MessageData -> Maybe [Text]
+ Langchain.LLM.Core: [toolCalls] :: MessageData -> Maybe [ToolCall]
- Langchain.LLM.Core: chat :: LLM m => m -> ChatMessage -> Maybe (LLMParams m) -> IO (Either String Text)
+ Langchain.LLM.Core: chat :: LLM llm => llm -> ChatHistory -> Maybe (LLMParams llm) -> IO (LangchainResult Message)
- Langchain.LLM.Core: class LLM m where {
+ Langchain.LLM.Core: class LLM llm where {
- Langchain.LLM.Core: data StreamHandler
+ Langchain.LLM.Core: data StreamHandler tokenType
- Langchain.LLM.Core: generate :: LLM m => m -> Text -> Maybe (LLMParams m) -> IO (Either String Text)
+ Langchain.LLM.Core: generate :: LLM llm => llm -> Text -> Maybe (LLMParams llm) -> IO (LangchainResult Text)
- Langchain.LLM.Core: stream :: LLM m => m -> ChatMessage -> StreamHandler -> Maybe (LLMParams m) -> IO (Either String ())
+ Langchain.LLM.Core: stream :: LLM llm => llm -> ChatHistory -> StreamHandler (LLMStreamTokenType llm) -> Maybe (LLMParams llm) -> IO (LangchainResult ())
- Langchain.LLM.Core: type LLMParams m;
+ Langchain.LLM.Core: type LLMParams llm;
- Langchain.LLM.OpenAI: OpenAI :: Text -> Text -> [Callback] -> OpenAI
+ Langchain.LLM.OpenAI: OpenAI :: Text -> [Callback] -> Maybe String -> OpenAI
- Langchain.Memory.Core: WindowBufferMemory :: Int -> ChatMessage -> WindowBufferMemory
+ Langchain.Memory.Core: WindowBufferMemory :: Int -> ChatHistory -> WindowBufferMemory
- Langchain.Memory.Core: [windowBufferMessages] :: WindowBufferMemory -> ChatMessage
+ Langchain.Memory.Core: [windowBufferMessages] :: WindowBufferMemory -> ChatHistory
- Langchain.Memory.Core: addAiMessage :: BaseMemory m => m -> Text -> IO (Either String m)
+ Langchain.Memory.Core: addAiMessage :: BaseMemory mem => mem -> Text -> IO (LangchainResult mem)
- Langchain.Memory.Core: addAndTrim :: Int -> Message -> ChatMessage -> ChatMessage
+ Langchain.Memory.Core: addAndTrim :: Int -> Message -> ChatHistory -> ChatHistory
- Langchain.Memory.Core: addMessage :: BaseMemory m => m -> Message -> IO (Either String m)
+ Langchain.Memory.Core: addMessage :: BaseMemory mem => mem -> Message -> IO (LangchainResult mem)
- Langchain.Memory.Core: addUserMessage :: BaseMemory m => m -> Text -> IO (Either String m)
+ Langchain.Memory.Core: addUserMessage :: BaseMemory mem => mem -> Text -> IO (LangchainResult mem)
- Langchain.Memory.Core: class BaseMemory m
+ Langchain.Memory.Core: class BaseMemory mem
- Langchain.Memory.Core: clear :: BaseMemory m => m -> IO (Either String m)
+ Langchain.Memory.Core: clear :: BaseMemory mem => mem -> IO (LangchainResult mem)
- Langchain.Memory.Core: initialChatMessage :: Text -> ChatMessage
+ Langchain.Memory.Core: initialChatMessage :: Text -> ChatHistory
- Langchain.Memory.Core: messages :: BaseMemory m => m -> IO (Either String ChatMessage)
+ Langchain.Memory.Core: messages :: BaseMemory mem => mem -> IO (LangchainResult ChatHistory)
- Langchain.Memory.Core: trimChatMessage :: Int -> ChatMessage -> ChatMessage
+ Langchain.Memory.Core: trimChatMessage :: Int -> ChatHistory -> ChatHistory
- Langchain.Memory.TokenBufferMemory: TokenBufferMemory :: Int -> ChatMessage -> TokenBufferMemory
+ Langchain.Memory.TokenBufferMemory: TokenBufferMemory :: Int -> ChatHistory -> TokenBufferMemory
- Langchain.Memory.TokenBufferMemory: [tokenBufferMessages] :: TokenBufferMemory -> ChatMessage
+ Langchain.Memory.TokenBufferMemory: [tokenBufferMessages] :: TokenBufferMemory -> ChatHistory
- Langchain.OutputParser.Core: parse :: OutputParser a => Text -> Either String a
+ Langchain.OutputParser.Core: parse :: OutputParser a => Text -> LangchainResult a
- Langchain.PromptTemplate: renderFewShotPrompt :: FewShotPromptTemplate -> Either String Text
+ Langchain.PromptTemplate: renderFewShotPrompt :: FewShotPromptTemplate -> LangchainResult Text
- Langchain.PromptTemplate: renderPrompt :: PromptTemplate -> Map Text Text -> Either String Text
+ Langchain.PromptTemplate: renderPrompt :: PromptTemplate -> Map Text Text -> LangchainResult Text
- Langchain.Retriever.Core: _get_relevant_documents :: Retriever a => a -> Text -> IO (Either String [Document])
+ Langchain.Retriever.Core: _get_relevant_documents :: Retriever a => a -> Text -> IO (LangchainResult [Document])
- Langchain.Retriever.MultiQueryRetriever: generateQueries :: LLM m => m -> QueryGenerationPrompt -> Text -> Int -> Bool -> IO (Either String [Text])
+ Langchain.Retriever.MultiQueryRetriever: generateQueries :: LLM m => m -> QueryGenerationPrompt -> Text -> Int -> Bool -> IO (Either LangchainError [Text])
- Langchain.Runnable.Chain: (|>>) :: (Runnable r1, Runnable r2, RunnableOutput r1 ~ RunnableInput r2) => r1 -> r2 -> RunnableInput r1 -> IO (Either String (RunnableOutput r2))
+ Langchain.Runnable.Chain: (|>>) :: (Runnable r1, Runnable r2, RunnableOutput r1 ~ RunnableInput r2) => r1 -> r2 -> RunnableInput r1 -> IO (Either LangchainError (RunnableOutput r2))
- Langchain.Runnable.Chain: branch :: (Runnable r1, Runnable r2, a ~ RunnableInput r1, a ~ RunnableInput r2) => r1 -> r2 -> a -> IO (Either String (RunnableOutput r1, RunnableOutput r2))
+ Langchain.Runnable.Chain: branch :: (Runnable r1, Runnable r2, a ~ RunnableInput r1, a ~ RunnableInput r2) => r1 -> r2 -> a -> IO (Either LangchainError (RunnableOutput r1, RunnableOutput r2))
- Langchain.Runnable.Chain: chain :: (Runnable r1, Runnable r2, RunnableOutput r1 ~ RunnableInput r2) => r1 -> r2 -> RunnableInput r1 -> IO (Either String (RunnableOutput r2))
+ Langchain.Runnable.Chain: chain :: (Runnable r1, Runnable r2, RunnableOutput r1 ~ RunnableInput r2) => r1 -> r2 -> RunnableInput r1 -> IO (Either LangchainError (RunnableOutput r2))
- Langchain.Runnable.Chain: runBranch :: RunnableBranch a b -> a -> IO (Either String b)
+ Langchain.Runnable.Chain: runBranch :: RunnableBranch a b -> a -> IO (Either LangchainError b)
- Langchain.Runnable.Chain: runMap :: RunnableMap a b c -> a -> IO (Either String c)
+ Langchain.Runnable.Chain: runMap :: RunnableMap a b c -> a -> IO (Either LangchainError c)
- Langchain.Runnable.Chain: runSequence :: RunnableSequence a b -> RunnableInputHead a -> IO (Either String b)
+ Langchain.Runnable.Chain: runSequence :: RunnableSequence a b -> RunnableInputHead a -> IO (Either LangchainError b)
- Langchain.Runnable.Core: batch :: Runnable r => r -> [RunnableInput r] -> IO (Either String [RunnableOutput r])
+ Langchain.Runnable.Core: batch :: Runnable r => r -> [RunnableInput r] -> IO (LangchainResult [RunnableOutput r])
- Langchain.Runnable.Core: invoke :: Runnable r => r -> RunnableInput r -> IO (Either String (RunnableOutput r))
+ Langchain.Runnable.Core: invoke :: Runnable r => r -> RunnableInput r -> IO (LangchainResult (RunnableOutput r))
- Langchain.Runnable.Core: stream :: Runnable r => r -> RunnableInput r -> (RunnableOutput r -> IO ()) -> IO (Either String ())
+ Langchain.Runnable.Core: stream :: Runnable r => r -> RunnableInput r -> (RunnableOutput r -> IO ()) -> IO (LangchainResult ())
- Langchain.TextSplitter.Character: CharacterSplitterOps :: Int -> Text -> CharacterSplitterOps
+ Langchain.TextSplitter.Character: CharacterSplitterOps :: Int64 -> Text -> CharacterSplitterOps
- Langchain.TextSplitter.Character: [chunkSize] :: CharacterSplitterOps -> Int
+ Langchain.TextSplitter.Character: [chunkSize] :: CharacterSplitterOps -> Int64
- Langchain.VectorStore.Core: addDocuments :: VectorStore m => m -> [Document] -> IO (Either String m)
+ Langchain.VectorStore.Core: addDocuments :: VectorStore vs => vs -> [Document] -> IO (LangchainResult vs)
- Langchain.VectorStore.Core: class VectorStore m
+ Langchain.VectorStore.Core: class VectorStore vs
- Langchain.VectorStore.Core: delete :: VectorStore m => m -> [Int64] -> IO (Either String m)
+ Langchain.VectorStore.Core: delete :: VectorStore vs => vs -> [Int64] -> IO (LangchainResult vs)
- Langchain.VectorStore.Core: similaritySearch :: VectorStore m => m -> Text -> Int -> IO (Either String [Document])
+ Langchain.VectorStore.Core: similaritySearch :: VectorStore vs => vs -> Text -> Int -> IO (LangchainResult [Document])
- Langchain.VectorStore.Core: similaritySearchByVector :: VectorStore m => m -> [Float] -> Int -> IO (Either String [Document])
+ Langchain.VectorStore.Core: similaritySearchByVector :: VectorStore vs => vs -> [Float] -> Int -> IO (LangchainResult [Document])
- Langchain.VectorStore.InMemory: fromDocuments :: Embeddings m => m -> [Document] -> IO (Either String (InMemory m))
+ Langchain.VectorStore.InMemory: fromDocuments :: Embeddings m => m -> [Document] -> IO (Either LangchainError (InMemory m))
Files
- CHANGELOG.md +22/−0
- README.md +6/−0
- Setup.hs +1/−0
- langchain-hs.cabal +24/−8
- src/Langchain/Agent/Core.hs +303/−0
- src/Langchain/Agent/Executor.hs +261/−0
- src/Langchain/Agent/Middleware.hs +133/−0
- src/Langchain/Agent/ReAct.hs +169/−0
- src/Langchain/Agents/Core.hs +0/−154
- src/Langchain/Agents/React.hs +0/−162
- src/Langchain/Callback.hs +1/−1
- src/Langchain/Chain/RetrievalQA.hs +3/−2
- src/Langchain/DocumentLoader/Core.hs +12/−5
- src/Langchain/DocumentLoader/DirectoryLoader.hs +27/−12
- src/Langchain/DocumentLoader/FileLoader.hs +42/−11
- src/Langchain/DocumentLoader/PdfLoader.hs +31/−11
- src/Langchain/Embeddings/Core.hs +26/−16
- src/Langchain/Embeddings/Gemini.hs +67/−0
- src/Langchain/Embeddings/Ollama.hs +36/−23
- src/Langchain/Embeddings/OpenAI.hs +46/−34
- src/Langchain/Error.hs +609/−0
- src/Langchain/LLM/Core.hs +181/−34
- src/Langchain/LLM/Deepseek.hs +67/−0
- src/Langchain/LLM/Gemini.hs +104/−0
- src/Langchain/LLM/Huggingface.hs +72/−60
- src/Langchain/LLM/Internal/Huggingface.hs +77/−34
- src/Langchain/LLM/Internal/OpenAI.hs +0/−1149
- src/Langchain/LLM/Ollama.hs +136/−152
- src/Langchain/LLM/OpenAI.hs +40/−265
- src/Langchain/LLM/OpenAICompatible.hs +403/−0
- src/Langchain/Memory/Core.hs +56/−32
- src/Langchain/Memory/TokenBufferMemory.hs +61/−21
- src/Langchain/OutputParser/Core.hs +20/−11
- src/Langchain/PromptTemplate.hs +9/−8
- src/Langchain/Retriever/Core.hs +15/−10
- src/Langchain/Retriever/MultiQueryRetriever.hs +8/−8
- src/Langchain/Runnable/Chain.hs +8/−7
- src/Langchain/Runnable/ConversationChain.hs +7/−22
- src/Langchain/Runnable/Core.hs +39/−59
- src/Langchain/Runnable/Utils.hs +4/−24
- src/Langchain/TextSplitter/Character.hs +6/−5
- src/Langchain/Tool/Calculator.hs +78/−41
- src/Langchain/Tool/Core.hs +41/−30
- src/Langchain/Tool/DuckDuckGo.hs +266/−0
- src/Langchain/Tool/Utils.hs +3/−2
- src/Langchain/Tool/WebScraper.hs +1/−1
- src/Langchain/Tool/WikipediaTool.hs +23/−11
- src/Langchain/Utils.hs +27/−0
- src/Langchain/VectorStore/Core.hs +50/−32
- src/Langchain/VectorStore/InMemory.hs +14/−9
- test/Spec.hs +4/−4
- test/Test/Langchain/Agent/Core.hs +0/−123
- test/Test/Langchain/Agent/ReAct.hs +210/−0
- test/Test/Langchain/DocumentLoader/Core.hs +11/−9
- test/Test/Langchain/DocumentLoader/DirectoryLoader.hs +41/−20
- test/Test/Langchain/Embeddings/Core.hs +7/−3
- test/Test/Langchain/LLM/Core.hs +34/−31
- test/Test/Langchain/LLM/Ollama.hs +75/−65
- test/Test/Langchain/Memory/Core.hs +52/−19
- test/Test/Langchain/Memory/TokenBufferMemory.hs +12/−14
- test/Test/Langchain/OutputParser/Core.hs +14/−12
- test/Test/Langchain/PromptTemplate.hs +7/−3
- test/Test/Langchain/Retriever/Core.hs +10/−5
- test/Test/Langchain/Runnable/Chains.hs +9/−6
- test/Test/Langchain/Runnable/ConversationChains.hs +17/−15
- test/Test/Langchain/Runnable/Core.hs +14/−11
- test/Test/Langchain/Runnable/Utils.hs +13/−7
- test/Test/Langchain/Tool/Core.hs +66/−73
- test/Test/Langchain/VectorStore/Core.hs +1/−1
CHANGELOG.md view
@@ -8,6 +8,28 @@ ## Unreleased +## 0.0.3.0 - 2025-11-16++### Added++- Added Agent middleware support.+- Added image input support for OpenAI and Gemini.++### Fixed++- Fixed Ollama stream's onComplete callback.+- Fixed system_fingerprint field to be nullable.++### Changed++- * Revamped Agent module with native tool_call support and improved architecture.+- * Migrated to Mercury's OpenAI client.+- * Changed Ollama params to use ChatOps type.+- * Renamed ChatMessage to ChatHistory.+- * Parameterized StreamHandler Token type.+- * Added Langchain error type for better error handling.+- Updated ollama-haskell to 0.2.1.0.+ ## 0.0.2.0 - 2025-05-04 ### Added
README.md view
@@ -24,6 +24,7 @@ - **Text Splitter**: Components for splitting text into smaller chunks for processing. - **Output Parser**: Components for parsing and processing the output of LLMs. - **VectorStore and Retriever**: Mechanism for storing and retrieving document embeddings.+ * Includes support for Faiss, a library for efficient similarity search. This integration is available through the separate [`faiss-hs`](https://github.com/tusharad/faiss-hs) repository. - **Embeddings**: Components for generating vector representations of text. ## Current Supported Providers@@ -31,6 +32,7 @@ - Ollama - OpenAI - Huggingface+ - OpenAI compatible APIs (LMStudio, OpenRouter, Llama-cpp, Deepseek) - More to come... ## Installation@@ -74,6 +76,10 @@ Left err -> putStrLn $ "Error: " ++ err Right response -> putStrLn $ "Translation: " ++ (T.unpack response) ```++## Projects using langchain-hs++- [ai-chatbot-hs](https://github.com/tusharad/ai-chatbot-hs) ## Contributing
Setup.hs view
@@ -1,2 +1,3 @@ import Distribution.Simple+ main = defaultMain
langchain-hs.cabal view
@@ -1,11 +1,11 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.37.0.+-- This file has been generated from package.yaml by hpack version 0.38.1. -- -- see: https://github.com/sol/hpack name: langchain-hs-version: 0.0.2.0+version: 0.0.3.0 synopsis: Haskell implementation of Langchain description: Build LLM-powered applications in Haskell. category: Web, AI@@ -34,8 +34,10 @@ library exposed-modules:- Langchain.Agents.Core- Langchain.Agents.React+ Langchain.Agent.Core+ Langchain.Agent.Executor+ Langchain.Agent.Middleware+ Langchain.Agent.ReAct Langchain.Callback Langchain.Chain.RetrievalQA Langchain.DocumentLoader.Core@@ -43,14 +45,18 @@ Langchain.DocumentLoader.FileLoader Langchain.DocumentLoader.PdfLoader Langchain.Embeddings.Core+ Langchain.Embeddings.Gemini Langchain.Embeddings.Ollama Langchain.Embeddings.OpenAI+ Langchain.Error Langchain.LLM.Core+ Langchain.LLM.Deepseek+ Langchain.LLM.Gemini Langchain.LLM.Huggingface Langchain.LLM.Internal.Huggingface- Langchain.LLM.Internal.OpenAI Langchain.LLM.Ollama Langchain.LLM.OpenAI+ Langchain.LLM.OpenAICompatible Langchain.Memory.Core Langchain.Memory.TokenBufferMemory Langchain.OutputParser.Core@@ -64,9 +70,11 @@ Langchain.TextSplitter.Character Langchain.Tool.Calculator Langchain.Tool.Core+ Langchain.Tool.DuckDuckGo Langchain.Tool.Utils Langchain.Tool.WebScraper Langchain.Tool.WikipediaTool+ Langchain.Utils Langchain.VectorStore.Core Langchain.VectorStore.InMemory other-modules:@@ -78,6 +86,7 @@ aeson ==2.* , async <3 , base >=4.7 && <5+ , base64-bytestring ==1.2.* , bytestring >=0.10 , conduit >=1.2 && <1.4 , containers >=0.6 && <0.9@@ -85,11 +94,14 @@ , filepath <2 , http-conduit ==2.* , http-types >=0.11 && <0.13- , ollama-haskell+ , ollama-haskell >=0.2.1+ , openai >=2.2.1 , parsec <4 , pdf-toolbox-document ==0.1.4 , tagsoup <0.15 , text >=1.2 && <3+ , time >=1.9 && <1.15+ , transformers , vector <0.14 default-language: Haskell2010 @@ -97,7 +109,7 @@ type: exitcode-stdio-1.0 main-is: Spec.hs other-modules:- Test.Langchain.Agent.Core+ Test.Langchain.Agent.ReAct Test.Langchain.DocumentLoader.Core Test.Langchain.DocumentLoader.DirectoryLoader Test.Langchain.Embeddings.Core@@ -123,6 +135,7 @@ aeson ==2.* , async <3 , base >=4.7 && <5+ , base64-bytestring ==1.2.* , bytestring >=0.10 , conduit >=1.2 && <1.4 , containers >=0.6 && <0.9@@ -131,7 +144,8 @@ , http-conduit ==2.* , http-types >=0.11 && <0.13 , langchain-hs- , ollama-haskell+ , ollama-haskell >=0.2.1+ , openai >=2.2.1 , parsec <4 , pdf-toolbox-document ==0.1.4 , tagsoup <0.15@@ -139,5 +153,7 @@ , tasty-hunit , temporary , text+ , time >=1.9 && <1.15+ , transformers , vector <0.14 default-language: Haskell2010
+ src/Langchain/Agent/Core.hs view
@@ -0,0 +1,303 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}++{- |+Module : Langchain.Agent.Core+Description : Core types and abstractions for LangChain agents+Copyright : (c) 2025 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++This module provides the foundational types and typeclasses for building agents+in langchain-hs. An LLM Agent runs tools in a loop to achieve a goal.+An agent runs until a stop condition is met -+when the model emits a final output or an iteration limit is reached.+-}+module Langchain.Agent.Core+ ( -- * Agent Typeclass+ Agent (..)++ -- * Agent Actions and Results+ , AgentAction (..)+ , AgentFinish (..)+ , AgentStep (..)+ , PlanResult (..)++ -- * Agent State and Configuration+ , AgentState (..)+ , AgentConfig (..)+ , AgentCallbacks (..)+ , defaultAgentConfig+ , defaultAgentCallbacks++ -- * Tool support+ , ToolAcceptingToolCall (..)++ -- * Memory support+ , SomeMemory (..)+ ) where++import Control.Monad.IO.Class (MonadIO, liftIO)+import Data.Aeson+import Data.Map.Strict (Map)+import Data.Text (Text)+import Data.Time (UTCTime)+import GHC.Generics (Generic)+import Langchain.Error (LangchainResult)+import Langchain.LLM.Core (ToolCall)+import Langchain.Memory.Core (BaseMemory)+import Langchain.Tool.Core++-- | Represents an action (ToolCall) that an agent has decided to take.+data AgentAction = AgentAction+ { actionToolCall :: [ToolCall]+ -- ^ tool call+ , actionLog :: Text+ -- ^ LLM's response while suggesting the tool call+ , actionMetadata :: Map Text Text+ -- ^ Additional metadata about the action+ }+ deriving (Show, Eq)++-- | Represents the final result when an agent completes its task.+data AgentFinish = AgentFinish+ { agentOutput :: Text+ -- ^ The final answer or result+ , finishMetadata :: Map Text Text+ -- ^ Additional information about the execution+ , finishLog :: Text+ -- ^ Final thoughts or reasoning+ }+ deriving (Show, Eq, Generic, ToJSON, FromJSON)++-- | Represents one step in the agent's execution.+data AgentStep = AgentStep+ { stepAction :: AgentAction+ -- ^ The action that was executed+ , stepObservation :: Text+ -- ^ The result/observation from the executed tool call+ , stepTimestamp :: UTCTime+ -- ^ When this step occurred+ }+ deriving (Show, Eq)++{- |+A SomeMemory is a wrapper around any type that implements BaseMemory.++> data MyMemory = MyMemory { ... }+> instance BaseMemory MyMemory where ...+>+> let memory = MyMemory { ... }+> let someMemory = SomeMemory memory+>+> let msg = defaultMessage { role = System, content = "You are an AI assistant" }+> let someMemory2 = SomeMemory (WindowBufferMemory 5 (NE.fromList [msg]))+-}+data SomeMemory where+ SomeMemory ::+ (BaseMemory m) =>+ m ->+ SomeMemory++instance Show SomeMemory where+ show (SomeMemory _) = "SomeMemory { <memory instance> }"++{- | Current state of the agent during execution.++Tracks:+- Memory instance for managing chat history+- Current input being processed+- Number of iterations so far+-}+data AgentState = AgentState+ { agentMemory :: SomeMemory+ -- ^ Memory instance for managing chat history with the LLM+ , agentInput :: Text+ -- ^ Current user input/query+ , agentIterations :: Int+ -- ^ Number of iterations so far+ }++instance Show AgentState where+ show (AgentState mem inp iters) =+ "AgentState { agentMemory = "+ ++ show mem+ ++ ", agentInput = "+ ++ show inp+ ++ ", agentIterations = "+ ++ show iters+ ++ " }"++data AgentConfig = AgentConfig+ { maxIterations :: Int+ -- ^ Maximum number of agent steps (default: 15)+ , maxExecutionTime :: Maybe Int+ -- ^ Maximum execution time in seconds (Nothing = no limit)+ , verboseLogging :: Bool+ -- ^ Enable verbose logging (default: False)+ , stateMemory :: Maybe SomeMemory+ {- ^ Configure type of Chat memory you want use.+ ^ (default: windowBufferMessages with 100 window size)+ -}+ }+ deriving (Show)++{- | Callbacks for agent events.+Allows hooking into various points in the agent lifecycle.+-}+data AgentCallbacks = AgentCallbacks+ { onAgentStart :: Text -> IO ()+ -- ^ Called when agent starts with the input+ , onAgentAction :: AgentAction -> IO ()+ -- ^ Called before executing an action+ , onAgentObservation :: Text -> IO ()+ -- ^ Called after receiving an observation / result of the tool call+ , onAgentFinish :: AgentFinish -> IO ()+ -- ^ Called when agent completes+ , onAgentStep :: AgentStep -> IO ()+ -- ^ Called after each complete step+ }++{- |+A ToolAcceptingToolCall is a special type of tool that+can be used by an agent to execute a tool call.++It is a wrapper around a tool type whose input is a ToolCall and output is a Text.+It is user's responsibility wrap your existing tool into this type.++Example:++> data AgeFinderTool = AgeFinderTool+> instance Tool AgeFinderTool where+> type Input AgeFinderTool = ToolCall+> type Output AgeFinderTool = Text+> toolName _ = "age_finder"+> toolDescription _ = "Finds the age of a person given their name."+> runTool _ (ToolCall _ _ ToolFunction {..}) = do+> if toolFunctionName == "age_finder"+> then do+> case HM.lookup "name" toolFunctionArguments of+> Nothing -> pure "Unknown"+> Just (String name_) -> pure $ getAge name_+> _ -> pure "Unknown"+> else pure "Unknown"+>+> getAge name_ = case name_ of+> "Alice" -> "30"+> "Bob" -> "25"+> _ -> "Unknown"+-}+data ToolAcceptingToolCall where+ ToolAcceptingToolCall ::+ ( Tool t+ , Input t ~ ToolCall+ , Output t ~ Text+ ) =>+ t -> ToolAcceptingToolCall++instance Eq ToolAcceptingToolCall where+ (ToolAcceptingToolCall t1) == (ToolAcceptingToolCall t2) = toolName t1 == toolName t2++instance Show ToolAcceptingToolCall where+ show (ToolAcceptingToolCall t) =+ "ToolAcceptingToolCall { name = " ++ show (toolName t) ++ " }"++data PlanResult = Continue AgentAction | Done AgentFinish+ deriving (Eq, Show)++{- | Core Agent typeclass.++An agent is a system that can plan and execute actions to accomplish a task.+Different agent types (ReAct, Plan-and-Execute, etc.) implement this interface.+-}+class Agent a where+ {- | Plan the next action or finish.++ Given the current state, decide:+ - What tool call to make next (Left AgentAction), or+ - That the task is complete and return the final result (Right AgentFinish)+ -}+ plan ::+ a ->+ AgentState ->+ IO (LangchainResult PlanResult)++ -- | Get the tools available to this agent.+ getTools :: a -> [ToolAcceptingToolCall]++ -- | Execute a tool.+ executeTool :: a -> ToolCall -> IO (LangchainResult Text)++ {- | Prepare the agent for execution.+ Initialize any necessary state before starting.+ Default implementation does nothing.+ -}+ initialize :: a -> AgentState -> IO (LangchainResult AgentState)+ initialize _ state = pure $ Right state++ {- | Clean up after agent execution.+ Release resources, save state, etc.+ Default implementation does nothing.+ -}+ finalize :: a -> AgentState -> IO ()+ finalize _ _ = pure ()++ -- | MonadIO version of plan+ planM ::+ MonadIO m =>+ a ->+ AgentState ->+ m (LangchainResult PlanResult)+ planM agent state = liftIO $ plan agent state++ -- | MonadIO version of executeTool+ executeToolM :: MonadIO m => a -> ToolCall -> m (LangchainResult Text)+ executeToolM a i = liftIO $ executeTool a i++ -- | MonadIO version of initialize+ initializeM ::+ MonadIO m =>+ a ->+ AgentState ->+ m (LangchainResult AgentState)+ initializeM agent state = liftIO $ initialize agent state++ -- | MonadIO version of finalize+ finalizeM :: MonadIO m => a -> AgentState -> m ()+ finalizeM agent state = liftIO $ finalize agent state++{- | Default agent configuration.++Sensible defaults:+- 15 max iterations+- No time limit+- No verbose logging+- WindowBufferMemory with window size 100+-}+defaultAgentConfig :: AgentConfig+defaultAgentConfig =+ AgentConfig+ { maxIterations = 15+ , maxExecutionTime = Nothing+ , verboseLogging = False+ , stateMemory = Nothing+ }++{- | Default agent callbacks (all no-ops).+Useful as a starting point for custom callbacks.+-}+defaultAgentCallbacks :: AgentCallbacks+defaultAgentCallbacks =+ AgentCallbacks+ { onAgentStart = \_ -> pure ()+ , onAgentAction = \_ -> pure ()+ , onAgentObservation = \_ -> pure ()+ , onAgentFinish = \_ -> pure ()+ , onAgentStep = \_ -> pure ()+ }
+ src/Langchain/Agent/Executor.hs view
@@ -0,0 +1,261 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RecordWildCards #-}++{- |+Module : Langchain.Agent.Executor+Description : Agent execution loop and orchestration+Copyright : (c) 2025 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++This module provides the execution engine for agents. It orchestrates the+agent planning loop, tool execution, and result collection.++The executor handles:+- The main agent loop (plan -> execute -> observe)+- Error handling and recovery+- Iteration limits and timeouts+- Callbacks and logging+- State management+-}+module Langchain.Agent.Executor+ ( -- * Main Execution Functions+ runAgentExecutor++ -- * Result Types+ , AgentExecutionResult (..)+ , ExecutionMetrics (..)++ -- * Utilities+ , createInitialState+ )+where++import Control.Monad (when)+import Control.Monad.IO.Class (MonadIO (liftIO))+import Control.Monad.Trans.Except+import Data.Maybe (fromMaybe)+import Data.Text (Text)+import qualified Data.Text as T+import Data.Time (UTCTime, diffUTCTime, getCurrentTime)+import Langchain.Agent.Core+import Langchain.Agent.Middleware+import Langchain.Error+ ( LangchainResult+ , agentError+ )+import Langchain.LLM.Core+import Langchain.Memory.Core++data AgentExecutionResult = AgentExecutionResult+ { executionFinish :: AgentFinish+ -- ^ The final result of the agent execution+ , executionSteps :: [AgentStep]+ -- ^ All tool calls made and their results+ , executionMetrics :: ExecutionMetrics+ -- ^ Performance metrics+ }+ deriving (Show, Eq)++data ExecutionMetrics = ExecutionMetrics+ { metricsIterations :: Int+ -- ^ Number of agent iterations+ , metricsExecutionTime :: Double+ -- ^ Total time in seconds+ , metricsToolCalls :: Int+ -- ^ Number of tool calls made+ , metricsSuccess :: Bool+ -- ^ Whether execution completed successfully+ }+ deriving (Show, Eq)++-- | Create the initial state of the agent with default memory.+createInitialState :: Maybe SomeMemory -> Text -> AgentState+createInitialState mbSomeMemory input =+ AgentState+ { agentMemory = fromMaybe (SomeMemory defaultMemory) mbSomeMemory+ , agentInput = input+ , agentIterations = 0+ }+ where+ defaultMemory =+ WindowBufferMemory+ { maxWindowSize = 100+ , windowBufferMessages = initialChatMessage "You are a helpful AI assistant."+ }++{-+Returns False if:+- Max iterations reached+- Max execution time exceeded+-}+shouldContinue :: AgentConfig -> AgentState -> Double -> Bool+shouldContinue AgentConfig {..} state elapsedSeconds =+ iterationsOk && timeOk+ where+ iterationsOk = agentIterations state < maxIterations+ timeOk = case maxExecutionTime of+ Nothing -> True+ Just maxTime -> elapsedSeconds < fromIntegral maxTime++-- | Helper function to add an action to the state's memory+addActionToState :: AgentState -> AgentAction -> IO (LangchainResult AgentState)+addActionToState state action =+ case agentMemory state of+ SomeMemory mem -> do+ eMemWithAction <- addMessage mem (actionToMsg action)+ case eMemWithAction of+ Left err -> pure $ Left err+ Right memWithAction -> pure $ Right $ state {agentMemory = SomeMemory memWithAction}+ where+ actionToMsg act =+ defaultMessage+ { role = Assistant+ , content = actionLog act+ , messageData =+ defaultMessageData+ { toolCalls = Just (actionToolCall act)+ }+ }++-- | Helper function to add observations to the state's memory+addObservationsToState :: AgentState -> [Text] -> IO (LangchainResult AgentState)+addObservationsToState state observations =+ case agentMemory state of+ SomeMemory mem -> do+ eMemsWithObs <- sequenceA <$> traverse (addMessage mem . toolResultToMsg) observations+ case eMemsWithObs of+ Left err -> pure $ Left err+ Right mems -> pure $ Right $ state {agentMemory = SomeMemory (last mems)}+ where+ toolResultToMsg res =+ defaultMessage+ { role = Tool+ , content = res+ }++executeAgentLoop ::+ Agent a =>+ a ->+ AgentConfig ->+ AgentCallbacks ->+ [AgentMiddleware a] ->+ AgentState ->+ UTCTime ->+ IO (LangchainResult AgentExecutionResult)+executeAgentLoop agent config callbacks middlewares initialState startTime =+ loop agent initialState []+ where+ loop agent0 state0 steps = runExceptT $ do+ currentTime <- liftIO getCurrentTime+ let elapsedSeconds = realToFrac $ diffUTCTime currentTime startTime+ -- Check termination conditions+ if not (shouldContinue config state0 elapsedSeconds)+ then do+ let err = agentError "Agent execution exceeded limits" Nothing Nothing+ ExceptT . pure $ Left err+ else do+ -- Plan next action+ when (verboseLogging config) $+ liftIO $+ putStrLn $+ "[Agent] Planning iteration " <> show (agentIterations state0)+ (state1, agent1) <-+ ExceptT $+ applyMiddlewares beforeModelCall middlewares (state0, agent0)+ plan_ <- ExceptT $ plan agent1 state1+ (state2, agent2) <-+ ExceptT $+ applyMiddlewares afterModelCall middlewares (state1, agent1)+ case plan_ of+ (Done finish) -> do+ -- Agent has finished+ let metrics =+ ExecutionMetrics+ { metricsIterations = agentIterations state2+ , metricsExecutionTime = elapsedSeconds+ , metricsToolCalls = length steps+ , metricsSuccess = True+ }+ return $ AgentExecutionResult finish steps metrics+ (Continue action) -> do+ -- add toolCalls in state memory+ state3 <- ExceptT $ addActionToState state2 action+ -- Execute action+ liftIO $ onAgentAction callbacks action+ (state4, agent4) <-+ ExceptT $+ applyMiddlewares beforeToolCall middlewares (state3, agent2)+ when (verboseLogging config) $+ liftIO $+ putStrLn $+ "[Agent] Executing: " <> show (actionToolCall action)+ observations <-+ ExceptT $+ sequenceA <$> traverse (executeTool agent4) (actionToolCall action)+ mapM_ (liftIO . onAgentObservation callbacks) observations+ when (verboseLogging config) $+ liftIO $+ putStrLn $+ "[Agent] Observation: " <> mconcat (T.unpack <$> observations)+ -- Record step+ timestamp <- liftIO getCurrentTime+ let newSteps = map (\obs -> AgentStep action obs timestamp) observations+ mapM_ (liftIO . onAgentStep callbacks) newSteps+ -- Update state memory with tool results and continue+ state5 <-+ ExceptT $+ addObservationsToState state4 observations+ (state6, agent6) <-+ ExceptT $+ applyMiddlewares afterToolCall middlewares (state5, agent4)+ let newState =+ state6+ { agentIterations = agentIterations state6 + 1+ }+ ExceptT (loop agent6 newState (steps ++ newSteps))++{- |+ Runs the agent executor.++ This function initializes the agent, runs the agent loop, and returns the final result.++ Arguments:+ - agent: The agent to run+ - config: The agent configuration+ - callbacks: The agent callbacks+ - input: The input to the agent++ Returns:+ - The final result of the agent execution+ - The execution metrics+ - The execution steps+-}+runAgentExecutor ::+ Agent a =>+ a ->+ AgentConfig ->+ AgentCallbacks ->+ [AgentMiddleware a] ->+ Text ->+ IO (LangchainResult AgentExecutionResult)+runAgentExecutor agent0 config callbacks middlewares input = do+ startTime <- getCurrentTime+ onAgentStart callbacks input+ runExceptT $ do+ let initialState = createInitialState (stateMemory config) input+ state0 <- ExceptT $ initialize agent0 initialState+ (state1, agent1) <-+ ExceptT $+ applyMiddlewares beforeAgent middlewares (state0, agent0)+ result <-+ ExceptT $+ executeAgentLoop agent1 config callbacks middlewares state1 startTime+ (state2, agent2) <-+ ExceptT $+ applyMiddlewares afterAgent middlewares (state1, agent1)+ liftIO $ finalize agent2 state2+ liftIO $ onAgentFinish callbacks (executionFinish result)+ return result
+ src/Langchain/Agent/Middleware.hs view
@@ -0,0 +1,133 @@+{-# LANGUAGE RankNTypes #-}++{- |+Module : Langchain.Agent.Middleware+Description : Built-in middlewares for LangChain agents+Copyright : (c) 2025 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++This module provides a comprehensive set of built-in middlewares for agents,+similar to Python LangChain's middleware system. Middlewares allow you to hook+into various points in the agent execution lifecycle.++Available middlewares:+- defaultMiddleware: No-op middleware (base implementation)+- humanInLoopMiddleware: Pause for human approval before tool execution+- toolCallLimitMiddleware: Limit the number of tool calls+-}+module Langchain.Agent.Middleware+ ( -- * Middleware Type+ AgentMiddleware (..)+ , applyMiddlewares++ -- * Built-in Middlewares+ , defaultMiddleware+ , humanInLoopMiddleware+ , toolCallLimitMiddleware+ ) where++import Control.Monad (foldM)+import Data.IORef (modifyIORef', newIORef, readIORef)+import qualified Data.List.NonEmpty as NE+import qualified Data.Text as T+import Langchain.Agent.Core+import Langchain.Error+ ( LangchainResult+ , agentError+ , fromString+ )+import Langchain.LLM.Core (Message (messageData), MessageData (toolCalls))+import Langchain.Memory.Core (BaseMemory (messages))++-- | Middleware hooks around agent execution steps.+data Agent a => AgentMiddleware a = AgentMiddleware+ { beforeModelCall :: (AgentState, a) -> IO (LangchainResult (AgentState, a))+ , afterModelCall :: (AgentState, a) -> IO (LangchainResult (AgentState, a))+ , beforeToolCall :: (AgentState, a) -> IO (LangchainResult (AgentState, a))+ , afterToolCall :: (AgentState, a) -> IO (LangchainResult (AgentState, a))+ , beforeAgent :: (AgentState, a) -> IO (LangchainResult (AgentState, a))+ , afterAgent :: (AgentState, a) -> IO (LangchainResult (AgentState, a))+ }++-- | Default middleware that does nothing (no-op).+defaultMiddleware :: Agent a => AgentMiddleware a+defaultMiddleware =+ AgentMiddleware+ { beforeModelCall = pure . Right+ , afterModelCall = pure . Right+ , beforeToolCall = pure . Right+ , afterToolCall = pure . Right+ , beforeAgent = pure . Right+ , afterAgent = pure . Right+ }++-- | Sequentially apply a list of middlewares for a given phase.+applyMiddlewares ::+ (AgentMiddleware a -> (AgentState, a) -> IO (LangchainResult (AgentState, a))) ->+ [AgentMiddleware a] ->+ (AgentState, a) ->+ IO (LangchainResult (AgentState, a))+applyMiddlewares f mws st =+ foldM+ ( \acc mw -> case acc of+ Left err -> pure $ Left err+ Right s -> f mw s+ )+ (Right st)+ mws++{- | Human-in-the-loop middleware.+Pauses execution before each tool call and asks for human approval.+This is useful for sensitive operations or debugging.++Example:+> runAgentExecutor agent config callbacks [humanInLoopMiddleware] "input"+-}+humanInLoopMiddleware :: Agent a => AgentMiddleware a+humanInLoopMiddleware =+ defaultMiddleware+ { beforeToolCall = \(st, a) -> do+ case agentMemory st of+ SomeMemory mem -> do+ eRes <- messages mem+ case eRes of+ Left err -> pure $ Left err+ Right msgs -> do+ let msg = NE.last msgs+ toolCallLst = toolCalls $ messageData msg+ putStrLn $ "Approve this tool call? " ++ show toolCallLst+ putStrLn "(y/n): "+ resp <- getLine+ if resp == "y"+ then pure $ Right (st, a)+ else pure $ Left $ fromString "Tool call rejected by human"+ }++{- | Tool call limit middleware.+Limits the total number of tool calls during agent execution.+This helps prevent excessive tool usage and control costs.++Example:+> toolCallLimitMiddleware 20 -- Limit to 20 tool calls+-}+toolCallLimitMiddleware :: Agent a => Int -> IO (AgentMiddleware a)+toolCallLimitMiddleware maxCalls = do+ counter <- newIORef 0+ pure $+ defaultMiddleware+ { beforeToolCall = \(st, a) -> do+ count <- readIORef counter+ if count >= maxCalls+ then+ pure $+ Left $+ agentError+ (T.pack $ "Tool call limit exceeded: " <> show maxCalls)+ Nothing+ (Just (T.pack "toolCallLimitMiddleware"))+ else do+ modifyIORef' counter (+ 1)+ pure $ Right (st, a)+ }
+ src/Langchain/Agent/ReAct.hs view
@@ -0,0 +1,169 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-}++{- |+Module : Langchain.Agent.ReAct+Description : ReAct (Reasoning + Acting) agent implementation+Copyright : (c) 2025 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++This module implements the ReAct (Reasoning + Acting) agent pattern.+ReAct combines reasoning traces and task-specific actions in an interleaved manner.+-}+module Langchain.Agent.ReAct+ ( -- * Agent Creation+ ReActAgent (..)+ , createReActAgent+ , createReActAgentWithPrompt++ -- * Prompt Templates+ , reActSystemPrompt+ ) where++import Control.Monad.Trans.Except+import Data.List (find)+import qualified Data.Map as Map+import Data.Text (Text)+import qualified Data.Text as T+import Langchain.Agent.Core+import qualified Langchain.Error as Error+import Langchain.LLM.Core+import Langchain.Memory.Core (BaseMemory (..))+import Langchain.Tool.Core++{- | ReAct agent.++Arguments:+- llm: The language model+- llmParams: The language model parameters+- systemPrompt: The system prompt+- maxThinkingSteps: The maximum number of thinking steps before forcing action+- tools: The tools available to the agent.+-}+data ReActAgent llm = ReActAgent+ { reactLLM :: llm+ -- ^ The language model for reasoning+ , reactLLMParams :: Maybe (LLMParams llm)+ -- ^ the llm params for language model+ , reactSystemPrompt :: Text+ -- ^ System prompt template+ , reactMaxThinkingSteps :: Int+ -- ^ Maximum consecutive thinking steps before forcing action (default: 3)+ , reactTools :: [ToolAcceptingToolCall]+ }++{- | Create a ReAct agent.++Arguments:+- llm: The language model+- llmParams: The language model parameters+- tools: The tools available to the agent++Important:+- It is user's responsibility to wrap the tools into ToolAcceptingToolCall.+- It is user's responsibility to pass tool_calls as part of LLMParams.+- The tool_calls shall be same as the reactTools (ToolAcceptingToolCall) list.+-}+createReActAgent ::+ -- | The language model+ llm ->+ -- | The language model parameters+ Maybe (LLMParams llm) ->+ -- | The tools available to the agent+ [ToolAcceptingToolCall] ->+ -- | The ReAct agent+ ReActAgent llm+createReActAgent llm mbLlmParams tools =+ ReActAgent+ { reactLLM = llm+ , reactLLMParams = mbLlmParams+ , reactSystemPrompt = reActSystemPrompt+ , reactMaxThinkingSteps = 3+ , reactTools = tools+ }++-- | Create a ReAct agent with a custom system prompt.+createReActAgentWithPrompt ::+ -- | The language model+ llm ->+ -- | The language model parameters+ Maybe (LLMParams llm) ->+ -- | The tools available to the agent+ [ToolAcceptingToolCall] ->+ -- | The custom system prompt+ Text ->+ -- | The ReAct agent+ ReActAgent llm+createReActAgentWithPrompt llm mbLlmParams tools prompt =+ ReActAgent+ { reactLLM = llm+ , reactLLMParams = mbLlmParams+ , reactSystemPrompt = prompt+ , reactMaxThinkingSteps = 3+ , reactTools = tools+ }++-- | Default system prompt for the ReAct agent.+reActSystemPrompt :: Text+reActSystemPrompt =+ "You are a helpful AI assistant that uses tools to answer user questions."++instance LLM llm => Agent (ReActAgent llm) where+ plan agent state = do+ let llm = reactLLM agent+ mbParams = reactLLMParams agent+ -- Get messages from memory - use case to handle existential type+ case agentMemory state of+ SomeMemory mem -> runExceptT $ do+ msgs <- ExceptT $ messages mem+ respMsg <- ExceptT $ chat llm msgs mbParams+ case toolCalls (messageData respMsg) of+ Nothing -> do+ -- No tool calls requested. Assume content as the final result+ pure $+ Done $+ AgentFinish+ { agentOutput = content respMsg+ , finishMetadata = Map.empty -- TODO: Add stuff from state+ , finishLog = content respMsg+ }+ Just toolCallList -> do+ pure $+ Continue+ AgentAction+ { actionToolCall = toolCallList+ , actionLog = content respMsg+ , actionMetadata = Map.empty -- TODO: what to add here?+ }++ getTools = reactTools++ executeTool agent toolCall = do+ let tools = getTools agent+ let inputFunctionName = toolFunctionName (toolCallFunction toolCall)+ case find (\(ToolAcceptingToolCall t) -> toolName t == inputFunctionName) tools of+ Nothing ->+ pure $+ Left $+ Error.fromString $+ "Cannot find tool with name: "+ <> T.unpack inputFunctionName+ Just (ToolAcceptingToolCall selectedTool) -> Right <$> runTool selectedTool toolCall++ initialize agent state = do+ let sysPrompt = reactSystemPrompt agent+ userInput = agentInput state+ sysMsg = defaultMessage {role = System, content = sysPrompt}+ userMsg = defaultMessage {role = User, content = userInput}+ case agentMemory state of+ SomeMemory mem -> runExceptT $ do+ memWithSys <- ExceptT $ addMessage mem sysMsg+ memWithUser <- ExceptT $ addMessage memWithSys userMsg+ pure+ AgentState+ { agentMemory = SomeMemory memWithUser+ , agentInput = userInput+ , agentIterations = 0+ }
− src/Langchain/Agents/Core.hs
@@ -1,154 +0,0 @@-{-# LANGUAGE GADTs #-}-{-# LANGUAGE RecordWildCards #-}--{- |-Module : Langchain.Agents.Core-Description : Core implementation of LangChain agents-Copyright : (c) 2025 Tushar Adhatrao-License : MIT-Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>--Agents use LLMs as reasoning engines to determine actions dynamically. --}-module Langchain.Agents.Core- ( AgentAction (..)- , AgentFinish (..)- , AgentStep (..)- , Agent (..)- , AnyTool (..)- , AgentState (..)- , runAgent- , runAgentLoop- , executeTool- , runSingleStep- , customAnyTool- ) where--import Control.Exception (SomeException, try)-import Data.List (find)-import qualified Data.Map.Strict as Map-import Data.Text (Text)-import qualified Data.Text as T-import Langchain.LLM.Core (Message (Message), Role (..), defaultMessageData)-import Langchain.Memory.Core (BaseMemory (..))-import Langchain.PromptTemplate (PromptTemplate)-import Langchain.Tool.Core (Tool (..))--{- |-Represents an action to be taken by the agent--}-data AgentAction = AgentAction- { actionToolName :: Text- -- ^ Tool name- , actionInput :: Text- -- ^ Input- , actionLog :: Text- -- ^ Execution log- }- deriving (Eq, Show)---- | Represents that agent has finished work with final value-data AgentFinish = AgentFinish- { returnValues :: Map.Map Text Text - , finishLog :: Text- }- deriving (Show, Eq)---- | Type that will be return from LLM --- Could be either Continue, making another call to LLM or Finish with final value-data AgentStep- = Continue AgentAction- | Finish AgentFinish- deriving (Eq, Show)---- | Type for maintaining state of the agent -data (BaseMemory m) => AgentState m = AgentState- { agentMemory :: m -- ^ Memory for storing chat history- , agentToolResults :: [(Text, Text)] -- ^ Tool results- , agentSteps :: [AgentAction] -- ^ Agent steps happened so far- }- deriving (Eq, Show)---- | A type that helps various types of Tools--- It encapsulates the Tool, and conversion functions --- to and from Text for Tool input and output since Agent takes and returns Text. --- If Tool takes or returns Text type itself you can use `id` at these places.-data AnyTool = forall a. Tool a => AnyTool- { anyTool :: a- , textToInput :: Text -> Input a- , outputToText :: Output a -> Text- }---- | Typeclass for Agent-class Agent a where- planNextAction :: BaseMemory m => a -> AgentState m -> IO (Either String AgentStep)- agentPrompt :: a -> IO PromptTemplate- agentTools :: a -> IO [AnyTool]---- | Function that *starts* the agent process.-runAgent :: (Agent a, BaseMemory m) => a -> AgentState m -> Text -> IO (Either String AgentFinish)-runAgent agent initialState@AgentState {..} initialInput = do- memWithInput <- addUserMessage agentMemory initialInput- case memWithInput of- Left err -> return $ Left err- Right updatedMem ->- let newState = initialState {agentMemory = updatedMem}- in runAgentLoop agent newState 0 10---- | Helper function for runAgent-runAgentLoop ::- (Agent a, BaseMemory m) => a -> AgentState m -> Int -> Int -> IO (Either String AgentFinish)-runAgentLoop agent agentState@AgentState {..} currIter maxIter- | currIter > maxIter = return $ Left "Max iterations excedded"- | otherwise = do- eStepResult <- runSingleStep agent agentState- case eStepResult of- Left err -> return $ Left err- Right (Finish agentFinish) -> return $ Right agentFinish- Right (Continue act@AgentAction {..}) -> do- toolList <- agentTools agent- toolResult <- executeTool toolList actionToolName actionInput- case toolResult of- Left err -> return $ Left err- Right result -> do- -- Add the tool result to memory as a tool message- let toolMsg = Message Tool result defaultMessageData- updatedMemResult <- addMessage agentMemory toolMsg- case updatedMemResult of- Left err -> return $ Left err- Right updatedMem ->- let updatedState =- agentState- { agentMemory = updatedMem- , agentToolResults = agentToolResults ++ [(actionToolName, result)]- , agentSteps = agentSteps ++ [act]- }- in runAgentLoop agent updatedState (currIter + 1) maxIter---- | Alias for planNextAction-runSingleStep :: (Agent a, BaseMemory m) => a -> AgentState m -> IO (Either String AgentStep)-runSingleStep = planNextAction--{- |-Execute a single tool call-Handles tool lookup and input/output conversion.--}-executeTool :: [AnyTool] -> Text -> Text -> IO (Either String Text)-executeTool tools toolName_ input = do- case find (\(AnyTool t _ _) -> toolName t == toolName_) tools of- Nothing -> return $ Left $ "Tool not found: " <> T.unpack toolName_- Just (AnyTool {..}) -> do- resultE <- try $ do- let typedInput = textToInput input- result <- runTool anyTool typedInput- return $ outputToText result- case resultE of- Left ex -> return $ Left $ "Tool execution error: " <> show (ex :: SomeException)- Right output -> return $ Right output--{- |-Helper for creating custom tool wrappers-Requires conversion functions between Text and tool-specific types.--}-customAnyTool :: Tool a => a -> (Text -> Input a) -> (Output a -> Text) -> AnyTool-customAnyTool tool inputConv outputConv = AnyTool tool inputConv outputConv
− src/Langchain/Agents/React.hs
@@ -1,162 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}--{- |-Module : Langchain.Agents.React-Description : Implementation of ReAct logic-Copyright : (c) 2025 Tushar Adhatrao-License : MIT-Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>--ReAct forces LLM to reflect on your question and injects responses as if LLM figured them out by itself. -This allows you to connect any datasource or tool tou your LLM.--}-module Langchain.Agents.React- ( defaultReactPromptTemplate- , runReactAgent- , ReactAgent (..)- ) where--import qualified Data.Map.Strict as Map-import Data.Text (Text)-import qualified Data.Text as T-import Langchain.Agents.Core-import Langchain.LLM.Core-import Langchain.Memory.Core-import Langchain.OutputParser.Core-import Langchain.PromptTemplate-import Langchain.Tool.Core---- | Default system prompt for React Agent-defaultReactPromptTemplate :: PromptTemplate-defaultReactPromptTemplate =- PromptTemplate $- T.unlines- [ "You are an AI assistant designed to help with tasks."- , "You have access to the following tools:"- , "{tools}"- , ""- , "If you don't know the answer, you can use tool to get the information."- , "Response in either one or the other format:"- , "1. If you don't the answer and want to make a tool call:"- , "Thought: you should always think about what to do"- , "Action: the action to take, should be one of [{tool_names}]"- , "Action Input: the input to the action"- , "... (this Thought/Action/Action Input can repeat N times)"- , ""- , "2. If you found out the answer:"- , "Thought: I now know the final answer"- , "Final Answer: the final answer to the original input question"- ]--newtype ReactAgentOutputParser = ReactAgentOutputParser AgentStep--instance OutputParser ReactAgentOutputParser where- parse = parseReactOutput---- | Parses the output from a React agent-parseReactOutput :: Text -> Either String ReactAgentOutputParser-parseReactOutput text- | T.isInfixOf "Final Answer:" text =- -- Extract the final answer- let answer = extractAfter "Final Answer:" text- in Right $- ReactAgentOutputParser $- Finish $- AgentFinish- { returnValues = Map.singleton "output" answer- , finishLog = text- }- | T.isInfixOf "Action:" text && T.isInfixOf "Action Input:" text =- -- Extract action and action input- let actionName =- extractAfter "Action:" $- T.takeWhile (/= '\n') $- T.dropWhile (/= 'A') text- actionInput_ =- extractAfter "Action Input:" $- T.takeWhile (/= '\n') $- snd $- T.breakOn "Action Input:" text- in Right $- ReactAgentOutputParser $- Continue $- AgentAction- { actionToolName = T.strip actionName- , actionInput = T.strip actionInput_- , actionLog = text- }- | otherwise = Left $ "Could not parse agent output: " <> T.unpack text---- Helper function to extract text after a marker-extractAfter :: Text -> Text -> Text-extractAfter marker text =- let afterMarker = snd $ T.breakOn marker text- in if T.null afterMarker- then ""- else T.strip $ T.drop 2 $ T.dropWhile (/= ':') afterMarker---- | ReactAgent Type-data (LLM llm) => ReactAgent llm = ReactAgent- { reactLLM :: llm- , reactLLMParams :: Maybe (LLMParams llm)- , reactToolList :: [AnyTool]- }---- | Run React Agent-runReactAgent ::- LLM llm =>- llm ->- Maybe (LLMParams llm) ->- [AnyTool] ->- Text ->- IO (Either String AgentFinish)-runReactAgent l mbParams tools userQuery = do- let promptVars =- Map.fromList- [ ("tools", formatTools tools)- , ("tool_names", formatToolNames tools)- ]- case renderPrompt defaultReactPromptTemplate promptVars of- Left err -> pure $ Left err- Right r -> do- let reactAgent =- ReactAgent- { reactLLM = l- , reactLLMParams = mbParams- , reactToolList = tools- }- let windowMem = WindowBufferMemory 10 (initialChatMessage r)- let agentState =- AgentState- { agentMemory = windowMem- , agentToolResults = []- , agentSteps = []- }- runAgent reactAgent agentState userQuery--formatTools :: [AnyTool] -> Text-formatTools tools = T.intercalate "\n\n" $ map formatTool tools- where- formatTool (AnyTool tool _ _) =- T.concat ["Tool: ", toolName tool, "\nDescription: ", toolDescription tool]--formatToolNames :: [AnyTool] -> Text-formatToolNames tools = T.intercalate ", " $ map (\(AnyTool tool _ _) -> toolName tool) tools--instance (LLM llm) => Agent (ReactAgent llm) where- planNextAction ReactAgent {..} AgentState {..} = do- eMsgs <- messages agentMemory- case eMsgs of- Left err -> pure $ Left err- Right msgs -> do- eResponse <- chat reactLLM msgs reactLLMParams- case eResponse of- Left err -> pure $ Left err- Right response -> do- case parse response of- Left err -> pure (Left $ "Failed to parse response " <> err <> show response)- Right (ReactAgentOutputParser step) -> return $ Right step-- agentPrompt _ = pure $ PromptTemplate ""- agentTools ReactAgent {..} = pure reactToolList
src/Langchain/Callback.hs view
@@ -83,5 +83,5 @@ stdOutCallback :: Callback stdOutCallback event = case event of LLMStart -> putStrLn "Model operation started"- LLMEnd -> putStrLn $ "Model completed with"+ LLMEnd -> putStrLn "Model completed with" LLMError err -> putStrLn $ "Error occurred: " ++ err
src/Langchain/Chain/RetrievalQA.hs view
@@ -20,6 +20,7 @@ import Data.Map.Strict (fromList) import Data.Text (Text) import qualified Data.Text as T+import qualified Data.Text.Lazy as TL import Langchain.DocumentLoader.Core (Document (..)) import Langchain.LLM.Core import Langchain.PromptTemplate (PromptTemplate (..), renderPrompt)@@ -47,7 +48,7 @@ -- | Make RetrievalQA an instance of Runnable to allow composition. instance (LLM llm, Retriever retriever) => Runnable (RetrievalQA llm retriever) where type RunnableInput (RetrievalQA llm retriever) = Text- type RunnableOutput (RetrievalQA llm retriever) = Text+ type RunnableOutput (RetrievalQA llm retriever) = Message invoke RetrievalQA {..} question = do -- Retrieve relevant documents@@ -55,7 +56,7 @@ case docResult of Left err -> return $ Left err Right docs -> do- let context = T.intercalate "\n\n" $ map (\(Document c _) -> c) docs+ let context = T.intercalate "\n\n" $ map (\(Document c _) -> TL.toStrict c) docs let vars = [("context", context)] -- Render prompt with context and question
src/Langchain/DocumentLoader/Core.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE OverloadedStrings #-} {- | Module : Langchain.DocumentLoader.Core Description : Core document loading functionality for LangChain Haskell@@ -50,9 +49,11 @@ , BaseLoader (..) ) where +import Control.Monad.IO.Class (MonadIO, liftIO) import Data.Aeson import Data.Map (Map, empty)-import Data.Text (Text)+import Data.Text.Lazy (Text)+import Langchain.Error (LangchainResult) {- | Document container with content and metadata. Used for storing loaded data and associated metadata like source URLs or page numbers.@@ -110,12 +111,18 @@ return $ Right (splitText defaultCharacterSplitterOps content) @ -}-class BaseLoader m where+class BaseLoader loader where -- | Load all documents from the source.- load :: m -> IO (Either String [Document])+ load :: loader -> IO (LangchainResult [Document]) + loadM :: MonadIO m => loader -> m (LangchainResult [Document])+ loadM loader = liftIO $ load loader+ -- | Load all the document and split them using recursiveCharacterSpliter- loadAndSplit :: m -> IO (Either String [Text])+ loadAndSplit :: loader -> IO (LangchainResult [Text])++ loadAndSplitM :: MonadIO m => loader -> m (LangchainResult [Text])+ loadAndSplitM loader = liftIO $ loadAndSplit loader {- $examples Key test case demonstrations:
src/Langchain/DocumentLoader/DirectoryLoader.hs view
@@ -12,10 +12,10 @@ DirectoryLoader document loader implements functionality for reading files from disk into Documents -} module Langchain.DocumentLoader.DirectoryLoader- ( - -- * Directory loader+ ( -- * Directory loader DirectoryLoader (..) , DirectoryLoaderOptions (..)+ -- * Default functions , defaultDirectoryLoaderOptions ) where@@ -23,12 +23,14 @@ import Control.Concurrent.Async (mapConcurrently) import Control.Monad (filterM) import Data.Maybe (listToMaybe)+import qualified Data.Text as T import Langchain.DocumentLoader.Core import Langchain.DocumentLoader.FileLoader (FileLoader (FileLoader)) import Langchain.DocumentLoader.PdfLoader (PdfLoader (PdfLoader))+import Langchain.Error (LangchainError, llmError) import Langchain.TextSplitter.Character import System.Directory (doesDirectoryExist, doesFileExist, listDirectory)-import System.FilePath (takeFileName, takeExtension, (</>))+import System.FilePath (takeExtension, takeFileName, (</>)) -- | Options for directory loading behavior data DirectoryLoaderOptions = DirectoryLoaderOptions@@ -71,7 +73,7 @@ shouldIncludeFile opts path = let ext = takeExtension path fName = takeFileName path- isHidden = if listToMaybe fName == Just '.' then True else False+ isHidden = listToMaybe fName == Just '.' matchesExt = null (extensions opts) || ext `elem` extensions opts passesHiddenCheck = not (excludeHidden opts) || not isHidden in matchesExt && passesHiddenCheck@@ -99,22 +101,33 @@ -- Skip hidden directories if excludeHidden is set let visibleSubdirs = if excludeHidden opts- then filter (\d -> not (null d) && last d /= '.') subdirs+ then filter (\d -> not (null d) && listToMaybe d /= Just '.') subdirs else subdirs -- Process subdirectories (potentially in parallel) if useMultithreading opts && not (null visibleSubdirs)- then concat <$> mapConcurrently (getFilesInDirectory opts (currentDepth + 1)) visibleSubdirs+ then+ concat+ <$> mapConcurrently+ (getFilesInDirectory opts (currentDepth + 1))+ visibleSubdirs else concat <$> mapM (getFilesInDirectory opts (currentDepth + 1)) visibleSubdirs else return [] return $ filteredFiles ++ subFiles -loadFileToDocument :: FilePath -> IO (Either String [Document])+loadFileToDocument :: FilePath -> IO (Either LangchainError [Document]) loadFileToDocument path = do exists <- doesFileExist path if not exists- then return $ Left $ "File does not exist: " ++ path+ then+ return $+ Left+ ( llmError+ (T.pack $ "File does not exist: " ++ path)+ Nothing+ Nothing+ ) else do -- if file is pdf then read it using PdfLoader else use fileLoader if takeExtension path == ".pdf"@@ -138,11 +151,13 @@ let (errors, documents) = foldr separateResults ([], []) docs -- Return documents or combined error message- case errors of- [] -> return $ Right documents- _ -> return $ Left $ unlines errors+ case listToMaybe errors of+ Nothing -> return $ Right documents+ Just err -> return $ Left err else- return $ Left $ "Directory does not exist: " ++ dirPath+ return $+ Left $+ llmError (T.pack $ "Directory does not exist: " ++ dirPath) Nothing Nothing where separateResults (Left err) (errs, docs) = (err : errs, docs) separateResults (Right doc) (errs, docs) = (errs, doc <> docs)
src/Langchain/DocumentLoader/FileLoader.hs view
@@ -30,8 +30,10 @@ import Data.Aeson import Data.Map (fromList)-import Data.Text (pack)+import qualified Data.Text as T+import qualified Data.Text.Lazy as TL import Langchain.DocumentLoader.Core+import Langchain.Error (SomeException, llmError, try) import Langchain.TextSplitter.Character import System.Directory (doesFileExist) @@ -43,13 +45,13 @@ >>> FileLoader "docs/example.txt" FileLoader "docs/example.txt" -}-data FileLoader = FileLoader FilePath+newtype FileLoader = FileLoader FilePath instance BaseLoader FileLoader where -- \| Load document with file source metadata -- -- Example:- + -- >>> load (FileLoader "test.txt") -- Right [Document {pageContent = "Test content", metadata = fromList [("source", "test.txt")]}] --@@ -57,16 +59,31 @@ exists <- doesFileExist path if exists then do- content <- readFile path- let meta = fromList [("source", String $ pack path)]- return $ Right [Document (pack content) meta]+ eContent <- try $ readFile path+ case eContent of+ Left err ->+ return $+ Left $+ llmError+ (T.pack $ "Error reading file: " ++ path ++ show (err :: SomeException))+ Nothing+ Nothing+ Right content -> do+ let meta = fromList [("source", String $ T.pack path)]+ return $ Right [Document (TL.pack content) meta] else- return $ Left $ "File not found: " ++ path+ return $+ Left+ ( llmError+ (T.pack $ "File not found: " ++ path)+ Nothing+ Nothing+ ) -- \| Load and split content using default character splitter -- -- Example:- + -- >>> loadAndSplit (FileLoader "split.txt") -- Right ["Paragraph 1", "Paragraph 2", ...] --@@ -74,10 +91,24 @@ exists <- doesFileExist path if exists then do- content <- readFile path- return $ Right $ splitText defaultCharacterSplitterOps (pack content)+ eContent <- try $ readFile path+ case eContent of+ Left err ->+ return $+ Left $+ llmError+ (T.pack $ "Error reading file: " ++ path ++ show (err :: SomeException))+ Nothing+ Nothing+ Right content -> return $ Right $ splitText defaultCharacterSplitterOps (TL.pack content) else- return $ Left $ "File not found: " ++ path+ return $+ Left+ ( llmError+ (T.pack $ "File not found: " ++ path)+ Nothing+ Nothing+ ) {- $examples Test case patterns:
src/Langchain/DocumentLoader/PdfLoader.hs view
@@ -8,7 +8,7 @@ Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com> Stability : experimental -This module provides a loader for loading PDF files. +This module provides a loader for loading PDF files. -} module Langchain.DocumentLoader.PdfLoader ( PdfLoader (..)@@ -16,8 +16,11 @@ import Data.Aeson import Data.Map (fromList)+import qualified Data.Text.Lazy as TL import Langchain.DocumentLoader.Core+import Langchain.Error (llmError) import Langchain.TextSplitter.Character+import Langchain.Utils (showText) import Pdf.Document hiding (Document) import System.Directory (doesFileExist) @@ -44,16 +47,25 @@ catalog <- documentCatalog doc rootNode <- catalogPageNode catalog count <- pageNodeNKids rootNode- textList <- sequence [pageExtractText =<< pageNodePageByNum rootNode i | i <- [0 .. count - 1]]- pure- $ map- ( \(content, pageNum) ->+ textList <-+ sequence+ [ pageExtractText+ =<< pageNodePageByNum rootNode i+ | i <- [0 .. count - 1]+ ]+ pure $+ zipWith+ ( \content pageNum -> Document { pageContent = content- , metadata = fromList [("page number", Number $ fromIntegral pageNum)]+ , metadata =+ fromList+ [ ("page number", Number $ fromIntegral pageNum)+ ] } )- $ zip textList [1 .. count]+ (map TL.fromStrict textList)+ [1 .. count] {- | A loader for PDF files.@@ -62,7 +74,7 @@ It implements the 'BaseLoader' interface to provide methods for loading and splitting PDF content. -}-data PdfLoader = PdfLoader FilePath+newtype PdfLoader = PdfLoader FilePath instance BaseLoader PdfLoader where -- \|@@ -82,7 +94,9 @@ content <- readPdf path return $ Right content else- return $ Left $ "File not found: " ++ path+ return $+ Left $+ llmError (showText $ "File not found: " ++ path) Nothing Nothing -- \| -- Loads the raw content of the PDF file and splits it using a character splitter.@@ -100,6 +114,12 @@ if exists then do documents <- readPdf path- return $ Right $ splitText defaultCharacterSplitterOps (pageContent $ mconcat documents)+ return $+ Right $+ splitText+ defaultCharacterSplitterOps+ (pageContent $ mconcat documents) else- return $ Left $ "File not found: " ++ path+ return $+ Left $+ llmError (showText $ "File not found: " ++ path) Nothing Nothing
src/Langchain/Embeddings/Core.hs view
@@ -30,8 +30,10 @@ Embeddings (..) ) where +import Control.Monad.IO.Class (MonadIO, liftIO) import Data.Text (Text) import Langchain.DocumentLoader.Core+import Langchain.Error (LangchainResult) {- | Typeclass for embedding models following LangChain's pattern. Converts text/documents into numerical vectors for machine learning tasks.@@ -53,23 +55,31 @@ embedQuery _ _ = return $ Right [0.4, 0.5, 0.6] @ -}-class Embeddings m where- -- | Convert documents to embedding vectors- --- -- Example:- --- -- >>> let doc = Document "Hello world" mempty- -- >>> embedDocuments TestEmbeddings [doc]- -- Right [[0.1, 0.2, 0.3]]- embedDocuments :: m -> [Document] -> IO (Either String [[Float]])+class Embeddings embed where+ {- | Convert documents to embedding vectors - -- | Convert query text to embedding vector- --- -- Example:- --- -- >>> embedQuery TestEmbeddings "Search query"- -- Right [0.4, 0.5, 0.6]- embedQuery :: m -> Text -> IO (Either String [Float])+ Example:++ >>> let doc = Document "Hello world" mempty+ >>> embedDocuments TestEmbeddings [doc]+ Right [[0.1, 0.2, 0.3]]+ -}+ embedDocuments :: embed -> [Document] -> IO (LangchainResult [[Float]])++ embedDocumentsM :: MonadIO m => embed -> [Document] -> m (LangchainResult [[Float]])+ embedDocumentsM embeddings docs = liftIO $ embedDocuments embeddings docs++ {- | Convert query text to embedding vector++ Example:++ >>> embedQuery TestEmbeddings "Search query"+ Right [0.4, 0.5, 0.6]+ -}+ embedQuery :: embed -> Text -> IO (LangchainResult [Float])++ embedQueryM :: MonadIO m => embed -> Text -> m (LangchainResult [Float])+ embedQueryM embeddings query = liftIO $ embedQuery embeddings query {- $examples Test case patterns:
+ src/Langchain/Embeddings/Gemini.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++{- |+Module : Langchain.Embeddings.Gemini+Description : Gemini integration for text embeddings in LangChain Haskell+Copyright : (c) 2025 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++Gemini implementation of LangChain's embedding interface. Supports document and query+embedding generation through Gemini's OpenAI compatible API.+Checkout docs here: https://ai.google.dev/gemini-api/docs/openai#embeddings+-}+module Langchain.Embeddings.Gemini+ ( -- * Types+ GeminiEmbeddings (..)+ , defaultGeminiEmbeddings+ , module Langchain.Embeddings.Core+ ) where++import Data.Text (Text, unpack)+import GHC.Generics+import Langchain.Embeddings.Core+import Langchain.Embeddings.OpenAI++data GeminiEmbeddings = GeminiEmbeddings+ { apiKey :: Text+ -- ^ Gemini API Key+ , baseUrl :: Maybe String+ -- ^ base url; default "https://generativelanguage.googleapis.com/v1beta/openai"+ , model :: Text+ -- ^ Model name for embeddings+ , dimensions :: Maybe Int+ -- ^ The number of dimensions the resulting output embeddings should have.+ , encodingFormat :: Maybe EncodingFormat+ {- ^ The format to return the embeddings in.+ ^ For now, only float is supported+ -}+ , embeddingsUser :: Maybe Text+ -- ^ A unique identifier representing your end-user, which can help monitor and detect abuse.+ , timeout :: Maybe Int+ -- ^ Override default responsetime out. unit = seconds.+ }+ deriving (Eq, Generic)++instance Show GeminiEmbeddings where+ show GeminiEmbeddings {..} = "GeminiEmbeddings " <> "model " <> unpack model++-- | Default values GeminiEmbeddings, api-key is empty+defaultGeminiEmbeddings :: GeminiEmbeddings+defaultGeminiEmbeddings =+ GeminiEmbeddings+ { apiKey = ""+ , baseUrl = pure "https://generativelanguage.googleapis.com/v1beta/openai"+ , model = "gemini-embedding-001"+ , dimensions = Nothing+ , encodingFormat = Nothing+ , embeddingsUser = Nothing+ , timeout = Nothing+ }++instance Embeddings GeminiEmbeddings where+ embedDocuments GeminiEmbeddings {..} = embedDocuments OpenAIEmbeddings {..}+ embedQuery GeminiEmbeddings {..} = embedQuery OpenAIEmbeddings {..}
src/Langchain/Embeddings/Ollama.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {- |@@ -33,13 +34,18 @@ -} module Langchain.Embeddings.Ollama ( OllamaEmbeddings (..)+ , module Langchain.DocumentLoader.Core ) where import Data.Maybe import Data.Ollama.Embeddings+import qualified Data.Ollama.Embeddings as O import Data.Text (Text)+import qualified Data.Text.Lazy as T import Langchain.DocumentLoader.Core import Langchain.Embeddings.Core+import Langchain.Error (llmError)+import Langchain.Utils (showText) {- | Ollama-specific embedding configuration Contains parameters for controlling:@@ -50,7 +56,7 @@ Example configuration: ->>> OllamaEmbeddings "nomic-embed" (Just False) (Just "1h")+>>> OllamaEmbeddings "nomic-embed" (Just False) (Just 3600) Nothing OllamaEmbeddings {model = "nomic-embed", ...} -} data OllamaEmbeddings = OllamaEmbeddings@@ -58,36 +64,35 @@ -- ^ The name of the Ollama model to use for embeddings , defaultTruncate :: Maybe Bool -- ^ Optional flag to truncate input if supported by the API- , defaultKeepAlive :: Maybe Text- -- ^ Keep model loaded for specified duration (e.g., "5m")+ , defaultKeepAlive :: Maybe Int+ -- ^ Keep model loaded for specified duration in seconds (e.g., 300 for 5 minutes)+ , modelOptions :: Maybe O.ModelOptions+ -- ^ Optional model parameters (e.g., temperature) as specified in the Modelfile. } deriving (Show, Eq) -go :: EmbeddingResp -> Either String [Float]-go embResp =- case listToMaybe (embedding_ embResp) of- Nothing -> Left "Embeddings are empty"- Just x -> Right x- instance Embeddings OllamaEmbeddings where- -- \| Document embedding implementation [[3]]:+ -- \| Document embedding implementation: -- Processes each document individually through Ollama's API. -- -- Example: -- >>> let doc = Document "Test content" mempty -- >>> embedDocuments ollamaEmb [doc] -- Right [[0.1, 0.2, ...], ...]- -- embedDocuments (OllamaEmbeddings {..}) docs = do -- For each input text, make an individual API call- results <-- mapM- (\doc -> embeddingOps model (pageContent doc) defaultTruncate defaultKeepAlive)- docs- -- Combine the results, handling errors appropriately- return $- sequence results >>= \resps ->- mapM go resps+ eRes <-+ embeddingOps+ model+ (map (T.toStrict . pageContent) docs)+ defaultTruncate+ defaultKeepAlive+ modelOptions+ Nothing+ Nothing+ case eRes of+ Left ollamaErr -> return $ Left $ llmError (showText ollamaErr) Nothing Nothing+ Right r -> return $ Right $ respondedEmbeddings r -- \| Query embedding implementation: -- Generates vector representation for search queries.@@ -97,10 +102,18 @@ -- Right [0.3, 0.4, ...] -- embedQuery (OllamaEmbeddings {..}) query = do- res <- embeddingOps model query defaultTruncate defaultKeepAlive- case fmap embedding_ res of- Left err -> pure $ Left err+ res <-+ embeddingOps+ model+ [query]+ defaultTruncate+ defaultKeepAlive+ modelOptions+ Nothing+ Nothing+ case fmap respondedEmbeddings res of+ Left err -> pure $ Left (llmError (showText err) Nothing Nothing) Right lst -> case listToMaybe lst of- Nothing -> pure $ Left "Embeddings are empty"+ Nothing -> pure $ Left (llmError "Embeddings are empty" Nothing Nothing) Just x -> pure $ Right x
src/Langchain/Embeddings/OpenAI.hs view
@@ -23,6 +23,7 @@ , textEmbedding3Small , textEmbedding3Large , textEmbeddingAda+ , EncodingFormat (..) ) where {-@@ -41,11 +42,14 @@ import Data.Aeson import Data.Maybe import Data.Text (Text, unpack)+import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8)+import qualified Data.Text.Lazy as TL import qualified Data.Vector as V import GHC.Generics import Langchain.DocumentLoader.Core import Langchain.Embeddings.Core+import Langchain.Error (llmError) import Network.HTTP.Conduit import Network.HTTP.Simple ( getResponseBody@@ -70,7 +74,6 @@ , dimensionsReq :: Maybe Int -- ^ Only supported in text-embedding-3 or later , encodingFormatReq :: Maybe EncodingFormat- , embeddingsUserReq :: Maybe Text } deriving (Show, Eq, Generic) @@ -89,7 +92,6 @@ , "model" .= modelReq , "dimensions" .= dimensionsReq , "encoding_format" .= encodingFormatReq- , "user" .= embeddingsUserReq ] -- Response@@ -101,7 +103,7 @@ data EmbeddingsObject = EmbeddingsObject { embeddings :: [Float]- , index :: Int+ , index :: Maybe Int , objectType :: Text } deriving (Eq, Show, Generic)@@ -110,7 +112,7 @@ { objectTypeResp :: Text , dataList :: [EmbeddingsObject] , responseModel :: Text- , usage :: EmbeddingsUsage+ , usage :: Maybe EmbeddingsUsage } deriving (Eq, Show, Generic) @@ -125,7 +127,7 @@ parseJSON (Object v) = EmbeddingsObject <$> v .: "embedding"- <*> v .: "index"+ <*> v .:? "index" <*> v .: "object" parseJSON _ = error "Parse error, expecting object" @@ -135,23 +137,25 @@ <$> v .: "object" <*> v .: "data" <*> v .: "model"- <*> v .: "usage"+ <*> v .:? "usage" parseJSON _ = error "Parse error, expecting object" -- | Embeddings type for OpenAI, can be used for embed documents with OpenAI. data OpenAIEmbeddings = OpenAIEmbeddings { apiKey :: Text -- ^ OpenAI API Key+ , baseUrl :: Maybe String+ -- ^ base url; default "https://api.openai.com/v1" , model :: Text -- ^ Model name for embeddings , dimensions :: Maybe Int- -- ^ The number of dimensions the resulting output embeddings should have.- -- ^ Only supported in text-embedding-3 or later+ {- ^ The number of dimensions the resulting output embeddings should have.+ ^ Only supported in text-embedding-3 or later+ -} , encodingFormat :: Maybe EncodingFormat- -- ^ The format to return the embeddings in.- -- ^ For now, only float is supported- , embeddingsUser :: Maybe Text- -- ^ A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse.+ {- ^ The format to return the embeddings in.+ ^ For now, only float is supported+ -} , timeout :: Maybe Int -- ^ Override default responsetime out. unit = seconds. }@@ -160,9 +164,12 @@ instance Show OpenAIEmbeddings where show OpenAIEmbeddings {..} = "OpenAIEmbeddings " <> "model " <> unpack model -openAIEmbeddingsRequest :: OpenAIEmbeddings -> [Text] -> IO (Either String OpenAIEmbeddingsResponse)+openAIEmbeddingsRequest ::+ OpenAIEmbeddings -> [Text] -> IO (Either String OpenAIEmbeddingsResponse) openAIEmbeddingsRequest OpenAIEmbeddings {..} txts = do- request_ <- parseRequest "https://api.openai.com/v1/embeddings"+ request_ <-+ parseRequest $+ fromMaybe "https://api.openai.com/v1" baseUrl <> "/embeddings" manager <- newManager tlsManagerSettings@@ -170,20 +177,19 @@ responseTimeoutMicro (fromMaybe 60 timeout * 1000000) } let req =- setRequestMethod "POST"- $ setRequestSecure True- $ setRequestHeader "Content-Type" ["application/json"]- $ setRequestHeader "Authorization" ["Bearer " <> encodeUtf8 apiKey]- $ setRequestBodyJSON- ( OpenAIEmbeddingsRequest- { inputReq = TextList txts- , modelReq = model- , dimensionsReq = dimensions- , encodingFormatReq = encodingFormat- , embeddingsUserReq = embeddingsUser- }- )- $ request_+ setRequestMethod "POST" $+ setRequestSecure True $+ setRequestHeader "Content-Type" ["application/json"] $+ setRequestHeader "Authorization" ["Bearer " <> encodeUtf8 apiKey] $+ setRequestBodyJSON+ ( OpenAIEmbeddingsRequest+ { inputReq = TextList txts+ , modelReq = model+ , dimensionsReq = dimensions+ , encodingFormatReq = encodingFormat+ }+ )+ request_ response <- httpLbs req manager let status = statusCode $ getResponseStatus response@@ -191,23 +197,29 @@ then case eitherDecode (getResponseBody response) of Left err -> return $ Left $ "JSON parse error: " <> err Right completionResponse -> return $ Right completionResponse- else return $ Left $ "API error: " <> show status <> " " <> show (getResponseBody response)+ else+ return $+ Left $+ "API error: "+ <> show status+ <> " "+ <> show (getResponseBody response) instance Embeddings OpenAIEmbeddings where embedDocuments openAIEmbeddings docs = do- eRes <- openAIEmbeddingsRequest openAIEmbeddings (map pageContent docs)+ eRes <- openAIEmbeddingsRequest openAIEmbeddings (map (TL.toStrict . pageContent) docs) case eRes of- Left err -> pure $ Left err+ Left err -> pure $ Left (llmError (T.pack err) Nothing Nothing) Right (OpenAIEmbeddingsResponse {..}) -> do pure $ Right $ map embeddings dataList embedQuery openAIEmbeddings query = do eRes <- openAIEmbeddingsRequest openAIEmbeddings [query] case eRes of- Left err -> pure $ Left err+ Left err -> pure $ Left (llmError (T.pack err) Nothing Nothing) Right (OpenAIEmbeddingsResponse {..}) -> do case listToMaybe dataList of- Nothing -> pure $ Left "Embeddings are empty"+ Nothing -> pure $ Left (llmError "Embeddings are empty" Nothing Nothing) Just x -> pure $ Right $ embeddings x -- Helper functions, model name functions@@ -229,9 +241,9 @@ defaultOpenAIEmbeddings = OpenAIEmbeddings { apiKey = ""+ , baseUrl = pure "https://api.openai.com/v1" , model = textEmbedding3Small , dimensions = Nothing , encodingFormat = Nothing- , embeddingsUser = Nothing , timeout = Nothing }
+ src/Langchain/Error.hs view
@@ -0,0 +1,609 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++{- |+Module : Langchain.Error+Description : Central error handling for langchain-hs+Copyright : (c) 2025 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++This module provides a comprehensive error handling system for langchain-hs,+replacing the previous `Either String` pattern with a structured, type-safe+approach that follows industry best practices.++The error system includes:++* Structured error types with context and metadata+* Error severity levels and categories+* Utility functions for error construction and handling+* Integration with existing langchain-hs components+* Support for error chaining and context preservation++Example usage:++@+import Langchain.Error++-- Creating errors+let err = llmError "Model timeout" (Just "gpt-4") Nothing++-- Error handling with context+result <- someOperation+case result of+ Left err -> do+ logError err+ handleError err+ Right value -> processValue value++-- Error chaining+chainError "Failed to process document" originalError+@+-}+module Langchain.Error+ ( -- * Error Types+ LangchainError (..)+ , ErrorSeverity (..)+ , ErrorCategory (..)+ , ErrorContext (..)++ -- * Error Construction+ , llmError+ , llmErrorWithContext+ , agentError+ , agentErrorWithContext+ , memoryError+ , memoryErrorWithContext+ , toolError+ , toolErrorWithContext+ , vectorStoreError+ , vectorStoreErrorWithContext+ , documentLoaderError+ , documentLoaderErrorWithContext+ , embeddingError+ , embeddingErrorWithContext+ , runnableError+ , runnableErrorWithContext+ , parsingError+ , parsingErrorWithContext+ , networkError+ , networkErrorWithContext+ , configurationError+ , configurationErrorWithContext+ , validationError+ , validationErrorWithContext+ , internalError+ , internalErrorWithContext++ -- * Error Utilities+ , chainError+ , addContext+ , withErrorContext+ , mapError+ , fromString+ , toString+ , toText+ , logError+ , isRetryable+ , getSeverity+ , getCategory+ , fromStringError+ , fromException+ , liftStringError+ , simpleError+ , catchToLangchainError+ , withContext+ , withContextIO++ -- * Type Aliases+ , LangchainResult+ , LangchainIO++ -- * Re-exports for convenience+ , module Control.Exception+ ) where++import Control.Exception (Exception, SomeException, displayException, try)+import Control.Monad.IO.Class (MonadIO, liftIO)+import Data.Aeson (FromJSON, ToJSON)+import Data.Maybe (fromMaybe)+import Data.Text (Text)+import qualified Data.Text as T+import Data.Time (UTCTime, getCurrentTime)+import GHC.Generics (Generic)+import System.IO (hPutStrLn, stderr)++-- | Severity levels for errors, following industry standards+data ErrorSeverity+ = -- | System-breaking errors that require immediate attention+ Critical+ | -- | Errors that prevent core functionality+ High+ | -- | Errors that degrade functionality but allow continuation+ Medium+ | -- | Minor errors or warnings+ Low+ | -- | Informational messages+ Info+ deriving (Eq, Ord, Show, Generic, ToJSON, FromJSON)++-- | Categories of errors for better organization and handling+data ErrorCategory+ = -- | Language model related errors+ LLMError+ | -- | Agent execution errors+ AgentError+ | -- | Memory management errors+ MemoryError+ | -- | Tool execution errors+ ToolError+ | -- | Vector store operation errors+ VectorStoreError+ | -- | Document loading errors+ DocumentLoaderError+ | -- | Embedding generation errors+ EmbeddingError+ | -- | Runnable execution errors+ RunnableError+ | -- | Data parsing and validation errors+ ParsingError+ | -- | Network and HTTP errors+ NetworkError+ | -- | Configuration and setup errors+ ConfigurationError+ | -- | Input validation errors+ ValidationError+ | -- | Internal system errors+ InternalError+ deriving (Eq, Show, Generic, ToJSON, FromJSON)++-- | Additional context information for errors+data ErrorContext = ErrorContext+ { contextComponent :: Maybe Text+ -- ^ Component where error occurred+ , contextOperation :: Maybe Text+ -- ^ Operation being performed+ , contextInput :: Maybe Text+ -- ^ Input that caused the error+ , contextMetadata :: [(Text, Text)]+ -- ^ Additional metadata+ , contextTimestamp :: UTCTime+ -- ^ When the error occurred+ }+ deriving (Eq, Show, Generic, ToJSON, FromJSON)++-- | The central error type for langchain-hs+data LangchainError = LangchainError+ { errorMessage :: Text+ -- ^ Human-readable error message+ , errorSeverity :: ErrorSeverity+ -- ^ Severity level+ , errorCategory :: ErrorCategory+ -- ^ Error category+ , errorContext :: Maybe ErrorContext+ -- ^ Additional context+ , errorCause :: Maybe LangchainError+ -- ^ Chained/nested error+ , errorCode :: Maybe Text+ -- ^ Optional error code+ }+ deriving (Eq, Show, Generic, ToJSON, FromJSON)++instance Exception LangchainError where+ displayException LangchainError {..} =+ T.unpack $+ T.unlines $+ filter+ (not . T.null)+ [ "["+ <> T.pack (show errorSeverity)+ <> "] "+ <> T.pack (show errorCategory)+ <> ": "+ <> errorMessage+ , maybe "" ("Error Code: " <>) errorCode+ , maybe "" formatContext errorContext+ , maybe "" (\cause -> "Caused by: " <> T.pack (show cause)) errorCause+ ]+ where+ formatContext ErrorContext {..} =+ T.unlines $+ filter+ (not . T.null)+ [ maybe "" ("Component: " <>) contextComponent+ , maybe "" ("Operation: " <>) contextOperation+ , maybe "" ("Input: " <>) contextInput+ , if null contextMetadata then "" else "Metadata: " <> T.pack (show contextMetadata)+ , "Timestamp: " <> T.pack (show contextTimestamp)+ ]++-- | Type alias for results that can fail with LangchainError+type LangchainResult a = Either LangchainError a++-- | Type alias for IO operations that can fail with LangchainError+type LangchainIO a = IO (LangchainResult a)++-- | Create an LLM-related error+llmError :: Text -> Maybe Text -> Maybe Text -> LangchainError+llmError msg _model _operation =+ LangchainError+ { errorMessage = msg+ , errorSeverity = High+ , errorCategory = LLMError+ , errorContext = Nothing+ , errorCause = Nothing+ , errorCode = Nothing+ }++-- | Create an LLM error with context+llmErrorWithContext ::+ Text ->+ Maybe Text ->+ Maybe Text ->+ ErrorContext ->+ LangchainError+llmErrorWithContext msg model operation ctx =+ (llmError msg model operation)+ { errorContext =+ Just ctx {contextComponent = model, contextOperation = operation}+ }++-- | Create an agent-related error+agentError :: Text -> Maybe Text -> Maybe Text -> LangchainError+agentError msg _agentType _operation =+ LangchainError+ { errorMessage = msg+ , errorSeverity = High+ , errorCategory = AgentError+ , errorContext = Nothing+ , errorCause = Nothing+ , errorCode = Nothing+ }++-- | Create an agent error with context+agentErrorWithContext :: Text -> Maybe Text -> Maybe Text -> ErrorContext -> LangchainError+agentErrorWithContext msg agentType operation ctx =+ (agentError msg agentType operation)+ { errorContext = Just ctx {contextComponent = agentType, contextOperation = operation}+ }++-- | Create a memory-related error+memoryError :: Text -> Maybe Text -> Maybe Text -> LangchainError+memoryError msg _memoryType _operation =+ LangchainError+ { errorMessage = msg+ , errorSeverity = Medium+ , errorCategory = MemoryError+ , errorContext = Nothing+ , errorCause = Nothing+ , errorCode = Nothing+ }++-- | Create a memory error with context+memoryErrorWithContext :: Text -> Maybe Text -> Maybe Text -> ErrorContext -> LangchainError+memoryErrorWithContext msg memoryType operation ctx =+ (memoryError msg memoryType operation)+ { errorContext = Just ctx {contextComponent = memoryType, contextOperation = operation}+ }++-- | Create a tool-related error+toolError :: Text -> Maybe Text -> Maybe Text -> LangchainError+toolError msg _toolName _operation =+ LangchainError+ { errorMessage = msg+ , errorSeverity = High+ , errorCategory = ToolError+ , errorContext = Nothing+ , errorCause = Nothing+ , errorCode = Nothing+ }++-- | Create a tool error with context+toolErrorWithContext :: Text -> Maybe Text -> Maybe Text -> ErrorContext -> LangchainError+toolErrorWithContext msg toolName operation ctx =+ (toolError msg toolName operation)+ { errorContext = Just ctx {contextComponent = toolName, contextOperation = operation}+ }++-- | Create a vector store error+vectorStoreError :: Text -> Maybe Text -> Maybe Text -> LangchainError+vectorStoreError msg _storeType _operation =+ LangchainError+ { errorMessage = msg+ , errorSeverity = High+ , errorCategory = VectorStoreError+ , errorContext = Nothing+ , errorCause = Nothing+ , errorCode = Nothing+ }++-- | Create a vector store error with context+vectorStoreErrorWithContext :: Text -> Maybe Text -> Maybe Text -> ErrorContext -> LangchainError+vectorStoreErrorWithContext msg storeType operation ctx =+ (vectorStoreError msg storeType operation)+ { errorContext = Just ctx {contextComponent = storeType, contextOperation = operation}+ }++-- | Create a document loader error+documentLoaderError :: Text -> Maybe Text -> Maybe Text -> LangchainError+documentLoaderError msg _loaderType _operation =+ LangchainError+ { errorMessage = msg+ , errorSeverity = Medium+ , errorCategory = DocumentLoaderError+ , errorContext = Nothing+ , errorCause = Nothing+ , errorCode = Nothing+ }++-- | Create a document loader error with context+documentLoaderErrorWithContext :: Text -> Maybe Text -> Maybe Text -> ErrorContext -> LangchainError+documentLoaderErrorWithContext msg loaderType operation ctx =+ (documentLoaderError msg loaderType operation)+ { errorContext = Just ctx {contextComponent = loaderType, contextOperation = operation}+ }++-- | Create an embedding error+embeddingError :: Text -> Maybe Text -> Maybe Text -> LangchainError+embeddingError msg _embeddingType _operation =+ LangchainError+ { errorMessage = msg+ , errorSeverity = High+ , errorCategory = EmbeddingError+ , errorContext = Nothing+ , errorCause = Nothing+ , errorCode = Nothing+ }++-- | Create an embedding error with context+embeddingErrorWithContext :: Text -> Maybe Text -> Maybe Text -> ErrorContext -> LangchainError+embeddingErrorWithContext msg embeddingType operation ctx =+ (embeddingError msg embeddingType operation)+ { errorContext = Just ctx {contextComponent = embeddingType, contextOperation = operation}+ }++-- | Create a runnable error+runnableError :: Text -> Maybe Text -> Maybe Text -> LangchainError+runnableError msg _runnableType _operation =+ LangchainError+ { errorMessage = msg+ , errorSeverity = High+ , errorCategory = RunnableError+ , errorContext = Nothing+ , errorCause = Nothing+ , errorCode = Nothing+ }++-- | Create a runnable error with context+runnableErrorWithContext :: Text -> Maybe Text -> Maybe Text -> ErrorContext -> LangchainError+runnableErrorWithContext msg runnableType operation ctx =+ (runnableError msg runnableType operation)+ { errorContext = Just ctx {contextComponent = runnableType, contextOperation = operation}+ }++-- | Create a parsing error+parsingError :: Text -> Maybe Text -> Maybe Text -> LangchainError+parsingError msg _parserType _input =+ LangchainError+ { errorMessage = msg <> fromMaybe "" _parserType+ , errorSeverity = Medium+ , errorCategory = ParsingError+ , errorContext = Nothing+ , errorCause = Nothing+ , errorCode = _input+ }++-- | Create a parsing error with context+parsingErrorWithContext :: Text -> Maybe Text -> Maybe Text -> ErrorContext -> LangchainError+parsingErrorWithContext msg parserType input ctx =+ (parsingError msg parserType input)+ { errorContext = Just ctx {contextComponent = parserType, contextInput = input}+ }++-- | Create a network error+networkError :: Text -> Maybe Text -> Maybe Text -> LangchainError+networkError msg _endpoint _operation =+ LangchainError+ { errorMessage = msg+ , errorSeverity = High+ , errorCategory = NetworkError+ , errorContext = Nothing+ , errorCause = Nothing+ , errorCode = Nothing+ }++-- | Create a network error with context+networkErrorWithContext :: Text -> Maybe Text -> Maybe Text -> ErrorContext -> LangchainError+networkErrorWithContext msg endpoint operation ctx =+ (networkError msg endpoint operation)+ { errorContext = Just ctx {contextComponent = endpoint, contextOperation = operation}+ }++-- | Create a configuration error+configurationError :: Text -> Maybe Text -> Maybe Text -> LangchainError+configurationError msg _configKey _operation =+ LangchainError+ { errorMessage = msg+ , errorSeverity = Critical+ , errorCategory = ConfigurationError+ , errorContext = Nothing+ , errorCause = Nothing+ , errorCode = Nothing+ }++-- | Create a configuration error with context+configurationErrorWithContext :: Text -> Maybe Text -> Maybe Text -> ErrorContext -> LangchainError+configurationErrorWithContext msg configKey operation ctx =+ (configurationError msg configKey operation)+ { errorContext = Just ctx {contextComponent = configKey, contextOperation = operation}+ }++-- | Create a validation error+validationError :: Text -> Maybe Text -> Maybe Text -> LangchainError+validationError msg _field _input =+ LangchainError+ { errorMessage = msg+ , errorSeverity = Medium+ , errorCategory = ValidationError+ , errorContext = Nothing+ , errorCause = Nothing+ , errorCode = Nothing+ }++-- | Create a validation error with context+validationErrorWithContext :: Text -> Maybe Text -> Maybe Text -> ErrorContext -> LangchainError+validationErrorWithContext msg field input ctx =+ (validationError msg field input)+ { errorContext = Just ctx {contextComponent = field, contextInput = input}+ }++-- | Create an internal error+internalError :: Text -> Maybe Text -> Maybe Text -> LangchainError+internalError msg _component _operation =+ LangchainError+ { errorMessage = msg+ , errorSeverity = Critical+ , errorCategory = InternalError+ , errorContext = Nothing+ , errorCause = Nothing+ , errorCode = Nothing+ }++-- | Create an internal error with context+internalErrorWithContext :: Text -> Maybe Text -> Maybe Text -> ErrorContext -> LangchainError+internalErrorWithContext msg component operation ctx =+ (internalError msg component operation)+ { errorContext = Just ctx {contextComponent = component, contextOperation = operation}+ }++-- | Chain an error with a new message, preserving the original as the cause+chainError :: Text -> LangchainError -> LangchainError+chainError msg originalError =+ LangchainError+ { errorMessage = msg+ , errorSeverity = errorSeverity originalError+ , errorCategory = errorCategory originalError+ , errorContext = errorContext originalError+ , errorCause = Just originalError+ , errorCode = errorCode originalError+ }++-- | Add context to an existing error+addContext :: ErrorContext -> LangchainError -> LangchainError+addContext ctx err = err {errorContext = Just ctx}++-- | Execute an action with error context, automatically adding context to any errors+withErrorContext :: MonadIO m => ErrorContext -> LangchainIO a -> m (LangchainResult a)+withErrorContext ctx action = liftIO $ do+ result <- action+ case result of+ Left err -> return $ Left $ addContext ctx err+ Right val -> return $ Right val++-- | Map a function over the error in a result+mapError :: (LangchainError -> LangchainError) -> LangchainResult a -> LangchainResult a+mapError f (Left err) = Left (f err)+mapError _ (Right val) = Right val++-- | Convert a String to LangchainError+fromString :: String -> LangchainError+fromString str = internalError (T.pack str) Nothing Nothing++-- | Convert LangchainError to String+toString :: LangchainError -> String+toString = displayException++-- | Convert LangchainError to Text+toText :: LangchainError -> Text+toText = T.pack . toString++-- | Log an error to stderr (can be extended to use proper logging)+logError :: MonadIO m => LangchainError -> m ()+logError err = liftIO $ hPutStrLn stderr $ toString err++-- | Check if an error is retryable based on its category and severity+isRetryable :: LangchainError -> Bool+isRetryable LangchainError {..} = case errorCategory of+ NetworkError -> errorSeverity <= High+ LLMError -> errorSeverity <= Medium+ VectorStoreError -> errorSeverity <= Medium+ EmbeddingError -> errorSeverity <= Medium+ ToolError -> errorSeverity <= Medium+ _ -> False++-- | Get the severity of an error+getSeverity :: LangchainError -> ErrorSeverity+getSeverity = errorSeverity++-- | Get the category of an error+getCategory :: LangchainError -> ErrorCategory+getCategory = errorCategory++-- | Convert a String error to LangchainError (for backward compatibility)+fromStringError :: String -> LangchainError+fromStringError = fromString++-- | Convert an IO exception to LangchainError+fromException :: SomeException -> LangchainError+fromException ex = internalError (T.pack $ displayException ex) Nothing Nothing++-- | Lift an Either String to LangchainResult+liftStringError :: Either String a -> LangchainResult a+liftStringError (Left err) = Left (fromString err)+liftStringError (Right val) = Right val++-- | Create a simple error with just a message (uses InternalError category)+simpleError :: Text -> LangchainError+simpleError msg = internalError msg Nothing Nothing++-- | Catch IO exceptions and convert them to LangchainError+catchToLangchainError :: IO a -> IO (LangchainResult a)+catchToLangchainError action = do+ result <- try action+ case result of+ Left ex -> return $ Left $ fromException ex+ Right val -> return $ Right val++-- | Run an action and add context to any errors+withContext :: Text -> Text -> LangchainResult a -> LangchainResult a+withContext component operation result = case result of+ Left err ->+ case errorContext err of+ Just ctx ->+ Left $+ err+ { errorContext =+ Just $+ ErrorContext+ { contextComponent = Just component+ , contextOperation = Just operation+ , contextInput = Nothing+ , contextMetadata = []+ , contextTimestamp = contextTimestamp ctx+ }+ }+ Nothing -> Left err+ Right val -> Right val++-- | Run an action and add context to any errors (IO version)+withContextIO :: MonadIO m => Text -> Text -> LangchainResult a -> m (LangchainResult a)+withContextIO component operation result = case result of+ Left err -> do+ now <- liftIO getCurrentTime+ return $+ Left $+ err+ { errorContext =+ Just $+ ErrorContext+ { contextComponent = Just component+ , contextOperation = Just operation+ , contextInput = Nothing+ , contextMetadata = []+ , contextTimestamp = now+ }+ }+ Right val -> return $ Right val
src/Langchain/LLM/Core.hs view
@@ -1,22 +1,23 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeFamilies #-} {- | Module: Langchain.LLM.Core Copyright: (c) 2025 Tushar Adhatrao License: MIT-Description: Core implementation of langchain chat models+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.+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.@@ -36,18 +37,27 @@ -- * Parameters , Message (..) , Role (..)- , ChatMessage+ , 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@@ -61,8 +71,8 @@ } @ -}-data StreamHandler = StreamHandler- { onToken :: Text -> IO ()+data StreamHandler tokenType = StreamHandler+ { onToken :: tokenType -> IO () -- ^ Action to perform for each token received , onComplete :: IO () -- ^ Action to perform when streaming is complete@@ -78,11 +88,17 @@ Assistant | -- | Tool role, for tool outputs or interactions Tool- | Developer- -- | Special role for developer messages. Specific to only some integrations- | Function- -- | Function call messages. Specific to only some integrations- deriving (Eq, Show, Generic, ToJSON, FromJSON)+ | -- | 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.@@ -107,6 +123,59 @@ } 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.@@ -114,8 +183,12 @@ data MessageData = MessageData { name :: Maybe Text -- ^ Optional name associated with the message- , toolCalls :: Maybe [Text]+ , 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) @@ -125,6 +198,8 @@ object [ "name" .= name , "tool_calls" .= toolCalls+ , "images" .= messageImages+ , "thinking" .= thinking -- Add more fields as they are added ] @@ -134,10 +209,21 @@ MessageData <$> v .:? "name" <*> v .:? "tool_calls"+ <*> v .:? "images"+ <*> v .:? "thinking" -- | Type alias for NonEmpty Message-type ChatMessage = 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 -}@@ -146,29 +232,90 @@ MessageData { name = Nothing , toolCalls = Nothing+ , messageImages = Nothing+ , thinking = Nothing } -- | Typeclass that all ChatModels should interface with-class LLM m where+class LLM llm where -- | Define the Parameter type for your LLM model.- type LLMParams m+ type LLMStreamTokenType llm - - -- | Invoke the language model with a single prompt.- -- Suitable for simple queries; returns either an error or generated text.- generate :: m -- ^ The type of the language model instance.- -> Text -- ^ The prompt to send to the model.- -> Maybe (LLMParams m) -- ^ Optional configuration parameters.- -> IO (Either String Text)+ type LLMParams llm - -- | Chat with the language model using a sequence of messages.- -- Suitable for multi-turn conversations; returns either an error or the response.- --- chat :: m -- ^ The type of the language model instance.- -> ChatMessage -- ^ A non-empty list of messages to send to the model.- -> Maybe (LLMParams m) -- ^ Optional configuration parameters.- -> IO (Either String Text) -- ^ The result of the chat, either an error or the response text.+ {- | 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) - -- | 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 :: m -> ChatMessage -> StreamHandler -> Maybe (LLMParams m) -> IO (Either String ())+ {- | 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
+ src/Langchain/LLM/Deepseek.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeFamilies #-}++{- |+Module : Langchain.LLM.Deepseek+Description : Deepseek integration for LangChain Haskell+Copyright : (c) 2025 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++This module provides the 'Deepseek' data type and implements the 'LLM' typeclass for interacting with Deepseek's language models.+It supports generating text, handling chat interactions, and streaming responses using Deepseek's API.++This implementation uses the OpenAI-compatible interface with baseUrl as "https://api.deepseek.com".++For more information on Deepseek's API, see: <https://platform.deepseek.com/api-docs/>+-}+module Langchain.LLM.Deepseek+ ( Deepseek (..)+ , module Langchain.LLM.Core+ ) where++import Data.Maybe (fromMaybe)+import Data.Text (Text)+import Langchain.Callback+import Langchain.LLM.Core+import qualified Langchain.LLM.Core as LLM+import Langchain.LLM.OpenAICompatible (OpenAICompatible (..))+import qualified Langchain.Runnable.Core as Run+import qualified OpenAI.V1.Chat.Completions as OpenAIV1++data Deepseek = Deepseek+ { apiKey :: Text+ -- ^ The API key for authenticating with Deepseek's services.+ , callbacks :: [Callback]+ -- ^ A list of callbacks for handling events during LLM operations.+ , baseUrl :: Maybe String+ -- ^ Base url; default "https://api.deepseek.com"+ }++instance Show Deepseek where+ show _ = "Deepseek"++toOpenAI :: Deepseek -> OpenAICompatible+toOpenAI Deepseek {..} =+ OpenAICompatible+ { apiKey = apiKey+ , callbacks = callbacks+ , baseUrl = Just $ fromMaybe "https://api.deepseek.com" baseUrl+ , providerName = "Deepseek"+ }++instance LLM.LLM Deepseek where+ type LLMParams Deepseek = OpenAIV1.CreateChatCompletion+ type LLMStreamTokenType Deepseek = OpenAIV1.ChatCompletionChunk++ generate deepseek = LLM.generate (toOpenAI deepseek)+ chat deepseek = LLM.chat (toOpenAI deepseek)+ stream deepseek = LLM.stream (toOpenAI deepseek)++instance Run.Runnable Deepseek where+ type RunnableInput Deepseek = (ChatHistory, Maybe OpenAIV1.CreateChatCompletion)+ type RunnableOutput Deepseek = LLM.Message++ invoke = uncurry . chat
+ src/Langchain/LLM/Gemini.hs view
@@ -0,0 +1,104 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}++{- |+Module : Langchain.LLM.Gemini+Description : Google Gemini integration for LangChain Haskell+Copyright : (c) 2025 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++This module provides the 'Gemini' data type and implements the 'LLM' typeclass for interacting+with Google's Gemini language models through OpenAI-compatible API endpoints.++The 'Gemini' type encapsulates the API key, model name, and callbacks for event handling.+The 'LLM' instance methods ('generate', 'chat', 'stream') allow for seamless integration+with LangChain's processing pipelines.++For more information on Gemini API, see: <https://ai.google.dev/gemini-api/docs>++Notes:+* Gemini only supports base64 encoded image content. Check out examples.+* Uses OpenAI-compatible endpoint: https://ai.google.dev/gemini-api/docs/openai++Example usage:++@+import Data.Text (Text)+import qualified Langchain.LLM.Core as LLM+import Langchain.LLM.Gemini (Gemini(..), defaultGemini)++main :: IO()+main = do+ let gemini = defaultGemini { apiKey = "your-api-key" }+ result <- LLM.generate gemini "Explain functional programming" Nothing+ case result of+ Left err -> putStrLn $ "Error: " ++ show err+ Right response -> print response+@+-}+module Langchain.LLM.Gemini+ ( Gemini (..)+ , defaultGemini+ , module Langchain.LLM.Core+ ) where++import Data.Maybe (fromMaybe)+import Data.Text (Text)+import Langchain.Callback+import Langchain.LLM.Core+import qualified Langchain.LLM.Core as LLM+import Langchain.LLM.OpenAICompatible (OpenAICompatible)+import qualified Langchain.LLM.OpenAICompatible as OpenAICompatible+import qualified Langchain.Runnable.Core as Run+import qualified OpenAI.V1.Chat.Completions as OpenAIV1++data Gemini = Gemini+ { apiKey :: Text+ -- ^ The API key for authenticating with Gemini's services.+ , callbacks :: [Callback]+ -- ^ A list of callbacks for handling events during LLM operations.+ , baseUrl :: Maybe String+ -- ^ Base url; default "https://generativelanguage.googleapis.com/v1beta/openai"+ }++instance Show Gemini where+ show _ = "Gemini"++toOpenAI :: Gemini -> OpenAICompatible+toOpenAI Gemini {..} =+ OpenAICompatible.OpenAICompatible+ { apiKey = apiKey+ , callbacks = callbacks+ , baseUrl =+ Just $+ fromMaybe+ "https://generativelanguage.googleapis.com/v1beta/openai"+ baseUrl+ , providerName = "Gemini"+ }++instance LLM.LLM Gemini where+ type LLMParams Gemini = OpenAIV1.CreateChatCompletion+ type LLMStreamTokenType Gemini = OpenAIV1.ChatCompletionChunk++ generate = LLM.generate . toOpenAI+ chat = LLM.chat . toOpenAI+ stream = LLM.stream . toOpenAI++instance Run.Runnable Gemini where+ type RunnableInput Gemini = (ChatHistory, Maybe OpenAIV1.CreateChatCompletion)+ type RunnableOutput Gemini = LLM.Message++ invoke = uncurry . chat++defaultGemini :: Gemini+defaultGemini =+ Gemini+ { apiKey = ""+ , callbacks = []+ , baseUrl = Just "https://generativelanguage.googleapis.com/v1beta/openai"+ }
src/Langchain/LLM/Huggingface.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeFamilies #-}@@ -25,13 +26,18 @@ -- * Functions , defaultHuggingfaceParams- , Huggingface.defaultMessage+ , Huggingface.defaultHugginfaceMessage++ -- * Re-export+ , module LLM ) where import qualified Data.List.NonEmpty as NE import Data.Maybe import Data.Text (Text, unpack)+import qualified Data.Text as T import Langchain.Callback+import Langchain.Error (llmError) import Langchain.LLM.Core as LLM import qualified Langchain.LLM.Internal.Huggingface as Huggingface @@ -92,6 +98,7 @@ instance LLM Huggingface where type LLMParams Huggingface = HuggingfaceParams+ type LLMStreamTokenType Huggingface = Text generate Huggingface {..} prompt mbHuggingfaceParams = do eRes <-@@ -100,35 +107,41 @@ Huggingface.defaultHuggingfaceChatCompletionRequest { Huggingface.provider = provider , Huggingface.messages =- [Huggingface.defaultMessage {Huggingface.content = Huggingface.TextContent prompt}]+ [ Huggingface.defaultHugginfaceMessage+ { Huggingface.content = Huggingface.TextContent prompt+ }+ ] , Huggingface.model = modelName , Huggingface.stream = False- , Huggingface.maxTokens = maybe Nothing maxTokens mbHuggingfaceParams- , Huggingface.frequencyPenalty = maybe Nothing frequencyPenalty mbHuggingfaceParams+ , Huggingface.maxTokens = maxTokens =<< mbHuggingfaceParams+ , Huggingface.frequencyPenalty = frequencyPenalty =<< mbHuggingfaceParams , -- , Huggingface.logProbs = maybe Nothing logProbs mbHuggingfaceParams- Huggingface.presencePenalty = maybe Nothing presencePenalty mbHuggingfaceParams+ Huggingface.presencePenalty = presencePenalty =<< mbHuggingfaceParams , -- , Huggingface.seed = maybe Nothing seed mbHuggingfaceParams- Huggingface.stop = maybe Nothing stop mbHuggingfaceParams- , Huggingface.temperature = maybe Nothing temperature mbHuggingfaceParams+ Huggingface.stop = stop =<< mbHuggingfaceParams+ , Huggingface.temperature = temperature =<< mbHuggingfaceParams , -- , Huggingface.toolPrompt = maybe Nothing toolPrompt mbHuggingfaceParams -- , Huggingface.topLogprobs = maybe Nothing topLogProbs mbHuggingfaceParams- Huggingface.topP = maybe Nothing topP mbHuggingfaceParams- , Huggingface.timeout = maybe Nothing timeout mbHuggingfaceParams+ Huggingface.topP = topP =<< mbHuggingfaceParams+ , Huggingface.timeout = timeout =<< mbHuggingfaceParams -- , Huggingface.streamOptions = maybe Nothing streamOptions mbHuggingfaceParams -- , Huggingface.responseFormat = maybe Nothing responseFormat mbHuggingfaceParams -- , Huggingface.tools = maybe Nothing tools mbHuggingfaceParams -- , Huggingface.toolChoice = maybe Nothing toolChoice mbHuggingfaceParams } case eRes of- Left err -> return $ Left err+ Left err -> return $ Left (llmError (T.pack err) Nothing Nothing) Right r -> do case listToMaybe ((\Huggingface.ChatCompletionResponse {..} -> choices) r) of- Nothing -> return $ Left "Did not received any response"+ Nothing ->+ return $+ Left+ (llmError "Did not received any response" Nothing Nothing) Just resp -> let Huggingface.Message {..} = Huggingface.message resp in pure $ Right $- ( \c -> case c of+ ( \case Huggingface.TextContent t -> t _ -> "" )@@ -143,65 +156,64 @@ , Huggingface.messages = toHuggingfaceMessages msgs , Huggingface.model = modelName , Huggingface.stream = False- , Huggingface.maxTokens = maybe Nothing maxTokens mbHuggingfaceParams- , Huggingface.frequencyPenalty = maybe Nothing frequencyPenalty mbHuggingfaceParams+ , Huggingface.maxTokens = maxTokens =<< mbHuggingfaceParams+ , Huggingface.frequencyPenalty = frequencyPenalty =<< mbHuggingfaceParams , -- , Huggingface.logProbs = maybe Nothing logProbs mbHuggingfaceParams- Huggingface.presencePenalty = maybe Nothing presencePenalty mbHuggingfaceParams+ Huggingface.presencePenalty = presencePenalty =<< mbHuggingfaceParams , -- , Huggingface.seed = maybe Nothing seed mbHuggingfaceParams- Huggingface.stop = maybe Nothing stop mbHuggingfaceParams- , Huggingface.temperature = maybe Nothing temperature mbHuggingfaceParams+ Huggingface.stop = stop =<< mbHuggingfaceParams+ , Huggingface.temperature = temperature =<< mbHuggingfaceParams , -- , Huggingface.toolPrompt = maybe Nothing toolPrompt mbHuggingfaceParams -- , Huggingface.topLogprobs = maybe Nothing topLogProbs mbHuggingfaceParams- Huggingface.topP = maybe Nothing topP mbHuggingfaceParams- , Huggingface.timeout = maybe Nothing timeout mbHuggingfaceParams+ Huggingface.topP = topP =<< mbHuggingfaceParams+ , Huggingface.timeout = timeout =<< mbHuggingfaceParams -- , Huggingface.streamOptions = maybe Nothing streamOptions mbHuggingfaceParams -- , Huggingface.responseFormat = maybe Nothing responseFormat mbHuggingfaceParams -- , Huggingface.tools = maybe Nothing tools mbHuggingfaceParams -- , Huggingface.toolChoice = maybe Nothing toolChoice mbHuggingfaceParams } case eRes of- Left err -> return $ Left err+ Left err -> return $ Left $ llmError (T.pack err) Nothing Nothing Right r -> do- case listToMaybe ((\Huggingface.ChatCompletionResponse {..} -> choices) r) of- Nothing -> return $ Left "Did not received any response"- Just resp ->- let Huggingface.Message {..} = Huggingface.message resp- in pure $- Right $- ( \c -> case c of- Huggingface.TextContent t -> t- _ -> ""- )- content+ case listToMaybe+ ((\Huggingface.ChatCompletionResponse {..} -> choices) r) of+ Nothing ->+ return $+ Left (llmError "Did not received any response" Nothing Nothing)+ Just resp -> return $ Right $ from (Huggingface.message resp) stream Huggingface {..} msgs LLM.StreamHandler {..} mbHuggingfaceParams = do- Huggingface.createChatCompletionStream- apiKey- Huggingface.defaultHuggingfaceChatCompletionRequest- { Huggingface.provider = provider- , Huggingface.messages = toHuggingfaceMessages msgs- , Huggingface.model = modelName- , Huggingface.stream = True- , Huggingface.maxTokens = maybe Nothing maxTokens mbHuggingfaceParams- , Huggingface.frequencyPenalty = maybe Nothing frequencyPenalty mbHuggingfaceParams- , -- , Huggingface.logProbs = maybe Nothing logProbs mbHuggingfaceParams- Huggingface.presencePenalty = maybe Nothing presencePenalty mbHuggingfaceParams- , -- , Huggingface.seed = maybe Nothing seed mbHuggingfaceParams- Huggingface.stop = maybe Nothing stop mbHuggingfaceParams- , Huggingface.temperature = maybe Nothing temperature mbHuggingfaceParams- , -- , Huggingface.toolPrompt = maybe Nothing toolPrompt mbHuggingfaceParams- -- , Huggingface.topLogprobs = maybe Nothing topLogProbs mbHuggingfaceParams- Huggingface.topP = maybe Nothing topP mbHuggingfaceParams- , Huggingface.timeout = maybe Nothing timeout mbHuggingfaceParams- -- , Huggingface.streamOptions = maybe Nothing streamOptions mbHuggingfaceParams- -- , Huggingface.responseFormat = maybe Nothing responseFormat mbHuggingfaceParams- -- , Huggingface.tools = maybe Nothing tools mbHuggingfaceParams- -- , Huggingface.toolChoice = maybe Nothing toolChoice mbHuggingfaceParams- }- Huggingface.HuggingfaceStreamHandler- { Huggingface.onComplete = onComplete- , Huggingface.onToken = onToken . chunkToText- }+ eRes <-+ Huggingface.createChatCompletionStream+ apiKey+ Huggingface.defaultHuggingfaceChatCompletionRequest+ { Huggingface.provider = provider+ , Huggingface.messages = toHuggingfaceMessages msgs+ , Huggingface.model = modelName+ , Huggingface.stream = True+ , Huggingface.maxTokens = maxTokens =<< mbHuggingfaceParams+ , Huggingface.frequencyPenalty = frequencyPenalty =<< mbHuggingfaceParams+ , -- , Huggingface.logProbs = maybe Nothing logProbs mbHuggingfaceParams+ Huggingface.presencePenalty = presencePenalty =<< mbHuggingfaceParams+ , -- , Huggingface.seed = maybe Nothing seed mbHuggingfaceParams+ Huggingface.stop = stop =<< mbHuggingfaceParams+ , Huggingface.temperature = temperature =<< mbHuggingfaceParams+ , -- , Huggingface.toolPrompt = maybe Nothing toolPrompt mbHuggingfaceParams+ -- , Huggingface.topLogprobs = maybe Nothing topLogProbs mbHuggingfaceParams+ Huggingface.topP = topP =<< mbHuggingfaceParams+ , Huggingface.timeout = timeout =<< mbHuggingfaceParams+ -- , Huggingface.streamOptions = maybe Nothing streamOptions mbHuggingfaceParams+ -- , Huggingface.responseFormat = maybe Nothing responseFormat mbHuggingfaceParams+ -- , Huggingface.tools = maybe Nothing tools mbHuggingfaceParams+ -- , Huggingface.toolChoice = maybe Nothing toolChoice mbHuggingfaceParams+ }+ Huggingface.HuggingfaceStreamHandler+ { Huggingface.onComplete = onComplete+ , Huggingface.onToken = onToken . chunkToText+ }+ case eRes of+ Left err -> pure $ Left $ llmError (T.pack err) Nothing Nothing+ Right r -> pure $ Right r where chunkToText :: Huggingface.ChatCompletionChunk -> Text chunkToText Huggingface.ChatCompletionChunk {..} = do@@ -210,7 +222,7 @@ Just Huggingface.ChoiceChunk {..} -> fromMaybe "" ((\Huggingface.Delta {..} -> deltaContent) delta) -toHuggingfaceMessages :: LLM.ChatMessage -> [Huggingface.Message]+toHuggingfaceMessages :: LLM.ChatHistory -> [Huggingface.Message] toHuggingfaceMessages msgs = map go (NE.toList msgs) where toRole :: LLM.Role -> Huggingface.Role@@ -225,7 +237,7 @@ go :: LLM.Message -> Huggingface.Message go msg =- Huggingface.defaultMessage+ Huggingface.defaultHugginfaceMessage { Huggingface.role = toRole $ LLM.role msg , Huggingface.content = Huggingface.TextContent (LLM.content msg) }
src/Langchain/LLM/Internal/Huggingface.hs view
@@ -43,12 +43,13 @@ , getProviderLink , createChatCompletion , defaultHuggingfaceChatCompletionRequest- , defaultMessage+ , defaultHugginfaceMessage , createChatCompletionStream , defaultHuggingfaceStreamHandler ) where import Conduit+import Control.Monad (when) import Data.Aeson import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as LBS@@ -59,6 +60,7 @@ import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8) import GHC.Generics+import qualified Langchain.LLM.Core as LLM import Network.HTTP.Conduit import Network.HTTP.Simple ( getResponseBody@@ -156,7 +158,7 @@ parseJSON invalid = fail $ "Invalid tool choice: " ++ show invalid -- | Provides details for a specific tool choice.-data SpecificToolChoice = SpecificToolChoice+newtype SpecificToolChoice = SpecificToolChoice { specificToolChoiceFunction :: Value -- ^ Function details }@@ -174,7 +176,7 @@ <$> v .: "function" -- | Options for streaming responses.-data StreamOptions = StreamOptions+newtype StreamOptions = StreamOptions { includeUsage :: Bool -- ^ Whether to include usage information }@@ -209,7 +211,7 @@ _ -> fail $ "Unknown role: " ++ T.unpack t -- | Image url object-data ImageUrl = ImageUrl+newtype ImageUrl = ImageUrl { url :: String } deriving (Eq, Show, Generic)@@ -264,11 +266,11 @@ deriving (Eq, Show, Generic) -- | Default message type-defaultMessage :: Message-defaultMessage =+defaultHugginfaceMessage :: Message+defaultHugginfaceMessage = Message { role = User- , content = TextContent "What is the meaining of life?"+ , content = TextContent "What is the meaning of life?" , name = Nothing } @@ -285,7 +287,7 @@ <*> v .: "content" <*> v .:? "name" -{- | $providers+{- | \$providers Supported providers and their API endpoints: - Cerebras: @https://router.huggingface.co/cerebras/...@@ -360,7 +362,7 @@ HuggingfaceChatCompletionRequest { provider = Cerebras , timeout = Nothing- , messages = [defaultMessage]+ , messages = [defaultHugginfaceMessage] , model = "llama-3.3-70b" , stream = False , maxTokens = Nothing@@ -500,7 +502,7 @@ <*> v .: "time_info" -- | Response for stream-data Delta = Delta+newtype Delta = Delta { deltaContent :: Maybe Text } deriving (Eq, Show, Generic)@@ -603,8 +605,7 @@ setRequestSecure True $ setRequestHeader "Content-Type" ["application/json"] $ setRequestHeader "Authorization" ["Bearer " <> encodeUtf8 apiKey] $- setRequestBodyJSON r $- request_+ setRequestBodyJSON r request_ response <- httpLbs req manager let status = statusCode $ getResponseStatus response@@ -612,7 +613,13 @@ then case eitherDecode (getResponseBody response) of Left err -> return $ Left $ "JSON parse error: " <> err Right completionResponse -> return $ Right completionResponse- else return $ Left $ "API error: " <> show status <> " " <> show (getResponseBody response)+ else+ return $+ Left $+ "API error: "+ <> show status+ <> " "+ <> show (getResponseBody response) {- | Handler for streaming chat completion responses. Provides callbacks for processing each token and handling stream completion.@@ -634,7 +641,10 @@ -- | Streaming function for huggingface createChatCompletionStream ::- Text -> HuggingfaceChatCompletionRequest -> HuggingfaceStreamHandler -> IO (Either String ())+ Text ->+ HuggingfaceChatCompletionRequest ->+ HuggingfaceStreamHandler ->+ IO (Either String ()) createChatCompletionStream apiKey r HuggingfaceStreamHandler {..} = do case getProviderLink (provider r) of Nothing -> pure $ Left "Incompatible provider"@@ -645,8 +655,7 @@ setRequestMethod "POST" $ setRequestSecure True $ setRequestHeader "Content-Type" ["application/json"] $- setRequestBodyJSON r $- request_+ setRequestBodyJSON r request_ manager <- newManager@@ -666,21 +675,55 @@ return $ Right () where processLine bufferRef line = do- if BS.isPrefixOf "data: " line- then do- do- let content = BS.drop 6 line -- Remove "data: " prefix- case decode (LBS.fromStrict content) of- Just chunk -> onToken chunk- Nothing -> do- -- Handle potential partial JSON by buffering- oldBuffer <- readIORef bufferRef- let newBuffer = oldBuffer <> content- writeIORef bufferRef newBuffer- -- Try to parse the combined buffer- case decode (LBS.fromStrict newBuffer) of- Just chunk -> do- onToken chunk- writeIORef bufferRef BS.empty -- Clear buffer after successful parse- Nothing -> return () -- Keep in buffer for next chunk- else return () -- Ignore non-data lines+ when+ (BS.isPrefixOf "data: " line)+ ( do+ do+ let content = BS.drop 6 line -- Remove "data: " prefix+ case decode (LBS.fromStrict content) of+ Just chunk -> onToken chunk+ Nothing -> do+ -- Handle potential partial JSON by buffering+ oldBuffer <- readIORef bufferRef+ let newBuffer = oldBuffer <> content+ writeIORef bufferRef newBuffer+ -- Try to parse the combined buffer+ case decode (LBS.fromStrict newBuffer) of+ Just chunk -> do+ onToken chunk+ writeIORef bufferRef BS.empty -- Clear buffer after successful parse+ Nothing -> return () -- Keep in buffer for next chunk+ )++instance LLM.MessageConvertible Message where+ -- to :: LLM.Message -> Message+ to msg =+ defaultHugginfaceMessage+ { role = toRole $ LLM.role msg+ , content = TextContent (LLM.content msg)+ }+ where+ toRole :: LLM.Role -> Role+ toRole r = case r of+ LLM.System -> System+ LLM.User -> User+ LLM.Assistant -> Assistant+ LLM.Tool -> Tool+ _ -> User++ -- from :: Message -> LLM.Message+ from msg =+ LLM.Message+ { LLM.role = toRole (role msg)+ , LLM.content = case content msg of+ TextContent txt -> txt+ _ -> ""+ , LLM.messageData = LLM.defaultMessageData+ }+ where+ toRole :: Role -> LLM.Role+ toRole r = case r of+ System -> LLM.System+ User -> LLM.User+ Assistant -> LLM.Assistant+ Tool -> LLM.Tool
− src/Langchain/LLM/Internal/OpenAI.hs
@@ -1,1149 +0,0 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}--{- |-Module : Langchain.LLM.Internal.OpenAI-Description : Internal module for OpenAI chat completion API interactions-Copyright : (c) 2025 Tushar Adhatrao-License : MIT-Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability : experimental--This module provides data types and functions to interact with OpenAI's chat completion API. It includes types for requests, responses, and streaming handlers, as well as functions to create and handle chat completion requests and streams. Designed for internal use within the LangChain library, it can also be used directly for fine-grained control over API interactions.--Key Features--- Data types for chat completion requests and responses-- Support for streaming chat completions with real-time token processing-- Default values and configurations for common use cases-- Comprehensive error handling for API interactions--### Example Usage--@-import Data.Text (Text)-import Langchain.LLM.Internal.OpenAI---- Create a simple message-let message = defaultMessage { role = User, content = Just (StringContent "Hello, how are you?") }---- Create a chat completion request-let request = defaultChatCompletionRequest- { messages = [message]- , model = "gpt-3.5-turbo"- , temperature = Just 0.7- }---- Send the request-response <- createChatCompletion "your-api-key" request-case response of- Right res -> print (choices res)- Left err -> putStrLn $ "Error: " ++ err-@--- Streaming Chat Completions:--@-import Langchain.LLM.Internal.OpenAI---- Define a stream handler-let handler = OpenAIStreamHandler- { onToken = \chunk -> putStr (maybe "" id $ contentForDelta $ delta $ head $ chunkChoices chunk)- , onComplete = putStrLn "Stream complete"- }---- Create a streaming request-let streamRequest = request { stream = Just True }---- Start streaming-result <- createChatCompletionStream "your-api-key" streamRequest handler-case result of- Right () -> putStrLn "Streaming completed successfully"- Left err -> putStrLn $ "Error: " ++ err-@--}-module Langchain.LLM.Internal.OpenAI- ( -- * Types- ChatCompletionChunk (..)- , ChunkChoice (..)- , Delta (..)- , OpenAIStreamHandler (..)- , ChatCompletionRequest (..)- , ChatCompletionResponse (..)- , Message (..)- , Role (..)- , MessageContent (..)- , TextContent (..)- , Tool_ (..)- , Function_ (..)- , ToolCall (..)- , FunctionCall_ (..)- , Usage (..)- , Choice (..)- , FinishReason (..)- , LogProbs (..)- , LogProbContent (..)- , TopLogProb (..)- , AudioConfig (..)- , AudioResponse (..)- , Modality (..)- , ToolChoice (..)- , SpecificToolChoice (..)- , ReasoningEffort (..)- , PredictionOutput (..)- , PredictionContent (..)- , ResponseFormat (..)- , StreamOptions (..)- , WebSearchOptions (..)- , UserLocation (..)- , ApproximateLocation (..)- , CompletionTokensDetails (..)- , PromptTokensDetails (..)-- -- * Default values for types- , defaultChatCompletionRequest- , createChatCompletion- , createChatCompletionStream- , defaultMessage- , defaultPredictionOutput- , defaultResponseFormat- , defaultStreamOptions- , defaultWebSearchOptions- , defaultUserLocation- , defaultAudioConfig- , defaultToolChoice- , defaultSpecificToolChoice- , defaultReasoningEffort- , defaultFunction- ) where--import Conduit-import Data.Aeson-import qualified Data.ByteString as BS-import qualified Data.ByteString.Lazy as LBS-import Data.IORef-import Data.Map (Map)-import Data.Maybe (fromMaybe)-import Data.Text (Text)-import Data.Text.Encoding (encodeUtf8)-import GHC.Generics-import Network.HTTP.Conduit-import Network.HTTP.Simple- ( getResponseStatus- , setRequestBodyJSON- , setRequestHeader- , setRequestMethod- , setRequestSecure- , getResponseBody- )-import Network.HTTP.Types.Status (statusCode)--{- | Represents a chunk of the chat completion response in a streaming context.-Contains a list of possible choices for the completion.--}-data ChatCompletionChunk = ChatCompletionChunk- { chunkChoices :: [ChunkChoice]- -- ^ List of choices in this chunk of the response- }- deriving (Show)--instance FromJSON ChatCompletionChunk where- parseJSON = withObject "ChatCompletionChunk" $ \v ->- ChatCompletionChunk <$> v .: "choices"--{- | Represents a single choice in a chat completion chunk.-Includes the incremental content and an optional reason for finishing.--}-data ChunkChoice = ChunkChoice- { delta :: Delta- -- ^ Incremental content added in this chunk- , finishReason :: Maybe FinishReason- -- ^ Reason why the completion stopped, if applicable- }- deriving (Show)--instance FromJSON ChunkChoice where- parseJSON = withObject "ChunkChoice" $ \v ->- ChunkChoice <$> v .: "delta" <*> v .:? "finish_reason"---- | Represents the incremental content added in a chat completion chunk.-data Delta = Delta- { contentForDelta :: Maybe Text- -- ^ Optional text content added in this chunk- }- deriving (Show)--instance FromJSON Delta where- parseJSON = withObject "Delta" $ \v ->- Delta <$> v .:? "content"--{- | Handler for streaming chat completion responses.-Provides callbacks for processing each token and handling stream completion.--}-data OpenAIStreamHandler = OpenAIStreamHandler- { onToken :: ChatCompletionChunk -> IO ()- -- ^ Callback for each token (chunk) received- , onComplete :: IO ()- -- ^ Callback when the stream is complete- }--{- | Represents the main request for chat completions.-Contains all parameters for configuring the OpenAI chat completion API call.--}-data ChatCompletionRequest = ChatCompletionRequest- { messages :: [Message]- -- ^ List of messages in the conversation history- , model :: Text- -- ^ The model to use for completion (e.g., "gpt-3.5-turbo")- , timeout :: Maybe Int- -- ^ Override default response timeout in seconds. Default = 60 seconds- , frequencyPenalty :: Maybe Double- -- ^ Penalty for frequent tokens (range: -2.0 to 2.0)- , logitBias :: Maybe (Map Text Double)- -- ^ Bias for specific tokens- , logprobs :: Maybe Bool- -- ^ Whether to return log probabilities- , maxCompletionTokens :: Maybe Int- -- ^ Maximum tokens to generate in the completion- , maxTokens :: Maybe Int- -- ^ Maximum tokens in the response- , metadata :: Maybe (Map Text Text)- -- ^ Metadata to attach to the request- , modalities :: Maybe [Modality]- -- ^ Modalities to use (e.g., text, audio)- , n :: Maybe Int- -- ^ Number of completions to generate- , parallelToolCalls :: Maybe Bool- -- ^ Whether to allow parallel tool calls- , prediction :: Maybe PredictionOutput- -- ^ Prediction output configuration- , presencePenalty :: Maybe Double- -- ^ Penalty for new tokens (range: -2.0 to 2.0)- , reasoningEffort :: Maybe ReasoningEffort- -- ^ Level of reasoning effort- , responseFormat :: Maybe ResponseFormat- -- ^ Format of the response (e.g., JSON)- , seed :: Maybe Int- -- ^ Seed for deterministic outputs- , serviceTier :: Maybe Text- -- ^ Service tier to use- , stop :: Maybe (Either Text [Text])- -- ^ Stop sequences to end the completion- , store :: Maybe Bool- -- ^ Whether to store the conversation- , stream :: Maybe Bool- -- ^ Whether to stream the response- , streamOptions :: Maybe StreamOptions- -- ^ Options for streaming- , temperature :: Maybe Double- -- ^ Sampling temperature (range: 0.0 to 2.0)- , toolChoice :: Maybe ToolChoice- -- ^ How to choose tools for the model- , tools :: Maybe [Tool_]- -- ^ Tools available to the model- , topLogprobs :: Maybe Int- -- ^ Number of top log probabilities to return- , topP :: Maybe Double- -- ^ Nucleus sampling parameter (range: 0.0 to 1.0)- , user :: Maybe Text- -- ^ User identifier- , webSearchOptions :: Maybe WebSearchOptions- -- ^ Options for web search features- , audio :: Maybe AudioConfig- -- ^ Configuration for audio processing- }- deriving (Show, Eq, Generic)--instance ToJSON ChatCompletionRequest where- toJSON ChatCompletionRequest {..} =- object $- [ "messages" .= messages- , "model" .= model- ]- ++ maybe [] (\fp -> ["frequency_penalty" .= fp]) frequencyPenalty- ++ maybe [] (\lb -> ["logit_bias" .= lb]) logitBias- ++ maybe [] (\lp -> ["logprobs" .= lp]) logprobs- ++ maybe [] (\mt -> ["max_completion_tokens" .= mt]) maxCompletionTokens- ++ maybe [] (\mt -> ["max_tokens" .= mt]) maxTokens- ++ maybe [] (\md -> ["metadata" .= md]) metadata- ++ maybe [] (\m -> ["modalities" .= m]) modalities- ++ maybe [] (\n' -> ["n" .= n']) n- ++ maybe [] (\ptc -> ["parallel_tool_calls" .= ptc]) parallelToolCalls- ++ maybe [] (\p -> ["prediction" .= p]) prediction- ++ maybe [] (\pp -> ["presence_penalty" .= pp]) presencePenalty- ++ maybe [] (\re -> ["reasoning_effort" .= re]) reasoningEffort- ++ maybe [] (\rf -> ["response_format" .= rf]) responseFormat- ++ maybe [] (\s -> ["seed" .= s]) seed- ++ maybe [] (\st -> ["service_tier" .= st]) serviceTier- ++ maybe [] (\s -> ["stop" .= s]) stop- ++ maybe [] (\s -> ["store" .= s]) store- ++ maybe [] (\s -> ["stream" .= s]) stream- ++ maybe [] (\so -> ["stream_options" .= so]) streamOptions- ++ maybe [] (\t -> ["temperature" .= t]) temperature- ++ maybe [] (\tc -> ["tool_choice" .= tc]) toolChoice- ++ maybe [] (\t -> ["tools" .= t]) tools- ++ maybe [] (\tlp -> ["top_logprobs" .= tlp]) topLogprobs- ++ maybe [] (\tp -> ["top_p" .= tp]) topP- ++ maybe [] (\u -> ["user" .= u]) user- ++ maybe [] (\wso -> ["web_search_options" .= wso]) webSearchOptions- ++ maybe [] (\a -> ["audio" .= a]) audio--{- | Represents the response from a chat completion request.-Contains the generated choices, metadata, and usage information.--}-data ChatCompletionResponse = ChatCompletionResponse- { choices :: [Choice]- -- ^ List of completion choices- , created :: Integer- -- ^ Timestamp of when the response was created- , id_ :: Text- -- ^ Unique identifier for the response- , responseModel :: Text- -- ^ The model used for the completion- , object_ :: Text- -- ^ Type of the response object- , responseServiceTier :: Maybe Text- -- ^ Service tier used- , systemFingerprint :: Text- -- ^ System fingerprint- , usage :: Usage- -- ^ Token usage information- }- deriving (Show, Eq, Generic)--instance FromJSON ChatCompletionResponse where- parseJSON = withObject "ChatCompletionResponse" $ \v ->- ChatCompletionResponse- <$> v .: "choices"- <*> v .: "created"- <*> v .: "id"- <*> v .: "model"- <*> v .: "object"- <*> v .:? "service_tier"- <*> v .: "system_fingerprint"- <*> v .: "usage"--{- | Represents a single message in a conversation.-Contains the role, content, and optional metadata like function calls or audio responses.--}-data Message = Message- { role :: Role- -- ^ The role of the message sender- , content :: Maybe MessageContent- -- ^ The content of the message- , name :: Maybe Text- -- ^ Optional name of the sender- , functionCall :: Maybe FunctionCall_- -- ^ Optional function call information- , toolCalls :: Maybe [ToolCall]- -- ^ Optional tool call information- , messageToolCallId :: Maybe Text- -- ^ Optional tool call ID- , messageAudio :: Maybe AudioResponse- -- ^ Optional audio response- , refusal :: Maybe Text- -- ^ Optional refusal reason- }- deriving (Show, Eq, Generic)---- | Default message with User role and no content.-defaultMessage :: Message-defaultMessage =- Message- { role = User- , content = Nothing- , name = Nothing- , functionCall = Nothing- , toolCalls = Nothing- , messageToolCallId = Nothing- , messageAudio = Nothing- , refusal = Nothing- }--instance ToJSON Message where- toJSON Message {..} =- object $- ["role" .= role]- ++ maybe [] (\c -> ["content" .= c]) content- ++ maybe [] (\n -> ["name" .= n]) name- ++ maybe [] (\fc -> ["function_call" .= fc]) functionCall- ++ maybe [] (\tc -> ["tool_calls" .= tc]) toolCalls- ++ maybe [] (\tcid -> ["tool_call_id" .= tcid]) messageToolCallId- ++ maybe [] (\a -> ["audio" .= a]) messageAudio- ++ maybe [] (\r -> ["refusal" .= r]) refusal--instance FromJSON Message where- parseJSON = withObject "Message" $ \v ->- Message- <$> v .: "role"- <*> v .:? "content"- <*> v .:? "name"- <*> v .:? "function_call"- <*> v .:? "tool_calls"- <*> v .:? "tool_call_id"- <*> v .:? "audio"- <*> v .:? "refusal"--{- | Represents different roles in a conversation.-Each role has a specific meaning in the context of the chat.--}-data Role- = -- | Human user input- User- | -- | AI-generated response- Assistant- | -- | System-level instructions- System- | -- | Special role for developer messages- Developer- | -- | Tool interaction messages- Tool- | -- | Function call messages- Function- deriving (Show, Eq, Generic)--instance ToJSON Role where- toJSON User = String "user"- toJSON Assistant = String "assistant"- toJSON System = String "system"- toJSON Developer = String "developer"- toJSON Tool = String "tool"- toJSON Function = String "function"--instance FromJSON Role where- parseJSON (String "user") = return User- parseJSON (String "assistant") = return Assistant- parseJSON (String "system") = return System- parseJSON (String "developer") = return Developer- parseJSON (String "tool") = return Tool- parseJSON (String "function") = return Function- parseJSON invalid = fail $ "Invalid role: " ++ show invalid---- | Represents the content of a message, which can be a simple string or structured parts.-data MessageContent- = -- | Simple text content- StringContent Text- | -- | Structured content parts- ContentParts [TextContent]- deriving (Show, Eq, Generic)--instance ToJSON MessageContent where- toJSON (StringContent text) = String text- toJSON (ContentParts parts) = toJSON parts--instance FromJSON MessageContent where- parseJSON (String s) = return $ StringContent s- parseJSON (Array arr) = ContentParts <$> parseJSON (Array arr)- parseJSON invalid = fail $ "Invalid message content: " ++ show invalid---- | Represents a piece of text content with a type.-data TextContent = TextContent- { text_ :: Text- -- ^ The text content- , contentType :: Text- -- ^ The type of the content- }- deriving (Show, Eq, Generic)--instance ToJSON TextContent where- toJSON TextContent {..} =- object- [ "text" .= text_- , "type" .= contentType- ]--instance FromJSON TextContent where- parseJSON = withObject "TextContent" $ \v ->- TextContent- <$> v .: "text"- <*> v .: "type"---- | Represents a tool that can be used in the conversation.-data Tool_ = Tool_- { toolType :: Text- -- ^ The type of the tool- , function :: Function_- -- ^ The function associated with the tool- }- deriving (Show, Eq, Generic)--instance ToJSON Tool_ where- toJSON Tool_ {..} =- object- [ "type" .= toolType- , "function" .= function- ]--instance FromJSON Tool_ where- parseJSON = withObject "Tool" $ \v ->- Tool_- <$> v .: "type"- <*> v .: "function"---- | Represents a function that can be called by the model.-data Function_ = Function_- { functionName :: Text- -- ^ The name of the function- , description :: Maybe Text- -- ^ Optional description of the function- , parameters :: Maybe Value- -- ^ Optional parameters for the function- , strict :: Maybe Bool- -- ^ Optional strictness flag- }- deriving (Show, Eq, Generic)--instance ToJSON Function_ where- toJSON Function_ {..} =- object $- [ "name" .= functionName- ]- ++ maybe [] (\d -> ["description" .= d]) description- ++ maybe [] (\p -> ["parameters" .= p]) parameters- ++ maybe [] (\s -> ["strict" .= s]) strict--instance FromJSON Function_ where- parseJSON = withObject "Function" $ \v ->- Function_- <$> v .: "name"- <*> v .:? "description"- <*> v .:? "parameters"- <*> v .:? "strict"---- | Represents a call to a tool.-data ToolCall = ToolCall- { toolCallId :: Text- -- ^ The ID of the tool call- , toolCallToolType :: Text- -- ^ The type of the tool- , toolCallfunction :: FunctionCall_- -- ^ The function call- }- deriving (Show, Eq, Generic)--instance ToJSON ToolCall where- toJSON ToolCall {..} =- object- [ "id" .= toolCallId- , "type" .= toolCallToolType- , "function" .= toolCallfunction- ]--instance FromJSON ToolCall where- parseJSON = withObject "ToolCall" $ \v ->- ToolCall- <$> v .: "id"- <*> v .: "type"- <*> v .: "function"---- | Represents a call to a function.-data FunctionCall_ = FunctionCall_- { functionCallName :: Text- -- ^ The name of the function- , arguments :: Text- -- ^ The arguments for the function- }- deriving (Show, Eq, Generic)--instance ToJSON FunctionCall_ where- toJSON FunctionCall_ {..} =- object- [ "name" .= functionCallName- , "arguments" .= arguments- ]--instance FromJSON FunctionCall_ where- parseJSON = withObject "FunctionCall" $ \v ->- FunctionCall_- <$> v .: "name"- <*> v .: "arguments"---- | Represents token usage information.-data Usage = Usage- { completionTokens :: Int- -- ^ Tokens used in completion- , promptTokens :: Int- -- ^ Tokens in the prompt- , totalTokens :: Int- -- ^ Total tokens used- , completionTokensDetails :: Maybe CompletionTokensDetails- -- ^ Detailed completion token info- , promptTokensDetails :: Maybe PromptTokensDetails- -- ^ Detailed prompt token info- }- deriving (Show, Eq, Generic)--instance FromJSON Usage where- parseJSON = withObject "Usage" $ \v ->- Usage- <$> v .: "completion_tokens"- <*> v .: "prompt_tokens"- <*> v .: "total_tokens"- <*> v .:? "completion_tokens_details"- <*> v .:? "prompt_tokens_details"---- | Represents a single choice in the chat completion response.-data Choice = Choice- { choiceFinishReason :: Maybe FinishReason- -- ^ Reason why the completion stopped- , index :: Int- -- ^ Index of the choice- , choiceLogprobs :: Maybe LogProbs- -- ^ Log probabilities, if requested- , message :: Message- -- ^ The generated message- }- deriving (Show, Eq, Generic)--instance FromJSON Choice where- parseJSON = withObject "Choice" $ \v ->- Choice- <$> v .: "finish_reason"- <*> v .: "index"- <*> v .:? "logprobs"- <*> v .: "message"---- | Represents the reason why the completion stopped.-data FinishReason = Stop | Length | ContentFilter | ToolCalls | FunctionCall- deriving (Show, Eq, Generic)--instance FromJSON FinishReason where- parseJSON (String "stop") = return Stop- parseJSON (String "length") = return Length- parseJSON (String "content_filter") = return ContentFilter- parseJSON (String "tool_calls") = return ToolCalls- parseJSON (String "function_call") = return FunctionCall- parseJSON invalid = fail $ "Invalid finish reason: " ++ show invalid---- | Represents log probability information for the completion.-data LogProbs = LogProbs- { contentForLogProbs :: Maybe [LogProbContent]- -- ^ Log probs for content- , logProbsRefusal :: Maybe [LogProbContent]- -- ^ Log probs for refusal- }- deriving (Show, Eq, Generic)--instance FromJSON LogProbs where- parseJSON = withObject "LogProbs" $ \v ->- LogProbs- <$> v .:? "content"- <*> v .:? "refusal"---- | Represents log probability content for a token.-data LogProbContent = LogProbContent- { bytes :: Maybe [Int]- -- ^ Optional byte representation- , logprob :: Double- -- ^ Log probability of the token- , token :: Text- -- ^ The token- , logProbContentTopLogprobs :: [TopLogProb]- -- ^ Top log probabilities- }- deriving (Show, Eq, Generic)--instance FromJSON LogProbContent where- parseJSON = withObject "LogProbContent" $ \v ->- LogProbContent- <$> v .:? "bytes"- <*> v .: "logprob"- <*> v .: "token"- <*> v .: "top_logprobs"---- | Represents a top log probability for a token.-data TopLogProb = TopLogProb- { topLogProbBytes :: Maybe [Int]- -- ^ Optional byte representation- , topLogProbLogprob :: Double- -- ^ Log probability- , topLogProbToken :: Text- -- ^ The token- }- deriving (Show, Eq, Generic)--instance FromJSON TopLogProb where- parseJSON = withObject "TopLogProb" $ \v ->- TopLogProb- <$> v .:? "bytes"- <*> v .: "logprob"- <*> v .: "token"--{- | Configuration for audio processing.-Specifies format and voice preferences for text-to-speech.--}-data AudioConfig = AudioConfig- { format :: Text- -- ^ Audio format (e.g., "mp3")- , voice :: Text- -- ^ Voice to use (e.g., "en-US")- }- deriving (Show, Eq, Generic)--instance ToJSON AudioConfig where- toJSON AudioConfig {..} =- object- [ "format" .= format- , "voice" .= voice- ]--instance FromJSON AudioConfig where- parseJSON = withObject "AudioConfig" $ \v ->- AudioConfig- <$> v .: "format"- <*> v .: "voice"---- | Represents an audio response.-data AudioResponse = AudioResponse- { audioResponseData :: Text- -- ^ Audio data- , expiresAt :: Integer- -- ^ Expiration time- , audioResponseId :: Text- -- ^ Unique ID- , transcript :: Text- -- ^ Transcript of the audio- }- deriving (Show, Eq, Generic)--instance ToJSON AudioResponse where- toJSON AudioResponse {..} =- object- [ "data" .= audioResponseData- , "expires_at" .= expiresAt- , "id" .= audioResponseId- , "transcript" .= transcript- ]--instance FromJSON AudioResponse where- parseJSON = withObject "AudioResponse" $ \v ->- AudioResponse- <$> v .: "data"- <*> v .: "expires_at"- <*> v .: "id"- <*> v .: "transcript"---- | Represents different modalities for the conversation.-data Modality = TextModality | AudioModality- deriving (Show, Eq, Generic)--instance ToJSON Modality where- toJSON TextModality = String "text"- toJSON AudioModality = String "audio"--instance FromJSON Modality where- parseJSON (String "text") = return TextModality- parseJSON (String "audio") = return AudioModality- parseJSON invalid = fail $ "Invalid modality: " ++ show invalid---- | Specifies how the model should choose tools.-data ToolChoice = None | Auto | Required | SpecificTool SpecificToolChoice- deriving (Show, Eq, Generic)--instance ToJSON ToolChoice where- toJSON None = String "none"- toJSON Auto = String "auto"- toJSON Required = String "required"- toJSON (SpecificTool choice) = toJSON choice--instance FromJSON ToolChoice where- parseJSON (String "none") = return None- parseJSON (String "auto") = return Auto- parseJSON (String "required") = return Required- parseJSON o@(Object _) = SpecificTool <$> parseJSON o- parseJSON invalid = fail $ "Invalid tool choice: " ++ show invalid---- | Provides details for a specific tool choice.-data SpecificToolChoice = SpecificToolChoice- { specificToolChoiceToolType :: Text- -- ^ Type of the tool- , specificToolChoiceFunction :: Value- -- ^ Function details- }- deriving (Show, Eq, Generic)--instance ToJSON SpecificToolChoice where- toJSON SpecificToolChoice {..} =- object- [ "type" .= specificToolChoiceToolType- , "function" .= specificToolChoiceFunction- ]--instance FromJSON SpecificToolChoice where- parseJSON = withObject "SpecificToolChoice" $ \v ->- SpecificToolChoice- <$> v .: "type"- <*> v .: "function"---- | Indicates the level of reasoning effort.-data ReasoningEffort = Low | Medium | High- deriving (Show, Eq, Generic)--instance ToJSON ReasoningEffort where- toJSON Low = String "low"- toJSON Medium = String "medium"- toJSON High = String "high"--instance FromJSON ReasoningEffort where- parseJSON (String "low") = return Low- parseJSON (String "medium") = return Medium- parseJSON (String "high") = return High- parseJSON invalid = fail $ "Invalid reasoning effort: " ++ show invalid---- | Represents prediction content.-data PredictionContent = PredictionContent- { contentForPredictionContent :: MessageContent- -- ^ The content- , predictionContentType :: Text- -- ^ Type of the content- }- deriving (Show, Eq, Generic)--instance ToJSON PredictionContent where- toJSON PredictionContent {..} =- object- [ "content" .= contentForPredictionContent- , "type" .= predictionContentType- ]--instance FromJSON PredictionContent where- parseJSON = withObject "PredictionContent" $ \v ->- PredictionContent- <$> v .: "content"- <*> v .: "type"---- | Represents prediction output.-data PredictionOutput = PredictionOutput- { predictionType :: Text- -- ^ Type of the prediction- , contentForPredictionOutput :: MessageContent- -- ^ The output content- }- deriving (Show, Eq, Generic)--instance ToJSON PredictionOutput where- toJSON PredictionOutput {..} =- object- [ "type" .= predictionType- , "content" .= contentForPredictionOutput- ]--instance FromJSON PredictionOutput where- parseJSON = withObject "PredictionOutput" $ \v ->- PredictionOutput- <$> v .: "type"- <*> v .: "content"---- | Specifies the format of the response.-data ResponseFormat = JsonObjectFormat | JsonSchemaFormat Value- deriving (Show, Eq, Generic)--instance ToJSON ResponseFormat where- toJSON JsonObjectFormat = object ["type" .= ("json_object" :: Text)]- toJSON (JsonSchemaFormat schema) =- object- [ "type" .= ("json_schema" :: Text)- , "json_schema" .= schema- ]--instance FromJSON ResponseFormat where- parseJSON = withObject "ResponseFormat" $ \v -> do- formatType <- v .: "type"- case formatType of- String "json_object" -> return JsonObjectFormat- String "json_schema" -> JsonSchemaFormat <$> v .: "json_schema"- _ -> fail $ "Invalid response format type: " ++ show formatType---- | Options for streaming responses.-data StreamOptions = StreamOptions- { includeUsage :: Bool- -- ^ Whether to include usage information- }- deriving (Show, Eq, Generic)--instance ToJSON StreamOptions where- toJSON StreamOptions {..} =- object- [ "include_usage" .= includeUsage- ]--instance FromJSON StreamOptions where- parseJSON = withObject "StreamOptions" $ \v ->- StreamOptions <$> v .: "include_usage"---- | Represents web search options.-data WebSearchOptions = WebSearchOptions- { searchContextSize :: Maybe Text- -- ^ Size of the search context- , userLocation :: Maybe UserLocation- -- ^ User location for context- }- deriving (Show, Eq, Generic)--instance ToJSON WebSearchOptions where- toJSON WebSearchOptions {..} =- object $- maybe [] (\s -> ["search_context_size" .= s]) searchContextSize- ++ maybe [] (\l -> ["user_location" .= l]) userLocation--instance FromJSON WebSearchOptions where- parseJSON = withObject "WebSearchOptions" $ \v ->- WebSearchOptions- <$> v .:? "search_context_size"- <*> v .:? "user_location"---- | Represents user location.-data UserLocation = UserLocation- { approximate :: ApproximateLocation- -- ^ Approximate location details- }- deriving (Show, Eq, Generic)--instance ToJSON UserLocation where- toJSON UserLocation {..} =- object- [ "approximate" .= approximate- ]--instance FromJSON UserLocation where- parseJSON = withObject "UserLocation" $ \v ->- UserLocation <$> v .: "approximate"---- | Represents approximate location.-data ApproximateLocation = ApproximateLocation- { locationType :: Text- -- ^ Type of the location- }- deriving (Show, Eq, Generic)--instance ToJSON ApproximateLocation where- toJSON ApproximateLocation {..} =- object- [ "type" .= locationType- ]--instance FromJSON ApproximateLocation where- parseJSON = withObject "ApproximateLocation" $ \v ->- ApproximateLocation <$> v .: "type"---- | Details about completion tokens.-data CompletionTokensDetails = CompletionTokensDetails- { acceptedPredictionTokens :: Int- -- ^ Accepted prediction tokens- , audioTokens :: Int- -- ^ Audio tokens- , reasoningTokens :: Int- -- ^ Reasoning tokens- , rejectedPredictionTokens :: Int- -- ^ Rejected prediction tokens- }- deriving (Show, Eq, Generic)--instance FromJSON CompletionTokensDetails where- parseJSON = withObject "CompletionTokensDetails" $ \v ->- CompletionTokensDetails- <$> v .: "accepted_prediction_tokens"- <*> v .: "audio_tokens"- <*> v .: "reasoning_tokens"- <*> v .: "rejected_prediction_tokens"---- | Details about prompt tokens.-data PromptTokensDetails = PromptTokensDetails- { promptTokenDetailsAudioTokens :: Int- -- ^ Audio tokens in the prompt- , cachedTokens :: Int- -- ^ Cached tokens- }- deriving (Show, Eq, Generic)--instance FromJSON PromptTokensDetails where- parseJSON = withObject "PromptTokensDetails" $ \v ->- PromptTokensDetails- <$> v .: "audio_tokens"- <*> v .: "cached_tokens"---- | Default chat completion request with "gpt-4.1-nano" model.-defaultChatCompletionRequest :: ChatCompletionRequest-defaultChatCompletionRequest =- ChatCompletionRequest- { messages = []- , model = "gpt-4.1-nano"- , timeout = Just 60- , frequencyPenalty = Nothing- , logitBias = Nothing- , logprobs = Nothing- , maxCompletionTokens = Nothing- , maxTokens = Nothing- , metadata = Nothing- , modalities = Nothing- , n = Nothing- , parallelToolCalls = Nothing- , prediction = Nothing- , presencePenalty = Nothing- , reasoningEffort = Nothing- , responseFormat = Nothing- , seed = Nothing- , serviceTier = Nothing- , stop = Nothing- , store = Nothing- , stream = Nothing- , streamOptions = Nothing- , temperature = Nothing- , toolChoice = Nothing- , tools = Nothing- , topLogprobs = Nothing- , topP = Nothing- , user = Nothing- , webSearchOptions = Nothing- , audio = Nothing- }--{- | Creates a chat completion request and returns the response.-Sends the request to OpenAI's API and parses the response.--}-createChatCompletion :: Text -> ChatCompletionRequest -> IO (Either String ChatCompletionResponse)-createChatCompletion apiKey r = do- request_ <- parseRequest "https://api.openai.com/v1/chat/completions"- manager <-- newManager- tlsManagerSettings- { managerResponseTimeout =- responseTimeoutMicro (fromMaybe 60 (timeout r) * 1000000)- }- let req =- setRequestMethod "POST" $- setRequestSecure True $- setRequestHeader "Content-Type" ["application/json"] $- setRequestHeader "Authorization" ["Bearer " <> encodeUtf8 apiKey] $- setRequestBodyJSON r $- request_-- response <- httpLbs req manager- let status = statusCode $ getResponseStatus response- if status >= 200 && status < 300- then case eitherDecode (getResponseBody response) of- Left err -> return $ Left $ "JSON parse error: " <> err- Right completionResponse -> return $ Right completionResponse- else return $ Left $ "API error: " <> show status <> " " <> show (getResponseBody response)--{- | Creates a streaming chat completion request.-Processes the stream using the provided handler.--}-createChatCompletionStream ::- Text -> ChatCompletionRequest -> OpenAIStreamHandler -> IO (Either String ())-createChatCompletionStream apiKey r OpenAIStreamHandler {..} = do- request_ <- parseRequest "POST https://api.openai.com/v1/chat/completions"- let httpReq =- setRequestHeader "Authorization" ["Bearer " <> encodeUtf8 apiKey] $- setRequestHeader "Content-Type" ["application/json"] $- setRequestBodyJSON r $- request_-- manager <-- newManager- tlsManagerSettings- { managerResponseTimeout =- responseTimeoutMicro (fromMaybe 60 (timeout r) * 1000000)- }- runResourceT $ do- response <- http httpReq manager- bufferRef <- liftIO $ newIORef BS.empty- runConduit $- responseBody response- .| linesUnboundedAsciiC- .| mapM_C (liftIO . processLine bufferRef)-- onComplete- return $ Right ()- where- processLine bufferRef line = do- if BS.isPrefixOf "data: " line- then do- if line == "data: [DONE]"- then return () -- Stream is complete- else do- let content = BS.drop 6 line -- Remove "data: " prefix- case decode (LBS.fromStrict content) of- Just chunk -> onToken chunk- Nothing -> do- -- Handle potential partial JSON by buffering- oldBuffer <- readIORef bufferRef- let newBuffer = oldBuffer <> content- writeIORef bufferRef newBuffer- -- Try to parse the combined buffer- case decode (LBS.fromStrict newBuffer) of- Just chunk -> do- onToken chunk- writeIORef bufferRef BS.empty -- Clear buffer after successful parse- Nothing -> return () -- Keep in buffer for next chunk- else return () -- Ignore non-data lines---- | Default prediction output.-defaultPredictionOutput :: PredictionOutput-defaultPredictionOutput =- PredictionOutput- { predictionType = "text"- , contentForPredictionOutput = StringContent ""- }---- | Default response format (JSON object).-defaultResponseFormat :: ResponseFormat-defaultResponseFormat = JsonObjectFormat---- | Default stream options.-defaultStreamOptions :: StreamOptions-defaultStreamOptions =- StreamOptions- { includeUsage = False- }---- | Default web search options.-defaultWebSearchOptions :: WebSearchOptions-defaultWebSearchOptions =- WebSearchOptions- { searchContextSize = Nothing- , userLocation = Nothing- }---- | Default user location.-defaultUserLocation :: UserLocation-defaultUserLocation =- UserLocation- { approximate = ApproximateLocation {locationType = "approximate"}- }---- | Default audio configuration.-defaultAudioConfig :: AudioConfig-defaultAudioConfig =- AudioConfig- { format = "mp3"- , voice = "en-US"- }---- | Default tool choice (None).-defaultToolChoice :: ToolChoice-defaultToolChoice = None---- | Default specific tool choice.-defaultSpecificToolChoice :: SpecificToolChoice-defaultSpecificToolChoice =- SpecificToolChoice- { specificToolChoiceToolType = "text"- , specificToolChoiceFunction = Null- }---- | Default reasoning effort (Low).-defaultReasoningEffort :: ReasoningEffort-defaultReasoningEffort = Low---- | Default function.-defaultFunction :: Function_-defaultFunction =- Function_- { functionName = "default_function"- , description = Nothing- , parameters = Nothing- , strict = Nothing- }
src/Langchain/LLM/Ollama.hs view
@@ -1,7 +1,9 @@ {-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeFamilies #-}+{-# OPTIONS_GHC -fno-warn-orphans #-} {- | Module : Langchain.LLM.Ollama@@ -22,7 +24,7 @@ @ -- Create Ollama configuration-ollamaLLM = Ollama "llama3" [stdOutCallback]+ollamaLLM = Ollama "gemma3" [stdOutCallback] -- Generate text response <- generate ollamaLLM "Explain Haskell monads" Nothing@@ -38,16 +40,23 @@ streamResult <- stream ollamaLLM messages streamHandler Nothing @ -}-module Langchain.LLM.Ollama (Ollama (..), OllamaParams(..), defaultOllamaParams) where+module Langchain.LLM.Ollama+ ( Ollama (..)+ , defaultOllama -import Data.Aeson-import Data.List.NonEmpty (NonEmpty)+ -- * Re-export+ , module Langchain.LLM.Core+ ) where+ import qualified Data.List.NonEmpty as NonEmpty+import Data.Maybe (fromMaybe) import qualified Data.Ollama.Chat as OllamaChat import qualified Data.Ollama.Common.Types as O-import qualified Data.Ollama.Generate as OllamaGenerate import Data.Text (Text)+import qualified Data.Text as T import Langchain.Callback (Callback, Event (..))+import Langchain.Error (llmError)+import qualified Langchain.Error as Error import Langchain.LLM.Core import qualified Langchain.Runnable.Core as Run @@ -72,37 +81,7 @@ instance Show Ollama where show (Ollama modelName _) = "Ollama " ++ show modelName --- | Ollama Params contains same fields GenerateOps and ChatOps from [ollama-haskell](https://hackage.haskell.org/package/ollama-haskell)-data OllamaParams = OllamaParams- { suffix :: Maybe Text- -- ^ An optional suffix to append to the generated text.- , images :: Maybe [Text]- -- ^ Optional list of base64 encoded images to include with the request.- , format :: Maybe O.Format- -- ^ An optional format specifier for the response.- , system :: Maybe Text- -- ^ Optional system text that can be included in the generation context.- , template :: Maybe Text- -- ^ An optional streaming function where the first function handles each chunk of response, and the second flushes the stream.- -- ^ This will not work for chat and stream, use promptTemplate instead- , raw :: Maybe Bool- -- ^ An optional flag to return the raw response.- , keepAlive :: Maybe Text- -- ^ Optional text to specify keep-alive behavior.- , hostUrl :: Maybe Text- -- ^ Override default Ollama host url. Default url = "http://127.0.0.1:11434"- , responseTimeOut :: Maybe Int- -- ^ Override default response timeout in minutes. Default = 15 minutes- , options :: Maybe Value- -- ^ additional model parameters listed in the documentation for the Modelfile such as temperature- , tools :: Maybe [Value]- -- ^ Optional tools that may be used in the chat. Will only work for chat and stream and not Generate.- }- deriving (Eq, Show)- {- | Ollama implementation of the LLM typeclass-Note: Params argument is currently ignored (see TODOs).- Example instance usage: @@@ -113,7 +92,8 @@ @ -} instance LLM Ollama where- type LLMParams Ollama = OllamaParams+ type LLMParams Ollama = OllamaChat.ChatOps+ type LLMStreamTokenType Ollama = OllamaChat.ChatResponse -- \| Generate text from a prompt -- Returns Left on API errors, Right on success.@@ -121,33 +101,26 @@ -- Example: -- >>> generate (Ollama "llama3.2" []) "Hello" Nothing -- Right "Hello! How can I assist you today?"- -- generate (Ollama model cbs) prompt mbOllamaParams = do mapM_ (\cb -> cb LLMStart) cbs- eRes <-- OllamaGenerate.generate- OllamaGenerate.defaultGenerateOps- { OllamaGenerate.modelName = model- , OllamaGenerate.prompt = prompt- , OllamaGenerate.stream = Nothing- , OllamaGenerate.suffix = maybe Nothing suffix mbOllamaParams- , OllamaGenerate.images = maybe Nothing images mbOllamaParams- , OllamaGenerate.format = maybe Nothing format mbOllamaParams- , OllamaGenerate.system = maybe Nothing system mbOllamaParams- , OllamaGenerate.template = maybe Nothing template mbOllamaParams- , OllamaGenerate.raw = maybe Nothing raw mbOllamaParams- , OllamaGenerate.keepAlive = maybe Nothing keepAlive mbOllamaParams- , OllamaGenerate.hostUrl = maybe Nothing hostUrl mbOllamaParams- , OllamaGenerate.responseTimeOut = maybe Nothing responseTimeOut mbOllamaParams- , OllamaGenerate.options = maybe Nothing options mbOllamaParams- }+ let chatOps_ = fromMaybe OllamaChat.defaultChatOps mbOllamaParams+ msg = OllamaChat.userMessage prompt+ chatOps =+ chatOps_+ { OllamaChat.modelName = model+ , OllamaChat.messages = [msg]+ }++ eRes <- OllamaChat.chat chatOps Nothing case eRes of Left err -> do- mapM_ (\cb -> cb (LLMError err)) cbs- return $ Left (show err)- Right res -> do+ mapM_ (\cb -> cb (LLMError $ show err)) cbs+ return $ Left (llmError (T.pack $ show err) Nothing Nothing)+ Right chatResponse -> do mapM_ (\cb -> cb LLMEnd) cbs- return $ Right (OllamaGenerate.response_ res)+ case OllamaChat.message chatResponse of+ Nothing -> pure $ Left (Error.fromString "Message not found in response")+ Just m -> pure $ Right $ OllamaChat.content m -- \| Chat interaction with message history. -- Uses Ollama's chat API for multi-turn conversations.@@ -156,31 +129,30 @@ -- >>> let msgs = UserMessage "Hi" :| [AssistantMessage "Hello!"] -- >>> chat (Ollama "llama3" []) msgs Nothing -- Right "How are you today?"- -- chat (Ollama model cbs) messages mbOllamaParams = do mapM_ (\cb -> cb LLMStart) cbs- eRes <-- OllamaChat.chat- OllamaChat.defaultChatOps- { OllamaChat.chatModelName = model- , OllamaChat.messages = toOllamaMessages messages- , OllamaChat.stream = Nothing- , OllamaChat.tools = maybe Nothing tools mbOllamaParams- , OllamaChat.format = maybe Nothing format mbOllamaParams- , OllamaChat.keepAlive = maybe Nothing keepAlive mbOllamaParams- , OllamaChat.hostUrl = maybe Nothing hostUrl mbOllamaParams- , OllamaChat.responseTimeOut = maybe Nothing responseTimeOut mbOllamaParams- , OllamaChat.options = maybe Nothing options mbOllamaParams- }+ let chatOps_ = fromMaybe OllamaChat.defaultChatOps mbOllamaParams+ chatOps =+ chatOps_+ { OllamaChat.modelName = model+ , OllamaChat.messages = NonEmpty.map to messages+ }+ eRes <- OllamaChat.chat chatOps Nothing case eRes of Left err -> do- mapM_ (\cb -> cb (LLMError err)) cbs- return $ Left (show err)+ mapM_ (\cb -> cb (LLMError $ show err)) cbs+ return $ Left (llmError (T.pack $ show err) Nothing Nothing) Right res -> do mapM_ (\cb -> cb LLMEnd) cbs- return $ Right (chatRespToText res)- where- chatRespToText resp = maybe "" OllamaChat.content (OllamaChat.message resp)+ case OllamaChat.message res of+ Nothing ->+ return $+ Left $+ llmError+ (T.pack $ "Message field not found: " <> show res)+ Nothing+ Nothing+ Just ollamaMsg -> return $ Right (from ollamaMsg) -- \| Streaming response handling. -- Processes tokens in real-time via StreamHandler.@@ -190,85 +162,97 @@ -- >>> stream (Ollama "llama3" []) messages handler Nothing -- Token: H Token: i Complete --- stream (Ollama model_ cbs) messages StreamHandler {onToken, onComplete} mbOllamaParams = do- mapM_ (\cb -> cb LLMStart) cbs- eRes <-- OllamaChat.chat- OllamaChat.defaultChatOps- { OllamaChat.chatModelName = model_- , OllamaChat.messages = toOllamaMessages messages- , OllamaChat.stream = Just (onToken . chatRespToText, onComplete)- , OllamaChat.tools = maybe Nothing tools mbOllamaParams- , OllamaChat.format = maybe Nothing format mbOllamaParams- , OllamaChat.keepAlive = maybe Nothing keepAlive mbOllamaParams- , OllamaChat.hostUrl = maybe Nothing hostUrl mbOllamaParams- , OllamaChat.responseTimeOut = maybe Nothing responseTimeOut mbOllamaParams- , OllamaChat.options = maybe Nothing options mbOllamaParams- }- case eRes of- Left err -> do- mapM_ (\cb -> cb (LLMError err)) cbs- return $ Left (show err)- Right _ -> do- mapM_ (\cb -> cb LLMEnd) cbs- return $ Right ()- where- chatRespToText OllamaChat.ChatResponse {..} = maybe "" OllamaChat.content message--{- | Convert LangChain messages to Ollama format.-Current limitations:-- Ignores 'messageData' field-- No tool call support (see TODO)+ -- Note: Don't pass streamHandler in ChatOps's stream field. It will be overridden.+ stream+ (Ollama model_ cbs)+ messages+ StreamHandler {onToken, onComplete}+ mbOllamaParams = do+ let chatOps_ = fromMaybe OllamaChat.defaultChatOps mbOllamaParams+ chatOps =+ chatOps_+ { OllamaChat.modelName = model_+ , OllamaChat.messages = NonEmpty.map to messages+ , OllamaChat.stream =+ Just+ ( onToken+ , pure ()+ )+ }+ mapM_ (\cb -> cb LLMStart) cbs+ eRes <- OllamaChat.chat chatOps Nothing+ case eRes of+ Left err -> do+ mapM_ (\cb -> cb (LLMError $ show err)) cbs+ return $ Left (llmError (T.pack $ show err) Nothing Nothing)+ Right _ -> do+ onComplete+ mapM_ (\cb -> cb LLMEnd) cbs+ return $ Right () -Example conversion:->>> let msg = Message System "You are an assistant" defaultMessageData->>> toOllamaMessages (msg :| [])-NonEmpty [OllamaChat.Message System "You are an assistant" Nothing Nothing]--}-toOllamaMessages :: NonEmpty Message -> NonEmpty OllamaChat.Message-toOllamaMessages = NonEmpty.map $ \Message {..} ->- OllamaChat.Message (toOllamaRole role) content Nothing Nothing- where- toOllamaRole User = OllamaChat.User- toOllamaRole System = OllamaChat.System- toOllamaRole Assistant = OllamaChat.Assistant- toOllamaRole Tool = OllamaChat.Tool- toOllamaRole _ = OllamaChat.User -- Ollama only supports above 4 Roles, others will be defaulted to user+toOllamaRole :: Role -> OllamaChat.Role+toOllamaRole User = OllamaChat.User+toOllamaRole System = OllamaChat.System+toOllamaRole Assistant = OllamaChat.Assistant+toOllamaRole Tool = OllamaChat.Tool+toOllamaRole _ = OllamaChat.User -- Ollama only supports above 4 Roles, others will be defaulted to user -instance Run.Runnable Ollama where- type RunnableInput Ollama = (ChatMessage, Maybe OllamaParams)- type RunnableOutput Ollama = Text+fromOllamaRole :: OllamaChat.Role -> Role+fromOllamaRole OllamaChat.User = User+fromOllamaRole OllamaChat.System = System+fromOllamaRole OllamaChat.Assistant = Assistant+fromOllamaRole OllamaChat.Tool = Tool - invoke = uncurry . chat +instance MessageConvertible OllamaChat.Message where+ to Message {..} =+ OllamaChat.Message+ (toOllamaRole role)+ content+ (messageImages messageData)+ (fmap toOllamaToolCall <$> toolCalls messageData)+ (thinking messageData)+ where+ toOllamaToolCall :: ToolCall -> O.ToolCall+ toOllamaToolCall ToolCall {..} =+ O.ToolCall+ { O.outputFunction =+ O.OutputFunction+ { O.outputFunctionName = toolFunctionName toolCallFunction+ , O.arguments = toolFunctionArguments toolCallFunction+ }+ } --- | Default values for OllamaParams-defaultOllamaParams :: OllamaParams-defaultOllamaParams = OllamaParams- { suffix = Nothing- , images = Nothing- , format = Nothing- , system = Nothing- , template = Nothing- , raw = Nothing- , keepAlive = Nothing- , hostUrl = Nothing- , responseTimeOut = Nothing- , options = Nothing- , tools = Nothing- }+ from (OllamaChat.Message role' content' imgs tools think) =+ Message+ { role = fromOllamaRole role'+ , content = content'+ , messageData =+ MessageData+ { messageImages = imgs+ , toolCalls = fmap toToolCall <$> tools+ , thinking = think+ , name = Nothing+ }+ }+ where+ toToolCall :: O.ToolCall -> ToolCall+ toToolCall O.ToolCall {..} =+ ToolCall+ { toolCallId = ""+ , toolCallType = "function"+ , toolCallFunction =+ ToolFunction+ { toolFunctionName = O.outputFunctionName outputFunction+ , toolFunctionArguments = O.arguments outputFunction+ }+ } -{- $examples-Test case patterns:-1. Basic generation- >>> generate (Ollama "test-model" []) "Hello" Nothing- Right "Mock response"+instance Run.Runnable Ollama where+ type RunnableInput Ollama = (ChatHistory, Maybe OllamaChat.ChatOps)+ type RunnableOutput Ollama = Message -2. Error handling- >>> generate (Ollama "invalid-model" []) "Test" Nothing- Left "API request failed"+ invoke = uncurry . chat -3. Streaming interaction- >>> let handler = StreamHandler print (pure ())- >>> stream (Ollama "llama3" []) (UserMessage "Hi" :| []) handler Nothing- Right ()--}+-- | Default values for Ollama+defaultOllama :: Ollama+defaultOllama = Ollama "llama3.2" []
src/Langchain/LLM/OpenAI.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeFamilies #-}@@ -28,8 +28,8 @@ main = do let openAI = OpenAI { apiKey = "your-api-key"- , openAIModelName = "gpt-3.5-turbo" , callbacks = []+ , baseUrl = Nothing } result <- LLM.generate openAI "Tell me a joke" Nothing case result of@@ -38,22 +38,25 @@ @ -} module Langchain.LLM.OpenAI- ( - -- * Types+ ( -- * Types OpenAI (..)- , OpenAIParams (..)+ -- * Default functions- , defaultOpenAIParams+ , defaultOpenAI++ -- * Re-export+ , module Langchain.LLM.Core ) where -import qualified Data.List.NonEmpty as NE-import Data.Map (Map)-import Data.Maybe (fromMaybe, listToMaybe)+import Data.Maybe (fromMaybe) import Data.Text (Text) import Langchain.Callback (Callback)+import Langchain.LLM.Core import qualified Langchain.LLM.Core as LLM-import qualified Langchain.LLM.Internal.OpenAI as OpenAI+import Langchain.LLM.OpenAICompatible (OpenAICompatible)+import qualified Langchain.LLM.OpenAICompatible as OpenAICompatible import qualified Langchain.Runnable.Core as Run+import qualified OpenAI.V1.Chat.Completions as OpenAIV1 {- | Configuration for OpenAI's language models. @@ -63,276 +66,48 @@ data OpenAI = OpenAI { apiKey :: Text -- ^ The API key for authenticating with OpenAI's services.- , openAIModelName :: Text- -- ^ The name of the OpenAI model to use (e.g., "gpt-3.5-turbo", "gpt-4"). , callbacks :: [Callback] -- ^ A list of callbacks for handling events during LLM operations.+ , baseUrl :: Maybe String+ -- ^ Base url; default "https://api.openai.com" } -- | Not including API key to avoid accidental leak instance Show OpenAI where- show OpenAI {..} = "OpenAI " ++ show openAIModelName+ show _ = "OpenAI" +toOpenAI :: OpenAI -> OpenAICompatible+toOpenAI OpenAI {..} =+ OpenAICompatible.OpenAICompatible+ { apiKey = apiKey+ , callbacks = callbacks+ , baseUrl =+ Just $+ fromMaybe+ "https://api.openai.com"+ baseUrl+ , providerName = "OpenAI"+ }+ {- | Implementation of the 'LLM' typeclass for OpenAI models. This instance provides methods for generating text, handling chat interactions, and streaming responses using OpenAI's API. -} instance LLM.LLM OpenAI where- type LLMParams OpenAI = OpenAIParams-- generate OpenAI {..} prompt mbOpenAIParams = do- eRes <-- OpenAI.createChatCompletion- apiKey- ( OpenAI.defaultChatCompletionRequest- { OpenAI.model = openAIModelName- , OpenAI.messages =- [OpenAI.defaultMessage {OpenAI.content = Just (OpenAI.StringContent prompt)}]- , OpenAI.timeout = maybe Nothing timeout mbOpenAIParams- , OpenAI.frequencyPenalty = maybe Nothing frequencyPenalty mbOpenAIParams- , OpenAI.logitBias = maybe Nothing logitBias mbOpenAIParams- , OpenAI.logprobs = maybe Nothing logprobs mbOpenAIParams- , OpenAI.maxCompletionTokens = maybe Nothing maxCompletionTokens mbOpenAIParams- , OpenAI.maxTokens = maybe Nothing maxTokens mbOpenAIParams- , OpenAI.metadata = maybe Nothing metadata mbOpenAIParams- , OpenAI.modalities = maybe Nothing modalities mbOpenAIParams- , OpenAI.n = maybe Nothing n mbOpenAIParams- , OpenAI.parallelToolCalls = maybe Nothing parallelToolCalls mbOpenAIParams- , OpenAI.prediction = maybe Nothing prediction mbOpenAIParams- , OpenAI.presencePenalty = maybe Nothing presencePenalty mbOpenAIParams- , OpenAI.reasoningEffort = maybe Nothing reasoningEffort mbOpenAIParams- , OpenAI.responseFormat = maybe Nothing responseFormat mbOpenAIParams- , OpenAI.seed = maybe Nothing seed mbOpenAIParams- , OpenAI.serviceTier = maybe Nothing serviceTier mbOpenAIParams- , OpenAI.stop = maybe Nothing stop mbOpenAIParams- , OpenAI.store = maybe Nothing store mbOpenAIParams- , OpenAI.temperature = maybe Nothing temperature mbOpenAIParams- , OpenAI.toolChoice = maybe Nothing toolChoice mbOpenAIParams- , OpenAI.tools = maybe Nothing tools mbOpenAIParams- , OpenAI.topLogprobs = maybe Nothing topLogprobs mbOpenAIParams- , OpenAI.topP = maybe Nothing topP mbOpenAIParams- , OpenAI.user = maybe Nothing user mbOpenAIParams- , OpenAI.webSearchOptions = maybe Nothing webSearchOptions mbOpenAIParams- , OpenAI.audio = maybe Nothing audio mbOpenAIParams- }- )- case eRes of- Left err -> return $ Left err- Right r -> do- case listToMaybe ((\OpenAI.ChatCompletionResponse {..} -> choices) r) of- Nothing -> return $ Left "Did not received any response"- Just resp ->- let OpenAI.Message {..} = OpenAI.message resp- in pure $- Right $- maybe- ""- ( \c -> case c of- OpenAI.StringContent t -> t- OpenAI.ContentParts _ -> ""- )- content- chat OpenAI {..} msgs mbOpenAIParams = do- eRes <-- OpenAI.createChatCompletion- apiKey- ( OpenAI.defaultChatCompletionRequest- { OpenAI.model = openAIModelName- , OpenAI.messages = toOpenAIMessages msgs- , OpenAI.timeout = maybe Nothing timeout mbOpenAIParams- , OpenAI.frequencyPenalty = maybe Nothing frequencyPenalty mbOpenAIParams- , OpenAI.logitBias = maybe Nothing logitBias mbOpenAIParams- , OpenAI.logprobs = maybe Nothing logprobs mbOpenAIParams- , OpenAI.maxCompletionTokens = maybe Nothing maxCompletionTokens mbOpenAIParams- , OpenAI.maxTokens = maybe Nothing maxTokens mbOpenAIParams- , OpenAI.metadata = maybe Nothing metadata mbOpenAIParams- , OpenAI.modalities = maybe Nothing modalities mbOpenAIParams- , OpenAI.n = maybe Nothing n mbOpenAIParams- , OpenAI.parallelToolCalls = maybe Nothing parallelToolCalls mbOpenAIParams- , OpenAI.prediction = maybe Nothing prediction mbOpenAIParams- , OpenAI.presencePenalty = maybe Nothing presencePenalty mbOpenAIParams- , OpenAI.reasoningEffort = maybe Nothing reasoningEffort mbOpenAIParams- , OpenAI.responseFormat = maybe Nothing responseFormat mbOpenAIParams- , OpenAI.seed = maybe Nothing seed mbOpenAIParams- , OpenAI.serviceTier = maybe Nothing serviceTier mbOpenAIParams- , OpenAI.stop = maybe Nothing stop mbOpenAIParams- , OpenAI.store = maybe Nothing store mbOpenAIParams- , OpenAI.temperature = maybe Nothing temperature mbOpenAIParams- , OpenAI.toolChoice = maybe Nothing toolChoice mbOpenAIParams- , OpenAI.tools = maybe Nothing tools mbOpenAIParams- , OpenAI.topLogprobs = maybe Nothing topLogprobs mbOpenAIParams- , OpenAI.topP = maybe Nothing topP mbOpenAIParams- , OpenAI.user = maybe Nothing user mbOpenAIParams- , OpenAI.webSearchOptions = maybe Nothing webSearchOptions mbOpenAIParams- , OpenAI.audio = maybe Nothing audio mbOpenAIParams- }- )- case eRes of- Left err -> return $ Left err- Right r -> do- case listToMaybe ((\OpenAI.ChatCompletionResponse {..} -> choices) r) of- Nothing -> return $ Left "Did not received any response"- Just resp ->- let OpenAI.Message {..} = OpenAI.message resp- in pure $- Right $- maybe- ""- ( \c -> case c of- OpenAI.StringContent t -> t- OpenAI.ContentParts _ -> ""- )- content-- stream OpenAI {..} msgs LLM.StreamHandler {onComplete, onToken} mbOpenAIParams = do- let req =- OpenAI.defaultChatCompletionRequest- { OpenAI.model = openAIModelName- , OpenAI.messages = toOpenAIMessages msgs- , OpenAI.stream = Just True -- Enable streaming'- , OpenAI.timeout = maybe Nothing timeout mbOpenAIParams- , OpenAI.frequencyPenalty = maybe Nothing frequencyPenalty mbOpenAIParams- , OpenAI.logitBias = maybe Nothing logitBias mbOpenAIParams- , OpenAI.logprobs = maybe Nothing logprobs mbOpenAIParams- , OpenAI.maxCompletionTokens = maybe Nothing maxCompletionTokens mbOpenAIParams- , OpenAI.maxTokens = maybe Nothing maxTokens mbOpenAIParams- , OpenAI.metadata = maybe Nothing metadata mbOpenAIParams- , OpenAI.modalities = maybe Nothing modalities mbOpenAIParams- , OpenAI.n = maybe Nothing n mbOpenAIParams- , OpenAI.parallelToolCalls = maybe Nothing parallelToolCalls mbOpenAIParams- , OpenAI.prediction = maybe Nothing prediction mbOpenAIParams- , OpenAI.presencePenalty = maybe Nothing presencePenalty mbOpenAIParams- , OpenAI.reasoningEffort = maybe Nothing reasoningEffort mbOpenAIParams- , OpenAI.responseFormat = maybe Nothing responseFormat mbOpenAIParams- , OpenAI.seed = maybe Nothing seed mbOpenAIParams- , OpenAI.serviceTier = maybe Nothing serviceTier mbOpenAIParams- , OpenAI.stop = maybe Nothing stop mbOpenAIParams- , OpenAI.store = maybe Nothing store mbOpenAIParams- , OpenAI.temperature = maybe Nothing temperature mbOpenAIParams- , OpenAI.toolChoice = maybe Nothing toolChoice mbOpenAIParams- , OpenAI.tools = maybe Nothing tools mbOpenAIParams- , OpenAI.topLogprobs = maybe Nothing topLogprobs mbOpenAIParams- , OpenAI.topP = maybe Nothing topP mbOpenAIParams- , OpenAI.user = maybe Nothing user mbOpenAIParams- , OpenAI.webSearchOptions = maybe Nothing webSearchOptions mbOpenAIParams- , OpenAI.audio = maybe Nothing audio mbOpenAIParams- }- OpenAI.createChatCompletionStream- apiKey- req- OpenAI.OpenAIStreamHandler- { OpenAI.onComplete = onComplete- , OpenAI.onToken = onToken . chunkToText- }- where- chunkToText :: OpenAI.ChatCompletionChunk -> Text- chunkToText OpenAI.ChatCompletionChunk {..} = do- case listToMaybe chunkChoices of- Nothing -> ""- Just OpenAI.ChunkChoice {..} ->- fromMaybe "" ((\OpenAI.Delta {..} -> contentForDelta) delta)--toOpenAIMessages :: LLM.ChatMessage -> [OpenAI.Message]-toOpenAIMessages msgs = map go (NE.toList msgs)- where- toRole :: LLM.Role -> OpenAI.Role- toRole r = case r of- LLM.System -> OpenAI.System- LLM.User -> OpenAI.User- LLM.Assistant -> OpenAI.Assistant- LLM.Tool -> OpenAI.Tool- LLM.Developer -> OpenAI.Developer- LLM.Function -> OpenAI.Function+ type LLMParams OpenAI = OpenAIV1.CreateChatCompletion+ type LLMStreamTokenType OpenAI = OpenAIV1.ChatCompletionChunk - go :: LLM.Message -> OpenAI.Message- go msg =- OpenAI.defaultMessage- { OpenAI.role = toRole $ LLM.role msg- , OpenAI.content = Just $ OpenAI.StringContent (LLM.content msg)- }+ generate = LLM.generate . toOpenAI+ chat = LLM.chat . toOpenAI+ stream = LLM.stream . toOpenAI instance Run.Runnable OpenAI where- type RunnableInput OpenAI = (LLM.ChatMessage, Maybe OpenAIParams)- type RunnableOutput OpenAI = Text-- invoke = uncurry . LLM.chat---- | Parameters for customizing OpenAI API calls.-data OpenAIParams = OpenAIParams- { timeout :: Maybe Int- , frequencyPenalty :: Maybe Double- , logitBias :: Maybe (Map Text Double)- , logprobs :: Maybe Bool- , maxCompletionTokens :: Maybe Int- , maxTokens :: Maybe Int- , metadata :: Maybe (Map Text Text)- , modalities :: Maybe [OpenAI.Modality]- , n :: Maybe Int- , parallelToolCalls :: Maybe Bool- , prediction :: Maybe OpenAI.PredictionOutput- , presencePenalty :: Maybe Double- , reasoningEffort :: Maybe OpenAI.ReasoningEffort- , responseFormat :: Maybe OpenAI.ResponseFormat- , seed :: Maybe Int- , serviceTier :: Maybe Text- , stop :: Maybe (Either Text [Text])- , store :: Maybe Bool- , temperature :: Maybe Double- , toolChoice :: Maybe OpenAI.ToolChoice- , tools :: Maybe [OpenAI.Tool_]- , topLogprobs :: Maybe Int- , topP :: Maybe Double- , user :: Maybe Text- , webSearchOptions :: Maybe OpenAI.WebSearchOptions- , audio :: Maybe OpenAI.AudioConfig- }+ type RunnableInput OpenAI = (ChatHistory, Maybe OpenAIV1.CreateChatCompletion)+ type RunnableOutput OpenAI = LLM.Message --- | Default parameters for OpenAI API calls.-defaultOpenAIParams :: OpenAIParams-defaultOpenAIParams =- OpenAIParams- { timeout = Just 60- , frequencyPenalty = Nothing- , logitBias = Nothing- , logprobs = Nothing- , maxCompletionTokens = Nothing- , maxTokens = Nothing- , metadata = Nothing- , modalities = Nothing- , n = Nothing- , parallelToolCalls = Nothing- , prediction = Nothing- , presencePenalty = Nothing- , reasoningEffort = Nothing- , responseFormat = Nothing- , seed = Nothing- , serviceTier = Nothing- , stop = Nothing- , store = Nothing- , temperature = Nothing- , toolChoice = Nothing- , tools = Nothing- , topLogprobs = Nothing- , topP = Nothing- , user = Nothing- , webSearchOptions = Nothing- , audio = Nothing- }+ invoke = uncurry . chat -{--ghci> :set -XOverloadedStrings-ghci> let o = OpenAI { apiKey = <my api key>- , openAIModelName = "gpt-4.1-nano"- , Langchain.LLM.OpenAI.callbacks = []- }-ghci> eRes <- generate o "What is 2+2" Nothing-ghci> eRes-Right "2 + 2 equals 4."-ghci> import qualified Data.List.NonEmpty as NE-ghci> let msg = Langchain.LLM.Core.Message Langchain.LLM.Core.User "What is 2+2" defaultMessageData-ghci> let chatMsg = NE.fromList [msg]-ghci> eRes <- chat o chatMsg Nothing-ghci> eRes-Right "2 + 2 equals 4."--}+-- | Default values for OpenAI+defaultOpenAI :: OpenAI+defaultOpenAI = OpenAI "your-api-key" [] Nothing
+ src/Langchain/LLM/OpenAICompatible.hs view
@@ -0,0 +1,403 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeFamilies #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++{- |+Module : Langchain.LLM.OpenAICompatible+Description : Generic OpenAI-compatible API integration for LangChain Haskell+Copyright : (c) 2025 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++This module provides a generic 'OpenAICompatible' data type and+implements the 'LLM' typeclass for interacting with any service that provides+an OpenAI-compatible API interface.+-}+module Langchain.LLM.OpenAICompatible+ ( OpenAICompatible (..)+ , mkOpenRouter+ , module Langchain.LLM.Core+ ) where++import Control.Exception (SomeException, try)+import qualified Data.Aeson as Aeson+import qualified Data.Aeson.KeyMap as KM+import qualified Data.ByteString.Lazy.Char8 as BSL+import qualified Data.List.NonEmpty as NE+import Data.Maybe (fromMaybe, listToMaybe)+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import qualified Data.Vector as V+import Langchain.Callback+import qualified Langchain.Error as Error+import Langchain.LLM.Core+import qualified Langchain.LLM.Core as LLM+import qualified Langchain.Runnable.Core as LLM+import OpenAI.V1+import OpenAI.V1.Chat.Completions+import qualified OpenAI.V1.Chat.Completions as OpenAIV1+import qualified OpenAI.V1.ToolCall as OpenAIV1++-- | Generic OpenAICompatible implementation for any service with an OpenAI-compatible API+data OpenAICompatible = OpenAICompatible+ { apiKey :: T.Text+ -- ^ The API key for authenticating.+ , callbacks :: [Callback]+ -- ^ A list of callbacks for handling events during LLM operations+ , baseUrl :: Maybe String+ -- ^ Base URL for the service. Default "https://api.openai.com"+ , providerName :: T.Text+ -- ^ The provider or service name+ }++instance Show OpenAICompatible where+ show OpenAICompatible {..} = show providerName++-- | Helper function to extract text from OpenAI Message T.Text+messageToText :: OpenAIV1.Message T.Text -> T.Text+messageToText (OpenAIV1.User {OpenAIV1.content = c}) = c+messageToText (OpenAIV1.System {OpenAIV1.content = c}) = c+messageToText (OpenAIV1.Assistant {OpenAIV1.assistant_content = ac}) = fromMaybe "" ac+messageToText (OpenAIV1.Tool {OpenAIV1.content = c}) = c++-- | Helper function to extract text from Vector Content+extractTextFromContent :: V.Vector OpenAIV1.Content -> T.Text+extractTextFromContent contents =+ fromMaybe "" $ listToMaybe $ V.toList $ V.mapMaybe getTextContent contents+ where+ getTextContent :: OpenAIV1.Content -> Maybe T.Text+ getTextContent (OpenAIV1.Text txt) = Just txt+ getTextContent _ = Nothing++-- | Helper function to create content list with text+makeContentList :: T.Text -> Maybe [T.Text] -> V.Vector OpenAIV1.Content+makeContentList text mbImageData = do+ let res = V.fromList [OpenAIV1.Text text]+ res <> case mbImageData of+ Just images ->+ V.fromList+ ( map+ ( OpenAIV1.Image_URL+ . (\urlText -> OpenAIV1.ImageURL {url = urlText, detail = Nothing})+ )+ images+ )+ Nothing -> V.empty++toOpenAIToolCall :: [ToolCall] -> V.Vector OpenAIV1.ToolCall+toOpenAIToolCall = V.fromList . map go+ where+ go :: ToolCall -> OpenAIV1.ToolCall+ go = \case+ ToolCall {toolCallId, toolCallFunction = ToolFunction {toolFunctionName, toolFunctionArguments}} ->+ OpenAIV1.ToolCall_Function+ { OpenAIV1.id = toolCallId+ , OpenAIV1.function =+ OpenAIV1.Function+ { OpenAIV1.name = toolFunctionName+ , OpenAIV1.arguments = T.decodeUtf8 $ BSL.toStrict $ Aeson.encode toolFunctionArguments+ }+ }++fromOpenAIToolCall :: OpenAIV1.ToolCall -> ToolCall+fromOpenAIToolCall = \case+ OpenAIV1.ToolCall_Function+ { OpenAIV1.id = tcId+ , OpenAIV1.function =+ OpenAIV1.Function+ { OpenAIV1.name = fnName+ , OpenAIV1.arguments = fnArgs+ }+ } ->+ let argsVal = Aeson.decode (BSL.fromStrict $ T.encodeUtf8 fnArgs) :: Maybe Aeson.Value+ argsMap = case argsVal of+ Just (Aeson.Object o) -> KM.toMapText o+ _ -> mempty+ in ToolCall+ { toolCallId = tcId+ , toolCallType = "function"+ , toolCallFunction =+ ToolFunction+ { toolFunctionName = fnName+ , toolFunctionArguments = argsMap+ }+ }++getToolId :: [ToolCall] -> T.Text+getToolId toolCalls = case toolCalls of+ (ToolCall {toolCallId} : _) -> toolCallId+ [] -> ""++getImageDataIfExists :: V.Vector OpenAIV1.Content -> Maybe [T.Text]+getImageDataIfExists contents =+ let images = V.toList $ V.mapMaybe getImageContent contents+ in if null images then Nothing else Just images+ where+ getImageContent :: OpenAIV1.Content -> Maybe T.Text+ getImageContent (OpenAIV1.Image_URL imgUrl) = Just $ url imgUrl+ getImageContent _ = Nothing++{- | MessageConvertible instance for OpenAIV1.Message (V.Vector OpenAIV1.Content)+This is used for request messages+-}+instance LLM.MessageConvertible (OpenAIV1.Message (V.Vector OpenAIV1.Content)) where+ -- \| Convert LLM.Message to OpenAIV1.Message (V.Vector OpenAIV1.Content)+ to :: LLM.Message -> OpenAIV1.Message (V.Vector OpenAIV1.Content)+ to msg =+ let imagesData = LLM.messageImages $ LLM.messageData msg+ contentVec = makeContentList (LLM.content msg) imagesData+ msgName = LLM.name $ LLM.messageData msg+ in case LLM.role msg of+ LLM.User ->+ OpenAIV1.User+ { OpenAIV1.content = contentVec+ , OpenAIV1.name = msgName+ }+ LLM.System ->+ OpenAIV1.System+ { OpenAIV1.content = contentVec+ , OpenAIV1.name = msgName+ }+ LLM.Assistant ->+ OpenAIV1.Assistant+ { OpenAIV1.assistant_content = Just contentVec+ , OpenAIV1.name = msgName+ , OpenAIV1.refusal = Nothing+ , OpenAIV1.assistant_audio = Nothing+ , OpenAIV1.tool_calls = fmap toOpenAIToolCall <$> toolCalls $ messageData msg+ }+ LLM.Tool ->+ OpenAIV1.Tool+ { OpenAIV1.content = contentVec+ , OpenAIV1.tool_call_id = fromMaybe "" $ fmap getToolId <$> toolCalls $ messageData msg+ }+ -- Fallback to User for unsupported roles (Developer, Function)+ _ ->+ OpenAIV1.User+ { OpenAIV1.content = contentVec+ , OpenAIV1.name = msgName+ }++ -- \| Convert OpenAIV1.Message (V.Vector OpenAIV1.Content) to LLM.Message+ from :: OpenAIV1.Message (V.Vector OpenAIV1.Content) -> LLM.Message+ from msg = case msg of+ OpenAIV1.User {OpenAIV1.content = c, OpenAIV1.name = n} ->+ LLM.Message+ { LLM.role = LLM.User+ , LLM.content = extractTextFromContent c+ , LLM.messageData =+ LLM.MessageData+ { LLM.name = n+ , LLM.toolCalls = Nothing+ , LLM.messageImages = getImageDataIfExists c+ , LLM.thinking = Nothing+ }+ }+ OpenAIV1.System {OpenAIV1.content = c, OpenAIV1.name = n} ->+ LLM.Message+ { LLM.role = LLM.System+ , LLM.content = extractTextFromContent c+ , LLM.messageData =+ LLM.MessageData+ { LLM.name = n+ , LLM.toolCalls = Nothing+ , LLM.messageImages = getImageDataIfExists c+ , LLM.thinking = Nothing+ }+ }+ OpenAIV1.Assistant+ { OpenAIV1.assistant_content = ac+ , OpenAIV1.name = n+ , OpenAIV1.tool_calls = mbToolVector+ } ->+ LLM.Message+ { LLM.role = LLM.Assistant+ , LLM.content = maybe "" extractTextFromContent ac+ , LLM.messageData =+ LLM.MessageData+ { LLM.name = n+ , LLM.toolCalls = fmap (V.toList . V.map fromOpenAIToolCall) mbToolVector+ , LLM.messageImages = getImageDataIfExists =<< ac+ , LLM.thinking = Nothing+ }+ }+ OpenAIV1.Tool {OpenAIV1.content = c, OpenAIV1.tool_call_id = toolCallid} ->+ LLM.Message+ { LLM.role = LLM.Tool+ , LLM.content = extractTextFromContent c+ , LLM.messageData =+ LLM.MessageData+ { LLM.name = Nothing+ , LLM.toolCalls =+ Just+ [ ToolCall+ { toolCallId = toolCallid+ , toolCallType = "function"+ , toolCallFunction =+ ToolFunction+ { toolFunctionName = ""+ , toolFunctionArguments = mempty+ }+ }+ ]+ , LLM.messageImages = getImageDataIfExists c+ , LLM.thinking = Nothing+ }+ }++instance LLM.MessageConvertible (OpenAIV1.Message T.Text) where+ to :: LLM.Message -> OpenAIV1.Message T.Text+ to _ = error "Conversion to OpenAIV1.Message T.Text not implemented."++ -- \| Convert OpenAIV1.Message T.Text to LLM.Message+ from :: OpenAIV1.Message T.Text -> LLM.Message+ from msg = case msg of+ OpenAIV1.User {OpenAIV1.content = c, OpenAIV1.name = n} ->+ LLM.Message+ { LLM.role = LLM.User+ , LLM.content = c+ , LLM.messageData =+ LLM.MessageData+ { LLM.name = n+ , LLM.toolCalls = Nothing+ , LLM.messageImages = Nothing+ , LLM.thinking = Nothing+ }+ }+ OpenAIV1.System {OpenAIV1.content = c, OpenAIV1.name = n} ->+ LLM.Message+ { LLM.role = LLM.System+ , LLM.content = c+ , LLM.messageData =+ LLM.MessageData+ { LLM.name = n+ , LLM.toolCalls = Nothing+ , LLM.messageImages = Nothing+ , LLM.thinking = Nothing+ }+ }+ OpenAIV1.Assistant+ { OpenAIV1.assistant_content = ac+ , OpenAIV1.name = n+ , OpenAIV1.tool_calls = mbToolVector+ } ->+ LLM.Message+ { LLM.role = LLM.Assistant+ , LLM.content = fromMaybe "" ac+ , LLM.messageData =+ LLM.MessageData+ { LLM.name = n+ , LLM.toolCalls = fmap (V.toList . V.map fromOpenAIToolCall) mbToolVector+ , LLM.messageImages = Nothing+ , LLM.thinking = Nothing+ }+ }+ OpenAIV1.Tool {OpenAIV1.content = c, OpenAIV1.tool_call_id = toolCallid} ->+ LLM.Message+ { LLM.role = LLM.Tool+ , LLM.content = c+ , LLM.messageData =+ LLM.MessageData+ { LLM.name = Nothing+ , LLM.toolCalls =+ Just+ [ ToolCall+ { toolCallId = toolCallid+ , toolCallType = "function"+ , toolCallFunction =+ ToolFunction+ { toolFunctionName = ""+ , toolFunctionArguments = mempty+ }+ }+ ]+ , LLM.messageImages = Nothing+ , LLM.thinking = Nothing+ }+ }++-- | Helper function to convert LLM.Message to OpenAI Message (using MessageConvertible)+toOpenAIMsg :: LLM.Message -> OpenAIV1.Message (V.Vector OpenAIV1.Content)+toOpenAIMsg = LLM.to++-- | Helper function to convert OpenAI Message to LLM.Message (using MessageConvertible)+fromOpenAIMsg :: OpenAIV1.Message T.Text -> LLM.Message+fromOpenAIMsg = LLM.from++instance LLM.LLM OpenAICompatible where+ type LLMParams OpenAICompatible = OpenAIV1.CreateChatCompletion+ type LLMStreamTokenType OpenAICompatible = OpenAIV1.ChatCompletionChunk++ generate OpenAICompatible {..} prompt mbLLMParams = do+ clientEnv <- getClientEnv $ maybe "https://api.openai.com" T.pack baseUrl+ let Methods {createChatCompletion} = makeMethods clientEnv apiKey Nothing Nothing+ let openaiParams = fromMaybe _CreateChatCompletion mbLLMParams++ eRes <-+ try $+ createChatCompletion+ openaiParams+ { OpenAIV1.messages =+ V.fromList+ [ OpenAIV1.User+ { OpenAIV1.content = V.fromList [OpenAIV1.Text prompt]+ , name = Nothing+ }+ ]+ }+ case eRes of+ Left err -> pure $ Left $ Error.fromString $ show (err :: SomeException)+ Right (ChatCompletionObject {choices}) -> do+ let Choice {message} = V.head choices+ pure (Right $ messageToText message)++ chat OpenAICompatible {..} chatHistory mbLLMParams = do+ clientEnv <- getClientEnv $ maybe "https://api.openai.com" T.pack baseUrl+ let Methods {createChatCompletion} = makeMethods clientEnv apiKey Nothing Nothing+ let openaiParams = fromMaybe _CreateChatCompletion mbLLMParams+ eRes <-+ try $+ createChatCompletion+ openaiParams {OpenAIV1.messages = V.fromList $ map toOpenAIMsg (NE.toList chatHistory)}+ case eRes of+ Left err -> pure $ Left $ Error.fromString $ show (err :: SomeException)+ Right (ChatCompletionObject {choices}) -> do+ let Choice {message} = V.head choices+ pure (Right $ fromOpenAIMsg message)++ stream OpenAICompatible {..} chatHistory streamHandler mbLLMParams = do+ let onEvent (Left _) = pure () -- ignore for now+ onEvent (Right chunk) = onToken streamHandler chunk++ clientEnv <- getClientEnv $ maybe "https://api.openai.com" T.pack baseUrl+ let Methods {createChatCompletionStreamTyped} = makeMethods clientEnv apiKey Nothing Nothing+ let openaiParams = fromMaybe _CreateChatCompletion mbLLMParams++ let req_ = openaiParams {OpenAIV1.messages = V.fromList $ map toOpenAIMsg (NE.toList chatHistory)}+ _ <- createChatCompletionStreamTyped req_ onEvent+ pure $ Right ()++{- | Create an OpenRouter instance+OpenRouter provides access to multiple model providers through a single API+Model name should be in the format "provider/model" (e.g., "anthropic/claude-3-opus")+-}+mkOpenRouter :: [Callback] -> Maybe String -> T.Text -> OpenAICompatible+mkOpenRouter callbacks' baseUrl' apiKey' =+ OpenAICompatible+ { apiKey = apiKey' -- OpenRouter requires an API key+ , callbacks = callbacks'+ , baseUrl = Just $ fromMaybe "https://openrouter.ai/api" baseUrl'+ , providerName = "OpenRouter"+ }++instance LLM.Runnable OpenAICompatible where+ type RunnableInput OpenAICompatible = (ChatHistory, Maybe OpenAIV1.CreateChatCompletion)+ type RunnableOutput OpenAICompatible = LLM.Message++ invoke = uncurry . chat
src/Langchain/Memory/Core.hs view
@@ -38,9 +38,16 @@ , initialChatMessage ) where +import Control.Monad.IO.Class (MonadIO, liftIO) import qualified Data.List.NonEmpty as NE import Data.Text (Text)-import Langchain.LLM.Core (ChatMessage, Message (..), Role (..), defaultMessageData)+import Langchain.Error (LangchainResult)+import Langchain.LLM.Core+ ( ChatHistory+ , Message (..)+ , Role (..)+ , defaultMessageData+ ) import Langchain.Runnable.Core {- | Base typeclass for memory implementations@@ -54,22 +61,37 @@ addUserMessage = ... @ -}-class BaseMemory m where+class BaseMemory mem where -- | Retrieve current chat history- messages :: m -> IO (Either String ChatMessage)+ messages :: mem -> IO (LangchainResult ChatHistory) -- | Add user message to history- addUserMessage :: m -> Text -> IO (Either String m)+ addUserMessage :: mem -> Text -> IO (LangchainResult mem) -- | Add AI response to history- addAiMessage :: m -> Text -> IO (Either String m)+ addAiMessage :: mem -> Text -> IO (LangchainResult mem) -- | Add generic message to history- addMessage :: m -> Message -> IO (Either String m)+ addMessage :: mem -> Message -> IO (LangchainResult mem) -- | Reset memory to initial state- clear :: m -> IO (Either String m)+ clear :: mem -> IO (LangchainResult mem) + messagesM :: MonadIO m => mem -> m (LangchainResult ChatHistory)+ messagesM = liftIO . messages++ addUserMessageM :: MonadIO m => mem -> Text -> m (LangchainResult mem)+ addUserMessageM mem msg = liftIO $ addUserMessage mem msg++ addAiMessageM :: MonadIO m => mem -> Text -> m (LangchainResult mem)+ addAiMessageM mem msg = liftIO $ addAiMessage mem msg++ addMessageM :: MonadIO m => mem -> Message -> m (LangchainResult mem)+ addMessageM mem msg = liftIO $ addMessage mem msg++ clearM :: MonadIO m => mem -> m (LangchainResult mem)+ clearM mem = liftIO $ clear mem+ {- | Sliding window memory implementation. Stores chat history with maximum size limit. @@ -83,10 +105,11 @@ -} data WindowBufferMemory = WindowBufferMemory { maxWindowSize :: Int- -- ^ Maximum number of messages to retain- -- ^ It is user's responsiblity to make sure the number is > 0.- , windowBufferMessages :: ChatMessage- -- ^ Current message buffer + {- ^ Maximum number of messages to retain+ ^ It is user's responsibility to make sure the number is > 0.+ -}+ , windowBufferMessages :: ChatHistory+ -- ^ Current message buffer } deriving (Show, Eq) @@ -97,7 +120,6 @@ -- -- >>> messages (WindowBufferMemory 5 initialMessages) -- Right initialMessages- -- messages WindowBufferMemory {..} = pure $ Right windowBufferMessages -- \| Add message with window trimming@@ -110,24 +132,25 @@ -- -- >>> addMessage mem msg3 -- Right (WindowBufferMemory {windowBufferMessages = [msg2, msg3]})- --- addMessage winBuffMem@WindowBufferMemory{..} newMsg = do+ addMessage winBuffMem@WindowBufferMemory {..} newMsg = do let currentMsgs = NE.toList windowBufferMessages newMsgs = currentMsgs ++ [newMsg] if length newMsgs > maxWindowSize- then do- let trimmedMsgs = removeOldestNonSystem newMsgs- pure $ Right $ winBuffMem { windowBufferMessages = NE.fromList trimmedMsgs }- else- pure $ Right $ winBuffMem { windowBufferMessages = NE.fromList newMsgs }+ then do+ let trimmedMsgs = removeOldestNonSystem newMsgs+ pure $+ Right $+ winBuffMem {windowBufferMessages = NE.fromList trimmedMsgs}+ else+ pure $ Right $ winBuffMem {windowBufferMessages = NE.fromList newMsgs} where isSystem (Message role _ _) = role == System- + removeOldestNonSystem = go where go [] = []- go (m:ms)+ go (m : ms) | isSystem m = m : go ms | otherwise = ms @@ -137,7 +160,6 @@ -- -- >>> addUserMessage mem "Hello" -- Right (WindowBufferMemory { ... })- -- addUserMessage winBuffMem uMsg = addMessage winBuffMem (Message User uMsg defaultMessageData) @@ -147,7 +169,6 @@ -- -- >>> addAiMessage mem "Response" -- Right (WindowBufferMemory { ... })- -- addAiMessage winBuffMem uMsg = addMessage winBuffMem (Message Assistant uMsg defaultMessageData) @@ -157,13 +178,13 @@ -- -- >>> clear mem -- Right (WindowBufferMemory { windowBufferMessages = [systemMsg] })- -- clear winBuffMem = pure $ Right $ winBuffMem { windowBufferMessages =- NE.singleton $ Message System "You are an AI model" defaultMessageData+ NE.singleton $+ Message System "You are an AI model" defaultMessageData } {- | Trim chat history to last n messages@@ -173,8 +194,10 @@ >>> trimChatMessage 2 msgs [msg2, msg3] -}-trimChatMessage :: Int -> ChatMessage -> ChatMessage-trimChatMessage n msgs = NE.fromList $ drop (max 0 (NE.length msgs - n)) (NE.toList msgs)+trimChatMessage :: Int -> ChatHistory -> ChatHistory+trimChatMessage n msgs =+ NE.fromList $+ drop (max 0 (NE.length msgs - n)) (NE.toList msgs) {- | Add and maintain window size Example:@@ -183,7 +206,7 @@ >>> addAndTrim 2 msg2 msgs [msg1, msg2] -}-addAndTrim :: Int -> Message -> ChatMessage -> ChatMessage+addAndTrim :: Int -> Message -> ChatHistory -> ChatHistory addAndTrim n msg msgs = trimChatMessage n (msgs <> NE.singleton msg) {- | Create initial chat history@@ -192,8 +215,10 @@ >>> initialChatMessage "You are Qwen" [Message System "You are Qwen"] -}-initialChatMessage :: Text -> ChatMessage-initialChatMessage systemPrompt = NE.singleton $ Message System systemPrompt defaultMessageData+initialChatMessage :: Text -> ChatHistory+initialChatMessage systemPrompt =+ NE.singleton $+ Message System systemPrompt defaultMessageData instance Runnable WindowBufferMemory where type RunnableInput WindowBufferMemory = Text@@ -205,8 +230,7 @@ -- -- >>> invoke memory "Hello" -- Right (WindowBufferMemory { ... })- --- invoke memory input = addUserMessage memory input+ invoke = addUserMessage {- $examples Test case patterns:
src/Langchain/Memory/TokenBufferMemory.hs view
@@ -14,11 +14,20 @@ Implementation of LangChain's Conversation token buffer. https://python.langchain.com/v0.1/docs/modules/memory/types/token_buffer/ -}-module Langchain.Memory.TokenBufferMemory (TokenBufferMemory (..), countTokens) where+module Langchain.Memory.TokenBufferMemory+ ( TokenBufferMemory (..)+ , countTokens+ ) where import qualified Data.List.NonEmpty as NE import qualified Data.Text as T-import Langchain.LLM.Core (ChatMessage, Message (..), Role (..), defaultMessageData)+import Langchain.Error (llmError)+import Langchain.LLM.Core+ ( ChatHistory+ , Message (..)+ , Role (..)+ , defaultMessageData+ ) import Langchain.Memory.Core import Langchain.Runnable.Core (Runnable (..)) @@ -26,7 +35,7 @@ data TokenBufferMemory = TokenBufferMemory { maxTokens :: Int -- ^ Max number of tokens. 4 characters = 1 Token- , tokenBufferMessages :: ChatMessage+ , tokenBufferMessages :: ChatHistory -- ^ Chat history (Nonempty List of Message) } deriving (Eq, Show)@@ -38,32 +47,62 @@ countTokens = sum . map go where go :: Message -> Int- go (Message _ content _) = ceiling @Double (fromIntegral (T.length content) / 4.0)+ go (Message _ content _) =+ ceiling @Double+ (fromIntegral (T.length content) / 4.0) instance BaseMemory TokenBufferMemory where messages TokenBufferMemory {..} = pure $ Right tokenBufferMessages addMessage t@TokenBufferMemory {..} newMsg = do let newMsgTokenCount = countTokens [newMsg]- currentMsgsTokenCount = countTokens $ NE.toList tokenBufferMessages - if newMsgTokenCount > maxTokens then - pure (Left "New message is exceeding limit")- else if newMsgTokenCount + currentMsgsTokenCount <= maxTokens then - pure (Right $ t { tokenBufferMessages = tokenBufferMessages <> NE.fromList [newMsg] })- else trimNonSystemMsgs (NE.toList tokenBufferMessages) newMsgTokenCount+ currentMsgsTokenCount = countTokens $ NE.toList tokenBufferMessages+ if newMsgTokenCount > maxTokens+ then+ pure (Left (llmError "New message is exceeding limit" Nothing Nothing))+ else+ if newMsgTokenCount + currentMsgsTokenCount <= maxTokens+ then+ pure+ ( Right $+ t+ { tokenBufferMessages =+ tokenBufferMessages <> NE.fromList [newMsg]+ }+ )+ else+ trimNonSystemMsgs+ (NE.toList tokenBufferMessages)+ newMsgTokenCount where trimNonSystemMsgs msgs newMsgTokenCount = do let trimmedMsgs = removeOldestNonSystem msgs- if trimmedMsgs == msgs then -- If no more non sys msg left- pure (Left "Cannot add new message since system message and new message excedds limit")- else- if countTokens trimmedMsgs + newMsgTokenCount <= maxTokens then - pure (Right $ t { tokenBufferMessages = NE.fromList $ trimmedMsgs <> [newMsg] })- else trimNonSystemMsgs trimmedMsgs newMsgTokenCount- + if trimmedMsgs == msgs -- If no more non sys msg left+ then+ pure+ ( Left $+ llmError+ ( "Cannot add new message since system"+ <> " message and new message exceeds limit"+ )+ Nothing+ Nothing+ )+ else+ if countTokens trimmedMsgs + newMsgTokenCount <= maxTokens+ then+ pure+ ( Right $+ t+ { tokenBufferMessages =+ NE.fromList $ trimmedMsgs <> [newMsg]+ }+ )+ else trimNonSystemMsgs trimmedMsgs newMsgTokenCount+ removeOldestNonSystem = go where- go [] = []- go (m:ms)+ go [] = []+ go (m : ms) | isSystem m = m : go ms | otherwise = ms @@ -80,11 +119,12 @@ Right $ tokBuffMem { tokenBufferMessages =- NE.singleton $ Message System "You are an AI model" defaultMessageData+ NE.singleton $+ Message System "You are an AI model" defaultMessageData } instance Runnable TokenBufferMemory where type RunnableInput TokenBufferMemory = T.Text type RunnableOutput TokenBufferMemory = TokenBufferMemory - invoke memory input = addUserMessage memory input+ invoke = addUserMessage
src/Langchain/OutputParser/Core.hs view
@@ -35,14 +35,16 @@ import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8) import Data.Text.Internal.Search (indices)+import Langchain.Error (LangchainResult, parsingError) {- | Typeclass for parsing output from language models into specific types. Instances of this class define how to convert a 'Text' output into a value of type 'a'. -} class OutputParser a where- -- | Parse the given text into a value of type 'a'.- -- Returns 'Left' with an error message if parsing fails, or 'Right' with the parsed value.- parse :: Text -> Either String a+ {- | Parse the given text into a value of type 'a'.+ Returns 'Left' with an error message if parsing fails, or 'Right' with the parsed value.+ -}+ parse :: Text -> LangchainResult a -- | Represents a list of text items separated by commas. newtype CommaSeparatedList = CommaSeparatedList [Text]@@ -74,15 +76,15 @@ -- @ parse txt = do let txt' = T.strip $ T.toLower txt- if length (indices "true" txt') > 0+ if not (null $ indices "true" txt') then Right True else- if length (indices "false" txt') > 0+ if not (null $ indices "false" txt') then Right False else- Left "Invalid boolean value"+ Left $ parsingError "Invalid boolean value" (Just "Bool") (Just txt) instance OutputParser CommaSeparatedList where -- \| Parse a comma-separated list from the text.@@ -135,7 +137,8 @@ instance FromJSON a => OutputParser (JSONOutputStructure a) where parse txt = case eitherDecode (fromStrict $ encodeUtf8 txt) of- Left err -> Left $ "JSON parsing error: " ++ err+ Left err ->+ Left $ parsingError ("JSON parsing error: " <> T.pack err) (Just "JSONOutputStructure") (Just txt) Right val -> Right val -- | Represents a list of text items separated by numbered prefixes, like "1. First item".@@ -169,10 +172,11 @@ parse txt = let s = trim (T.unpack txt) in case dropUntilAndConsumeBoundary s of- Nothing -> Left "No valid numbered items found"+ Nothing -> Left $ parsingError "No valid numbered items found" (Just "NumberSeparatedList") (Just txt) Just rest -> -- Parse the rest into items and wrap them in our newtype.- Right . NumberSeparatedList . map (T.pack . trim) $ parseItems rest+ Right . NumberSeparatedList . map (T.pack . trim) $+ parseItems rest {- | Drops noise until we find a valid boundary marker (number with dot) and then consumes the marker.@@ -187,7 +191,7 @@ Returns the index and length of the marker. -} findBoundary :: String -> Maybe (Int, Int)-findBoundary s = go 0 s+findBoundary = go 0 where go _ [] = Nothing go i xs@(_ : rest) =@@ -211,7 +215,12 @@ (c : rest3) | c == '.' -> let (spaces2, _) = span isSpace rest3- in Just (length digits + length spaces + 1 + length spaces2)+ in Just+ ( length digits+ + length spaces+ + 1+ + length spaces2+ ) _ -> Nothing -- | Recursively splits the string into items using the boundary markers.
src/Langchain/PromptTemplate.hs view
@@ -40,6 +40,7 @@ import qualified Data.Map.Strict as HM import Data.Text (Text) import qualified Data.Text as T+import Langchain.Error (LangchainResult, validationError) import Langchain.Runnable.Core (Runnable (..)) -- TODO: Add Mechanism for custom example selector@@ -75,7 +76,7 @@ -- Result: Left "Missing variable: place" @ -}-renderPrompt :: PromptTemplate -> HM.Map Text Text -> Either String Text+renderPrompt :: PromptTemplate -> HM.Map Text Text -> LangchainResult Text renderPrompt (PromptTemplate template) vars = interpolate vars template {- | Represents a few-shot prompt template with examples.@@ -118,12 +119,12 @@ -- Result: Right "Examples of {type}:\nInput: Hello\nOutput: Bonjour\n\nInput: Goodbye\nOutput: Au revoir\nNow translate: {query}" @ -}-renderFewShotPrompt :: FewShotPromptTemplate -> Either String Text+renderFewShotPrompt :: FewShotPromptTemplate -> LangchainResult Text renderFewShotPrompt FewShotPromptTemplate {..} = do -- Format each example using the example template formattedExamples <- mapM- (\ex -> interpolate ex fsExampleTemplate)+ (`interpolate` fsExampleTemplate) fsExamples -- Join the formatted examples with the separator let examplesText = T.intercalate fsExampleSeparator formattedExamples@@ -133,23 +134,23 @@ {- | Interpolate variables into a template string. Placeholders are of the form {key}, where key is a sequence of alphanumeric characters and underscores. -}-interpolate :: HM.Map Text Text -> Text -> Either String Text-interpolate vars template = go template+interpolate :: HM.Map Text Text -> Text -> LangchainResult Text+interpolate vars = go where- go :: Text -> Either String Text+ go :: Text -> LangchainResult Text go t = case T.breakOn "{" t of (before, after) | T.null after -> Right before (before, after') -> case T.breakOn "}" (T.drop 1 after') of- (_, after'') | T.null after'' -> Left "Unclosed brace"+ (_, after'') | T.null after'' -> Left $ validationError "Unclosed brace" Nothing Nothing (key, after''') -> let key' = T.strip key in case HM.lookup key' vars of Just val -> do rest <- go (T.drop 1 after''') return $ before <> val <> rest- Nothing -> Left $ "Missing variable: " <> T.unpack key'+ Nothing -> Left $ validationError ("Missing variable: " <> key') (Just key') Nothing instance Runnable PromptTemplate where type RunnableInput PromptTemplate = HM.Map Text Text
src/Langchain/Retriever/Core.hs view
@@ -35,12 +35,13 @@ , VectorStoreRetriever (..) ) where +import Control.Monad.IO.Class (MonadIO, liftIO)+import Data.Text (Text) import Langchain.DocumentLoader.Core (Document)+import Langchain.Error (LangchainResult) import Langchain.Runnable.Core import Langchain.VectorStore.Core -import Data.Text (Text)- {- | Typeclass for document retrieval systems Implementations should return documents relevant to a given query. @@ -56,14 +57,18 @@ @ -} class Retriever a where- -- | Retrieve documents relevant to the query- --- -- Example:- --- -- >>> _get_relevant_documents (VectorStoreRetriever myStore) "AI"- -- Right [Document "AI definition...", ...]- _get_relevant_documents :: a -> Text -> IO (Either String [Document])+ {- | Retrieve documents relevant to the query + Example:++ >>> _get_relevant_documents (VectorStoreRetriever myStore) "AI"+ Right [Document "AI definition...", ...]+ -}+ _get_relevant_documents :: a -> Text -> IO (LangchainResult [Document])++ _get_relevant_documentsM :: MonadIO m => a -> Text -> m (LangchainResult [Document])+ _get_relevant_documentsM retriever query = liftIO $ _get_relevant_documents retriever query+ {- | Vector store-backed retriever implementation Wraps any 'VectorStore' instance to provide similarity-based retrieval. @@ -108,7 +113,7 @@ type RunnableInput (VectorStoreRetriever a) = Text type RunnableOutput (VectorStoreRetriever a) = [Document] - invoke retriever query = _get_relevant_documents retriever query+ invoke = _get_relevant_documents {- $examples Test case patterns:
src/Langchain/Retriever/MultiQueryRetriever.hs view
@@ -50,6 +50,7 @@ import qualified Data.Map.Strict as HM import Data.Text (Text) import qualified Data.Text as T+import Langchain.Error (LangchainError, llmError) {- | Query generation prompt template Controls how the LLM generates multiple query variants from the original query.@@ -85,8 +86,7 @@ ] } -{- | Configuration for multi-query retrieval--}+-- | Configuration for multi-query retrieval data MultiQueryRetrieverConfig = MultiQueryRetrieverConfig { numQueries :: Int -- ^ Number of queries to generate@@ -176,7 +176,7 @@ Right ["Haskell", "Haskell features", "Haskell applications"] -} generateQueries ::- LLM m => m -> QueryGenerationPrompt -> Text -> Int -> Bool -> IO (Either String [Text])+ LLM m => m -> QueryGenerationPrompt -> Text -> Int -> Bool -> IO (Either LangchainError [Text]) generateQueries model (QueryGenerationPrompt promptTemplate) query n includeOriginal = do let vars = HM.fromList [("query", query), ("num_queries", T.pack $ show n)] case renderPrompt promptTemplate vars of@@ -186,8 +186,8 @@ case result of Left err -> return $ Left err Right response -> do- case parse response :: Either String NumberSeparatedList of- Left err -> return $ Left $ "Failed to parse LLM response: " ++ err+ case parse response :: Either LangchainError NumberSeparatedList of+ Left err -> return $ Left err Right (NumberSeparatedList queries) -> do let uniqueQueries = nub $ filter (not . T.null) queries return $@@ -231,7 +231,7 @@ (includeOriginalQuery cfg) case queriesResult of- Left err -> return $ Left $ "Error generating queries: " ++ err+ Left err -> return $ Left err Right queries -> do -- Get documents for each query results <- mapM (_get_relevant_documents baseRetriever) queries@@ -240,7 +240,7 @@ let validResults = rights results if null validResults- then return $ Left "No valid results from any query"+ then return $ Left (llmError "No valid results from any query" Nothing Nothing) else return $ Right $ combineDocuments validResults {-@@ -269,7 +269,7 @@ type RunnableInput (MultiQueryRetriever a m) = Text type RunnableOutput (MultiQueryRetriever a m) = [Document] - invoke r query = _get_relevant_documents r query+ invoke = _get_relevant_documents {- $examples Test case patterns:
src/Langchain/Runnable/Chain.hs view
@@ -41,6 +41,7 @@ ) where import Data.List (find)+import Langchain.Error (LangchainError) import Langchain.Runnable.Core {- | Chains two 'Runnable' instances together sequentially.@@ -61,7 +62,7 @@ r1 -> r2 -> RunnableInput r1 ->- IO (Either String (RunnableOutput r2))+ IO (Either LangchainError (RunnableOutput r2)) chain r1 r2 input = do output1 <- invoke r1 input case output1 of@@ -86,7 +87,7 @@ r1 -> r2 -> a ->- IO (Either String (RunnableOutput r1, RunnableOutput r2))+ IO (Either LangchainError (RunnableOutput r1, RunnableOutput r2)) branch r1 r2 input = do result1 <- invoke r1 input result2 <- invoke r2 input@@ -121,7 +122,7 @@ :} Right "This is a question, so I'm handling it with the question processor." -}-runBranch :: RunnableBranch a b -> a -> IO (Either String b)+runBranch :: RunnableBranch a b -> a -> IO (Either LangchainError b) runBranch (RunnableBranch options defaultR) input = case find (\(cond, _) -> cond input) options of Just (_, r) -> invoke r input@@ -156,7 +157,7 @@ :} Right False -}-runMap :: RunnableMap a b c -> a -> IO (Either String c)+runMap :: RunnableMap a b c -> a -> IO (Either LangchainError c) runMap (RunnableMap inputFn outputFn r) input = do result <- invoke r (inputFn input) return $ fmap outputFn result@@ -185,7 +186,7 @@ RunnableSequence a b -- RSCons adds a runnable at the front of the chain. -- | Run a sequence of runnables, chaining the output of one as input to the next.-runSequence :: RunnableSequence a b -> RunnableInputHead a -> IO (Either String b)+runSequence :: RunnableSequence a b -> RunnableInputHead a -> IO (Either LangchainError b) runSequence RSNil input = return (Right input) runSequence (RSCons r rs) input = do result <- invoke r input@@ -240,7 +241,7 @@ -} appendSequence :: ( Runnable r2- , RunnableOutput (RunnableSequence a b) ~ (RunnableInput r2)+ , RunnableOutput (RunnableSequence a b) ~ RunnableInput r2 ) => RunnableSequence a b -> r2 ->@@ -260,7 +261,7 @@ r1 -> r2 -> RunnableInput r1 ->- IO (Either String (RunnableOutput r2))+ IO (Either LangchainError (RunnableOutput r2)) (|>>) = chain infix 4 |>>
src/Langchain/Runnable/ConversationChain.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeFamilies #-} {- |@@ -32,6 +31,7 @@ ConversationChain (..) ) where +import Control.Monad.Trans.Except import Data.Text (Text) import Langchain.LLM.Core import Langchain.Memory.Core@@ -153,24 +153,9 @@ -- response3 <- invoke chatbot "Can you explain it in simpler terms?" -- @ --- invoke ConversationChain {..} input = do- -- Add user message to memory- updatedMemResult <- addUserMessage memory input- case updatedMemResult of- Left err -> return $ Left err- Right updatedMem -> do- -- Get all messages- messagesResult <- messages updatedMem- case messagesResult of- Left err -> return $ Left err- Right allMessages -> do- -- Format messages for the LLM- let formattedMessages = allMessages- -- Get response from LLM- llmResponse <- chat llm formattedMessages Nothing- case llmResponse of- Left err -> return $ Left err- Right response -> do- -- Store AI response in memory- _ <- addAiMessage updatedMem response- return $ Right response+ invoke chain input = runExceptT $ do+ updatedMem <- ExceptT $ addUserMessage (memory chain) input+ allMessages <- ExceptT $ messages updatedMem+ response <- ExceptT $ chat (llm chain) allMessages Nothing+ _ <- ExceptT $ addAiMessage updatedMem (content response)+ return $ content response
src/Langchain/Runnable/Core.hs view
@@ -29,6 +29,9 @@ ( Runnable (..) ) where +import Control.Monad.IO.Class (MonadIO, liftIO)+import Langchain.Error (LangchainResult)+ {- | The core 'Runnable' typeclass represents anything that can "run" with an input and produce an output. This typeclass is the foundation of the LangChain Expression Language (LCEL) in Haskell,@@ -56,76 +59,49 @@ @ -} class Runnable r where- -- | The type of input the runnable accepts.- --- -- For example, an LLM might accept 'String' or 'PromptValue' as input.+ {- | The type of input the runnable accepts.++ For example, an LLM might accept 'String' or 'PromptValue' as input.+ -} type RunnableInput r - -- | The type of output the runnable produces.- --- -- For example, an LLM might produce 'String' or 'LLMResult' as output.+ {- | The type of output the runnable produces.++ For example, an LLM might produce 'String' or 'LLMResult' as output.+ -} type RunnableOutput r - -- | Core method to invoke (run) this component with a single input.- --- -- This is the primary method that must be implemented for any 'Runnable'.- -- It processes a single input and returns either an error message or the output.- --- -- Example usage:- --- -- @- -- let model = OpenAI { temperature = 0.7, model = "gpt-3.5-turbo" }- -- result <- invoke model "Explain monads in simple terms."- -- case result of- -- Left err -> putStrLn $ "Error: " ++ err- -- Right response -> putStrLn response- -- @- invoke :: r -> RunnableInput r -> IO (Either String (RunnableOutput r))+ {- | Core method to invoke (run) this component with a single input. - -- | Batch process multiple inputs at once.- --- -- This method can be overridden to provide more efficient batch processing,- -- particularly for components like LLMs that may have batch APIs.- --- -- The default implementation simply maps 'invoke' over each input and- -- sequences the results.- --- -- Example usage:- --- -- @- -- let retriever = VectorDBRetriever { ... }- -- questions <- ["What is Haskell?", "Explain monads.", "How do I install GHC?"]- -- result <- batch retriever questions- -- case result of- -- Left err -> putStrLn $ "Batch processing failed: " ++ err- -- Right docs -> mapM_ print docs- -- @- batch :: r -> [RunnableInput r] -> IO (Either String [RunnableOutput r])+ This is the primary method that must be implemented for any 'Runnable'.+ It processes a single input and returns either an error message or the output. + Example usage:++ @+ let model = OpenAI { temperature = 0.7, model = "gpt-3.5-turbo" }+ result <- invoke model "Explain monads in simple terms."+ case result of+ Left err -> putStrLn $ "Error: " ++ err+ Right response -> putStrLn response+ @+ -}+ invoke :: r -> RunnableInput r -> IO (LangchainResult (RunnableOutput r))++ invokeM :: MonadIO m => r -> RunnableInput r -> m (LangchainResult (RunnableOutput r))+ invokeM runnable input = liftIO $ invoke runnable input++ batch :: r -> [RunnableInput r] -> IO (LangchainResult [RunnableOutput r])++ batchM :: MonadIO m => r -> [RunnableInput r] -> m (LangchainResult [RunnableOutput r])+ batchM runnable inputs = liftIO $ batch runnable inputs+ -- | Default implementation of batch that processes each input sequentially batch r inputs = do results <- mapM (invoke r) inputs return $ sequence results - -- | Stream results for components that support streaming.- --- -- This method is particularly useful for LLMs that can stream tokens as they're- -- generated, allowing for more responsive user interfaces.- --- -- The callback function is called with each piece of the output as it becomes available.- --- -- Example usage:- --- -- @- -- let model = OpenAI { temperature = 0.7, model = "gpt-3.5-turbo", streaming = True }- -- result <- stream model "Write a story about a programmer." $ \chunk -> do- -- putStr chunk- -- hFlush stdout- -- case result of- -- Left err -> putStrLn $ "\\nError: " ++ err- -- Right _ -> putStrLn "\\nStreaming completed successfully."- -- @- stream :: r -> RunnableInput r -> (RunnableOutput r -> IO ()) -> IO (Either String ())+ stream :: r -> RunnableInput r -> (RunnableOutput r -> IO ()) -> IO (LangchainResult ()) -- | Default implementation that invokes the runnable and then calls the callback with the full result stream r input callback = do@@ -135,3 +111,7 @@ Right output -> do callback output return $ Right ()++ streamM ::+ MonadIO m => r -> RunnableInput r -> (RunnableOutput r -> IO ()) -> m (LangchainResult ())+ streamM runnable input callback = liftIO $ stream runnable input callback
src/Langchain/Runnable/Utils.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} @@ -38,6 +39,7 @@ import Control.Concurrent import Data.Map.Strict as Map+import Langchain.Error (llmError) import Langchain.Runnable.Core {- | Wrapper for 'Runnable' components with configurable behavior.@@ -82,7 +84,7 @@ type RunnableInput (WithConfig config r) = RunnableInput r type RunnableOutput (WithConfig config r) = RunnableOutput r - invoke (WithConfig r1 _) input = invoke r1 input+ invoke (WithConfig r1 _) = invoke r1 {- | Cache results of a 'Runnable' to avoid duplicate computations. @@ -106,28 +108,6 @@ -- ^ Thread-safe cache storage } -{- | Create a new cached 'Runnable'.--This function initializes an empty cache and wraps the provided 'Runnable'-in a 'Cached' wrapper.--Example:--@-main = do- -- Create a cached LLM to avoid redundant API calls- let expensiveModel = OpenAI { model = "gpt-4", temperature = 0.7 }- cachedModel <- cached expensiveModel-- -- These will all use the same cached result for identical inputs- result1 <- invoke cachedModel "What is functional programming?"- result2 <- invoke cachedModel "What is functional programming?"- result3 <- invoke cachedModel "What is functional programming?"-- -- This will compute a new result- result4 <- invoke cachedModel "What is Haskell?"-@--} cached :: (Runnable r, Ord (RunnableInput r)) => r -> IO (Cached r) cached r = do cache <- newMVar Map.empty@@ -264,4 +244,4 @@ case result of Just r_ -> return r_- Nothing -> return $ Left "Operation timed out"+ Nothing -> return $ Left (llmError "Operation timed out" Nothing Nothing)
src/Langchain/TextSplitter/Character.hs view
@@ -35,10 +35,11 @@ , splitText ) where -import Data.Text (Text)-import qualified Data.Text as T+import Data.Int (Int64)+import Data.Text.Lazy (Text)+import qualified Data.Text.Lazy as T -{- | Configuration for character-based text splitting +{- | Configuration for character-based text splitting Contains: - 'chunkSize' : Maximum characters per chunk@@ -47,12 +48,12 @@ Default values follow LangChain's recommended settings for LLM input preparation. -} data CharacterSplitterOps = CharacterSplitterOps- { chunkSize :: Int+ { chunkSize :: Int64 , separator :: Text } deriving (Show, Eq) -{- | Default splitter configuration +{- | Default splitter configuration - 100 character chunks - Splits on double newlines ("\n\n")
src/Langchain/Tool/Calculator.hs view
@@ -1,18 +1,52 @@-{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeFamilies #-} +{- |+Module : Langchain.Tool.Calculator+Description : Mathematical expression calculator tool for LangChain Haskell+Copyright : (c) 2025 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++This module provides a calculator tool that can be used with LangChain agents to perform+arithmetic operations. It parses and evaluates mathematical expressions including:++* Basic arithmetic: addition (+), subtraction (-), multiplication (*), division (/)+* Exponentiation (^)+* Parentheses for grouping+* Floating-point numbers++The calculator uses a parser combinator approach to handle operator precedence correctly.++Example usage:++@+import Langchain.Tool.Calculator+import Langchain.Tool.Core (runTool)++main :: IO ()+main = do+ let calc = CalculatorTool+ result <- runTool calc "2 + 3 * 4"+ case result of+ Left err -> putStrLn $ "Error: " ++ err+ Right value -> putStrLn $ "Result: " ++ show value+ -- Output: Result: 14.0+@+-} module Langchain.Tool.Calculator- ( CalculatorTool(..)+ ( CalculatorTool (..) , Expr (..) , parseExpression , evaluateExpression ) where +import Control.Monad (void) import Data.Text (Text) import qualified Data.Text as T+import Langchain.Tool.Core (Tool (..)) import Text.ParserCombinators.Parsec-import Langchain.Tool.Core (Tool(..))-import Control.Monad (void) -- | Expression data type for our calculator data Expr@@ -31,12 +65,13 @@ instance Tool CalculatorTool where type Input CalculatorTool = Text type Output CalculatorTool = Either String Double- + toolName _ = "calculator"- - toolDescription _ = "A calculator tool that can perform basic arithmetic operations. " <>- "Input should be a mathematical expression like '2 + 3 * 4'."- ++ toolDescription _ =+ "A calculator tool that can perform basic arithmetic operations. "+ <> "Input should be a mathematical expression like '2 + 3 * 4'."+ runTool _ input = do case parseExpression input of Left err -> return $ Left $ "Failed to parse expression: " ++ show err@@ -52,50 +87,52 @@ left <- mulDivExpr rest left where- rest left = - (do- void $ char '+' <* spaces- right <- mulDivExpr- rest (Add left right))- <|> - (do- void $ char '-' <* spaces- right <- mulDivExpr- rest (Sub left right))- <|> return left+ rest left =+ ( do+ void $ char '+' <* spaces+ right <- mulDivExpr+ rest (Add left right)+ )+ <|> ( do+ void $ char '-' <* spaces+ right <- mulDivExpr+ rest (Sub left right)+ )+ <|> return left mulDivExpr = do left <- powExpr rest left where- rest left = - (do- void $ char '*' <* spaces- right <- powExpr- rest (Mul left right))- <|> - (do- void $ char '/' <* spaces- right <- powExpr- rest (Div left right))- <|> return left+ rest left =+ ( do+ void $ char '*' <* spaces+ right <- powExpr+ rest (Mul left right)+ )+ <|> ( do+ void $ char '/' <* spaces+ right <- powExpr+ rest (Div left right)+ )+ <|> return left powExpr = do left <- factor rest left where- rest left = - (do- void $ char '^' <* spaces- right <- factor- rest (Pow left right))- <|> return left+ rest left =+ ( do+ void $ char '^' <* spaces+ right <- factor+ rest (Pow left right)+ )+ <|> return left - factor = + factor = (Number_ . read <$> numberStr)- <|> - (spaces *> char '(' *> spaces *> expr <* spaces <* char ')' <* spaces)- + <|> (spaces *> char '(' *> spaces *> expr <* spaces <* char ')' <* spaces)+ numberStr = do i <- many1 digit d <- option "" $ (:) <$> char '.' <*> many1 digit
src/Langchain/Tool/Core.hs view
@@ -1,10 +1,11 @@+{-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} -{- | Module : Langchain.VectorStore.InMemory-Description : In-memory vector store implementation for LangChain Haskell+{- | Module : Langchain.Tool.Core+Description : Core Tool typeclass for LangChain Haskell Copyright : (c) 2025 Tushar Adhatrao License : MIT Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>@@ -32,6 +33,7 @@ ( Tool (..) ) where +import Control.Monad.IO.Class (MonadIO, liftIO) import Data.Text (Text) {- | Typeclass defining the interface for tools that can be used with LLMs.@@ -47,40 +49,49 @@ while the IO monad accommodates both pure and effectful implementations. -} class Tool a where- -- | Input type required by the tool- --- -- Example: For a weather lookup tool, this might be 'LocationCoordinates'+ {- | Input type required by the tool++ Example: For a weather lookup tool, this might be 'LocationCoordinates'+ -} type Input a - -- | Output type produced by the tool- --- -- Example: For a calculator tool, this could be 'Int' or 'Double'+ {- | Output type produced by the tool++ Example: For a calculator tool, this could be 'Int' or 'Double'+ -} type Output a - -- | Get the tool's unique identifier- --- -- >>> toolName (undefined :: Calculator)- -- "calculator"+ {- | Get the tool's unique identifier++ >>> toolName (undefined :: Calculator)+ "calculator"+ -} toolName :: a -> Text - -- | Get human-readable description of the tool's purpose- --- -- >>> toolDescription (undefined :: Calculator)- -- "Performs arithmetic operations on two integers"+ {- | Get human-readable description of the tool's purpose++ >>> toolDescription (undefined :: Calculator)+ "Performs arithmetic operations on two integers"+ -} toolDescription :: a -> Text - -- | Execute the tool with given input- --- -- This function bridges the gap between LLM abstractions and concrete- -- implementations. The IO context allows for:- --- -- * Pure computations (via 'pure')- -- * External API calls- -- * Database queries- --- -- Example implementation:- --- -- > runTool _ (a, b) = do- -- > putStrLn "Calculating..."- -- > pure (a + b)+ {- | Execute the tool with given input++ This function bridges the gap between LLM abstractions and concrete+ implementations. The IO context allows for:++ * Pure computations (via 'pure')+ * External API calls+ * Database queries++ Example implementation:++ > runTool _ (a, b) = do+ > putStrLn "Calculating..."+ > pure (a + b)+ -} runTool :: a -> Input a -> IO (Output a)++ -- | MonadIO version of runTool+ runToolM :: MonadIO m => a -> Input a -> m (Output a)+ runToolM tool toolInput = liftIO $ runTool tool toolInput
+ src/Langchain/Tool/DuckDuckGo.hs view
@@ -0,0 +1,266 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeFamilies #-}++{- |+Module : Langchain.Tool.DuckDuckGo+Description : Tool for extracting DuckDuckGo search content+Copyright : (c) 2025 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++Please note: DuckDuckGo Tool only returns result if the search term has a abstract card+-}+module Langchain.Tool.DuckDuckGo (DuckDuckGo (..)) where++import Control.Exception (SomeException, catch)+import Data.Aeson+import Data.Maybe+import Data.Text (Text)+import qualified Data.Text as T+import GHC.Generics (Generic)+import Langchain.Tool.Core+import Network.HTTP.Simple++-- | Icon data within related topics+newtype Icon = Icon+ { iconURL :: Maybe Text+ }+ deriving (Show, Eq, Generic)++instance FromJSON Icon where+ parseJSON = withObject "Icon" $ \v ->+ Icon+ <$> v .:? "URL"++-- | A single related topic+data RelatedTopic = RelatedTopic+ { topicFirstURL :: Maybe Text+ , topicIcon :: Maybe Icon+ , topicResult :: Maybe Text+ , topicText :: Maybe Text+ , topicName :: Maybe Text+ , topicTopics :: Maybe [RelatedTopic]+ }+ deriving (Show, Eq, Generic)++instance FromJSON RelatedTopic where+ parseJSON = withObject "RelatedTopic" $ \v ->+ RelatedTopic+ <$> v .:? "FirstURL"+ <*> v .:? "Icon"+ <*> v .:? "Result"+ <*> v .:? "Text"+ <*> v .:? "Name"+ <*> v .:? "Topics"++-- | Meta information about the source+data MetaDeveloper = MetaDeveloper+ { devName :: Text+ , devURL :: Text+ }+ deriving (Show, Eq, Generic)++instance FromJSON MetaDeveloper where+ parseJSON = withObject "MetaDeveloper" $ \v ->+ MetaDeveloper+ <$> v .: "name"+ <*> v .: "url"++-- | Source options within meta information+data MetaSrcOptions = MetaSrcOptions+ { isMediaWiki :: Maybe Int+ , isWikipedia :: Maybe Int+ , language :: Maybe Text+ }+ deriving (Show, Eq, Generic)++instance FromJSON MetaSrcOptions where+ parseJSON = withObject "MetaSrcOptions" $ \v ->+ MetaSrcOptions+ <$> v .:? "is_mediawiki"+ <*> v .:? "is_wikipedia"+ <*> v .:? "language"++-- | Meta information about the response+data Meta = Meta+ { metaDescription :: Maybe Text+ , metaDeveloper :: Maybe [MetaDeveloper]+ , metaName :: Maybe Text+ , metaPerlModule :: Maybe Text+ , metaSrcDomain :: Maybe Text+ , metaSrcName :: Maybe Text+ , metaSrcOptions :: Maybe MetaSrcOptions+ }+ deriving (Show, Eq, Generic)++instance FromJSON Meta where+ parseJSON = withObject "Meta" $ \v ->+ Meta+ <$> v .:? "description"+ <*> v .:? "developer"+ <*> v .:? "name"+ <*> v .:? "perl_module"+ <*> v .:? "src_domain"+ <*> v .:? "src_name"+ <*> v .:? "src_options"++-- | DuckDuckGo API response+data DuckDuckGoResponse = DuckDuckGoResponse+ { abstract :: Text+ , abstractSource :: Text+ , abstractText :: Text+ , abstractURL :: Text+ , answer :: Text+ , answerType :: Text+ , definition :: Text+ , definitionSource :: Text+ , definitionURL :: Text+ , entity :: Text+ , heading :: Text+ , image :: Text+ , imageHeight :: Int+ , imageIsLogo :: Int+ , imageWidth :: Int+ , infobox :: Text+ , redirect :: Text+ , relatedTopics :: [RelatedTopic]+ , results :: [Value]+ , resultType :: Text -- Called "Type" in the API+ , meta :: Maybe Meta+ }+ deriving (Show, Eq, Generic)++instance FromJSON DuckDuckGoResponse where+ parseJSON = withObject "DuckDuckGoResponse" $ \v ->+ DuckDuckGoResponse+ <$> v .: "Abstract"+ <*> v .: "AbstractSource"+ <*> v .: "AbstractText"+ <*> v .: "AbstractURL"+ <*> v .: "Answer"+ <*> v .: "AnswerType"+ <*> v .: "Definition"+ <*> v .: "DefinitionSource"+ <*> v .: "DefinitionURL"+ <*> v .: "Entity"+ <*> v .: "Heading"+ <*> v .: "Image"+ <*> v .: "ImageHeight"+ <*> v .: "ImageIsLogo"+ <*> v .: "ImageWidth"+ <*> v .: "Infobox"+ <*> v .: "Redirect"+ <*> v .: "RelatedTopics"+ <*> v .: "Results"+ <*> v .: "Type"+ <*> v .:? "meta"++{-+-- | Error type for DuckDuckGo API calls+data DuckDuckGoError+ = NetworkError Text+ | ParseError Text+ | OtherError Text+ deriving (Show, Eq, Generic)++instance ToJSON DuckDuckGoError where+ toJSON (NetworkError msg) = object ["type" .= ("network" :: Text), "message" .= msg]+ toJSON (ParseError msg) = object ["type" .= ("parse" :: Text), "message" .= msg]+ toJSON (OtherError msg) = object ["type" .= ("other" :: Text), "message" .= msg]+ -}++-- | Query parameter for DuckDuckGo search+newtype DuckDuckGoQuery = DuckDuckGoQuery+ { query :: Text+ }+ deriving (Show, Eq, Generic)++instance ToJSON DuckDuckGoQuery where+ toJSON q = object ["query" .= query q]++-- | The DuckDuckGo tool data type+data DuckDuckGo = DuckDuckGo+ deriving (Show, Eq)++-- | Tool instance for DuckDuckGo+instance Tool DuckDuckGo where+ type Input DuckDuckGo = Text+ type Output DuckDuckGo = Text++ toolName _ = "duckduckgo"++ toolDescription _ =+ "Performs web searches using DuckDuckGo and returns structured information about results"++ runTool _ queryData = do+ let searchTerm = T.replace " " "+" (T.strip queryData)+ let urlString =+ "https://duckduckgo.com/?q="+ <> T.unpack searchTerm+ <> "&format=json"+ eResult <-+ ( do+ request <- parseRequest urlString+ response <- httpLbs request+ let body = getResponseBody response+ case eitherDecode body of+ Left err -> pure $ Left $ T.pack $ show err+ Right ddgResponse_ -> pure $ Right ddgResponse_+ )+ `catch` \e -> pure $ Left $ T.pack $ show (e :: SomeException)+ case eResult of+ Left err -> pure err+ Right r -> pure $ ddgToText r++-- | Converts a DuckDuckGoResponse into a concise textual summary suitable for LLM input.+ddgToText :: DuckDuckGoResponse -> Text+ddgToText resp =+ T.intercalate "\n\n" $+ catMaybes+ [ Just ("# " <> heading resp)+ , abstractSection resp+ , answerSection resp+ , definitionSection resp+ , relatedTopicsSection (relatedTopics resp)+ ]++abstractSection :: DuckDuckGoResponse -> Maybe Text+abstractSection resp = do+ abst <- if T.null (abstract resp) then Nothing else Just (abstract resp)+ url <- if T.null (abstractURL resp) then Nothing else Just (abstractURL resp)+ Just $ "Abstract: " <> abst <> "\nSource: " <> url++answerSection :: DuckDuckGoResponse -> Maybe Text+answerSection resp =+ if T.null (answer resp)+ then Nothing+ else Just ("Answer: " <> answer resp)++definitionSection :: DuckDuckGoResponse -> Maybe Text+definitionSection resp = do+ def <- if T.null (definition resp) then Nothing else Just (definition resp)+ url <-+ if T.null (definitionURL resp)+ then+ Nothing+ else Just (definitionURL resp)+ Just $ "Definition: " <> def <> "\nSource: " <> url++relatedTopicsSection :: [RelatedTopic] -> Maybe Text+relatedTopicsSection rts =+ let processed = concatMap processRelatedTopic rts+ in if null processed then Nothing else Just (T.unlines processed)++processRelatedTopic :: RelatedTopic -> [Text]+processRelatedTopic rt =+ case (topicName rt, topicTopics rt) of+ -- Handle categorized group+ (Just name, Just subtopics) ->+ ("*" <> name <> "*") : concatMap processRelatedTopic subtopics+ -- Handle individual topic+ _ ->+ case (topicText rt, topicFirstURL rt) of+ (Just text, Just url) -> ["- [" <> text <> "](" <> url <> ")"]+ _ -> []
src/Langchain/Tool/Utils.hs view
@@ -2,13 +2,14 @@ {- | Module : Langchain.Tool.Utils-Description : Tool for scrapping text content from URL+Description : Common utility functions for LangChain tool modules Copyright : (c) 2025 Tushar Adhatrao License : MIT Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com> Stability : experimental -Common utility functions for Tool modules+This module provides utility functions used by various tool implementations,+particularly for HTML content processing and cleaning operations. -} module Langchain.Tool.Utils (cleanBodyContent, cleanHtmlContent) where
src/Langchain/Tool/WebScraper.hs view
@@ -71,7 +71,7 @@ case eResp of Left err -> pure $ Left (show err) Right r -> do- let rBody = (getResponseBody r)+ let rBody = getResponseBody r let htmlContent = TE.decodeUtf8 $ LBS.toStrict rBody -- Clean and extract the content
src/Langchain/Tool/WikipediaTool.hs view
@@ -41,8 +41,8 @@ import GHC.Generics import Langchain.Runnable.Core (Runnable (..)) import Langchain.Tool.Core-import Network.HTTP.Simple import Langchain.Tool.Utils (cleanHtmlContent)+import Network.HTTP.Simple {- | Wikipedia search tool configuration@@ -123,7 +123,7 @@ -- "A wrapper around Wikipedia. Useful for answering..." -- toolDescription _ =- "A wrapper around Wikipedia. Useful for answering general questions about people, places, companies, facts, historical events, or other subjects. Input should be a search query."+ "A wrapper around Wikipedia. Useful for answering general questions about people, places, companies, facts, historical events, or other subjects. Input should be a single worded search query." -- \| -- Executes Wikipedia search and content retrieval.@@ -142,7 +142,7 @@ -- - JSON parsing errors -- - Missing page content --- runTool tool q = searchWiki tool q+ runTool = searchWiki -- | Perform a Wikipedia search and retrieve page extracts. searchWiki :: WikipediaTool -> Text -> IO Text@@ -153,7 +153,13 @@ else do let pageIds = map pageid (take (topK tool) (search query)) pages <- mapM (getPage tool) pageIds- let extracts = map (T.take (docMaxChars tool) . cleanHtmlContent . extract) pages+ let extracts =+ map+ ( T.take (docMaxChars tool)+ . cleanHtmlContent+ . extract+ )+ pages return $ T.intercalate "\n\n" extracts -- | Perform a search on Wikipedia.@@ -169,7 +175,10 @@ ] url = T.pack $- "https://" <> T.unpack (languageCode tool) <> ".wikipedia.org/w/api.php?" <> urlEncode params+ "https://"+ <> T.unpack (languageCode tool)+ <> ".wikipedia.org/w/api.php?"+ <> urlEncode params request <- parseRequest (T.unpack url) response <- httpLbs request let body = getResponseBody response@@ -189,7 +198,10 @@ ] url = T.pack $- "https://" <> T.unpack (languageCode tool) <> ".wikipedia.org/w/api.php?" <> urlEncode params+ "https://"+ <> T.unpack (languageCode tool)+ <> ".wikipedia.org/w/api.php?"+ <> urlEncode params request <- parseRequest (T.unpack url) response <- httpLbs request let body = getResponseBody response@@ -204,13 +216,13 @@ urlEncode = concatMap (\(k, v) -> k ++ "=" ++ v ++ "&") . M.toList -- | Data types for JSON parsing.-data SearchResponse = SearchResponse+newtype SearchResponse = SearchResponse { query :: SearchQuery } deriving (Show, Generic, FromJSON) -- | Type for list of search result-data SearchQuery = SearchQuery+newtype SearchQuery = SearchQuery { search :: [SearchResult] } deriving (Show)@@ -244,13 +256,13 @@ <*> v .: "timestamp" -- | Wikipedia response-data PageResponse = PageResponse+newtype PageResponse = PageResponse { query :: Pages } deriving (Generic, Eq, Show, FromJSON) -- | Collection of Wikipedia pages, where key is page id-data Pages = Pages+newtype Pages = Pages { pages :: Map String Page } deriving (Generic, Eq, Show, FromJSON)@@ -285,4 +297,4 @@ type RunnableOutput WikipediaTool = Text -- TODO: runTool should return an Either- invoke tool input = fmap Right $ runTool tool input+ invoke tool input = Right <$> runTool tool input
+ src/Langchain/Utils.hs view
@@ -0,0 +1,27 @@+{- |+Module : Langchain.Utils+Description : Utility functions for LangChain Haskell+Copyright : (c) 2025 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++This module provides utility functions used throughout the LangChain Haskell library.+-}+module Langchain.Utils (showText) where++import Data.Text (Text, pack)++{- | Convert any 'Show' instance to 'Text'+Convenience function for converting values to Text format.++Example:++>>> showText (42 :: Int)+"42"++>>> showText (True)+"True"+-}+showText :: Show a => a -> Text+showText = pack . show
src/Langchain/VectorStore/Core.hs view
@@ -31,9 +31,11 @@ module Langchain.VectorStore.Core (VectorStore (..)) where +import Control.Monad.IO.Class (MonadIO, liftIO) import Data.Int (Int64) import Data.Text (Text) import Langchain.DocumentLoader.Core+import Langchain.Error (LangchainResult) -- TODO: Add delete document mechanism, for this we need to generate and use id (Int) @@ -53,41 +55,57 @@ similaritySearch store query k = ... @ -}-class VectorStore m where- -- | Add documents to the vector store- --- -- Example:- --- -- >>> addDocuments myStore [Document "Test content" mempty]- -- Right (updatedStoreWithNewDocs)- addDocuments :: m -> [Document] -> IO (Either String m)+class VectorStore vs where+ {- | Add documents to the vector store - -- |- -- Requires document ID tracking to be implemented in store instances.- --- -- Example usage (when implemented):- --- -- >>> delete myStore [123]- -- Right (storeWithoutDoc123)- delete :: m -> [Int64] -> IO (Either String m)+ Example: - -- | Find documents similar to query text- -- Uses embedded vector representations for semantic search.- --- -- Example:- --- -- >>> similaritySearch store "Haskell monads" 3- -- Right [Document "Monads in FP...", ...]- similaritySearch :: m -> Text -> Int -> IO (Either String [Document])+ >>> addDocuments myStore [Document "Test content" mempty]+ Right (updatedStoreWithNewDocs)+ -}+ addDocuments :: vs -> [Document] -> IO (LangchainResult vs) - -- | Find documents similar to vector representation- -- For direct vector comparisons without text conversion.- --- -- Example:- --- -- >>> similaritySearchByVector store [0.1, 0.3, ...] 5- -- Right [mostSimilarDoc1, ...]- similaritySearchByVector :: m -> [Float] -> Int -> IO (Either String [Document])+ addDocumentsM :: MonadIO m => vs -> [Document] -> m (LangchainResult vs)+ addDocumentsM store docs = liftIO $ addDocuments store docs++ {- |+ Requires document ID tracking to be implemented in store instances.++ Example usage (when implemented):++ >>> delete myStore [123]+ Right (storeWithoutDoc123)+ -}+ delete :: vs -> [Int64] -> IO (LangchainResult vs)++ deleteM :: MonadIO m => vs -> [Int64] -> m (LangchainResult vs)+ deleteM store ids = liftIO $ delete store ids++ {- | Find documents similar to query text+ Uses embedded vector representations for semantic search.++ Example:++ >>> similaritySearch store "Haskell monads" 3+ Right [Document "Monads in FP...", ...]+ -}+ similaritySearch :: vs -> Text -> Int -> IO (LangchainResult [Document])++ similaritySearchM :: MonadIO m => vs -> Text -> Int -> m (LangchainResult [Document])+ similaritySearchM store query k = liftIO $ similaritySearch store query k++ {- | Find documents similar to vector representation+ For direct vector comparisons without text conversion.++ Example:++ >>> similaritySearchByVector store [0.1, 0.3, ...] 5+ Right [mostSimilarDoc1, ...]+ -}+ similaritySearchByVector :: vs -> [Float] -> Int -> IO (LangchainResult [Document])++ similaritySearchByVectorM :: MonadIO m => vs -> [Float] -> Int -> m (LangchainResult [Document])+ similaritySearchByVectorM store vector k = liftIO $ similaritySearchByVector store vector k {- $examples Test case patterns:
src/Langchain/VectorStore/InMemory.hs view
@@ -1,5 +1,3 @@-{-# LANGUAGE RecordWildCards #-}- {- | Module : Langchain.VectorStore.InMemory Description : In-memory vector store implementation for LangChain Haskell@@ -39,12 +37,14 @@ , cosineSimilarity ) where +import Data.Bifunctor import Data.Int (Int64) import Data.List (sortBy) import qualified Data.Map.Strict as Map import Data.Ord (comparing) import Langchain.DocumentLoader.Core (Document) import Langchain.Embeddings.Core+import Langchain.Error (LangchainError) import Langchain.VectorStore.Core {- | Compute dot product of two vectors@@ -89,7 +89,7 @@ >>> fromDocuments ollamaEmb [Document "Test" mempty] Right (InMemory {_store = ...}) -}-fromDocuments :: Embeddings m => m -> [Document] -> IO (Either String (InMemory m))+fromDocuments :: Embeddings m => m -> [Document] -> IO (Either LangchainError (InMemory m)) fromDocuments model docs = do let vs = emptyInMemoryVectorStore model addDocuments vs docs@@ -119,8 +119,12 @@ Left err -> pure $ Left err Right floats -> do let currStore = store inMem- mbMaxKey = (Map.lookupMax currStore)- newStore = Map.fromList $ zip [(maybe 1 (\x -> fst x + 1) mbMaxKey) ..] (zip docs floats)+ mbMaxKey = Map.lookupMax currStore+ newStore =+ Map.fromList $+ zip+ [(maybe 1 (\x -> fst x + 1) mbMaxKey) ..]+ (zip docs floats) newInMem = inMem {store = Map.union newStore currStore} pure $ Right newInMem @@ -132,7 +136,7 @@ -- delete inMem ids = do let currStore = store inMem- newStore = foldl (\acc i -> Map.delete i acc) currStore ids+ newStore = foldl (flip Map.delete) currStore ids newInMem = inMem {store = newStore} pure $ Right newInMem @@ -159,9 +163,10 @@ similaritySearchByVector vs queryVec k = do let similarities = map- (\(doc, vec) -> (doc, cosineSimilarity queryVec vec))- (map snd $ Map.toList $ store vs)- sorted = sortBy (comparing (negate . snd)) similarities -- Sort in descending order+ (second (cosineSimilarity queryVec) . snd)+ (Map.toList $ store vs)+ sorted = sortBy (comparing (negate . snd)) similarities+ -- Sort in descending order topK = take k sorted return $ Right $ map fst topK
test/Spec.hs view
@@ -1,4 +1,5 @@-import qualified Test.Langchain.Agent.Core as AgentTest+import qualified Test.Langchain.Agent.ReAct as ReActTest+ -- import qualified Test.Langchain.Agent.ReactAgent as ReactAgentTest import qualified Test.Langchain.DocumentLoader.Core as DocumentLoaderTest import qualified Test.Langchain.DocumentLoader.DirectoryLoader as DirectoryLoaderTest@@ -6,6 +7,7 @@ import qualified Test.Langchain.LLM.Core as LLMCoreTest import qualified Test.Langchain.LLM.Ollama as OllamaLLMTest import qualified Test.Langchain.Memory.Core as MemoryTest+import qualified Test.Langchain.Memory.TokenBufferMemory as TokenBufferMemoryTest import qualified Test.Langchain.OutputParser.Core as OutputParserTest import qualified Test.Langchain.PromptTemplate as PromptTemplateTest import qualified Test.Langchain.Retriever.Core as RetrieverTest@@ -16,7 +18,6 @@ import qualified Test.Langchain.TextSplitter.Character as TextSplitterTest import qualified Test.Langchain.Tool.Core as ToolTest import qualified Test.Langchain.VectorStore.Core as VectorStoreTest-import qualified Test.Langchain.Memory.TokenBufferMemory as TokenBufferMemoryTest import Test.Tasty main :: IO ()@@ -36,8 +37,7 @@ , EmbeddingsTest.tests , RetrieverTest.tests , ToolTest.tests- , AgentTest.tests- -- , ReactAgentTest.tests+ , ReActTest.tests , RunnableTest.tests , RunnableUtilsTest.tests , RunnableChainsTest.tests
− test/Test/Langchain/Agent/Core.hs
@@ -1,123 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications #-}-{-# LANGUAGE TypeFamilies #-}--module Test.Langchain.Agent.Core (tests) where--import Control.Exception (throwIO)-import Data.IORef (IORef, newIORef, readIORef, writeIORef)-import qualified Data.List.NonEmpty as NE-import qualified Data.Map.Strict as Map-import Data.Text (Text, isInfixOf, pack)-import Langchain.Agents.Core-import Langchain.LLM.Core-import Langchain.Memory.Core (BaseMemory (..))-import Langchain.PromptTemplate-import Langchain.Tool.Core (Tool (..))-import Test.Tasty (TestTree, testGroup)-import Test.Tasty.HUnit (assertBool, assertEqual, testCase)--data DummyTool = DummyTool deriving (Show)--instance Tool DummyTool where- type Input DummyTool = Text- type Output DummyTool = Text- toolName _ = "dummy-tool"- toolDescription _ = "dummy tool description"- runTool _ input = return $ "Processed: " <> input--data FaultyTool = FaultyTool deriving (Show)--instance Tool FaultyTool where- type Input FaultyTool = Text- type Output FaultyTool = Text- toolName _ = "faulty-tool"- toolDescription _ = "fulty tool description"- runTool _ _ = throwIO $ userError "Intentional tool error"--data StepSequenceAgent = StepSequenceAgent (IORef [AgentStep]) [AnyTool]--instance Agent StepSequenceAgent where- planNextAction (StepSequenceAgent ref _) _ = do- steps <- readIORef ref- case steps of- [] -> return $ Left "No steps left"- (step : rest) -> do- writeIORef ref rest- return $ Right step- agentTools (StepSequenceAgent _ tools) = return tools- agentPrompt _ = return $ PromptTemplate "test prompt"---- Test Memory Implementation--data TestMemory = TestMemory [Message]--instance BaseMemory TestMemory where- addMessage (TestMemory msgs) newMsg = return $ Right $ TestMemory (msgs ++ [newMsg])- addUserMessage (TestMemory msgs) input = do- let userMsg = Message User input defaultMessageData- return $ Right $ TestMemory (msgs ++ [userMsg])- addAiMessage (TestMemory msgs) input = do- let aiMsg = Message System input defaultMessageData- return $ Right $ TestMemory (msgs ++ [aiMsg])- messages (TestMemory msgs) = return $ Right $ NE.fromList msgs- clear _ = return $ Right (TestMemory [])--tests :: TestTree-tests =- testGroup- "Agent Tests"- [ testCase "executeTool valid tool" $ do- let dummyAnyTool = customAnyTool DummyTool id id- tools = [dummyAnyTool]- result <- executeTool tools "dummy-tool" "test input"- assertEqual "Should process input" (Right "Processed: test input") result- , testCase "executeTool tool not found" $ do- let tools = []- result <- executeTool tools "unknown-tool" "input"- assertEqual "Should return tool not found error" (Left "Tool not found: unknown-tool") result- , testCase "executeTool tool throws exception" $ do- let faultyAnyTool = customAnyTool FaultyTool id id- tools = [faultyAnyTool]- result <- executeTool tools "faulty-tool" "input"- assertBool- "Should return execution error"- ("Intentional tool error" `isInfixOf` (pack $ fromLeft "" result))- , testCase "runAgentLoop max iterations exceeded" $ do- agentRef <- newIORef []- let agent = StepSequenceAgent agentRef []- initialState = AgentState (TestMemory []) [] []- result <- runAgentLoop agent initialState 10 5- assertEqual "Should return max iteration error" (Left "Max iterations excedded") result- , testCase "runAgent immediate finish" $ do- agentRef <- newIORef [Finish (AgentFinish (Map.singleton "result" "success") "Finished")]- let agent = StepSequenceAgent agentRef []- initialState = AgentState (TestMemory []) [] []- result <- runAgent agent initialState "input"- assertEqual- "Should return finish result"- (Right (AgentFinish (Map.singleton "result" "success") "Finished"))- result- , testCase "runAgentLoop continue then finish" $ do- agentRef <-- newIORef- [ Continue (AgentAction "dummy-tool" "input" "log")- , Finish (AgentFinish Map.empty "Done")- ]- let dummyAnyTool = customAnyTool DummyTool id id- agent = StepSequenceAgent agentRef [dummyAnyTool]- initialState = AgentState (TestMemory []) [] []- result <- runAgentLoop agent initialState 0 10- assertEqual "Should finish after one step" (Right (AgentFinish Map.empty "Done")) result- , testCase "customAnyTool wraps correctly" $ do- let tool = customAnyTool DummyTool id id- input = "test"- expectedOutput = "Processed: test"- result <- executeTool [tool] "dummy-tool" input- assertEqual "Should apply conversions" (Right expectedOutput) result- ]- where- fromLeft :: a -> Either a b -> a- fromLeft _ (Left x) = x- fromLeft def _ = def
+ test/Test/Langchain/Agent/ReAct.hs view
@@ -0,0 +1,210 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeFamilies #-}++module Test.Langchain.Agent.ReAct (tests) where++import Data.Aeson (object, (.=))+import qualified Data.List.NonEmpty as NE+import qualified Data.Map as Map+import Data.Text (Text)+import Langchain.Agent.Core+import Langchain.Agent.ReAct+import Langchain.Error (LangchainError, llmError)+import Langchain.LLM.Core+import Langchain.Memory.Core (BaseMemory (..), WindowBufferMemory (..))+import Langchain.Tool.Core+import Test.Tasty+import Test.Tasty.HUnit++-- Mock LLM for testing+newtype MockLLM = MockLLM+ { mockResponse :: Either LangchainError Message+ }++instance LLM MockLLM where+ type LLMParams MockLLM = ()+ type LLMStreamTokenType MockLLM = Text++ generate _ _ _ = pure $ Left $ llmError "Not implemented" Nothing Nothing++ chat llm _ _ = pure $ mockResponse llm++ stream _ _ _ _ = pure $ Left $ llmError "Not implemented" Nothing Nothing++-- Mock Tool for testing+newtype MockTool = MockTool Text+ deriving (Show, Eq)++instance Tool MockTool where+ type Input MockTool = ToolCall+ type Output MockTool = Text++ toolName (MockTool toolName_) = toolName_+ toolDescription _ = "A mock tool for testing"+ runTool _ tc = pure $ "Executed: " <> toolFunctionName (toolCallFunction tc)++tests :: TestTree+tests =+ testGroup+ "Agent.ReAct"+ [ testPlanReturnsFinishWhenNoToolCalls+ , testPlanReturnsActionWhenToolCallsPresent+ , testPlanPropagatesLLMError+ , testExecuteToolFindsCorrectTool+ , testExecuteToolReturnsErrorWhenToolNotFound+ , testInitializeSetsUpStateCorrectly+ ]++-- Test that plan returns AgentFinish when LLM returns no tool calls+testPlanReturnsFinishWhenNoToolCalls :: TestTree+testPlanReturnsFinishWhenNoToolCalls = testCase "plan returns AgentFinish when no tool calls" $ do+ let mockMsg = Message Assistant "Final answer" defaultMessageData+ mockLLM = MockLLM (Right mockMsg)+ agent = createReActAgent mockLLM Nothing []+ testMemory = WindowBufferMemory 10 (NE.fromList [defaultMessage {content = "test"}])+ state =+ AgentState+ { agentMemory = SomeMemory testMemory+ , agentInput = "test input"+ , agentIterations = 0+ }++ result <- plan agent state+ case result of+ Right (Done finish) -> do+ assertEqual "Output should match content" "Final answer" (agentOutput finish)+ assertEqual "Log should match content" "Final answer" (finishLog finish)+ _ -> assertFailure $ "Expected Right (Right AgentFinish), got: " ++ show result++-- Test that plan returns AgentAction when LLM returns tool calls+testPlanReturnsActionWhenToolCallsPresent :: TestTree+testPlanReturnsActionWhenToolCallsPresent = testCase "plan returns AgentAction when tool calls present" $ do+ let toolCall =+ ToolCall+ { toolCallId = "call_123"+ , toolCallType = "function"+ , toolCallFunction =+ ToolFunction+ { toolFunctionName = "search"+ , toolFunctionArguments = Map.fromList [("query", object ["text" .= ("test" :: Text)])]+ }+ }+ msgData = defaultMessageData {toolCalls = Just [toolCall]}+ mockMsg = Message Assistant "Let me search" msgData+ mockLLM = MockLLM (Right mockMsg)+ agent = createReActAgent mockLLM Nothing []+ testMemory = WindowBufferMemory 10 (NE.fromList [defaultMessage {content = "test"}])+ state =+ AgentState+ { agentMemory = SomeMemory testMemory+ , agentInput = "test input"+ , agentIterations = 0+ }++ result <- plan agent state+ case result of+ Right (Continue action) -> do+ assertEqual "Should have one tool call" 1 (length $ actionToolCall action)+ assertEqual "Log should match content" "Let me search" (actionLog action)+ _ -> assertFailure $ "Expected Right (Left AgentAction), got: " ++ show result++-- Test that plan propagates LLM errors+testPlanPropagatesLLMError :: TestTree+testPlanPropagatesLLMError = testCase "plan propagates LLM error" $ do+ let mockError = llmError "LLM failed" Nothing Nothing+ mockLLM = MockLLM (Left mockError)+ agent = createReActAgent mockLLM Nothing []+ testMemory = WindowBufferMemory 10 (NE.fromList [defaultMessage {content = "test"}])+ state =+ AgentState+ { agentMemory = SomeMemory testMemory+ , agentInput = "test input"+ , agentIterations = 0+ }++ result <- plan agent state+ case result of+ Left _ -> pure () -- Expected error+ Right _ -> assertFailure "Expected Left error, got Right"++-- Test that executeTool finds and executes the correct tool+testExecuteToolFindsCorrectTool :: TestTree+testExecuteToolFindsCorrectTool = testCase "executeTool finds and executes correct tool" $ do+ let tool1 = ToolAcceptingToolCall (MockTool "tool1")+ tool2 = ToolAcceptingToolCall (MockTool "tool2")+ mockLLM = MockLLM (Right defaultMessage)+ agent = createReActAgent mockLLM Nothing [tool1, tool2]+ toolCall =+ ToolCall+ { toolCallId = "call_123"+ , toolCallType = "function"+ , toolCallFunction =+ ToolFunction+ { toolFunctionName = "tool2"+ , toolFunctionArguments = Map.empty+ }+ }++ result <- executeTool agent toolCall+ case result of+ Right output -> do+ assertEqual "Should execute tool2" "Executed: tool2" output+ Left err -> assertFailure $ "Expected Right, got error: " ++ show err++-- Test that executeTool returns error when tool not found+testExecuteToolReturnsErrorWhenToolNotFound :: TestTree+testExecuteToolReturnsErrorWhenToolNotFound = testCase "executeTool returns error when tool not found" $ do+ let tool1 = ToolAcceptingToolCall (MockTool "tool1")+ mockLLM = MockLLM (Right defaultMessage)+ agent = createReActAgent mockLLM Nothing [tool1]+ toolCall =+ ToolCall+ { toolCallId = "call_123"+ , toolCallType = "function"+ , toolCallFunction =+ ToolFunction+ { toolFunctionName = "nonexistent"+ , toolFunctionArguments = Map.empty+ }+ }++ result <- executeTool agent toolCall+ case result of+ Left _ -> pure () -- Expected error+ Right _ -> assertFailure "Expected error for nonexistent tool"++-- Test that initialize sets up state correctly+testInitializeSetsUpStateCorrectly :: TestTree+testInitializeSetsUpStateCorrectly = testCase "initialize sets up state correctly" $ do+ let mockLLM = MockLLM (Right defaultMessage)+ agent = createReActAgent mockLLM Nothing []+ testMemory = WindowBufferMemory 10 (NE.fromList [defaultMessage])+ inputState =+ AgentState+ { agentMemory = SomeMemory testMemory+ , agentInput = "What is 2+2?"+ , agentIterations = 0+ }++ result <- initialize agent inputState+ case result of+ Right newState -> do+ assertEqual "Input should be preserved" "What is 2+2?" (agentInput newState)+ assertEqual "Iterations should be 0" 0 (agentIterations newState)++ -- Check chat history has system message and user message by accessing memory+ case agentMemory newState of+ SomeMemory mem -> do+ eHistory <- messages mem+ case eHistory of+ Right history -> do+ let historyList = NE.toList history+ assertEqual "Should have 3 messages (initial + system + user)" 3 (length historyList)+ case reverse historyList of+ (userMsg : sysMsg : _) -> do+ assertEqual "Last message should be User" User (role userMsg)+ assertEqual "Second to last message should be System" System (role sysMsg)+ assertEqual "User message content should match input" "What is 2+2?" (content userMsg)+ _ -> assertFailure "Expected at least 2 messages in history"+ Left err -> assertFailure $ "Failed to get messages from memory: " ++ show err+ Left err -> assertFailure $ "Expected Right, got error: " ++ show err
test/Test/Langchain/DocumentLoader/Core.hs view
@@ -6,6 +6,7 @@ import Data.Map (empty, fromList) import qualified Data.Map as Map import qualified Data.Text as T+import qualified Data.Text.Lazy as TL import System.FilePath ((</>)) import System.IO.Temp (withSystemTempDirectory) import Test.Tasty@@ -13,9 +14,10 @@ import Langchain.DocumentLoader.Core import Langchain.DocumentLoader.FileLoader+import Langchain.Utils (showText) createTestFile :: FilePath -> String -> IO ()-createTestFile path content = writeFile path content+createTestFile = writeFile withTestFile :: String -> (FilePath -> IO a) -> IO a withTestFile content action =@@ -37,7 +39,7 @@ , testCase "Document Monoid instance should have identity element" $ do let doc = Document "Content" (fromList [("key", String "value")]) doc <> mempty @?= doc- mempty <> doc @?= doc+ doc @?= doc pageContent mempty @?= "" metadata mempty @?= empty ]@@ -50,7 +52,7 @@ withTestFile "Test content for the file." $ \filePath -> do result <- load (FileLoader filePath) case result of- Left err -> assertFailure $ "Expected Right but got Left: " ++ err+ Left err -> assertFailure $ "Expected Right but got Left: " ++ show err Right docs@(doc : _) -> do length docs @?= 1 pageContent doc @?= "Test content for the file."@@ -62,13 +64,13 @@ Left err -> assertBool "Error message should mention file not found"- (T.isInfixOf "File not found" (T.pack err))+ (T.isInfixOf "File not found" (showText err)) Right _ -> assertFailure "Expected Left for non-existent file but got Right" , testCase "loadAndSplit should split content using defaultCharacterSplitterOps" $ withTestFile "Paragraph 1\n\nParagraph 2\n\nParagraph 3" $ \filePath -> do result <- loadAndSplit (FileLoader filePath) case result of- Left err -> assertFailure $ "Expected Right but got Left: " ++ err+ Left err -> assertFailure $ "Expected Right but got Left: " ++ show err Right chunks -> do chunks @?= ["Paragraph 1", "Paragraph 2", "Paragraph 3"] , testCase "loadAndSplit should return error for non-existent file" $ do@@ -77,13 +79,13 @@ Left err -> assertBool "Error message should mention file not found"- (T.isInfixOf "File not found" (T.pack err))+ (T.isInfixOf "File not found" (showText err)) Right _ -> assertFailure "Expected Left for non-existent file but got Right" , testCase "load should handle empty files" $ withTestFile "" $ \filePath -> do result <- load (FileLoader filePath) case result of- Left err -> assertFailure $ "Expected Right but got Left: " ++ err+ Left err -> assertFailure $ "Expected Right but got Left: " ++ show err Right docs@(doc : _) -> do length docs @?= 1 pageContent doc @?= ""@@ -92,10 +94,10 @@ withTestFile (concat $ replicate 1000 "Line of test content\n") $ \filePath -> do result <- load (FileLoader filePath) case result of- Left err -> assertFailure $ "Expected Right but got Left: " ++ err+ Left err -> assertFailure $ "Expected Right but got Left: " ++ show err Right docs@(doc : _) -> do length docs @?= 1- T.length (pageContent doc) @?= 21000 -- 21 chars * 1000+ T.length (TL.toStrict $ pageContent doc) @?= 21000 -- 21 chars * 1000 Right _ -> assertFailure "Document list is empty" ]
test/Test/Langchain/DocumentLoader/DirectoryLoader.hs view
@@ -16,12 +16,13 @@ import Langchain.DocumentLoader.Core import Langchain.DocumentLoader.DirectoryLoader+import Langchain.Error (toString) -- Helper Functions -- | Creates a single file with the specified content. createTestFile :: FilePath -> String -> IO ()-createTestFile path content = writeFile path content+createTestFile = writeFile -- | Creates multiple files in a directory with specified relative paths and contents. createTestFiles :: FilePath -> [(FilePath, String)] -> IO ()@@ -48,7 +49,7 @@ , testHiddenFilesExclusion , testMultithreading , testErrorHandling- , testLoadAndSplit+ -- , testLoadAndSplit ] -- Test Cases@@ -64,10 +65,20 @@ let loader = DirectoryLoader dir defaultDirectoryLoaderOptions result <- load loader case result of- Left err -> assertFailure $ "Expected Right but got Left: " ++ err+ Left err -> assertFailure $ "Expected Right but got Left: " ++ toString err Right docs -> do- let docMap = Map.fromList [(fromMaybe "" (getSource d), pageContent d) | d <- docs]- expectedMap = Map.fromList [(file1, "Content of file1"), (file2, "Content of file2")]+ let docMap =+ Map.fromList+ [ ( fromMaybe "" (getSource d)+ , pageContent d+ )+ | d <- docs+ ]+ expectedMap =+ Map.fromList+ [ (file1, "Content of file1")+ , (file2, "Content of file2")+ ] docMap @?= expectedMap -- | Tests recursive loading with different depth limits.@@ -80,7 +91,11 @@ , ("subdir1/file2.txt", "Content of file2") , ("subdir1/subsubdir/file3.txt", "Content of file3") ]- let allFiles = [dir </> "file1.txt", dir </> "subdir1/file2.txt", dir </> "subdir1/subsubdir/file3.txt"]+ let allFiles =+ [ dir </> "file1.txt"+ , dir </> "subdir1/file2.txt"+ , dir </> "subdir1/subsubdir/file3.txt"+ ] level0Files = [dir </> "file1.txt"] level1Files = [dir </> "file1.txt", dir </> "subdir1/file2.txt"] -- Unlimited recursion@@ -88,7 +103,7 @@ loader = DirectoryLoader dir opts result <- load loader case result of- Left err -> assertFailure $ "Expected Right but got Left: " ++ err+ Left err -> assertFailure $ "Expected Right but got Left: " ++ toString err Right docs -> do let sources = mapMaybe getSource docs sort sources @?= sort allFiles@@ -97,7 +112,7 @@ loader0 = DirectoryLoader dir opts0 result0 <- load loader0 case result0 of- Left err -> assertFailure $ "Expected Right but got Left: " ++ err+ Left err -> assertFailure $ "Expected Right but got Left: " ++ toString err Right docs -> do let sources = mapMaybe getSource docs sort sources @?= sort level0Files@@ -106,7 +121,7 @@ loader1 = DirectoryLoader dir opts1 result1 <- load loader1 case result1 of- Left err -> assertFailure $ "Expected Right but got Left: " ++ err+ Left err -> assertFailure $ "Expected Right but got Left: " ++ toString err Right docs -> do let sources = mapMaybe getSource docs sort sources @?= sort level1Files@@ -115,7 +130,7 @@ loader2 = DirectoryLoader dir opts2 result2 <- load loader2 case result2 of- Left err -> assertFailure $ "Expected Right but got Left: " ++ err+ Left err -> assertFailure $ "Expected Right but got Left: " ++ toString err Right docs -> do let sources = mapMaybe getSource docs sort sources @?= sort allFiles@@ -138,7 +153,7 @@ loader = DirectoryLoader dir opts result <- load loader case result of- Left err -> assertFailure $ "Expected Right but got Left: " ++ err+ Left err -> assertFailure $ "Expected Right but got Left: " ++ toString err Right docs -> do let sources = mapMaybe getSource docs sort sources @?= sort txtFiles@@ -147,7 +162,7 @@ loader2 = DirectoryLoader dir opts2 result2 <- load loader2 case result2 of- Left err -> assertFailure $ "Expected Right but got Left: " ++ err+ Left err -> assertFailure $ "Expected Right but got Left: " ++ toString err Right docs -> do let sources = mapMaybe getSource docs sort sources @?= sort txtMdFiles@@ -156,7 +171,7 @@ loader3 = DirectoryLoader dir opts3 result3 <- load loader3 case result3 of- Left err -> assertFailure $ "Expected Right but got Left: " ++ err+ Left err -> assertFailure $ "Expected Right but got Left: " ++ toString err Right docs -> do let sources = mapMaybe getSource docs sort sources @?= sort allFiles@@ -177,7 +192,7 @@ loader = DirectoryLoader dir opts result <- load loader case result of- Left err -> assertFailure $ "Expected Right but got Left: " ++ err+ Left err -> assertFailure $ "Expected Right but got Left: " ++ toString err Right docs -> do let sources = mapMaybe getSource docs sort sources @?= sort visibleFiles@@ -186,7 +201,7 @@ loader2 = DirectoryLoader dir opts2 result2 <- load loader2 case result2 of- Left err -> assertFailure $ "Expected Right but got Left: " ++ err+ Left err -> assertFailure $ "Expected Right but got Left: " ++ toString err Right docs -> do let sources = mapMaybe getSource docs sort sources @?= sort allFiles@@ -205,7 +220,7 @@ loader = DirectoryLoader dir opts result <- load loader case result of- Left err -> assertFailure $ "Expected Right but got Left: " ++ err+ Left err -> assertFailure $ "Expected Right but got Left: " ++ toString err Right docs -> do let sources = mapMaybe getSource docs sort sources @?= sort files@@ -216,10 +231,13 @@ testGroup "Error handling" [ testCase "Non-existent directory" $ do- let loader = DirectoryLoader "non-existent-dir" defaultDirectoryLoaderOptions+ let loader =+ DirectoryLoader+ "non-existent-dir"+ defaultDirectoryLoaderOptions result <- load loader case result of- Left err -> assertBool "Expected error message" (not $ null err)+ Left _ -> pure () Right _ -> assertFailure "Expected Left but got Right" , testCase "Path is a file" $ withSystemTempDirectory "test-dir-loader" $ \dir -> do@@ -228,11 +246,13 @@ let loader = DirectoryLoader filePath defaultDirectoryLoaderOptions result <- load loader case result of- Left err -> assertBool "Expected error message" (not $ null err)+ Left _ -> pure () Right _ -> assertFailure "Expected Left but got Right" ] -- | Tests the loadAndSplit function.++{- testLoadAndSplit :: TestTree testLoadAndSplit = testCase "loadAndSplit" $ withSystemTempDirectory "test-dir-loader" $ \dir -> do@@ -246,4 +266,5 @@ case result of Left err -> assertFailure $ "Expected Right but got Left: " ++ err Right chunks -> do- chunks @?= ["Paragraph 3","Paragraph 4Paragraph 1","Paragraph 2"]+ chunks @?= ["Paragraph 1","Paragraph 2Paragraph 3","Paragraph 4"]+ -}
test/Test/Langchain/Embeddings/Core.hs view
@@ -2,9 +2,10 @@ module Test.Langchain.Embeddings.Core (tests) where -import Data.Text (isInfixOf, pack)+import Data.Text (isInfixOf) import Langchain.Embeddings.Core import Langchain.Embeddings.Ollama+import Langchain.Utils (showText) import Test.Tasty import Test.Tasty.HUnit @@ -15,11 +16,14 @@ [ testGroup "embedQuery Tests" [ testCase "Propagates API errors" $ do- let embeddings = OllamaEmbeddings "error-model" Nothing Nothing+ let embeddings = OllamaEmbeddings "error-model" Nothing Nothing Nothing -- Assuming embeddingOps returns Left "API Failure" result <- embedQuery embeddings "error query" case result of- Left err -> assertBool "Error message contains 'error'" ("error" `isInfixOf` (pack err))+ Left err ->+ assertBool+ "Error message contains 'error'"+ ("error" `isInfixOf` showText err) Right _ -> assertFailure "Expected API error propagation" ] ]
test/Test/Langchain/LLM/Core.hs view
@@ -1,5 +1,4 @@ {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeFamilies #-} module Test.Langchain.LLM.Core (tests) where@@ -7,11 +6,13 @@ import Test.Tasty import Test.Tasty.HUnit -import Data.Aeson (Result (..), decode, encode, fromJSON, toJSON)+import Data.Aeson (Result (..), decode, fromJSON, toJSON)+import Data.Either import Data.List.NonEmpty (NonEmpty (..))+import Data.Maybe (fromMaybe) import Data.Text (Text)+import Langchain.Error (llmError) import Langchain.LLM.Core-import Data.Maybe (fromMaybe) data TestLLM = TestLLM { responseText :: Text@@ -20,18 +21,19 @@ instance LLM TestLLM where type LLMParams TestLLM = Text- + type LLMStreamTokenType TestLLM = Text+ generate m _ mbParams = pure $ if shouldSucceed m then Right (fromMaybe (responseText m) mbParams)- else Left "Test error"+ else Left (llmError "Test error" Nothing Nothing) chat m _ _ = pure $ if shouldSucceed m- then Right (responseText m)- else Left "Test error"+ then Right $ Message User (responseText m) defaultMessageData+ else Left (llmError "Test error" Nothing Nothing) stream m _ handler _ = do if shouldSucceed m@@ -39,14 +41,13 @@ onToken handler (responseText m) onComplete handler pure (Right ())- else pure (Left "Test error")+ else pure (Left (llmError "Test error" Nothing Nothing)) tests :: TestTree tests = testGroup "LLMCoreTest"- [ - testGroup+ [ testGroup "Role" [ testCase "has correct equality" $ do assertEqual "System equals System" System System@@ -89,37 +90,39 @@ let md = defaultMessageData assertEqual "name should be Nothing" Nothing (name md) assertEqual "toolCalls should be Nothing" Nothing (toolCalls md)- , testCase "serializes to correct JSON structure" $ do- let md = MessageData (Just "Alice") (Just ["tool1", "tool2"])- expected = "{\"name\":\"Alice\",\"tool_calls\":[\"tool1\",\"tool2\"]}"- assertEqual "JSON encoding of MessageData" expected (encode md)- , testCase "deserializes from JSON correctly" $ do- let json = "{\"name\":\"Bob\",\"tool_calls\":[\"tool3\"]}"- expected = MessageData (Just "Bob") (Just ["tool3"])- assertEqual "JSON decoding of MessageData" (Just expected) (decode json)- , testCase "handles partial JSON correctly" $ do+ , {-+ , testCase "serializes to correct JSON structure" $ do+ let md = MessageData (Just "Alice") (Just ["tool1", "tool2"])+ expected = "{\"name\":\"Alice\",\"tool_calls\":[\"tool1\",\"tool2\"]}"++ assertEqual "JSON encoding of MessageData" expected (encode md)++ , testCase "deserializes from JSON correctly" $ do+ let json = "{\"name\":\"Bob\",\"tool_calls\":[\"tool3\"]}"+ expected = MessageData (Just "Bob") (Just ["tool3"])+ assertEqual "JSON decoding of MessageData" (Just expected) (decode json)+ -}+ testCase "handles partial JSON correctly" $ do let json = "{\"name\":\"Charlie\"}"- expected = MessageData (Just "Charlie") Nothing+ expected = MessageData (Just "Charlie") Nothing Nothing Nothing assertEqual "Partial JSON decoding of MessageData" (Just expected) (decode json) ] , testGroup "LLM Typeclass" [ testGroup "generate"- [ - testCase "generate uses provided LLMParams" $ do- let testLLM = TestLLM { responseText = "Default", shouldSucceed = True }- result <- generate testLLM "Prompt" (Just "CustomParam")- assertEqual "Should return CustomParam" (Right "CustomParam") result-- , testCase "returns Right with response for successful generation" $ do+ [ testCase "generate uses provided LLMParams" $ do+ let testLLM = TestLLM {responseText = "Default", shouldSucceed = True}+ result <- generate testLLM "Prompt" (Just "CustomParam")+ assertEqual "Should return CustomParam" (Right "CustomParam") result+ , testCase "returns Right with response for successful generation" $ do let successLLM = TestLLM "Success response" True result <- generate successLLM "Test prompt" Nothing assertEqual "Successful generation" (Right "Success response") result , testCase "returns Left with error for failed generation" $ do let failureLLM = TestLLM "Failure response" False result <- generate failureLLM "Test prompt" Nothing- assertEqual "Failed generation" (Left "Test error") result+ assertEqual "Failed generation" (Left (llmError "Test error" Nothing Nothing)) result ] , testGroup "chat"@@ -128,13 +131,13 @@ singleMsg = Message User "Test prompt" defaultMessageData chatMsgs = singleMsg :| [] result <- chat successLLM chatMsgs Nothing- assertEqual "Successful chat" (Right "Success response") result+ assertBool "Successful chat" (isRight result) , testCase "returns Left with error for failed chat" $ do let failureLLM = TestLLM "Failure response" False singleMsg = Message User "Test prompt" defaultMessageData chatMsgs = singleMsg :| [] result <- chat failureLLM chatMsgs Nothing- assertEqual "Failed chat" (Left "Test error") result+ assertEqual "Failed chat" (Left (llmError "Test error" Nothing Nothing)) result ] , testGroup "stream"@@ -159,7 +162,7 @@ , onComplete = pure () } result <- stream failureLLM chatMsgs handler Nothing- assertEqual "Failed stream" (Left "Test error") result+ assertEqual "Failed stream" (Left (llmError "Test error" Nothing Nothing)) result ] ] , testGroup
test/Test/Langchain/LLM/Ollama.hs view
@@ -1,5 +1,4 @@ {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} module Test.Langchain.LLM.Ollama (tests) where@@ -13,13 +12,13 @@ import qualified Data.Text as T import qualified Data.Text.Encoding as T +import Data.Aeson+import qualified Data.ByteString.Lazy.Char8 as BSL+import qualified Data.Ollama.Chat as O import Langchain.Callback (Callback, Event (..)) import Langchain.LLM.Core import Langchain.LLM.Ollama import qualified Langchain.Runnable.Core as Run-import qualified Data.Ollama.Common.Types as O-import Data.Aeson-import qualified Data.ByteString.Lazy.Char8 as BSL captureEvents :: IO (Callback, IO [Event]) captureEvents = do@@ -29,7 +28,7 @@ return (callback, getEvents) testModelName :: Text-testModelName = "llama3.2:latest"+testModelName = "qwen3:0.6b" tests :: TestTree tests =@@ -44,7 +43,7 @@ let prompt = "What is functional programming?" result <- generate ollama prompt Nothing case result of- Left err -> assertFailure $ "Expected success, got error: " ++ err+ Left err -> assertFailure $ "Expected success, got error: " ++ show err Right response -> do assertBool "Non-empty response expected" (T.length response > 0) events <- getEvents@@ -58,128 +57,139 @@ result <- generate ollama prompt Nothing case result of Left err -> do- assertBool "Error should mention model" ("model" `T.isInfixOf` T.pack err)+ assertBool+ "Error should mention model"+ ("model" `T.isInfixOf` T.pack (show err)) events <- getEvents- assertBool "LLM should tried to be started" (events `shouldContainAll` [LLMStart])+ assertBool+ "LLM should tried to be started"+ (events `shouldContainAll` [LLMStart]) length (filter isErrorEvent events) @?= 1 Right _ -> assertFailure "Expected error, but got success" , testCase "chat returns text response for messages" $ do (callback, getEvents) <- captureEvents let ollama = Ollama testModelName [callback]- let messages = Message User "What's the capital of France?" defaultMessageData :| []+ let messages =+ Message+ User+ "What's the capital of France?"+ defaultMessageData+ :| [] result <- chat ollama messages Nothing case result of- Left err -> assertFailure $ "Expected success, got error: " ++ err+ Left err -> assertFailure $ "Expected success, got error: " ++ show err Right response -> do- assertBool "Response should mention Paris" ("paris" `T.isInfixOf` T.toLower response)+ assertBool+ "Response should mention Paris"+ ("paris" `T.isInfixOf` T.toLower (content response)) events <- getEvents- assertBool "LLM should be completed" (events `shouldContainAll` [LLMStart, LLMEnd])+ assertBool+ "LLM should be completed"+ (events `shouldContainAll` [LLMStart, LLMEnd]) , testCase "chat handles multi-turn conversations" $ do (callback, _) <- captureEvents let ollama = Ollama testModelName [callback] let messages = Message System "You are a helpful assistant." defaultMessageData- :| [ Message User "What's the capital of France?" defaultMessageData- , Message Assistant "The capital of France is Paris." defaultMessageData- , Message User "And what about Italy?" defaultMessageData+ :| [ Message+ User+ "What's the capital of France?"+ defaultMessageData+ , Message+ Assistant+ "The capital of France is Paris."+ defaultMessageData+ , Message+ User+ "And what about Italy?"+ defaultMessageData ] result <- chat ollama messages Nothing case result of- Left err -> assertFailure $ "Expected success, got error: " ++ err- Right response -> assertBool "Response should mention Rome" ("rome" `T.isInfixOf` T.toLower response)+ Left err -> assertFailure $ "Expected success, got error: " ++ show err+ Right response ->+ assertBool+ "Response should mention Rome"+ ("rome" `T.isInfixOf` T.toLower (content response)) , testCase "stream calls handlers for streaming responses" $ do let ollama = Ollama testModelName [] let messages = Message User "Count from 1 to 5 briefly." defaultMessageData :| [] tokensRef <- newIORef []- completedRef <- newIORef False let handler = StreamHandler { onToken = \token -> modifyIORef tokensRef (token :)- , onComplete = writeIORef completedRef True+ , onComplete = pure () }+ -- \| onComplete does not support Ollama result <- stream ollama messages handler Nothing case result of- Left err -> assertFailure $ "Expected success, got error: " ++ err+ Left err -> assertFailure $ "Expected success, got error: " ++ show err Right () -> do tokens <- readIORef tokensRef assertBool "Should receive tokens" (not (null tokens))- completed <- readIORef completedRef- completed @?= True , testCase "invoke calls chat with the input messages" $ do let ollama = Ollama testModelName [] let input = Message User "What is 2+2?" defaultMessageData :| [] result <- Run.invoke ollama (input, Nothing) case result of- Left err -> assertFailure $ "Expected success, got error: " ++ err- Right response -> assertBool "Should mention 4" ("4" `T.isInfixOf` T.toLower response)- {- llama3.2 does not support insert- , testCase "generate appends suffix when provided" $ do- (callback, getEvents) <- captureEvents- let ollama = Ollama testModelName [callback]- let prompt = "What is functional programming?"- let params = defaultOllamaParams { suffix = Just " [End]" }- result <- generate ollama prompt (Just params)- case result of- Left err -> assertFailure $ "Expected success, got error: " ++ err- Right response -> do- assertBool "Response should end with suffix" (T.isSuffixOf " [End]" response)- events <- getEvents- assertBool "should contain all events" (events `shouldContainAll` [LLMStart, LLMEnd])- -}- , testCase "generate uses system message for context" $ do+ Left err -> assertFailure $ "Expected success, got error: " ++ show err+ Right response ->+ assertBool+ "Should mention 4"+ ("4" `T.isInfixOf` T.toLower (content response))+ , {- qwen3:06b does not support insert+ , testCase "generate appends suffix when provided" $ do+ (callback, getEvents) <- captureEvents+ let ollama = Ollama testModelName [callback]+ let prompt = "What is functional programming?"+ result <- generate ollama prompt Nothing+ case result of+ Left err -> assertFailure $ "Expected success, got error: " ++ err+ Right response -> do+ assertBool "Response should end with suffix" (T.isSuffixOf " [End]" response)+ events <- getEvents+ assertBool "should contain all events"+ (events `shouldContainAll` [LLMStart, LLMEnd])+ -}++ testCase "generate uses system message for context" $ do (callback, getEvents) <- captureEvents let ollama = Ollama testModelName [callback]- let prompt = "Explain monads."- let systemMsg = "You are a Haskell expert."- let params = defaultOllamaParams { system = Just systemMsg }- result <- generate ollama prompt (Just params)+ let prompt = "What is 2 + 2?"+ result <- generate ollama prompt Nothing case result of- Left err -> assertFailure $ "Expected success, got error: " ++ err+ Left err -> assertFailure $ "Expected success, got error: " ++ show err Right response -> do- assertBool "Response should mention Haskell" ("haskell" `T.isInfixOf` T.toLower response)+ assertBool "Response should mention 4" ("4" `T.isInfixOf` T.toLower response) events <- getEvents assertBool "should contain all events" (events `shouldContainAll` [LLMStart, LLMEnd]) , testCase "generate returns JSON response when format is set" $ do (callback, getEvents) <- captureEvents let ollama = Ollama testModelName [callback] let prompt = "What is JSON?"- let params = defaultOllamaParams { format = Just O.JsonFormat }+ let params = O.defaultChatOps {O.format = Just O.JsonFormat} result <- generate ollama prompt (Just params) case result of- Left err -> assertFailure $ "Expected success, got error: " ++ err+ Left err -> assertFailure $ "Expected success, got error: " ++ show err Right response -> do case eitherDecode (BSL.fromStrict $ T.encodeUtf8 response) :: Either String Value of Left _ -> assertFailure "Response is not valid JSON" Right _ -> return () events <- getEvents assertBool "should contain all events" (events `shouldContainAll` [LLMStart, LLMEnd])- , testCase "generate uses temperature option" $ do- (callback, getEvents) <- captureEvents- let ollama = Ollama testModelName [callback]- let prompt = "Write a short story."- let temp = 0.7- let opts = object ["temperature" .= Number temp]- let params = defaultOllamaParams { options = Just opts }- result <- generate ollama prompt (Just params)- case result of- Left err -> assertFailure $ "Expected success, got error: " ++ err- Right response -> do- assertBool "Response should not be empty" (T.length response > 0)- events <- getEvents- assertBool "should contain all events" (events `shouldContainAll` [LLMStart, LLMEnd]) , testCase "chat returns JSON response when format is set" $ do (callback, getEvents) <- captureEvents let ollama = Ollama testModelName [callback] let messages = Message User "What is JSON?" defaultMessageData :| []- let params = defaultOllamaParams { format = Just O.JsonFormat }+ let params = O.defaultChatOps {O.format = Just O.JsonFormat} result <- chat ollama messages (Just params) case result of- Left err -> assertFailure $ "Expected success, got error: " ++ err+ Left err -> assertFailure $ "Expected success, got error: " ++ show err Right response -> do- case eitherDecode (BSL.fromStrict $ T.encodeUtf8 response) :: Either String Value of+ case eitherDecode (BSL.fromStrict $ T.encodeUtf8 (content response)) :: Either String Value of Left _ -> assertFailure "Response is not valid JSON" Right _ -> return () events <- getEvents@@ -189,4 +199,4 @@ isErrorEvent (LLMError _) = True isErrorEvent _ = False - shouldContainAll xs ys = all (`elem` xs) ys+ shouldContainAll xs = all (`elem` xs)
test/Test/Langchain/Memory/Core.hs view
@@ -11,6 +11,7 @@ import qualified Data.List.NonEmpty as NE import Data.Text (Text)+import Langchain.Error (toString) systemMsg :: Text -> Message systemMsg text = Message System text defaultMessageData@@ -31,7 +32,13 @@ NE.length result @?= 1 NE.head result @?= systemMsg prompt , testCase "trimChatMessage should keep specified number of messages" $ do- let msgs = NE.fromList [systemMsg "System", userMsg "User1", aiMsg "AI1", userMsg "User2"]+ let msgs =+ NE.fromList+ [ systemMsg "System"+ , userMsg "User1"+ , aiMsg "AI1"+ , userMsg "User2"+ ] trimmed = trimChatMessage 2 msgs NE.length trimmed @?= 2 NE.toList trimmed @?= [aiMsg "AI1", userMsg "User2"]@@ -62,7 +69,7 @@ memory = WindowBufferMemory 3 initialMsgs result <- messages memory case result of- Left err -> assertFailure $ "Expected Right but got Left: " ++ err+ Left err -> assertFailure $ "Expected Right but got Left: " ++ toString err Right msgs -> msgs @?= initialMsgs , testCase "addMessage should add message when under capacity" $ do let initialMsgs = NE.fromList [systemMsg "System"]@@ -70,36 +77,51 @@ newMsg = userMsg "User1" result <- addMessage memory newMsg case result of- Left err -> assertFailure $ "Expected Right but got Left: " ++ err+ Left err -> assertFailure $ "Expected Right but got Left: " ++ toString err Right newMemory -> do msgsResult <- messages newMemory case msgsResult of- Left err -> assertFailure $ "Expected Right but got Left: " ++ err- Right msgs -> NE.toList msgs @?= [systemMsg "System", userMsg "User1"]+ Left err ->+ assertFailure $+ "Expected Right but got Left: " ++ toString err+ Right msgs ->+ NE.toList msgs+ @?= [ systemMsg "System"+ , userMsg "User1"+ ] , testCase "addMessage should maintain max window size" $ do- let initialMsgs = NE.fromList [systemMsg "System", userMsg "User1", aiMsg "AI1"]+ let initialMsgs =+ NE.fromList+ [ systemMsg "System"+ , userMsg "User1"+ , aiMsg "AI1"+ ] memory = WindowBufferMemory 3 initialMsgs newMsg = userMsg "User2" result <- addMessage memory newMsg case result of- Left err -> assertFailure $ "Expected Right but got Left: " ++ err+ Left err -> assertFailure $ "Expected Right but got Left: " ++ toString err Right newMemory -> do msgsResult <- messages newMemory case msgsResult of- Left err -> assertFailure $ "Expected Right but got Left: " ++ err+ Left err -> assertFailure $ "Expected Right but got Left: " ++ toString err Right msgs -> do NE.length msgs @?= 3- NE.toList msgs @?= [systemMsg "System", aiMsg "AI1", userMsg "User2"]+ NE.toList msgs+ @?= [ systemMsg "System"+ , aiMsg "AI1"+ , userMsg "User2"+ ] , testCase "addUserMessage should add message with User role" $ do let initialMsgs = NE.fromList [systemMsg "System"] memory = WindowBufferMemory 3 initialMsgs result <- addUserMessage memory "Hello" case result of- Left err -> assertFailure $ "Expected Right but got Left: " ++ err+ Left err -> assertFailure $ "Expected Right but got Left: " ++ toString err Right newMemory -> do msgsResult <- messages newMemory case msgsResult of- Left err -> assertFailure $ "Expected Right but got Left: " ++ err+ Left err -> assertFailure $ "Expected Right but got Left: " ++ toString err Right msgs -> do NE.length msgs @?= 2 NE.toList msgs @?= [systemMsg "System", userMsg "Hello"]@@ -108,24 +130,35 @@ memory = WindowBufferMemory 3 initialMsgs result <- addAiMessage memory "I can help" case result of- Left err -> assertFailure $ "Expected Right but got Left: " ++ err+ Left err -> assertFailure $ "Expected Right but got Left: " ++ toString err Right newMemory -> do msgsResult <- messages newMemory case msgsResult of- Left err -> assertFailure $ "Expected Right but got Left: " ++ err+ Left err -> assertFailure $ "Expected Right but got Left: " ++ toString err Right msgs -> do NE.length msgs @?= 2- NE.toList msgs @?= [systemMsg "System", aiMsg "I can help"]+ NE.toList msgs+ @?= [ systemMsg "System"+ , aiMsg "I can help"+ ] , testCase "clear should reset to just system message" $ do- let initialMsgs = NE.fromList [systemMsg "System", userMsg "User1", aiMsg "AI1"]+ let initialMsgs =+ NE.fromList+ [ systemMsg "System"+ , userMsg "User1"+ , aiMsg "AI1"+ ] memory = WindowBufferMemory 3 initialMsgs result <- clear memory case result of- Left err -> assertFailure $ "Expected Right but got Left: " ++ err+ Left err -> assertFailure $ "Expected Right but got Left: " ++ toString err Right newMemory -> do msgsResult <- messages newMemory case msgsResult of- Left err -> assertFailure $ "Expected Right but got Left: " ++ err+ Left err ->+ assertFailure $+ "Expected Right but got Left: "+ ++ toString err Right msgs -> do NE.length msgs @?= 1 NE.head msgs @?= systemMsg "You are an AI model"@@ -140,11 +173,11 @@ memory = WindowBufferMemory 3 initialMsgs result <- invoke memory "Test input" case result of- Left err -> assertFailure $ "Expected Right but got Left: " ++ err+ Left err -> assertFailure $ "Expected Right but got Left: " ++ toString err Right newMemory -> do msgsResult <- messages newMemory case msgsResult of- Left err -> assertFailure $ "Expected Right but got Left: " ++ err+ Left err -> assertFailure $ "Expected Right but got Left: " ++ toString err Right msgs -> do NE.length msgs @?= 2 NE.toList msgs @?= [systemMsg "System", userMsg "Test input"]
test/Test/Langchain/Memory/TokenBufferMemory.hs view
@@ -3,14 +3,17 @@ module Test.Langchain.Memory.TokenBufferMemory (tests) where +import Data.Either (isRight) import qualified Data.List.NonEmpty as NE import Data.Text (Text) import qualified Data.Text as T+import Langchain.Error (llmError) import Langchain.LLM.Core import Langchain.Memory.Core (BaseMemory (..)) import qualified Langchain.Memory.TokenBufferMemory as TB import Test.Tasty (TestTree, testGroup)-import Test.Tasty.HUnit (Assertion, assertFailure, testCase, (@?=))+import Test.Tasty.HUnit+ #if MIN_VERSION_base(4,19,0) import Data.List (unsnoc) #else@@ -21,15 +24,7 @@ mkMsg :: Role -> Text -> Message mkMsg role1 content1 = Message role1 content1 defaultMessageData -assertRight :: Either String a -> Assertion-assertRight (Right _) = pure ()-assertRight (Left err) = error $ "Expected Right but got Left: " ++ err--assertLeft :: String -> Either String a -> Assertion-assertLeft expectedErr (Left actualErr) = actualErr @?= expectedErr-assertLeft _ (Right _) = error $ "Expected Left but got Right"--runAddAndGet :: TB.TokenBufferMemory -> [Message] -> IO ChatMessage+runAddAndGet :: TB.TokenBufferMemory -> [Message] -> IO ChatHistory runAddAndGet initial msgs = do TB.tokenBufferMessages <$> foldl@@ -91,7 +86,10 @@ let initial = TB.TokenBufferMemory 1 (NE.fromList [mkMsg System ""]) bigMsg = mkMsg User (T.replicate 10 "a") -- 10 chars → 2.5 tokens (ceil to 3) result <- addMessage initial bigMsg- assertLeft "New message is exceeding limit" result+ assertEqual+ "New message is exceeding limit"+ (Left (llmError "New message is exceeding limit" Nothing Nothing))+ result ] addUserAndAiMessageTests :: TestTree@@ -106,7 +104,7 @@ Right mem -> do let msgs = NE.toList $ TB.tokenBufferMessages mem unsnoc msgs @?= Just ([mkMsg System ""], mkMsg User userContent)- Left err -> assertFailure $ "Unexpected Left: " ++ err+ Left err -> assertFailure $ "Unexpected Left: " ++ show err , testCase "addAiMessage adds Assistant role message" $ do let initial = TB.TokenBufferMemory 100 (NE.fromList [mkMsg System ""]) aiContent = "I'm an assistant."@@ -115,7 +113,7 @@ Right mem -> do let msgs = NE.toList $ TB.tokenBufferMessages mem unsnoc msgs @?= Just ([mkMsg System ""], mkMsg Assistant aiContent)- Left err -> assertFailure $ "Unexpected Left: " ++ err+ Left err -> assertFailure $ "Unexpected Left: " ++ show err ] clearTest :: TestTree@@ -123,7 +121,7 @@ testCase "clear resets messages to default system message" $ do let initial = TB.TokenBufferMemory 100 (NE.fromList [mkMsg User "old"]) cleared <- clear initial- assertRight cleared+ assertBool "Clear should be right" (isRight cleared) case cleared of Right mem -> TB.tokenBufferMessages mem
test/Test/Langchain/OutputParser/Core.hs view
@@ -7,6 +7,7 @@ import Data.Aeson import Data.Text (Text)+import Langchain.Error (LangchainError) import Langchain.OutputParser.Core data Person = Person@@ -26,23 +27,23 @@ testGroup "OutputParser Tests" [ testCase "Bool parser should parse 'true'" $- (parse "true" :: Either String Bool) @?= Right True+ (parse "true" :: Either LangchainError Bool) @?= Right True , testCase "Bool parser should parse 'True'" $- (parse "True" :: Either String Bool) @?= Right True+ (parse "True" :: Either LangchainError Bool) @?= Right True , testCase "Bool parser should parse 'TRUE'" $- (parse "TRUE" :: Either String Bool) @?= Right True+ (parse "TRUE" :: Either LangchainError Bool) @?= Right True , testCase "Bool parser should parse 'true' with whitespace" $- (parse " true " :: Either String Bool) @?= Right True+ (parse " true " :: Either LangchainError Bool) @?= Right True , testCase "Bool parser should parse 'false'" $- (parse "false" :: Either String Bool) @?= Right False+ (parse "false" :: Either LangchainError Bool) @?= Right False , testCase "Bool parser should parse 'False'" $- (parse "False" :: Either String Bool) @?= Right False+ (parse "False" :: Either LangchainError Bool) @?= Right False , testCase "Bool parser should parse 'FALSE'" $- (parse "FALSE" :: Either String Bool) @?= Right False+ (parse "FALSE" :: Either LangchainError Bool) @?= Right False , testCase "Bool parser should parse 'false' with whitespace" $- (parse " false " :: Either String Bool) @?= Right False+ (parse " false " :: Either LangchainError Bool) @?= Right False , testCase "Bool parser should fail on invalid input" $- case parse "not a boolean" :: Either String Bool of+ case parse "not a boolean" :: Either LangchainError Bool of Left _ -> assertBool "Should be Left" True Right _ -> assertFailure "Should have failed parsing" , testCase "CommaSeparatedList parser should parse empty string" $@@ -54,9 +55,10 @@ , testCase "CommaSeparatedList parser should trim whitespace" $ parse " item1 , item2 , item3 " @?= Right (CommaSeparatedList ["item1", "item2", "item3"]) , testCase "JSONOutputStructure parser should parse valid JSON" $- parse "{\"name\":\"John\",\"age\":30}" @?= (Right (JSONOutputStructure (Person "John" 30)))+ parse "{\"name\":\"John\",\"age\":30}"+ @?= Right (JSONOutputStructure (Person "John" 30)) , testCase "JSONOutputStructure parser should fail on invalid JSON" $- case parse "{not valid json}" :: Either String (JSONOutputStructure Person) of+ case parse "{not valid json}" :: Either LangchainError (JSONOutputStructure Person) of Left _ -> assertBool "Should be Left" True Right _ -> assertFailure "Should have failed parsing" , testCase "NumberSeparatedList parser should parse numbered list" $@@ -75,7 +77,7 @@ parse "1 . First item\n2 . Second item" @?= Right (NumberSeparatedList ["First item", "Second item"]) , testCase "NumberSeparatedList parser should fail if no numbers are found" $- case parse "No numbers here, just text" :: Either String NumberSeparatedList of+ case parse "No numbers here, just text" :: Either LangchainError NumberSeparatedList of Left _ -> assertBool "Should be Left" True Right _ -> assertFailure "Should have failed parsing" ]
test/Test/Langchain/PromptTemplate.hs view
@@ -27,7 +27,7 @@ , testCase "returns an error for missing variables" $ let missingVars = HM.fromList [("name", "Charlie")] in case renderPrompt template missingVars of- Left err -> "place" `T.isInfixOf` (T.pack err) @? "Expected error to contain 'place'"+ Left err -> "place" `T.isInfixOf` T.pack (show err) @? "Expected error to contain 'place'" Right _ -> assertFailure "Expected an error for missing variable" {- TODO: Need to take care of incomplete brace cases , testCase "handles unclosed braces" $@@ -65,8 +65,12 @@ , fsExampleTemplate = "{input} translates to {output}" } in case renderFewShotPrompt badExamples of- Left err -> "input" `T.isInfixOf` (T.pack err) @? "Expected error to contain 'input'"- Right _ -> assertFailure "Expected an error for missing example variable"+ Left err ->+ "input" `T.isInfixOf` T.pack (show err)+ @? "Expected error to contain 'input'"+ Right _ ->+ assertFailure+ "Expected an error for missing example variable" , testCase "correctly uses the example separator" $ let customSep = fewShotTemplate {fsExampleSeparator = " ### "} in renderFewShotPrompt customSep
test/Test/Langchain/Retriever/Core.hs view
@@ -6,31 +6,36 @@ import Test.Tasty import Test.Tasty.HUnit +import qualified Data.Text.Lazy as T import Langchain.DocumentLoader.Core (Document (..)) import Langchain.LLM.Core (LLM (..))+import qualified Langchain.LLM.Core as LLM import Langchain.Retriever.Core (Retriever (..)) import Langchain.Retriever.MultiQueryRetriever import qualified Data.Map.Strict as HM+import Data.Text (Text) data DummyLLM = DummyLLM ---TODO: Add some real world examples here+-- TODO: Add some real world examples here instance LLM DummyLLM where type LLMParams DummyLLM = String+ type LLMStreamTokenType DummyLLM = Text+ -- When 'generate' is called, we return a fixed response in the format expected by the -- NumberSeparatedList parser. For example: -- -- "1. test query 1\n2. test query 2" generate _ _ _ = return $ Right "1. test query 1\n2. test query 2"- chat _ _ _ = return $ Right "dummy chat response"+ chat _ _ _ = return $ Right $ LLM.Message LLM.User "dummy chat response" LLM.defaultMessageData stream _ _ _ _ = return $ Right () data DummyRetriever = DummyRetriever instance Retriever DummyRetriever where _get_relevant_documents _ query =- return $ Right [Document (query <> " result") HM.empty]+ return $ Right [Document (T.fromStrict $ query <> " result") HM.empty] test_generateQueries :: Assertion test_generateQueries = do@@ -41,7 +46,7 @@ queryPrompt = defaultQueryGenerationPrompt result <- generateQueries dummyLLM queryPrompt query numQueriesToGenerate includeOriginal case result of- Left err -> assertFailure ("generateQueries failed with error: " ++ err)+ Left err -> assertFailure ("generateQueries failed with error: " ++ show err) Right qs -> do let expectedQueries = [ "original query"@@ -61,7 +66,7 @@ originalQuery = "original query" result <- _get_relevant_documents mqRetriever originalQuery case result of- Left err -> assertFailure ("MultiQueryRetriever failed with error: " ++ err)+ Left err -> assertFailure ("MultiQueryRetriever failed with error: " ++ show err) Right docs -> do -- Since generateQueries returns three queries (original plus two generated), -- and DummyRetriever returns one document per query, we expect 3 documents.
test/Test/Langchain/Runnable/Chains.hs view
@@ -1,10 +1,10 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} module Test.Langchain.Runnable.Chains (tests) where +import Langchain.Error (LangchainError, llmError) import Langchain.Runnable.Chain import Langchain.Runnable.Core import Test.Tasty (TestTree, testGroup)@@ -17,12 +17,12 @@ multiplyByTwo = MockRunnable (\x -> return $ Right (x * 2)) evenCheck :: MockRunnable Int Bool-evenCheck = MockRunnable (\x -> return $ Right (even x))+evenCheck = MockRunnable $ return . Right . even failingMock :: MockRunnable a b-failingMock = MockRunnable (\_ -> return $ Left "Mock error")+failingMock = MockRunnable (\_ -> return $ Left (llmError "Mock error" Nothing Nothing)) -data MockRunnable a b = MockRunnable {runMock :: a -> IO (Either String b)}+newtype MockRunnable a b = MockRunnable {runMock :: a -> IO (Either LangchainError b)} instance Runnable (MockRunnable a b) where type RunnableInput (MockRunnable a b) = a@@ -81,7 +81,7 @@ , testCase "Propagates errors in chain" $ do let pipeline = failingMock |>> multiplyByTwo result <- pipeline ()- assertEqual "Error in first step" (Left "Mock error") result+ assertEqual "Error in first step" (Left (llmError "Mock error" Nothing Nothing)) result ] , testGroup "Branch Tests"@@ -90,6 +90,9 @@ assertEqual "Both branches run" (Right (True, 5)) result , testCase "Handles branch errors" $ do result <- branch failingMock addOne 5- assertEqual "Left error in first branch" (Left "Mock error" :: Either String (Bool, Int)) result+ assertEqual+ "Left error in first branch"+ (Left (llmError "Mock error" Nothing Nothing) :: Either LangchainError (Bool, Int))+ result ] ]
test/Test/Langchain/Runnable/ConversationChains.hs view
@@ -1,6 +1,5 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} module Test.Langchain.Runnable.ConversationChains (tests) where@@ -9,6 +8,7 @@ import Data.List.NonEmpty (NonEmpty (..)) import qualified Data.List.NonEmpty as NE import Data.Text (Text)+import Langchain.Error (LangchainError, llmError, memoryError) import Langchain.LLM.Core import Langchain.Memory.Core (BaseMemory (..)) import Langchain.PromptTemplate (PromptTemplate (..))@@ -17,7 +17,7 @@ import Test.Tasty (TestTree, testGroup) import Test.Tasty.HUnit (assertEqual, testCase, (@?=)) -data TestMemory = TestMemory (IORef [Message])+newtype TestMemory = TestMemory (IORef [Message]) instance BaseMemory TestMemory where addUserMessage (TestMemory ref) input = do@@ -35,7 +35,7 @@ return $ Right (TestMemory ref) clear (TestMemory ref) = do- (modifyIORef ref (const []))+ writeIORef ref [] return $ Right $ TestMemory ref messages (TestMemory ref) = fmap Right (NE.fromList <$> readIORef ref)@@ -43,19 +43,21 @@ data FailingMemory = FailingMemory instance BaseMemory FailingMemory where- addUserMessage _ _ = return $ Left "Memory error"- addAiMessage _ _ = return $ Left "Memory error"- messages _ = return $ Left "memory error"- addMessage _ _ = return $ Left "memory error"- clear _ = return $ Left "memory error"+ addUserMessage _ _ = return $ Left $ memoryError "Memory error" Nothing Nothing+ addAiMessage _ _ = return $ Left $ memoryError "Memory error" Nothing Nothing+ messages _ = return $ Left $ memoryError "Memory error" Nothing Nothing+ addMessage _ _ = return $ Left $ memoryError "Memory error" Nothing Nothing+ clear _ = return $ Left $ memoryError "Memory error" Nothing Nothing data MockLLM = MockLLM- { llmResponse :: Either String Text+ { llmResponse :: Either LangchainError Message , receivedMessages :: IORef [Message] } instance LLM MockLLM where type LLMParams MockLLM = String+ type LLMStreamTokenType MockLLM = Text+ chat llm0 (msgs :: NonEmpty Message) _ = do writeIORef (receivedMessages llm0) (NE.toList msgs) return (llmResponse llm0)@@ -70,7 +72,7 @@ memRef <- newIORef [] let testMem = TestMemory memRef msgRef <- newIORef []- let mockLLM = MockLLM (Right "Hello!") msgRef+ let mockLLM = MockLLM (Right $ Message User "Hello!" defaultMessageData) msgRef chain = ConversationChain testMem mockLLM (PromptTemplate "") result <- invoke chain "Hi" result @?= Right "Hello!"@@ -88,18 +90,18 @@ , testCase "Error adding user message" $ do nRef <- newIORef [] let failingMem = FailingMemory- mockLLM = MockLLM (Right "") nRef+ mockLLM = MockLLM (Right $ Message User "" defaultMessageData) nRef chain = ConversationChain failingMem mockLLM (PromptTemplate "") result <- invoke chain "Hi"- result @?= Left "Memory error"+ result @?= Left (memoryError "Memory error" Nothing Nothing) , testCase "LLM returns error" $ do memRef <- newIORef [] let testMem = TestMemory memRef msgRef <- newIORef []- let mockLLM = MockLLM (Left "LLM error") msgRef+ let mockLLM = MockLLM (Left $ llmError "LLM error" Nothing Nothing) msgRef chain = ConversationChain testMem mockLLM (PromptTemplate "") result <- invoke chain "Hi"- result @?= Left "LLM error"+ result @?= Left (llmError "LLM error" Nothing Nothing) -- Verify only user message in memory mem <- readIORef memRef assertEqual "Only user message in memory" [Message User "Hi" defaultMessageData] mem@@ -107,7 +109,7 @@ memRef <- newIORef [] nRef <- newIORef [] let testMem = TestMemory memRef- mockLLM = MockLLM (Right "Response") nRef+ mockLLM = MockLLM (Right $ Message User "Response" defaultMessageData) nRef chain = ConversationChain testMem mockLLM (PromptTemplate "") _ <- invoke chain "Test" mem <- readIORef memRef
test/Test/Langchain/Runnable/Core.hs view
@@ -1,17 +1,17 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} module Test.Langchain.Runnable.Core (tests) where -import Data.IORef (modifyIORef, newIORef, readIORef)+import Data.IORef (modifyIORef, newIORef, readIORef, writeIORef)+import Langchain.Error (LangchainError, llmError) import Langchain.Runnable.Core import Test.Tasty (TestTree, testGroup) import Test.Tasty.HUnit (assertEqual, testCase) -data MockRunnable a b = MockRunnable- { runMock :: a -> IO (Either String b)+newtype MockRunnable a b = MockRunnable+ { runMock :: a -> IO (Either LangchainError b) } instance Runnable (MockRunnable a b) where@@ -28,9 +28,12 @@ result <- invoke mock "input" assertEqual "Should process input" (Right "input processed") result , testCase "invoke error" $ do- let mock = MockRunnable (\(_ :: String) -> return $ Left "mock error")+ let mock = MockRunnable (\(_ :: String) -> return $ Left (llmError "mock error" Nothing Nothing)) result <- invoke mock "input"- assertEqual "Should return error" (Left "mock error" :: Either String String) result+ assertEqual+ "Should return error"+ (Left (llmError "mock error" Nothing Nothing) :: Either LangchainError String)+ result , testCase "batch success" $ do let mock = MockRunnable (\(s :: String) -> return $ Right (s ++ "!")) result <- batch mock ["a", "b", "c"]@@ -38,10 +41,10 @@ , testCase "batch with error" $ do let mock = MockRunnable $ \(s :: String) -> if s == "b"- then return (Left "error in batch")+ then return (Left (llmError "error in batch" Nothing Nothing)) else return (Right (s ++ "!")) result <- batch mock ["a", "b", "c"]- assertEqual "Should return first error" (Left "error in batch") result+ assertEqual "Should return first error" (Left (llmError "error in batch" Nothing Nothing)) result , testCase "stream success" $ do ref <- newIORef [] let mock = MockRunnable (\(s :: String) -> return $ Right (s ++ "!"))@@ -52,10 +55,10 @@ assertEqual "Callback called with correct value" ["test!"] readRef , testCase "stream error" $ do ref <- newIORef []- let mock = MockRunnable (\(_ :: String) -> return $ Left "stream error")- callback _ = modifyIORef ref (const ["should not be called" :: String])+ let mock = MockRunnable (\(_ :: String) -> return $ Left (llmError "stream error" Nothing Nothing))+ callback _ = writeIORef ref ["should not be called" :: String] result <- stream mock "test" callback readRef <- readIORef ref- assertEqual "Stream should return error" (Left "stream error") result+ assertEqual "Stream should return error" (Left (llmError "stream error" Nothing Nothing)) result assertEqual "Callback not called" [] readRef ]
test/Test/Langchain/Runnable/Utils.hs view
@@ -1,18 +1,18 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} module Test.Langchain.Runnable.Utils (tests) where import Control.Concurrent (threadDelay) import Data.IORef (IORef, modifyIORef, newIORef, readIORef)+import Langchain.Error (LangchainError, llmError) import Langchain.Runnable.Core import Langchain.Runnable.Utils import Test.Tasty (TestTree, testGroup) import Test.Tasty.HUnit (assertEqual, testCase) -data InvocationCounter a b = InvocationCounter (IORef Int) (a -> IO (Either String b))+data InvocationCounter a b = InvocationCounter (IORef Int) (a -> IO (Either LangchainError b)) instance Runnable (InvocationCounter a b) where type RunnableInput (InvocationCounter a b) = a@@ -62,7 +62,7 @@ let mock = InvocationCounter counter $ \_ -> do cnt <- readIORef counter if cnt < 1- then return $ Left "Error"+ then return $ Left (llmError "Error" Nothing Nothing) else return $ Right ("Success" :: String) retryMock = Retry mock 3 5000 -- 1 retry, 5ms delay result <- invoke retryMock ("input" :: String)@@ -71,11 +71,14 @@ assertEqual "Invoked twice" 1 cnt , testCase "Retry exhausts retries and fails" $ do counter <- newIORef 0- let mock = InvocationCounter counter (\_ -> return $ Left "Error")+ let mock = InvocationCounter counter (\_ -> return $ Left (llmError "Error" Nothing Nothing)) retryMock = Retry mock 2 1000 -- 2 retries result <- invoke retryMock ("input" :: String) cnt <- readIORef counter- assertEqual "All retries exhausted" (Left "Error" :: Either String String) result+ assertEqual+ "All retries exhausted"+ (Left (llmError "Error" Nothing Nothing) :: Either LangchainError String)+ result assertEqual "Three attempts made" 3 cnt ] , testGroup@@ -91,11 +94,14 @@ return $ Right "Too slow" timeoutMock = WithTimeout mock 100000 -- 100ms timeout result <- invoke timeoutMock ("input" :: String)- assertEqual "Timeout error" (Left "Operation timed out" :: Either String String) result+ assertEqual+ "Timeout error"+ (Left (llmError "Operation timed out" Nothing Nothing) :: Either LangchainError String)+ result ] ] -data MockRunnable a b = MockRunnable {runMock :: a -> IO (Either String b)}+newtype MockRunnable a b = MockRunnable {runMock :: a -> IO (Either LangchainError b)} instance Runnable (MockRunnable a b) where type RunnableInput (MockRunnable a b) = a
test/Test/Langchain/Tool/Core.hs view
@@ -6,19 +6,19 @@ module Test.Langchain.Tool.Core (tests) where import Data.Aeson (decode)+import Data.Either (isLeft) import qualified Data.Map as M import Data.Text (Text) import qualified Data.Text as T import Test.Tasty import Test.Tasty.HUnit-import Data.Either (isLeft) +import Langchain.Tool.Calculator import Langchain.Tool.Core import Langchain.Tool.WebScraper import Langchain.Tool.WikipediaTool-import Langchain.Tool.Calculator -data MockTool = MockTool Text+newtype MockTool = MockTool Text deriving (Show, Eq) instance Tool MockTool where@@ -43,90 +43,83 @@ ] testCalculatorTool :: TestTree-testCalculatorTool = testGroup "Langchain.Tool.Calculator"- [ parseExpressionTests- , evaluateExpressionTests- , calculatorToolTests- ]+testCalculatorTool =+ testGroup+ "Langchain.Tool.Calculator"+ [ parseExpressionTests+ , evaluateExpressionTests+ , calculatorToolTests+ ] -- | Test cases for parseExpression parseExpressionTests :: TestTree-parseExpressionTests = testGroup "parseExpression"- [ testCase "Parses integer" $- parseExpression "123" @?= Right (Number_ 123.0)-- , testCase "Parses decimal" $- parseExpression "45.67" @?= Right (Number_ 45.67)-- , testCase "Handles addition" $- parseExpression "2+3" @?= Right (Add (Number_ 2) (Number_ 3))-- , testCase "Handles subtraction" $- parseExpression "5 - 1" @?= Right (Sub (Number_ 5) (Number_ 1))-- , testCase "Handles multiplication" $- parseExpression "4*2" @?= Right (Mul (Number_ 4) (Number_ 2))-- , testCase "Handles division" $- parseExpression "8 / 2" @?= Right (Div (Number_ 8) (Number_ 2))-- , testCase "Handles exponentiation" $- parseExpression "2^3" @?= Right (Pow (Number_ 2) (Number_ 3))-- , testCase "Respects operator precedence" $- parseExpression "2 + 3 * 4" @?= Right (Add (Number_ 2) (Mul (Number_ 3) (Number_ 4)))-- , testCase "Respects parentheses" $- parseExpression "(2 + 3) * 4" @?= Right (Mul (Add (Number_ 2) (Number_ 3)) (Number_ 4))-- , testCase "Fails on invalid input" $- isLeft (parseExpression "hello") @? "Expected parse failure for 'hello'"- ]+parseExpressionTests =+ testGroup+ "parseExpression"+ [ testCase "Parses integer" $+ parseExpression "123" @?= Right (Number_ 123.0)+ , testCase "Parses decimal" $+ parseExpression "45.67" @?= Right (Number_ 45.67)+ , testCase "Handles addition" $+ parseExpression "2+3" @?= Right (Add (Number_ 2) (Number_ 3))+ , testCase "Handles subtraction" $+ parseExpression "5 - 1" @?= Right (Sub (Number_ 5) (Number_ 1))+ , testCase "Handles multiplication" $+ parseExpression "4*2" @?= Right (Mul (Number_ 4) (Number_ 2))+ , testCase "Handles division" $+ parseExpression "8 / 2" @?= Right (Div (Number_ 8) (Number_ 2))+ , testCase "Handles exponentiation" $+ parseExpression "2^3" @?= Right (Pow (Number_ 2) (Number_ 3))+ , testCase "Respects operator precedence" $+ parseExpression "2 + 3 * 4" @?= Right (Add (Number_ 2) (Mul (Number_ 3) (Number_ 4)))+ , testCase "Respects parentheses" $+ parseExpression "(2 + 3) * 4" @?= Right (Mul (Add (Number_ 2) (Number_ 3)) (Number_ 4))+ , testCase "Fails on invalid input" $+ isLeft (parseExpression "hello") @? "Expected parse failure for 'hello'"+ ] -- | Test cases for evaluateExpression evaluateExpressionTests :: TestTree-evaluateExpressionTests = testGroup "evaluateExpression"- [ testCase "Evaluates Num" $- evaluateExpression (Number_ 5) @?= 5.0-- , testCase "Evaluates Add" $- evaluateExpression (Add (Number_ 2) (Number_ 3)) @?= 5.0-- , testCase "Evaluates Mul" $- evaluateExpression (Mul (Number_ 3) (Number_ 4)) @?= 12.0-- , testCase "Evaluates Pow" $- evaluateExpression (Pow (Number_ 2) (Number_ 3)) @?= 8.0- ]+evaluateExpressionTests =+ testGroup+ "evaluateExpression"+ [ testCase "Evaluates Num" $+ evaluateExpression (Number_ 5) @?= 5.0+ , testCase "Evaluates Add" $+ evaluateExpression (Add (Number_ 2) (Number_ 3)) @?= 5.0+ , testCase "Evaluates Mul" $+ evaluateExpression (Mul (Number_ 3) (Number_ 4)) @?= 12.0+ , testCase "Evaluates Pow" $+ evaluateExpression (Pow (Number_ 2) (Number_ 3)) @?= 8.0+ ] -- | Test cases for CalculatorTool calculatorToolTests :: TestTree-calculatorToolTests = testGroup "CalculatorTool"- [ testCase "Computes 2 + 3 * 4" $ do- result <- runTool CalculatorTool "2 + 3 * 4"- result @?= Right 14.0-- , testCase "Computes (2 + 3) * 4" $ do- result <- runTool CalculatorTool "(2 + 3) * 4"- result @?= Right 20.0-- , testCase "Computes 2 ^ 3" $ do- result <- runTool CalculatorTool "2 ^ 3"- result @?= Right 8.0-- , testCase "Fails on invalid expression" $ do- let badExpr = "2 +"- errOrRes <- runTool CalculatorTool badExpr- case errOrRes of- Left _ -> return ()- Right _ -> assertFailure "Expected error when parsing invalid expression"- ]+calculatorToolTests =+ testGroup+ "CalculatorTool"+ [ testCase "Computes 2 + 3 * 4" $ do+ result <- runTool CalculatorTool "2 + 3 * 4"+ result @?= Right 14.0+ , testCase "Computes (2 + 3) * 4" $ do+ result <- runTool CalculatorTool "(2 + 3) * 4"+ result @?= Right 20.0+ , testCase "Computes 2 ^ 3" $ do+ result <- runTool CalculatorTool "2 ^ 3"+ result @?= Right 8.0+ , testCase "Fails on invalid expression" $ do+ let badExpr = "2 +"+ errOrRes <- runTool CalculatorTool badExpr+ case errOrRes of+ Left _ -> return ()+ Right _ -> assertFailure "Expected error when parsing invalid expression"+ ] testWebScraperTool :: Assertion testWebScraperTool = do eRes <- runTool WebScraper "https://hackage.haskell.org/package/scalpel-0.6.2.2" assertBool "Scraper should contain stuff like title" $ do- case eRes of + case eRes of Left _ -> False Right r -> do T.isInfixOf "Scalpel is a web scraping library inspired by libraries like" r
test/Test/Langchain/VectorStore/Core.hs view
@@ -49,7 +49,7 @@ , testCase "cosineSimilarity should compute correct similarity" $ do assertBool "Expected near same similarity"- ((cosineSimilarity [1.0, 2.0, 3.0] [1.0, 2.0, 3.0]) >= 0.999999)+ (cosineSimilarity [1.0, 2.0, 3.0] [1.0, 2.0, 3.0] >= 0.999999) let similarity = cosineSimilarity [1.0, 0.0, 0.0] [0.0, 1.0, 0.0] assertBool "Expected near 0" (abs similarity < 0.000001)