packages feed

langchain-hs 0.0.5.0 → 0.0.6.0

raw patch · 113 files changed

+974/−12625 lines, 113 filesdep −aeson-qqdep −format-heavydep −http-clientdep ~aesondep ~asyncdep ~bytestringsetup-changedPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies removed: aeson-qq, format-heavy, http-client, http-client-tls, http-conduit, http-types, ollama-haskell, openai, process, random, scientific, servant, servant-client, servant-client-core, servant-conduit, servant-event-stream, sqlite-simple, transformers, wai, warp

Dependency ranges changed: aeson, async, bytestring, conduit, containers, directory, filepath, langchain-hs-core, langchain-hs-graph, mtl, resourcet, stm, text, time, vector

API changes (from Hackage documentation)

- Langchain.Agent.PlanAndExecute: Plan :: [PlanStep] -> Plan
- Langchain.Agent.PlanAndExecute: PlanAndExecuteAgent :: planner -> executor -> Maybe Text -> PlanAndExecuteAgent planner executor
- Langchain.Agent.PlanAndExecute: PlanStep :: !Int -> !Text -> PlanStep
- Langchain.Agent.PlanAndExecute: [planPromptTemplate] :: PlanAndExecuteAgent planner executor -> Maybe Text
- Langchain.Agent.PlanAndExecute: [planSteps] :: Plan -> [PlanStep]
- Langchain.Agent.PlanAndExecute: [plannerModel] :: PlanAndExecuteAgent planner executor -> planner
- Langchain.Agent.PlanAndExecute: [stepDescription] :: PlanStep -> !Text
- Langchain.Agent.PlanAndExecute: [stepExecutor] :: PlanAndExecuteAgent planner executor -> executor
- Langchain.Agent.PlanAndExecute: [stepNumber] :: PlanStep -> !Int
- Langchain.Agent.PlanAndExecute: class StepExecutor e (m :: Type -> Type)
- Langchain.Agent.PlanAndExecute: data PlanAndExecuteAgent planner executor
- Langchain.Agent.PlanAndExecute: data PlanStep
- Langchain.Agent.PlanAndExecute: executeStep :: StepExecutor e m => e -> Text -> m Text
- Langchain.Agent.PlanAndExecute: instance (Langchain.Core.Model.ChatModel model, Control.Monad.IO.Class.MonadIO m, Control.Monad.Error.Class.MonadError Langchain.Core.Error.LangchainError m) => Langchain.Agent.PlanAndExecute.StepExecutor model m
- Langchain.Agent.PlanAndExecute: instance (m GHC.Types.~ n) => Langchain.Agent.PlanAndExecute.StepExecutor (Data.Text.Internal.Text -> m Data.Text.Internal.Text) n
- Langchain.Agent.PlanAndExecute: instance (m GHC.Types.~ n, Langchain.Tool.Binding.ToolBinder model m, Control.Monad.IO.Class.MonadIO n, Control.Monad.Error.Class.MonadError Langchain.Core.Error.LangchainError n) => Langchain.Agent.PlanAndExecute.StepExecutor (Langchain.Agent.ReAct.ReActAgent model m) n
- Langchain.Agent.PlanAndExecute: instance (m GHC.Types.~ n, Langchain.Tool.Binding.ToolBinder model m, Control.Monad.IO.Class.MonadIO n, Control.Monad.Error.Class.MonadError Langchain.Core.Error.LangchainError n) => Langchain.Agent.PlanAndExecute.StepExecutor (model, [Langchain.Core.Tool.Tool m]) n
- Langchain.Agent.PlanAndExecute: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Agent.PlanAndExecute.Plan
- Langchain.Agent.PlanAndExecute: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Agent.PlanAndExecute.PlanStep
- Langchain.Agent.PlanAndExecute: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Agent.PlanAndExecute.Plan
- Langchain.Agent.PlanAndExecute: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Agent.PlanAndExecute.PlanStep
- Langchain.Agent.PlanAndExecute: instance GHC.Classes.Eq Langchain.Agent.PlanAndExecute.Plan
- Langchain.Agent.PlanAndExecute: instance GHC.Classes.Eq Langchain.Agent.PlanAndExecute.PlanStep
- Langchain.Agent.PlanAndExecute: instance GHC.Generics.Generic Langchain.Agent.PlanAndExecute.Plan
- Langchain.Agent.PlanAndExecute: instance GHC.Generics.Generic Langchain.Agent.PlanAndExecute.PlanStep
- Langchain.Agent.PlanAndExecute: instance GHC.Show.Show Langchain.Agent.PlanAndExecute.Plan
- Langchain.Agent.PlanAndExecute: instance GHC.Show.Show Langchain.Agent.PlanAndExecute.PlanStep
- Langchain.Agent.PlanAndExecute: instance Langchain.OutputParser.Structured.StructuredOutput Langchain.Agent.PlanAndExecute.Plan
- Langchain.Agent.PlanAndExecute: instance Langchain.OutputParser.Structured.TypeSchema Langchain.Agent.PlanAndExecute.Plan
- Langchain.Agent.PlanAndExecute: instance Langchain.OutputParser.Structured.TypeSchema Langchain.Agent.PlanAndExecute.PlanStep
- Langchain.Agent.PlanAndExecute: newPlanAndExecuteAgent :: planner -> executor -> Maybe Text -> PlanAndExecuteAgent planner executor
- Langchain.Agent.PlanAndExecute: newPlanAndExecuteAgentWithTools :: forall planner model (m :: Type -> Type). planner -> model -> [Tool m] -> Maybe Text -> PlanAndExecuteAgent planner (ReActAgent model m)
- Langchain.Agent.PlanAndExecute: newtype Plan
- Langchain.Agent.PlanAndExecute: runPlanAndExecute :: (ChatModel planner, StepExecutor executor m, MonadIO m, MonadError LangchainError m) => PlanAndExecuteAgent planner executor -> Text -> m Text
- Langchain.Agent.ReAct: AgentAction :: Message -> [ToolCall] -> AgentStep
- Langchain.Agent.ReAct: AgentFinish :: Message -> AgentStep
- Langchain.Agent.ReAct: ReActAgent :: model -> [Tool m] -> Int -> ReActAgent model (m :: Type -> Type)
- Langchain.Agent.ReAct: [agentMaxIterations] :: ReActAgent model (m :: Type -> Type) -> Int
- Langchain.Agent.ReAct: [agentModel] :: ReActAgent model (m :: Type -> Type) -> model
- Langchain.Agent.ReAct: [agentTools] :: ReActAgent model (m :: Type -> Type) -> [Tool m]
- Langchain.Agent.ReAct: createReActAgent :: forall model (m :: Type -> Type). model -> [Tool m] -> ReActAgent model m
- Langchain.Agent.ReAct: data AgentStep
- Langchain.Agent.ReAct: data ReActAgent model (m :: Type -> Type)
- Langchain.Agent.ReAct: instance GHC.Classes.Eq Langchain.Agent.ReAct.AgentStep
- Langchain.Agent.ReAct: instance GHC.Show.Show Langchain.Agent.ReAct.AgentStep
- Langchain.Agent.ReAct: reactStep :: (ToolBinder model m, MonadIO m, MonadError LangchainError m) => model -> [Tool m] -> [Message] -> m AgentStep
- Langchain.Agent.ReAct: runReActAgent :: (ToolBinder model m, MonadIO m, MonadError LangchainError m) => ReActAgent model m -> [Message] -> m Message
- Langchain.Cache.Core: CachedModel :: model -> cache -> CachedModel model cache
- Langchain.Cache.Core: InMemoryCache :: TVar (Map Text Message) -> InMemoryCache
- Langchain.Cache.Core: SQLiteCache :: FilePath -> SQLiteCache
- Langchain.Cache.Core: [memCacheVar] :: InMemoryCache -> TVar (Map Text Message)
- Langchain.Cache.Core: [modelCache] :: CachedModel model cache -> cache
- Langchain.Cache.Core: [sqliteCacheDbPath] :: SQLiteCache -> FilePath
- Langchain.Cache.Core: [underlyingModel] :: CachedModel model cache -> model
- Langchain.Cache.Core: cacheModelIdentity :: CacheableChatModel model => model -> Maybe (ModelConfig model) -> Value
- Langchain.Cache.Core: class CacheBackend cb
- Langchain.Cache.Core: class ChatModel model => CacheableChatModel model
- Langchain.Cache.Core: clearCache :: (CacheBackend cb, MonadIO m) => cb -> m ()
- Langchain.Cache.Core: computeCacheKey :: CacheableChatModel model => model -> Maybe (ModelConfig model) -> [Message] -> Text
- Langchain.Cache.Core: data CachedModel model cache
- Langchain.Cache.Core: getCache :: (CacheBackend cb, MonadIO m) => cb -> Text -> m (Maybe Message)
- Langchain.Cache.Core: instance (Langchain.Cache.Core.CacheableChatModel model, Langchain.Cache.Core.CacheBackend cache) => Langchain.Core.Model.ChatModel (Langchain.Cache.Core.CachedModel model cache)
- Langchain.Cache.Core: instance (Langchain.Cache.Core.CacheableChatModel model, Langchain.Cache.Core.CacheBackend cache, Langchain.Tool.Binding.ToolBinder model m) => Langchain.Tool.Binding.ToolBinder (Langchain.Cache.Core.CachedModel model cache) m
- Langchain.Cache.Core: instance Langchain.Cache.Core.CacheBackend Langchain.Cache.Core.InMemoryCache
- Langchain.Cache.Core: instance Langchain.Cache.Core.CacheBackend Langchain.Cache.Core.SQLiteCache
- Langchain.Cache.Core: instance Langchain.Cache.Core.CacheableChatModel Langchain.Provider.Gemini.Gemini
- Langchain.Cache.Core: instance Langchain.Cache.Core.CacheableChatModel Langchain.Provider.Ollama.Ollama
- Langchain.Cache.Core: instance Langchain.Cache.Core.CacheableChatModel Langchain.Provider.OpenAI.OpenAI
- Langchain.Cache.Core: newInMemoryCache :: MonadIO m => m InMemoryCache
- Langchain.Cache.Core: newSQLiteCache :: MonadIO m => FilePath -> m SQLiteCache
- Langchain.Cache.Core: newtype InMemoryCache
- Langchain.Cache.Core: newtype SQLiteCache
- Langchain.Cache.Core: putCache :: (CacheBackend cb, MonadIO m) => cb -> Text -> Message -> m ()
- Langchain.Cache.Core: withCaching :: model -> cache -> CachedModel model cache
- Langchain.Callback.Manager: CallbackHandler :: !Text -> (CallbackEvent -> IO ()) -> CallbackHandler
- Langchain.Callback.Manager: CallbackManager :: TVar [CallbackHandler] -> CallbackManager
- Langchain.Callback.Manager: OnChainEnd :: !Text -> !Text -> !Int -> !UTCTime -> CallbackEvent
- Langchain.Callback.Manager: OnChainStart :: !Text -> !Text -> !UTCTime -> CallbackEvent
- Langchain.Callback.Manager: OnError :: !Text -> !Text -> !UTCTime -> CallbackEvent
- Langchain.Callback.Manager: OnGraphNodeEnd :: !Text -> !Text -> !Int -> !UTCTime -> CallbackEvent
- Langchain.Callback.Manager: OnGraphNodeStart :: !Text -> !Text -> !UTCTime -> CallbackEvent
- Langchain.Callback.Manager: OnLLMEnd :: !Text -> !Text -> !Int -> !UTCTime -> CallbackEvent
- Langchain.Callback.Manager: OnLLMStart :: !Text -> ![Text] -> !UTCTime -> CallbackEvent
- Langchain.Callback.Manager: OnRetrieverEnd :: !Text -> ![Text] -> !Int -> !UTCTime -> CallbackEvent
- Langchain.Callback.Manager: OnRetrieverStart :: !Text -> !Text -> !UTCTime -> CallbackEvent
- Langchain.Callback.Manager: OnToolEnd :: !Text -> !Text -> !Int -> !UTCTime -> CallbackEvent
- Langchain.Callback.Manager: OnToolStart :: !Text -> !Value -> !UTCTime -> CallbackEvent
- Langchain.Callback.Manager: [handleEvent] :: CallbackHandler -> CallbackEvent -> IO ()
- Langchain.Callback.Manager: [handlerName] :: CallbackHandler -> !Text
- Langchain.Callback.Manager: [handlersVar] :: CallbackManager -> TVar [CallbackHandler]
- Langchain.Callback.Manager: data CallbackEvent
- Langchain.Callback.Manager: data CallbackHandler
- Langchain.Callback.Manager: dispatchEvent :: MonadIO m => CallbackManager -> CallbackEvent -> m ()
- Langchain.Callback.Manager: dispatchEventAsync :: MonadIO m => CallbackManager -> CallbackEvent -> m ()
- Langchain.Callback.Manager: getCallbackLogs :: MonadIO m => TVar [Text] -> m [Text]
- Langchain.Callback.Manager: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Callback.Manager.CallbackEvent
- Langchain.Callback.Manager: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Callback.Manager.CallbackEvent
- Langchain.Callback.Manager: instance GHC.Classes.Eq Langchain.Callback.Manager.CallbackEvent
- Langchain.Callback.Manager: instance GHC.Generics.Generic Langchain.Callback.Manager.CallbackEvent
- Langchain.Callback.Manager: instance GHC.Show.Show Langchain.Callback.Manager.CallbackEvent
- Langchain.Callback.Manager: newCallbackManager :: MonadIO m => m CallbackManager
- Langchain.Callback.Manager: newLoggingCallbackHandler :: MonadIO m => Text -> m (CallbackHandler, TVar [Text])
- Langchain.Callback.Manager: newtype CallbackManager
- Langchain.Callback.Manager: registerHandler :: MonadIO m => CallbackManager -> CallbackHandler -> m ()
- Langchain.Chain.MapReduce: MapReduceChain :: model -> PromptTemplate -> PromptTemplate -> Text -> Text -> MapReduceChain model
- Langchain.Chain.MapReduce: [mapDocVar] :: MapReduceChain model -> Text
- Langchain.Chain.MapReduce: [mapPromptTemplate] :: MapReduceChain model -> PromptTemplate
- Langchain.Chain.MapReduce: [mapReduceModel] :: MapReduceChain model -> model
- Langchain.Chain.MapReduce: [reduceDocVar] :: MapReduceChain model -> Text
- Langchain.Chain.MapReduce: [reducePromptTemplate] :: MapReduceChain model -> PromptTemplate
- Langchain.Chain.MapReduce: data MapReduceChain model
- Langchain.Chain.MapReduce: defaultMapPrompt :: PromptTemplate
- Langchain.Chain.MapReduce: defaultReducePrompt :: PromptTemplate
- Langchain.Chain.MapReduce: newMapReduceChain :: model -> MapReduceChain model
- Langchain.Chain.MapReduce: runMapReduceChain :: (ChatModel model, MonadIO m, MonadError LangchainError m) => MapReduceChain model -> [Document] -> Map Text Text -> m Message
- Langchain.Chain.RetrievalQA: RetrievalQA :: model -> retriever -> PromptTemplate -> RetrievalQA model retriever
- Langchain.Chain.RetrievalQA: [model] :: RetrievalQA model retriever -> model
- Langchain.Chain.RetrievalQA: [prompt] :: RetrievalQA model retriever -> PromptTemplate
- Langchain.Chain.RetrievalQA: [retriever] :: RetrievalQA model retriever -> retriever
- Langchain.Chain.RetrievalQA: data RetrievalQA model retriever
- Langchain.Chain.RetrievalQA: defaultQAPrompt :: PromptTemplate
- Langchain.Chain.RetrievalQA: newRetrievalQA :: model -> retriever -> RetrievalQA model retriever
- Langchain.Chain.RetrievalQA: runRetrievalQA :: (ChatModel model, Retriever retriever, MonadIO m, MonadError LangchainError m) => RetrievalQA model retriever -> Text -> m Message
- Langchain.DocumentLoader.Core: Document :: Text -> Map Text Value -> Document
- Langchain.DocumentLoader.Core: [metadata] :: Document -> Map Text Value
- Langchain.DocumentLoader.Core: [pageContent] :: Document -> Text
- Langchain.DocumentLoader.Core: class BaseLoader loader
- Langchain.DocumentLoader.Core: data Document
- Langchain.DocumentLoader.Core: instance GHC.Base.Monoid Langchain.DocumentLoader.Core.Document
- Langchain.DocumentLoader.Core: instance GHC.Base.Semigroup Langchain.DocumentLoader.Core.Document
- Langchain.DocumentLoader.Core: instance GHC.Classes.Eq Langchain.DocumentLoader.Core.Document
- Langchain.DocumentLoader.Core: instance GHC.Show.Show Langchain.DocumentLoader.Core.Document
- Langchain.DocumentLoader.Core: load :: (BaseLoader loader, MonadIO m, MonadError LangchainError m) => loader -> m [Document]
- Langchain.DocumentLoader.Core: loadAndSplit :: (BaseLoader loader, MonadIO m, MonadError LangchainError m) => loader -> m [Text]
- Langchain.DocumentLoader.Csv: CsvLoader :: FilePath -> Char -> Maybe [Text] -> Maybe (Text -> [Text]) -> CsvLoader
- Langchain.DocumentLoader.Csv: [csvContentColumns] :: CsvLoader -> Maybe [Text]
- Langchain.DocumentLoader.Csv: [csvDelimiter] :: CsvLoader -> Char
- Langchain.DocumentLoader.Csv: [csvFilePath] :: CsvLoader -> FilePath
- Langchain.DocumentLoader.Csv: [csvSplitter] :: CsvLoader -> Maybe (Text -> [Text])
- Langchain.DocumentLoader.Csv: data CsvLoader
- Langchain.DocumentLoader.Csv: defaultCsvLoader :: FilePath -> CsvLoader
- Langchain.DocumentLoader.Csv: instance Langchain.DocumentLoader.Core.BaseLoader Langchain.DocumentLoader.Csv.CsvLoader
- Langchain.DocumentLoader.Csv: parseCsvRows :: Char -> Text -> [[Text]]
- Langchain.DocumentLoader.DirectoryLoader: DirectoryLoader :: FilePath -> DirectoryLoaderOptions -> DirectoryLoader
- Langchain.DocumentLoader.DirectoryLoader: DirectoryLoaderOptions :: Maybe Int -> [String] -> Bool -> Bool -> DirectoryLoaderOptions
- Langchain.DocumentLoader.DirectoryLoader: [dirPath] :: DirectoryLoader -> FilePath
- Langchain.DocumentLoader.DirectoryLoader: [directoryLoaderOptions] :: DirectoryLoader -> DirectoryLoaderOptions
- Langchain.DocumentLoader.DirectoryLoader: [excludeHidden] :: DirectoryLoaderOptions -> Bool
- Langchain.DocumentLoader.DirectoryLoader: [extensions] :: DirectoryLoaderOptions -> [String]
- Langchain.DocumentLoader.DirectoryLoader: [recursiveDepth] :: DirectoryLoaderOptions -> Maybe Int
- Langchain.DocumentLoader.DirectoryLoader: [useMultithreading] :: DirectoryLoaderOptions -> Bool
- Langchain.DocumentLoader.DirectoryLoader: data DirectoryLoader
- Langchain.DocumentLoader.DirectoryLoader: data DirectoryLoaderOptions
- Langchain.DocumentLoader.DirectoryLoader: defaultDirectoryLoaderOptions :: DirectoryLoaderOptions
- Langchain.DocumentLoader.DirectoryLoader: instance GHC.Classes.Eq Langchain.DocumentLoader.DirectoryLoader.DirectoryLoader
- Langchain.DocumentLoader.DirectoryLoader: instance GHC.Classes.Eq Langchain.DocumentLoader.DirectoryLoader.DirectoryLoaderOptions
- Langchain.DocumentLoader.DirectoryLoader: instance GHC.Show.Show Langchain.DocumentLoader.DirectoryLoader.DirectoryLoader
- Langchain.DocumentLoader.DirectoryLoader: instance GHC.Show.Show Langchain.DocumentLoader.DirectoryLoader.DirectoryLoaderOptions
- Langchain.DocumentLoader.DirectoryLoader: instance Langchain.DocumentLoader.Core.BaseLoader Langchain.DocumentLoader.DirectoryLoader.DirectoryLoader
- Langchain.DocumentLoader.FileLoader: FileLoader :: FilePath -> FileLoader
- Langchain.DocumentLoader.FileLoader: instance GHC.Classes.Eq Langchain.DocumentLoader.FileLoader.FileLoader
- Langchain.DocumentLoader.FileLoader: instance GHC.Show.Show Langchain.DocumentLoader.FileLoader.FileLoader
- Langchain.DocumentLoader.FileLoader: instance Langchain.DocumentLoader.Core.BaseLoader Langchain.DocumentLoader.FileLoader.FileLoader
- Langchain.DocumentLoader.FileLoader: newtype FileLoader
- Langchain.Embeddings.Core: class Embeddings embed
- Langchain.Embeddings.Core: embedDocuments :: (Embeddings embed, MonadIO m, MonadError LangchainError m) => embed -> [Document] -> m [[Float]]
- Langchain.Embeddings.Core: embedQuery :: (Embeddings embed, MonadIO m, MonadError LangchainError m) => embed -> Text -> m [Float]
- Langchain.Embeddings.Ollama: OllamaEmbeddings :: Text -> Maybe Bool -> Maybe Text -> Maybe ModelOptions -> OllamaEmbeddings
- Langchain.Embeddings.Ollama: [defaultKeepAlive] :: OllamaEmbeddings -> Maybe Text
- Langchain.Embeddings.Ollama: [defaultTruncate] :: OllamaEmbeddings -> Maybe Bool
- Langchain.Embeddings.Ollama: [modelOptions] :: OllamaEmbeddings -> Maybe ModelOptions
- Langchain.Embeddings.Ollama: [model] :: OllamaEmbeddings -> Text
- Langchain.Embeddings.Ollama: data OllamaEmbeddings
- Langchain.Embeddings.Ollama: instance GHC.Classes.Eq Langchain.Embeddings.Ollama.OllamaEmbeddings
- Langchain.Embeddings.Ollama: instance GHC.Show.Show Langchain.Embeddings.Ollama.OllamaEmbeddings
- Langchain.Embeddings.Ollama: instance Langchain.Embeddings.Core.Embeddings Langchain.Embeddings.Ollama.OllamaEmbeddings
- Langchain.Embeddings.OpenAI: Base64Format :: EncodingFormat
- Langchain.Embeddings.OpenAI: FloatFormat :: EncodingFormat
- Langchain.Embeddings.OpenAI: OpenAIEmbeddings :: Text -> Maybe String -> Text -> Maybe Int -> Maybe EncodingFormat -> Maybe Int -> OpenAIEmbeddings
- Langchain.Embeddings.OpenAI: [apiKey] :: OpenAIEmbeddings -> Text
- Langchain.Embeddings.OpenAI: [baseUrl] :: OpenAIEmbeddings -> Maybe String
- Langchain.Embeddings.OpenAI: [dimensions] :: OpenAIEmbeddings -> Maybe Int
- Langchain.Embeddings.OpenAI: [encodingFormat] :: OpenAIEmbeddings -> Maybe EncodingFormat
- Langchain.Embeddings.OpenAI: [model] :: OpenAIEmbeddings -> Text
- Langchain.Embeddings.OpenAI: [timeout] :: OpenAIEmbeddings -> Maybe Int
- Langchain.Embeddings.OpenAI: data EncodingFormat
- Langchain.Embeddings.OpenAI: data OpenAIEmbeddings
- Langchain.Embeddings.OpenAI: defaultOpenAIEmbeddings :: OpenAIEmbeddings
- Langchain.Embeddings.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Embeddings.OpenAI.EmbeddingsObject
- Langchain.Embeddings.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Embeddings.OpenAI.EmbeddingsUsage
- Langchain.Embeddings.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Embeddings.OpenAI.OpenAIEmbeddingsResponse
- Langchain.Embeddings.OpenAI: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Embeddings.OpenAI.EmbeddingsInput
- Langchain.Embeddings.OpenAI: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Embeddings.OpenAI.EncodingFormat
- Langchain.Embeddings.OpenAI: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Embeddings.OpenAI.OpenAIEmbeddingsRequest
- Langchain.Embeddings.OpenAI: instance GHC.Classes.Eq Langchain.Embeddings.OpenAI.EmbeddingsInput
- Langchain.Embeddings.OpenAI: instance GHC.Classes.Eq Langchain.Embeddings.OpenAI.EmbeddingsObject
- Langchain.Embeddings.OpenAI: instance GHC.Classes.Eq Langchain.Embeddings.OpenAI.EmbeddingsUsage
- Langchain.Embeddings.OpenAI: instance GHC.Classes.Eq Langchain.Embeddings.OpenAI.EncodingFormat
- Langchain.Embeddings.OpenAI: instance GHC.Classes.Eq Langchain.Embeddings.OpenAI.OpenAIEmbeddings
- Langchain.Embeddings.OpenAI: instance GHC.Classes.Eq Langchain.Embeddings.OpenAI.OpenAIEmbeddingsRequest
- Langchain.Embeddings.OpenAI: instance GHC.Classes.Eq Langchain.Embeddings.OpenAI.OpenAIEmbeddingsResponse
- Langchain.Embeddings.OpenAI: instance GHC.Generics.Generic Langchain.Embeddings.OpenAI.EmbeddingsObject
- Langchain.Embeddings.OpenAI: instance GHC.Generics.Generic Langchain.Embeddings.OpenAI.EmbeddingsUsage
- Langchain.Embeddings.OpenAI: instance GHC.Generics.Generic Langchain.Embeddings.OpenAI.EncodingFormat
- Langchain.Embeddings.OpenAI: instance GHC.Generics.Generic Langchain.Embeddings.OpenAI.OpenAIEmbeddings
- Langchain.Embeddings.OpenAI: instance GHC.Generics.Generic Langchain.Embeddings.OpenAI.OpenAIEmbeddingsRequest
- Langchain.Embeddings.OpenAI: instance GHC.Generics.Generic Langchain.Embeddings.OpenAI.OpenAIEmbeddingsResponse
- Langchain.Embeddings.OpenAI: instance GHC.Show.Show Langchain.Embeddings.OpenAI.EmbeddingsInput
- Langchain.Embeddings.OpenAI: instance GHC.Show.Show Langchain.Embeddings.OpenAI.EmbeddingsObject
- Langchain.Embeddings.OpenAI: instance GHC.Show.Show Langchain.Embeddings.OpenAI.EmbeddingsUsage
- Langchain.Embeddings.OpenAI: instance GHC.Show.Show Langchain.Embeddings.OpenAI.EncodingFormat
- Langchain.Embeddings.OpenAI: instance GHC.Show.Show Langchain.Embeddings.OpenAI.OpenAIEmbeddings
- Langchain.Embeddings.OpenAI: instance GHC.Show.Show Langchain.Embeddings.OpenAI.OpenAIEmbeddingsRequest
- Langchain.Embeddings.OpenAI: instance GHC.Show.Show Langchain.Embeddings.OpenAI.OpenAIEmbeddingsResponse
- Langchain.Embeddings.OpenAI: instance Langchain.Embeddings.Core.Embeddings Langchain.Embeddings.OpenAI.OpenAIEmbeddings
- Langchain.Embeddings.OpenAI: textEmbedding3Large :: Text
- Langchain.Embeddings.OpenAI: textEmbedding3Small :: Text
- Langchain.Embeddings.OpenAI: textEmbeddingAda :: Text
- Langchain.Guardrail.Core: Guardrail :: !Text -> (Text -> m GuardrailResult) -> (Text -> m GuardrailResult) -> Guardrail (m :: Type -> Type)
- Langchain.Guardrail.Core: GuardrailFail :: !Text -> GuardrailResult
- Langchain.Guardrail.Core: GuardrailPass :: GuardrailResult
- Langchain.Guardrail.Core: [guardrailName] :: Guardrail (m :: Type -> Type) -> !Text
- Langchain.Guardrail.Core: [validateInput] :: Guardrail (m :: Type -> Type) -> Text -> m GuardrailResult
- Langchain.Guardrail.Core: [validateOutput] :: Guardrail (m :: Type -> Type) -> Text -> m GuardrailResult
- Langchain.Guardrail.Core: composeGuardrails :: forall (m :: Type -> Type). MonadIO m => [Guardrail m] -> Guardrail m
- Langchain.Guardrail.Core: contentSafetyGuardrail :: forall (m :: Type -> Type). MonadIO m => [Text] -> Guardrail m
- Langchain.Guardrail.Core: data Guardrail (m :: Type -> Type)
- Langchain.Guardrail.Core: data GuardrailResult
- Langchain.Guardrail.Core: instance GHC.Classes.Eq Langchain.Guardrail.Core.GuardrailResult
- Langchain.Guardrail.Core: instance GHC.Show.Show Langchain.Guardrail.Core.GuardrailResult
- Langchain.Guardrail.Core: outputLengthGuardrail :: forall (m :: Type -> Type). MonadIO m => Int -> Guardrail m
- Langchain.Guardrail.Core: topicGuardrail :: forall model (m :: Type -> Type). (ChatModel model, MonadIO m, MonadError LangchainError m) => model -> Text -> Guardrail m
- Langchain.Guardrail.Core: withGuardrails :: (MonadIO m, MonadError LangchainError m) => Guardrail m -> (Text -> m Text) -> Text -> m Text
- Langchain.MCP.Client: HttpTransport :: !Text -> McpTransport
- Langchain.MCP.Client: McpClient :: !McpTransport -> !Text -> McpClient
- Langchain.MCP.Client: McpResource :: !Text -> !Text -> !Maybe Text -> McpResource
- Langchain.MCP.Client: McpToolInfo :: !Text -> !Text -> !Value -> McpToolInfo
- Langchain.MCP.Client: StdioTransport :: !FilePath -> ![String] -> McpTransport
- Langchain.MCP.Client: [clientTransport] :: McpClient -> !McpTransport
- Langchain.MCP.Client: [mcpResourceMimeType] :: McpResource -> !Maybe Text
- Langchain.MCP.Client: [mcpResourceName] :: McpResource -> !Text
- Langchain.MCP.Client: [mcpResourceUri] :: McpResource -> !Text
- Langchain.MCP.Client: [mcpToolDescription] :: McpToolInfo -> !Text
- Langchain.MCP.Client: [mcpToolInputSchema] :: McpToolInfo -> !Value
- Langchain.MCP.Client: [mcpToolName] :: McpToolInfo -> !Text
- Langchain.MCP.Client: [serverName] :: McpClient -> !Text
- Langchain.MCP.Client: callMcpTool :: (MonadIO m, MonadError LangchainError m) => McpClient -> Text -> Value -> m Text
- Langchain.MCP.Client: data McpClient
- Langchain.MCP.Client: data McpResource
- Langchain.MCP.Client: data McpToolInfo
- Langchain.MCP.Client: data McpTransport
- Langchain.MCP.Client: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.MCP.Client.McpResource
- Langchain.MCP.Client: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.MCP.Client.McpToolInfo
- Langchain.MCP.Client: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.MCP.Client.McpToolInfo
- Langchain.MCP.Client: instance GHC.Classes.Eq Langchain.MCP.Client.McpClient
- Langchain.MCP.Client: instance GHC.Classes.Eq Langchain.MCP.Client.McpResource
- Langchain.MCP.Client: instance GHC.Classes.Eq Langchain.MCP.Client.McpToolInfo
- Langchain.MCP.Client: instance GHC.Classes.Eq Langchain.MCP.Client.McpTransport
- Langchain.MCP.Client: instance GHC.Show.Show Langchain.MCP.Client.McpClient
- Langchain.MCP.Client: instance GHC.Show.Show Langchain.MCP.Client.McpResource
- Langchain.MCP.Client: instance GHC.Show.Show Langchain.MCP.Client.McpToolInfo
- Langchain.MCP.Client: instance GHC.Show.Show Langchain.MCP.Client.McpTransport
- Langchain.MCP.Client: listMcpTools :: (MonadIO m, MonadError LangchainError m) => McpClient -> m [McpToolInfo]
- Langchain.MCP.Client: mcpToolToLangchainTool :: McpClient -> McpToolInfo -> Tool IO
- Langchain.MCP.Client: newHttpMcpClient :: Text -> Text -> McpClient
- Langchain.MCP.Client: newStdioMcpClient :: Text -> FilePath -> [String] -> McpClient
- Langchain.Memory.Core: TokenBufferMemory :: !Int -> !TVar [Message] -> TokenBufferMemory
- Langchain.Memory.Core: WindowBufferMemory :: !Int -> !TVar [Message] -> WindowBufferMemory
- Langchain.Memory.Core: [maxTokens] :: TokenBufferMemory -> !Int
- Langchain.Memory.Core: [maxWindowSize] :: WindowBufferMemory -> !Int
- Langchain.Memory.Core: [memVar] :: TokenBufferMemory -> !TVar [Message]
- Langchain.Memory.Core: addAiMessage :: (BaseMemory mem, MonadIO m, MonadError LangchainError m) => mem -> Text -> m ()
- Langchain.Memory.Core: addMessage :: (BaseMemory mem, MonadIO m, MonadError LangchainError m) => mem -> Message -> m ()
- Langchain.Memory.Core: addUserMessage :: (BaseMemory mem, MonadIO m, MonadError LangchainError m) => mem -> Text -> m ()
- Langchain.Memory.Core: class BaseMemory mem
- Langchain.Memory.Core: clear :: (BaseMemory mem, MonadIO m, MonadError LangchainError m) => mem -> m ()
- Langchain.Memory.Core: countTokens :: [Message] -> Int
- Langchain.Memory.Core: data TokenBufferMemory
- Langchain.Memory.Core: data WindowBufferMemory
- Langchain.Memory.Core: initialMessages :: Text -> [Message]
- Langchain.Memory.Core: instance GHC.Classes.Eq Langchain.Memory.Core.TokenBufferMemory
- Langchain.Memory.Core: instance GHC.Classes.Eq Langchain.Memory.Core.WindowBufferMemory
- Langchain.Memory.Core: instance GHC.Show.Show Langchain.Memory.Core.TokenBufferMemory
- Langchain.Memory.Core: instance GHC.Show.Show Langchain.Memory.Core.WindowBufferMemory
- Langchain.Memory.Core: instance Langchain.Memory.Core.BaseMemory Langchain.Memory.Core.TokenBufferMemory
- Langchain.Memory.Core: instance Langchain.Memory.Core.BaseMemory Langchain.Memory.Core.WindowBufferMemory
- Langchain.Memory.Core: messages :: (BaseMemory mem, MonadIO m, MonadError LangchainError m) => mem -> m [Message]
- Langchain.Memory.Core: newTokenBufferMemory :: MonadIO m => Int -> [Message] -> m TokenBufferMemory
- Langchain.Memory.Core: newWindowBufferMemory :: MonadIO m => Int -> [Message] -> m WindowBufferMemory
- Langchain.Memory.Core: trimMessages :: Int -> [Message] -> [Message]
- Langchain.Memory.Entity: EntityMemory :: model -> !TVar (Map Text Text) -> !TVar [Message] -> EntityMemory model
- Langchain.Memory.Entity: [entityMessagesVar] :: EntityMemory model -> !TVar [Message]
- Langchain.Memory.Entity: [entityModel] :: EntityMemory model -> model
- Langchain.Memory.Entity: [entityStoreVar] :: EntityMemory model -> !TVar (Map Text Text)
- Langchain.Memory.Entity: data EntityMemory model
- Langchain.Memory.Entity: getEntities :: MonadIO m => EntityMemory model -> m (Map Text Text)
- Langchain.Memory.Entity: instance Langchain.Core.Model.ChatModel model => Langchain.Memory.Core.BaseMemory (Langchain.Memory.Entity.EntityMemory model)
- Langchain.Memory.Entity: newEntityMemory :: MonadIO m => model -> [Message] -> m (EntityMemory model)
- Langchain.Memory.Entity: setEntity :: MonadIO m => EntityMemory model -> Text -> Text -> m ()
- Langchain.Memory.Summary: SummaryMemory :: model -> !Int -> !TVar Text -> !TVar [Message] -> SummaryMemory model
- Langchain.Memory.Summary: [maxMessageThreshold] :: SummaryMemory model -> !Int
- Langchain.Memory.Summary: [recentMessagesVar] :: SummaryMemory model -> !TVar [Message]
- Langchain.Memory.Summary: [summaryBufferVar] :: SummaryMemory model -> !TVar Text
- Langchain.Memory.Summary: [summaryModel] :: SummaryMemory model -> model
- Langchain.Memory.Summary: data SummaryMemory model
- Langchain.Memory.Summary: getSummary :: MonadIO m => SummaryMemory model -> m Text
- Langchain.Memory.Summary: instance Langchain.Core.Model.ChatModel model => Langchain.Memory.Core.BaseMemory (Langchain.Memory.Summary.SummaryMemory model)
- Langchain.Memory.Summary: newSummaryMemory :: MonadIO m => model -> Int -> [Message] -> m (SummaryMemory model)
- Langchain.Observability: ClientSpan :: SpanKind
- Langchain.Observability: ConsumerSpan :: SpanKind
- Langchain.Observability: DebugLevel :: LogLevel
- Langchain.Observability: ErrorLevel :: LogLevel
- Langchain.Observability: InMemoryLogger :: !TVar [LogEvent] -> !LogLevel -> InMemoryLogger
- Langchain.Observability: InfoLevel :: LogLevel
- Langchain.Observability: InternalSpan :: SpanKind
- Langchain.Observability: LogEvent :: !LogLevel -> !UTCTime -> !Text -> !Text -> !Map Text Text -> LogEvent
- Langchain.Observability: Logger :: !LogLevel -> (LogEvent -> IO ()) -> Logger
- Langchain.Observability: OTelTracer :: !Text -> !TVar [Span] -> OTelTracer
- Langchain.Observability: ProducerSpan :: SpanKind
- Langchain.Observability: ServerSpan :: SpanKind
- Langchain.Observability: Span :: !Text -> !Text -> !Text -> !Maybe Text -> !SpanKind -> !UTCTime -> !Maybe UTCTime -> !Maybe Int -> !Map Text Text -> !SpanStatus -> Span
- Langchain.Observability: StatusError :: !Text -> SpanStatus
- Langchain.Observability: StatusOk :: SpanStatus
- Langchain.Observability: StatusUnset :: SpanStatus
- Langchain.Observability: WarnLevel :: LogLevel
- Langchain.Observability: [inMemoryMinLevel] :: InMemoryLogger -> !LogLevel
- Langchain.Observability: [inMemoryVar] :: InMemoryLogger -> !TVar [LogEvent]
- Langchain.Observability: [logComponent] :: LogEvent -> !Text
- Langchain.Observability: [logLevel] :: LogEvent -> !LogLevel
- Langchain.Observability: [logMessage] :: LogEvent -> !Text
- Langchain.Observability: [logMetadata] :: LogEvent -> !Map Text Text
- Langchain.Observability: [logTimestamp] :: LogEvent -> !UTCTime
- Langchain.Observability: [minLevel] :: Logger -> !LogLevel
- Langchain.Observability: [spanAttributes] :: Span -> !Map Text Text
- Langchain.Observability: [spanDurationMicros] :: Span -> !Maybe Int
- Langchain.Observability: [spanEndTime] :: Span -> !Maybe UTCTime
- Langchain.Observability: [spanId] :: Span -> !Text
- Langchain.Observability: [spanKind] :: Span -> !SpanKind
- Langchain.Observability: [spanName] :: Span -> !Text
- Langchain.Observability: [spanParentId] :: Span -> !Maybe Text
- Langchain.Observability: [spanStartTime] :: Span -> !UTCTime
- Langchain.Observability: [spanStatus] :: Span -> !SpanStatus
- Langchain.Observability: [spanTraceId] :: Span -> !Text
- Langchain.Observability: [tracerSpansVar] :: OTelTracer -> !TVar [Span]
- Langchain.Observability: [tracerTraceId] :: OTelTracer -> !Text
- Langchain.Observability: [writeLog] :: Logger -> LogEvent -> IO ()
- Langchain.Observability: addSpanAttribute :: MonadIO m => OTelTracer -> Text -> Text -> Text -> m ()
- Langchain.Observability: data InMemoryLogger
- Langchain.Observability: data LogEvent
- Langchain.Observability: data LogLevel
- Langchain.Observability: data Logger
- Langchain.Observability: data OTelTracer
- Langchain.Observability: data Span
- Langchain.Observability: data SpanKind
- Langchain.Observability: data SpanStatus
- Langchain.Observability: endSpan :: MonadIO m => OTelTracer -> Text -> SpanStatus -> m ()
- Langchain.Observability: exportSpansJson :: MonadIO m => OTelTracer -> m Text
- Langchain.Observability: getInMemoryLogs :: MonadIO m => InMemoryLogger -> m [LogEvent]
- Langchain.Observability: getSpans :: MonadIO m => OTelTracer -> m [Span]
- Langchain.Observability: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Observability.LogEvent
- Langchain.Observability: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Observability.LogLevel
- Langchain.Observability: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Observability.Span
- Langchain.Observability: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Observability.SpanKind
- Langchain.Observability: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Observability.SpanStatus
- Langchain.Observability: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Observability.LogEvent
- Langchain.Observability: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Observability.LogLevel
- Langchain.Observability: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Observability.Span
- Langchain.Observability: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Observability.SpanKind
- Langchain.Observability: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Observability.SpanStatus
- Langchain.Observability: instance GHC.Classes.Eq Langchain.Observability.LogEvent
- Langchain.Observability: instance GHC.Classes.Eq Langchain.Observability.LogLevel
- Langchain.Observability: instance GHC.Classes.Eq Langchain.Observability.Span
- Langchain.Observability: instance GHC.Classes.Eq Langchain.Observability.SpanKind
- Langchain.Observability: instance GHC.Classes.Eq Langchain.Observability.SpanStatus
- Langchain.Observability: instance GHC.Classes.Ord Langchain.Observability.LogLevel
- Langchain.Observability: instance GHC.Enum.Bounded Langchain.Observability.LogLevel
- Langchain.Observability: instance GHC.Enum.Enum Langchain.Observability.LogLevel
- Langchain.Observability: instance GHC.Generics.Generic Langchain.Observability.LogEvent
- Langchain.Observability: instance GHC.Generics.Generic Langchain.Observability.LogLevel
- Langchain.Observability: instance GHC.Generics.Generic Langchain.Observability.Span
- Langchain.Observability: instance GHC.Generics.Generic Langchain.Observability.SpanKind
- Langchain.Observability: instance GHC.Generics.Generic Langchain.Observability.SpanStatus
- Langchain.Observability: instance GHC.Show.Show Langchain.Observability.LogEvent
- Langchain.Observability: instance GHC.Show.Show Langchain.Observability.LogLevel
- Langchain.Observability: instance GHC.Show.Show Langchain.Observability.Span
- Langchain.Observability: instance GHC.Show.Show Langchain.Observability.SpanKind
- Langchain.Observability: instance GHC.Show.Show Langchain.Observability.SpanStatus
- Langchain.Observability: logDebug :: MonadIO m => Logger -> Text -> Text -> m ()
- Langchain.Observability: logError :: MonadIO m => Logger -> Text -> Text -> m ()
- Langchain.Observability: logEvent :: MonadIO m => Logger -> LogLevel -> Text -> Text -> Map Text Text -> m ()
- Langchain.Observability: logInfo :: MonadIO m => Logger -> Text -> Text -> m ()
- Langchain.Observability: logWarn :: MonadIO m => Logger -> Text -> Text -> m ()
- Langchain.Observability: newInMemoryLogger :: MonadIO m => LogLevel -> m InMemoryLogger
- Langchain.Observability: newOTelTracer :: MonadIO m => Maybe Text -> m OTelTracer
- Langchain.Observability: startSpan :: MonadIO m => OTelTracer -> Text -> Maybe Text -> SpanKind -> Map Text Text -> m Span
- Langchain.Observability: stderrLogger :: LogLevel -> Logger
- Langchain.Observability: withSpan :: (MonadIO m, MonadError LangchainError m) => OTelTracer -> Text -> Maybe Text -> SpanKind -> Map Text Text -> m a -> m a
- Langchain.OutputParser.Core: CommaSeparatedList :: [Text] -> CommaSeparatedList
- Langchain.OutputParser.Core: JSONOutputStructure :: a -> JSONOutputStructure a
- Langchain.OutputParser.Core: NumberSeparatedList :: [Text] -> NumberSeparatedList
- Langchain.OutputParser.Core: [jsonValue] :: JSONOutputStructure a -> a
- Langchain.OutputParser.Core: class OutputParser a
- Langchain.OutputParser.Core: instance Data.Aeson.Types.FromJSON.FromJSON a => Data.Aeson.Types.FromJSON.FromJSON (Langchain.OutputParser.Core.JSONOutputStructure a)
- Langchain.OutputParser.Core: instance Data.Aeson.Types.FromJSON.FromJSON a => Langchain.OutputParser.Core.OutputParser (Langchain.OutputParser.Core.JSONOutputStructure a)
- Langchain.OutputParser.Core: instance GHC.Classes.Eq Langchain.OutputParser.Core.CommaSeparatedList
- Langchain.OutputParser.Core: instance GHC.Classes.Eq Langchain.OutputParser.Core.NumberSeparatedList
- Langchain.OutputParser.Core: instance GHC.Classes.Eq a => GHC.Classes.Eq (Langchain.OutputParser.Core.JSONOutputStructure a)
- Langchain.OutputParser.Core: instance GHC.Show.Show Langchain.OutputParser.Core.CommaSeparatedList
- Langchain.OutputParser.Core: instance GHC.Show.Show Langchain.OutputParser.Core.NumberSeparatedList
- Langchain.OutputParser.Core: instance GHC.Show.Show a => GHC.Show.Show (Langchain.OutputParser.Core.JSONOutputStructure a)
- Langchain.OutputParser.Core: instance Langchain.OutputParser.Core.OutputParser GHC.Types.Bool
- Langchain.OutputParser.Core: instance Langchain.OutputParser.Core.OutputParser Langchain.OutputParser.Core.CommaSeparatedList
- Langchain.OutputParser.Core: instance Langchain.OutputParser.Core.OutputParser Langchain.OutputParser.Core.NumberSeparatedList
- Langchain.OutputParser.Core: newtype CommaSeparatedList
- Langchain.OutputParser.Core: newtype FromJSON a => JSONOutputStructure a
- Langchain.OutputParser.Core: newtype NumberSeparatedList
- Langchain.OutputParser.Core: parse :: OutputParser a => Text -> LangchainResult a
- Langchain.OutputParser.Structured: ($dmoutputSchema) :: (StructuredOutput a, GRecordSchema (Rep a)) => Proxy a -> Value
- Langchain.OutputParser.Structured: ($dmtypeJsonSchema) :: (TypeSchema a, GRecordSchema (Rep a)) => Proxy a -> Value
- Langchain.OutputParser.Structured: class GRecordSchema (f :: Type -> Type)
- Langchain.OutputParser.Structured: class FromJSON a => StructuredOutput a
- Langchain.OutputParser.Structured: class TypeSchema a
- Langchain.OutputParser.Structured: extractJsonFromMarkdown :: Text -> Text
- Langchain.OutputParser.Structured: fromOllamaSchema :: Schema -> Value
- Langchain.OutputParser.Structured: gRecordSchema :: GRecordSchema f => Proxy f -> ([(Key, Value)], [Text])
- Langchain.OutputParser.Structured: genericJsonSchema :: GRecordSchema (Rep a) => Proxy a -> Value
- Langchain.OutputParser.Structured: instance (GHC.Generics.Selector s, Langchain.OutputParser.Structured.TypeSchema a) => Langchain.OutputParser.Structured.GRecordSchema (GHC.Generics.M1 GHC.Generics.S s (GHC.Generics.K1 GHC.Generics.R a))
- Langchain.OutputParser.Structured: instance (Langchain.OutputParser.Structured.GRecordSchema f, Langchain.OutputParser.Structured.GRecordSchema g) => Langchain.OutputParser.Structured.GRecordSchema (f GHC.Generics.:*: g)
- Langchain.OutputParser.Structured: instance Langchain.OutputParser.Structured.GRecordSchema f => Langchain.OutputParser.Structured.GRecordSchema (GHC.Generics.M1 GHC.Generics.C c f)
- Langchain.OutputParser.Structured: instance Langchain.OutputParser.Structured.GRecordSchema f => Langchain.OutputParser.Structured.GRecordSchema (GHC.Generics.M1 GHC.Generics.D c f)
- Langchain.OutputParser.Structured: instance Langchain.OutputParser.Structured.TypeSchema Data.Aeson.Types.Internal.Value
- Langchain.OutputParser.Structured: instance Langchain.OutputParser.Structured.TypeSchema Data.Scientific.Scientific
- Langchain.OutputParser.Structured: instance Langchain.OutputParser.Structured.TypeSchema Data.Text.Internal.Text
- Langchain.OutputParser.Structured: instance Langchain.OutputParser.Structured.TypeSchema Data.Time.Calendar.Days.Day
- Langchain.OutputParser.Structured: instance Langchain.OutputParser.Structured.TypeSchema Data.Time.Clock.Internal.UTCTime.UTCTime
- Langchain.OutputParser.Structured: instance Langchain.OutputParser.Structured.TypeSchema GHC.Base.String
- Langchain.OutputParser.Structured: instance Langchain.OutputParser.Structured.TypeSchema GHC.Int.Int16
- Langchain.OutputParser.Structured: instance Langchain.OutputParser.Structured.TypeSchema GHC.Int.Int32
- Langchain.OutputParser.Structured: instance Langchain.OutputParser.Structured.TypeSchema GHC.Int.Int64
- Langchain.OutputParser.Structured: instance Langchain.OutputParser.Structured.TypeSchema GHC.Int.Int8
- Langchain.OutputParser.Structured: instance Langchain.OutputParser.Structured.TypeSchema GHC.Num.Integer.Integer
- Langchain.OutputParser.Structured: instance Langchain.OutputParser.Structured.TypeSchema GHC.Types.Bool
- Langchain.OutputParser.Structured: instance Langchain.OutputParser.Structured.TypeSchema GHC.Types.Char
- Langchain.OutputParser.Structured: instance Langchain.OutputParser.Structured.TypeSchema GHC.Types.Double
- Langchain.OutputParser.Structured: instance Langchain.OutputParser.Structured.TypeSchema GHC.Types.Float
- Langchain.OutputParser.Structured: instance Langchain.OutputParser.Structured.TypeSchema GHC.Types.Int
- Langchain.OutputParser.Structured: instance Langchain.OutputParser.Structured.TypeSchema GHC.Types.Word
- Langchain.OutputParser.Structured: instance Langchain.OutputParser.Structured.TypeSchema GHC.Word.Word16
- Langchain.OutputParser.Structured: instance Langchain.OutputParser.Structured.TypeSchema GHC.Word.Word32
- Langchain.OutputParser.Structured: instance Langchain.OutputParser.Structured.TypeSchema GHC.Word.Word64
- Langchain.OutputParser.Structured: instance Langchain.OutputParser.Structured.TypeSchema GHC.Word.Word8
- Langchain.OutputParser.Structured: instance Langchain.OutputParser.Structured.TypeSchema a => Langchain.OutputParser.Structured.TypeSchema (Data.Map.Internal.Map Data.Text.Internal.Text a)
- Langchain.OutputParser.Structured: instance Langchain.OutputParser.Structured.TypeSchema a => Langchain.OutputParser.Structured.TypeSchema (GHC.Maybe.Maybe a)
- Langchain.OutputParser.Structured: instance Langchain.OutputParser.Structured.TypeSchema a => Langchain.OutputParser.Structured.TypeSchema [a]
- Langchain.OutputParser.Structured: isOptionalType :: TypeSchema a => Proxy a -> Bool
- Langchain.OutputParser.Structured: outputSchema :: StructuredOutput a => Proxy a -> Value
- Langchain.OutputParser.Structured: structuredInvoke :: (StructuredOutput a, ChatModel model, MonadIO m, MonadError LangchainError m) => model -> [Message] -> m a
- Langchain.OutputParser.Structured: structuredInvokeWithRetries :: (StructuredOutput a, ChatModel model, MonadIO m, MonadError LangchainError m) => model -> [Message] -> Int -> m a
- Langchain.OutputParser.Structured: toOllamaSchema :: Value -> Maybe Schema
- Langchain.OutputParser.Structured: typeJsonSchema :: TypeSchema a => Proxy a -> Value
- Langchain.Prelude: ($dmderiveToolSchema) :: (DeriveToolSchema a, GToolRecordSchema (Rep a)) => Proxy a -> Value
- Langchain.Prelude: ($dmoutputSchema) :: (StructuredOutput a, GRecordSchema (Rep a)) => Proxy a -> Value
- Langchain.Prelude: ($dmtypeJsonSchema) :: (TypeSchema a, GRecordSchema (Rep a)) => Proxy a -> Value
- Langchain.Prelude: (&>&) :: RunnableTree (ExceptT LangchainError IO) a b -> RunnableTree (ExceptT LangchainError IO) a c -> RunnableTree (ExceptT LangchainError IO) a (b, c)
- Langchain.Prelude: (|>>) :: forall (m :: Type -> Type) a b c. RunnableTree m a b -> RunnableTree m b c -> RunnableTree m a c
- Langchain.Prelude: AgentAction :: Message -> [ToolCall] -> AgentStep
- Langchain.Prelude: AgentError :: Text -> Maybe ErrorContext -> LangchainError
- Langchain.Prelude: AgentFinish :: Message -> AgentStep
- Langchain.Prelude: AudioBlock :: Text -> Text -> ContentBlock
- Langchain.Prelude: BM25Index :: ![Document] -> !Map Int Int -> !Double -> !Map Text (Map Int Int) -> !Double -> !Double -> BM25Index
- Langchain.Prelude: CSharp :: Language
- Langchain.Prelude: CachedModel :: model -> cache -> CachedModel model cache
- Langchain.Prelude: CallbackHandler :: !Text -> (CallbackEvent -> IO ()) -> CallbackHandler
- Langchain.Prelude: CallbackManager :: TVar [CallbackHandler] -> CallbackManager
- Langchain.Prelude: ChainEnd :: Text -> Text -> Value -> StreamEvent
- Langchain.Prelude: ChainStart :: Text -> Text -> Value -> StreamEvent
- Langchain.Prelude: CharacterSplitterOps :: Int64 -> Text -> CharacterSplitterOps
- Langchain.Prelude: CircuitBreaker :: !Text -> !CircuitBreakerConfig -> !TVar (CircuitState, Int) -> CircuitBreaker
- Langchain.Prelude: CircuitBreakerConfig :: !Int -> !Double -> CircuitBreakerConfig
- Langchain.Prelude: CircuitClosed :: CircuitState
- Langchain.Prelude: CircuitHalfOpen :: CircuitState
- Langchain.Prelude: CircuitOpen :: !UTCTime -> CircuitState
- Langchain.Prelude: ClientSpan :: SpanKind
- Langchain.Prelude: CodeSplitterOps :: Language -> Int64 -> Int64 -> CodeSplitterOps
- Langchain.Prelude: CommaSeparatedList :: [Text] -> CommaSeparatedList
- Langchain.Prelude: ConditionalEdge :: (s -> m (Either LangchainError NodeId)) -> Edge s (m :: Type -> Type)
- Langchain.Prelude: ConfigurationError :: Text -> Maybe ErrorContext -> LangchainError
- Langchain.Prelude: ConsumerSpan :: SpanKind
- Langchain.Prelude: Cpp :: Language
- Langchain.Prelude: CsvLoader :: FilePath -> Char -> Maybe [Text] -> Maybe (Text -> [Text]) -> CsvLoader
- Langchain.Prelude: DataBlock :: ByteString -> ContentBlock
- Langchain.Prelude: DebugLevel :: LogLevel
- Langchain.Prelude: DirectoryLoader :: FilePath -> DirectoryLoaderOptions -> DirectoryLoader
- Langchain.Prelude: DirectoryLoaderOptions :: Maybe Int -> [String] -> Bool -> Bool -> DirectoryLoaderOptions
- Langchain.Prelude: Document :: Text -> Map Text Value -> Document
- Langchain.Prelude: DocumentLoaderError :: Text -> Maybe ErrorContext -> LangchainError
- Langchain.Prelude: EmbeddingError :: Text -> Maybe ErrorContext -> LangchainError
- Langchain.Prelude: EntityMemory :: model -> !TVar (Map Text Text) -> !TVar [Message] -> EntityMemory model
- Langchain.Prelude: ErrorContext :: Text -> Text -> UTCTime -> Map Text Text -> ErrorContext
- Langchain.Prelude: ErrorLevel :: LogLevel
- Langchain.Prelude: FString :: TemplateFormat
- Langchain.Prelude: FewShotPromptTemplate :: Text -> [Map Text Text] -> Text -> Text -> Text -> FewShotPromptTemplate
- Langchain.Prelude: FileLoader :: FilePath -> FileLoader
- Langchain.Prelude: Go :: Language
- Langchain.Prelude: Guardrail :: !Text -> (Text -> m GuardrailResult) -> (Text -> m GuardrailResult) -> Guardrail (m :: Type -> Type)
- Langchain.Prelude: GuardrailFail :: !Text -> GuardrailResult
- Langchain.Prelude: GuardrailPass :: GuardrailResult
- Langchain.Prelude: Haskell :: Language
- Langchain.Prelude: HttpTransport :: !Text -> McpTransport
- Langchain.Prelude: HybridRetriever :: !BM25Index -> !Text -> Int -> IO [Document] -> !Double -> !Double -> !Double -> HybridRetriever
- Langchain.Prelude: ImageBlock :: ImageContent -> ContentBlock
- Langchain.Prelude: InMemory :: m -> Map Int64 (Document, [Float]) -> InMemory m
- Langchain.Prelude: InMemoryCache :: TVar (Map Text Message) -> InMemoryCache
- Langchain.Prelude: InMemoryLogger :: !TVar [LogEvent] -> !LogLevel -> InMemoryLogger
- Langchain.Prelude: InfoLevel :: LogLevel
- Langchain.Prelude: InternalError :: Text -> Maybe ErrorContext -> LangchainError
- Langchain.Prelude: InternalSpan :: SpanKind
- Langchain.Prelude: JSONOutputStructure :: a -> JSONOutputStructure a
- Langchain.Prelude: Java :: Language
- Langchain.Prelude: JavaScript :: Language
- Langchain.Prelude: LLMChunk :: Text -> Text -> Maybe ToolCall -> StreamEvent
- Langchain.Prelude: LLMEnd :: Text -> Message -> Maybe TokenUsage -> StreamEvent
- Langchain.Prelude: LLMError :: Text -> Maybe ErrorContext -> LangchainError
- Langchain.Prelude: LLMStart :: Text -> Text -> [Message] -> StreamEvent
- Langchain.Prelude: LogEvent :: !LogLevel -> !UTCTime -> !Text -> !Text -> !Map Text Text -> LogEvent
- Langchain.Prelude: Logger :: !LogLevel -> (LogEvent -> IO ()) -> Logger
- Langchain.Prelude: MapReduceChain :: model -> PromptTemplate -> PromptTemplate -> Text -> Text -> MapReduceChain model
- Langchain.Prelude: MarkdownCode :: Language
- Langchain.Prelude: MarkdownSplitterOps :: Int64 -> Int64 -> [(Text, Text)] -> MarkdownSplitterOps
- Langchain.Prelude: McpClient :: !McpTransport -> !Text -> McpClient
- Langchain.Prelude: McpResource :: !Text -> !Text -> !Maybe Text -> McpResource
- Langchain.Prelude: McpToolInfo :: !Text -> !Text -> !Value -> McpToolInfo
- Langchain.Prelude: MemoryCheckpointer :: TVar (Map (Text, NodeId) ByteString) -> MemoryCheckpointer
- Langchain.Prelude: MemoryError :: Text -> Maybe ErrorContext -> LangchainError
- Langchain.Prelude: Message :: Role -> NonEmpty ContentBlock -> Maybe Text -> Maybe [ToolCall] -> Maybe Text -> Map Text Value -> Message
- Langchain.Prelude: ModelOptions :: !Maybe Int -> !Maybe Int -> !Maybe Int -> !Maybe Int -> !Maybe Int -> !Maybe Double -> !Maybe Double -> !Maybe Double -> !Maybe Int -> !Maybe Double -> !Maybe Double -> !Maybe Double -> !Maybe Double -> !Maybe Bool -> !Maybe [Text] -> !Maybe Bool -> !Maybe Int -> !Maybe Int -> !Maybe Int -> !Maybe Int -> !Maybe Bool -> !Maybe Int -> ModelOptions
- Langchain.Prelude: ModelRunnable :: c -> ModelRunnable c
- Langchain.Prelude: NetworkError :: Text -> Maybe ErrorContext -> LangchainError
- Langchain.Prelude: Node :: NodeId -> (s -> m (Either LangchainError s)) -> Node s (m :: Type -> Type)
- Langchain.Prelude: NodeEnd :: Text -> Text -> Value -> StreamEvent
- Langchain.Prelude: NodeStart :: Text -> Text -> Value -> StreamEvent
- Langchain.Prelude: NumberSeparatedList :: [Text] -> NumberSeparatedList
- Langchain.Prelude: OTelTracer :: !Text -> !TVar [Span] -> OTelTracer
- Langchain.Prelude: Ollama :: OllamaClient -> Text -> Ollama
- Langchain.Prelude: OllamaClientConfig :: !Text -> !Int -> !RetryPolicy -> !Maybe Manager -> ![(CI ByteString, ByteString)] -> !Maybe Text -> !Maybe (LogLevel -> Text -> IO ()) -> !Maybe (IO ()) -> !Maybe (IO ()) -> !Maybe (IO ()) -> OllamaClientConfig
- Langchain.Prelude: OllamaEmbeddings :: Text -> Maybe Bool -> Maybe Text -> Maybe ModelOptions -> OllamaEmbeddings
- Langchain.Prelude: OllamaWithTools :: !Ollama -> ![Tool m] -> OllamaWithTools (m :: Type -> Type)
- Langchain.Prelude: OnChainEnd :: !Text -> !Text -> !Int -> !UTCTime -> CallbackEvent
- Langchain.Prelude: OnChainStart :: !Text -> !Text -> !UTCTime -> CallbackEvent
- Langchain.Prelude: OnError :: !Text -> !Text -> !UTCTime -> CallbackEvent
- Langchain.Prelude: OnGraphNodeEnd :: !Text -> !Text -> !Int -> !UTCTime -> CallbackEvent
- Langchain.Prelude: OnGraphNodeStart :: !Text -> !Text -> !UTCTime -> CallbackEvent
- Langchain.Prelude: OnLLMEnd :: !Text -> !Text -> !Int -> !UTCTime -> CallbackEvent
- Langchain.Prelude: OnLLMStart :: !Text -> ![Text] -> !UTCTime -> CallbackEvent
- Langchain.Prelude: OnRetrieverEnd :: !Text -> ![Text] -> !Int -> !UTCTime -> CallbackEvent
- Langchain.Prelude: OnRetrieverStart :: !Text -> !Text -> !UTCTime -> CallbackEvent
- Langchain.Prelude: OnToolEnd :: !Text -> !Text -> !Int -> !UTCTime -> CallbackEvent
- Langchain.Prelude: OnToolStart :: !Text -> !Value -> !UTCTime -> CallbackEvent
- Langchain.Prelude: OpenAIEmbeddings :: Text -> Maybe String -> Text -> Maybe Int -> Maybe EncodingFormat -> Maybe Int -> OpenAIEmbeddings
- Langchain.Prelude: ParsingError :: Text -> Maybe ErrorContext -> LangchainError
- Langchain.Prelude: Plan :: [PlanStep] -> Plan
- Langchain.Prelude: PlanAndExecuteAgent :: planner -> executor -> Maybe Text -> PlanAndExecuteAgent planner executor
- Langchain.Prelude: PlanStep :: !Int -> !Text -> PlanStep
- Langchain.Prelude: ProducerSpan :: SpanKind
- Langchain.Prelude: PromptTemplate :: Text -> [Text] -> Map Text Text -> TemplateFormat -> PromptTemplate
- Langchain.Prelude: PromptTemplateOptions :: Map Text Text -> PromptTemplateOptions
- Langchain.Prelude: Python :: Language
- Langchain.Prelude: RateLimiter :: !Double -> !Double -> !TVar Double -> !TVar UTCTime -> RateLimiter
- Langchain.Prelude: ReActAgent :: model -> [Tool m] -> Int -> ReActAgent model (m :: Type -> Type)
- Langchain.Prelude: RecursiveCharacterSplitterOps :: Int64 -> Int64 -> [Text] -> RecursiveCharacterSplitterOps
- Langchain.Prelude: RetrievalQA :: model -> retriever -> PromptTemplate -> RetrievalQA model retriever
- Langchain.Prelude: RetryPolicy :: !Int -> !Int -> !Int -> !Bool -> RetryPolicy
- Langchain.Prelude: RunnableError :: Text -> Maybe ErrorContext -> LangchainError
- Langchain.Prelude: Rust :: Language
- Langchain.Prelude: SQLiteCache :: FilePath -> SQLiteCache
- Langchain.Prelude: SQLiteCheckpointer :: FilePath -> SQLiteCheckpointer
- Langchain.Prelude: ServerSpan :: SpanKind
- Langchain.Prelude: Span :: !Text -> !Text -> !Text -> !Maybe Text -> !SpanKind -> !UTCTime -> !Maybe UTCTime -> !Maybe Int -> !Map Text Text -> !SpanStatus -> Span
- Langchain.Prelude: SqliteVecStore :: FilePath -> e -> SqliteVecStore e
- Langchain.Prelude: StateGraph :: Map NodeId (Node s m) -> Map NodeId (Edge s m) -> StateReducer s -> StateGraph s (m :: Type -> Type)
- Langchain.Prelude: StaticEdge :: NodeId -> Edge s (m :: Type -> Type)
- Langchain.Prelude: StatusError :: !Text -> SpanStatus
- Langchain.Prelude: StatusOk :: SpanStatus
- Langchain.Prelude: StatusUnset :: SpanStatus
- Langchain.Prelude: StdioTransport :: !FilePath -> ![String] -> McpTransport
- Langchain.Prelude: SummaryMemory :: model -> !Int -> !TVar Text -> !TVar [Message] -> SummaryMemory model
- Langchain.Prelude: TextBlock :: Text -> ContentBlock
- Langchain.Prelude: TextModelRunnable :: c -> TextModelRunnable c
- Langchain.Prelude: TokenBufferMemory :: !Int -> !TVar [Message] -> TokenBufferMemory
- Langchain.Prelude: TokenSplitterOps :: Int -> Int -> (Text -> Int) -> TokenSplitterOps
- Langchain.Prelude: TokenUsage :: Int -> Int -> Int -> TokenUsage
- Langchain.Prelude: Tool :: Text -> Text -> Value -> (Value -> m (Either LangchainError Text)) -> Tool (m :: Type -> Type)
- Langchain.Prelude: ToolCall :: Text -> Text -> Text -> Value -> ToolCall
- Langchain.Prelude: ToolEnd :: Text -> Text -> Value -> StreamEvent
- Langchain.Prelude: ToolError :: Text -> Maybe ErrorContext -> LangchainError
- Langchain.Prelude: ToolErrorEvent :: Text -> Text -> LangchainError -> StreamEvent
- Langchain.Prelude: ToolStart :: Text -> Text -> Value -> StreamEvent
- Langchain.Prelude: TypeScript :: Language
- Langchain.Prelude: ValidationError :: Text -> Maybe ErrorContext -> LangchainError
- Langchain.Prelude: VectorStoreError :: Text -> Maybe ErrorContext -> LangchainError
- Langchain.Prelude: VectorStoreRetriever :: a -> VectorStoreRetriever a
- Langchain.Prelude: WarnLevel :: LogLevel
- Langchain.Prelude: WindowBufferMemory :: !Int -> !TVar [Message] -> WindowBufferMemory
- Langchain.Prelude: [Branch] :: forall i (m :: Type -> Type) o. (i -> m Bool) -> RunnableTree m i o -> RunnableTree m i o -> RunnableTree m i o
- Langchain.Prelude: [Fallback] :: forall (m :: Type -> Type) i o. RunnableTree m i o -> RunnableTree m i o -> RunnableTree m i o
- Langchain.Prelude: [Id] :: forall (m :: Type -> Type) i. RunnableTree m i i
- Langchain.Prelude: [Lambda] :: forall i (m :: Type -> Type) o. (i -> m (Either LangchainError o)) -> RunnableTree m i o
- Langchain.Prelude: [Par] :: forall i o1 o2. RunnableTree (ExceptT LangchainError IO) i o1 -> RunnableTree (ExceptT LangchainError IO) i o2 -> RunnableTree (ExceptT LangchainError IO) i (o1, o2)
- Langchain.Prelude: [Prim] :: forall r (m :: Type -> Type) i o. (Runnable r m, RunnableInput r ~ i, RunnableOutput r ~ o) => r -> RunnableTree m i o
- Langchain.Prelude: [Seq] :: forall (m :: Type -> Type) i mid o. RunnableTree m i mid -> RunnableTree m mid o -> RunnableTree m i o
- Langchain.Prelude: [blockBase64] :: ContentBlock -> Text
- Langchain.Prelude: [blockBytes] :: ContentBlock -> ByteString
- Langchain.Prelude: [blockMimeType] :: ContentBlock -> Text
- Langchain.Prelude: [blockText] :: ContentBlock -> Text
- Langchain.Prelude: [bm25AvgDocLen] :: BM25Index -> !Double
- Langchain.Prelude: [bm25B] :: BM25Index -> !Double
- Langchain.Prelude: [bm25DocLens] :: BM25Index -> !Map Int Int
- Langchain.Prelude: [bm25Docs] :: BM25Index -> ![Document]
- Langchain.Prelude: [bm25InvertedIndex] :: BM25Index -> !Map Text (Map Int Int)
- Langchain.Prelude: [bm25K1] :: BM25Index -> !Double
- Langchain.Prelude: [bucketCapacity] :: RateLimiter -> !Double
- Langchain.Prelude: [circuitConfig] :: CircuitBreaker -> !CircuitBreakerConfig
- Langchain.Prelude: [circuitName] :: CircuitBreaker -> !Text
- Langchain.Prelude: [circuitStateVar] :: CircuitBreaker -> !TVar (CircuitState, Int)
- Langchain.Prelude: [clientTransport] :: McpClient -> !McpTransport
- Langchain.Prelude: [client] :: Ollama -> OllamaClient
- Langchain.Prelude: [completionTokens] :: TokenUsage -> Int
- Langchain.Prelude: [component] :: ErrorContext -> Text
- Langchain.Prelude: [configApiKey] :: OllamaClientConfig -> !Maybe Text
- Langchain.Prelude: [configBaseUrl] :: OllamaClientConfig -> !Text
- Langchain.Prelude: [configHeaders] :: OllamaClientConfig -> ![(CI ByteString, ByteString)]
- Langchain.Prelude: [configLogger] :: OllamaClientConfig -> !Maybe (LogLevel -> Text -> IO ())
- Langchain.Prelude: [configManager] :: OllamaClientConfig -> !Maybe Manager
- Langchain.Prelude: [configOnError] :: OllamaClientConfig -> !Maybe (IO ())
- Langchain.Prelude: [configOnStart] :: OllamaClientConfig -> !Maybe (IO ())
- Langchain.Prelude: [configOnSuccess] :: OllamaClientConfig -> !Maybe (IO ())
- Langchain.Prelude: [configRetry] :: OllamaClientConfig -> !RetryPolicy
- Langchain.Prelude: [configTimeout] :: OllamaClientConfig -> !Int
- Langchain.Prelude: [csvContentColumns] :: CsvLoader -> Maybe [Text]
- Langchain.Prelude: [csvDelimiter] :: CsvLoader -> Char
- Langchain.Prelude: [csvFilePath] :: CsvLoader -> FilePath
- Langchain.Prelude: [csvSplitter] :: CsvLoader -> Maybe (Text -> [Text])
- Langchain.Prelude: [dbFilePath] :: SQLiteCheckpointer -> FilePath
- Langchain.Prelude: [defaultKeepAlive] :: OllamaEmbeddings -> Maybe Text
- Langchain.Prelude: [defaultTruncate] :: OllamaEmbeddings -> Maybe Bool
- Langchain.Prelude: [details] :: ErrorContext -> Map Text Text
- Langchain.Prelude: [dirPath] :: DirectoryLoader -> FilePath
- Langchain.Prelude: [directoryLoaderOptions] :: DirectoryLoader -> DirectoryLoaderOptions
- Langchain.Prelude: [embeddingModel] :: InMemory m -> m
- Langchain.Prelude: [entityMessagesVar] :: EntityMemory model -> !TVar [Message]
- Langchain.Prelude: [entityModel] :: EntityMemory model -> model
- Langchain.Prelude: [entityStoreVar] :: EntityMemory model -> !TVar (Map Text Text)
- Langchain.Prelude: [excludeHidden] :: DirectoryLoaderOptions -> Bool
- Langchain.Prelude: [extensions] :: DirectoryLoaderOptions -> [String]
- Langchain.Prelude: [failureThreshold] :: CircuitBreakerConfig -> !Int
- Langchain.Prelude: [fsExampleSeparator] :: FewShotPromptTemplate -> Text
- Langchain.Prelude: [fsExampleTemplate] :: FewShotPromptTemplate -> Text
- Langchain.Prelude: [fsExamples] :: FewShotPromptTemplate -> [Map Text Text]
- Langchain.Prelude: [fsPrefix] :: FewShotPromptTemplate -> Text
- Langchain.Prelude: [fsSuffix] :: FewShotPromptTemplate -> Text
- Langchain.Prelude: [graphEdges] :: StateGraph s (m :: Type -> Type) -> Map NodeId (Edge s m)
- Langchain.Prelude: [graphNodes] :: StateGraph s (m :: Type -> Type) -> Map NodeId (Node s m)
- Langchain.Prelude: [graphReducer] :: StateGraph s (m :: Type -> Type) -> StateReducer s
- Langchain.Prelude: [guardrailName] :: Guardrail (m :: Type -> Type) -> !Text
- Langchain.Prelude: [handleEvent] :: CallbackHandler -> CallbackEvent -> IO ()
- Langchain.Prelude: [handlerName] :: CallbackHandler -> !Text
- Langchain.Prelude: [handlersVar] :: CallbackManager -> TVar [CallbackHandler]
- Langchain.Prelude: [hybridBM25] :: HybridRetriever -> !BM25Index
- Langchain.Prelude: [hybridDenseWeight] :: HybridRetriever -> !Double
- Langchain.Prelude: [hybridRrfK] :: HybridRetriever -> !Double
- Langchain.Prelude: [hybridSparseWeight] :: HybridRetriever -> !Double
- Langchain.Prelude: [hybridVectorSearch] :: HybridRetriever -> !Text -> Int -> IO [Document]
- Langchain.Prelude: [inMemoryMinLevel] :: InMemoryLogger -> !LogLevel
- Langchain.Prelude: [inMemoryVar] :: InMemoryLogger -> !TVar [LogEvent]
- Langchain.Prelude: [inputVariables] :: PromptTemplate -> [Text]
- Langchain.Prelude: [jsonValue] :: JSONOutputStructure a -> a
- Langchain.Prelude: [lastRefillVar] :: RateLimiter -> !TVar UTCTime
- Langchain.Prelude: [logComponent] :: LogEvent -> !Text
- Langchain.Prelude: [logLevel] :: LogEvent -> !LogLevel
- Langchain.Prelude: [logMessage] :: LogEvent -> !Text
- Langchain.Prelude: [logMetadata] :: LogEvent -> !Map Text Text
- Langchain.Prelude: [logTimestamp] :: LogEvent -> !UTCTime
- Langchain.Prelude: [mapDocVar] :: MapReduceChain model -> Text
- Langchain.Prelude: [mapPromptTemplate] :: MapReduceChain model -> PromptTemplate
- Langchain.Prelude: [mapReduceModel] :: MapReduceChain model -> model
- Langchain.Prelude: [maxMessageThreshold] :: SummaryMemory model -> !Int
- Langchain.Prelude: [maxTokens] :: TokenBufferMemory -> !Int
- Langchain.Prelude: [maxWindowSize] :: WindowBufferMemory -> !Int
- Langchain.Prelude: [mcpResourceMimeType] :: McpResource -> !Maybe Text
- Langchain.Prelude: [mcpResourceName] :: McpResource -> !Text
- Langchain.Prelude: [mcpResourceUri] :: McpResource -> !Text
- Langchain.Prelude: [mcpToolDescription] :: McpToolInfo -> !Text
- Langchain.Prelude: [mcpToolInputSchema] :: McpToolInfo -> !Value
- Langchain.Prelude: [mcpToolName] :: McpToolInfo -> !Text
- Langchain.Prelude: [memCacheVar] :: InMemoryCache -> TVar (Map Text Message)
- Langchain.Prelude: [memStore] :: MemoryCheckpointer -> TVar (Map (Text, NodeId) ByteString)
- Langchain.Prelude: [memVar] :: TokenBufferMemory -> !TVar [Message]
- Langchain.Prelude: [messageContents] :: Message -> NonEmpty ContentBlock
- Langchain.Prelude: [messageMetadata] :: Message -> Map Text Value
- Langchain.Prelude: [messageName] :: Message -> Maybe Text
- Langchain.Prelude: [messageRole] :: Message -> Role
- Langchain.Prelude: [messageToolCalls] :: Message -> Maybe [ToolCall]
- Langchain.Prelude: [messageToolId] :: Message -> Maybe Text
- Langchain.Prelude: [metadata] :: Document -> Map Text Value
- Langchain.Prelude: [minLevel] :: Logger -> !LogLevel
- Langchain.Prelude: [modelCache] :: CachedModel model cache -> cache
- Langchain.Prelude: [modelOptions] :: OllamaEmbeddings -> Maybe ModelOptions
- Langchain.Prelude: [model] :: OllamaEmbeddings -> Text
- Langchain.Prelude: [ollamaBaseModel] :: OllamaWithTools (m :: Type -> Type) -> !Ollama
- Langchain.Prelude: [ollamaBoundTools] :: OllamaWithTools (m :: Type -> Type) -> ![Tool m]
- Langchain.Prelude: [ollamaModelName] :: Ollama -> Text
- Langchain.Prelude: [operation] :: ErrorContext -> Text
- Langchain.Prelude: [optDraftNumPredict] :: ModelOptions -> !Maybe Int
- Langchain.Prelude: [optFrequencyPenalty] :: ModelOptions -> !Maybe Double
- Langchain.Prelude: [optMainGpu] :: ModelOptions -> !Maybe Int
- Langchain.Prelude: [optMinP] :: ModelOptions -> !Maybe Double
- Langchain.Prelude: [optNumBatch] :: ModelOptions -> !Maybe Int
- Langchain.Prelude: [optNumCtx] :: ModelOptions -> !Maybe Int
- Langchain.Prelude: [optNumGpu] :: ModelOptions -> !Maybe Int
- Langchain.Prelude: [optNumKeep] :: ModelOptions -> !Maybe Int
- Langchain.Prelude: [optNumPredict] :: ModelOptions -> !Maybe Int
- Langchain.Prelude: [optNumThread] :: ModelOptions -> !Maybe Int
- Langchain.Prelude: [optNuma] :: ModelOptions -> !Maybe Bool
- Langchain.Prelude: [optPenalizeNewline] :: ModelOptions -> !Maybe Bool
- Langchain.Prelude: [optPresencePenalty] :: ModelOptions -> !Maybe Double
- Langchain.Prelude: [optRepeatLastN] :: ModelOptions -> !Maybe Int
- Langchain.Prelude: [optRepeatPenalty] :: ModelOptions -> !Maybe Double
- Langchain.Prelude: [optSeed] :: ModelOptions -> !Maybe Int
- Langchain.Prelude: [optStop] :: ModelOptions -> !Maybe [Text]
- Langchain.Prelude: [optTemperature] :: ModelOptions -> !Maybe Double
- Langchain.Prelude: [optTopK] :: ModelOptions -> !Maybe Int
- Langchain.Prelude: [optTopP] :: ModelOptions -> !Maybe Double
- Langchain.Prelude: [optTypicalP] :: ModelOptions -> !Maybe Double
- Langchain.Prelude: [optUseMmap] :: ModelOptions -> !Maybe Bool
- Langchain.Prelude: [pageContent] :: Document -> Text
- Langchain.Prelude: [partialVariables] :: PromptTemplateOptions -> Map Text Text
- Langchain.Prelude: [planPromptTemplate] :: PlanAndExecuteAgent planner executor -> Maybe Text
- Langchain.Prelude: [planSteps] :: Plan -> [PlanStep]
- Langchain.Prelude: [plannerModel] :: PlanAndExecuteAgent planner executor -> planner
- Langchain.Prelude: [promptTokens] :: TokenUsage -> Int
- Langchain.Prelude: [recentMessagesVar] :: SummaryMemory model -> !TVar [Message]
- Langchain.Prelude: [recursiveDepth] :: DirectoryLoaderOptions -> Maybe Int
- Langchain.Prelude: [reduceDocVar] :: MapReduceChain model -> Text
- Langchain.Prelude: [reducePromptTemplate] :: MapReduceChain model -> PromptTemplate
- Langchain.Prelude: [refillRatePerSec] :: RateLimiter -> !Double
- Langchain.Prelude: [resetTimeoutSec] :: CircuitBreakerConfig -> !Double
- Langchain.Prelude: [serverName] :: McpClient -> !Text
- Langchain.Prelude: [spanAttributes] :: Span -> !Map Text Text
- Langchain.Prelude: [spanDurationMicros] :: Span -> !Maybe Int
- Langchain.Prelude: [spanEndTime] :: Span -> !Maybe UTCTime
- Langchain.Prelude: [spanId] :: Span -> !Text
- Langchain.Prelude: [spanKind] :: Span -> !SpanKind
- Langchain.Prelude: [spanName] :: Span -> !Text
- Langchain.Prelude: [spanParentId] :: Span -> !Maybe Text
- Langchain.Prelude: [spanStartTime] :: Span -> !UTCTime
- Langchain.Prelude: [spanStatus] :: Span -> !SpanStatus
- Langchain.Prelude: [spanTraceId] :: Span -> !Text
- Langchain.Prelude: [sqliteCacheDbPath] :: SQLiteCache -> FilePath
- Langchain.Prelude: [sqliteDbPath] :: SqliteVecStore e -> FilePath
- Langchain.Prelude: [sqliteEmbeddings] :: SqliteVecStore e -> e
- Langchain.Prelude: [stepDescription] :: PlanStep -> !Text
- Langchain.Prelude: [stepExecutor] :: PlanAndExecuteAgent planner executor -> executor
- Langchain.Prelude: [stepNumber] :: PlanStep -> !Int
- Langchain.Prelude: [store] :: InMemory m -> Map Int64 (Document, [Float])
- Langchain.Prelude: [summaryBufferVar] :: SummaryMemory model -> !TVar Text
- Langchain.Prelude: [summaryModel] :: SummaryMemory model -> model
- Langchain.Prelude: [templateFormat] :: PromptTemplate -> TemplateFormat
- Langchain.Prelude: [template] :: PromptTemplate -> Text
- Langchain.Prelude: [timestamp] :: ErrorContext -> UTCTime
- Langchain.Prelude: [tokensVar] :: RateLimiter -> !TVar Double
- Langchain.Prelude: [toolCallArguments] :: ToolCall -> Value
- Langchain.Prelude: [toolCallId] :: ToolCall -> Text
- Langchain.Prelude: [toolCallName] :: ToolCall -> Text
- Langchain.Prelude: [toolCallType] :: ToolCall -> Text
- Langchain.Prelude: [toolDescription] :: Tool (m :: Type -> Type) -> Text
- Langchain.Prelude: [toolExecute] :: Tool (m :: Type -> Type) -> Value -> m (Either LangchainError Text)
- Langchain.Prelude: [toolName] :: Tool (m :: Type -> Type) -> Text
- Langchain.Prelude: [toolSchema] :: Tool (m :: Type -> Type) -> Value
- Langchain.Prelude: [totalTokens] :: TokenUsage -> Int
- Langchain.Prelude: [tracerSpansVar] :: OTelTracer -> !TVar [Span]
- Langchain.Prelude: [tracerTraceId] :: OTelTracer -> !Text
- Langchain.Prelude: [underlyingModel] :: CachedModel model cache -> model
- Langchain.Prelude: [useMultithreading] :: DirectoryLoaderOptions -> Bool
- Langchain.Prelude: [validateInput] :: Guardrail (m :: Type -> Type) -> Text -> m GuardrailResult
- Langchain.Prelude: [validateOutput] :: Guardrail (m :: Type -> Type) -> Text -> m GuardrailResult
- Langchain.Prelude: [vs] :: VectorStoreRetriever a -> a
- Langchain.Prelude: [writeLog] :: Logger -> LogEvent -> IO ()
- Langchain.Prelude: addAiMessage :: (BaseMemory mem, MonadIO m, MonadError LangchainError m) => mem -> Text -> m ()
- Langchain.Prelude: addConditionalEdge :: NodeId -> (s -> m (Either LangchainError NodeId)) -> StateGraph s m -> StateGraph s m
- Langchain.Prelude: addDocuments :: (VectorStore vs, MonadIO m, MonadError LangchainError m) => vs -> [Document] -> m vs
- Langchain.Prelude: addDocumentsBM25 :: [Document] -> BM25Index -> BM25Index
- Langchain.Prelude: addEdge :: forall s (m :: Type -> Type). NodeId -> NodeId -> StateGraph s m -> StateGraph s m
- Langchain.Prelude: addMessage :: (BaseMemory mem, MonadIO m, MonadError LangchainError m) => mem -> Message -> m ()
- Langchain.Prelude: addNode :: NodeId -> (s -> m (Either LangchainError s)) -> StateGraph s m -> StateGraph s m
- Langchain.Prelude: addParallelNodes :: forall (m :: Type -> Type) s. MonadIO m => NodeId -> [s -> IO (Either LangchainError s)] -> (s -> [s] -> s) -> StateGraph s m -> StateGraph s m
- Langchain.Prelude: addSpanAttribute :: MonadIO m => OTelTracer -> Text -> Text -> Text -> m ()
- Langchain.Prelude: addUserMessage :: (BaseMemory mem, MonadIO m, MonadError LangchainError m) => mem -> Text -> m ()
- Langchain.Prelude: agentError :: Text -> Maybe Text -> Maybe Text -> LangchainError
- Langchain.Prelude: appendMessagesReducer :: StateReducer [Message]
- Langchain.Prelude: assistantMessage :: Text -> Message
- Langchain.Prelude: batch :: (ChatModel model, MonadIO m, MonadError LangchainError m) => model -> [[Message]] -> Maybe (ModelConfig model) -> m [Message]
- Langchain.Prelude: bindTools :: forall (m :: Type -> Type). [Tool m] -> Ollama -> OllamaWithTools m
- Langchain.Prelude: bindToolsConfig :: ToolBinder model m => [Tool m] -> Maybe (ModelConfig model) -> Maybe (ModelConfig model)
- Langchain.Prelude: bm25Search :: BM25Index -> Text -> Int -> [Document]
- Langchain.Prelude: bm25SearchWithScores :: BM25Index -> Text -> Int -> [(Document, Double)]
- Langchain.Prelude: callMcpTool :: (MonadIO m, MonadError LangchainError m) => McpClient -> Text -> Value -> m Text
- Langchain.Prelude: chatRequestFor :: Ollama -> [Message] -> ChatRequest
- Langchain.Prelude: class BaseLoader loader
- Langchain.Prelude: class BaseMemory mem
- Langchain.Prelude: class CacheBackend cb
- Langchain.Prelude: class ChatModel model where {
- Langchain.Prelude: class Checkpointer cp (m :: Type -> Type)
- Langchain.Prelude: class DeriveToolSchema a
- Langchain.Prelude: class Embeddings embed
- Langchain.Prelude: class OutputParser a
- Langchain.Prelude: class Retriever a
- Langchain.Prelude: class StepExecutor e (m :: Type -> Type)
- Langchain.Prelude: class FromJSON a => StructuredOutput a
- Langchain.Prelude: class ChatModel model => ToolBinder model (m :: Type -> Type)
- Langchain.Prelude: class TypeSchema a
- Langchain.Prelude: class VectorStore vs
- Langchain.Prelude: clear :: (BaseMemory mem, MonadIO m, MonadError LangchainError m) => mem -> m ()
- Langchain.Prelude: clearCache :: (CacheBackend cb, MonadIO m) => cb -> m ()
- Langchain.Prelude: collectEvents :: Monad m => EventStream m -> m [StreamEvent]
- Langchain.Prelude: compileGraph :: forall s (m :: Type -> Type). StateGraph s m -> Either LangchainError (StateGraph s m)
- Langchain.Prelude: composeGuardrails :: forall (m :: Type -> Type). MonadIO m => [Guardrail m] -> Guardrail m
- Langchain.Prelude: configurationError :: Text -> Maybe Text -> Maybe Text -> LangchainError
- Langchain.Prelude: contentSafetyGuardrail :: forall (m :: Type -> Type). MonadIO m => [Text] -> Guardrail m
- Langchain.Prelude: countTokens :: [Message] -> Int
- Langchain.Prelude: createReActAgent :: forall model (m :: Type -> Type). model -> [Tool m] -> ReActAgent model m
- Langchain.Prelude: createTool :: Text -> Text -> Value -> (Value -> m (Either LangchainError Text)) -> Tool m
- Langchain.Prelude: data AgentStep
- Langchain.Prelude: data BM25Index
- Langchain.Prelude: data CachedModel model cache
- Langchain.Prelude: data CallbackEvent
- Langchain.Prelude: data CallbackHandler
- Langchain.Prelude: data CharacterSplitterOps
- Langchain.Prelude: data CircuitBreaker
- Langchain.Prelude: data CircuitBreakerConfig
- Langchain.Prelude: data CircuitState
- Langchain.Prelude: data CodeSplitterOps
- Langchain.Prelude: data ContentBlock
- Langchain.Prelude: data CsvLoader
- Langchain.Prelude: data DirectoryLoader
- Langchain.Prelude: data DirectoryLoaderOptions
- Langchain.Prelude: data Document
- Langchain.Prelude: data Edge s (m :: Type -> Type)
- Langchain.Prelude: data EntityMemory model
- Langchain.Prelude: data ErrorContext
- Langchain.Prelude: data FewShotPromptTemplate
- Langchain.Prelude: data Gemini
- Langchain.Prelude: data Guardrail (m :: Type -> Type)
- Langchain.Prelude: data GuardrailResult
- Langchain.Prelude: data HybridRetriever
- Langchain.Prelude: data InMemory m
- Langchain.Prelude: data InMemoryLogger
- Langchain.Prelude: data LangchainError
- Langchain.Prelude: data Language
- Langchain.Prelude: data LogEvent
- Langchain.Prelude: data LogLevel
- Langchain.Prelude: data Logger
- Langchain.Prelude: data MapReduceChain model
- Langchain.Prelude: data MarkdownSplitterOps
- Langchain.Prelude: data McpClient
- Langchain.Prelude: data McpResource
- Langchain.Prelude: data McpToolInfo
- Langchain.Prelude: data McpTransport
- Langchain.Prelude: data Message
- Langchain.Prelude: data ModelOptions
- Langchain.Prelude: data Node s (m :: Type -> Type)
- Langchain.Prelude: data OTelTracer
- Langchain.Prelude: data Ollama
- Langchain.Prelude: data OllamaClientConfig
- Langchain.Prelude: data OllamaEmbeddings
- Langchain.Prelude: data OllamaWithTools (m :: Type -> Type)
- Langchain.Prelude: data OpenAI
- Langchain.Prelude: data OpenAIEmbeddings
- Langchain.Prelude: data PlanAndExecuteAgent planner executor
- Langchain.Prelude: data PlanStep
- Langchain.Prelude: data PromptTemplate
- Langchain.Prelude: data RateLimiter
- Langchain.Prelude: data ReActAgent model (m :: Type -> Type)
- Langchain.Prelude: data RecursiveCharacterSplitterOps
- Langchain.Prelude: data RetrievalQA model retriever
- Langchain.Prelude: data RetryPolicy
- Langchain.Prelude: data Role
- Langchain.Prelude: data RunnableTree (m :: Type -> Type) i o
- Langchain.Prelude: data Span
- Langchain.Prelude: data SpanKind
- Langchain.Prelude: data SpanStatus
- Langchain.Prelude: data SqliteVecStore e
- Langchain.Prelude: data StateGraph s (m :: Type -> Type)
- Langchain.Prelude: data StreamEvent
- Langchain.Prelude: data SummaryMemory model
- Langchain.Prelude: data TemplateFormat
- Langchain.Prelude: data TokenBufferMemory
- Langchain.Prelude: data TokenSplitterOps
- Langchain.Prelude: data TokenUsage
- Langchain.Prelude: data Tool (m :: Type -> Type)
- Langchain.Prelude: data ToolCall
- Langchain.Prelude: data WindowBufferMemory
- Langchain.Prelude: defaultCharacterSplitterOps :: CharacterSplitterOps
- Langchain.Prelude: defaultCircuitConfig :: CircuitBreakerConfig
- Langchain.Prelude: defaultConfig :: OllamaClientConfig
- Langchain.Prelude: defaultCsvLoader :: FilePath -> CsvLoader
- Langchain.Prelude: defaultDirectoryLoaderOptions :: DirectoryLoaderOptions
- Langchain.Prelude: defaultMarkdownSplitterOps :: MarkdownSplitterOps
- Langchain.Prelude: defaultOpenAIEmbeddings :: OpenAIEmbeddings
- Langchain.Prelude: defaultOptions :: ModelOptions
- Langchain.Prelude: defaultPromptTemplateOptions :: PromptTemplateOptions
- Langchain.Prelude: defaultRecursiveCharacterSplitterOps :: RecursiveCharacterSplitterOps
- Langchain.Prelude: defaultRetryPolicy :: RetryPolicy
- Langchain.Prelude: defaultTokenSplitterOps :: TokenSplitterOps
- Langchain.Prelude: delete :: (VectorStore vs, MonadIO m, MonadError LangchainError m) => vs -> [Int64] -> m vs
- Langchain.Prelude: deriveToolParametersSchema :: GToolRecordSchema (Rep a) => Proxy a -> Value
- Langchain.Prelude: deriveToolSchema :: DeriveToolSchema a => Proxy a -> Value
- Langchain.Prelude: dispatchEvent :: MonadIO m => CallbackManager -> CallbackEvent -> m ()
- Langchain.Prelude: dispatchEventAsync :: MonadIO m => CallbackManager -> CallbackEvent -> m ()
- Langchain.Prelude: documentLoaderError :: Text -> Maybe Text -> Maybe Text -> LangchainError
- Langchain.Prelude: embedDocuments :: (Embeddings embed, MonadIO m, MonadError LangchainError m) => embed -> [Document] -> m [[Float]]
- Langchain.Prelude: embedQuery :: (Embeddings embed, MonadIO m, MonadError LangchainError m) => embed -> Text -> m [Float]
- Langchain.Prelude: embedSubGraphNode :: forall (m :: Type -> Type) subState parentState. (MonadIO m, MonadError LangchainError m) => NodeId -> StateGraph subState m -> (parentState -> subState) -> (parentState -> subState -> parentState) -> Node parentState m
- Langchain.Prelude: embeddingError :: Text -> Maybe Text -> Maybe Text -> LangchainError
- Langchain.Prelude: emptyInMemoryVectorStore :: m -> InMemory m
- Langchain.Prelude: emptyStateGraph :: forall s (m :: Type -> Type). StateReducer s -> StateGraph s m
- Langchain.Prelude: endNodeId :: NodeId
- Langchain.Prelude: endSpan :: MonadIO m => OTelTracer -> Text -> SpanStatus -> m ()
- Langchain.Prelude: errorMessage :: LangchainError -> Text
- Langchain.Prelude: executeStep :: StepExecutor e m => e -> Text -> m Text
- Langchain.Prelude: executeToolAsync :: MonadIO m => Tool IO -> Value -> m (Async (Either LangchainError Text))
- Langchain.Prelude: executeToolBatchConcurrently :: (MonadIO m, MonadError LangchainError m) => [(Tool IO, Value)] -> m [Text]
- Langchain.Prelude: executeToolWithTimeout :: (MonadIO m, MonadError LangchainError m) => Tool IO -> Value -> Int -> m Text
- Langchain.Prelude: exportSpansJson :: MonadIO m => OTelTracer -> m Text
- Langchain.Prelude: extractMessageText :: Message -> Text
- Langchain.Prelude: fromDocuments :: (Embeddings m, MonadIO monad, MonadError LangchainError monad) => m -> [Document] -> monad (InMemory m)
- Langchain.Prelude: fromOllamaSchema :: Schema -> Value
- Langchain.Prelude: fromTemplate :: Text -> PromptTemplate
- Langchain.Prelude: fromTemplateWithFormat :: Text -> TemplateFormat -> Map Text Text -> PromptTemplate
- Langchain.Prelude: fromTemplateWithOptions :: Text -> PromptTemplateOptions -> PromptTemplate
- Langchain.Prelude: getCache :: (CacheBackend cb, MonadIO m) => cb -> Text -> m (Maybe Message)
- Langchain.Prelude: getCallbackLogs :: MonadIO m => TVar [Text] -> m [Text]
- Langchain.Prelude: getCircuitState :: MonadIO m => CircuitBreaker -> m CircuitState
- Langchain.Prelude: getInMemoryLogs :: MonadIO m => InMemoryLogger -> m [LogEvent]
- Langchain.Prelude: getRelevantDocuments :: (Retriever a, MonadIO m, MonadError LangchainError m) => a -> Text -> m [Document]
- Langchain.Prelude: getSpans :: MonadIO m => OTelTracer -> m [Span]
- Langchain.Prelude: hitlNode :: (Checkpointer cp m, ToJSON s, MonadIO m) => cp -> Text -> NodeId -> (s -> m (Either LangchainError s)) -> Node s m
- Langchain.Prelude: imageMessage :: Role -> Text -> Text -> Message
- Langchain.Prelude: infixl 1 |>>
- Langchain.Prelude: infixl 2 &>&
- Langchain.Prelude: initialMessages :: Text -> [Message]
- Langchain.Prelude: internalError :: Text -> Maybe Text -> Maybe Text -> LangchainError
- Langchain.Prelude: interpret :: (MonadIO m, MonadError LangchainError m) => RunnableTree m i o -> i -> m o
- Langchain.Prelude: invoke :: (ChatModel model, MonadIO m, MonadError LangchainError m) => model -> [Message] -> Maybe (ModelConfig model) -> m Message
- Langchain.Prelude: isOptionalType :: TypeSchema a => Proxy a -> Bool
- Langchain.Prelude: listMcpTools :: (MonadIO m, MonadError LangchainError m) => McpClient -> m [McpToolInfo]
- Langchain.Prelude: llmError :: Text -> Maybe Text -> Maybe Text -> LangchainError
- Langchain.Prelude: load :: (BaseLoader loader, MonadIO m, MonadError LangchainError m) => loader -> m [Document]
- Langchain.Prelude: loadAndSplit :: (BaseLoader loader, MonadIO m, MonadError LangchainError m) => loader -> m [Text]
- Langchain.Prelude: loadCheckpoint :: (Checkpointer cp m, FromJSON s) => cp -> Text -> NodeId -> m (Either LangchainError (Maybe s))
- Langchain.Prelude: logDebug :: MonadIO m => Logger -> Text -> Text -> m ()
- Langchain.Prelude: logError :: MonadIO m => Logger -> Text -> Text -> m ()
- Langchain.Prelude: logEvent :: MonadIO m => Logger -> LogLevel -> Text -> Text -> Map Text Text -> m ()
- Langchain.Prelude: logInfo :: MonadIO m => Logger -> Text -> Text -> m ()
- Langchain.Prelude: logWarn :: MonadIO m => Logger -> Text -> Text -> m ()
- Langchain.Prelude: mcpToolToLangchainTool :: McpClient -> McpToolInfo -> Tool IO
- Langchain.Prelude: memoryError :: Text -> Maybe Text -> Maybe Text -> LangchainError
- Langchain.Prelude: messages :: (BaseMemory mem, MonadIO m, MonadError LangchainError m) => mem -> m [Message]
- Langchain.Prelude: mkContext :: Text -> Text -> Map Text Text -> ErrorContext
- Langchain.Prelude: mkContextIO :: MonadIO m => Text -> Text -> Map Text Text -> m ErrorContext
- Langchain.Prelude: networkError :: Text -> Maybe Text -> Maybe Text -> LangchainError
- Langchain.Prelude: newBM25Index :: [Document] -> BM25Index
- Langchain.Prelude: newBM25IndexWithParams :: Double -> Double -> [Document] -> BM25Index
- Langchain.Prelude: newCallbackManager :: MonadIO m => m CallbackManager
- Langchain.Prelude: newCircuitBreaker :: MonadIO m => Text -> CircuitBreakerConfig -> m CircuitBreaker
- Langchain.Prelude: newEntityMemory :: MonadIO m => model -> [Message] -> m (EntityMemory model)
- Langchain.Prelude: newGemini :: Text -> Text -> Maybe Text -> Gemini
- Langchain.Prelude: newHttpMcpClient :: Text -> Text -> McpClient
- Langchain.Prelude: newHybridRetriever :: BM25Index -> (Text -> Int -> IO [Document]) -> HybridRetriever
- Langchain.Prelude: newHybridRetrieverWithWeights :: BM25Index -> (Text -> Int -> IO [Document]) -> Double -> Double -> Double -> HybridRetriever
- Langchain.Prelude: newInMemoryCache :: MonadIO m => m InMemoryCache
- Langchain.Prelude: newInMemoryLogger :: MonadIO m => LogLevel -> m InMemoryLogger
- Langchain.Prelude: newLoggingCallbackHandler :: MonadIO m => Text -> m (CallbackHandler, TVar [Text])
- Langchain.Prelude: newMapReduceChain :: model -> MapReduceChain model
- Langchain.Prelude: newMemoryCheckpointer :: MonadIO m => m MemoryCheckpointer
- Langchain.Prelude: newOTelTracer :: MonadIO m => Maybe Text -> m OTelTracer
- Langchain.Prelude: newOllama :: MonadIO m => Text -> OllamaClientConfig -> m Ollama
- Langchain.Prelude: newOllamaWithClient :: Text -> OllamaClient -> Ollama
- Langchain.Prelude: newOpenAI :: Text -> Text -> OpenAI
- Langchain.Prelude: newPlanAndExecuteAgent :: planner -> executor -> Maybe Text -> PlanAndExecuteAgent planner executor
- Langchain.Prelude: newPlanAndExecuteAgentWithTools :: forall planner model (m :: Type -> Type). planner -> model -> [Tool m] -> Maybe Text -> PlanAndExecuteAgent planner (ReActAgent model m)
- Langchain.Prelude: newRateLimiter :: MonadIO m => Double -> Double -> m RateLimiter
- Langchain.Prelude: newRetrievalQA :: model -> retriever -> RetrievalQA model retriever
- Langchain.Prelude: newSQLiteCache :: MonadIO m => FilePath -> m SQLiteCache
- Langchain.Prelude: newSQLiteCheckpointer :: MonadIO m => FilePath -> m SQLiteCheckpointer
- Langchain.Prelude: newSqliteVecStore :: (MonadIO m, MonadError LangchainError m) => FilePath -> e -> m (SqliteVecStore e)
- Langchain.Prelude: newStdioMcpClient :: Text -> FilePath -> [String] -> McpClient
- Langchain.Prelude: newSummaryMemory :: MonadIO m => model -> Int -> [Message] -> m (SummaryMemory model)
- Langchain.Prelude: newTokenBufferMemory :: MonadIO m => Int -> [Message] -> m TokenBufferMemory
- Langchain.Prelude: newWindowBufferMemory :: MonadIO m => Int -> [Message] -> m WindowBufferMemory
- Langchain.Prelude: newtype CallbackManager
- Langchain.Prelude: newtype CommaSeparatedList
- Langchain.Prelude: newtype FileLoader
- Langchain.Prelude: newtype InMemoryCache
- Langchain.Prelude: newtype FromJSON a => JSONOutputStructure a
- Langchain.Prelude: newtype MemoryCheckpointer
- Langchain.Prelude: newtype ModelRunnable c
- Langchain.Prelude: newtype NumberSeparatedList
- Langchain.Prelude: newtype Plan
- Langchain.Prelude: newtype PromptTemplateOptions
- Langchain.Prelude: newtype SQLiteCache
- Langchain.Prelude: newtype SQLiteCheckpointer
- Langchain.Prelude: newtype TextModelRunnable c
- Langchain.Prelude: newtype VectorStore a => VectorStoreRetriever a
- Langchain.Prelude: outputLengthGuardrail :: forall (m :: Type -> Type). MonadIO m => Int -> Guardrail m
- Langchain.Prelude: outputSchema :: StructuredOutput a => Proxy a -> Value
- Langchain.Prelude: parallelNode :: forall (m :: Type -> Type) s. MonadIO m => NodeId -> [s -> IO (Either LangchainError s)] -> (s -> [s] -> s) -> Node s m
- Langchain.Prelude: parse :: OutputParser a => Text -> LangchainResult a
- Langchain.Prelude: parsingError :: Text -> Maybe Text -> Maybe Text -> LangchainError
- Langchain.Prelude: partialPromptTemplate :: PromptTemplate -> Map Text Text -> PromptTemplate
- Langchain.Prelude: printEvents :: EventStream (ExceptT LangchainError (ResourceT IO)) -> IO (Either LangchainError ())
- Langchain.Prelude: putCache :: (CacheBackend cb, MonadIO m) => cb -> Text -> Message -> m ()
- Langchain.Prelude: reactStep :: (ToolBinder model m, MonadIO m, MonadError LangchainError m) => model -> [Tool m] -> [Message] -> m AgentStep
- Langchain.Prelude: reciprocalRankFusion :: Double -> [([Document], Double)] -> [(Document, Double)]
- Langchain.Prelude: registerHandler :: MonadIO m => CallbackManager -> CallbackHandler -> m ()
- Langchain.Prelude: renderFewShotPrompt :: FewShotPromptTemplate -> Either LangchainError Text
- Langchain.Prelude: renderPrompt :: PromptTemplate -> Map Text Text -> Either LangchainError Text
- Langchain.Prelude: replaceFieldReducer :: StateReducer a
- Langchain.Prelude: resolveChatRequest :: Ollama -> [Message] -> Maybe ChatRequest -> (ChatRequest, Text, [Message])
- Langchain.Prelude: resumeGraph :: (Checkpointer cp m, FromJSON s, ToJSON s, MonadIO m, MonadError LangchainError m) => StateGraph s m -> cp -> Text -> NodeId -> NodeId -> (s -> s) -> m s
- Langchain.Prelude: retrieveWithCallbacks :: (Retriever a, MonadIO m, MonadError LangchainError m) => CallbackManager -> Text -> a -> Text -> m [Document]
- Langchain.Prelude: runBranch :: (i -> m Bool) -> RunnableTree m i o -> RunnableTree m i o -> RunnableTree m i o
- Langchain.Prelude: runChat :: forall c (m :: Type -> Type). (ChatModel c, MonadIO m) => c -> RunnableTree m Text Text
- Langchain.Prelude: runFallback :: forall (m :: Type -> Type) i o. RunnableTree m i o -> RunnableTree m i o -> RunnableTree m i o
- Langchain.Prelude: runGraph :: (MonadIO m, MonadError LangchainError m) => StateGraph s m -> NodeId -> s -> m s
- Langchain.Prelude: runIdent :: forall (m :: Type -> Type) a. RunnableTree m a a
- Langchain.Prelude: runLambda :: (i -> m (Either LangchainError o)) -> RunnableTree m i o
- Langchain.Prelude: runLangchainT :: r -> LangchainT r m a -> m (Either LangchainError a)
- Langchain.Prelude: runMapReduceChain :: (ChatModel model, MonadIO m, MonadError LangchainError m) => MapReduceChain model -> [Document] -> Map Text Text -> m Message
- Langchain.Prelude: runModel :: forall c (m :: Type -> Type). (ChatModel c, MonadIO m) => c -> RunnableTree m [Message] Message
- Langchain.Prelude: runPassthrough :: forall (m :: Type -> Type) a. RunnableTree m a a
- Langchain.Prelude: runPlanAndExecute :: (ChatModel planner, StepExecutor executor m, MonadIO m, MonadError LangchainError m) => PlanAndExecuteAgent planner executor -> Text -> m Text
- Langchain.Prelude: runPrim :: forall r (m :: Type -> Type) i o. (Runnable r m, RunnableInput r ~ i, RunnableOutput r ~ o) => r -> RunnableTree m i o
- Langchain.Prelude: runPure :: forall (m :: Type -> Type) i o. Monad m => (i -> o) -> RunnableTree m i o
- Langchain.Prelude: runReActAgent :: (ToolBinder model m, MonadIO m, MonadError LangchainError m) => ReActAgent model m -> [Message] -> m Message
- Langchain.Prelude: runRetrievalQA :: (ChatModel model, Retriever retriever, MonadIO m, MonadError LangchainError m) => RetrievalQA model retriever -> Text -> m Message
- Langchain.Prelude: runRetriever :: forall a (m :: Type -> Type). (Retriever a, MonadIO m) => a -> RunnableTree m Text [Document]
- Langchain.Prelude: runnableError :: Text -> Maybe Text -> Maybe Text -> LangchainError
- Langchain.Prelude: saveCheckpoint :: (Checkpointer cp m, ToJSON s) => cp -> Text -> NodeId -> s -> m (Either LangchainError ())
- Langchain.Prelude: searchHybrid :: MonadIO m => HybridRetriever -> Text -> Int -> m [Document]
- Langchain.Prelude: searchHybridWithScores :: MonadIO m => HybridRetriever -> Text -> Int -> m [(Document, Double)]
- Langchain.Prelude: shellTool :: forall (m :: Type -> Type). MonadIO m => Tool m
- Langchain.Prelude: similaritySearch :: (VectorStore vs, MonadIO m, MonadError LangchainError m) => vs -> Text -> Int -> m [Document]
- Langchain.Prelude: similaritySearchByVector :: (VectorStore vs, MonadIO m, MonadError LangchainError m) => vs -> [Float] -> Int -> m [Document]
- Langchain.Prelude: splitByTokens :: TokenSplitterOps -> Text -> [Text]
- Langchain.Prelude: splitCode :: CodeSplitterOps -> Text -> [Text]
- Langchain.Prelude: splitMarkdown :: MarkdownSplitterOps -> Text -> [Text]
- Langchain.Prelude: splitMarkdownToChunks :: MarkdownSplitterOps -> Text -> [MarkdownChunk]
- Langchain.Prelude: splitText :: CharacterSplitterOps -> Text -> [Text]
- Langchain.Prelude: splitTextRecursive :: RecursiveCharacterSplitterOps -> Text -> [Text]
- Langchain.Prelude: startNodeId :: NodeId
- Langchain.Prelude: startSpan :: MonadIO m => OTelTracer -> Text -> Maybe Text -> SpanKind -> Map Text Text -> m Span
- Langchain.Prelude: stderrLogger :: LogLevel -> Logger
- Langchain.Prelude: stream :: ChatModel model => model -> [Message] -> Maybe (ModelConfig model) -> ChatStream
- Langchain.Prelude: structuredInvoke :: (StructuredOutput a, ChatModel model, MonadIO m, MonadError LangchainError m) => model -> [Message] -> m a
- Langchain.Prelude: structuredInvokeWithRetries :: (StructuredOutput a, ChatModel model, MonadIO m, MonadError LangchainError m) => model -> [Message] -> Int -> m a
- Langchain.Prelude: supervisorNode :: forall model (m :: Type -> Type) s. (ChatModel model, MonadIO m, MonadError LangchainError m) => model -> NodeId -> [(Text, NodeId)] -> (s -> Text) -> (Text -> s -> s) -> Node s m
- Langchain.Prelude: systemMessage :: Text -> Message
- Langchain.Prelude: textEmbedding3Large :: Text
- Langchain.Prelude: textEmbedding3Small :: Text
- Langchain.Prelude: textEmbeddingAda :: Text
- Langchain.Prelude: textMessage :: Role -> Text -> Message
- Langchain.Prelude: throwLangchainError :: MonadError LangchainError m => LangchainError -> m a
- Langchain.Prelude: toOllamaSchema :: Value -> Maybe Schema
- Langchain.Prelude: toOllamaTool :: forall (m :: Type -> Type). Tool m -> Maybe Tool
- Langchain.Prelude: toOllamaTools :: forall (m :: Type -> Type). [Tool m] -> [Tool]
- Langchain.Prelude: toolError :: Text -> Maybe Text -> Maybe Text -> LangchainError
- Langchain.Prelude: toolMessage :: Text -> Message
- Langchain.Prelude: toolToValue :: forall (m :: Type -> Type). Tool m -> Value
- Langchain.Prelude: topicGuardrail :: forall model (m :: Type -> Type). (ChatModel model, MonadIO m, MonadError LangchainError m) => model -> Text -> Guardrail m
- Langchain.Prelude: trimMessages :: Int -> [Message] -> [Message]
- Langchain.Prelude: type EventStream (m :: Type -> Type) = ConduitT () StreamEvent m ()
- Langchain.Prelude: type LangchainResult a = Either LangchainError a
- Langchain.Prelude: type LangchainT r (m :: Type -> Type) = ReaderT r ExceptT LangchainError m
- Langchain.Prelude: type ModelConfig model;
- Langchain.Prelude: type NodeId = Text
- Langchain.Prelude: type StateReducer s = s -> s -> s
- Langchain.Prelude: typeJsonSchema :: TypeSchema a => Proxy a -> Value
- Langchain.Prelude: userMessage :: Text -> Message
- Langchain.Prelude: validationError :: Text -> Maybe Text -> Maybe Text -> LangchainError
- Langchain.Prelude: vectorStoreError :: Text -> Maybe Text -> Maybe Text -> LangchainError
- Langchain.Prelude: withCaching :: model -> cache -> CachedModel model cache
- Langchain.Prelude: withCircuitBreaker :: (MonadIO m, MonadError LangchainError m) => CircuitBreaker -> m a -> m a
- Langchain.Prelude: withGuardrails :: (MonadIO m, MonadError LangchainError m) => Guardrail m -> (Text -> m Text) -> Text -> m Text
- Langchain.Prelude: withJsonFormat :: ChatRequest -> ChatRequest
- Langchain.Prelude: withOptions :: ModelOptions -> ChatRequest -> ChatRequest
- Langchain.Prelude: withRateLimit :: MonadIO m => RateLimiter -> m a -> m a
- Langchain.Prelude: withRetry :: (MonadIO m, MonadError LangchainError m) => RetryPolicy -> m a -> m a
- Langchain.Prelude: withSchemaFormat :: Schema -> ChatRequest -> ChatRequest
- Langchain.Prelude: withSpan :: (MonadIO m, MonadError LangchainError m) => OTelTracer -> Text -> Maybe Text -> SpanKind -> Map Text Text -> m a -> m a
- Langchain.Prelude: withStructuredOutput :: ToSchema a => ChatRequest -> ChatRequest
- Langchain.Prelude: withTools :: forall (m :: Type -> Type). [Tool m] -> ChatRequest -> ChatRequest
- Langchain.Prelude: }
- Langchain.PromptTemplate.Chat: class BaseMessagePromptTemplate template input
- Langchain.PromptTemplate.Chat: extractTemplateVariables :: Text -> [Text]
- Langchain.PromptTemplate.Chat: formatMessages :: BaseMessagePromptTemplate template input => template -> input -> Either LangchainError [Message]
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: ChatPromptInputs :: Map Text Text -> Map Text [Message] -> ChatPromptInput
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: ChatPromptMessageList :: [Message] -> ChatPromptInput
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: ChatPromptTemplate :: [ChatPromptMessage] -> [Text] -> ChatPromptTemplate
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: ChatPromptValue :: [Message] -> ChatPromptValue
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: ChatPromptVariables :: Map Text Text -> ChatPromptInput
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: ImagePromptBlock :: TemplateFormat -> ImageContent -> ContentPromptBlock
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: PartialMessages :: [Message] -> PartialValue
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: PartialText :: Text -> PartialValue
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: TextPromptBlock :: TemplateFormat -> Text -> ContentPromptBlock
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: [inputVariables] :: ChatPromptTemplate -> [Text]
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: [messages] :: ChatPromptValue -> [Message]
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: append :: ChatPromptTemplate -> ChatPromptMessage -> ChatPromptTemplate
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: contentMessage :: Role -> [ContentPromptBlock] -> ChatPromptMessage
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: data ChatPromptInput
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: data ChatPromptMessage
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: data ChatPromptTemplate
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: data ContentPromptBlock
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: data PartialValue
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: extend :: ChatPromptTemplate -> [ChatPromptMessage] -> ChatPromptTemplate
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: format :: ChatPromptTemplate -> Map Text Text -> Either LangchainError Text
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: formatPrompt :: ChatPromptTemplate -> Map Text Text -> Either LangchainError ChatPromptValue
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: fromMessages :: [ChatPromptMessage] -> ChatPromptTemplate
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: fromTemplate :: Text -> ChatPromptTemplate
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: fromTemplateWithOptions :: Text -> PromptTemplateOptions -> ChatPromptTemplate
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.PromptTemplate.Chat.ChatPromptTemplate.ChatPromptInput
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.PromptTemplate.Chat.ChatPromptTemplate.ChatPromptMessage
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.PromptTemplate.Chat.ChatPromptTemplate.ChatPromptTemplate
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.PromptTemplate.Chat.ChatPromptTemplate.ChatPromptValue
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.PromptTemplate.Chat.ChatPromptTemplate.ContentPromptBlock
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.PromptTemplate.Chat.ChatPromptTemplate.PartialValue
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.PromptTemplate.Chat.ChatPromptTemplate.ChatPromptInput
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.PromptTemplate.Chat.ChatPromptTemplate.ChatPromptMessage
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.PromptTemplate.Chat.ChatPromptTemplate.ChatPromptTemplate
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.PromptTemplate.Chat.ChatPromptTemplate.ChatPromptValue
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.PromptTemplate.Chat.ChatPromptTemplate.ContentPromptBlock
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.PromptTemplate.Chat.ChatPromptTemplate.PartialValue
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: instance GHC.Classes.Eq Langchain.PromptTemplate.Chat.ChatPromptTemplate.ChatPromptInput
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: instance GHC.Classes.Eq Langchain.PromptTemplate.Chat.ChatPromptTemplate.ChatPromptMessage
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: instance GHC.Classes.Eq Langchain.PromptTemplate.Chat.ChatPromptTemplate.ChatPromptTemplate
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: instance GHC.Classes.Eq Langchain.PromptTemplate.Chat.ChatPromptTemplate.ChatPromptValue
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: instance GHC.Classes.Eq Langchain.PromptTemplate.Chat.ChatPromptTemplate.ContentPromptBlock
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: instance GHC.Classes.Eq Langchain.PromptTemplate.Chat.ChatPromptTemplate.PartialValue
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: instance GHC.Generics.Generic Langchain.PromptTemplate.Chat.ChatPromptTemplate.ChatPromptInput
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: instance GHC.Generics.Generic Langchain.PromptTemplate.Chat.ChatPromptTemplate.ChatPromptMessage
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: instance GHC.Generics.Generic Langchain.PromptTemplate.Chat.ChatPromptTemplate.ChatPromptTemplate
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: instance GHC.Generics.Generic Langchain.PromptTemplate.Chat.ChatPromptTemplate.ChatPromptValue
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: instance GHC.Generics.Generic Langchain.PromptTemplate.Chat.ChatPromptTemplate.ContentPromptBlock
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: instance GHC.Generics.Generic Langchain.PromptTemplate.Chat.ChatPromptTemplate.PartialValue
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: instance GHC.Show.Show Langchain.PromptTemplate.Chat.ChatPromptTemplate.ChatPromptInput
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: instance GHC.Show.Show Langchain.PromptTemplate.Chat.ChatPromptTemplate.ChatPromptMessage
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: instance GHC.Show.Show Langchain.PromptTemplate.Chat.ChatPromptTemplate.ChatPromptTemplate
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: instance GHC.Show.Show Langchain.PromptTemplate.Chat.ChatPromptTemplate.ChatPromptValue
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: instance GHC.Show.Show Langchain.PromptTemplate.Chat.ChatPromptTemplate.ContentPromptBlock
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: instance GHC.Show.Show Langchain.PromptTemplate.Chat.ChatPromptTemplate.PartialValue
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: invoke :: ChatPromptTemplate -> ChatPromptInput -> Either LangchainError ChatPromptValue
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: message :: Message -> ChatPromptMessage
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: messagesPlaceholder :: Text -> ChatPromptMessage
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: messagesPlaceholderWithOptions :: MessagesPlaceholderOptions -> ChatPromptMessage
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: newtype ChatPromptValue
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: partial :: ChatPromptTemplate -> Map Text PartialValue -> ChatPromptTemplate
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: templateMessage :: Role -> Text -> ChatPromptMessage
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: templateMessageWithFormat :: Role -> TemplateFormat -> Text -> ChatPromptMessage
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: toMessages :: ChatPromptValue -> [Message]
- Langchain.PromptTemplate.Chat.ChatPromptTemplate: toString :: ChatPromptValue -> Text
- Langchain.PromptTemplate.Chat.MessagesPlaceholder: MessagesPlaceholder :: Text -> Bool -> Maybe Int -> MessagesPlaceholder
- Langchain.PromptTemplate.Chat.MessagesPlaceholder: MessagesPlaceholderOptions :: Text -> Bool -> Maybe Int -> MessagesPlaceholderOptions
- Langchain.PromptTemplate.Chat.MessagesPlaceholder: [nMessages] :: MessagesPlaceholderOptions -> Maybe Int
- Langchain.PromptTemplate.Chat.MessagesPlaceholder: [optional] :: MessagesPlaceholderOptions -> Bool
- Langchain.PromptTemplate.Chat.MessagesPlaceholder: [variableName] :: MessagesPlaceholderOptions -> Text
- Langchain.PromptTemplate.Chat.MessagesPlaceholder: data MessagesPlaceholder
- Langchain.PromptTemplate.Chat.MessagesPlaceholder: data MessagesPlaceholderOptions
- Langchain.PromptTemplate.Chat.MessagesPlaceholder: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.PromptTemplate.Chat.MessagesPlaceholder.MessagesPlaceholder
- Langchain.PromptTemplate.Chat.MessagesPlaceholder: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.PromptTemplate.Chat.MessagesPlaceholder.MessagesPlaceholderOptions
- Langchain.PromptTemplate.Chat.MessagesPlaceholder: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.PromptTemplate.Chat.MessagesPlaceholder.MessagesPlaceholder
- Langchain.PromptTemplate.Chat.MessagesPlaceholder: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.PromptTemplate.Chat.MessagesPlaceholder.MessagesPlaceholderOptions
- Langchain.PromptTemplate.Chat.MessagesPlaceholder: instance GHC.Classes.Eq Langchain.PromptTemplate.Chat.MessagesPlaceholder.MessagesPlaceholder
- Langchain.PromptTemplate.Chat.MessagesPlaceholder: instance GHC.Classes.Eq Langchain.PromptTemplate.Chat.MessagesPlaceholder.MessagesPlaceholderOptions
- Langchain.PromptTemplate.Chat.MessagesPlaceholder: instance GHC.Generics.Generic Langchain.PromptTemplate.Chat.MessagesPlaceholder.MessagesPlaceholder
- Langchain.PromptTemplate.Chat.MessagesPlaceholder: instance GHC.Generics.Generic Langchain.PromptTemplate.Chat.MessagesPlaceholder.MessagesPlaceholderOptions
- Langchain.PromptTemplate.Chat.MessagesPlaceholder: instance GHC.Show.Show Langchain.PromptTemplate.Chat.MessagesPlaceholder.MessagesPlaceholder
- Langchain.PromptTemplate.Chat.MessagesPlaceholder: instance GHC.Show.Show Langchain.PromptTemplate.Chat.MessagesPlaceholder.MessagesPlaceholderOptions
- Langchain.PromptTemplate.Chat.MessagesPlaceholder: instance Langchain.PromptTemplate.Chat.BaseMessagePromptTemplate Langchain.PromptTemplate.Chat.MessagesPlaceholder.MessagesPlaceholder (Data.Map.Internal.Map Data.Text.Internal.Text [Langchain.Core.Model.Types.Message])
- Langchain.PromptTemplate.Chat.MessagesPlaceholder: messagesPlaceholder :: Text -> MessagesPlaceholder
- Langchain.PromptTemplate.Chat.MessagesPlaceholder: messagesPlaceholderOptions :: Text -> MessagesPlaceholderOptions
- Langchain.PromptTemplate.Chat.MessagesPlaceholder: messagesPlaceholderVariableName :: MessagesPlaceholder -> Text
- Langchain.PromptTemplate.Chat.MessagesPlaceholder: messagesPlaceholderWithOptions :: MessagesPlaceholderOptions -> MessagesPlaceholder
- Langchain.PromptTemplate.FewShot: FewShotPromptTemplate :: Text -> [Map Text Text] -> Text -> Text -> Text -> FewShotPromptTemplate
- Langchain.PromptTemplate.FewShot: [fsExampleSeparator] :: FewShotPromptTemplate -> Text
- Langchain.PromptTemplate.FewShot: [fsExampleTemplate] :: FewShotPromptTemplate -> Text
- Langchain.PromptTemplate.FewShot: [fsExamples] :: FewShotPromptTemplate -> [Map Text Text]
- Langchain.PromptTemplate.FewShot: [fsPrefix] :: FewShotPromptTemplate -> Text
- Langchain.PromptTemplate.FewShot: [fsSuffix] :: FewShotPromptTemplate -> Text
- Langchain.PromptTemplate.FewShot: data FewShotPromptTemplate
- Langchain.PromptTemplate.FewShot: instance GHC.Classes.Eq Langchain.PromptTemplate.FewShot.FewShotPromptTemplate
- Langchain.PromptTemplate.FewShot: instance GHC.Show.Show Langchain.PromptTemplate.FewShot.FewShotPromptTemplate
- Langchain.PromptTemplate.FewShot: renderFewShotPrompt :: FewShotPromptTemplate -> Either LangchainError Text
- Langchain.PromptTemplate.FewShot: renderFewShotPromptWithVars :: FewShotPromptTemplate -> Map Text Text -> Either LangchainError Text
- Langchain.PromptTemplate.Prompt: FString :: TemplateFormat
- Langchain.PromptTemplate.Prompt: PromptTemplate :: Text -> [Text] -> Map Text Text -> TemplateFormat -> PromptTemplate
- Langchain.PromptTemplate.Prompt: PromptTemplateOptions :: Map Text Text -> PromptTemplateOptions
- Langchain.PromptTemplate.Prompt: [inputVariables] :: PromptTemplate -> [Text]
- Langchain.PromptTemplate.Prompt: [partialVariables] :: PromptTemplateOptions -> Map Text Text
- Langchain.PromptTemplate.Prompt: [templateFormat] :: PromptTemplate -> TemplateFormat
- Langchain.PromptTemplate.Prompt: [template] :: PromptTemplate -> Text
- Langchain.PromptTemplate.Prompt: data PromptTemplate
- Langchain.PromptTemplate.Prompt: data TemplateFormat
- Langchain.PromptTemplate.Prompt: defaultPromptTemplateOptions :: PromptTemplateOptions
- Langchain.PromptTemplate.Prompt: extractTemplateVariables :: Text -> [Text]
- Langchain.PromptTemplate.Prompt: extractTemplateVariablesWithFormat :: TemplateFormat -> Text -> [Text]
- Langchain.PromptTemplate.Prompt: fromTemplate :: Text -> PromptTemplate
- Langchain.PromptTemplate.Prompt: fromTemplateWithFormat :: Text -> TemplateFormat -> Map Text Text -> PromptTemplate
- Langchain.PromptTemplate.Prompt: fromTemplateWithOptions :: Text -> PromptTemplateOptions -> PromptTemplate
- Langchain.PromptTemplate.Prompt: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.PromptTemplate.Prompt.PromptTemplate
- Langchain.PromptTemplate.Prompt: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.PromptTemplate.Prompt.PromptTemplateOptions
- Langchain.PromptTemplate.Prompt: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.PromptTemplate.Prompt.PromptTemplate
- Langchain.PromptTemplate.Prompt: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.PromptTemplate.Prompt.PromptTemplateOptions
- Langchain.PromptTemplate.Prompt: instance GHC.Base.Monad m => Langchain.Core.Runnable.Runnable Langchain.PromptTemplate.Prompt.PromptTemplate m
- Langchain.PromptTemplate.Prompt: instance GHC.Classes.Eq Langchain.PromptTemplate.Prompt.PromptTemplate
- Langchain.PromptTemplate.Prompt: instance GHC.Classes.Eq Langchain.PromptTemplate.Prompt.PromptTemplateOptions
- Langchain.PromptTemplate.Prompt: instance GHC.Generics.Generic Langchain.PromptTemplate.Prompt.PromptTemplate
- Langchain.PromptTemplate.Prompt: instance GHC.Generics.Generic Langchain.PromptTemplate.Prompt.PromptTemplateOptions
- Langchain.PromptTemplate.Prompt: instance GHC.Show.Show Langchain.PromptTemplate.Prompt.PromptTemplate
- Langchain.PromptTemplate.Prompt: instance GHC.Show.Show Langchain.PromptTemplate.Prompt.PromptTemplateOptions
- Langchain.PromptTemplate.Prompt: newtype PromptTemplateOptions
- Langchain.PromptTemplate.Prompt: partialPromptTemplate :: PromptTemplate -> Map Text Text -> PromptTemplate
- Langchain.PromptTemplate.Prompt: renderFStringTemplate :: Map Text Text -> Text -> Either LangchainError Text
- Langchain.PromptTemplate.Prompt: renderPrompt :: PromptTemplate -> Map Text Text -> Either LangchainError Text
- Langchain.PromptTemplate.Prompt: renderTemplateWithFormat :: TemplateFormat -> Map Text Text -> Text -> Either LangchainError Text
- Langchain.PromptTemplate.String: FString :: TemplateFormat
- Langchain.PromptTemplate.String: data TemplateFormat
- Langchain.PromptTemplate.String: extractTemplateVariables :: Text -> [Text]
- Langchain.PromptTemplate.String: extractTemplateVariablesWithFormat :: TemplateFormat -> Text -> [Text]
- Langchain.PromptTemplate.String: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.PromptTemplate.String.TemplateFormat
- Langchain.PromptTemplate.String: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.PromptTemplate.String.TemplateFormat
- Langchain.PromptTemplate.String: instance GHC.Classes.Eq Langchain.PromptTemplate.String.TemplateFormat
- Langchain.PromptTemplate.String: instance GHC.Generics.Generic Langchain.PromptTemplate.String.TemplateFormat
- Langchain.PromptTemplate.String: instance GHC.Show.Show Langchain.PromptTemplate.String.TemplateFormat
- Langchain.PromptTemplate.String: renderFStringTemplate :: Map Text Text -> Text -> Either LangchainError Text
- Langchain.PromptTemplate.String: renderTemplateWithFormat :: TemplateFormat -> Map Text Text -> Text -> Either LangchainError Text
- Langchain.Provider.Gemini: Gemini :: Text -> Text -> Maybe Text -> Gemini
- Langchain.Provider.Gemini: GeminiConfig :: Text -> Text -> GeminiConfig
- Langchain.Provider.Gemini: [apiKey] :: Gemini -> Text
- Langchain.Provider.Gemini: [baseUrl] :: Gemini -> Maybe Text
- Langchain.Provider.Gemini: [configApiKey] :: GeminiConfig -> Text
- Langchain.Provider.Gemini: [configModel] :: GeminiConfig -> Text
- Langchain.Provider.Gemini: [model] :: Gemini -> Text
- Langchain.Provider.Gemini: data Gemini
- Langchain.Provider.Gemini: data GeminiConfig
- Langchain.Provider.Gemini: defaultConfig :: Text -> GeminiConfig
- Langchain.Provider.Gemini: defaultGeminiConfig :: Text -> GeminiConfig
- Langchain.Provider.Gemini: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Provider.Gemini.GeminiConfig
- Langchain.Provider.Gemini: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Provider.Gemini.GeminiStreamCandidate
- Langchain.Provider.Gemini: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Provider.Gemini.GeminiStreamChunk
- Langchain.Provider.Gemini: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Provider.Gemini.GeminiConfig
- Langchain.Provider.Gemini: instance GHC.Classes.Eq Langchain.Provider.Gemini.Gemini
- Langchain.Provider.Gemini: instance GHC.Classes.Eq Langchain.Provider.Gemini.GeminiConfig
- Langchain.Provider.Gemini: instance GHC.Generics.Generic Langchain.Provider.Gemini.GeminiConfig
- Langchain.Provider.Gemini: instance GHC.Show.Show Langchain.Provider.Gemini.Gemini
- Langchain.Provider.Gemini: instance GHC.Show.Show Langchain.Provider.Gemini.GeminiConfig
- Langchain.Provider.Gemini: instance Langchain.Core.Model.ChatModel Langchain.Provider.Gemini.Gemini
- Langchain.Provider.Gemini: instance Langchain.Tool.Binding.ToolBinder Langchain.Provider.Gemini.Gemini m
- Langchain.Provider.Gemini: instance Servant.API.EventStream.FromServerEvent Langchain.Provider.Gemini.GeminiStreamEvent
- Langchain.Provider.Gemini: newGemini :: Text -> Text -> Maybe Text -> Gemini
- Langchain.Provider.Gemini: parseGeminiResponse :: Value -> Either String Message
- Langchain.Provider.Ollama: ($dmtoJsonType) :: (ToJsonType a, ToSchema a) => JsonType
- Langchain.Provider.Ollama: ($dmtoSchema) :: (ToSchema a, GToSchema (Rep a)) => Schema
- Langchain.Provider.Ollama: Base64Image :: Text -> Base64Image
- Langchain.Provider.Ollama: JArray :: !JsonType -> JsonType
- Langchain.Provider.Ollama: JBoolean :: JsonType
- Langchain.Provider.Ollama: JInteger :: JsonType
- Langchain.Provider.Ollama: JNull :: JsonType
- Langchain.Provider.Ollama: JNumber :: JsonType
- Langchain.Provider.Ollama: JObject :: !Schema -> JsonType
- Langchain.Provider.Ollama: JString :: JsonType
- Langchain.Provider.Ollama: JsonFormat :: Format
- Langchain.Provider.Ollama: ModelName :: Text -> ModelName
- Langchain.Provider.Ollama: Ollama :: OllamaClient -> Text -> Ollama
- Langchain.Provider.Ollama: OllamaWithTools :: !Ollama -> ![Tool m] -> OllamaWithTools (m :: Type -> Type)
- Langchain.Provider.Ollama: Property :: JsonType -> Property
- Langchain.Provider.Ollama: Schema :: !Map Text Property -> ![Text] -> Schema
- Langchain.Provider.Ollama: SchemaFormat :: !Schema -> Format
- Langchain.Provider.Ollama: [client] :: Ollama -> OllamaClient
- Langchain.Provider.Ollama: [ollamaBaseModel] :: OllamaWithTools (m :: Type -> Type) -> !Ollama
- Langchain.Provider.Ollama: [ollamaBoundTools] :: OllamaWithTools (m :: Type -> Type) -> ![Tool m]
- Langchain.Provider.Ollama: [ollamaModelName] :: Ollama -> Text
- Langchain.Provider.Ollama: [schemaProperties] :: Schema -> !Map Text Property
- Langchain.Provider.Ollama: [schemaRequired] :: Schema -> ![Text]
- Langchain.Provider.Ollama: [unBase64Image] :: Base64Image -> Text
- Langchain.Provider.Ollama: [unModelName] :: ModelName -> Text
- Langchain.Provider.Ollama: bindTools :: forall (m :: Type -> Type). [Tool m] -> Ollama -> OllamaWithTools m
- Langchain.Provider.Ollama: chatRequestFor :: Ollama -> [Message] -> ChatRequest
- Langchain.Provider.Ollama: class ToJsonType a
- Langchain.Provider.Ollama: class ToSchema a
- Langchain.Provider.Ollama: data Format
- Langchain.Provider.Ollama: data JsonType
- Langchain.Provider.Ollama: data Ollama
- Langchain.Provider.Ollama: data OllamaWithTools (m :: Type -> Type)
- Langchain.Provider.Ollama: data Schema
- Langchain.Provider.Ollama: fromOllamaMessage :: Message -> Message
- Langchain.Provider.Ollama: fromOllamaRole :: Role -> Role
- Langchain.Provider.Ollama: instance GHC.Show.Show (Langchain.Provider.Ollama.OllamaWithTools m)
- Langchain.Provider.Ollama: instance GHC.Show.Show Langchain.Provider.Ollama.Ollama
- Langchain.Provider.Ollama: instance Langchain.Core.Model.ChatModel (Langchain.Provider.Ollama.OllamaWithTools m)
- Langchain.Provider.Ollama: instance Langchain.Core.Model.ChatModel Langchain.Provider.Ollama.Ollama
- Langchain.Provider.Ollama: instance Langchain.Tool.Binding.ToolBinder (Langchain.Provider.Ollama.OllamaWithTools m) n
- Langchain.Provider.Ollama: instance Langchain.Tool.Binding.ToolBinder Langchain.Provider.Ollama.Ollama m
- Langchain.Provider.Ollama: newOllama :: MonadIO m => Text -> OllamaClientConfig -> m Ollama
- Langchain.Provider.Ollama: newOllamaWithClient :: Text -> OllamaClient -> Ollama
- Langchain.Provider.Ollama: newtype Base64Image
- Langchain.Provider.Ollama: newtype ModelName
- Langchain.Provider.Ollama: newtype Property
- Langchain.Provider.Ollama: resolveChatRequest :: Ollama -> [Message] -> Maybe ChatRequest -> (ChatRequest, Text, [Message])
- Langchain.Provider.Ollama: toJsonType :: ToJsonType a => JsonType
- Langchain.Provider.Ollama: toOllamaMessage :: Message -> Message
- Langchain.Provider.Ollama: toOllamaRole :: Role -> Role
- Langchain.Provider.Ollama: toOllamaTool :: forall (m :: Type -> Type). Tool m -> Maybe Tool
- Langchain.Provider.Ollama: toOllamaTools :: forall (m :: Type -> Type). [Tool m] -> [Tool]
- Langchain.Provider.Ollama: toSchema :: ToSchema a => Schema
- Langchain.Provider.Ollama: withJsonFormat :: ChatRequest -> ChatRequest
- Langchain.Provider.Ollama: withOptions :: ModelOptions -> ChatRequest -> ChatRequest
- Langchain.Provider.Ollama: withSchemaFormat :: Schema -> ChatRequest -> ChatRequest
- Langchain.Provider.Ollama: withStructuredOutput :: ToSchema a => ChatRequest -> ChatRequest
- Langchain.Provider.Ollama: withTools :: forall (m :: Type -> Type). [Tool m] -> ChatRequest -> ChatRequest
- Langchain.Provider.OpenAI: OpenAI :: Text -> Text -> Text -> Maybe Double -> OpenAI
- Langchain.Provider.OpenAI: OpenAIConfig :: Text -> Text -> Maybe Text -> Maybe Double -> OpenAIConfig
- Langchain.Provider.OpenAI: OpenAIToolAuto :: OpenAIToolChoice
- Langchain.Provider.OpenAI: OpenAIToolFunction :: Text -> OpenAIToolChoice
- Langchain.Provider.OpenAI: OpenAIToolNone :: OpenAIToolChoice
- Langchain.Provider.OpenAI: OpenAIToolRequired :: OpenAIToolChoice
- Langchain.Provider.OpenAI: [apiKey] :: OpenAI -> Text
- Langchain.Provider.OpenAI: [baseUrl] :: OpenAI -> Text
- Langchain.Provider.OpenAI: [configApiKey] :: OpenAIConfig -> Text
- Langchain.Provider.OpenAI: [configBaseUrl] :: OpenAIConfig -> Maybe Text
- Langchain.Provider.OpenAI: [configModel] :: OpenAIConfig -> Text
- Langchain.Provider.OpenAI: [configTemperature] :: OpenAIConfig -> Maybe Double
- Langchain.Provider.OpenAI: [model] :: OpenAI -> Text
- Langchain.Provider.OpenAI: [temperature] :: OpenAI -> Maybe Double
- Langchain.Provider.OpenAI: data OpenAI
- Langchain.Provider.OpenAI: data OpenAIConfig
- Langchain.Provider.OpenAI: data OpenAIToolChoice
- Langchain.Provider.OpenAI: defaultConfig :: Text -> OpenAIConfig
- Langchain.Provider.OpenAI: defaultOpenAIConfig :: Text -> OpenAIConfig
- Langchain.Provider.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Provider.OpenAI.OpenAIConfig
- Langchain.Provider.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Provider.OpenAI.OpenAIStreamChoice
- Langchain.Provider.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Provider.OpenAI.OpenAIStreamChunk
- Langchain.Provider.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Provider.OpenAI.OpenAIStreamDelta
- Langchain.Provider.OpenAI: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Provider.OpenAI.OpenAIStreamToolCall
- Langchain.Provider.OpenAI: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Provider.OpenAI.OpenAIConfig
- Langchain.Provider.OpenAI: instance GHC.Classes.Eq Langchain.Provider.OpenAI.OpenAI
- Langchain.Provider.OpenAI: instance GHC.Classes.Eq Langchain.Provider.OpenAI.OpenAIConfig
- Langchain.Provider.OpenAI: instance GHC.Classes.Eq Langchain.Provider.OpenAI.OpenAIToolChoice
- Langchain.Provider.OpenAI: instance GHC.Generics.Generic Langchain.Provider.OpenAI.OpenAIConfig
- Langchain.Provider.OpenAI: instance GHC.Show.Show Langchain.Provider.OpenAI.OpenAI
- Langchain.Provider.OpenAI: instance GHC.Show.Show Langchain.Provider.OpenAI.OpenAIConfig
- Langchain.Provider.OpenAI: instance GHC.Show.Show Langchain.Provider.OpenAI.OpenAIToolChoice
- Langchain.Provider.OpenAI: instance Langchain.Core.Model.ChatModel Langchain.Provider.OpenAI.OpenAI
- Langchain.Provider.OpenAI: instance Langchain.Tool.Binding.ToolBinder Langchain.Provider.OpenAI.OpenAI m
- Langchain.Provider.OpenAI: instance Servant.API.EventStream.FromServerEvent Langchain.Provider.OpenAI.OpenAIStreamEvent
- Langchain.Provider.OpenAI: newOpenAI :: Text -> Text -> OpenAI
- Langchain.Provider.OpenAI: normalizeBaseUrl :: Text -> Text
- Langchain.Provider.OpenAI: openAICompatible :: Text -> Text -> Text -> OpenAI
- Langchain.Provider.OpenAI: openAITools :: forall (m :: Type -> Type). [Tool m] -> OpenAIToolChoice -> Value
- Langchain.Provider.OpenAI: parseOpenAIResponse :: Value -> Either String (Message, Maybe TokenUsage)
- Langchain.Resilience.CircuitBreaker: CircuitBreaker :: !Text -> !CircuitBreakerConfig -> !TVar (CircuitState, Int) -> CircuitBreaker
- Langchain.Resilience.CircuitBreaker: CircuitBreakerConfig :: !Int -> !Double -> CircuitBreakerConfig
- Langchain.Resilience.CircuitBreaker: CircuitClosed :: CircuitState
- Langchain.Resilience.CircuitBreaker: CircuitHalfOpen :: CircuitState
- Langchain.Resilience.CircuitBreaker: CircuitOpen :: !UTCTime -> CircuitState
- Langchain.Resilience.CircuitBreaker: [circuitConfig] :: CircuitBreaker -> !CircuitBreakerConfig
- Langchain.Resilience.CircuitBreaker: [circuitName] :: CircuitBreaker -> !Text
- Langchain.Resilience.CircuitBreaker: [circuitStateVar] :: CircuitBreaker -> !TVar (CircuitState, Int)
- Langchain.Resilience.CircuitBreaker: [failureThreshold] :: CircuitBreakerConfig -> !Int
- Langchain.Resilience.CircuitBreaker: [resetTimeoutSec] :: CircuitBreakerConfig -> !Double
- Langchain.Resilience.CircuitBreaker: data CircuitBreaker
- Langchain.Resilience.CircuitBreaker: data CircuitBreakerConfig
- Langchain.Resilience.CircuitBreaker: data CircuitState
- Langchain.Resilience.CircuitBreaker: defaultCircuitConfig :: CircuitBreakerConfig
- Langchain.Resilience.CircuitBreaker: getCircuitState :: MonadIO m => CircuitBreaker -> m CircuitState
- Langchain.Resilience.CircuitBreaker: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Resilience.CircuitBreaker.CircuitBreakerConfig
- Langchain.Resilience.CircuitBreaker: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Resilience.CircuitBreaker.CircuitState
- Langchain.Resilience.CircuitBreaker: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Resilience.CircuitBreaker.CircuitBreakerConfig
- Langchain.Resilience.CircuitBreaker: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Resilience.CircuitBreaker.CircuitState
- Langchain.Resilience.CircuitBreaker: instance GHC.Classes.Eq Langchain.Resilience.CircuitBreaker.CircuitBreakerConfig
- Langchain.Resilience.CircuitBreaker: instance GHC.Classes.Eq Langchain.Resilience.CircuitBreaker.CircuitState
- Langchain.Resilience.CircuitBreaker: instance GHC.Generics.Generic Langchain.Resilience.CircuitBreaker.CircuitBreakerConfig
- Langchain.Resilience.CircuitBreaker: instance GHC.Generics.Generic Langchain.Resilience.CircuitBreaker.CircuitState
- Langchain.Resilience.CircuitBreaker: instance GHC.Show.Show Langchain.Resilience.CircuitBreaker.CircuitBreakerConfig
- Langchain.Resilience.CircuitBreaker: instance GHC.Show.Show Langchain.Resilience.CircuitBreaker.CircuitState
- Langchain.Resilience.CircuitBreaker: newCircuitBreaker :: MonadIO m => Text -> CircuitBreakerConfig -> m CircuitBreaker
- Langchain.Resilience.CircuitBreaker: withCircuitBreaker :: (MonadIO m, MonadError LangchainError m) => CircuitBreaker -> m a -> m a
- Langchain.Resilience.Retry: RateLimiter :: !Double -> !Double -> !TVar Double -> !TVar UTCTime -> RateLimiter
- Langchain.Resilience.Retry: RetryPolicy :: !Int -> !Int -> !Int -> !Bool -> RetryPolicy
- Langchain.Resilience.Retry: [baseDelayMicros] :: RetryPolicy -> !Int
- Langchain.Resilience.Retry: [bucketCapacity] :: RateLimiter -> !Double
- Langchain.Resilience.Retry: [lastRefillVar] :: RateLimiter -> !TVar UTCTime
- Langchain.Resilience.Retry: [maxDelayMicros] :: RetryPolicy -> !Int
- Langchain.Resilience.Retry: [maxRetries] :: RetryPolicy -> !Int
- Langchain.Resilience.Retry: [refillRatePerSec] :: RateLimiter -> !Double
- Langchain.Resilience.Retry: [tokensVar] :: RateLimiter -> !TVar Double
- Langchain.Resilience.Retry: [useJitter] :: RetryPolicy -> !Bool
- Langchain.Resilience.Retry: data RateLimiter
- Langchain.Resilience.Retry: data RetryPolicy
- Langchain.Resilience.Retry: defaultRetryPolicy :: RetryPolicy
- Langchain.Resilience.Retry: instance GHC.Classes.Eq Langchain.Resilience.Retry.RetryPolicy
- Langchain.Resilience.Retry: instance GHC.Show.Show Langchain.Resilience.Retry.RetryPolicy
- Langchain.Resilience.Retry: newRateLimiter :: MonadIO m => Double -> Double -> m RateLimiter
- Langchain.Resilience.Retry: withRateLimit :: MonadIO m => RateLimiter -> m a -> m a
- Langchain.Resilience.Retry: withRetry :: (MonadIO m, MonadError LangchainError m) => RetryPolicy -> m a -> m a
- Langchain.Retriever.BM25: BM25Index :: ![Document] -> !Map Int Int -> !Double -> !Map Text (Map Int Int) -> !Double -> !Double -> BM25Index
- Langchain.Retriever.BM25: [bm25AvgDocLen] :: BM25Index -> !Double
- Langchain.Retriever.BM25: [bm25B] :: BM25Index -> !Double
- Langchain.Retriever.BM25: [bm25DocLens] :: BM25Index -> !Map Int Int
- Langchain.Retriever.BM25: [bm25Docs] :: BM25Index -> ![Document]
- Langchain.Retriever.BM25: [bm25InvertedIndex] :: BM25Index -> !Map Text (Map Int Int)
- Langchain.Retriever.BM25: [bm25K1] :: BM25Index -> !Double
- Langchain.Retriever.BM25: addDocumentsBM25 :: [Document] -> BM25Index -> BM25Index
- Langchain.Retriever.BM25: bm25Search :: BM25Index -> Text -> Int -> [Document]
- Langchain.Retriever.BM25: bm25SearchWithScores :: BM25Index -> Text -> Int -> [(Document, Double)]
- Langchain.Retriever.BM25: data BM25Index
- Langchain.Retriever.BM25: instance GHC.Base.Monad m => Langchain.Core.Runnable.Runnable Langchain.Retriever.BM25.BM25Index m
- Langchain.Retriever.BM25: instance GHC.Classes.Eq Langchain.Retriever.BM25.BM25Index
- Langchain.Retriever.BM25: instance GHC.Generics.Generic Langchain.Retriever.BM25.BM25Index
- Langchain.Retriever.BM25: instance GHC.Show.Show Langchain.Retriever.BM25.BM25Index
- Langchain.Retriever.BM25: instance Langchain.Retriever.Core.Retriever Langchain.Retriever.BM25.BM25Index
- Langchain.Retriever.BM25: newBM25Index :: [Document] -> BM25Index
- Langchain.Retriever.BM25: newBM25IndexWithParams :: Double -> Double -> [Document] -> BM25Index
- Langchain.Retriever.BM25: tokenize :: Text -> [Text]
- Langchain.Retriever.Core: VectorStoreRetriever :: a -> VectorStoreRetriever a
- Langchain.Retriever.Core: [vs] :: VectorStoreRetriever a -> a
- Langchain.Retriever.Core: class Retriever a
- Langchain.Retriever.Core: getRelevantDocuments :: (Retriever a, MonadIO m, MonadError LangchainError m) => a -> Text -> m [Document]
- Langchain.Retriever.Core: instance (Langchain.VectorStore.Core.VectorStore a, GHC.Show.Show a) => GHC.Show.Show (Langchain.Retriever.Core.VectorStoreRetriever a)
- Langchain.Retriever.Core: instance GHC.Classes.Eq a => GHC.Classes.Eq (Langchain.Retriever.Core.VectorStoreRetriever a)
- Langchain.Retriever.Core: instance Langchain.VectorStore.Core.VectorStore a => Langchain.Retriever.Core.Retriever (Langchain.Retriever.Core.VectorStoreRetriever a)
- Langchain.Retriever.Core: newtype VectorStore a => VectorStoreRetriever a
- Langchain.Retriever.Core: retrieveWithCallbacks :: (Retriever a, MonadIO m, MonadError LangchainError m) => CallbackManager -> Text -> a -> Text -> m [Document]
- Langchain.Retriever.Core: runRetriever :: forall a (m :: Type -> Type). (Retriever a, MonadIO m) => a -> RunnableTree m Text [Document]
- Langchain.Retriever.Hybrid: HybridRetriever :: !BM25Index -> !Text -> Int -> IO [Document] -> !Double -> !Double -> !Double -> HybridRetriever
- Langchain.Retriever.Hybrid: [hybridBM25] :: HybridRetriever -> !BM25Index
- Langchain.Retriever.Hybrid: [hybridDenseWeight] :: HybridRetriever -> !Double
- Langchain.Retriever.Hybrid: [hybridRrfK] :: HybridRetriever -> !Double
- Langchain.Retriever.Hybrid: [hybridSparseWeight] :: HybridRetriever -> !Double
- Langchain.Retriever.Hybrid: [hybridVectorSearch] :: HybridRetriever -> !Text -> Int -> IO [Document]
- Langchain.Retriever.Hybrid: data HybridRetriever
- Langchain.Retriever.Hybrid: instance Control.Monad.IO.Class.MonadIO m => Langchain.Core.Runnable.Runnable Langchain.Retriever.Hybrid.HybridRetriever m
- Langchain.Retriever.Hybrid: instance GHC.Show.Show Langchain.Retriever.Hybrid.HybridRetriever
- Langchain.Retriever.Hybrid: instance Langchain.Retriever.Core.Retriever Langchain.Retriever.Hybrid.HybridRetriever
- Langchain.Retriever.Hybrid: newHybridRetriever :: BM25Index -> (Text -> Int -> IO [Document]) -> HybridRetriever
- Langchain.Retriever.Hybrid: newHybridRetrieverWithWeights :: BM25Index -> (Text -> Int -> IO [Document]) -> Double -> Double -> Double -> HybridRetriever
- Langchain.Retriever.Hybrid: reciprocalRankFusion :: Double -> [([Document], Double)] -> [(Document, Double)]
- Langchain.Retriever.Hybrid: searchHybrid :: MonadIO m => HybridRetriever -> Text -> Int -> m [Document]
- Langchain.Retriever.Hybrid: searchHybridWithScores :: MonadIO m => HybridRetriever -> Text -> Int -> m [(Document, Double)]
- Langchain.TextSplitter.Character: CharacterSplitterOps :: Int64 -> Text -> CharacterSplitterOps
- Langchain.TextSplitter.Character: [chunkSize] :: CharacterSplitterOps -> Int64
- Langchain.TextSplitter.Character: [separator] :: CharacterSplitterOps -> Text
- Langchain.TextSplitter.Character: data CharacterSplitterOps
- Langchain.TextSplitter.Character: defaultCharacterSplitterOps :: CharacterSplitterOps
- Langchain.TextSplitter.Character: instance GHC.Classes.Eq Langchain.TextSplitter.Character.CharacterSplitterOps
- Langchain.TextSplitter.Character: instance GHC.Show.Show Langchain.TextSplitter.Character.CharacterSplitterOps
- Langchain.TextSplitter.Character: splitText :: CharacterSplitterOps -> Text -> [Text]
- Langchain.TextSplitter.Code: CSharp :: Language
- Langchain.TextSplitter.Code: CodeSplitterOps :: Language -> Int64 -> Int64 -> CodeSplitterOps
- Langchain.TextSplitter.Code: Cpp :: Language
- Langchain.TextSplitter.Code: Go :: Language
- Langchain.TextSplitter.Code: Haskell :: Language
- Langchain.TextSplitter.Code: Java :: Language
- Langchain.TextSplitter.Code: JavaScript :: Language
- Langchain.TextSplitter.Code: MarkdownCode :: Language
- Langchain.TextSplitter.Code: Python :: Language
- Langchain.TextSplitter.Code: Rust :: Language
- Langchain.TextSplitter.Code: TypeScript :: Language
- Langchain.TextSplitter.Code: [codeChunkOverlap] :: CodeSplitterOps -> Int64
- Langchain.TextSplitter.Code: [codeChunkSize] :: CodeSplitterOps -> Int64
- Langchain.TextSplitter.Code: [codeLanguage] :: CodeSplitterOps -> Language
- Langchain.TextSplitter.Code: data CodeSplitterOps
- Langchain.TextSplitter.Code: data Language
- Langchain.TextSplitter.Code: defaultCodeSplitterOps :: Language -> CodeSplitterOps
- Langchain.TextSplitter.Code: instance GHC.Classes.Eq Langchain.TextSplitter.Code.CodeSplitterOps
- Langchain.TextSplitter.Code: instance GHC.Classes.Eq Langchain.TextSplitter.Code.Language
- Langchain.TextSplitter.Code: instance GHC.Enum.Bounded Langchain.TextSplitter.Code.Language
- Langchain.TextSplitter.Code: instance GHC.Enum.Enum Langchain.TextSplitter.Code.Language
- Langchain.TextSplitter.Code: instance GHC.Show.Show Langchain.TextSplitter.Code.CodeSplitterOps
- Langchain.TextSplitter.Code: instance GHC.Show.Show Langchain.TextSplitter.Code.Language
- Langchain.TextSplitter.Code: languageSeparators :: Language -> [Text]
- Langchain.TextSplitter.Code: splitCode :: CodeSplitterOps -> Text -> [Text]
- Langchain.TextSplitter.Markdown: MarkdownChunk :: Text -> Map Text Text -> MarkdownChunk
- Langchain.TextSplitter.Markdown: MarkdownSplitterOps :: Int64 -> Int64 -> [(Text, Text)] -> MarkdownSplitterOps
- Langchain.TextSplitter.Markdown: [chunkContent] :: MarkdownChunk -> Text
- Langchain.TextSplitter.Markdown: [chunkHeaders] :: MarkdownChunk -> Map Text Text
- Langchain.TextSplitter.Markdown: [headersToSplitOn] :: MarkdownSplitterOps -> [(Text, Text)]
- Langchain.TextSplitter.Markdown: [mdChunkOverlap] :: MarkdownSplitterOps -> Int64
- Langchain.TextSplitter.Markdown: [mdChunkSize] :: MarkdownSplitterOps -> Int64
- Langchain.TextSplitter.Markdown: data MarkdownChunk
- Langchain.TextSplitter.Markdown: data MarkdownSplitterOps
- Langchain.TextSplitter.Markdown: defaultMarkdownSplitterOps :: MarkdownSplitterOps
- Langchain.TextSplitter.Markdown: instance GHC.Classes.Eq Langchain.TextSplitter.Markdown.MarkdownChunk
- Langchain.TextSplitter.Markdown: instance GHC.Classes.Eq Langchain.TextSplitter.Markdown.MarkdownSplitterOps
- Langchain.TextSplitter.Markdown: instance GHC.Show.Show Langchain.TextSplitter.Markdown.MarkdownChunk
- Langchain.TextSplitter.Markdown: instance GHC.Show.Show Langchain.TextSplitter.Markdown.MarkdownSplitterOps
- Langchain.TextSplitter.Markdown: splitMarkdown :: MarkdownSplitterOps -> Text -> [Text]
- Langchain.TextSplitter.Markdown: splitMarkdownToChunks :: MarkdownSplitterOps -> Text -> [MarkdownChunk]
- Langchain.TextSplitter.RecursiveCharacter: RecursiveCharacterSplitterOps :: Int64 -> Int64 -> [Text] -> RecursiveCharacterSplitterOps
- Langchain.TextSplitter.RecursiveCharacter: [chunkOverlap] :: RecursiveCharacterSplitterOps -> Int64
- Langchain.TextSplitter.RecursiveCharacter: [chunkSize] :: RecursiveCharacterSplitterOps -> Int64
- Langchain.TextSplitter.RecursiveCharacter: [separators] :: RecursiveCharacterSplitterOps -> [Text]
- Langchain.TextSplitter.RecursiveCharacter: data RecursiveCharacterSplitterOps
- Langchain.TextSplitter.RecursiveCharacter: defaultRecursiveCharacterSplitterOps :: RecursiveCharacterSplitterOps
- Langchain.TextSplitter.RecursiveCharacter: instance GHC.Classes.Eq Langchain.TextSplitter.RecursiveCharacter.RecursiveCharacterSplitterOps
- Langchain.TextSplitter.RecursiveCharacter: instance GHC.Show.Show Langchain.TextSplitter.RecursiveCharacter.RecursiveCharacterSplitterOps
- Langchain.TextSplitter.RecursiveCharacter: splitTextRecursive :: RecursiveCharacterSplitterOps -> Text -> [Text]
- Langchain.TextSplitter.Token: TokenSplitterOps :: Int -> Int -> (Text -> Int) -> TokenSplitterOps
- Langchain.TextSplitter.Token: [maxTokens] :: TokenSplitterOps -> Int
- Langchain.TextSplitter.Token: [tokenCounter] :: TokenSplitterOps -> Text -> Int
- Langchain.TextSplitter.Token: [tokenOverlap] :: TokenSplitterOps -> Int
- Langchain.TextSplitter.Token: countTokensApprox :: Text -> Int
- Langchain.TextSplitter.Token: data TokenSplitterOps
- Langchain.TextSplitter.Token: defaultTokenSplitterOps :: TokenSplitterOps
- Langchain.TextSplitter.Token: instance GHC.Show.Show Langchain.TextSplitter.Token.TokenSplitterOps
- Langchain.TextSplitter.Token: splitByTokens :: TokenSplitterOps -> Text -> [Text]
- Langchain.Tool.Async: executeToolAsync :: MonadIO m => Tool IO -> Value -> m (Async (Either LangchainError Text))
- Langchain.Tool.Async: executeToolBatchConcurrently :: (MonadIO m, MonadError LangchainError m) => [(Tool IO, Value)] -> m [Text]
- Langchain.Tool.Async: executeToolWithTimeout :: (MonadIO m, MonadError LangchainError m) => Tool IO -> Value -> Int -> m Text
- Langchain.Tool.Binding: bindToolsConfig :: ToolBinder model m => [Tool m] -> Maybe (ModelConfig model) -> Maybe (ModelConfig model)
- Langchain.Tool.Binding: class ChatModel model => ToolBinder model (m :: Type -> Type)
- Langchain.Tool.Calculator: calculatorTool :: forall (m :: Type -> Type). MonadIO m => Tool m
- Langchain.Tool.Calculator: evaluateExpr :: Text -> Either String Double
- Langchain.Tool.FileSystem: listDirTool :: forall (m :: Type -> Type). MonadIO m => Tool m
- Langchain.Tool.FileSystem: readFileTool :: forall (m :: Type -> Type). MonadIO m => Tool m
- Langchain.Tool.FileSystem: writeFileTool :: forall (m :: Type -> Type). MonadIO m => Tool m
- Langchain.Tool.GenericSchema: ($dmderiveToolSchema) :: (DeriveToolSchema a, GToolRecordSchema (Rep a)) => Proxy a -> Value
- Langchain.Tool.GenericSchema: class DeriveToolSchema a
- Langchain.Tool.GenericSchema: deriveToolParametersSchema :: GToolRecordSchema (Rep a) => Proxy a -> Value
- Langchain.Tool.GenericSchema: deriveToolSchema :: DeriveToolSchema a => Proxy a -> Value
- Langchain.Tool.GenericSchema: instance (GHC.Generics.Selector s, Langchain.Tool.GenericSchema.ToolFieldSchema a) => Langchain.Tool.GenericSchema.GToolRecordSchema (GHC.Generics.M1 GHC.Generics.S s (GHC.Generics.K1 GHC.Generics.R a))
- Langchain.Tool.GenericSchema: instance (Langchain.Tool.GenericSchema.GToolRecordSchema f, Langchain.Tool.GenericSchema.GToolRecordSchema g) => Langchain.Tool.GenericSchema.GToolRecordSchema (f GHC.Generics.:*: g)
- Langchain.Tool.GenericSchema: instance Langchain.Tool.GenericSchema.GToolRecordSchema f => Langchain.Tool.GenericSchema.GToolRecordSchema (GHC.Generics.M1 GHC.Generics.C c f)
- Langchain.Tool.GenericSchema: instance Langchain.Tool.GenericSchema.GToolRecordSchema f => Langchain.Tool.GenericSchema.GToolRecordSchema (GHC.Generics.M1 GHC.Generics.D c f)
- Langchain.Tool.GenericSchema: instance Langchain.Tool.GenericSchema.ToolFieldSchema Data.Aeson.Types.Internal.Value
- Langchain.Tool.GenericSchema: instance Langchain.Tool.GenericSchema.ToolFieldSchema Data.Scientific.Scientific
- Langchain.Tool.GenericSchema: instance Langchain.Tool.GenericSchema.ToolFieldSchema Data.Text.Internal.Text
- Langchain.Tool.GenericSchema: instance Langchain.Tool.GenericSchema.ToolFieldSchema Data.Time.Calendar.Days.Day
- Langchain.Tool.GenericSchema: instance Langchain.Tool.GenericSchema.ToolFieldSchema Data.Time.Clock.Internal.UTCTime.UTCTime
- Langchain.Tool.GenericSchema: instance Langchain.Tool.GenericSchema.ToolFieldSchema GHC.Base.String
- Langchain.Tool.GenericSchema: instance Langchain.Tool.GenericSchema.ToolFieldSchema GHC.Int.Int16
- Langchain.Tool.GenericSchema: instance Langchain.Tool.GenericSchema.ToolFieldSchema GHC.Int.Int32
- Langchain.Tool.GenericSchema: instance Langchain.Tool.GenericSchema.ToolFieldSchema GHC.Int.Int64
- Langchain.Tool.GenericSchema: instance Langchain.Tool.GenericSchema.ToolFieldSchema GHC.Int.Int8
- Langchain.Tool.GenericSchema: instance Langchain.Tool.GenericSchema.ToolFieldSchema GHC.Num.Integer.Integer
- Langchain.Tool.GenericSchema: instance Langchain.Tool.GenericSchema.ToolFieldSchema GHC.Types.Bool
- Langchain.Tool.GenericSchema: instance Langchain.Tool.GenericSchema.ToolFieldSchema GHC.Types.Char
- Langchain.Tool.GenericSchema: instance Langchain.Tool.GenericSchema.ToolFieldSchema GHC.Types.Double
- Langchain.Tool.GenericSchema: instance Langchain.Tool.GenericSchema.ToolFieldSchema GHC.Types.Float
- Langchain.Tool.GenericSchema: instance Langchain.Tool.GenericSchema.ToolFieldSchema GHC.Types.Int
- Langchain.Tool.GenericSchema: instance Langchain.Tool.GenericSchema.ToolFieldSchema GHC.Types.Word
- Langchain.Tool.GenericSchema: instance Langchain.Tool.GenericSchema.ToolFieldSchema GHC.Word.Word16
- Langchain.Tool.GenericSchema: instance Langchain.Tool.GenericSchema.ToolFieldSchema GHC.Word.Word32
- Langchain.Tool.GenericSchema: instance Langchain.Tool.GenericSchema.ToolFieldSchema GHC.Word.Word64
- Langchain.Tool.GenericSchema: instance Langchain.Tool.GenericSchema.ToolFieldSchema GHC.Word.Word8
- Langchain.Tool.GenericSchema: instance Langchain.Tool.GenericSchema.ToolFieldSchema a => Langchain.Tool.GenericSchema.ToolFieldSchema (Data.Map.Internal.Map Data.Text.Internal.Text a)
- Langchain.Tool.GenericSchema: instance Langchain.Tool.GenericSchema.ToolFieldSchema a => Langchain.Tool.GenericSchema.ToolFieldSchema (GHC.Maybe.Maybe a)
- Langchain.Tool.GenericSchema: instance Langchain.Tool.GenericSchema.ToolFieldSchema a => Langchain.Tool.GenericSchema.ToolFieldSchema [a]
- Langchain.Tool.Shell: shellTool :: forall (m :: Type -> Type). MonadIO m => Tool m
- Langchain.VectorStore.Core: addDocuments :: (VectorStore vs, MonadIO m, MonadError LangchainError m) => vs -> [Document] -> m vs
- Langchain.VectorStore.Core: class VectorStore vs
- Langchain.VectorStore.Core: delete :: (VectorStore vs, MonadIO m, MonadError LangchainError m) => vs -> [Int64] -> m vs
- Langchain.VectorStore.Core: similaritySearch :: (VectorStore vs, MonadIO m, MonadError LangchainError m) => vs -> Text -> Int -> m [Document]
- Langchain.VectorStore.Core: similaritySearchByVector :: (VectorStore vs, MonadIO m, MonadError LangchainError m) => vs -> [Float] -> Int -> m [Document]
- Langchain.VectorStore.InMemory: InMemory :: m -> Map Int64 (Document, [Float]) -> InMemory m
- Langchain.VectorStore.InMemory: [embeddingModel] :: InMemory m -> m
- Langchain.VectorStore.InMemory: [store] :: InMemory m -> Map Int64 (Document, [Float])
- Langchain.VectorStore.InMemory: cosineSimilarity :: [Float] -> [Float] -> Float
- Langchain.VectorStore.InMemory: data InMemory m
- Langchain.VectorStore.InMemory: dotProduct :: [Float] -> [Float] -> Float
- Langchain.VectorStore.InMemory: emptyInMemoryVectorStore :: m -> InMemory m
- Langchain.VectorStore.InMemory: fromDocuments :: (Embeddings m, MonadIO monad, MonadError LangchainError monad) => m -> [Document] -> monad (InMemory m)
- Langchain.VectorStore.InMemory: instance GHC.Classes.Eq m => GHC.Classes.Eq (Langchain.VectorStore.InMemory.InMemory m)
- Langchain.VectorStore.InMemory: instance GHC.Show.Show m => GHC.Show.Show (Langchain.VectorStore.InMemory.InMemory m)
- Langchain.VectorStore.InMemory: instance Langchain.Embeddings.Core.Embeddings m => Langchain.VectorStore.Core.VectorStore (Langchain.VectorStore.InMemory.InMemory m)
- Langchain.VectorStore.InMemory: norm :: [Float] -> Float
- Langchain.VectorStore.SqliteVec: SqliteVecStore :: FilePath -> e -> SqliteVecStore e
- Langchain.VectorStore.SqliteVec: [sqliteDbPath] :: SqliteVecStore e -> FilePath
- Langchain.VectorStore.SqliteVec: [sqliteEmbeddings] :: SqliteVecStore e -> e
- Langchain.VectorStore.SqliteVec: data SqliteVecStore e
- Langchain.VectorStore.SqliteVec: initSqliteVecSchema :: (MonadIO m, MonadError LangchainError m) => FilePath -> m ()
- Langchain.VectorStore.SqliteVec: instance Langchain.Embeddings.Core.Embeddings e => Langchain.VectorStore.Core.VectorStore (Langchain.VectorStore.SqliteVec.SqliteVecStore e)
- Langchain.VectorStore.SqliteVec: newSqliteVecStore :: (MonadIO m, MonadError LangchainError m) => FilePath -> e -> m (SqliteVecStore e)

Files

CHANGELOG.md view
@@ -1,5 +1,51 @@ # Changelog for `langchain-hs` +## 0.0.6.0 - 2026-10-15++### Highlights++- **Complete Modular Provider Architecture**:+  All concrete AI providers and protocol clients are now decoupled into standalone Hackage packages:+  - `langchain-hs-core` (0.0.6.0): Core abstractions, runnables, tools, agents, vector stores, memory, output parsers, and resilience.+  - `langchain-hs-graph` (0.0.6.0): Production StateGraph engine with state reducers, checkpointing, and time-travel replay.+  - `langchain-hs-ollama` (0.0.6.0): Ollama chat model and embeddings provider.+  - `langchain-hs-openai` (0.0.6.0): OpenAI, OpenRouter, and OpenAI-compatible provider with structured output and streaming.+  - `langchain-hs-gemini` (0.0.6.0): Google Gemini provider with function calling and SSE streaming.+  - `langchain-hs-mcp` (0.0.6.0): Model Context Protocol (MCP) client over stdio and HTTP JSON-RPC 2.0.+  - `langchain-hs` (0.0.6.0): Umbrella package re-exporting `langchain-hs-core` and `langchain-hs-graph`.++### StateGraph Productionization (`langchain-hs-graph`)++- Replaced prototype modules with robust execution combinators: `runGraph`, `runGraphWithConfig`, `runGraphInterleaved`, and `streamGraphSteps`.+- Added `runnableNode` and `chatModelNode` combinators to lift arbitrary `Runnable` and `ChatModel` instances into graph nodes.+- Full support for recursion limits, step-by-step transition logging, checkpointing, and human-in-the-loop interruption.++### Memory Hardening (`langchain-hs-core`)++- **`InVariableMemory`**: Thread-safe STM-backed transactional memory with snapshotting, resets, and clear semantics.+- **`SessionMemory`**: Multi-session conversation manager supporting per-session state isolation, active session tracking, and TTL-based pruning.+- **`FileMemory`**: Persistent file-backed conversation memory with atomic disk writes and metadata preservation across process restarts.+- **Reset Helpers**: Added explicit seed-state reset combinators `resetConversationBuffer`, `resetWindowBuffer`, `resetTokenBuffer`, and `resetEntitiesToInitial`.+- **Umbrella Prelude Re-exports**: Re-exported all memory implementations (`ConversationBufferMemory`, `SessionMemoryStore`, `EntityMemory`, `CombinedMemory`), buffer reset helpers, and `splitDocuments` in `Langchain.Prelude`.++### Output Parsers & Document Loaders++- Trimmed fragile character-scanning parsers (`NumberSeparatedList`) and redundant wrapper types (`JSONOutputStructure`) in favor of type-safe `StructuredOutput` and `TextOutputParser`.+- Hardened boolean output parser to exact normalized token recognition.+- Added standard numeric `OutputParser` instances for `Int`, `Integer`, and `Double` with whitespace stripping.+- Eliminated lazy I/O file handle leaks in `FileLoader` and `DirectoryLoader` with strict text I/O.+- Added `splitDocuments` preserving document metadata across chunk splits, and provided default `loadAndSplit` implementation in `BaseLoader`.++### Breaking Changes from 0.0.5.0++- Provider modules are no longer bundled in `langchain-hs`:+  - For Ollama: Add `langchain-hs-ollama` to dependencies and `import Langchain.Provider.Ollama`.+  - For OpenAI: Add `langchain-hs-openai` to dependencies and `import Langchain.Provider.OpenAI`.+  - For Gemini: Add `langchain-hs-gemini` to dependencies and `import Langchain.Provider.Gemini`.+  - For MCP: Add `langchain-hs-mcp` to dependencies and `import Langchain.MCP.Client`.+- `Langchain.Prelude` no longer re-exports provider-specific constructors (`newOllama`, `newOpenAI`, `newGemini`, `newStdioMcpClient`). Import the corresponding provider package.+- `JSONOutputStructure` and `NumberSeparatedList` have been removed from `Langchain.OutputParser.Core` and `Langchain.Prelude`. Use `StructuredOutput` or `TextOutputParser`.+ ## 0.0.5.0 - 2026-09-10  ### Major Architecture & Ecosystem Evolution@@ -29,7 +75,7 @@   - Migrated to `MercuryTechnologies/openai` client.   - Full PVP upper bounds across all packages for Hackage compliance. -### ⚠️ Breaking Changes from 0.0.3.0+### Breaking Changes from 0.0.3.0  - **`LangchainT` is now parameterized over `env`** (`LangchainT env m a` instead of the former implicit `LangchainConfig`).   - Replace `runLangchainT config action` with `runLangchainT env action` where `env` is your custom environment type (use `()` if you have no shared config).
README.md view
@@ -1,275 +1,266 @@-# 🦜️🔗 LangChain Haskell (`langchain-hs`)+# LangChain Haskell (langchain-hs) -> **The Pure Functional, Effect-Polymorphic AI Agent & Multi-Agent Graph Engine in Haskell**->-> *A strictly typed, effect-polymorphic, AI ecosystem built on pure AST pipelines (`RunnableTree`), cyclic state machines (`StateGraph`), Model Context Protocol (MCP), and production observability.*+> Type-Safe AI Agents & Multi-Agent Graphs in Haskell ----+A functional, effect-polymorphic Haskell ecosystem for building production AI applications, featuring pure AST pipelines (`RunnableTree`), cyclic state graphs (`StateGraph`), Model Context Protocol (MCP), and OpenTelemetry observability. -[![Hackage](https://img.shields.io/badge/hackage-0.0.5.0-blue.svg)](https://hackage.haskell.org/package/langchain-hs)+[![Hackage](https://img.shields.io/badge/hackage-0.0.6.0-blue.svg)](https://hackage.haskell.org/package/langchain-hs) [![GHC](https://img.shields.io/badge/GHC-9.8%2B-purple.svg)](https://www.haskell.org/ghc/)-[![Components](https://img.shields.io/badge/components-20%20verified-brightgreen.svg)](#-20-core-components--verified-targets)-[![Providers](https://img.shields.io/badge/providers-Ollama%20%7C%20OpenAI%20%7C%20Gemini-orange.svg)](#-dual-provider-parity-ollama--openai) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)-[![Whitepaper](https://img.shields.io/badge/whitepaper-read%20now-blueviolet.svg)](whitepaper.md)-------## Why `langchain-hs`?--Modern AI orchestration frameworks often struggle with race conditions, hidden side-effects, fragile dynamic schemas, and uninspectable opaque execution chains. `langchain-hs` brings mathematical precision and functional programming principles to AI development:--1. **First-Class Runnable AST Composition (`RunnableTree`)**: Every component—models, prompts, tools, chains, retrievers, and parsers—implements the `Runnable` typeclass. Connect components into trees or graphs using type-safe operators:-   - `|>>` : Sequential composition (data flows from left to right).-   - `&>&` : Parallel fan-out (concurrent evaluation of independent branches).-   - `>>>#` : Fallback chains (automatic failover if the primary branch errors).-2. **LangGraph in Haskell (`StateGraph`)**: Full cyclic state machine engine with pure monoidal state reducers (`StateReducer s`), thread-safe STM memory checkpointers (`TVar`), persistent SQLite checkpointers, Human-in-the-Loop (`HITL`) interrupts, and Time-Travel state replay.+[![Documentation](https://img.shields.io/badge/docs-website-blueviolet.svg)](https://tusharad.github.io/langchain-hs/)  --- +## Installation -### Monorepo Packages+Install `langchain-hs` and provider packages via **Cabal**: -| Package | Directory | Version | Description |-|---|---|---|---|-| `langchain-hs-core` | [`langchain-hs-core/`](./langchain-hs-core) | `0.0.5.0` | Zero-dependency pure core: `RunnableTree`, `ChatModel`, `ContentBlock`, `Tool`, and `LangchainT`. |-| `langchain-hs-graph` | [`langchain-hs-graph/`](./langchain-hs-graph) | `0.0.5.0` | Stateful graph engine: `StateGraph s m`, checkpointers, HITL, time-travel, and parallel nodes. |-| `langchain-hs` | [`./`](./) | `0.0.5.0` | Production ecosystem: Ollama/OpenAI providers, Agents, MCP, Vector Stores, Chains, Observability. |-| `examples` | [`examples/`](./examples) | - | 41 runnable executables covering all 20 components for Ollama and OpenAI. |-| `site` | [`site/`](./site) | - | Hakyll documentation website with live provider toggle and component reference. |+```bash+cabal update+cabal install langchain-hs langchain-hs-ollama langchain-hs-openai+``` ----+Or add to your `stack.yaml`: -## 20 Core Components & Verified Targets+```yaml+extra-deps:+  - langchain-hs-0.0.6.0+  - langchain-hs-core-0.0.6.0+  - langchain-hs-graph-0.0.6.0+  - langchain-hs-ollama-0.0.6.0+  - langchain-hs-openai-0.0.6.0+``` -| # | Component | Package Layer | Ollama Executable | OpenAI Executable | Documentation |-|:---:|---|---|---|---|:---:|-| 1 | **Chat Models** | `Langchain.Core.Model` | `stack run simpleollama` | `stack run simpleopenai` | [Docs](site/components/chat-models.md) |-| 2 | **Conduit Streaming** | `Langchain.Core.Stream` | `stack run streamollama` | `stack run streamopenai` | [Docs](site/components/streaming.md) |-| 3 | **Langchain Monad** | `Langchain.Core.Monad` | `stack run monadollama` | `stack run monadopenai` | [Docs](site/components/monad.md) |-| 4 | **Tools & Function Calling** | `Langchain.Core.Tool` | `stack run toolollama` | `stack run toolopenai` | [Docs](site/components/tools.md) |-| 5 | **Structured Outputs** | `Langchain.OutputParser` | `stack run jsonollama` | `stack run jsonopenai` | [Docs](site/components/structured-output.md) |-| 6 | **RAG & Embeddings** | `Langchain.Embedding` | `stack run ragollama` | `stack run ragopenai` | [Docs](site/components/rag.md) |-| 7 | **Hybrid Retrievers** | `Langchain.Retriever` | `stack run retrieverollama` | `stack run retrieveropenai` | [Docs](site/components/retrievers.md) |-| 8 | **Memory Systems** | `Langchain.Memory` | `stack run memoryollama` | `stack run memoryopenai` | [Docs](site/components/memory.md) |-| 9 | **Retrieval QA Chains** | `Langchain.Chain.RetrievalQA` | `stack run retrievalqaollama` | `stack run retrievalqaopenai` | [Docs](site/components/retrieval-qa.md) |-| 10 | **Map-Reduce Processing** | `Langchain.Chain.MapReduce` | `stack run mapreduceollama` | `stack run mapreduceopenai` | [Docs](site/components/map-reduce.md) |-| 11 | **ReAct Agent** | `Langchain.Agent.ReAct` | `stack run reactollama` | `stack run reactopenai` | [Docs](site/components/react-agent.md) |-| 12 | **Plan-and-Execute Agent** | `Langchain.Agent.PlanAndExecute` | `stack run planandexecuteollama` | `stack run planandexecuteopenai` | [Docs](site/components/plan-and-execute.md) |-| 13 | **Guardrails & Safety** | `Langchain.Guardrails` | `stack run guardrailollama` | `stack run guardrailopenai` | [Docs](site/components/guardrails.md) |-| 14 | **Resilience & Retries** | `Langchain.Resilience` | `stack run resilienceollama` | `stack run resilienceopenai` | [Docs](site/components/resilience.md) |-| 15 | **Observability & Tracing** | `Langchain.Observability` | `stack run observabilityollama` | `stack run observabilityopenai` | [Docs](site/components/observability.md) |-| 16 | **Model Context Protocol** | `Langchain.MCP.Client` | `stack run mcpollama` | `stack run mcpopenai` | [Docs](site/components/mcp.md) |-| 17 | **StateGraph Workflows** | `Langchain.Graph` | `stack run stategraphollama` | `stack run stategraphopenai` | [Docs](site/components/state-graph.md) |-| 18 | **Multi-Agent Systems** | `Langchain.Graph.MultiAgent` | `stack run multiagentollama` | `stack run multiagentopenai` | [Docs](site/components/multi-agent.md) |-| 19 | **Human-in-the-Loop (HITL)** | `Langchain.Graph.Checkpointer` | `stack run hitlollama` | `stack run hitlopenai` | [Docs](site/components/hitl.md) |-| 20 | **Runnables & AST Composition** | `Langchain.Core.Runnable` | `stack run runnableollama` | `stack run runnableopenai` | [Docs](site/components/runnables.md) |+Package reference on Hackage: [https://hackage.haskell.org/package/langchain-hs](https://hackage.haskell.org/package/langchain-hs)  --- -## Code Showcases--### 1. The Power of Runnables: Pure AST Composition+## 1. Simple Chat Calling -Compose complex multi-stage pipelines using typed operators without executing any `IO` until interpretation:+Send a prompt to an LLM (local Ollama, OpenAI, or Gemini) and retrieve a typed response:  ```haskell {-# LANGUAGE OverloadedStrings #-} module Main where +import Control.Monad.Except (runExceptT)+import qualified Data.Text.IO as T import Langchain.Prelude---- Compose pure AST pipelines with (|>>), (&>&), and (>>>#)-pipeline :: RunnableTree IO Text Text-pipeline =-      runLambda (\q -> (q, q))                          -- duplicate input query-  |>> (fetchDocuments &>& generateFollowup)              -- parallel branch fan-out-  |>> runLambda (\(docs, fup) -> renderPrompt docs fup) -- pure prompt synthesis-  |>> (invokeLLM primaryModel >>># invokeLLM backupModel) -- fallback resilience-  |>> parseStructuredResponse                           -- JSON parser+import Langchain.Provider.Ollama  main :: IO () main = do-  output <- interpret pipeline "Explain Monads in Haskell"-  print output+  -- Connect to local Ollama instance (DeepSeek, Llama 3, Gemma, etc.)+  model <- newOllama "llama3.2" defaultConfig+  +  let messages = [userMessage "Explain in two sentences why Haskell makes AI agents more reliable."]+  result <- runExceptT $ invoke model messages Nothing+  +  case result of+    Left err   -> T.putStrLn $ errorMessage err+    Right resp -> T.putStrLn $ extractMessageText resp ``` +### Output++```text+Haskell's static type system prevents common agent failures like missing dictionary keys or unexpected nulls before code ever reaches production. Furthermore, pure state transitions guarantee that autonomous agents cannot silently mutate variables behind your back.+```+ --- -### 2. Dual-Provider Chat Comparison: Ollama vs OpenAI+## 2. JSON Structured Output -#### Ollama (Local & Offline)+Guarantee that the LLM response conforms to a typed Haskell record using `FromJSON` and `ToSchema`:+ ```haskell+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE OverloadedStrings #-}-import Control.Monad.Except (runExceptT)+{-# LANGUAGE TypeApplications #-}+module Main where++import Data.Aeson (FromJSON)+import qualified Data.Text as T import qualified Data.Text.IO as T+import GHC.Generics (Generic) import Langchain.Prelude+import Langchain.Provider.Ollama +-- Define your target data schema+data Developer = Developer+  { name              :: T.Text+  , preferredLanguage :: T.Text+  , yearsOfExperience :: Int+  , favoriteLibrary   :: T.Text+  } deriving (Show, Generic, FromJSON, ToSchema)+ main :: IO () main = do-  -- Connect to local Ollama instance (DeepSeek, Llama 3, Gemma)-  model <- newOllama "gemma3" defaultConfig+  model <- newOllama "llama3.2" defaultConfig   -  let msg = [userMessage "Write a poem about functional programming"]-  res <- runExceptT $ invoke model msg Nothing-  case res of-    Left err -> T.putStrLn $ errorMessage err-    Right m  -> T.putStrLn $ extractMessageText m+  let prompt =+        [ userMessage "Extract profile: Alan has been writing Haskell for 8 years and loves langchain-hs." ]+      options = withStructuredOutput (Proxy @Developer) defaultOllamaConfig++  result <- runLangchainT () $ invoke model prompt (Just options)+  case result of+    Left err  -> T.putStrLn $ errorMessage err+    Right msg -> T.putStrLn $ extractMessageText msg ```-*Run:* `stack run simpleollama` -#### OpenAI / OpenRouter (Cloud)+### Output++```json+{+  "name": "Alan",+  "preferredLanguage": "Haskell",+  "yearsOfExperience": 8,+  "favoriteLibrary": "langchain-hs"+}+```++---++## 3. Tool Calling++Equip models with effectful Haskell tools. The LLM automatically emits structured tool calls, which your application executes and feeds back for final synthesis:+ ```haskell {-# LANGUAGE OverloadedStrings #-}-import Control.Monad.Except (runExceptT)+{-# LANGUAGE TypeApplications #-}+module Main where++import Control.Monad.IO.Class (liftIO)+import qualified Data.Text as T import qualified Data.Text.IO as T import Langchain.Prelude-import OpenAI.Common (defaultModelName, getOpenRouterModel)+import Langchain.Provider.Ollama  main :: IO () main = do-  -- Connect to OpenAI or OpenRouter using environment API key-  model <- getOpenRouterModel defaultModelName+  model <- newOllama "qwen2.5:7b" defaultConfig   -  let msg = [userMessage "Write a poem about functional programming"]-  res <- runExceptT $ invoke model msg Nothing-  case res of-    Left err -> T.putStrLn $ errorMessage err-    Right m  -> T.putStrLn $ extractMessageText m-```-*Run:* `stack run simpleopenai`-----+  -- Instantiate typed filesystem tool+  let fsTool   = findFilesTool @IO defaultSecurityPolicy+      toolCfg  = withTools [fsTool] defaultOllamaConfig+      prompt   = [userMessage "Search for Haskell source files using find_files with pattern '*.hs'."] -### 3. Stateful Graphs (`StateGraph`): Cyclic Multi-Agent Workflow+  res <- runLangchainT () $ do+    response <- invoke model prompt (Just toolCfg)+    case messageToolCalls response of+      Just (tCall:_) -> do+        liftIO $ T.putStrLn $ "Tool Invoked: " <> toolCallName tCall+        liftIO $ T.putStrLn $ "Arguments:    " <> T.pack (show (toolCallArguments tCall))+        +        -- Execute tool with LLM arguments+        execResult <- liftIO $ toolExecute fsTool (toolCallArguments tCall)+        let observation = case execResult of+              Left err  -> "Error: " <> errorMessage err+              Right out -> out+        liftIO $ T.putStrLn $ "Observation:  " <> observation+        +        -- Supply tool observation back to model for final response+        let toolMsg = (toolMessage observation) { messageName = Just (toolCallName tCall) }+            thread  = prompt ++ [response, toolMsg]+            +        finalMsg <- invoke model thread (Just toolCfg)+        liftIO $ T.putStrLn "\nFinal Assistant Answer:"+        liftIO $ T.putStrLn $ extractMessageText finalMsg+      _ -> do+        liftIO $ T.putStrLn $ extractMessageText response -```haskell-{-# LANGUAGE OverloadedStrings #-}-import Langchain.Graph.StateGraph-import Langchain.Prelude+  case res of+    Left err -> T.putStrLn $ "Error: " <> errorMessage err+    Right () -> pure ()+``` --- Pure state with a list-append reducer-data AgentState = AgentState { messages :: [Message], loopCount :: Int }+### Output --- Build the graph using pure combinators-workflow :: StateGraph AgentState IO-workflow =-  addEdge "reviewer" "planner"          -- cyclic feedback loop!-    $ addConditionalEdge "executor"-        (\s -> pure $ if done s then Right endNodeId else Right "reviewer")-    $ addEdge "planner" "executor"-    $ addEdge startNodeId "planner"-    $ addNode "reviewer" (Node reviewerNode replaceFieldReducer)-    $ addNode "executor" (Node executorNode replaceFieldReducer)-    $ addNode "planner"  (Node plannerNode  replaceFieldReducer)-    $ emptyStateGraph+```text+Tool Invoked: find_files+Arguments:    fromList [("pattern",String "*.hs")]+Observation:  ["src/Main.hs","src/Agent.hs","src/Tools.hs"] -main :: IO ()-main = do-  checkpointer <- newMemoryCheckpointer-  case compileGraph workflow of-    Left err -> print err-    Right compiled -> do-      result <- runGraph compiled initialState (Just checkpointer)-      print result+Final Assistant Answer:+I scanned the repository and identified 3 Haskell source files matching your pattern:+- src/Main.hs+- src/Agent.hs+- src/Tools.hs ```-*Run:* `stack run stategraphollama` or `stack run stategraphopenai`  --- -### 4. Model Context Protocol (MCP) Tools Integration+## 4. ReAct Agent -Connect Haskell agents to any external MCP server (e.g., Hackage doc search, SQLite, Filesystem, GitHub) over stdio:+Run an autonomous **Reason + Act** agent loop that decomposes user tasks, selects tools, evaluates observations, and iterates until completion:  ```haskell {-# LANGUAGE OverloadedStrings #-}+module Main where++import Control.Monad.Except (runExceptT)+import qualified Data.Text.IO as T import Langchain.Prelude+import Langchain.Provider.Ollama  main :: IO () main = do-  -- Connect to any MCP server via stdio JSON-RPC 2.0-  client <- newStdioMcpClient "docker" ["run", "-i", "--rm", "mcp/hackage-doc"]+  -- Setup sample file for demonstration+  writeFile "/tmp/server_status.txt" "Service: api-gateway, Status: Degraded, Latency: 420ms"   -  -- Discover available tools from server-  mcpTools <- listMcpTools client-  let nativeTools = map mcpToolToLangchainTool mcpTools+  model <- newOllama "qwen2.5:7b" defaultConfig   -  -- Bind tools to your ReAct or Plan-and-Execute Agent-  let agent = createReActAgent model nativeTools defaultAgentConfig-  res <- runReActAgent agent "Search Hoogle for the signature of 'traverse'"-  print res+  -- Equip agent with file inspection tools+  let policy = defaultSecurityPolicy+      tools  = [readFileTool Nothing policy, findFilesTool policy]+      agent  = defaultReActAgent model tools+      prompt = [userMessage "Inspect /tmp/server_status.txt using read_file and summarize the service health."]++  res <- runExceptT $ runReActAgent agent prompt+  case res of+    Left err  -> T.putStrLn $ errorMessage err+    Right msg -> T.putStrLn $ extractMessageText msg ```-*Run:* `stack run mcpollama` or `stack run mcpopenai` ----+### Output -## Installation+```text+[ReAct Iteration 1]+Thought: I should read the contents of /tmp/server_status.txt to check the current health status.+Action: read_file {"path": "/tmp/server_status.txt"}+Observation: Service: api-gateway, Status: Degraded, Latency: 420ms -### Stack-Add to your `stack.yaml`:-```yaml-extra-deps:-  - langchain-hs-core-0.0.5.0-  - langchain-hs-graph-0.0.5.0-  - langchain-hs-0.0.5.0-```-Then in your `.cabal` or `package.yaml`:-```yaml-dependencies:-  - langchain-hs        # full ecosystem (providers, agents, MCP, vector stores)-  - langchain-hs-core   # pure core only (no HTTP dependencies)-  - langchain-hs-graph  # graph engine only-```+[ReAct Iteration 2]+Thought: The file has been read. The api-gateway service is in a degraded state with elevated latency. I will now summarize this for the user. -### Cabal-```bash-cabal install langchain-hs+Final Answer:+The api-gateway service is currently experiencing degraded health with high latency (420ms). Immediate investigation of the gateway service is recommended. ```  --- -## Development & Quality Commands+## Documentation & Resources -The repository enforces strict code quality and formatting via `make`:+- **Hackage Package:** [https://hackage.haskell.org/package/langchain-hs](https://hackage.haskell.org/package/langchain-hs)+- **Documentation Website:** [https://tusharad.github.io/langchain-hs/](https://tusharad.github.io/langchain-hs/)+- **Whitepaper:** [whitepaper.md](whitepaper.md) (Effect polymorphism, categorical design, state graphs)+- **Examples Directory:** [`examples/`](examples/) +---++## Development & Testing+ ```bash-# Build the entire monorepo and all 41 example executables+# Build project stack build -# Run unit and property-based test suites+# Run test suite stack test -# Run HLint across all source trees (zero hints policy)+# Code quality checks make lint--# Check code formatting with Fourmolu make format-check--# Format all files in-place-make format--# Build the documentation website (Hakyll)-make site-build--# Run live documentation server with auto-reload (port 8000)-make site-watch-```-------## Documentation & Research--| Resource | Description |-|:---|:---|-| **[Hackage Docs](https://hackage.haskell.org/package/langchain-hs)** | Full Haddock API reference for all exported modules |-| **[Whitepaper](whitepaper.md)** | Deep technical dive: category theory foundations, algebraic laws, effect-polymorphic design, and advanced multi-agent patterns |-| **[Documentation Website](site/)** | Hakyll site with 20 component pages, live provider toggle, and instant search (`Cmd+K`) |-| **[Examples](examples/)** | 41 runnable executables covering every component for Ollama and OpenAI |--To build the Haddock API docs locally:-```bash-make docs-# Opens in .stack-work/install/.../doc/index.html ```  ---
− Setup.hs
@@ -1,3 +0,0 @@-import Distribution.Simple--main = defaultMain
langchain-hs.cabal view
@@ -1,13 +1,18 @@-cabal-version: 1.12+cabal-version: 1.22  -- This file has been generated from package.yaml by hpack version 0.39.6. -- -- see: https://github.com/sol/hpack  name:           langchain-hs-version:        0.0.5.0+version:        0.0.6.0 synopsis:       Pure functional LLM agent framework and multi-agent graph engine in Haskell-description:    Build LLM-powered applications, ReAct agents, and LangGraph-style stateful workflows in Haskell with zero unsafePerformIO.+description:    Build LLM-powered applications, ReAct agents, and LangGraph-style stateful workflows+                in Haskell. Provider-specific packages:+                  - langchain-hs-ollama+                  - langchain-hs-openai+                  - langchain-hs-gemini+                  - langchain-hs-mcp category:       AI homepage:       https://github.com/tusharad/langchain-hs#readme bug-reports:    https://github.com/tusharad/langchain-hs/issues@@ -21,7 +26,7 @@     GHC == 9.12.4   , GHC == 9.10.3   , GHC == 9.8.4-extra-source-files:+extra-doc-files:     README.md     CHANGELOG.md @@ -30,96 +35,78 @@   location: https://github.com/tusharad/langchain-hs  library-  exposed-modules:-      Langchain.Agent.PlanAndExecute-      Langchain.Agent.ReAct-      Langchain.Cache.Core-      Langchain.Callback.Manager-      Langchain.Chain.MapReduce-      Langchain.Chain.RetrievalQA-      Langchain.DocumentLoader.Core-      Langchain.DocumentLoader.Csv-      Langchain.DocumentLoader.DirectoryLoader-      Langchain.DocumentLoader.FileLoader-      Langchain.Embeddings.Core-      Langchain.Embeddings.Ollama-      Langchain.Embeddings.OpenAI-      Langchain.Guardrail.Core-      Langchain.MCP.Client-      Langchain.Memory.Core-      Langchain.Memory.Entity-      Langchain.Memory.Summary-      Langchain.Observability-      Langchain.OutputParser.Core-      Langchain.OutputParser.Structured-      Langchain.Prelude-      Langchain.PromptTemplate.Chat-      Langchain.PromptTemplate.Chat.ChatPromptTemplate-      Langchain.PromptTemplate.Chat.MessagesPlaceholder-      Langchain.PromptTemplate.FewShot-      Langchain.PromptTemplate.Prompt-      Langchain.PromptTemplate.String-      Langchain.Provider.Gemini-      Langchain.Provider.Ollama-      Langchain.Provider.OpenAI-      Langchain.Resilience.CircuitBreaker-      Langchain.Resilience.Retry-      Langchain.Retriever.BM25-      Langchain.Retriever.Core-      Langchain.Retriever.Hybrid-      Langchain.TextSplitter.Character-      Langchain.TextSplitter.Code-      Langchain.TextSplitter.Markdown-      Langchain.TextSplitter.RecursiveCharacter-      Langchain.TextSplitter.Token-      Langchain.Tool.Async-      Langchain.Tool.Binding-      Langchain.Tool.Calculator-      Langchain.Tool.Core-      Langchain.Tool.FileSystem-      Langchain.Tool.GenericSchema-      Langchain.Tool.Shell-      Langchain.VectorStore.Core-      Langchain.VectorStore.InMemory-      Langchain.VectorStore.SqliteVec   other-modules:       Paths_langchain_hs-  hs-source-dirs:-      src+  reexported-modules:+      Langchain.Error+    , Langchain.Model+    , Langchain.Model.Types+    , Langchain.Monad+    , Langchain.Stream+    , Langchain.Runnable+    , Langchain.Tool+    , Langchain.Tool.Binding+    , Langchain.Tool.Checkpoint+    , Langchain.Tool.Core+    , Langchain.Tool.FileSystem+    , Langchain.Tool.GenericSchema+    , Langchain.Tool.Process+    , Langchain.Tool.Security+    , Langchain.Tool.Shell+    , Langchain.Embeddings+    , Langchain.Embeddings.Core+    , Langchain.DocumentLoader+    , Langchain.DocumentLoader.Core+    , Langchain.DocumentLoader.DirectoryLoader+    , Langchain.DocumentLoader.FileLoader+    , Langchain.TextSplitter.Character+    , Langchain.TextSplitter.Code+    , Langchain.TextSplitter.Markdown+    , Langchain.TextSplitter.RecursiveCharacter+    , Langchain.TextSplitter.Token+    , Langchain.VectorStore+    , Langchain.VectorStore.Core+    , Langchain.VectorStore.InMemory+    , Langchain.PromptTemplate+    , Langchain.PromptTemplate.Prompt+    , Langchain.PromptTemplate.String+    , Langchain.Observability+    , Langchain.Callback+    , Langchain.Callback.Manager+    , Langchain.Retriever+    , Langchain.Retriever.Core+    , Langchain.Retriever.BM25+    , Langchain.Retriever.Hybrid+    , Langchain.Resilience+    , Langchain.Resilience.CircuitBreaker+    , Langchain.Resilience.Retry+    , Langchain.Memory+    , Langchain.Memory.Core+    , Langchain.Memory.Summary+    , Langchain.Memory.Session+    , Langchain.Memory.Entity+    , Langchain.Memory.Combined+    , Langchain.Cache+    , Langchain.Cache.Core+    , Langchain.OutputParser+    , Langchain.OutputParser.Core+    , Langchain.OutputParser.Structured+    , Langchain.Chain+    , Langchain.Chain.RetrievalQA+    , Langchain.Agent+    , Langchain.Agent.ReAct+    , Langchain.Agent.PlanAndExecute+    , Langchain.Guardrail+    , Langchain.Guardrail.Core+    , Langchain.Prelude+    , Langchain.Graph.StateGraph+    , Langchain.Graph.Checkpointer+    , Langchain.Graph.HITL   ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints   build-depends:-      aeson ==2.*-    , async ==2.2.*-    , base >=4.17 && <5-    , bytestring >=0.10 && <0.13-    , conduit >=1.2 && <1.4-    , containers >=0.6 && <0.9-    , directory >=1.3.6 && <1.4-    , filepath >=1.4 && <2-    , format-heavy ==0.1.*-    , http-client ==0.7.*-    , http-client-tls >=0.3 && <0.5-    , http-conduit ==2.*-    , http-types >=0.11 && <0.13-    , langchain-hs-core ==0.0.5.*-    , langchain-hs-graph ==0.0.5.*-    , mtl >=2.2 && <2.4-    , ollama-haskell >=0.4.0.0 && <0.5-    , openai >=2.2.1 && <3-    , process ==1.6.*-    , random ==1.2.*-    , scientific ==0.3.*-    , servant ==0.20.*-    , servant-client ==0.20.*-    , servant-client-core ==0.20.*-    , servant-conduit ==0.16.*-    , servant-event-stream ==0.4.*-    , sqlite-simple >=0.4.18 && <0.5-    , stm ==2.5.*-    , text >=1.2 && <3-    , time >=1.9 && <1.15-    , transformers >=0.5 && <0.7-    , vector >=0.12 && <0.14+      base >=4.17 && <5+    , langchain-hs-core ==0.0.6.*+    , langchain-hs-graph ==0.0.6.*   default-language: Haskell2010  test-suite langchain-hs-test@@ -129,66 +116,31 @@       Test.Langchain.Agent.AdvancedAgentsSpec       Test.Langchain.Agent.ReAct       Test.Langchain.Cache.CacheSpec-      Test.Langchain.Callback.CallbackManagerSpec-      Test.Langchain.Chain.ChainsSpec       Test.Langchain.Chain.RetrievalQASpec       Test.Langchain.DocumentLoader.Core-      Test.Langchain.DocumentLoader.CsvSpec       Test.Langchain.DocumentLoader.DirectoryLoader       Test.Langchain.Error       Test.Langchain.Graph.CompilationSpec       Test.Langchain.Guardrail.GuardrailSpec-      Test.Langchain.Integration.FullRagE2ESpec-      Test.Langchain.Integration.OllamaChatSpec-      Test.Langchain.Integration.OllamaEmbeddingSpec-      Test.Langchain.Integration.OllamaStreamSpec-      Test.Langchain.Integration.OllamaToolSpec-      Test.Langchain.Integration.ReActAgentE2ESpec-      Test.Langchain.Integration.StateGraphE2ESpec-      Test.Langchain.Integration.StreamingCachingRetryE2ESpec-      Test.Langchain.MCP.McpSpec       Test.Langchain.Memory.Core-      Test.Langchain.Memory.EntitySpec       Test.Langchain.Memory.SummarySpec       Test.Langchain.Memory.TokenBufferMemory-      Test.Langchain.ObservabilitySpec       Test.Langchain.OutputParser.AdvancedParsersSpec       Test.Langchain.OutputParser.Core-      Test.Langchain.PromptTemplate.Chat.ChatPromptTemplateSpec-      Test.Langchain.PromptTemplate.Chat.MessagesPlaceholderSpec-      Test.Langchain.PromptTemplate.FewShotSpec-      Test.Langchain.PromptTemplate.PromptSpec-      Test.Langchain.Property.CheckpointerSpec-      Test.Langchain.Property.ErrorSpec-      Test.Langchain.Property.MessageSpec-      Test.Langchain.Property.PromptTemplateSpec-      Test.Langchain.Property.RunnableSpec-      Test.Langchain.Property.TextSplitterSpec-      Test.Langchain.Provider.FixturesSpec-      Test.Langchain.Provider.Gemini       Test.Langchain.Provider.Mock-      Test.Langchain.Provider.Ollama-      Test.Langchain.Provider.OllamaConversionSpec-      Test.Langchain.Provider.OpenAI-      Test.Langchain.Provider.TestSseServer       Test.Langchain.RegressionSpec-      Test.Langchain.Resilience.CircuitBreakerSpec-      Test.Langchain.Resilience.RetrySpec       Test.Langchain.Retriever.BM25Spec-      Test.Langchain.Retriever.Core-      Test.Langchain.Retriever.HybridSpec-      Test.Langchain.TestHelpers       Test.Langchain.TextSplitter.Character       Test.Langchain.TextSplitter.CodeSpec       Test.Langchain.TextSplitter.MarkdownSpec       Test.Langchain.TextSplitter.RecursiveCharacterSpec       Test.Langchain.TextSplitter.TokenSpec       Test.Langchain.Tool.AdvancedToolsSpec-      Test.Langchain.Tool.Calculator       Test.Langchain.Tool.FileSystem+      Test.Langchain.Tool.ProcessSpec+      Test.Langchain.Tool.SecuritySpec       Test.Langchain.Tool.Shell       Test.Langchain.VectorStore.Core-      Test.Langchain.VectorStore.SqliteVecSpec       Paths_langchain_hs   hs-source-dirs:       test@@ -196,44 +148,24 @@   build-depends:       QuickCheck     , aeson-    , aeson-qq     , async     , base >=4.17 && <5     , bytestring-    , conduit >=1.2 && <1.4+    , conduit     , containers     , directory     , filepath-    , format-heavy ==0.1.*-    , http-client ==0.7.*-    , http-client-tls >=0.3 && <0.5-    , http-conduit-    , http-types     , langchain-hs-    , langchain-hs-core-    , langchain-hs-graph-    , mtl >=2.2 && <2.4-    , ollama-haskell >=0.4.0.0 && <0.5-    , openai >=2.2.1 && <3-    , process ==1.6.*-    , random ==1.2.*-    , resourcet >=1.2 && <1.4-    , scientific ==0.3.*-    , servant ==0.20.*-    , servant-client ==0.20.*-    , servant-client-core ==0.20.*-    , servant-conduit ==0.16.*-    , servant-event-stream ==0.4.*-    , sqlite-simple >=0.4.18 && <0.5-    , stm ==2.5.*+    , langchain-hs-core ==0.0.6.*+    , langchain-hs-graph ==0.0.6.*+    , mtl+    , resourcet+    , stm     , tasty     , tasty-hunit     , tasty-quickcheck     , temporary     , text-    , time >=1.9 && <1.15-    , transformers >=0.5 && <0.7-    , vector >=0.12 && <0.14-    , wai-    , warp+    , time+    , vector   default-language: Haskell2010
− src/Langchain/Agent/PlanAndExecute.hs
@@ -1,198 +0,0 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE IncoherentInstances #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE UndecidableInstances #-}--{- |-Module      : Langchain.Agent.PlanAndExecute-Description : Plan-and-Execute agent architecture using JSON structured output and effectful step executors-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Separates complex multi-step reasoning into a two-phase architecture:-1. Planner LLM generates an explicit sequence of structured steps as typed JSON.-2. Executor (an agent with tools, a function, or a model) executes each step sequentially with accumulated context.--}-module Langchain.Agent.PlanAndExecute-  ( PlanStep (..)-  , Plan (..)-  , StepExecutor (..)-  , PlanAndExecuteAgent (..)-  , newPlanAndExecuteAgent-  , newPlanAndExecuteAgentWithTools-  , runPlanAndExecute-  ) where--import Control.Applicative ((<|>))-import Control.Monad.Except (MonadError, throwError)-import Control.Monad.IO.Class (MonadIO)-import Data.Aeson (FromJSON (..), ToJSON, Value (..), withObject, (.!=), (.:), (.:?))-import Data.Aeson.Types (prependFailure, typeMismatch)-import Data.Text (Text)-import qualified Data.Text as T-import GHC.Generics (Generic)--import Langchain.Agent.ReAct (ReActAgent, createReActAgent, runReActAgent)-import Langchain.Core.Error (LangchainError, agentError)-import Langchain.Core.Model-  ( ChatModel (..)-  , extractMessageText-  , userMessage-  )-import Langchain.Core.Tool (Tool)-import Langchain.OutputParser.Structured (StructuredOutput, TypeSchema, structuredInvoke)-import Langchain.Tool.Binding (ToolBinder (..))---- | Single step in an execution plan-data PlanStep = PlanStep-  { stepNumber :: !Int-  , stepDescription :: !Text-  }-  deriving stock (Show, Eq, Generic)-  deriving anyclass (ToJSON, TypeSchema)--instance FromJSON PlanStep where-  parseJSON = withObject "PlanStep" $ \o -> do-    num <--      o .:? "stepNumber" >>= \case-        Just n -> pure n-        Nothing ->-          o .:? "step" >>= \case-            Just n -> pure n-            Nothing -> o .:? "number" .!= 1-    desc <--      o .:? "stepDescription" >>= \case-        Just d -> pure d-        Nothing ->-          o .:? "description" >>= \case-            Just d -> pure d-            Nothing ->-              o .:? "task" >>= \case-                Just d -> pure d-                Nothing -> o .: "action"-    pure $ PlanStep num desc---- | Collection of steps forming a plan-newtype Plan = Plan-  { planSteps :: [PlanStep]-  }-  deriving stock (Show, Eq, Generic)-  deriving anyclass (ToJSON, StructuredOutput, TypeSchema)--instance FromJSON Plan where-  parseJSON (Object o) = Plan <$> (o .: "planSteps" <|> o .: "steps" <|> o .: "plan")-  parseJSON (Array arr) = Plan <$> parseJSON (Array arr)-  parseJSON invalid = prependFailure "parsing Plan failed, " (typeMismatch "Object or Array" invalid)---- | Abstraction for executing individual steps of a plan (agents, models with tools, or custom runners)-class StepExecutor e m where-  executeStep :: e -> Text -> m Text--instance-  {-# OVERLAPPING #-}-  (m ~ n, ToolBinder model m, MonadIO n, MonadError LangchainError n) =>-  StepExecutor (ReActAgent model m) n-  where-  executeStep agent prompt = do-    msg <- runReActAgent agent [userMessage prompt]-    pure $ extractMessageText msg--instance-  {-# OVERLAPPING #-}-  (m ~ n, ToolBinder model m, MonadIO n, MonadError LangchainError n) =>-  StepExecutor (model, [Tool m]) n-  where-  executeStep (model, tools) prompt = do-    let agent = createReActAgent model tools-    executeStep agent prompt--instance {-# OVERLAPPING #-} (m ~ n) => StepExecutor (Text -> m Text) n where-  executeStep = id--instance {-# OVERLAPPABLE #-} (ChatModel model, MonadIO m, MonadError LangchainError m) => StepExecutor model m where-  executeStep model prompt = do-    msg <- invoke model [userMessage prompt] Nothing-    pure $ extractMessageText msg---- | Plan-and-Execute agent container-data PlanAndExecuteAgent planner executor = PlanAndExecuteAgent-  { plannerModel :: planner-  , stepExecutor :: executor-  , planPromptTemplate :: Maybe Text-  }---- | Construct a new PlanAndExecuteAgent with any StepExecutor (agent, function, or model)-newPlanAndExecuteAgent ::-  planner ->-  executor ->-  Maybe Text ->-  PlanAndExecuteAgent planner executor-newPlanAndExecuteAgent = PlanAndExecuteAgent---- | Construct a PlanAndExecuteAgent with tools using a ReActAgent as the step executor-newPlanAndExecuteAgentWithTools ::-  planner ->-  model ->-  [Tool m] ->-  Maybe Text ->-  PlanAndExecuteAgent planner (ReActAgent model m)-newPlanAndExecuteAgentWithTools planner model tools =-  PlanAndExecuteAgent planner (createReActAgent model tools)---- | Execute a goal using the Plan-and-Execute workflow with structured JSON planning-runPlanAndExecute ::-  (ChatModel planner, StepExecutor executor m, MonadIO m, MonadError LangchainError m) =>-  PlanAndExecuteAgent planner executor ->-  Text ->-  m Text-runPlanAndExecute PlanAndExecuteAgent {..} userGoal = do-  let planPrompt = case planPromptTemplate of-        Just p -> p <> "\nGoal: " <> userGoal-        Nothing ->-          "You are an expert planner. For the following goal, generate a concise step-by-step execution plan.\n"-            <> "Output JSON format: {\"planSteps\": [{\"stepNumber\": 1, \"stepDescription\": \"...\"}]}\n"-            <> "Keep the plan focused and minimal (between 2 to 3 distinct, actionable steps).\n"-            <> "Goal: "-            <> userGoal-  plan <- structuredInvoke plannerModel [userMessage planPrompt]-  if null (planSteps plan)-    then throwError $ agentError "Planner generated an empty plan" (Just "PlanAndExecuteAgent") Nothing-    else executeSteps (planSteps plan) []-  where-    executeSteps [] stepOutputs = do-      let synthesisPrompt =-            "User Goal: "-              <> userGoal-              <> "\n\nStep Execution History:\n"-              <> T.unlines-                [T.pack (show num) <> ". " <> desc <> " -> " <> out | (PlanStep num desc, out) <- stepOutputs]-              <> "\n\nProvide the final synthesized answer satisfying the goal:"-      executeStep stepExecutor synthesisPrompt-    executeSteps (currStep : restSteps) prevOutputs = do-      let stepPrompt =-            "User Goal: "-              <> userGoal-              <> ( if null prevOutputs-                     then ""-                     else-                       "\n\nCompleted Steps So Far:\n"-                         <> T.unlines-                           [T.pack (show num) <> ". " <> desc <> " -> " <> out | (PlanStep num desc, out) <- prevOutputs]-                 )-              <> "\n\nCurrent Task To Execute (Step "-              <> T.pack (show (stepNumber currStep))-              <> "): "-              <> stepDescription currStep-              <> "\nExecute this task using any appropriate tools available and provide the outcome:"-      stepOut <- executeStep stepExecutor stepPrompt-      executeSteps restSteps (prevOutputs ++ [(currStep, stepOut)])
− src/Langchain/Agent/ReAct.hs
@@ -1,107 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications #-}--{- |-Module      : Langchain.Agent.ReAct-Description : Effect-polymorphic ReAct (Reasoning + Acting) Agent engine-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Modernized ReAct agent operating over ChatModel, Tool m, and multi-modal Message history.-Uses 'ToolBinder' to pass tool definitions to the LLM provider in a provider-agnostic way.--}-module Langchain.Agent.ReAct-  ( AgentStep (..)-  , ReActAgent (..)-  , createReActAgent-  , reactStep-  , runReActAgent-  ) where--import Control.Monad (forM)-import Control.Monad.Except (MonadError, throwError)-import Control.Monad.IO.Class (MonadIO)-import Data.List (find)--import Langchain.Core.Error (LangchainError, agentError, errorMessage)-import Langchain.Core.Model-import qualified Langchain.Core.Model.Types as M-import Langchain.Core.Tool-import Langchain.Tool.Binding (ToolBinder (..))---- | Step result of ReAct reasoning iteration-data AgentStep-  = AgentAction Message [ToolCall]-  | AgentFinish Message-  deriving (Eq, Show)---- | Effect-polymorphic ReAct Agent configuration-data ReActAgent model m = ReActAgent-  { agentModel :: model-  , agentTools :: [Tool m]-  , agentMaxIterations :: Int-  }---- | Construct a ReAct Agent instance-createReActAgent :: model -> [Tool m] -> ReActAgent model m-createReActAgent model tools =-  ReActAgent-    { agentModel = model-    , agentTools = tools-    , agentMaxIterations = 10-    }---- | Run a single step of ReAct reasoning using ChatModel-reactStep ::-  forall model m.-  (ToolBinder model m, MonadIO m, MonadError LangchainError m) =>-  model ->-  [Tool m] ->-  [Message] ->-  m AgentStep-reactStep model tools history = do-  let cfg = bindToolsConfig @model tools Nothing-  responseMsg <- invoke model history cfg-  case messageToolCalls responseMsg of-    Just tcs@(_ : _) -> pure $ AgentAction responseMsg tcs-    _ -> pure $ AgentFinish responseMsg---- | Execute the full ReAct reasoning loop until AgentFinish or max iterations reached-runReActAgent ::-  (ToolBinder model m, MonadIO m, MonadError LangchainError m) =>-  ReActAgent model m ->-  [Message] ->-  m Message-runReActAgent agent initialHistory = go initialHistory (agentMaxIterations agent)-  where-    go history maxIter-      | maxIter <= 0 = throwError $ agentError "ReAct Agent exceeded maximum iterations" Nothing Nothing-      | otherwise = do-          step <- reactStep (agentModel agent) (agentTools agent) history-          case step of-            AgentFinish finalMsg -> pure finalMsg-            AgentAction respMsg tcs -> do-              obsMsgs <- forM tcs $ \tc -> do-                let tName = toolCallName tc-                outTxt <- case find (\t -> toolName t == tName) (agentTools agent) of-                  Nothing ->-                    pure $ "Error: Tool not found: " <> tName-                  Just tool -> do-                    eOut <- toolExecute tool (toolCallArguments tc)-                    case eOut of-                      Left err ->-                        pure $ "Error executing tool " <> tName <> ": " <> errorMessage err-                      Right out ->-                        pure out-                pure $-                  (textMessage M.Tool outTxt)-                    { M.messageName = Just tName-                    , M.messageToolId = Just (toolCallId tc)-                    }-              let newHistory = history ++ [respMsg] ++ obsMsgs-              go newHistory (maxIter - 1)
− src/Langchain/Cache/Core.hs
@@ -1,255 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE UndecidableInstances #-}--{- |-Module      : Langchain.Cache.Core-Description : LLM response caching layer with in-memory and SQLite backends-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Transparent caching for ChatModel invocations to reduce latency, cost, and API usage.--}-module Langchain.Cache.Core-  ( CacheBackend (..)-  , InMemoryCache (..)-  , newInMemoryCache-  , SQLiteCache (..)-  , newSQLiteCache-  , CachedModel (..)-  , CacheableChatModel (..)-  , withCaching-  , computeCacheKey-  )-where--import Control.Concurrent.STM-import Control.Exception (SomeException, try)-import Control.Monad.IO.Class (MonadIO, liftIO)-import Data.Aeson (ToJSON, Value, decode, encode, object, (.=))-import qualified Data.ByteString.Lazy as LBS-import Data.Map.Strict (Map)-import qualified Data.Map.Strict as Map-import Data.Maybe (fromMaybe)-import Data.Text (Text)-import qualified Data.Text as TS-import qualified Data.Text.Encoding as TE-import Database.SQLite.Simple-import Langchain.Core.Model-  ( ChatModel (..)-  , Message (..)-  )-import Langchain.Provider.Gemini (Gemini (..))-import Langchain.Provider.Ollama (ModelName (..), Ollama (..))-import Langchain.Provider.OpenAI (OpenAI (..))-import Langchain.Tool.Binding (ToolBinder (..))-import qualified Ollama.API.Chat as OllamaChat-import Ollama.Client (OllamaClient (..))-import Ollama.Client.Config (OllamaClientConfig (..))---- | Effect-polymorphic cache backend typeclass-class CacheBackend cb where-  getCache :: (MonadIO m) => cb -> Text -> m (Maybe Message)-  putCache :: (MonadIO m) => cb -> Text -> Message -> m ()-  clearCache :: (MonadIO m) => cb -> m ()---- | Thread-safe in-memory cache backed by STM TVar-newtype InMemoryCache = InMemoryCache-  { memCacheVar :: TVar (Map Text Message)-  }---- | Construct a new InMemoryCache-newInMemoryCache :: (MonadIO m) => m InMemoryCache-newInMemoryCache = liftIO $ do-  var <- newTVarIO Map.empty-  pure $ InMemoryCache var--instance CacheBackend InMemoryCache where-  getCache InMemoryCache {..} key = liftIO $ do-    m <- readTVarIO memCacheVar-    pure $ Map.lookup key m--  putCache InMemoryCache {..} key msg = liftIO $ do-    atomically $ modifyTVar' memCacheVar (Map.insert key msg)--  clearCache InMemoryCache {..} = liftIO $ do-    atomically $ writeTVar memCacheVar Map.empty---- | Persistent SQLite cache backend-newtype SQLiteCache = SQLiteCache-  { sqliteCacheDbPath :: FilePath-  }---- | Construct a new SQLiteCache and create cache table-newSQLiteCache :: (MonadIO m) => FilePath -> m SQLiteCache-newSQLiteCache dbPath = liftIO $ do-  _ <--    ( try $ withConnection dbPath $ \conn -> do-        execute_-          conn-          "CREATE TABLE IF NOT EXISTS langchain_cache (\-          \ cache_key TEXT PRIMARY KEY,\-          \ response_json TEXT NOT NULL,\-          \ created_at DATETIME DEFAULT CURRENT_TIMESTAMP\-          \);"-    ) ::-      IO (Either SomeException ())-  pure $ SQLiteCache dbPath--instance CacheBackend SQLiteCache where-  getCache SQLiteCache {..} key = liftIO $ do-    rowsRes <--      ( try $ withConnection sqliteCacheDbPath $ \conn -> do-          query conn "SELECT response_json FROM langchain_cache WHERE cache_key = ?" (Only (TS.unpack key)) ::-            IO [Only String]-      ) ::-        IO (Either SomeException [Only String])-    case rowsRes of-      Right [Only jsonStr] ->-        let bs = LBS.fromStrict (TE.encodeUtf8 (TS.pack jsonStr))-         in pure (decode bs)-      _ -> pure Nothing--  putCache SQLiteCache {..} key msg = liftIO $ do-    let jsonStr = TS.unpack $ TE.decodeUtf8 $ LBS.toStrict (encode msg)-    _ <--      ( try $ withConnection sqliteCacheDbPath $ \conn -> do-          execute-            conn-            "INSERT OR REPLACE INTO langchain_cache (cache_key, response_json) VALUES (?, ?)"-            (TS.unpack key, jsonStr)-      ) ::-        IO (Either SomeException ())-    pure ()--  clearCache SQLiteCache {..} = liftIO $ do-    _ <--      ( try $ withConnection sqliteCacheDbPath $ \conn -> do-          execute_ conn "DELETE FROM langchain_cache;"-      ) ::-        IO (Either SomeException ())-    pure ()---- | ChatModel wrapper that provides transparent response caching-data CachedModel model cache = CachedModel-  { underlyingModel :: model-  , modelCache :: cache-  }--{- | Wrap a cacheable chat model with a cache backend.--The wrapped model uses the provider-specific identity supplied by-'CacheableChatModel' when looking up responses.--}-withCaching :: model -> cache -> CachedModel model cache-withCaching = CachedModel---- | Encode a value as JSON text suitable for a cache key.-toJsonText :: (ToJSON a) => a -> Text-toJsonText = TE.decodeUtf8 . LBS.toStrict . encode--{- | Provider-specific data that distinguishes cacheable model invocations.--Implementations should include every model property and effective invocation-parameter that can affect a response, but must not include credentials.--}-class (ChatModel model) => CacheableChatModel model where-  -- | Return the JSON identity used to distinguish this model's cache entries.-  cacheModelIdentity :: model -> Maybe (ModelConfig model) -> Value--instance CacheableChatModel OpenAI where-  cacheModelIdentity OpenAI {..} _ =-    object-      [ "provider" .= ("openai" :: Text)-      , "model" .= model-      , "baseUrl" .= baseUrl-      , "temperature" .= temperature-      ]--instance CacheableChatModel Ollama where-  cacheModelIdentity o cfg =-    let effectiveOptions = cfg >>= OllamaChat.chatOptions-        effectiveKeepAlive = cfg >>= OllamaChat.chatKeepAlive-        effectiveModel = case cfg of-          Just r ->-            let m = unModelName (OllamaChat.chatModel r)-             in if TS.null m then ollamaModelName o else m-          Nothing -> ollamaModelName o-     in object-          [ "provider" .= ("ollama" :: Text)-          , "baseUrl" .= configBaseUrl (clientConfig (client o))-          , "model" .= effectiveModel-          , "config"-              .= object-                [ "tools" .= (OllamaChat.chatTools <$> cfg)-                , "format" .= (OllamaChat.chatFormat <$> cfg)-                , "options" .= effectiveOptions-                , "keep_alive" .= effectiveKeepAlive-                , "think" .= (OllamaChat.chatThink <$> cfg)-                ]-          ]--instance CacheableChatModel Gemini where-  cacheModelIdentity (Gemini _ modelName baseUrl) config-    | effectiveBaseUrl == defaultGeminiBaseUrl = defaultIdentity-    | otherwise =-        object $-          [ "provider" .= ("gemini" :: Text)-          , "model" .= modelName-          , "baseUrl" .= effectiveBaseUrl-          ]-            <> maybe [] (pure . ("config" .=)) config-    where-      effectiveBaseUrl = TS.dropWhileEnd (== '/') $ fromMaybe "" baseUrl-      defaultGeminiBaseUrl = "https://generativelanguage.googleapis.com"-      defaultIdentity =-        object $-          [ "provider" .= ("gemini" :: Text)-          , "model" .= modelName-          ]-            <> maybe [] (pure . ("config" .=)) config--{- | Compute a canonical cache key from a model identity and complete input messages.--The key includes all fields of each 'Message', so multi-modal content and-tool calls cannot collide with text-only requests.--}-computeCacheKey ::-  (CacheableChatModel model) => model -> Maybe (ModelConfig model) -> [Message] -> Text-computeCacheKey model cfg msgs =-  toJsonText $-    object-      [ "model" .= cacheModelIdentity model cfg-      , "messages" .= msgs-      ]--instance (CacheableChatModel model, CacheBackend cache) => ChatModel (CachedModel model cache) where-  type ModelConfig (CachedModel model cache) = ModelConfig model--  invoke CachedModel {..} msgs mbCfg = do-    let key = computeCacheKey underlyingModel mbCfg msgs-    mbCached <- getCache modelCache key-    case mbCached of-      Just cachedMsg -> pure cachedMsg-      Nothing -> do-        freshMsg <- invoke underlyingModel msgs mbCfg-        putCache modelCache key freshMsg-        pure freshMsg--  stream CachedModel {..} =-    stream underlyingModel---- | Delegate tool binding to the underlying model-instance-  (CacheableChatModel model, CacheBackend cache, ToolBinder model m) =>-  ToolBinder (CachedModel model cache) m-  where-  bindToolsConfig = bindToolsConfig @model
− src/Langchain/Callback/Manager.hs
@@ -1,100 +0,0 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE RecordWildCards #-}--{- |-Module      : Langchain.Callback.Manager-Description : Typed event-driven callback system with synchronous and asynchronous dispatch-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Provides typed callback lifecycle events across models, tools, chains, and state graphs,-with support for filtering and non-blocking asynchronous event dispatch.--}-module Langchain.Callback.Manager-  ( CallbackEvent (..)-  , CallbackHandler (..)-  , CallbackManager (..)-  , newCallbackManager-  , registerHandler-  , dispatchEvent-  , dispatchEventAsync-  , newLoggingCallbackHandler-  , getCallbackLogs-  ) where--import Control.Concurrent.Async (async)-import Control.Concurrent.STM-import Control.Monad.IO.Class (MonadIO, liftIO)-import Data.Aeson (FromJSON, ToJSON, Value)-import Data.Text (Text)-import qualified Data.Text as T-import Data.Time.Clock (UTCTime)-import GHC.Generics (Generic)---- | Comprehensive lifecycle events emitted across Langchain components-data CallbackEvent-  = OnLLMStart !Text ![Text] !UTCTime -- Model name, Prompt inputs, Timestamp-  | OnLLMEnd !Text !Text !Int !UTCTime -- Model name, Output text, Latency micros, Timestamp-  | OnToolStart !Text !Value !UTCTime -- Tool name, Arguments, Timestamp-  | OnToolEnd !Text !Text !Int !UTCTime -- Tool name, Output text, Latency micros, Timestamp-  | OnRetrieverStart !Text !Text !UTCTime -- Retriever name, Query, Timestamp-  | OnRetrieverEnd !Text ![Text] !Int !UTCTime -- Retriever name, Retrieved snippets, Latency micros, Timestamp-  | OnChainStart !Text !Text !UTCTime -- Chain name, Input, Timestamp-  | OnChainEnd !Text !Text !Int !UTCTime -- Chain name, Output, Latency micros, Timestamp-  | OnGraphNodeStart !Text !Text !UTCTime -- NodeId, State summary, Timestamp-  | OnGraphNodeEnd !Text !Text !Int !UTCTime -- NodeId, Next node/state summary, Latency micros, Timestamp-  | OnError !Text !Text !UTCTime -- Component name, Error message, Timestamp-  deriving (Show, Eq, Generic, ToJSON, FromJSON)---- | Handler for processing emitted callback events-data CallbackHandler = CallbackHandler-  { handlerName :: !Text-  , handleEvent :: CallbackEvent -> IO ()-  }---- | Thread-safe CallbackManager backed by STM TVar-newtype CallbackManager = CallbackManager-  { handlersVar :: TVar [CallbackHandler]-  }---- | Construct an empty CallbackManager-newCallbackManager :: MonadIO m => m CallbackManager-newCallbackManager = liftIO $ do-  var <- newTVarIO []-  pure $ CallbackManager var---- | Register a new callback handler-registerHandler :: MonadIO m => CallbackManager -> CallbackHandler -> m ()-registerHandler CallbackManager {..} handler = liftIO $ do-  atomically $ modifyTVar' handlersVar (\handlers -> handlers ++ [handler])---- | Dispatch an event synchronously to all registered handlers-dispatchEvent :: MonadIO m => CallbackManager -> CallbackEvent -> m ()-dispatchEvent CallbackManager {..} event = liftIO $ do-  handlers <- readTVarIO handlersVar-  mapM_ (`handleEvent` event) handlers---- | Dispatch an event asynchronously in background threads without blocking-dispatchEventAsync :: MonadIO m => CallbackManager -> CallbackEvent -> m ()-dispatchEventAsync CallbackManager {..} event = liftIO $ do-  handlers <- readTVarIO handlersVar-  mapM_ (\h -> async (handleEvent h event)) handlers---- | Create a simple callback handler that logs event descriptions into an STM TVar-newLoggingCallbackHandler :: MonadIO m => Text -> m (CallbackHandler, TVar [Text])-newLoggingCallbackHandler name = liftIO $ do-  logsVar <- newTVarIO []-  let handler =-        CallbackHandler-          { handlerName = name-          , handleEvent = \event -> atomically $ modifyTVar' logsVar (\logs -> logs ++ [T.pack (show event)])-          }-  pure (handler, logsVar)---- | Read all logs accumulated by a logging callback handler-getCallbackLogs :: MonadIO m => TVar [Text] -> m [Text]-getCallbackLogs = liftIO . readTVarIO
− src/Langchain/Chain/MapReduce.hs
@@ -1,99 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}--{- |-Module      : Langchain.Chain.MapReduce-Description : Map-Reduce document summarization and synthesis chain-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Applies a map LLM prompt individually over each document, then combines and synthesizes results-using a reduce LLM prompt.--}-module Langchain.Chain.MapReduce-  ( MapReduceChain (..)-  , newMapReduceChain-  , defaultMapPrompt-  , defaultReducePrompt-  , runMapReduceChain-  ) where--import Control.Monad (forM)-import Control.Monad.Except (MonadError, throwError)-import Control.Monad.IO.Class (MonadIO)-import Data.Map.Strict (Map)-import qualified Data.Map.Strict as Map-import Data.Text (Text)-import qualified Data.Text as T-import qualified Data.Text.Lazy as TL--import Langchain.Core.Error (LangchainError)-import Langchain.Core.Model-  ( ChatModel (..)-  , Message-  , extractMessageText-  , userMessage-  )-import Langchain.DocumentLoader.Core (Document (..))-import Langchain.PromptTemplate.Prompt (PromptTemplate, fromTemplate, renderPrompt)---- | Map-Reduce chain configuration-data MapReduceChain model = MapReduceChain-  { mapReduceModel :: model-  , mapPromptTemplate :: PromptTemplate-  , reducePromptTemplate :: PromptTemplate-  , mapDocVar :: Text-  , reduceDocVar :: Text-  }---- | Default map prompt for individual document summarization-defaultMapPrompt :: PromptTemplate-defaultMapPrompt =-  fromTemplate-    "Summarize the key information in the following document concisely:\n\n{document}\n\nSummary:"---- | Default reduce prompt for synthesizing all document summaries-defaultReducePrompt :: PromptTemplate-defaultReducePrompt =-  fromTemplate-    "Combine and synthesize the following summaries into a comprehensive final response:\n\n{summaries}\n\nFinal Synthesis:"---- | Construct a new MapReduceChain-newMapReduceChain :: model -> MapReduceChain model-newMapReduceChain m =-  MapReduceChain-    { mapReduceModel = m-    , mapPromptTemplate = defaultMapPrompt-    , reducePromptTemplate = defaultReducePrompt-    , mapDocVar = "document"-    , reduceDocVar = "summaries"-    }---- | Execute MapReduceChain across documents-runMapReduceChain ::-  (ChatModel model, MonadIO m, MonadError LangchainError m) =>-  MapReduceChain model ->-  [Document] ->-  Map Text Text ->-  m Message-runMapReduceChain MapReduceChain {..} docs baseVars = do-  -- Phase 1: Map over each document-  summaries <- forM docs $ \doc -> do-    let docTxt = TL.toStrict (pageContent doc)-        vars = Map.insert mapDocVar docTxt baseVars-    rendered <- case renderPrompt mapPromptTemplate vars of-      Left err -> throwError err-      Right p -> pure p-    resp <- invoke mapReduceModel [userMessage rendered] Nothing-    pure $ extractMessageText resp--  -- Phase 2: Reduce summaries into final synthesis-  let combinedSummaries = T.intercalate "\n\n---\n\n" summaries-      reduceVars = Map.insert reduceDocVar combinedSummaries baseVars-  renderedReduce <- case renderPrompt reducePromptTemplate reduceVars of-    Left err -> throwError err-    Right p -> pure p-  invoke mapReduceModel [userMessage renderedReduce] Nothing
− src/Langchain/Chain/RetrievalQA.hs
@@ -1,75 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}--{- |-Module      : Langchain.Chain.RetrievalQA-Description : Effect-polymorphic RetrievalQA chain-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--RetrievalQA chain combining retriever search, context assembly, prompt rendering,-and ChatModel question answering.--}-module Langchain.Chain.RetrievalQA-  ( RetrievalQA (..)-  , newRetrievalQA-  , defaultQAPrompt-  , runRetrievalQA-  ) where--import Control.Monad.Except (MonadError, throwError)-import Control.Monad.IO.Class (MonadIO)-import Data.Map.Strict (fromList)-import Data.Text (Text)-import qualified Data.Text as T-import qualified Data.Text.Lazy as TL--import Langchain.Core.Error (LangchainError)-import Langchain.Core.Model-  ( ChatModel (..)-  , Message-  , systemMessage-  , userMessage-  )-import Langchain.DocumentLoader.Core (Document (..))-import Langchain.PromptTemplate.Prompt (PromptTemplate, fromTemplate, renderPrompt)-import Langchain.Retriever.Core (Retriever (..))---- | QA Chain configuration combining retrieval and LLM response generation.-data RetrievalQA model retriever = RetrievalQA-  { model :: model-  , retriever :: retriever-  , prompt :: PromptTemplate-  }---- | Construct a new RetrievalQA chain with default prompt-newRetrievalQA :: model -> retriever -> RetrievalQA model retriever-newRetrievalQA m r = RetrievalQA m r defaultQAPrompt---- | Default QA prompt template-defaultQAPrompt :: PromptTemplate-defaultQAPrompt =-  fromTemplate-    ( "Use the following pieces of context to answer the question at the end.\n"-        <> "If you don't know the answer, just say that you don't know, don't try to make up an answer.\n\n"-        <> "Context:\n{context}"-    )---- | Execute RetrievalQA chain on a user question-runRetrievalQA ::-  (ChatModel model, Retriever retriever, MonadIO m, MonadError LangchainError m) =>-  RetrievalQA model retriever ->-  Text ->-  m Message-runRetrievalQA RetrievalQA {..} question = do-  docs <- getRelevantDocuments retriever question-  let contextText = T.intercalate "\n\n" $ map (TL.toStrict . pageContent) docs-      vars = fromList [("context", contextText)]-  renderedPrompt <- case renderPrompt prompt vars of-    Left err -> throwError err-    Right p -> pure p-  let conversation = [systemMessage renderedPrompt, userMessage question]-  invoke model conversation Nothing
− src/Langchain/DocumentLoader/Core.hs
@@ -1,57 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}--{- |-Module      : Langchain.DocumentLoader.Core-Description : Core document loading functionality for LangChain Haskell-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Implementation of LangChain's document loading abstraction.--}-module Langchain.DocumentLoader.Core-  ( Document (..)-  , BaseLoader (..)-  ) where--import Control.Monad.Except (MonadError)-import Control.Monad.IO.Class (MonadIO)-import Data.Aeson (Value)-import Data.Map (Map, empty)-import qualified Data.Text as TS-import Data.Text.Lazy (Text)--import Langchain.Core.Error (LangchainError)---- | Document container with content and metadata-data Document = Document-  { pageContent :: Text-  -- ^ The text content of the document-  , metadata :: Map TS.Text Value-  -- ^ Additional metadata (e.g., source, page number)-  }-  deriving (Show, Eq)--instance Semigroup Document where-  doc1 <> doc2 =-    Document-      (pageContent doc1 <> pageContent doc2)-      (metadata doc1 <> metadata doc2)--instance Monoid Document where-  mempty = Document mempty empty---- | Effect-polymorphic BaseLoader typeclass-class BaseLoader loader where-  -- | Load all documents from the source-  load ::-    (MonadIO m, MonadError LangchainError m) =>-    loader ->-    m [Document]--  -- | Load all documents and split their content-  loadAndSplit ::-    (MonadIO m, MonadError LangchainError m) =>-    loader ->-    m [Text]
− src/Langchain/DocumentLoader/Csv.hs
@@ -1,127 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedStrings #-}--{- |-Module      : Langchain.DocumentLoader.Csv-Description : CSV file document loader-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Loads CSV files as LangChain Documents where each row produces a Document.--}-module Langchain.DocumentLoader.Csv-  ( CsvLoader (..)-  , defaultCsvLoader-  , parseCsvRows-  ) where--import Control.Exception (try)-import Control.Monad.Except (throwError)-import Control.Monad.IO.Class (liftIO)-import Data.Aeson (Value (..))-import qualified Data.Map.Strict as Map-import qualified Data.Text as TS-import Data.Text.Lazy (Text)-import qualified Data.Text.Lazy as TL-import qualified Data.Text.Lazy.IO as TLIO--import Langchain.Core.Error (documentLoaderError)-import Langchain.DocumentLoader.Core (BaseLoader (..), Document (..))-import Langchain.TextSplitter.Character (defaultCharacterSplitterOps, splitText)---- | Configuration options for CSV loader-data CsvLoader = CsvLoader-  { csvFilePath :: FilePath-  , csvDelimiter :: Char-  , csvContentColumns :: Maybe [TS.Text]-  -- ^ Optional list of columns to include in pageContent. If Nothing, all columns are concatenated.-  , csvSplitter :: Maybe (Text -> [Text])-  }---- | Default CSV loader configuration-defaultCsvLoader :: FilePath -> CsvLoader-defaultCsvLoader path =-  CsvLoader-    { csvFilePath = path-    , csvDelimiter = ','-    , csvContentColumns = Nothing-    , csvSplitter = Nothing-    }--instance BaseLoader CsvLoader where-  load loader = do-    contentRes <- liftIO $ try $ TLIO.readFile (csvFilePath loader)-    content <- case contentRes of-      Left err ->-        throwError $-          documentLoaderError-            (TS.pack $ "Failed to read CSV file: " ++ show (err :: IOError))-            (Just "CsvLoader")-            Nothing-      Right c -> pure c--    let rows = parseCsvRows (csvDelimiter loader) content-    case rows of-      [] -> pure []-      (headers : dataRows) -> do-        let headerTexts = map (TS.pack . TL.unpack . TL.strip) headers-            docs =-              [ makeDocument headerTexts (map TL.strip row) (csvContentColumns loader) (csvFilePath loader) idx-              | (idx, row) <- zip [1 ..] dataRows-              , not (null row) && not (all TL.null row)-              ]-        pure docs--  loadAndSplit loader = do-    docs <- load loader-    let splitter = case csvSplitter loader of-          Just s -> s-          Nothing -> splitText defaultCharacterSplitterOps-    pure $ concatMap (splitter . pageContent) docs--makeDocument :: [TS.Text] -> [Text] -> Maybe [TS.Text] -> FilePath -> Int -> Document-makeDocument headers values mbSelectedCols filePath rowIdx =-  let pairs = zip headers values-      metaMap =-        Map.fromList-          [ (h, String (TS.pack $ TL.unpack val))-          | (h, val) <- pairs-          ]-      metaWithSource =-        Map.insert "source" (String $ TS.pack filePath) $-          Map.insert "row" (Number $ fromIntegral rowIdx) metaMap-      contentLines = case mbSelectedCols of-        Just selected ->-          [ h <> ": " <> TS.pack (TL.unpack val)-          | (h, val) <- pairs-          , h `elem` selected-          ]-        Nothing ->-          [ h <> ": " <> TS.pack (TL.unpack val)-          | (h, val) <- pairs-          ]-      content = TL.pack $ TS.unpack $ TS.intercalate "\n" contentLines-   in Document content metaWithSource---- | Robust CSV line parser supporting quoted cells with commas-parseCsvRows :: Char -> Text -> [[Text]]-parseCsvRows delim text =-  let allLines = TL.lines text-   in map (parseCsvLine delim) allLines--parseCsvLine :: Char -> Text -> [Text]-parseCsvLine delim line = go False [] "" (TL.unpack line)-  where-    go :: Bool -> [Text] -> String -> String -> [Text]-    go _ acc cur [] = reverse (TL.pack (reverse cur) : acc)-    go inQuote acc cur ('"' : cs) =-      case cs of-        ('"' : rest) -> go inQuote acc ('"' : cur) rest-        _ -> go (not inQuote) acc cur cs-    go inQuote acc cur (c : cs)-      | c == delim && not inQuote =-          go inQuote (TL.pack (reverse cur) : acc) "" cs-      | otherwise =-          go inQuote acc (c : cur) cs
− src/Langchain/DocumentLoader/DirectoryLoader.hs
@@ -1,125 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}--{- |-Module      : Langchain.DocumentLoader.DirectoryLoader-Description : Directory loading implementation for LangChain Haskell-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--DirectoryLoader document loader reads files from disk into Documents.--}-module Langchain.DocumentLoader.DirectoryLoader-  ( DirectoryLoader (..)-  , DirectoryLoaderOptions (..)-  , defaultDirectoryLoaderOptions-  ) where--import Control.Concurrent.Async (mapConcurrently)-import Control.Monad (filterM, forM)-import Control.Monad.Except (throwError)-import Control.Monad.IO.Class (liftIO)-import Data.Maybe (listToMaybe)-import qualified Data.Text as T-import System.Directory (doesDirectoryExist, doesFileExist, listDirectory)-import System.FilePath (takeExtension, takeFileName, (</>))--import Langchain.Core.Error (documentLoaderError)-import Langchain.DocumentLoader.Core-import Langchain.DocumentLoader.FileLoader (FileLoader (FileLoader))-import Langchain.TextSplitter.Character---- | Options for directory loading behavior-data DirectoryLoaderOptions = DirectoryLoaderOptions-  { recursiveDepth :: Maybe Int-  -- ^ Nothing = unlimited depth, Just 0 = No recursive, Just 3 = 3 level deep-  , extensions :: [String]-  -- ^ File extensions to include (e.g., [".txt", ".md"])-  , excludeHidden :: Bool-  -- ^ Whether to exclude hidden files (starting with '.')-  , useMultithreading :: Bool-  -- ^ Whether to use multithreading when loading files-  }-  deriving (Eq, Show)---- | Default directory loader options-defaultDirectoryLoaderOptions :: DirectoryLoaderOptions-defaultDirectoryLoaderOptions =-  DirectoryLoaderOptions-    { recursiveDepth = Nothing-    , extensions = []-    , excludeHidden = True-    , useMultithreading = False-    }---- | Directory loader configuration-data DirectoryLoader = DirectoryLoader-  { dirPath :: FilePath-  , directoryLoaderOptions :: DirectoryLoaderOptions-  }-  deriving (Eq, Show)---- | Helper to check if a file should be included based on options-shouldIncludeFile :: DirectoryLoaderOptions -> FilePath -> Bool-shouldIncludeFile opts path =-  let ext = takeExtension path-      fName = takeFileName path-      isHidden = listToMaybe fName == Just '.'-      matchesExt = null (extensions opts) || ext `elem` extensions opts-      passesHiddenCheck = not (excludeHidden opts) || not isHidden-   in matchesExt && passesHiddenCheck---- | Get all files in a directory, with controlled recursion-getFilesInDirectory :: DirectoryLoaderOptions -> Int -> FilePath -> IO [FilePath]-getFilesInDirectory opts currentDepth dir = do-  let canRecurse = case recursiveDepth opts of-        Nothing -> True-        Just maxD -> currentDepth < maxD--  entries <- listDirectory dir-  let fullPaths = map (dir </>) entries--  files <- filterM doesFileExist fullPaths-  let filteredFiles = filter (shouldIncludeFile opts) files--  subFiles <--    if canRecurse-      then do-        subdirs <- filterM doesDirectoryExist fullPaths-        let visibleSubdirs =-              if excludeHidden opts-                then filter (\d -> not (null d) && listToMaybe d /= Just '.') subdirs-                else subdirs--        if useMultithreading opts && not (null visibleSubdirs)-          then-            concat-              <$> mapConcurrently-                (getFilesInDirectory opts (currentDepth + 1))-                visibleSubdirs-          else concat <$> mapM (getFilesInDirectory opts (currentDepth + 1)) visibleSubdirs-      else pure []--  pure $ filteredFiles ++ subFiles--instance BaseLoader DirectoryLoader where-  load DirectoryLoader {..} = do-    exists <- liftIO $ doesDirectoryExist dirPath-    if exists-      then do-        filePaths <- liftIO $ getFilesInDirectory directoryLoaderOptions 0 dirPath-        fmap concat $ forM filePaths $ \path ->-          load (FileLoader path)-      else-        throwError $-          documentLoaderError-            (T.pack $ "Directory does not exist: " ++ dirPath)-            (Just "DirectoryLoader")-            (Just $ T.pack dirPath)--  loadAndSplit dirLoader = do-    documents <- load dirLoader-    pure $ splitText defaultCharacterSplitterOps (pageContent $ mconcat documents)
− src/Langchain/DocumentLoader/FileLoader.hs
@@ -1,76 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedStrings #-}--{- |-Module      : Langchain.DocumentLoader.FileLoader-Description : File loading implementation for LangChain Haskell-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--File-based document loader implementation following LangChain's document loading patterns.--}-module Langchain.DocumentLoader.FileLoader-  ( FileLoader (..)-  ) where--import Control.Exception (SomeException, try)-import Control.Monad.Except (throwError)-import Control.Monad.IO.Class (liftIO)-import Data.Aeson-import Data.Map (fromList)-import qualified Data.Text as T-import qualified Data.Text.Lazy as TL-import System.Directory (doesFileExist)--import Langchain.Core.Error (documentLoaderError)-import Langchain.DocumentLoader.Core-import Langchain.TextSplitter.Character---- | File loader configuration-newtype FileLoader = FileLoader FilePath-  deriving (Eq, Show)--instance BaseLoader FileLoader where-  load (FileLoader path) = do-    exists <- liftIO $ doesFileExist path-    if exists-      then do-        eContent <- liftIO $ try (readFile path)-        case eContent of-          Left err ->-            throwError $-              documentLoaderError-                (T.pack $ "Error reading file " ++ path ++ ": " ++ show (err :: SomeException))-                (Just "FileLoader")-                (Just $ T.pack path)-          Right content -> do-            let meta = fromList [("source", String $ T.pack path)]-            pure [Document (TL.pack content) meta]-      else-        throwError $-          documentLoaderError-            (T.pack $ "File not found: " ++ path)-            (Just "FileLoader")-            (Just $ T.pack path)--  loadAndSplit (FileLoader path) = do-    exists <- liftIO $ doesFileExist path-    if exists-      then do-        eContent <- liftIO $ try (readFile path)-        case eContent of-          Left err ->-            throwError $-              documentLoaderError-                (T.pack $ "Error reading file " ++ path ++ ": " ++ show (err :: SomeException))-                (Just "FileLoader")-                (Just $ T.pack path)-          Right content -> pure $ splitText defaultCharacterSplitterOps (TL.pack content)-      else-        throwError $-          documentLoaderError-            (T.pack $ "File not found: " ++ path)-            (Just "FileLoader")-            (Just $ T.pack path)
− src/Langchain/Embeddings/Core.hs
@@ -1,38 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}--{- |-Module      : Langchain.Embeddings.Core-Description : Effect-polymorphic embedding model interface-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Effect-polymorphic Embeddings typeclass.--}-module Langchain.Embeddings.Core-  ( Embeddings (..)-  ) where--import Control.Monad.Except (MonadError)-import Control.Monad.IO.Class (MonadIO)-import Data.Text (Text)--import Langchain.Core.Error (LangchainError)-import Langchain.DocumentLoader.Core (Document)---- | Effect-polymorphic Embeddings typeclass-class Embeddings embed where-  -- | Convert documents to embedding vectors-  embedDocuments ::-    (MonadIO m, MonadError LangchainError m) =>-    embed ->-    [Document] ->-    m [[Float]]--  -- | Convert query text to embedding vector-  embedQuery ::-    (MonadIO m, MonadError LangchainError m) =>-    embed ->-    Text ->-    m [Float]
− src/Langchain/Embeddings/Ollama.hs
@@ -1,75 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}--{- |-Module      : Langchain.Embeddings.Ollama-Description : Ollama integration for text embeddings in LangChain Haskell-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Ollama implementation of LangChain's embedding interface using ollama-haskell 0.3.0.0.--}-module Langchain.Embeddings.Ollama-  ( OllamaEmbeddings (..)-  ) where--import Control.Monad.Except (throwError)-import Control.Monad.IO.Class (liftIO)-import Data.Text (Text)-import qualified Data.Text as T-import qualified Data.Text.Lazy as TL-import Langchain.Core.Error (llmError)-import Langchain.DocumentLoader.Core (Document (..))-import Langchain.Embeddings.Core--import Ollama.API.Embed (EmbedRequest (..), EmbedResponse (..), embed)-import Ollama.Client (defaultClient)-import Ollama.Types.Common (ModelName (..))-import Ollama.Types.Options (ModelOptions)--data OllamaEmbeddings = OllamaEmbeddings-  { model :: Text-  , defaultTruncate :: Maybe Bool-  , defaultKeepAlive :: Maybe Text-  , modelOptions :: Maybe ModelOptions-  }-  deriving (Show, Eq)--instance Embeddings OllamaEmbeddings where-  embedDocuments (OllamaEmbeddings {..}) docs = do-    client <- liftIO defaultClient-    let inputs = map (TL.toStrict . pageContent) docs-        req =-          EmbedRequest-            { embModel = ModelName model-            , embInput = Right inputs-            , embTruncate = defaultTruncate-            , embOptions = modelOptions-            , embKeepAlive = defaultKeepAlive-            , embDimensions = Nothing-            }-    eRes <- liftIO $ embed client req-    case eRes of-      Left ollamaErr -> throwError $ llmError (T.pack (show ollamaErr)) (Just "OllamaEmbeddings") Nothing-      Right resp -> pure $ map (map realToFrac) (erEmbeddings resp)--  embedQuery (OllamaEmbeddings {..}) query = do-    client <- liftIO defaultClient-    let req =-          EmbedRequest-            { embModel = ModelName model-            , embInput = Left query-            , embTruncate = defaultTruncate-            , embOptions = modelOptions-            , embKeepAlive = defaultKeepAlive-            , embDimensions = Nothing-            }-    eRes <- liftIO $ embed client req-    case eRes of-      Left err -> throwError $ llmError (T.pack (show err)) (Just "OllamaEmbeddings") Nothing-      Right resp -> case erEmbeddings resp of-        (vec : _) -> pure $ map realToFrac vec-        [] -> throwError $ llmError "Embeddings are empty" (Just "OllamaEmbeddings") Nothing
− src/Langchain/Embeddings/OpenAI.hs
@@ -1,221 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE ScopedTypeVariables #-}--{- |-Module      : Langchain.Embeddings.OpenAI-Description : OpenAI integration for text embeddings in LangChain Haskell-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--OpenAI implementation of LangChain's embedding interface.--}-module Langchain.Embeddings.OpenAI-  ( OpenAIEmbeddings (..)-  , defaultOpenAIEmbeddings-  , textEmbedding3Small-  , textEmbedding3Large-  , textEmbeddingAda-  , EncodingFormat (..)-  ) where--import Control.Exception (SomeException, try)-import Control.Monad.Except (throwError)-import Control.Monad.IO.Class (liftIO)-import Data.Aeson-import qualified Data.ByteString.Lazy as LBS-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.Core.Error (llmError)-import Langchain.DocumentLoader.Core-import Langchain.Embeddings.Core-import Network.HTTP.Conduit-import Network.HTTP.Simple-  ( getResponseBody-  , getResponseStatus-  , setRequestBodyJSON-  , setRequestHeader-  , setRequestMethod-  )-import Network.HTTP.Types.Status (statusCode)---- Internal types for serialization of OpenAI request.-data EncodingFormat = FloatFormat | Base64Format-  deriving (Eq, Show, Generic)--data EmbeddingsInput = TextInput Text | TextList [Text]-  deriving (Show, Eq)--data OpenAIEmbeddingsRequest = OpenAIEmbeddingsRequest-  { inputReq :: EmbeddingsInput-  , modelReq :: Text-  , dimensionsReq :: Maybe Int-  , encodingFormatReq :: Maybe EncodingFormat-  }-  deriving (Show, Eq, Generic)--instance ToJSON EncodingFormat where-  toJSON FloatFormat = String "float"-  toJSON Base64Format = String "base64"--instance ToJSON EmbeddingsInput where-  toJSON (TextInput t) = String t-  toJSON (TextList t) = Array (V.fromList $ map String t)--instance ToJSON OpenAIEmbeddingsRequest where-  toJSON OpenAIEmbeddingsRequest {..} =-    object $-      [ "input" .= inputReq-      , "model" .= modelReq-      ]-        ++ catMaybes-          [ ("dimensions" .=) <$> dimensionsReq-          , ("encoding_format" .=) <$> encodingFormatReq-          ]---- Response-data EmbeddingsUsage = EmbeddingsUsage-  { promptTokens :: Int-  , totalTokens :: Int-  }-  deriving (Eq, Show, Generic)--data EmbeddingsObject = EmbeddingsObject-  { embeddings :: [Float]-  , index :: Maybe Int-  , objectType :: Text-  }-  deriving (Eq, Show, Generic)--data OpenAIEmbeddingsResponse = OpenAIEmbeddingsResponse-  { objectTypeResp :: Text-  , dataList :: [EmbeddingsObject]-  , responseModel :: Text-  , usage :: Maybe EmbeddingsUsage-  }-  deriving (Eq, Show, Generic)--instance FromJSON EmbeddingsUsage where-  parseJSON = withObject "EmbeddingsUsage" $ \v ->-    EmbeddingsUsage-      <$> v .: "prompt_tokens"-      <*> v .: "total_tokens"--instance FromJSON EmbeddingsObject where-  parseJSON = withObject "EmbeddingsObject" $ \v ->-    EmbeddingsObject-      <$> v .: "embedding"-      <*> v .:? "index"-      <*> v .: "object"--instance FromJSON OpenAIEmbeddingsResponse where-  parseJSON = withObject "OpenAIEmbeddingsResponse" $ \v ->-    OpenAIEmbeddingsResponse-      <$> v .: "object"-      <*> v .: "data"-      <*> v .: "model"-      <*> v .:? "usage"---- | Embeddings type for OpenAI-data OpenAIEmbeddings = OpenAIEmbeddings-  { apiKey :: Text-  , baseUrl :: Maybe String-  , model :: Text-  , dimensions :: Maybe Int-  , encodingFormat :: Maybe EncodingFormat-  , timeout :: Maybe Int-  }-  deriving (Eq, Generic)--instance Show OpenAIEmbeddings where-  show OpenAIEmbeddings {..} = "OpenAIEmbeddings " <> "model " <> unpack model--openAIEmbeddingsRequest ::-  OpenAIEmbeddings -> [Text] -> IO (Either String OpenAIEmbeddingsResponse)-openAIEmbeddingsRequest OpenAIEmbeddings {..} txts = do-  eReq <- try $ parseRequest $ fromMaybe "https://api.openai.com/v1" baseUrl <> "/embeddings"-  case eReq of-    Left (err :: SomeException) -> pure $ Left $ "Invalid URL: " ++ show err-    Right request_ -> do-      manager <--        newManager-          tlsManagerSettings-            { managerResponseTimeout =-                responseTimeoutMicro (fromMaybe 60 timeout * 1000000)-            }-      let req =-            setRequestMethod "POST" $-              setRequestHeader "Content-Type" ["application/json"] $-                setRequestHeader "Authorization" ["Bearer " <> encodeUtf8 apiKey] $-                  setRequestBodyJSON-                    ( OpenAIEmbeddingsRequest-                        { inputReq = TextList txts-                        , modelReq = model-                        , dimensionsReq = dimensions-                        , encodingFormatReq = encodingFormat-                        }-                    )-                    request_--      eResponse <- try (httpLbs req manager) :: IO (Either SomeException (Response LBS.ByteString))-      case eResponse of-        Left err -> pure $ Left $ "Network error: " ++ show err-        Right response -> do-          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)--instance Embeddings OpenAIEmbeddings where-  embedDocuments openAIEmbeddings docs = do-    eRes <- liftIO $ openAIEmbeddingsRequest openAIEmbeddings (map (TL.toStrict . pageContent) docs)-    case eRes of-      Left err -> throwError $ llmError (T.pack err) (Just "OpenAIEmbeddings") Nothing-      Right (OpenAIEmbeddingsResponse {..}) -> pure $ map embeddings dataList--  embedQuery openAIEmbeddings query = do-    eRes <- liftIO $ openAIEmbeddingsRequest openAIEmbeddings [query]-    case eRes of-      Left err -> throwError $ llmError (T.pack err) (Just "OpenAIEmbeddings") Nothing-      Right (OpenAIEmbeddingsResponse {..}) ->-        case listToMaybe dataList of-          Nothing -> throwError $ llmError "Embeddings are empty" (Just "OpenAIEmbeddings") Nothing-          Just x -> pure $ embeddings x--textEmbedding3Small :: Text-textEmbedding3Small = "text-embedding-3-small"--textEmbedding3Large :: Text-textEmbedding3Large = "text-embedding-3-large"--textEmbeddingAda :: Text-textEmbeddingAda = "text-embedding-ada-002"--defaultOpenAIEmbeddings :: OpenAIEmbeddings-defaultOpenAIEmbeddings =-  OpenAIEmbeddings-    { apiKey = ""-    , baseUrl = pure "https://api.openai.com/v1"-    , model = textEmbedding3Small-    , dimensions = Nothing-    , encodingFormat = Nothing-    , timeout = Nothing-    }
− src/Langchain/Guardrail/Core.hs
@@ -1,143 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedStrings #-}--{- |-Module      : Langchain.Guardrail.Core-Description : Agent input/output validation guardrails and safety filters-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Composable guardrails for validating prompt safety, topic restriction, and response format constraints.--}-module Langchain.Guardrail.Core-  ( GuardrailResult (..)-  , Guardrail (..)-  , contentSafetyGuardrail-  , topicGuardrail-  , outputLengthGuardrail-  , composeGuardrails-  , withGuardrails-  ) where--import Control.Monad.Except (MonadError, throwError)-import Control.Monad.IO.Class (MonadIO)-import Data.Text (Text)-import qualified Data.Text as T--import Langchain.Core.Error (LangchainError, agentError)-import Langchain.Core.Model-  ( ChatModel (..)-  , extractMessageText-  , userMessage-  )---- | Outcome of evaluating a guardrail check-data GuardrailResult-  = GuardrailPass-  | GuardrailFail !Text -- Reason for failure-  deriving (Show, Eq)---- | Composable guardrail container-data Guardrail m = Guardrail-  { guardrailName :: !Text-  , validateInput :: Text -> m GuardrailResult-  , validateOutput :: Text -> m GuardrailResult-  }---- | Simple keyword-based content safety guardrail-contentSafetyGuardrail :: MonadIO m => [Text] -> Guardrail m-contentSafetyGuardrail forbiddenWords =-  Guardrail-    { guardrailName = "ContentSafety"-    , validateInput = \input ->-        let lower = T.toLower input-            matched = filter (`T.isInfixOf` lower) (map T.toLower forbiddenWords)-         in pure $-              if null matched-                then GuardrailPass-                else GuardrailFail ("Input contains forbidden content: " <> T.intercalate ", " matched)-    , validateOutput = \output ->-        let lower = T.toLower output-            matched = filter (`T.isInfixOf` lower) (map T.toLower forbiddenWords)-         in pure $-              if null matched-                then GuardrailPass-                else GuardrailFail ("Output contains forbidden content: " <> T.intercalate ", " matched)-    }---- | Output length guardrail-outputLengthGuardrail :: MonadIO m => Int -> Guardrail m-outputLengthGuardrail maxLen =-  Guardrail-    { guardrailName = "OutputLength"-    , validateInput = \_ -> pure GuardrailPass-    , validateOutput = \out ->-        if T.length out <= maxLen-          then pure GuardrailPass-          else-            pure $-              GuardrailFail-                ("Output length (" <> T.pack (show (T.length out)) <> ") exceeds limit of " <> T.pack (show maxLen))-    }---- | LLM-based topic relevance guardrail-topicGuardrail ::-  (ChatModel model, MonadIO m, MonadError LangchainError m) => model -> Text -> Guardrail m-topicGuardrail model allowedTopic =-  Guardrail-    { guardrailName = "TopicRestriction"-    , validateInput = \input -> do-        let prompt =-              "Allowed Topic: "-                <> allowedTopic-                <> "\n\nUser Input: "-                <> input-                <> "\nIs the user input relevant to the allowed topic? Reply ONLY with 'YES' or 'NO: <reason>'."-        resp <- invoke model [userMessage prompt] Nothing-        let ans = T.strip (extractMessageText resp)-        pure $-          if "YES" `T.isPrefixOf` ans-            then GuardrailPass-            else GuardrailFail ("Topic violation: " <> ans)-    , validateOutput = \_ -> pure GuardrailPass-    }---- | Compose multiple guardrails in sequence-composeGuardrails :: (MonadIO m) => [Guardrail m] -> Guardrail m-composeGuardrails [] =-  Guardrail "NoOp" (\_ -> pure GuardrailPass) (\_ -> pure GuardrailPass)-composeGuardrails rails =-  Guardrail-    { guardrailName = T.intercalate "+" (map guardrailName rails)-    , validateInput = checkAll (map validateInput rails)-    , validateOutput = checkAll (map validateOutput rails)-    }-  where-    checkAll [] _ = pure GuardrailPass-    checkAll (v : vs) txt = do-      res <- v txt-      case res of-        GuardrailPass -> checkAll vs txt-        failRes -> pure failRes---- | Execute an action wrapped by input and output guardrails-withGuardrails ::-  (MonadIO m, MonadError LangchainError m) =>-  Guardrail m ->-  (Text -> m Text) ->-  Text ->-  m Text-withGuardrails rail action input = do-  inRes <- validateInput rail input-  case inRes of-    GuardrailFail reason ->-      throwError $ agentError ("Input guardrail failed: " <> reason) (Just (guardrailName rail)) Nothing-    GuardrailPass -> do-      output <- action input-      outRes <- validateOutput rail output-      case outRes of-        GuardrailFail reason ->-          throwError $ agentError ("Output guardrail failed: " <> reason) (Just (guardrailName rail)) Nothing-        GuardrailPass -> pure output
− src/Langchain/MCP/Client.hs
@@ -1,305 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}--{- |-Module      : Langchain.MCP.Client-Description : Model Context Protocol (MCP) Client over JSON-RPC 2.0-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--First-class Haskell client implementation for the open Model Context Protocol (MCP).-Supports stdio process and HTTP/SSE JSON-RPC 2.0 transports, tool discovery, resource reading,-and seamless conversion of remote MCP tools into native Langchain 'Tool' records.--}-module Langchain.MCP.Client-  ( McpTransport (..)-  , McpToolInfo (..)-  , McpResource (..)-  , McpClient (..)-  , newStdioMcpClient-  , newHttpMcpClient-  , listMcpTools-  , callMcpTool-  , mcpToolToLangchainTool-  ) where--import Control.Exception (SomeException, try)-import Control.Monad.Except (MonadError, runExceptT, throwError)-import Control.Monad.IO.Class (MonadIO, liftIO)-import Data.Aeson-  ( FromJSON (..)-  , ToJSON (..)-  , Value (..)-  , decode-  , encode-  , object-  , withObject-  , (.!=)-  , (.:)-  , (.:?)-  , (.=)-  )-import Data.Aeson.Types (parseEither)-import qualified Data.ByteString.Lazy as LBS-import qualified Data.ByteString.Lazy.Char8 as LBSC-import Data.Text (Text)-import qualified Data.Text as T-import qualified Data.Text.Encoding as TE-import Network.HTTP.Simple-import System.IO (BufferMode (..), hClose, hFlush, hGetLine, hSetBuffering)-import System.Process (CreateProcess (..), StdStream (..), createProcess, proc, terminateProcess)--import Langchain.Core.Error (LangchainError, toolError)-import Langchain.Tool.Core (Tool (..), createTool)---- | MCP Transport type-data McpTransport-  = StdioTransport !FilePath ![String]-  | HttpTransport !Text-  deriving (Show, Eq)---- | Information about an MCP tool published by the server-data McpToolInfo = McpToolInfo-  { mcpToolName :: !Text-  , mcpToolDescription :: !Text-  , mcpToolInputSchema :: !Value-  }-  deriving (Show, Eq)--instance FromJSON McpToolInfo where-  parseJSON = withObject "McpToolInfo" $ \o -> do-    mcpToolName <- o .: "name"-    mcpToolDescription <- o .:? "description" .!= ""-    mcpToolInputSchema <- o .:? "inputSchema" .!= object []-    pure McpToolInfo {..}--instance ToJSON McpToolInfo where-  toJSON McpToolInfo {..} =-    object-      [ "name" .= mcpToolName-      , "description" .= mcpToolDescription-      , "inputSchema" .= mcpToolInputSchema-      ]---- | MCP Resource descriptor-data McpResource = McpResource-  { mcpResourceUri :: !Text-  , mcpResourceName :: !Text-  , mcpResourceMimeType :: !(Maybe Text)-  }-  deriving (Show, Eq)--instance FromJSON McpResource where-  parseJSON = withObject "McpResource" $ \o -> do-    mcpResourceUri <- o .: "uri"-    mcpResourceName <- o .: "name"-    mcpResourceMimeType <- o .:? "mimeType"-    pure McpResource {..}---- | MCP Client handle-data McpClient = McpClient-  { clientTransport :: !McpTransport-  , serverName :: !Text-  }-  deriving (Show, Eq)---- | Construct a stdio MCP client-newStdioMcpClient :: Text -> FilePath -> [String] -> McpClient-newStdioMcpClient sName cmd args =-  McpClient-    { clientTransport = StdioTransport cmd args-    , serverName = sName-    }---- | Construct an HTTP MCP client-newHttpMcpClient :: Text -> Text -> McpClient-newHttpMcpClient sName url =-  McpClient-    { clientTransport = HttpTransport url-    , serverName = sName-    }---- | Execute a JSON-RPC 2.0 interaction over a stdio process-execStdioJsonRpc ::-  (MonadIO m, MonadError LangchainError m) =>-  FilePath ->-  [String] ->-  Value ->-  m Value-execStdioJsonRpc cmd args rpcReq = do-  eRes <- liftIO $ try $ do-    let cp =-          (proc cmd args)-            { std_in = CreatePipe-            , std_out = CreatePipe-            , std_err = NoStream-            }-    (Just hIn, Just hOut, _, ph) <- createProcess cp-    hSetBuffering hIn LineBuffering-    hSetBuffering hOut LineBuffering--    -- Send initialize handshake-    let initMsg =-          object-            [ "jsonrpc" .= ("2.0" :: Text)-            , "id" .= (1 :: Int)-            , "method" .= ("initialize" :: Text)-            , "params"-                .= object-                  [ "protocolVersion" .= ("2024-11-05" :: Text)-                  , "capabilities" .= object []-                  , "clientInfo" .= object ["name" .= ("langchain-hs" :: Text), "version" .= ("0.5.0" :: Text)]-                  ]-            ]-    LBSC.hPutStrLn hIn (encode initMsg)-    hFlush hIn-    _initResp <- hGetLine hOut--    -- Send notifications/initialized-    let notifyMsg =-          object-            [ "jsonrpc" .= ("2.0" :: Text)-            , "method" .= ("notifications/initialized" :: Text)-            ]-    LBSC.hPutStrLn hIn (encode notifyMsg)-    hFlush hIn--    -- Send actual request-    LBSC.hPutStrLn hIn (encode rpcReq)-    hFlush hIn-    respLine <- hGetLine hOut-    hClose hIn-    hClose hOut-    terminateProcess ph-    pure (decode (LBSC.pack respLine) :: Maybe Value)--  case eRes of-    Left err ->-      let errStr = show (err :: SomeException)-       in throwError $ toolError ("MCP stdio process failed: " <> T.pack errStr) (Just (T.pack cmd)) Nothing-    Right Nothing ->-      throwError $ toolError "MCP stdio returned invalid JSON" (Just (T.pack cmd)) Nothing-    Right (Just val) -> pure val---- | Query server for available tools via tools/list JSON-RPC call-listMcpTools ::-  (MonadIO m, MonadError LangchainError m) =>-  McpClient ->-  m [McpToolInfo]-listMcpTools McpClient {..} = case clientTransport of-  HttpTransport url -> do-    let reqPayload =-          object-            [ "jsonrpc" .= ("2.0" :: Text)-            , "id" .= (100 :: Int)-            , "method" .= ("tools/list" :: Text)-            , "params" .= object []-            ]-    let req =-          setRequestMethod "POST" $-            setRequestHeader "Content-Type" ["application/json"] $-              setRequestBodyJSON reqPayload (parseRequest_ (T.unpack url))-    eResp <- liftIO (try $ httpLBS req :: IO (Either SomeException (Response LBS.ByteString)))-    case eResp of-      Left err ->-        throwError $-          toolError ("MCP HTTP tools/list failed: " <> T.pack (show err)) (Just serverName) Nothing-      Right resp -> do-        let body = getResponseBody resp-        case decode body of-          Just val -> parseToolsResult val-          Nothing -> throwError $ toolError "Invalid JSON received from MCP HTTP endpoint" (Just serverName) Nothing-  StdioTransport cmd args -> do-    let reqPayload =-          object-            [ "jsonrpc" .= ("2.0" :: Text)-            , "id" .= (100 :: Int)-            , "method" .= ("tools/list" :: Text)-            , "params" .= object []-            ]-    val <- execStdioJsonRpc cmd args reqPayload-    parseToolsResult val-  where-    parseToolsResult val =-      case parseEither parseResult val of-        Left err ->-          throwError $ toolError ("Failed to parse MCP tools list: " <> T.pack err) (Just serverName) Nothing-        Right tools -> pure tools--    parseResult = withObject "JsonRpcResponse" $ \o -> do-      resultObj <- o .: "result"-      resultObj .: "tools"---- | Execute a tool on the remote MCP server via tools/call JSON-RPC method-callMcpTool ::-  (MonadIO m, MonadError LangchainError m) =>-  McpClient ->-  Text ->-  Value ->-  m Text-callMcpTool McpClient {..} tName args = case clientTransport of-  HttpTransport url -> do-    let reqPayload =-          object-            [ "jsonrpc" .= ("2.0" :: Text)-            , "id" .= (200 :: Int)-            , "method" .= ("tools/call" :: Text)-            , "params"-                .= object-                  [ "name" .= tName-                  , "arguments" .= args-                  ]-            ]-    let req =-          setRequestMethod "POST" $-            setRequestHeader "Content-Type" ["application/json"] $-              setRequestBodyJSON reqPayload (parseRequest_ (T.unpack url))-    eResp <- liftIO (try $ httpLBS req :: IO (Either SomeException (Response LBS.ByteString)))-    case eResp of-      Left err -> throwError $ toolError ("MCP tools/call failed: " <> T.pack (show err)) (Just tName) Nothing-      Right resp -> do-        let body = getResponseBody resp-        case decode body of-          Just val -> extractCallContent val-          Nothing -> pure $ TE.decodeUtf8 $ LBS.toStrict body-  StdioTransport cmd cmdArgs-    | cmd `elem` ["mock", "echo"] ->-        pure $ "Executed MCP tool " <> tName <> " via stdio."-    | otherwise -> do-        let reqPayload =-              object-                [ "jsonrpc" .= ("2.0" :: Text)-                , "id" .= (200 :: Int)-                , "method" .= ("tools/call" :: Text)-                , "params"-                    .= object-                      [ "name" .= tName-                      , "arguments" .= args-                      ]-                ]-        val <- execStdioJsonRpc cmd cmdArgs reqPayload-        extractCallContent val-  where-    extractCallContent val =-      case parseEither parseContent val of-        Right textRes -> pure textRes-        Left _ -> pure $ TE.decodeUtf8 $ LBS.toStrict (encode val)--    parseContent = withObject "JsonRpcCallResponse" $ \o -> do-      res <- o .: "result"-      contentArr <- res .: "content"-      case contentArr of-        (Object firstBlock : _) -> firstBlock .: "text"-        _ -> pure ""---- | Convert an MCP Tool descriptor into a native Langchain Tool-mcpToolToLangchainTool :: McpClient -> McpToolInfo -> Tool IO-mcpToolToLangchainTool client McpToolInfo {..} =-  createTool-    mcpToolName-    mcpToolDescription-    mcpToolInputSchema-    (runExceptT . callMcpTool client mcpToolName)
− src/Langchain/Memory/Core.hs
@@ -1,173 +0,0 @@-{-# LANGUAGE DuplicateRecordFields #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedStrings #-}--{- |-Module      : Langchain.Memory.Core-Description : Effect-polymorphic memory management for LangChain Haskell-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Thread-safe, effect-polymorphic conversation memory interfaces using STM.--}-module Langchain.Memory.Core-  ( BaseMemory (..)-  , WindowBufferMemory (..)-  , newWindowBufferMemory-  , TokenBufferMemory (..)-  , newTokenBufferMemory-  , countTokens-  , trimMessages-  , initialMessages-  ) where--import Control.Concurrent.STM (TVar, atomically, modifyTVar', newTVarIO, readTVarIO, writeTVar)-import Control.Monad.Except (MonadError, throwError)-import Control.Monad.IO.Class (MonadIO, liftIO)-import Data.Text (Text)-import qualified Data.Text as T--import Langchain.Core.Error (LangchainError, memoryError)-import Langchain.Core.Model-  ( Message (..)-  , Role (..)-  , assistantMessage-  , extractMessageText-  , systemMessage-  , userMessage-  )---- | Effect-polymorphic BaseMemory typeclass-class BaseMemory mem where-  -- | Retrieve current conversation messages-  messages ::-    (MonadIO m, MonadError LangchainError m) =>-    mem ->-    m [Message]--  -- | Add a user message to history-  addUserMessage ::-    (MonadIO m, MonadError LangchainError m) =>-    mem ->-    Text ->-    m ()-  addUserMessage mem txt = addMessage mem (userMessage txt)--  -- | Add an AI response message to history-  addAiMessage ::-    (MonadIO m, MonadError LangchainError m) =>-    mem ->-    Text ->-    m ()-  addAiMessage mem txt = addMessage mem (assistantMessage txt)--  -- | Add a structured message to history-  addMessage ::-    (MonadIO m, MonadError LangchainError m) =>-    mem ->-    Message ->-    m ()--  -- | Reset memory to initial state-  clear ::-    (MonadIO m, MonadError LangchainError m) =>-    mem ->-    m ()---- | Sliding window memory backed by thread-safe STM TVar-data WindowBufferMemory = WindowBufferMemory-  { maxWindowSize :: !Int-  , memVar :: !(TVar [Message])-  }--instance Show WindowBufferMemory where-  show (WindowBufferMemory sz _) = "WindowBufferMemory { maxWindowSize = " ++ show sz ++ " }"--instance Eq WindowBufferMemory where-  (WindowBufferMemory sz1 tv1) == (WindowBufferMemory sz2 tv2) =-    sz1 == sz2 && tv1 == tv2---- | Construct a thread-safe WindowBufferMemory in MonadIO-newWindowBufferMemory :: MonadIO m => Int -> [Message] -> m WindowBufferMemory-newWindowBufferMemory sz initMsgs = liftIO $ do-  tv <- newTVarIO initMsgs-  pure $ WindowBufferMemory sz tv--instance BaseMemory WindowBufferMemory where-  messages (WindowBufferMemory _ tv) = liftIO $ readTVarIO tv--  addMessage (WindowBufferMemory maxSz tv) newMsg = liftIO $ do-    atomically $ modifyTVar' tv $ \currMsgs ->-      let combined = currMsgs ++ [newMsg]-       in if length combined > maxSz-            then removeOldestNonSystem combined-            else combined-    where-      removeOldestNonSystem [] = []-      removeOldestNonSystem (m : ms)-        | messageRole m == System = m : removeOldestNonSystem ms-        | otherwise = ms--  clear (WindowBufferMemory _ tv) = liftIO $ do-    atomically $ writeTVar tv [systemMessage "You are a helpful AI assistant"]---- | Token-based sliding window memory type-data TokenBufferMemory = TokenBufferMemory-  { maxTokens :: !Int-  , memVar :: !(TVar [Message])-  }--instance Show TokenBufferMemory where-  show (TokenBufferMemory maxT _) = "TokenBufferMemory { maxTokens = " ++ show maxT ++ " }"--instance Eq TokenBufferMemory where-  (TokenBufferMemory t1 tv1) == (TokenBufferMemory t2 tv2) =-    t1 == t2 && tv1 == tv2---- | Construct a new TokenBufferMemory-newTokenBufferMemory :: MonadIO m => Int -> [Message] -> m TokenBufferMemory-newTokenBufferMemory maxT initMsgs = liftIO $ do-  tv <- newTVarIO initMsgs-  pure $ TokenBufferMemory maxT tv---- | Approximate token count: 4 characters ≈ 1 token-countTokens :: [Message] -> Int-countTokens = sum . map (\m -> ceiling (fromIntegral (T.length (extractMessageText m)) / (4.0 :: Double)))--instance BaseMemory TokenBufferMemory where-  messages (TokenBufferMemory _ tv) = liftIO $ readTVarIO tv--  addMessage (TokenBufferMemory maxT tv) newMsg = do-    let newMsgTokens = countTokens [newMsg]-    if newMsgTokens > maxT-      then-        throwError $-          memoryError "New message exceeds maximum token limit" (Just "TokenBufferMemory") Nothing-      else liftIO $ atomically $ modifyTVar' tv $ \currMsgs ->-        trimToLimit currMsgs newMsgTokens [newMsg]-    where-      trimToLimit currMsgs newMsgToks acc =-        let candidate = currMsgs ++ acc-         in if countTokens candidate <= maxT-              then candidate-              else case removeOldestNonSystem currMsgs of-                Just trimmed -> trimToLimit trimmed newMsgToks acc-                Nothing -> [newMsg]--      removeOldestNonSystem [] = Nothing-      removeOldestNonSystem (m : ms)-        | messageRole m == System = fmap (m :) (removeOldestNonSystem ms)-        | otherwise = Just ms--  clear (TokenBufferMemory _ tv) = liftIO $ do-    atomically $ writeTVar tv [systemMessage "You are a helpful AI assistant"]---- | Pure helper to trim messages to last N-trimMessages :: Int -> [Message] -> [Message]-trimMessages n msgs = drop (max 0 (length msgs - n)) msgs---- | Pure helper to construct initial system message history-initialMessages :: Text -> [Message]-initialMessages sysPrompt = [systemMessage sysPrompt]
− src/Langchain/Memory/Entity.hs
@@ -1,99 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}--{- |-Module      : Langchain.Memory.Entity-Description : Entity extraction and tracking conversation memory-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Extracts and tracks key named entities and facts across multi-turn conversations.--}-module Langchain.Memory.Entity-  ( EntityMemory (..)-  , newEntityMemory-  , getEntities-  , setEntity-  ) where--import Control.Concurrent.STM-import Control.Monad (when)-import Control.Monad.IO.Class (MonadIO, liftIO)-import Data.Map.Strict (Map)-import qualified Data.Map.Strict as Map-import Data.Text (Text)-import qualified Data.Text as T--import Langchain.Core.Model-  ( ChatModel (..)-  , Message (..)-  , Role (..)-  , extractMessageText-  , systemMessage-  , userMessage-  )-import Langchain.Memory.Core (BaseMemory (..))---- | Entity tracking memory backed by STM TVars-data EntityMemory model = EntityMemory-  { entityModel :: model-  , entityStoreVar :: !(TVar (Map Text Text))-  , entityMessagesVar :: !(TVar [Message])-  }---- | Construct a new EntityMemory instance-newEntityMemory :: MonadIO m => model -> [Message] -> m (EntityMemory model)-newEntityMemory model initMsgs = liftIO $ do-  eVar <- newTVarIO Map.empty-  mVar <- newTVarIO initMsgs-  pure $ EntityMemory model eVar mVar---- | Retrieve all currently tracked entities-getEntities :: MonadIO m => EntityMemory model -> m (Map Text Text)-getEntities EntityMemory {..} = liftIO $ readTVarIO entityStoreVar---- | Manually set or update an entity definition-setEntity :: MonadIO m => EntityMemory model -> Text -> Text -> m ()-setEntity EntityMemory {..} k v =-  liftIO $ atomically $ modifyTVar' entityStoreVar (Map.insert k v)--instance (ChatModel model) => BaseMemory (EntityMemory model) where-  messages EntityMemory {..} = liftIO $ do-    entities <- readTVarIO entityStoreVar-    msgs <- readTVarIO entityMessagesVar-    if Map.null entities-      then pure msgs-      else-        let entityCtx =-              "Known Entities & Context:\n"-                <> T.unlines ["- " <> k <> ": " <> v | (k, v) <- Map.toList entities]-         in pure (systemMessage entityCtx : msgs)--  addMessage EntityMemory {..} newMsg = do-    liftIO $ atomically $ modifyTVar' entityMessagesVar (\msgs -> msgs ++ [newMsg])-    -- If user message, prompt entityModel to extract any entities-    when (messageRole newMsg == User) $ do-      let prompt =-            "Extract any key entities, topics, or facts mentioned in this message in the format 'Entity: Description'.\n"-              <> "Message: "-              <> extractMessageText newMsg-      resp <- invoke entityModel [userMessage prompt] Nothing-      let extracted = parseEntityLines (extractMessageText resp)-      liftIO $ atomically $ modifyTVar' entityStoreVar (Map.union (Map.fromList extracted))--  clear EntityMemory {..} = liftIO $ atomically $ do-    writeTVar entityStoreVar Map.empty-    writeTVar entityMessagesVar []--parseEntityLines :: Text -> [(Text, Text)]-parseEntityLines txt =-  [ (T.strip (T.dropAround (`elem` ['*', '-', ' ']) k), T.strip v)-  | line <- T.lines txt-  , let (k, rest) = T.breakOn ":" line-  , not (T.null rest)-  , let v = T.drop 1 rest-  , not (T.null (T.strip k)) && not (T.null (T.strip v))-  ]
− src/Langchain/Memory/Summary.hs
@@ -1,103 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}--{- |-Module      : Langchain.Memory.Summary-Description : Summary-based conversation memory with progressive LLM summarization-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Progressively summarizes older conversation history using a ChatModel when history exceeds a threshold.--}-module Langchain.Memory.Summary-  ( SummaryMemory (..)-  , newSummaryMemory-  , getSummary-  ) where--import Control.Concurrent.STM-import Control.Monad (when)-import Control.Monad.IO.Class (MonadIO, liftIO)-import Data.Text (Text)-import qualified Data.Text as T--import Langchain.Core.Model-  ( ChatModel (..)-  , Message (..)-  , Role (..)-  , extractMessageText-  , systemMessage-  , userMessage-  )-import Langchain.Memory.Core (BaseMemory (..))---- | Progressive summarization memory backed by STM TVars-data SummaryMemory model = SummaryMemory-  { summaryModel :: model-  , maxMessageThreshold :: !Int-  , summaryBufferVar :: !(TVar Text)-  , recentMessagesVar :: !(TVar [Message])-  }---- | Construct a new SummaryMemory instance-newSummaryMemory :: MonadIO m => model -> Int -> [Message] -> m (SummaryMemory model)-newSummaryMemory model threshold initMsgs = liftIO $ do-  sVar <- newTVarIO ""-  mVar <- newTVarIO initMsgs-  pure $ SummaryMemory model threshold sVar mVar---- | Retrieve the current accumulated summary text-getSummary :: MonadIO m => SummaryMemory model -> m Text-getSummary SummaryMemory {..} = liftIO $ readTVarIO summaryBufferVar--instance (ChatModel model) => BaseMemory (SummaryMemory model) where-  messages SummaryMemory {..} = liftIO $ do-    sumTxt <- readTVarIO summaryBufferVar-    recent <- readTVarIO recentMessagesVar-    if T.null sumTxt-      then pure recent-      else pure (systemMessage ("Summary of previous conversation:\n" <> sumTxt) : recent)--  addMessage SummaryMemory {..} newMsg = do-    (shouldSummarize, toSummarize, _) <- liftIO $ atomically $ do-      modifyTVar' recentMessagesVar (\msgs -> msgs ++ [newMsg])-      currentMsgs <- readTVar recentMessagesVar-      if length currentMsgs > maxMessageThreshold-        then do-          let (old, keep) = splitAt (length currentMsgs - max 2 (maxMessageThreshold `div` 2)) currentMsgs-          writeTVar recentMessagesVar keep-          pure (True, old, keep)-        else pure (False, [], currentMsgs)--    when (shouldSummarize && not (null toSummarize)) $ do-      currentSummary <- liftIO $ readTVarIO summaryBufferVar-      let summaryPrompt =-            "Current summary:\n"-              <> currentSummary-              <> "\n\nNew lines to summarize:\n"-              <> formatMessages toSummarize-              <> "\n\nPlease provide an updated, concise summary of the conversation above."-      aiResp <- invoke summaryModel [userMessage summaryPrompt] Nothing-      let newSummary = extractMessageText aiResp-      liftIO $ atomically $ writeTVar summaryBufferVar newSummary--  clear SummaryMemory {..} = liftIO $ atomically $ do-    writeTVar summaryBufferVar ""-    writeTVar recentMessagesVar []--formatMessages :: [Message] -> Text-formatMessages msgs =-  T.unlines-    [ formatRole (messageRole m) <> ": " <> extractMessageText m-    | m <- msgs-    ]-  where-    formatRole System = "System"-    formatRole User = "Human"-    formatRole Assistant = "AI"-    formatRole Tool = "Tool"-    formatRole Developer = "Developer"-    formatRole Function = "Function"
− src/Langchain/Observability.hs
@@ -1,275 +0,0 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}--{- |-Module      : Langchain.Observability-Description : Unified logging and OpenTelemetry tracing-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Provides unified structured logging and OpenTelemetry-compatible tracing.--}-module Langchain.Observability-  ( -- * Structured Logging-    LogLevel (..)-  , LogEvent (..)-  , Logger (..)-  , InMemoryLogger (..)-  , newInMemoryLogger-  , getInMemoryLogs-  , stderrLogger-  , logEvent-  , logDebug-  , logInfo-  , logWarn-  , logError--    -- * OpenTelemetry Tracing-  , SpanKind (..)-  , SpanStatus (..)-  , Span (..)-  , OTelTracer (..)-  , newOTelTracer-  , getSpans-  , startSpan-  , endSpan-  , addSpanAttribute-  , withSpan-  , exportSpansJson-  ) where--import Control.Concurrent.STM-import Control.Monad (when)-import Control.Monad.Except (MonadError, catchError, throwError)-import Control.Monad.IO.Class (MonadIO, liftIO)-import Data.Aeson (FromJSON, ToJSON, encode)-import qualified Data.ByteString.Lazy.Char8 as LBSC-import Data.Map.Strict (Map)-import qualified Data.Map.Strict as Map-import Data.Text (Text)-import qualified Data.Text as T-import Data.Time.Clock (UTCTime, diffUTCTime, getCurrentTime)-import GHC.Generics (Generic)-import System.IO (hPutStrLn, stderr)-import System.Random (randomRIO)--import Langchain.Core.Error (LangchainError)------------------------------------------------------------------------------------- Structured Logging------------------------------------------------------------------------------------- | Severity level for log events-data LogLevel-  = DebugLevel-  | InfoLevel-  | WarnLevel-  | ErrorLevel-  deriving (Show, Eq, Ord, Enum, Bounded, Generic, ToJSON, FromJSON)---- | Structured log event with metadata-data LogEvent = LogEvent-  { logLevel :: !LogLevel-  , logTimestamp :: !UTCTime-  , logComponent :: !Text-  , logMessage :: !Text-  , logMetadata :: !(Map Text Text)-  }-  deriving (Show, Eq, Generic, ToJSON, FromJSON)---- | Pluggable logger backend-data Logger = Logger-  { minLevel :: !LogLevel-  , writeLog :: LogEvent -> IO ()-  }---- | In-memory logger storing events in STM TVar-data InMemoryLogger = InMemoryLogger-  { inMemoryVar :: !(TVar [LogEvent])-  , inMemoryMinLevel :: !LogLevel-  }---- | Construct a new InMemoryLogger-newInMemoryLogger :: MonadIO m => LogLevel -> m InMemoryLogger-newInMemoryLogger minLvl = liftIO $ do-  var <- newTVarIO []-  pure $ InMemoryLogger var minLvl---- | Retrieve all logged events from an InMemoryLogger-getInMemoryLogs :: MonadIO m => InMemoryLogger -> m [LogEvent]-getInMemoryLogs InMemoryLogger {..} = liftIO $ readTVarIO inMemoryVar---- | Default stderr logger-stderrLogger :: LogLevel -> Logger-stderrLogger minLvl =-  Logger-    { minLevel = minLvl-    , writeLog = \event -> do-        let line = LBSC.unpack (encode event)-        hPutStrLn stderr line-    }---- | Log a structured event through a logger-logEvent :: MonadIO m => Logger -> LogLevel -> Text -> Text -> Map Text Text -> m ()-logEvent Logger {..} lvl comp msg meta =-  when (lvl >= minLevel) $ liftIO $ do-    now <- getCurrentTime-    let event = LogEvent lvl now comp msg meta-    writeLog event---- | Log a debug message-logDebug :: MonadIO m => Logger -> Text -> Text -> m ()-logDebug logger comp msg = logEvent logger DebugLevel comp msg Map.empty---- | Log an info message-logInfo :: MonadIO m => Logger -> Text -> Text -> m ()-logInfo logger comp msg = logEvent logger InfoLevel comp msg Map.empty---- | Log a warning message-logWarn :: MonadIO m => Logger -> Text -> Text -> m ()-logWarn logger comp msg = logEvent logger WarnLevel comp msg Map.empty---- | Log an error message-logError :: MonadIO m => Logger -> Text -> Text -> m ()-logError logger comp msg = logEvent logger ErrorLevel comp msg Map.empty------------------------------------------------------------------------------------- OpenTelemetry Tracing------------------------------------------------------------------------------------- | OpenTelemetry Span Kind-data SpanKind-  = InternalSpan-  | ClientSpan-  | ServerSpan-  | ProducerSpan-  | ConsumerSpan-  deriving (Show, Eq, Generic, ToJSON, FromJSON)---- | OpenTelemetry Span Status-data SpanStatus-  = StatusUnset-  | StatusOk-  | StatusError !Text-  deriving (Show, Eq, Generic, ToJSON, FromJSON)---- | Single OpenTelemetry Span-data Span = Span-  { spanName :: !Text-  , spanTraceId :: !Text-  , spanId :: !Text-  , spanParentId :: !(Maybe Text)-  , spanKind :: !SpanKind-  , spanStartTime :: !UTCTime-  , spanEndTime :: !(Maybe UTCTime)-  , spanDurationMicros :: !(Maybe Int)-  , spanAttributes :: !(Map Text Text)-  , spanStatus :: !SpanStatus-  }-  deriving (Show, Eq, Generic, ToJSON, FromJSON)---- | Thread-safe in-memory OpenTelemetry tracer backed by STM TVar-data OTelTracer = OTelTracer-  { tracerTraceId :: !Text-  , tracerSpansVar :: !(TVar [Span])-  }---- | Construct a new OTelTracer with a given or auto-generated trace ID-newOTelTracer :: MonadIO m => Maybe Text -> m OTelTracer-newOTelTracer mbTraceId = liftIO $ do-  tId <- case mbTraceId of-    Just tid -> pure tid-    Nothing -> do-      randVal <- randomRIO (1000000000000000 :: Integer, 9999999999999999 :: Integer)-      pure $ "trace-" <> T.pack (show randVal)-  var <- newTVarIO []-  pure $ OTelTracer tId var---- | Retrieve all recorded spans-getSpans :: MonadIO m => OTelTracer -> m [Span]-getSpans OTelTracer {..} = liftIO $ readTVarIO tracerSpansVar---- | Start a new OpenTelemetry span-startSpan ::-  MonadIO m =>-  OTelTracer ->-  Text ->-  Maybe Text ->-  SpanKind ->-  Map Text Text ->-  m Span-startSpan OTelTracer {..} name parentId kind attrs = liftIO $ do-  now <- getCurrentTime-  randSpan <- randomRIO (10000000 :: Integer, 99999999 :: Integer)-  let sId = "span-" <> T.pack (show randSpan)-      sp =-        Span-          { spanName = name-          , spanTraceId = tracerTraceId-          , spanId = sId-          , spanParentId = parentId-          , spanKind = kind-          , spanStartTime = now-          , spanEndTime = Nothing-          , spanDurationMicros = Nothing-          , spanAttributes = attrs-          , spanStatus = StatusUnset-          }-  atomically $ modifyTVar' tracerSpansVar (\spans -> spans ++ [sp])-  pure sp---- | Complete an active span with final status-endSpan :: MonadIO m => OTelTracer -> Text -> SpanStatus -> m ()-endSpan OTelTracer {..} targetSpanId status = liftIO $ do-  now <- getCurrentTime-  atomically $ modifyTVar' tracerSpansVar (map (finalizeSpan now))-  where-    finalizeSpan now sp-      | spanId sp == targetSpanId =-          let durMicros = round (diffUTCTime now (spanStartTime sp) * 1000000)-           in sp-                { spanEndTime = Just now-                , spanDurationMicros = Just durMicros-                , spanStatus = status-                }-      | otherwise = sp---- | Add or update an attribute on an active or completed span-addSpanAttribute :: MonadIO m => OTelTracer -> Text -> Text -> Text -> m ()-addSpanAttribute OTelTracer {..} targetSpanId key val = liftIO $ do-  atomically $ modifyTVar' tracerSpansVar (map updateAttr)-  where-    updateAttr sp-      | spanId sp == targetSpanId =-          sp {spanAttributes = Map.insert key val (spanAttributes sp)}-      | otherwise = sp---- | Wrap a monadic computation within an OpenTelemetry span-withSpan ::-  (MonadIO m, MonadError LangchainError m) =>-  OTelTracer ->-  Text ->-  Maybe Text ->-  SpanKind ->-  Map Text Text ->-  m a ->-  m a-withSpan tracer name parentId kind attrs action = do-  sp <- startSpan tracer name parentId kind attrs-  res <--    action `catchError` \err -> do-      endSpan tracer (spanId sp) (StatusError (T.pack (show err)))-      throwError err-  endSpan tracer (spanId sp) StatusOk-  pure res---- | Export all recorded spans as JSON ByteString-exportSpansJson :: MonadIO m => OTelTracer -> m Text-exportSpansJson tracer = do-  spans <- getSpans tracer-  pure $ T.pack $ LBSC.unpack $ encode spans
− src/Langchain/OutputParser/Core.hs
@@ -1,241 +0,0 @@-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE GeneralisedNewtypeDeriving #-}-{-# LANGUAGE OverloadedStrings #-}--{- |-Module:      Langchain.OutputParser.Core-Copyright:   (c) 2026 Tushar Adhatrao-License:     MIT-Maintainer:  Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability:   experimental--This module provides the core types and instances for output parsers in the Langchain Haskell port.-Output parsers are used to transform the raw output from language models into structured data formats,-making it easier to work with the results in downstream applications.--The 'OutputParser' typeclass defines the interface for parsing model output into specific types,-and this module provides instances for common data structures such as booleans, lists, and JSON objects.--For more information on output parsers in the original Langchain library, see:-https://python.langchain.com/docs/concepts/output_parsers/--}-module Langchain.OutputParser.Core-  ( -- * Typeclass-    OutputParser (..)--    -- * Parsers-  , CommaSeparatedList (..)-  , JSONOutputStructure (..)-  , NumberSeparatedList (..)-  ) where--import Data.Aeson-import Data.ByteString.Lazy.Char8 (fromStrict)-import Data.Char (isDigit, isSpace)-import Data.Text (Text)-import qualified Data.Text as T-import Data.Text.Encoding (encodeUtf8)-import Data.Text.Internal.Search (indices)-import Langchain.Core.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 -> LangchainResult a---- | Represents a list of text items separated by commas.-newtype CommaSeparatedList = CommaSeparatedList [Text]-  deriving (Show, Eq)--instance OutputParser Bool where-  -- \| Parse a boolean value from the text.-  -- The text is considered 'True' if it contains the word "true" (case-insensitive),-  -- and 'False' if it contains "false". Otherwise, parsing fails.-  ---  -- === Examples-  ---  -- \* Parsing "true":-  ---  -- @-  -- parse "true" :: Either String Bool == Right True-  -- @-  ---  -- \* Parsing "False":-  ---  -- @-  -- parse "False" :: Either String Bool == Right False-  -- @-  ---  -- \* Parsing invalid input:-  ---  -- @-  -- parse "yes" :: Either String Bool == Left "Invalid boolean value"-  -- @-  parse txt = do-    let txt' = T.strip $ T.toLower txt-    if not (null $ indices "true" txt')-      then-        Right True-      else-        if not (null $ indices "false" txt')-          then-            Right False-          else-            Left $ parsingError "Invalid boolean value" (Just "Bool") (Just txt)--instance OutputParser CommaSeparatedList where-  -- \| Parse a comma-separated list from the text.-  -- The text is split by commas, and each part is stripped of leading/trailing whitespace.-  ---  -- === Examples-  ---  -- \* Parsing an empty string:-  ---  -- @-  -- parse "" :: Either String CommaSeparatedList == Right (CommaSeparatedList [""])-  -- @-  ---  -- \* Parsing a single item:-  ---  -- @-  -- parse "item" :: Either String CommaSeparatedList == Right (CommaSeparatedList ["item"])-  -- @-  ---  -- \* Parsing multiple items:-  ---  -- @-  -- parse "item1,item2,item3" :: Either String CommaSeparatedList == Right (CommaSeparatedList ["item1", "item2", "item3"])-  -- @-  parse txt = Right $ CommaSeparatedList $ map T.strip $ T.splitOn "," txt--{- | JSON parser wrapper-Requires 'FromJSON' instance for target type. Uses Aeson for parsing.--Example data type:--@-data Person = Person-  { name :: Text-  , age :: Int-  } deriving (Show, Eq, FromJSON)-@--Usage:-->>> parse "{\"name\": \"Bob\", \"age\": 25}" :: Either String (JSONOutputStructure Person)-Right (JSONOutputStructure {jsonValue = Person {name = "Bob", age = 25}})--}-newtype FromJSON a => JSONOutputStructure a = JSONOutputStructure-  { jsonValue :: a-  }-  deriving newtype (Show, Eq, FromJSON)---- | Instance for parsing JSON into any type that implements FromJSON.-instance FromJSON a => OutputParser (JSONOutputStructure a) where-  parse txt =-    case eitherDecode (fromStrict $ encodeUtf8 txt) of-      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".-newtype NumberSeparatedList = NumberSeparatedList [Text]-  deriving (Show, Eq)--instance OutputParser NumberSeparatedList where-  -- \| Parse a numbered list from the text.-  -- The input is expected to be a list of items prefixed with numbers followed by dots,-  -- such as "1. First item\n2. Second item". Whitespace is trimmed from each item.-  ---  -- === Examples-  ---  -- \* Parsing a simple numbered list:-  ---  -- @-  -- parse "1. First item\n2. Second item\n3. Third item" :: Either String NumberSeparatedList == Right (NumberSeparatedList ["First item", "Second item", "Third item"])-  -- @-  ---  -- \* Handling whitespace:-  ---  -- @-  -- parse "1.   First item  \n  2.  Second item\n3. Third item" :: Either String NumberSeparatedList == Right (NumberSeparatedList ["First item", "Second item", "Third item"])-  -- @-  ---  -- \* Handling multi-digit numbers:-  ---  -- @-  -- parse "10. First item\n11. Second item\n12. Third item" :: Either String NumberSeparatedList == Right (NumberSeparatedList ["First item", "Second item", "Third item"])-  -- @-  parse txt =-    let s = trim (T.unpack txt)-     in case dropUntilAndConsumeBoundary s of-          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--{- | Drops noise until we find a valid boundary marker (number with dot)-and then consumes the marker.--}-dropUntilAndConsumeBoundary :: String -> Maybe String-dropUntilAndConsumeBoundary s =-  case findBoundary s of-    Nothing -> Nothing-    Just (idx, n) -> Just (drop (idx + n) s)--{- | Scans the string for the next occurrence of a valid boundary.-Returns the index and length of the marker.--}-findBoundary :: String -> Maybe (Int, Int)-findBoundary = go 0-  where-    go _ [] = Nothing-    go i xs@(_ : rest) =-      case isBoundaryAt xs of-        Just n -> Just (i, n)-        Nothing -> go (i + 1) rest--{- | Checks if the given string starts with a valid boundary.-A valid boundary has one or more digits, optional spaces,-a dot, then optional spaces. Returns the number of characters-consumed by the boundary if matched.--}-isBoundaryAt :: String -> Maybe Int-isBoundaryAt s =-  let (digits, rest1) = span isDigit s-   in if null digits-        then Nothing-        else-          let (spaces, rest2) = span isSpace rest1-           in case rest2 of-                (c : rest3)-                  | c == '.' ->-                      let (spaces2, _) = span isSpace rest3-                       in Just-                            ( length digits-                                + length spaces-                                + 1-                                + length spaces2-                            )-                _ -> Nothing---- | Recursively splits the string into items using the boundary markers.-parseItems :: String -> [String]-parseItems s =-  case findBoundary s of-    Nothing -> [s] -- No further boundaries; the rest is one item.-    Just (idx, n) ->-      let item = take idx s-          rest = drop (idx + n) s-       in item : parseItems rest---- | A simple trim function to remove leading and trailing whitespace.-trim :: String -> String-trim = f . f-  where-    f = reverse . dropWhile isSpace
− src/Langchain/OutputParser/Structured.hs
@@ -1,348 +0,0 @@-{-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE DefaultSignatures #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE PolyKinds #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeOperators #-}--{- |-Module      : Langchain.OutputParser.Structured-Description : Type-safe structured output extraction using GHC Generics and JSON Schemas-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Generates JSON Schemas automatically from Haskell types using GHC Generics,-prompts the ChatModel for structured JSON output, and parses the response into typed values-with an automatic error-correction retry loop.--}-module Langchain.OutputParser.Structured-  ( StructuredOutput (..)-  , TypeSchema (..)-  , GRecordSchema (..)-  , genericJsonSchema-  , toOllamaSchema-  , fromOllamaSchema-  , structuredInvoke-  , structuredInvokeWithRetries-  , extractJsonFromMarkdown-  ) where--import Control.Monad.Except (MonadError, throwError)-import Control.Monad.IO.Class (MonadIO)-import Data.Aeson (FromJSON, Value (..), decode, encode, object, (.=))-import qualified Data.Aeson.Key as Key-import qualified Data.Aeson.KeyMap as KM-import qualified Data.ByteString.Lazy.Char8 as LBSC-import Data.Int (Int16, Int32, Int64, Int8)-import Data.Kind (Type)-import qualified Data.Map.Strict as Map-import Data.Proxy (Proxy (..))-import Data.Scientific (Scientific)-import Data.Text (Text)-import qualified Data.Text as TS-import qualified Data.Text.Encoding as TE-import Data.Time (Day, UTCTime)-import qualified Data.Vector as V-import Data.Word (Word16, Word32, Word64, Word8)-import GHC.Generics--import Langchain.Core.Error (LangchainError, parsingError)-import Langchain.Core.Model-  ( ChatModel (..)-  , Message (..)-  , extractMessageText-  , systemMessage-  , userMessage-  )-import qualified Ollama.Types.Format.SchemaBuilder as SB---- | Typeclass for types that declare a JSON Schema and structured parser-class (FromJSON a) => StructuredOutput a where-  outputSchema :: Proxy a -> Value-  default outputSchema :: (GRecordSchema (Rep a)) => Proxy a -> Value-  outputSchema _ = genericJsonSchema (Proxy :: Proxy a)---- | Generic JSON Schema derivation helper-genericJsonSchema :: forall a. (GRecordSchema (Rep a)) => Proxy a -> Value-genericJsonSchema _ =-  let (props, reqs) = gRecordSchema (Proxy :: Proxy (Rep a))-   in object-        [ "type" .= ("object" :: Text)-        , "properties" .= object props-        , "required" .= reqs-        ]--class GRecordSchema (f :: Type -> Type) where-  gRecordSchema :: Proxy f -> ([(Key.Key, Value)], [Text])--instance (GRecordSchema f, GRecordSchema g) => GRecordSchema (f :*: g) where-  gRecordSchema _ =-    let (p1, r1) = gRecordSchema (Proxy :: Proxy f)-        (p2, r2) = gRecordSchema (Proxy :: Proxy g)-     in (p1 ++ p2, r1 ++ r2)--instance (GRecordSchema f) => GRecordSchema (M1 D c f) where-  gRecordSchema _ = gRecordSchema (Proxy :: Proxy f)--instance (GRecordSchema f) => GRecordSchema (M1 C c f) where-  gRecordSchema _ = gRecordSchema (Proxy :: Proxy f)--instance (Selector s, TypeSchema a) => GRecordSchema (M1 S s (K1 R a)) where-  gRecordSchema _ =-    let selNameStr = selName (undefined :: M1 S s (K1 R a) p)-        propKey = Key.fromString selNameStr-        propSchema = typeJsonSchema (Proxy :: Proxy a)-        req = [TS.pack selNameStr | not (isOptionalType (Proxy :: Proxy a))]-     in ([(propKey, propSchema)], req)---- | Typeclass defining JSON Schema mapping for Haskell primitive and composite types-class TypeSchema a where-  typeJsonSchema :: Proxy a -> Value-  default typeJsonSchema :: (GRecordSchema (Rep a)) => Proxy a -> Value-  typeJsonSchema _ = genericJsonSchema (Proxy :: Proxy a)--  isOptionalType :: Proxy a -> Bool-  isOptionalType _ = False--instance (TypeSchema a) => TypeSchema (Maybe a) where-  typeJsonSchema _ = typeJsonSchema (Proxy :: Proxy a)-  isOptionalType _ = True--instance TypeSchema Text where-  typeJsonSchema _ = object ["type" .= ("string" :: Text)]--instance TypeSchema String where-  typeJsonSchema _ = object ["type" .= ("string" :: Text)]--instance TypeSchema Char where-  typeJsonSchema _ = object ["type" .= ("string" :: Text)]--instance TypeSchema Int where-  typeJsonSchema _ = object ["type" .= ("integer" :: Text)]--instance TypeSchema Int8 where-  typeJsonSchema _ = object ["type" .= ("integer" :: Text)]--instance TypeSchema Int16 where-  typeJsonSchema _ = object ["type" .= ("integer" :: Text)]--instance TypeSchema Int32 where-  typeJsonSchema _ = object ["type" .= ("integer" :: Text)]--instance TypeSchema Int64 where-  typeJsonSchema _ = object ["type" .= ("integer" :: Text)]--instance TypeSchema Integer where-  typeJsonSchema _ = object ["type" .= ("integer" :: Text)]--instance TypeSchema Word where-  typeJsonSchema _ = object ["type" .= ("integer" :: Text)]--instance TypeSchema Word8 where-  typeJsonSchema _ = object ["type" .= ("integer" :: Text)]--instance TypeSchema Word16 where-  typeJsonSchema _ = object ["type" .= ("integer" :: Text)]--instance TypeSchema Word32 where-  typeJsonSchema _ = object ["type" .= ("integer" :: Text)]--instance TypeSchema Word64 where-  typeJsonSchema _ = object ["type" .= ("integer" :: Text)]--instance TypeSchema Double where-  typeJsonSchema _ = object ["type" .= ("number" :: Text)]--instance TypeSchema Float where-  typeJsonSchema _ = object ["type" .= ("number" :: Text)]--instance TypeSchema Scientific where-  typeJsonSchema _ = object ["type" .= ("number" :: Text)]--instance TypeSchema Bool where-  typeJsonSchema _ = object ["type" .= ("boolean" :: Text)]--instance TypeSchema UTCTime where-  typeJsonSchema _ =-    object-      [ "type" .= ("string" :: Text)-      , "format" .= ("date-time" :: Text)-      ]--instance TypeSchema Day where-  typeJsonSchema _ =-    object-      [ "type" .= ("string" :: Text)-      , "format" .= ("date" :: Text)-      ]--instance TypeSchema Value where-  typeJsonSchema _ = object ["type" .= ("object" :: Text)]--instance (TypeSchema a) => TypeSchema (Map.Map Text a) where-  typeJsonSchema _ =-    object-      [ "type" .= ("object" :: Text)-      , "additionalProperties" .= typeJsonSchema (Proxy :: Proxy a)-      ]--instance {-# OVERLAPPABLE #-} (TypeSchema a) => TypeSchema [a] where-  typeJsonSchema _ =-    object-      [ "type" .= ("array" :: Text)-      , "items" .= typeJsonSchema (Proxy :: Proxy a)-      ]---- | Convert a Langchain JSON Schema Value into an ollama-haskell Schema-toOllamaSchema :: Value -> Maybe SB.Schema-toOllamaSchema (Object obj) = do-  propsVal <- KM.lookup "properties" obj-  propsMap <- case propsVal of-    Object pObj ->-      Just $-        Map.fromList-          [ (Key.toText k, SB.Property jt)-          | (k, v) <- KM.toList pObj-          , Just jt <- [valueToJsonType v]-          ]-    _ -> Nothing-  let reqs = case KM.lookup "required" obj of-        Just (Array arr) -> [t | String t <- V.toList arr]-        _ -> []-  pure $ SB.Schema propsMap reqs-  where-    valueToJsonType :: Value -> Maybe SB.JsonType-    valueToJsonType (Object vObj) = case KM.lookup "type" vObj of-      Just (String "string") -> Just SB.JString-      Just (String "integer") -> Just SB.JInteger-      Just (String "number") -> Just SB.JNumber-      Just (String "boolean") -> Just SB.JBoolean-      Just (String "null") -> Just SB.JNull-      Just (String "array") -> do-        itemVal <- KM.lookup "items" vObj-        itemType <- valueToJsonType itemVal-        pure $ SB.JArray itemType-      Just (String "object") -> do-        subSchema <- toOllamaSchema (Object vObj)-        pure $ SB.JObject subSchema-      _ -> Nothing-    valueToJsonType _ = Nothing-toOllamaSchema _ = Nothing---- | Convert an ollama-haskell Schema into a Langchain JSON Schema Value-fromOllamaSchema :: SB.Schema -> Value-fromOllamaSchema (SB.Schema props reqs) =-  object-    [ "type" .= ("object" :: Text)-    , "properties"-        .= object [Key.fromText k .= jsonTypeToValue jt | (k, SB.Property jt) <- Map.toList props]-    , "required" .= reqs-    ]-  where-    jsonTypeToValue :: SB.JsonType -> Value-    jsonTypeToValue SB.JString = object ["type" .= ("string" :: Text)]-    jsonTypeToValue SB.JInteger = object ["type" .= ("integer" :: Text)]-    jsonTypeToValue SB.JNumber = object ["type" .= ("number" :: Text)]-    jsonTypeToValue SB.JBoolean = object ["type" .= ("boolean" :: Text)]-    jsonTypeToValue SB.JNull = object ["type" .= ("null" :: Text)]-    jsonTypeToValue (SB.JArray jt) =-      object-        [ "type" .= ("array" :: Text)-        , "items" .= jsonTypeToValue jt-        ]-    jsonTypeToValue (SB.JObject subSchema) = fromOllamaSchema subSchema--{- | Invoke a 'ChatModel' and extract a typed 'StructuredOutput' value.--This function injects the JSON Schema into a system prompt and parses the LLM's response,-retrying up to 3 times with error feedback if parsing fails.--__Provider-Specific Grammar Enforcement:__-Note that 'structuredInvoke' relies on prompt-based instructions and schema validation across-generic 'ChatModel' instances. If you are using Ollama and want strict token-level schema-enforcement (where Ollama guarantees valid JSON conforming to the schema at generation time),-use 'withStructuredOutput' or set 'chatFormat' on 'ChatRequest' directly:--@-import Langchain.Provider.Ollama (ChatRequest(..), SchemaFormat(..))-let req = def { chatFormat = Just (SchemaFormat (toOllamaSchema (outputSchema (Proxy :: Proxy MyType)))) }-@--}-structuredInvoke ::-  forall a model m.-  (StructuredOutput a, ChatModel model, MonadIO m, MonadError LangchainError m) =>-  model ->-  [Message] ->-  m a-structuredInvoke model msgs = structuredInvokeWithRetries model msgs 3---- | Invoke a ChatModel with up to N retry iterations with error-correction feedback-structuredInvokeWithRetries ::-  forall a model m.-  (StructuredOutput a, ChatModel model, MonadIO m, MonadError LangchainError m) =>-  model ->-  [Message] ->-  Int ->-  m a-structuredInvokeWithRetries model baseMsgs maxAttempts = do-  let schema = outputSchema (Proxy :: Proxy a)-      schemaStr = TE.decodeUtf8 $ LBSC.toStrict $ encode schema-      systemInstruction =-        systemMessage-          ( "You are a structured data extractor. You must respond ONLY with a valid JSON object matching this JSON Schema:\n"-              <> schemaStr-              <> "\nDo NOT wrap the JSON in Markdown backticks or provide conversational text."-          )-      fullConversation = systemInstruction : baseMsgs-  go fullConversation maxAttempts-  where-    go conv attemptsLeft = do-      resp <- invoke model conv Nothing-      let rawText = extractMessageText resp-          cleanJson = extractJsonFromMarkdown rawText-          bs = LBSC.fromStrict (TE.encodeUtf8 cleanJson)-      case decode bs of-        Just parsedVal -> pure parsedVal-        Nothing ->-          if attemptsLeft <= 1-            then-              throwError $-                parsingError-                  ( "Failed to parse structured JSON output from LLM: "-                      <> rawText-                      <> " (Schema: "-                      <> TE.decodeUtf8 (LBSC.toStrict (encode (outputSchema (Proxy :: Proxy a))))-                      <> ")"-                  )-                  (Just "structuredInvoke")-                  Nothing-            else do-              let correctionMsg =-                    userMessage-                      ( "Your previous response was not valid JSON matching the schema. Error: failed to parse.\n"-                          <> "Please re-output ONLY valid JSON matching the schema."-                      )-                  updatedConv = conv ++ [resp, correctionMsg]-              go updatedConv (attemptsLeft - 1)---- | Robust helper to unwrap JSON from markdown ```json ``` blocks-extractJsonFromMarkdown :: Text -> Text-extractJsonFromMarkdown t =-  let stripped = TS.strip t-   in if "```json" `TS.isPrefixOf` stripped-        then-          let afterPrefix = TS.drop 7 stripped-           in case TS.breakOn "```" afterPrefix of-                (jsonPart, _) -> TS.strip jsonPart-        else-          if "```" `TS.isPrefixOf` stripped-            then-              let afterPrefix = TS.drop 3 stripped-               in case TS.breakOn "```" afterPrefix of-                    (jsonPart, _) -> TS.strip jsonPart-            else stripped
− src/Langchain/Prelude.hs
@@ -1,439 +0,0 @@-{-# LANGUAGE DuplicateRecordFields #-}-{-# LANGUAGE FlexibleContexts #-}--{- |-Module      : Langchain.Prelude-Description : Canonical umbrella re-export module for langchain-hs-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Exports all core data types, typeclasses, models, vector stores, memory stores,-graph orchestration primitives, advanced multi-agent patterns, guardrails, MCP client,-observability, structured logging, circuit breakers, pipeline DSLs, and runtime execution monads.--}-module Langchain.Prelude-  ( -- * Core Monad & Errors-    LangchainT-  , runLangchainT-  , throwLangchainError-  , LangchainError (..)-  , ErrorContext (..)-  , errorMessage-  , mkContext-  , mkContextIO-  , LangchainResult-  , llmError-  , parsingError-  , vectorStoreError-  , documentLoaderError-  , embeddingError-  , runnableError-  , toolError-  , agentError-  , memoryError-  , networkError-  , configurationError-  , validationError-  , internalError--    -- * Multi-Modal Models & Messages-  , ChatModel (..)-  , Message (..)-  , Role-  , ContentBlock (..)-  , ToolCall (..)-  , textMessage-  , userMessage-  , systemMessage-  , assistantMessage-  , toolMessage-  , imageMessage-  , extractMessageText-  , StreamEvent-    ( LLMStart-    , LLMChunk-    , LLMEnd-    , ToolStart-    , ToolEnd-    , ToolErrorEvent-    , ChainStart-    , ChainEnd-    , NodeStart-    , NodeEnd-    )-  , TokenUsage (..)-  , EventStream-  , collectEvents-  , printEvents--    -- * Pure AST Pipelines (RunnableTree)-  , RunnableTree (..)-  , (|>>)-  , (&>&)-  , interpret-  , runLambda-  , runPrim-  , runPure-  , runPassthrough-  , runIdent-  , runBranch-  , runFallback-  , runChat-  , runModel-  , runRetriever-  , ModelRunnable (..)-  , TextModelRunnable (..)--    -- * Effect-Polymorphic Tools-  , Tool (..)-  , createTool-  , toolToValue-  , ToolBinder (..)-  , DeriveToolSchema (..)-  , deriveToolParametersSchema-  , executeToolAsync-  , executeToolWithTimeout-  , executeToolBatchConcurrently--    -- * State Graphs & Multi-Agent-  , StateGraph (..)-  , Node (Node)-  , Edge (..)-  , NodeId-  , startNodeId-  , endNodeId-  , StateReducer-  , emptyStateGraph-  , addNode-  , addEdge-  , addConditionalEdge-  , compileGraph-  , runGraph-  , appendMessagesReducer-  , replaceFieldReducer-  , Checkpointer (..)-  , MemoryCheckpointer (..)-  , newMemoryCheckpointer-  , SQLiteCheckpointer (..)-  , newSQLiteCheckpointer-  , hitlNode-  , resumeGraph-  , supervisorNode-  , embedSubGraphNode-  , parallelNode-  , addParallelNodes--    -- * Advanced Agent Patterns-  , PlanStep (..)-  , Plan (..)-  , StepExecutor (..)-  , PlanAndExecuteAgent (..)-  , newPlanAndExecuteAgent-  , newPlanAndExecuteAgentWithTools-  , runPlanAndExecute--    -- * Guardrails & Safety-  , GuardrailResult (..)-  , Guardrail (..)-  , contentSafetyGuardrail-  , topicGuardrail-  , outputLengthGuardrail-  , composeGuardrails-  , withGuardrails--    -- * Model Context Protocol (MCP) Client-  , McpTransport (..)-  , McpToolInfo (..)-  , McpResource (..)-  , McpClient (..)-  , newStdioMcpClient-  , newHttpMcpClient-  , listMcpTools-  , callMcpTool-  , mcpToolToLangchainTool--    -- * Telemetry, Logging & OpenTelemetry-  , LogLevel (..)-  , LogEvent (..)-  , Logger (..)-  , InMemoryLogger (..)-  , newInMemoryLogger-  , getInMemoryLogs-  , stderrLogger-  , logEvent-  , logDebug-  , logInfo-  , logWarn-  , logError-  , SpanKind (..)-  , SpanStatus (..)-  , Span (..)-  , OTelTracer (..)-  , newOTelTracer-  , getSpans-  , startSpan-  , endSpan-  , addSpanAttribute-  , withSpan-  , exportSpansJson--    -- * Callbacks-  , CallbackEvent (..)-  , CallbackHandler (..)-  , CallbackManager (..)-  , newCallbackManager-  , registerHandler-  , dispatchEvent-  , dispatchEventAsync-  , newLoggingCallbackHandler-  , getCallbackLogs--    -- * Resilience-  , CircuitState (..)-  , CircuitBreakerConfig (..)-  , defaultCircuitConfig-  , CircuitBreaker (..)-  , newCircuitBreaker-  , getCircuitState-  , withCircuitBreaker--    -- * Memory Systems-  , BaseMemory (..)-  , WindowBufferMemory (..)-  , newWindowBufferMemory-  , TokenBufferMemory (..)-  , newTokenBufferMemory-  , countTokens-  , SummaryMemory (..)-  , newSummaryMemory-  , EntityMemory (..)-  , newEntityMemory-  , initialMessages-  , trimMessages--    -- * Vector Stores & Retrieval-  , VectorStore (..)-  , InMemory (..)-  , emptyInMemoryVectorStore-  , fromDocuments-  , SqliteVecStore (..)-  , newSqliteVecStore-  , Retriever (..)-  , VectorStoreRetriever (..)-  , retrieveWithCallbacks--    -- * Embeddings-  , Embeddings (..)-  , OllamaEmbeddings (..)-  , OpenAIEmbeddings (OpenAIEmbeddings)-  , defaultOpenAIEmbeddings-  , textEmbedding3Small-  , textEmbedding3Large-  , textEmbeddingAda--    -- * Document Loaders-  , Document (..)-  , BaseLoader (..)-  , FileLoader (..)-  , DirectoryLoader (..)-  , DirectoryLoaderOptions (..)-  , defaultDirectoryLoaderOptions-  , CsvLoader (..)-  , defaultCsvLoader--    -- * Prompt Templates-  , PromptTemplate (..)-  , PromptTemplateOptions (..)-  , TemplateFormat (..)-  , defaultPromptTemplateOptions-  , fromTemplate-  , fromTemplateWithOptions-  , fromTemplateWithFormat-  , partialPromptTemplate-  , FewShotPromptTemplate (..)-  , renderPrompt-  , renderFewShotPrompt--    -- * Text Splitters-  , CharacterSplitterOps (CharacterSplitterOps)-  , defaultCharacterSplitterOps-  , splitText-  , RecursiveCharacterSplitterOps (RecursiveCharacterSplitterOps)-  , defaultRecursiveCharacterSplitterOps-  , splitTextRecursive-  , MarkdownSplitterOps (MarkdownSplitterOps)-  , defaultMarkdownSplitterOps-  , splitMarkdown-  , splitMarkdownToChunks-  , TokenSplitterOps (TokenSplitterOps)-  , defaultTokenSplitterOps-  , splitByTokens-  , Language (..)-  , CodeSplitterOps (CodeSplitterOps)-  , splitCode--    -- * Caching & Resilience-  , CacheBackend (..)-  , InMemoryCache (..)-  , newInMemoryCache-  , SQLiteCache (..)-  , newSQLiteCache-  , CachedModel (..)-  , withCaching-  , RetryPolicy (RetryPolicy)-  , defaultRetryPolicy-  , withRetry-  , RateLimiter (..)-  , newRateLimiter-  , withRateLimit--    -- * Chains-  , RetrievalQA (RetrievalQA)-  , newRetrievalQA-  , runRetrievalQA-  , MapReduceChain (..)-  , newMapReduceChain-  , runMapReduceChain--    -- * Structured Output & Parsers-  , OutputParser (..)-  , CommaSeparatedList (..)-  , JSONOutputStructure (..)-  , NumberSeparatedList (..)-  , StructuredOutput (..)-  , TypeSchema (..)-  , toOllamaSchema-  , fromOllamaSchema-  , structuredInvoke-  , structuredInvokeWithRetries-  , withJsonFormat-  , withSchemaFormat-  , withStructuredOutput--    -- * Agents & Execution-  , ReActAgent (ReActAgent)-  , AgentStep (..)-  , createReActAgent-  , reactStep-  , runReActAgent--    -- * Standard Tools-  , shellTool--    -- * Hybrid Retrieval & BM25-  , BM25Index (..)-  , newBM25Index-  , newBM25IndexWithParams-  , addDocumentsBM25-  , bm25Search-  , bm25SearchWithScores-  , HybridRetriever (..)-  , newHybridRetriever-  , newHybridRetrieverWithWeights-  , searchHybrid-  , searchHybridWithScores-  , reciprocalRankFusion--    -- * Providers-  , Ollama (..)-  , OllamaClientConfig (..)-  , defaultConfig-  , newOllama-  , newOllamaWithClient-  , ModelOptions (..)-  , defaultOptions-  , withOptions-  , chatRequestFor-  , resolveChatRequest-  , withTools-  , toOllamaTool-  , toOllamaTools-  , OllamaWithTools (..)-  , bindTools-  , OpenAI-  , newOpenAI-  , Gemini-  , newGemini-  ) where--import Langchain.Agent.PlanAndExecute-import Langchain.Agent.ReAct-import Langchain.Cache.Core-import Langchain.Callback.Manager-import Langchain.Chain.MapReduce-import Langchain.Chain.RetrievalQA-import Langchain.Core.Error-import Langchain.Core.Model-import Langchain.Core.Monad-import Langchain.Core.Runnable hiding (invoke)-import Langchain.Core.Stream-import Langchain.Core.Tool-import Langchain.DocumentLoader.Core-import Langchain.DocumentLoader.Csv-import Langchain.DocumentLoader.DirectoryLoader-import Langchain.DocumentLoader.FileLoader-import Langchain.Embeddings.Core-import Langchain.Embeddings.Ollama (OllamaEmbeddings (..))-import Langchain.Embeddings.OpenAI-  ( OpenAIEmbeddings (OpenAIEmbeddings)-  , defaultOpenAIEmbeddings-  , textEmbedding3Large-  , textEmbedding3Small-  , textEmbeddingAda-  )-import Langchain.Graph.Checkpointer-import Langchain.Graph.HITL-import Langchain.Graph.MultiAgent-import Langchain.Graph.Parallel-import Langchain.Graph.StateGraph-import Langchain.Guardrail.Core-import Langchain.MCP.Client-import Langchain.Memory.Core-import Langchain.Memory.Entity-import Langchain.Memory.Summary-import Langchain.Observability-import Langchain.OutputParser.Core-import Langchain.OutputParser.Structured-import Langchain.PromptTemplate.FewShot-import Langchain.PromptTemplate.Prompt-import Langchain.Provider.Gemini (Gemini, newGemini)-import Langchain.Provider.Ollama-  ( ModelOptions (..)-  , Ollama (..)-  , OllamaClientConfig (..)-  , OllamaWithTools (..)-  , bindTools-  , chatRequestFor-  , defaultConfig-  , defaultOptions-  , newOllama-  , newOllamaWithClient-  , resolveChatRequest-  , toOllamaTool-  , toOllamaTools-  , withJsonFormat-  , withOptions-  , withSchemaFormat-  , withStructuredOutput-  , withTools-  )-import Langchain.Provider.OpenAI (OpenAI, newOpenAI)-import Langchain.Resilience.CircuitBreaker-import Langchain.Resilience.Retry-import Langchain.Retriever.BM25-import Langchain.Retriever.Core-import Langchain.Retriever.Hybrid-import Langchain.TextSplitter.Character-import Langchain.TextSplitter.Code-import Langchain.TextSplitter.Markdown-import Langchain.TextSplitter.RecursiveCharacter-import Langchain.TextSplitter.Token-import Langchain.Tool.Async-import Langchain.Tool.Binding-import Langchain.Tool.GenericSchema-import Langchain.Tool.Shell (shellTool)-import Langchain.VectorStore.Core-import Langchain.VectorStore.InMemory-import Langchain.VectorStore.SqliteVec
− src/Langchain/PromptTemplate/Chat.hs
@@ -1,25 +0,0 @@-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}--{- |-Module      : Langchain.PromptTemplate.Chat-Description : Chat prompt template primitives-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Minimal chat prompt primitives ported from LangChain Python chat prompts.--}-module Langchain.PromptTemplate.Chat-  ( BaseMessagePromptTemplate (..)-  , extractTemplateVariables-  ) where--import Langchain.Core.Error (LangchainError)-import Langchain.Core.Model.Types (Message)-import Langchain.PromptTemplate.Prompt (extractTemplateVariables)---- | Base class for message prompt templates.-class BaseMessagePromptTemplate template input where-  formatMessages :: template -> input -> Either LangchainError [Message]
− src/Langchain/PromptTemplate/Chat/ChatPromptTemplate.hs
@@ -1,451 +0,0 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DuplicateRecordFields #-}-{-# LANGUAGE OverloadedStrings #-}--{- |-Module      : Langchain.PromptTemplate.Chat.ChatPromptTemplate-Description : ChatPromptTemplate prompt template-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--}-module Langchain.PromptTemplate.Chat.ChatPromptTemplate-  ( ChatPromptTemplate (..)-  , ChatPromptMessage-  , ContentPromptBlock (..)-  , ChatPromptInput (..)-  , ChatPromptValue (..)-  , PartialValue (..)-  , fromTemplate-  , fromTemplateWithOptions-  , fromMessages-  , message-  , templateMessage-  , templateMessageWithFormat-  , contentMessage-  , messagesPlaceholder-  , messagesPlaceholderWithOptions-  , append-  , extend-  , partial-  , invoke-  , formatPrompt-  , format-  , toMessages-  , toString-  ) where--import Data.Aeson (FromJSON (..), ToJSON (..), Value (..), object, withObject, (.:), (.:?), (.=))-import Data.Aeson.Types (Parser)-import Data.Either (fromRight)-import qualified Data.List.NonEmpty as NonEmpty-import qualified Data.Map.Strict as Map-import Data.Maybe (isJust)-import Data.Text (Text)-import qualified Data.Text as T-import GHC.Generics (Generic)--import Langchain.Core.Error (LangchainError, validationError)-import Langchain.Core.Model.Types-  ( ContentBlock (..)-  , ImageContent (..)-  , ImageSource (..)-  , Message (..)-  , Role (..)-  , formatMessageString-  , textMessage-  , userMessage-  )-import Langchain.PromptTemplate.Chat (BaseMessagePromptTemplate (formatMessages))-import Langchain.PromptTemplate.Chat.MessagesPlaceholder-  ( MessagesPlaceholder (..)-  , messagesPlaceholderVariableName-  )-import qualified Langchain.PromptTemplate.Chat.MessagesPlaceholder as MessagesPlaceholder-import Langchain.PromptTemplate.Prompt (PromptTemplateOptions, TemplateFormat (..))-import qualified Langchain.PromptTemplate.Prompt as Prompt---- | A single chat message template inside a chat prompt.-data ChatPromptMessage-  = HumanMessagePrompt Prompt.PromptTemplate-  | SystemMessagePrompt Prompt.PromptTemplate-  | AIMessagePrompt Prompt.PromptTemplate-  | ChatMessagePrompt Role Prompt.PromptTemplate-  | ContentMessagePrompt Role [ContentPromptBlock]-  | MessagesPlaceholderPrompt MessagesPlaceholder (Maybe [Message])-  | StaticMessage Message-  deriving (Show, Eq, Generic, ToJSON, FromJSON)---- | A templated block inside a multipart chat message.-data ContentPromptBlock-  = TextPromptBlock TemplateFormat Text-  | ImagePromptBlock TemplateFormat ImageContent-  deriving (Show, Eq, Generic)--instance ToJSON ContentPromptBlock where-  toJSON (TextPromptBlock templateFormat template) =-    object-      [ "type" .= ("text_prompt" :: Text)-      , "templateFormat" .= templateFormat-      , "template" .= template-      ]-  toJSON (ImagePromptBlock templateFormat imageContent) =-    object-      [ "type" .= ("image_prompt" :: Text)-      , "templateFormat" .= templateFormat-      , "imageContent" .= imageContentToJSON imageContent-      ]--instance FromJSON ContentPromptBlock where-  parseJSON = withObject "ContentPromptBlock" $ \value -> do-    blockType <- value .: "type"-    case (blockType :: Text) of-      "text_prompt" -> TextPromptBlock <$> value .: "templateFormat" <*> value .: "template"-      "image_prompt" ->-        ImagePromptBlock <$> value .: "templateFormat" <*> (value .: "imageContent" >>= parseImageContent)-      other -> fail $ "Unknown ContentPromptBlock type: " ++ show other--imageContentToJSON :: ImageContent -> Value-imageContentToJSON ImageContent {imageSource = source, imageDetail = detail, imageMetadata = metadata} =-  object-    [ "source" .= imageSourceToJSON source-    , "detail" .= detail-    , "metadata" .= metadata-    ]--imageSourceToJSON :: ImageSource -> Value-imageSourceToJSON (ImageBase64 mime sourceData) =-  object-    [ "type" .= ("base64" :: Text)-    , "mimeType" .= mime-    , "data" .= sourceData-    ]-imageSourceToJSON (ImageUrl url) =-  object-    [ "type" .= ("url" :: Text)-    , "url" .= url-    ]--parseImageContent :: Value -> Parser ImageContent-parseImageContent = withObject "ImageContent" $ \value ->-  ImageContent-    <$> (value .: "source" >>= parseImageSource)-    <*> value .:? "detail"-    <*> value .:? "metadata"--parseImageSource :: Value -> Parser ImageSource-parseImageSource = withObject "ImageSource" $ \value -> do-  sourceType <- value .: "type"-  case (sourceType :: Text) of-    "base64" -> ImageBase64 <$> value .:? "mimeType" <*> value .: "data"-    "url" -> ImageUrl <$> value .: "url"-    other -> fail $ "Unknown ImageSource type: " ++ show other---- | A chat prompt template made of ordered message templates.-data ChatPromptTemplate = ChatPromptTemplate-  { messages :: [ChatPromptMessage]-  , inputVariables :: [Text]-  }-  deriving (Show, Eq, Generic, ToJSON, FromJSON)---- | A rendered chat prompt as concrete messages.-newtype ChatPromptValue = ChatPromptValue-  { messages :: [Message]-  }-  deriving (Show, Eq, Generic, ToJSON, FromJSON)---- | Inputs accepted by 'invoke' for chat prompts.-data ChatPromptInput-  = ChatPromptVariables (Map.Map Text Text)-  | ChatPromptMessageList [Message]-  | ChatPromptInputs (Map.Map Text Text) (Map.Map Text [Message])-  deriving (Show, Eq, Generic, ToJSON, FromJSON)---- | Partial values that can pre-bind text or message placeholders.-data PartialValue-  = PartialText Text-  | PartialMessages [Message]-  deriving (Show, Eq, Generic, ToJSON, FromJSON)---- | Create a single-message user chat prompt from raw text.-fromTemplate :: Text -> ChatPromptTemplate-fromTemplate template = fromTemplateWithOptions template Prompt.defaultPromptTemplateOptions---- | Create a single-message user chat prompt with partial variables.-fromTemplateWithOptions :: Text -> PromptTemplateOptions -> ChatPromptTemplate-fromTemplateWithOptions template options =-  let promptTemplate = Prompt.fromTemplateWithOptions template options-   in ChatPromptTemplate-        { messages = [ChatMessagePrompt User promptTemplate]-        , inputVariables = Prompt.inputVariables promptTemplate-        }---- | Create a chat prompt from an explicit list of message templates.-fromMessages :: [ChatPromptMessage] -> ChatPromptTemplate-fromMessages promptMessages =-  ChatPromptTemplate-    { messages = promptMessages-    , inputVariables = unique $ concatMap messageInputVariables promptMessages-    }---- | Wrap a concrete message as part of a chat prompt.-message :: Message -> ChatPromptMessage-message = StaticMessage---- | Create a templated message for a specific role.-templateMessage :: Role -> Text -> ChatPromptMessage-templateMessage role = ChatMessagePrompt role . Prompt.fromTemplate---- | Create a templated message for a specific role and template format.-templateMessageWithFormat :: Role -> TemplateFormat -> Text -> ChatPromptMessage-templateMessageWithFormat role templateFormat template =-  ChatMessagePrompt role $-    Prompt.fromTemplateWithFormat template templateFormat Map.empty---- | Create a multipart content message for a specific role.-contentMessage :: Role -> [ContentPromptBlock] -> ChatPromptMessage-contentMessage = ContentMessagePrompt---- | Create a placeholder for an injected message list.-messagesPlaceholder :: Text -> ChatPromptMessage-messagesPlaceholder name = messagesPlaceholderWithOptions $ MessagesPlaceholder.messagesPlaceholderOptions name---- | Create a message-list placeholder with explicit options.-messagesPlaceholderWithOptions ::-  MessagesPlaceholder.MessagesPlaceholderOptions -> ChatPromptMessage-messagesPlaceholderWithOptions options =-  MessagesPlaceholderPrompt (MessagesPlaceholder.messagesPlaceholderWithOptions options) Nothing---- | Append one message template to the end of a chat prompt.-append :: ChatPromptTemplate -> ChatPromptMessage -> ChatPromptTemplate-append chatPromptTemplate promptMessage = extend chatPromptTemplate [promptMessage]---- | Append multiple message templates to the end of a chat prompt.-extend :: ChatPromptTemplate -> [ChatPromptMessage] -> ChatPromptTemplate-extend ChatPromptTemplate {messages = promptMessages} newMessages =-  fromMessages $ promptMessages <> newMessages---- | Apply partial text and message bindings to a chat prompt.-partial :: ChatPromptTemplate -> Map.Map Text PartialValue -> ChatPromptTemplate-partial ChatPromptTemplate {messages = promptMessages} partialVariables =-  fromMessages $ map (`partialMessage` partialVariables) promptMessages---- | Render a chat prompt to concrete messages.-formatPrompt :: ChatPromptTemplate -> Map.Map Text Text -> Either LangchainError ChatPromptValue-formatPrompt ChatPromptTemplate {messages = promptMessages} variables =-  formatPromptWithMessages promptMessages variables Map.empty---- | Render a chat prompt with either variables or message-list inputs.-invoke :: ChatPromptTemplate -> ChatPromptInput -> Either LangchainError ChatPromptValue-invoke chatPromptTemplate (ChatPromptVariables variables) = formatPrompt chatPromptTemplate variables-invoke ChatPromptTemplate {messages = [MessagesPlaceholderPrompt placeholder _]} (ChatPromptMessageList promptMessages) =-  ChatPromptValue-    <$> formatMessages-      placeholder-      (Map.singleton (messagesPlaceholderVariableName placeholder) promptMessages)-invoke ChatPromptTemplate {messages = promptMessages} (ChatPromptInputs variables messageVariables) =-  formatPromptWithMessages promptMessages variables messageVariables-invoke _ (ChatPromptMessageList _) =-  Left $-    validationError-      "List input is only supported for a single MessagesPlaceholder"-      (Just "ChatPromptTemplate")-      (Just "invoke")---- | Render a chat prompt to a single formatted text value.-format :: ChatPromptTemplate -> Map.Map Text Text -> Either LangchainError Text-format chatPromptTemplate variables = toString <$> formatPrompt chatPromptTemplate variables---- | Extract the concrete messages from a rendered chat prompt.-toMessages :: ChatPromptValue -> [Message]-toMessages (ChatPromptValue promptMessages) = promptMessages---- | Render a chat prompt as newline-separated message text.-toString :: ChatPromptValue -> Text-toString (ChatPromptValue promptMessages) =-  T.intercalate "\n" $ map formatMessageString promptMessages--messageInputVariables :: ChatPromptMessage -> [Text]-messageInputVariables (HumanMessagePrompt promptTemplate) = Prompt.inputVariables promptTemplate-messageInputVariables (SystemMessagePrompt promptTemplate) = Prompt.inputVariables promptTemplate-messageInputVariables (AIMessagePrompt promptTemplate) = Prompt.inputVariables promptTemplate-messageInputVariables (ChatMessagePrompt _ promptTemplate) = Prompt.inputVariables promptTemplate-messageInputVariables (ContentMessagePrompt _ blocks) = unique $ concatMap contentBlockInputVariables blocks-messageInputVariables-  ( MessagesPlaceholderPrompt-      MessagesPlaceholder-        { variableName = variableName'-        , optional = optional'-        }-      storedMessages-    )-    | optional' || isJust storedMessages = []-    | otherwise = [variableName']-messageInputVariables (StaticMessage _) = []--partialMessage :: ChatPromptMessage -> Map.Map Text PartialValue -> ChatPromptMessage-partialMessage (HumanMessagePrompt promptTemplate) partialVariables =-  HumanMessagePrompt $-    Prompt.partialPromptTemplate promptTemplate (textPartialVariables partialVariables)-partialMessage (SystemMessagePrompt promptTemplate) partialVariables =-  SystemMessagePrompt $-    Prompt.partialPromptTemplate promptTemplate (textPartialVariables partialVariables)-partialMessage (AIMessagePrompt promptTemplate) partialVariables =-  AIMessagePrompt $-    Prompt.partialPromptTemplate promptTemplate (textPartialVariables partialVariables)-partialMessage (ChatMessagePrompt role promptTemplate) partialVariables =-  ChatMessagePrompt role $-    Prompt.partialPromptTemplate promptTemplate (textPartialVariables partialVariables)-partialMessage (ContentMessagePrompt role blocks) partialVariables =-  ContentMessagePrompt role $-    map (\block -> partialContentBlock block (textPartialVariables partialVariables)) blocks-partialMessage (MessagesPlaceholderPrompt placeholder storedMessages) partialVariables =-  MessagesPlaceholderPrompt placeholder $-    case Map.lookup (messagesPlaceholderVariableName placeholder) partialVariables of-      Just (PartialMessages promptMessages) -> Just promptMessages-      _ -> storedMessages-partialMessage (StaticMessage staticMessage) _ = StaticMessage staticMessage--textPartialVariables :: Map.Map Text PartialValue -> Map.Map Text Text-textPartialVariables = Map.mapMaybe toText-  where-    toText :: PartialValue -> Maybe Text-    toText (PartialText value) = Just value-    toText (PartialMessages _) = Nothing--formatPromptWithMessages ::-  [ChatPromptMessage] ->-  Map.Map Text Text ->-  Map.Map Text [Message] ->-  Either LangchainError ChatPromptValue-formatPromptWithMessages promptMessages variables messageVariables =-  ChatPromptValue . concat-    <$> traverse (\promptMessage -> formatMessage promptMessage variables messageVariables) promptMessages--formatMessage ::-  ChatPromptMessage -> Map.Map Text Text -> Map.Map Text [Message] -> Either LangchainError [Message]-formatMessage (HumanMessagePrompt promptTemplate) variables _ =-  (: []) . userMessage <$> Prompt.renderPrompt promptTemplate variables-formatMessage (SystemMessagePrompt promptTemplate) variables _ =-  (: []) . textMessage System <$> Prompt.renderPrompt promptTemplate variables-formatMessage (AIMessagePrompt promptTemplate) variables _ =-  (: []) . textMessage Assistant <$> Prompt.renderPrompt promptTemplate variables-formatMessage (ChatMessagePrompt role promptTemplate) variables _ =-  (: []) . textMessage role <$> Prompt.renderPrompt promptTemplate variables-formatMessage (ContentMessagePrompt role blocks) variables _ = do-  renderedBlocks <- concat <$> traverse (renderContentBlock variables) blocks-  case NonEmpty.nonEmpty renderedBlocks of-    Nothing -> Right []-    Just nonEmptyBlocks -> Right [Message role nonEmptyBlocks Nothing Nothing Nothing Map.empty]-formatMessage (MessagesPlaceholderPrompt placeholder storedMessages) _ messageVariables =-  formatMessages placeholder $-    case storedMessages of-      Nothing -> messageVariables-      Just promptMessages ->-        messageVariables-          `Map.union` Map.singleton (messagesPlaceholderVariableName placeholder) promptMessages-formatMessage (StaticMessage staticMessage) _ _ = Right [staticMessage]--contentBlockInputVariables :: ContentPromptBlock -> [Text]-contentBlockInputVariables (TextPromptBlock templateFormat template) =-  Prompt.extractTemplateVariablesWithFormat templateFormat template-contentBlockInputVariables (ImagePromptBlock templateFormat imageContent) =-  imageContentInputVariables templateFormat imageContent--partialContentBlock :: ContentPromptBlock -> Map.Map Text Text -> ContentPromptBlock-partialContentBlock (TextPromptBlock templateFormat template) partials =-  TextPromptBlock templateFormat $ renderPartial templateFormat partials template-partialContentBlock (ImagePromptBlock templateFormat imageContent) partials =-  ImagePromptBlock templateFormat $ partialImageContent templateFormat partials imageContent--renderContentBlock ::-  Map.Map Text Text -> ContentPromptBlock -> Either LangchainError [ContentBlock]-renderContentBlock variables (TextPromptBlock templateFormat template) = do-  rendered <- renderTemplate templateFormat variables template-  pure [TextBlock rendered | not (T.null rendered)]-renderContentBlock variables (ImagePromptBlock templateFormat imageContent) = do-  renderedImage <- renderImageContent templateFormat variables imageContent-  pure [ImageBlock renderedImage]--imageContentInputVariables :: TemplateFormat -> ImageContent -> [Text]-imageContentInputVariables templateFormat ImageContent {imageSource = source, imageDetail = detail, imageMetadata = metadata} =-  imageSourceInputVariables templateFormat source-    <> maybe [] (Prompt.extractTemplateVariablesWithFormat templateFormat) detail-    <> maybe [] (valueInputVariables templateFormat) metadata--imageSourceInputVariables :: TemplateFormat -> ImageSource -> [Text]-imageSourceInputVariables templateFormat (ImageBase64 _ imageTemplate) =-  Prompt.extractTemplateVariablesWithFormat templateFormat imageTemplate-imageSourceInputVariables templateFormat (ImageUrl url) =-  Prompt.extractTemplateVariablesWithFormat templateFormat url--partialImageContent :: TemplateFormat -> Map.Map Text Text -> ImageContent -> ImageContent-partialImageContent templateFormat partials ImageContent {imageSource = source, imageDetail = detail, imageMetadata = metadata} =-  ImageContent-    { imageSource = partialImageSource templateFormat partials source-    , imageDetail = renderPartial templateFormat partials <$> detail-    , imageMetadata = renderPartialValue templateFormat partials <$> metadata-    }--partialImageSource :: TemplateFormat -> Map.Map Text Text -> ImageSource -> ImageSource-partialImageSource templateFormat partials (ImageBase64 mime imageTemplate) =-  ImageBase64 mime $ renderPartial templateFormat partials imageTemplate-partialImageSource templateFormat partials (ImageUrl url) =-  ImageUrl $ renderPartial templateFormat partials url--renderImageContent ::-  TemplateFormat -> Map.Map Text Text -> ImageContent -> Either LangchainError ImageContent-renderImageContent templateFormat variables ImageContent {imageSource = source, imageDetail = detail, imageMetadata = metadata} = do-  renderedSource <- renderImageSource templateFormat variables source-  renderedDetail <- traverse (renderTemplate templateFormat variables) detail-  renderedMetadata <- traverse (renderValue templateFormat variables) metadata-  pure $ ImageContent renderedSource renderedDetail renderedMetadata--renderImageSource ::-  TemplateFormat -> Map.Map Text Text -> ImageSource -> Either LangchainError ImageSource-renderImageSource templateFormat variables (ImageBase64 mime imageTemplate) =-  ImageBase64 mime <$> renderTemplate templateFormat variables imageTemplate-renderImageSource templateFormat variables (ImageUrl url) =-  ImageUrl <$> renderTemplate templateFormat variables url--renderTemplate :: TemplateFormat -> Map.Map Text Text -> Text -> Either LangchainError Text-renderTemplate templateFormat variables template =-  Prompt.renderPrompt-    (Prompt.fromTemplateWithFormat template templateFormat Map.empty)-    variables--renderPartial :: TemplateFormat -> Map.Map Text Text -> Text -> Text-renderPartial templateFormat partials template =-  fromRight template $ renderTemplate templateFormat partials template--valueInputVariables :: TemplateFormat -> Value -> [Text]-valueInputVariables templateFormat (String value) =-  Prompt.extractTemplateVariablesWithFormat templateFormat value-valueInputVariables templateFormat (Array values) =-  concatMap (valueInputVariables templateFormat) values-valueInputVariables templateFormat (Object objectValue) =-  concatMap (valueInputVariables templateFormat) objectValue-valueInputVariables _ _ = []--renderValue :: TemplateFormat -> Map.Map Text Text -> Value -> Either LangchainError Value-renderValue templateFormat variables (String value) =-  String <$> renderTemplate templateFormat variables value-renderValue templateFormat variables (Array values) =-  Array <$> traverse (renderValue templateFormat variables) values-renderValue templateFormat variables (Object objectValue) =-  Object <$> traverse (renderValue templateFormat variables) objectValue-renderValue _ _ value = Right value--renderPartialValue :: TemplateFormat -> Map.Map Text Text -> Value -> Value-renderPartialValue templateFormat partials value =-  fromRight value $ renderValue templateFormat partials value--unique :: [Text] -> [Text]-unique = foldl addIfMissing []-  where-    addIfMissing :: [Text] -> Text -> [Text]-    addIfMissing variableNames name-      | name `elem` variableNames = variableNames-      | otherwise = variableNames <> [name]
− src/Langchain/PromptTemplate/Chat/MessagesPlaceholder.hs
@@ -1,98 +0,0 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DuplicateRecordFields #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedStrings #-}--{- |-Module      : Langchain.PromptTemplate.Chat.MessagesPlaceholder-Description : MessagesPlaceholder prompt template-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--}-module Langchain.PromptTemplate.Chat.MessagesPlaceholder-  ( MessagesPlaceholder (..)-  , MessagesPlaceholderOptions (..)-  , messagesPlaceholder-  , messagesPlaceholderOptions-  , messagesPlaceholderWithOptions-  , messagesPlaceholderVariableName-  ) where--import Data.Aeson (FromJSON, ToJSON)-import qualified Data.Map.Strict as Map-import Data.Text (Text)-import GHC.Generics (Generic)--import Langchain.Core.Error (validationError)-import Langchain.Core.Model.Types (Message)-import Langchain.PromptTemplate.Chat (BaseMessagePromptTemplate (..))---- | Prompt template that expects one variable to contain an existing message list.-data MessagesPlaceholder = MessagesPlaceholder-  { variableName :: Text-  , optional :: Bool-  , nMessages :: Maybe Int-  }-  deriving (Show, Eq, Generic, ToJSON, FromJSON)--data MessagesPlaceholderOptions = MessagesPlaceholderOptions-  { variableName :: Text-  , optional :: Bool-  , nMessages :: Maybe Int-  }-  deriving (Show, Eq, Generic, ToJSON, FromJSON)--instance BaseMessagePromptTemplate MessagesPlaceholder (Map.Map Text [Message]) where-  formatMessages-    MessagesPlaceholder-      { variableName = variableName'-      , optional = optional'-      , nMessages = nMessages'-      }-    inputs = do-      values <--        case Map.lookup variableName' inputs of-          Just values' -> Right values'-          Nothing-            | optional' -> Right []-            | otherwise ->-                Left $-                  validationError-                    ("Missing variable: " <> variableName')-                    (Just variableName')-                    Nothing-      case nMessages' of-        Just limit-          | limit <= 0 ->-              Left $ validationError "n_messages must be positive" (Just variableName') Nothing-        _ -> pure $ maybe values (`takeLast` values) nMessages'---- | Create a required messages placeholder.-messagesPlaceholder :: Text -> MessagesPlaceholder-messagesPlaceholder name = messagesPlaceholderWithOptions $ messagesPlaceholderOptions name--messagesPlaceholderOptions :: Text -> MessagesPlaceholderOptions-messagesPlaceholderOptions name =-  MessagesPlaceholderOptions-    { variableName = name-    , optional = False-    , nMessages = Nothing-    }--messagesPlaceholderWithOptions :: MessagesPlaceholderOptions -> MessagesPlaceholder-messagesPlaceholderWithOptions MessagesPlaceholderOptions {variableName = name, optional = optional', nMessages = nMessages'} =-  MessagesPlaceholder-    { variableName = name-    , optional = optional'-    , nMessages = nMessages'-    }--messagesPlaceholderVariableName :: MessagesPlaceholder -> Text-messagesPlaceholderVariableName MessagesPlaceholder {variableName = name} = name--takeLast :: Int -> [a] -> [a]-takeLast n values = drop (max 0 (length values - n)) values
− src/Langchain/PromptTemplate/FewShot.hs
@@ -1,47 +0,0 @@-{-# LANGUAGE DuplicateRecordFields #-}-{-# LANGUAGE RecordWildCards #-}--{- |-Module      : Langchain.PromptTemplate.FewShot-Description : Few-shot prompt templates-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--}-module Langchain.PromptTemplate.FewShot-  ( FewShotPromptTemplate (..)-  , renderFewShotPrompt-  , renderFewShotPromptWithVars-  ) where--import qualified Data.Map.Strict as Map-import Data.Text (Text)-import qualified Data.Text as T--import Langchain.Core.Error (LangchainError)-import Langchain.PromptTemplate.Prompt (fromTemplate, renderPrompt)---- | Represents a few-shot prompt template with examples-data FewShotPromptTemplate = FewShotPromptTemplate-  { fsPrefix :: Text-  , fsExamples :: [Map.Map Text Text]-  , fsExampleTemplate :: Text-  , fsExampleSeparator :: Text-  , fsSuffix :: Text-  }-  deriving (Show, Eq)---- | Render a few-shot prompt template-renderFewShotPrompt :: FewShotPromptTemplate -> Either LangchainError Text-renderFewShotPrompt FewShotPromptTemplate {..} = do-  formattedExamples <- traverse (renderPrompt (fromTemplate fsExampleTemplate)) fsExamples-  let examplesText = T.intercalate fsExampleSeparator formattedExamples-  pure $ fsPrefix <> examplesText <> fsSuffix---- | Render few-shot template with additional variables-renderFewShotPromptWithVars ::-  FewShotPromptTemplate -> Map.Map Text Text -> Either LangchainError Text-renderFewShotPromptWithVars template vars = do-  renderedBase <- renderFewShotPrompt template-  renderPrompt (fromTemplate renderedBase) vars
− src/Langchain/PromptTemplate/Prompt.hs
@@ -1,104 +0,0 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DuplicateRecordFields #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE TypeFamilies #-}--{- |-Module      : Langchain.PromptTemplate.Prompt-Description : String prompt templates-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Prompt templates backed by string interpolation.--}-module Langchain.PromptTemplate.Prompt-  ( PromptTemplate (..)-  , PromptTemplateOptions (..)-  , TemplateFormat (..)-  , defaultPromptTemplateOptions-  , fromTemplate-  , fromTemplateWithOptions-  , fromTemplateWithFormat-  , partialPromptTemplate-  , renderPrompt-  , renderTemplateWithFormat-  , renderFStringTemplate-  , extractTemplateVariables-  , extractTemplateVariablesWithFormat-  ) where--import Data.Aeson (FromJSON, ToJSON)-import qualified Data.Map.Strict as Map-import Data.Text (Text)-import GHC.Generics (Generic)--import Langchain.Core.Error (LangchainError)-import Langchain.Core.Runnable (Runnable (..))-import Langchain.PromptTemplate.String-  ( TemplateFormat (..)-  , extractTemplateVariables-  , extractTemplateVariablesWithFormat-  , renderFStringTemplate-  , renderTemplateWithFormat-  )---- | Prompt template container with template string containing {var} placeholders.-data PromptTemplate = PromptTemplate-  { template :: Text-  , inputVariables :: [Text]-  , -- Matches Python partial_variables: pre-bound values reduce required inputs-    -- without changing the original template string.-    partialVariables :: Map.Map Text Text-  , templateFormat :: TemplateFormat-  }-  deriving (Show, Eq, Generic, ToJSON, FromJSON)---- | Options for building a prompt template, currently only partial variables.-newtype PromptTemplateOptions = PromptTemplateOptions-  { partialVariables :: Map.Map Text Text-  }-  deriving (Show, Eq, Generic, ToJSON, FromJSON)---- | Default prompt template options with no partial variables.-defaultPromptTemplateOptions :: PromptTemplateOptions-defaultPromptTemplateOptions = PromptTemplateOptions mempty---- | Build a string prompt template using the default FString format.-fromTemplate :: Text -> PromptTemplate-fromTemplate source = fromTemplateWithOptions source defaultPromptTemplateOptions---- | Build a string prompt template with pre-bound partial variables.-fromTemplateWithOptions :: Text -> PromptTemplateOptions -> PromptTemplate-fromTemplateWithOptions source (PromptTemplateOptions partials) =-  fromTemplateWithFormat source FString partials---- | Build a prompt template from raw text, format, and partial variables.-fromTemplateWithFormat :: Text -> TemplateFormat -> Map.Map Text Text -> PromptTemplate-fromTemplateWithFormat source format partials =-  PromptTemplate-    { template = source-    , inputVariables =-        filter (`Map.notMember` partials) (extractTemplateVariablesWithFormat format source)-    , partialVariables = partials-    , templateFormat = format-    }---- | Apply additional partial variables to an existing prompt template.-partialPromptTemplate :: PromptTemplate -> Map.Map Text Text -> PromptTemplate-partialPromptTemplate (PromptTemplate source _ existingPartials format) partials =-  fromTemplateWithFormat source format (partials `Map.union` existingPartials)---- | Render a prompt template with the given variable map.-renderPrompt :: PromptTemplate -> Map.Map Text Text -> Either LangchainError Text-renderPrompt (PromptTemplate source _ partials format) vars =-  renderTemplateWithFormat format (vars `Map.union` partials) source---- | 'PromptTemplate' implements 'Runnable' transforming variable 'Map' to rendered 'Text'.-instance Monad m => Runnable PromptTemplate m where-  type RunnableInput PromptTemplate = Map.Map Text Text-  type RunnableOutput PromptTemplate = Text-  invoke pt vars = pure (renderPrompt pt vars)
− src/Langchain/PromptTemplate/String.hs
@@ -1,104 +0,0 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE OverloadedStrings #-}--{- |-Module      : Langchain.PromptTemplate.String-Description : String prompt template formatting helpers-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--String template parsing, variable extraction, and interpolation helpers.--}-module Langchain.PromptTemplate.String-  ( TemplateFormat (..)-  , renderTemplateWithFormat-  , renderFStringTemplate-  , extractTemplateVariables-  , extractTemplateVariablesWithFormat-  ) where--import Data.Aeson (FromJSON, ToJSON)-import Data.Char (isDigit)-import Data.Foldable (traverse_)-import qualified Data.Map.Strict as Map-import Data.Text (Text)-import qualified Data.Text as T-import Data.Text.Format.Heavy.Build (formatEither)-import Data.Text.Format.Heavy.Instances ()-import Data.Text.Format.Heavy.Parse (FormatParseItem (..), parse, parseFormat)-import qualified Data.Text.Lazy as TL-import GHC.Generics (Generic)--import Langchain.Core.Error (LangchainError, validationError)--data TemplateFormat-  = FString-  deriving (Show, Eq, Generic, ToJSON, FromJSON)--renderTemplateWithFormat ::-  TemplateFormat -> Map.Map Text Text -> Text -> Either LangchainError Text-renderTemplateWithFormat FString = renderFStringTemplate--renderFStringTemplate :: Map.Map Text Text -> Text -> Either LangchainError Text-renderFStringTemplate vars source = do-  items <- parseFStringTemplate source-  traverse_ validateFStringItem items-  format <- mapParseError $ parseFormat (TL.fromStrict source)-  mapFormatError $ TL.toStrict <$> formatEither format (toFStringVars vars)-  where-    mapFormatError :: Either String a -> Either LangchainError a-    mapFormatError (Left err) = Left $ validationError (T.pack err) (Just "PromptTemplate") Nothing-    mapFormatError (Right result) = Right result--toFStringVars :: Map.Map Text Text -> Map.Map TL.Text Text-toFStringVars = Map.mapKeys TL.fromStrict--parseFStringTemplate :: Text -> Either LangchainError [FormatParseItem]-parseFStringTemplate source = mapParseError $ parse (TL.fromStrict source)--mapParseError :: (Show err) => Either err a -> Either LangchainError a-mapParseError (Left err) = Left $ validationError (T.pack $ show err) (Just "PromptTemplate") Nothing-mapParseError (Right result) = Right result--validateFStringItem :: FormatParseItem -> Either LangchainError ()-validateFStringItem (FormatString _) = Right ()-validateFStringItem (FormatReplacementField variableName formatSpec) = do-  validateFStringVariableName variableName-  traverse_ validateFStringFormatSpec formatSpec--validateFStringVariableName :: TL.Text -> Either LangchainError ()-validateFStringVariableName variableName-  | TL.all isDigit variableName =-      Left $-        validationError "Positional arguments are not supported" (Just $ TL.toStrict variableName) Nothing-  | TL.any (== '.') variableName =-      Left $ validationError "Attribute access is not supported" (Just $ TL.toStrict variableName) Nothing-  | TL.any (`elem` ['[', ']']) variableName =-      Left $ validationError "Index access is not supported" (Just $ TL.toStrict variableName) Nothing-  | otherwise = Right ()--validateFStringFormatSpec :: TL.Text -> Either LangchainError ()-validateFStringFormatSpec formatSpec-  | TL.any (`elem` ['{', '}']) formatSpec =-      Left $ validationError "Nested replacement fields are not allowed" (Just "PromptTemplate") Nothing-  | otherwise = Right ()--extractTemplateVariables :: Text -> [Text]-extractTemplateVariables = extractTemplateVariablesWithFormat FString--extractTemplateVariablesWithFormat :: TemplateFormat -> Text -> [Text]-extractTemplateVariablesWithFormat FString source =-  case parseFStringTemplate source of-    Left _ -> []-    Right parts -> unique [TL.toStrict variableName | FormatReplacementField variableName _ <- parts]--unique :: [Text] -> [Text]-unique = foldl addIfMissing []--addIfMissing :: [Text] -> Text -> [Text]-addIfMissing variableNames variableName-  | variableName `elem` variableNames = variableNames-  | otherwise = variableNames <> [variableName]
− src/Langchain/Provider/Gemini.hs
@@ -1,483 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE NamedFieldPuns #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-}--{- |-Module      : Langchain.Provider.Gemini-Description : Google Gemini provider implementing ChatModel-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Gemini provider with multi-modal content parts support.--}-module Langchain.Provider.Gemini-  ( Gemini (..)-  , GeminiConfig (..)-  , defaultConfig-  , defaultGeminiConfig-  , newGemini-  , parseGeminiResponse-  ) where--import Control.Applicative ((<|>))-import Control.Concurrent.Async (AsyncCancelled (..))-import Control.Exception (SomeException, fromException, throwIO, try)-import Control.Monad.Except (throwError)-import Control.Monad.IO.Class (liftIO)-import Data.Aeson-import qualified Data.Aeson.KeyMap as KeyMap-import Data.Aeson.Types (Parser, parseEither)-import Data.Conduit (ConduitT, await, runConduit, yield, (.|))-import qualified Data.Conduit.Combinators as C-import qualified Data.List as List-import qualified Data.List.NonEmpty as NonEmpty-import qualified Data.Map.Strict as Map-import Data.Maybe (fromMaybe)-import qualified Data.Proxy as Proxy-import Data.Text (Text)-import qualified Data.Text as T-import GHC.Generics (Generic)-import Network.HTTP.Client (newManager)-import Network.HTTP.Client.TLS (tlsManagerSettings)-import Network.HTTP.Simple-import Servant.API (Capture, JSON, QueryParam, ReqBody, (:>))-import Servant.API.EventStream-  ( FromServerEvent (fromServerEvent)-  , PostServerSentEvents-  , jsonData-  )-import Servant.Client.Core.BaseUrl (parseBaseUrl)-import Servant.Client.Streaming (ClientM, client, mkClientEnv, withClientM)-import Servant.Conduit ()--import Langchain.Core.Error (LangchainError, llmError)-import Langchain.Core.Model-import Langchain.Core.Stream (StreamEvent (..), TokenUsage (..), callbackSource)-import qualified Langchain.Core.Tool as CoreTool-import Langchain.Tool.Binding (ToolBinder (..))---- | Gemini configuration-data GeminiConfig = GeminiConfig-  { configApiKey :: Text-  , configModel :: Text-  }-  deriving (Eq, Show, Generic, ToJSON, FromJSON)--defaultConfig :: Text -> GeminiConfig-defaultConfig key = GeminiConfig key "gemini-2.0-flash"--defaultGeminiConfig :: Text -> GeminiConfig-defaultGeminiConfig = defaultConfig---- | Gemini ChatModel provider-data Gemini-  = Gemini-  { apiKey :: Text-  , model :: Text-  , baseUrl :: Maybe Text-  }-  deriving (Eq, Show)---- | Create a new Gemini provider instance-newGemini :: Text -> Text -> Maybe Text -> Gemini-newGemini = Gemini--geminiApiKey :: Gemini -> Text-geminiApiKey = apiKey--geminiModel :: Gemini -> Text-geminiModel = model--geminiBaseUrl :: Gemini -> Text-geminiBaseUrl Gemini {baseUrl = Just baseUrl} = T.dropWhileEnd (== '/') baseUrl-geminiBaseUrl Gemini {} = "https://generativelanguage.googleapis.com"---- Convert ContentBlock to Gemini Part JSON-contentBlockToPart :: ContentBlock -> Value-contentBlockToPart (TextBlock t) =-  object ["text" .= t]-contentBlockToPart (ImageBlock ImageContent {imageSource = ImageBase64 (Just mime) b64}) =-  object-    [ "inline_data"-        .= object-          [ "mime_type" .= mime-          , "data" .= b64-          ]-    ]-contentBlockToPart (ImageBlock ImageContent {imageSource = ImageUrl url}) =-  object ["text" .= ("[Image URL: " <> url <> "]")]-contentBlockToPart (ImageBlock ImageContent {imageSource = ImageBase64 Nothing _}) =-  object ["text" .= ("[Image data block: base64]" :: Text)]-contentBlockToPart (AudioBlock mime b64) =-  object-    [ "inline_data"-        .= object-          [ "mime_type" .= mime-          , "data" .= b64-          ]-    ]-contentBlockToPart (DataBlock _) =-  object ["text" .= ("[Data block]" :: Text)]---- Convert a non-tool Message to Gemini Content JSON.-messageToGemini :: Message -> Value-messageToGemini msg =-  let role = messageRole msg-      geminiRole = case role of-        User -> "user"-        Assistant -> "model"-        System -> "user"-        Developer -> "user"-        Tool -> "user"-        Function -> "user"-      toolCallParts = case role of-        Assistant -> maybe [] (functionCallParts $ messageMetadata msg) (messageToolCalls msg)-        _ -> []-      contentBlocks = NonEmpty.toList (messageContents msg)-      contentParts = map contentBlockToPart contentBlocks-      parts-        | null toolCallParts = contentParts-        | otherwise = map contentBlockToPart (filter (not . emptyTextPart) contentBlocks) <> toolCallParts-   in object ["role" .= (geminiRole :: Text), "parts" .= parts]-  where-    functionCallParts metadata toolCalls =-      zipWith functionCallPart toolCalls (thoughtSignatures metadata <> repeat Nothing)--    functionCallPart (ToolCall {toolCallName = name, toolCallArguments = args, toolCallId = callId}) thoughtSignature =-      object $-        [ "functionCall"-            .= object-              ( [ "name" .= name-                , "args" .= args-                ]-                  <> (["id" .= callId | notNull callId])-              )-        ]-          <> maybe [] (pure . ("thoughtSignature" .=)) thoughtSignature--    notNull = not . T.null--    emptyTextPart (TextBlock text) = T.null text-    emptyTextPart _ = False--geminiThoughtSignaturesKey :: Text-geminiThoughtSignaturesKey = "langchain.gemini.thoughtSignatures"--thoughtSignatures :: Map.Map Text Value -> [Maybe Text]-thoughtSignatures metadata =-  case Map.lookup geminiThoughtSignaturesKey metadata of-    Just value -> case fromJSON value of-      Success signatures -> signatures-      Error _ -> []-    Nothing -> []--withThoughtSignatures :: [ToolCall] -> [Maybe Text] -> Message -> Message-withThoughtSignatures [] _ message = message {messageToolCalls = Nothing}-withThoughtSignatures toolCalls signatures message =-  message-    { messageToolCalls = Just toolCalls-    , messageMetadata =-        Map.insert geminiThoughtSignaturesKey (toJSON signatures) (messageMetadata message)-    }--functionResponsePart :: [ToolCall] -> Message -> Either Text Value-functionResponsePart priorToolCalls msg = do-  toolName <--    maybe-      (Left "Gemini function response is missing a function name")-      Right-      (messageName msg <|> (messageToolId msg >>= lookupToolName))-  let functionResponseFields =-        [ "name" .= toolName-        , "response" .= object ["result" .= extractMessageText msg]-        ]-          <> maybe [] (pure . ("id" .=)) (messageToolId msg)-  pure $ object ["functionResponse" .= object functionResponseFields]-  where-    lookupToolName toolId =-      toolCallName <$> List.find ((== toolId) . toolCallId) priorToolCalls--messagesToGemini :: [ToolCall] -> [Message] -> Either Text [Value]-messagesToGemini _ [] = Right []-messagesToGemini priorToolCalls (msg : remaining)-  | isFunctionResponse msg = do-      let (responseMessages, followingMessages) = span isFunctionResponse remaining-      parts <- traverse (functionResponsePart priorToolCalls) (msg : responseMessages)-      contents <- messagesToGemini priorToolCalls followingMessages-      pure $ object ["role" .= ("user" :: Text), "parts" .= parts] : contents-  | otherwise = do-      contents <- messagesToGemini priorToolCalls remaining-      pure $ messageToGemini msg : contents-  where-    isFunctionResponse message = messageRole message `elem` [Tool, Function]--geminiRequestPayload :: [Message] -> Maybe Value -> Either Text Value-geminiRequestPayload inputMsgs config = do-  let priorToolCalls = concatMap (fromMaybe [] . messageToolCalls) inputMsgs-  contents <- messagesToGemini priorToolCalls inputMsgs-  case config of-    Just (Object fields) -> pure $ Object $ KeyMap.insert "contents" (toJSON contents) fields-    Nothing -> pure $ object ["contents" .= contents]-    Just _ -> Left "Gemini config must be a JSON object"--instance ChatModel Gemini where-  type ModelConfig Gemini = Value--  invoke provider inputMsgs config = do-    payload <--      either (throwError . \err -> llmError err Nothing Nothing) pure $-        geminiRequestPayload inputMsgs config-    let url =-          geminiBaseUrl provider-            <> "/v1beta/models/"-            <> geminiModel provider-            <> ":generateContent?key="-            <> geminiApiKey provider-        initReq = parseRequest_ (T.unpack url)-        req =-          setRequestMethod "POST" $-            setRequestHeader "Content-Type" ["application/json"] $-              setRequestBodyJSON payload initReq--    eRes <- liftIO $ safeHttpRequest req-    case eRes of-      Left err -> throwError $ llmError err Nothing Nothing-      Right bodyVal -> case parseGeminiResponse bodyVal of-        Left parseErr -> throwError $ llmError (T.pack parseErr) Nothing Nothing-        Right respMsg -> pure respMsg--  stream provider inputMsgs config = do-    let model = geminiModel provider-        requestPayload = geminiRequestPayload inputMsgs-    yield $ LLMStart rId model inputMsgs--    payload <--      either (throwError . llmError') pure $ requestPayload config--    let events = geminiEvents payload-    (accumulated, toolCalls, thoughtSignatures', usage) <--      callbackSource events-        .| receiveChunks "" [] [] Nothing--    let message = withThoughtSignatures toolCalls thoughtSignatures' $ assistantMessage accumulated-    yield $ LLMEnd rId message usage-    where-      receiveChunks accumulated toolCalls thoughtSignatures' usage = do-        next <- await-        case next of-          Nothing -> pure (accumulated, toolCalls, thoughtSignatures', usage)-          Just (Left err) -> throwError $ llmError' err-          Just (Right (GeminiStreamEvent GeminiStreamChunk {streamCandidates, streamUsage})) -> do-            let parts = maybe [] streamParts $ candidate0 streamCandidates-                texts = [text | GeminiText text <- parts]-                calls = [(toolCall, signature) | GeminiFunctionCall toolCall signature <- parts]-                nextUsage = streamUsage <|> usage-            emitParts texts (map fst calls)-            receiveChunks-              (accumulated <> mconcat texts)-              (toolCalls <> map fst calls)-              (thoughtSignatures' <> map snd calls)-              nextUsage--      candidate0 = List.find ((== 0) . streamCandidateIndex)--      emitParts texts [] = mapM_ (`yieldChunk` Nothing) texts-      emitParts texts (toolCall : remaining) = do-        yieldChunk (mconcat texts) (Just toolCall)-        mapM_ (yieldChunk "" . Just) remaining--      yieldChunk text mbToolCall = yield $ LLMChunk rId text mbToolCall--      geminiEvents requestPayload emit = do-        result <- try $ do-          manager <- newManager tlsManagerSettings--          let baseUrl = parseBaseUrl (T.unpack $ geminiBaseUrl provider)-              model = geminiModel provider-              apiKey = geminiApiKey provider-              request =-                geminiStreamClient-                  (model <> ":streamGenerateContent")-                  (Just "sse")-                  (Just apiKey)-                  requestPayload--          clientEnv <- mkClientEnv manager <$> baseUrl-          withClientM request clientEnv $-            either-              emitError-              (\source -> runConduit $ source .| C.mapM_ (emit . Right))-        case result of-          Left err-            | Just AsyncCancelled <- fromException err -> throwIO err-            | otherwise -> emitError err-          Right () -> pure ()-        where-          emitError :: Show a => a -> IO ()-          emitError =-            emit . Left . redactKey (geminiApiKey provider) . T.pack . show--      rId = "gemini-stream-run"--llmError' :: Text -> LangchainError-llmError' err = llmError err Nothing Nothing--{- | Replace the literal API key with @[REDACTED]@ in error messages so it-  never appears in 'LangchainError' values or test output.--}-redactKey :: Text -> Text -> Text-redactKey key txt-  | T.null key = txt-  | otherwise = T.replace key "[REDACTED]" txt--data GeminiStreamChunk = GeminiStreamChunk-  { streamCandidates :: [GeminiStreamCandidate]-  , streamUsage :: Maybe TokenUsage-  }--instance FromJSON GeminiStreamChunk where-  parseJSON = withObject "GeminiStreamChunk" $ \obj ->-    GeminiStreamChunk-      <$> obj .:? "candidates" .!= []-      <*> (obj .:? "usageMetadata" >>= traverse parseGeminiUsage)--data GeminiStreamCandidate = GeminiStreamCandidate-  { streamCandidateIndex :: Int-  , streamParts :: [GeminiPart]-  }--instance FromJSON GeminiStreamCandidate where-  parseJSON = withObject "GeminiStreamCandidate" $ \obj -> do-    streamCandidateIndex <- obj .:? "index" .!= 0-    content <- obj .:? "content"-    streamParts <- case content of-      Nothing -> pure []-      Just contentValue -> withObject "GeminiStreamContent" parseParts contentValue-    pure GeminiStreamCandidate {streamCandidateIndex, streamParts}-    where-      parseParts contentObj = do-        parts <- contentObj .:? "parts" .!= []-        traverse parseGeminiPart parts--data GeminiPart-  = GeminiText Text-  | GeminiFunctionCall ToolCall (Maybe Text)--parseGeminiPart :: Value -> Parser GeminiPart-parseGeminiPart = withObject "GeminiPart" $ \obj -> do-  functionCall <- obj .:? "functionCall"-  case functionCall of-    Just value -> GeminiFunctionCall <$> parseGeminiFunctionCall value <*> pure (parseThoughtSignature obj)-    Nothing -> GeminiText <$> obj .:? "text" .!= ""--parseGeminiFunctionCall :: Value -> Parser ToolCall-parseGeminiFunctionCall = withObject "GeminiFunctionCall" $ \obj ->-  ToolCall-    <$> obj .:? "id" .!= ""-    <*> pure "function"-    <*> obj .: "name"-    <*> obj .:? "args" .!= object []--parseThoughtSignature :: Object -> Maybe Text-parseThoughtSignature obj =-  case KeyMap.lookup "thoughtSignature" obj of-    Just (String signature) -> Just signature-    _ -> Nothing--parseGeminiUsage :: Value -> Parser TokenUsage-parseGeminiUsage = withObject "GeminiUsageMetadata" $ \obj ->-  TokenUsage-    <$> obj .:? "promptTokenCount" .!= 0-    <*> obj .:? "candidatesTokenCount" .!= 0-    <*> obj .:? "totalTokenCount" .!= 0--newtype GeminiStreamEvent = GeminiStreamEvent GeminiStreamChunk--instance FromServerEvent GeminiStreamEvent where-  fromServerEvent event = GeminiStreamEvent <$> jsonData event--type GeminiStreamApi =-  "v1beta"-    :> "models"-    :> Capture "modelAction" Text-    :> QueryParam "alt" Text-    :> QueryParam "key" Text-    :> ReqBody '[JSON] Value-    :> PostServerSentEvents (ConduitT () GeminiStreamEvent IO ())--geminiStreamClient ::-  Text -> Maybe Text -> Maybe Text -> Value -> ClientM (ConduitT () GeminiStreamEvent IO ())-geminiStreamClient = client (Proxy.Proxy :: Proxy.Proxy GeminiStreamApi)---- Helper for HTTP requests-safeHttpRequest :: Request -> IO (Either Text Value)-safeHttpRequest req = do-  eRes <--    try (httpJSONEither req) :: IO (Either SomeException (Response (Either JSONException Value)))-  case eRes of-    Left ex -> pure $ Left (T.pack $ show ex)-    Right res -> case getResponseBody res of-      Left err -> pure $ Left (T.pack $ show err)-      Right val -> pure $ Right val---- Parse Gemini response JSON-parseGeminiResponse :: Value -> Either String Message-parseGeminiResponse = parseEither $ withObject "GeminiResponse" $ \o -> do-  -- Surface API-level errors (e.g. safety blocks, quota exhausted) verbatim-  case KeyMap.lookup "error" o of-    Just (Object errObj) -> do-      msg <- errObj .: "message" <|> pure "Unknown Gemini API error"-      fail (T.unpack msg)-    _ -> pure ()-  candidates <- o .: "candidates"-  case candidates of-    [] -> fail "Empty candidates array in Gemini response"-    (c : _) ->-      flip (withObject "Candidate") c $ \cand -> do-        -- "content" is absent when finishReason is SAFETY or MAX_TOKENS with no output-        mContentObj <- cand .:? "content"-        case mContentObj of-          Nothing -> pure $ assistantMessage ""-          Just contentObj -> do-            parts <- contentObj .: "parts"-            parsedParts <- traverse parseGeminiPart parts-            let texts = [text | GeminiText text <- parsedParts]-                toolCalls = [toolCall | GeminiFunctionCall toolCall _ <- parsedParts]-                signatures = [signature | GeminiFunctionCall _ signature <- parsedParts]-            pure $-              withThoughtSignatures toolCalls signatures $-                assistantMessage $-                  T.intercalate "\n" texts---- | Bind tools to a Gemini model by adding function declarations to the config.-instance ToolBinder Gemini m where-  bindToolsConfig tools config =-    case tools of-      [] -> config-      _ ->-        let generated =-              KeyMap.singleton-                "tools"-                ( toJSON-                    [ object ["functionDeclarations" .= map functionDeclaration tools]-                    ]-                )-         in Just $ case config of-              Nothing -> Object generated-              Just (Object existing) -> Object (KeyMap.union generated existing)-              Just other -> other-    where-      functionDeclaration tool =-        object-          [ "name" .= CoreTool.toolName tool-          , "description" .= CoreTool.toolDescription tool-          , "parameters" .= CoreTool.toolSchema tool-          ]
− src/Langchain/Provider/Ollama.hs
@@ -1,396 +0,0 @@-{-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications #-}-{-# LANGUAGE TypeFamilies #-}--{- |-Module      : Langchain.Provider.Ollama-Description : Ollama provider implementing the effect-polymorphic ChatModel typeclass-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Ollama provider using 'ollama-haskell' 0.4.0.0. Supports native structured outputs-via JSON Schema grammar sampling, streaming, tool calling, and embeddings.--== Request & Precedence Semantics-Because Ollama's 'ModelConfig' is 'OllamaChat.ChatRequest', which contains both-'chatModel' and 'chatMessages', callers may supply messages and model names either-via the 'ChatModel' interface ('invoke' / 'stream' arguments) or within the 'ChatRequest'.--The provider resolves these with well-defined precedence rules:--1. __Messages Precedence__:-   * When @inputMsgs@ is non-empty (@not (null inputMsgs)@), it takes precedence over-     'ChatRequest.chatMessages'. This enables reusing request templates across multiple calls-     and ensures 'batch' processes each item's messages properly.-   * When @inputMsgs@ is empty (@null inputMsgs@), the provider falls back to-     'ChatRequest.chatMessages' if a 'ChatRequest' is provided.-   * If both are empty, defaults to an empty user message.--2. __Model Name Precedence__:-   * When a 'ChatRequest' is provided with a non-empty 'chatModel', it overrides-     the provider's default 'ollamaModelName'.-   * Otherwise, the provider's 'ollamaModelName' is used as the default.--}-module Langchain.Provider.Ollama-  ( Ollama (..)-  , newOllama-  , newOllamaWithClient-  , toOllamaRole-  , fromOllamaRole-  , toOllamaMessage-  , fromOllamaMessage-  , withJsonFormat-  , withSchemaFormat-  , withStructuredOutput-  , withOptions-  , chatRequestFor-  , resolveChatRequest-  , withTools-  , toOllamaTool-  , toOllamaTools-  , OllamaWithTools (..)-  , bindTools--    -- * Re-exports from ollama-haskell format, schema, options & client config-  , module Ollama.API.Chat-  , module Ollama.Client.Config-  , module Ollama.Types.Common-  , module Ollama.Types.Options-  , OFormat.Format (..)-  , OSB.Schema (..)-  , OSB.Property (..)-  , OSB.JsonType (..)-  , OSD.ToSchema (..)-  , OSD.ToJsonType (..)-  ) where--import Control.Monad (when)-import Control.Monad.Except (throwError)-import Control.Monad.IO.Class (MonadIO, liftIO)-import Data.Aeson (Result (..), decode, fromJSON, toJSON)-import qualified Data.Aeson as Aeson-import qualified Data.ByteString.Lazy.Char8 as LBSC-import Data.Conduit (await, transPipe, yield, (.|))-import qualified Data.List.NonEmpty as NonEmpty-import Data.Maybe (fromMaybe, isJust, mapMaybe)-import Data.Text (Text)-import qualified Data.Text as T-import qualified Data.Text.Encoding as TE--import Langchain.Core.Error (llmError)-import Langchain.Core.Model-import Langchain.Core.Stream (StreamEvent (..), TokenUsage (..))-import Langchain.Core.Tool (Tool, toolToValue)-import Langchain.Tool.Binding (ToolBinder (..))--import Ollama.API.Chat-import qualified Ollama.API.Chat as OllamaChat-import Ollama.Client (OllamaClient, newClient)-import Ollama.Client.Config-import Ollama.Types.Common (Base64Image (..), ModelName (..))-import qualified Ollama.Types.Format as OFormat-import qualified Ollama.Types.Format.SchemaBuilder as OSB-import qualified Ollama.Types.Format.SchemaDerive as OSD-import qualified Ollama.Types.Message as O-import Ollama.Types.Options (ModelOptions (..), defaultOptions)-import qualified Ollama.Types.Tool as OTool---- | Ollama provider data type wrapping OllamaClient and model name-data Ollama = Ollama-  { client :: OllamaClient-  , ollamaModelName :: Text-  }--instance Show Ollama where-  show (Ollama _ m) = "Ollama provider (" ++ show m ++ ")"---- | Create a new Ollama provider with model name and client config-newOllama :: MonadIO m => Text -> OllamaClientConfig -> m Ollama-newOllama model cfg = do-  c <- liftIO $ newClient cfg-  pure $ Ollama c model---- | Create an Ollama provider using an existing OllamaClient handle-newOllamaWithClient :: Text -> OllamaClient -> Ollama-newOllamaWithClient model c = Ollama c model---- | Helper to convert core Role to Ollama Role-toOllamaRole :: Role -> O.Role-toOllamaRole System = O.System-toOllamaRole User = O.User-toOllamaRole Assistant = O.Assistant-toOllamaRole Tool = O.Tool-toOllamaRole Developer = O.System-toOllamaRole Function = O.Tool---- | Helper to convert Ollama Role to core Role-fromOllamaRole :: O.Role -> Role-fromOllamaRole O.System = System-fromOllamaRole O.User = User-fromOllamaRole O.Assistant = Assistant-fromOllamaRole O.Tool = Tool---- | Convert core Message to Ollama Message-toOllamaMessage :: Message -> O.Message-toOllamaMessage msg =-  let r = toOllamaRole (messageRole msg)-      txt = extractMessageText msg-      imgs = case [ b64-                  | ImageBlock ImageContent {imageSource = ImageBase64 _ b64} <- NonEmpty.toList (messageContents msg)-                  ] of-        [] -> Nothing-        xs -> Just (map Base64Image xs)-      tools = case messageToolCalls msg of-        Nothing -> Nothing-        Just tcs ->-          Just-            [ OTool.ToolCall-                { OTool.tcFunction =-                    OTool.ToolCallFunction-                      { OTool.tcfName = toolCallName tc-                      , OTool.tcfArguments = parseArgs (toolCallArguments tc)-                      }-                }-            | tc <- tcs-            ]-      parseArgs v = case v of-        Aeson.Object _ -> case fromJSON v of-          Success m -> m-          _ -> mempty-        Aeson.String s -> fromMaybe mempty $ decode (LBSC.fromStrict (TE.encodeUtf8 s))-        _ -> case fromJSON v of-          Success m -> m-          _ -> mempty-   in O.Message r txt imgs tools (messageName msg) Nothing---- | Convert Ollama Message to core Message-fromOllamaMessage :: O.Message -> Message-fromOllamaMessage (O.Message r txt _imgs tools name _think) =-  let cRole = fromOllamaRole r-      cMsg = (textMessage cRole txt) {messageName = name}-      cTools = case tools of-        Nothing -> Nothing-        Just tcs ->-          Just-            [ ToolCall-                { toolCallId = ""-                , toolCallType = "function"-                , toolCallName = OTool.tcfName (OTool.tcFunction tc)-                , toolCallArguments = toJSON (OTool.tcfArguments (OTool.tcFunction tc))-                }-            | tc <- tcs-            ]-   in cMsg {messageToolCalls = cTools}--{- | Construct a 'ChatRequest' for an 'Ollama' instance with the given messages.--Sets 'chatModel' to the provider's 'ollamaModelName'. When passed to 'invoke'-or 'stream', any non-empty message argument passed directly to 'invoke' or-'stream' will take priority over the messages in this 'ChatRequest'.--}-chatRequestFor :: Ollama -> [Message] -> OllamaChat.ChatRequest-chatRequestFor model inputMsgs =-  let oMsgs = case inputMsgs of-        [] -> O.userMessage "" NonEmpty.:| []-        (m : ms) -> NonEmpty.map toOllamaMessage (m NonEmpty.:| ms)-   in OllamaChat.chatRequest (ModelName (ollamaModelName model)) oMsgs--{- | Resolve the effective 'ChatRequest', effective model name, and effective messages-given the provider instance, explicit message arguments, and optional 'ChatRequest'.--= Precedence Rules--* __Messages Precedence__:-  1. If @inputMsgs@ is non-empty (@not (null inputMsgs)@), it takes priority and-     is used as the request conversation. This enables reusing a configured-     'ChatRequest' (tools, formats, options) across invocations and guarantees-     that 'batch' processes each item's messages properly.-  2. If @inputMsgs@ is empty (@null inputMsgs@) and a 'ChatRequest' is provided,-     its 'chatMessages' field is preserved and used.-  3. If both are empty (or @inputMsgs@ is empty and 'mbReq' is 'Nothing'),-     it defaults to a single empty user message.--* __Model Name Precedence__:-  1. If a 'ChatRequest' is provided and its 'chatModel' is non-empty,-     it overrides the provider's default 'ollamaModelName'.-  2. Otherwise, the provider's 'ollamaModelName' is used as the default.--}-resolveChatRequest ::-  Ollama ->-  [Message] ->-  Maybe OllamaChat.ChatRequest ->-  (OllamaChat.ChatRequest, Text, [Message])-resolveChatRequest model inputMsgs mbReq =-  let providerModel = ollamaModelName model-      resolvedModelText = case mbReq of-        Just r ->-          let m = unModelName (OllamaChat.chatModel r)-           in if T.null m then providerModel else m-        Nothing -> providerModel-      resolvedModelName = ModelName resolvedModelText--      (resolvedOMsgs, resolvedCoreMsgs) = case inputMsgs of-        (m : ms) ->-          let oList = NonEmpty.map toOllamaMessage (m NonEmpty.:| ms)-           in (oList, inputMsgs)-        [] -> case mbReq of-          Just r ->-            let oList = OllamaChat.chatMessages r-                coreList = map fromOllamaMessage (NonEmpty.toList oList)-             in (oList, coreList)-          Nothing ->-            (O.userMessage "" NonEmpty.:| [], [])--      resolvedReq = case mbReq of-        Nothing ->-          OllamaChat.chatRequest resolvedModelName resolvedOMsgs-        Just r ->-          r-            { OllamaChat.chatModel = resolvedModelName-            , OllamaChat.chatMessages = resolvedOMsgs-            }-   in (resolvedReq, resolvedModelText, resolvedCoreMsgs)--instance ChatModel Ollama where-  type ModelConfig Ollama = OllamaChat.ChatRequest--  invoke model inputMsgs mbReq = do-    let (req, _modelName, _msgs) = resolveChatRequest model inputMsgs mbReq-    eRes <- liftIO $ OllamaChat.chat (client model) req-    case eRes of-      Left err -> throwError $ llmError (T.pack $ show err) Nothing Nothing-      Right resp -> case OllamaChat.crMessage resp of-        Nothing -> throwError $ llmError "No message in response" Nothing Nothing-        Just oMsg -> pure $ fromOllamaMessage oMsg--  stream model inputMsgs mbReq = do-    let runId_ = "ollama-run"-        (req, resolvedModel, resolvedMsgs) = resolveChatRequest model inputMsgs mbReq--    yield $ LLMStart runId_ resolvedModel resolvedMsgs-    transPipe liftIO (OllamaChat.chatStream (client model) req) .| processChunks runId_-    where-      processChunks rId = loop [] Nothing Nothing-        where-          loop accChunks mbLastUsage mbLastTools =-            await >>= \case-              Nothing -> do-                let fullText = T.concat (reverse accChunks)-                    finalMsg =-                      (assistantMessage fullText)-                        { messageToolCalls = mbLastTools-                        }-                yield $ LLMEnd rId finalMsg mbLastUsage-              Just resp -> do-                let mbMsg = OllamaChat.crMessage resp-                    chunkTxt = maybe "" O.messageContent mbMsg-                    mbTools = mbMsg >>= O.messageToolCalls-                    toolCalls = case mbTools of-                      Nothing -> Nothing-                      Just tcs ->-                        Just-                          [ ToolCall-                              { toolCallId = ""-                              , toolCallType = "function"-                              , toolCallName = OTool.tcfName (OTool.tcFunction tc)-                              , toolCallArguments = toJSON (OTool.tcfArguments (OTool.tcFunction tc))-                              }-                          | tc <- tcs-                          ]-                    toolDelta = case toolCalls of-                      Just (tc : _) -> Just tc-                      _ -> Nothing-                    newAccChunks = if T.null chunkTxt then accChunks else chunkTxt : accChunks-                    newTools = case toolCalls of-                      Just tcs -> Just $ maybe tcs (++ tcs) mbLastTools-                      Nothing -> mbLastTools-                    newUsage = case (OllamaChat.crPromptEvalCount resp, OllamaChat.crEvalCount resp) of-                      (Just p, Just c) -> Just $ TokenUsage p c (p + c)-                      _ -> mbLastUsage-                when (not (T.null chunkTxt) || isJust toolDelta) $-                  yield $-                    LLMChunk rId chunkTxt toolDelta-                loop newAccChunks newUsage newTools---- | Set model options on an Ollama ChatRequest-withOptions :: ModelOptions -> OllamaChat.ChatRequest -> OllamaChat.ChatRequest-withOptions opts req = req {OllamaChat.chatOptions = Just opts}---- | Attach Langchain tools to an Ollama ChatRequest-withTools :: [Tool m] -> OllamaChat.ChatRequest -> OllamaChat.ChatRequest-withTools ts req = req {OllamaChat.chatTools = Just (toOllamaTools ts)}---- | Convert a Langchain 'Tool' definition to an Ollama 'OTool.Tool'-toOllamaTool :: Tool m -> Maybe OTool.Tool-toOllamaTool t = case fromJSON (toolToValue t) of-  Success ot -> Just ot-  Aeson.Error _ -> Nothing---- | Convert a list of Langchain 'Tool' definitions to Ollama 'OTool.Tool's-toOllamaTools :: [Tool m] -> [OTool.Tool]-toOllamaTools = mapMaybe toOllamaTool---- | Attach generic JSON format constraint to Ollama ChatRequest-withJsonFormat :: OllamaChat.ChatRequest -> OllamaChat.ChatRequest-withJsonFormat req = req {OllamaChat.chatFormat = Just OFormat.JsonFormat}---- | Attach specific Schema format constraint to Ollama ChatRequest-withSchemaFormat :: OSB.Schema -> OllamaChat.ChatRequest -> OllamaChat.ChatRequest-withSchemaFormat schema req = req {OllamaChat.chatFormat = Just (OFormat.SchemaFormat schema)}---- | Attach automatic ToSchema derived format constraint to Ollama ChatRequest-withStructuredOutput ::-  forall a.-  (OSD.ToSchema a) =>-  OllamaChat.ChatRequest ->-  OllamaChat.ChatRequest-withStructuredOutput req =-  req {OllamaChat.chatFormat = Just (OFormat.SchemaFormat (OSD.toSchema @a))}---- | Ollama model with pre-bound tools-data OllamaWithTools m = OllamaWithTools-  { ollamaBaseModel :: !Ollama-  , ollamaBoundTools :: ![Tool m]-  }--instance Show (OllamaWithTools m) where-  show (OllamaWithTools m _) = "OllamaWithTools (" ++ show m ++ ")"---- | Bind tools to an Ollama model so any invocation automatically includes tool definitions-bindTools :: [Tool m] -> Ollama -> OllamaWithTools m-bindTools ts model = OllamaWithTools model ts--instance ChatModel (OllamaWithTools m) where-  type ModelConfig (OllamaWithTools m) = OllamaChat.ChatRequest-  invoke (OllamaWithTools model ts) msgs mbReq =-    let req = fromMaybe (chatRequestFor model msgs) mbReq-     in invoke model msgs (Just (withTools ts req))-  stream (OllamaWithTools model ts) msgs mbReq =-    let req = fromMaybe (chatRequestFor model msgs) mbReq-     in stream model msgs (Just (withTools ts req))---- | Bind tools to an Ollama model by creating/merging a ChatRequest with tool definitions-instance ToolBinder Ollama m where-  bindToolsConfig tools mbReq =-    case tools of-      [] -> mbReq-      _ ->-        let baseReq = fromMaybe (OllamaChat.chatRequest (ModelName "") (O.userMessage "" NonEmpty.:| [])) mbReq-         in Just $ withTools tools baseReq---- | OllamaWithTools already has tools bound, but merges additional tools if provided-instance ToolBinder (OllamaWithTools m) n where-  bindToolsConfig tools mbReq =-    case tools of-      [] -> mbReq-      _ ->-        let baseReq = fromMaybe (OllamaChat.chatRequest (ModelName "") (O.userMessage "" NonEmpty.:| [])) mbReq-         in Just $ withTools tools baseReq
− src/Langchain/Provider/OpenAI.hs
@@ -1,608 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DuplicateRecordFields #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE NamedFieldPuns #-}-{-# LANGUAGE OverloadedLists #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-}--{- |-Module      : Langchain.Provider.OpenAI-Description : OpenAI provider implementing effect-polymorphic ChatModel-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--OpenAI and OpenAICompatible provider using the @openai@ Haskell package-for typed API calls. Multi-modal content and streaming support.--}-module Langchain.Provider.OpenAI-  ( OpenAI (..)-  , OpenAIConfig (..)-  , defaultConfig-  , defaultOpenAIConfig-  , OpenAIToolChoice (..)-  , openAITools-  , newOpenAI-  , openAICompatible-  , normalizeBaseUrl-  , parseOpenAIResponse-  ) where--import Control.Applicative ((<|>))-import Control.Concurrent.Async (AsyncCancelled (..))-import Control.Exception (SomeException, fromException, throwIO, try)-import Control.Monad (forM)-import Control.Monad.Except (throwError)-import Control.Monad.IO.Class (liftIO)-import Control.Monad.Trans.Class (lift)-import qualified Data.Conduit.Combinators as C--import Data.Aeson (Value (..), object, (.=))-import qualified Data.Aeson as Aeson-import qualified Data.Aeson.KeyMap as KeyMap-import Data.Aeson.Types (Parser, parseEither, parseMaybe)-import Data.Bifunctor (first)-import qualified Data.ByteString.Lazy as LBS-import Data.Conduit-import qualified Data.List as List-import qualified Data.List.NonEmpty as NonEmpty-import qualified Data.Map.Strict as Map-import Data.Maybe (fromMaybe)-import Data.Proxy (Proxy (..))-import Data.Text (Text)-import qualified Data.Text as T-import qualified Data.Text.Encoding as TE-import qualified Data.Vector as V-import GHC.Generics (Generic)--import qualified OpenAI.V1 as OAI-import qualified OpenAI.V1.Chat.Completions as CC-import qualified OpenAI.V1.Models as OM-import qualified OpenAI.V1.ToolCall as OTC-import qualified OpenAI.V1.Usage as OU--import Langchain.Core.Error (LangchainError, llmError)-import Langchain.Core.Model-import Langchain.Core.Stream (StreamEvent (..), StreamM, TokenUsage (..), callbackSource)-import Langchain.Core.Tool (Tool, toolToValue)-import Langchain.Tool.Binding (ToolBinder (..))-import Network.HTTP.Client (newManager)-import Network.HTTP.Client.TLS (tlsManagerSettings)-import Servant.API (Header, JSON, ReqBody, (:>))-import Servant.API.EventStream-  ( FromServerEvent (fromServerEvent)-  , PostServerSentEvents-  , ServerEvent (eventData)-  , jsonData-  )-import Servant.Client.Core.BaseUrl (parseBaseUrl)-import Servant.Client.Streaming (ClientM, client, mkClientEnv, withClientM)-import Servant.Conduit ()---- | Configuration for OpenAI provider-data OpenAIConfig = OpenAIConfig-  { configApiKey :: Text-  , configModel :: Text-  , configBaseUrl :: Maybe Text-  , configTemperature :: Maybe Double-  }-  deriving (Eq, Show, Generic, Aeson.ToJSON, Aeson.FromJSON)--defaultConfig :: Text -> OpenAIConfig-defaultConfig key = OpenAIConfig key "gpt-4o" Nothing (Just 0.7)--defaultOpenAIConfig :: Text -> OpenAIConfig-defaultOpenAIConfig = defaultConfig---- | OpenAI ChatModel implementation-data OpenAI = OpenAI-  { apiKey :: Text-  , model :: Text-  , baseUrl :: Text-  {- ^ Base URL (e.g. @"https://api.openai.com"@). The @openai@ package-  automatically appends @\/v1\/chat\/completions@.-  -}-  , temperature :: Maybe Double-  }-  deriving (Eq, Show)---- | Controls how OpenAI chooses among request tool definitions.-data OpenAIToolChoice-  = OpenAIToolAuto-  | OpenAIToolNone-  | OpenAIToolRequired-  | OpenAIToolFunction Text-  deriving (Eq, Show)---- | Build stream request options from langchain tools.-openAITools :: [Tool m] -> OpenAIToolChoice -> Value-openAITools tools choice =-  object-    [ "tools" .= map toolToValue tools-    , "tool_choice" .= toolChoiceValue choice-    ]--toolChoiceValue :: OpenAIToolChoice -> Value-toolChoiceValue OpenAIToolAuto = String "auto"-toolChoiceValue OpenAIToolNone = String "none"-toolChoiceValue OpenAIToolRequired = String "required"-toolChoiceValue (OpenAIToolFunction name) =-  object-    [ "type" .= ("function" :: Text)-    , "function" .= object ["name" .= name]-    ]--data OpenAIStreamEvent-  = OpenAIChunk OpenAIStreamChunk-  | OpenAIDone--instance FromServerEvent OpenAIStreamEvent where-  fromServerEvent event-    | eventData event == "[DONE]" = Right OpenAIDone-    | otherwise = OpenAIChunk <$> jsonData event--data OpenAIStreamChunk = OpenAIStreamChunk-  { streamChoices :: [OpenAIStreamChoice]-  , streamUsage :: Maybe TokenUsage-  }--instance Aeson.FromJSON OpenAIStreamChunk where-  parseJSON = Aeson.withObject "OpenAIStreamChunk" $ \obj ->-    OpenAIStreamChunk-      <$> obj Aeson..:? "choices" Aeson..!= []-      <*> (obj Aeson..:? "usage" >>= traverse parseOpenAIStreamUsage)--parseOpenAIStreamUsage :: Value -> Parser TokenUsage-parseOpenAIStreamUsage = Aeson.withObject "OpenAIStreamUsage" $ \obj ->-  TokenUsage-    <$> obj Aeson..: "prompt_tokens"-    <*> obj Aeson..: "completion_tokens"-    <*> obj Aeson..: "total_tokens"--data OpenAIStreamChoice = OpenAIStreamChoice-  { streamChoiceIndex :: Int-  , streamChoiceDelta :: OpenAIStreamDelta-  }--instance Aeson.FromJSON OpenAIStreamChoice where-  parseJSON = Aeson.withObject "OpenAIStreamChoice" $ \obj ->-    OpenAIStreamChoice-      <$> obj Aeson..: "index"-      <*> obj Aeson..: "delta"--data OpenAIStreamDelta = OpenAIStreamDelta-  { streamContent :: Maybe Text-  , streamToolCalls :: [OpenAIStreamToolCall]-  }--instance Aeson.FromJSON OpenAIStreamDelta where-  parseJSON = Aeson.withObject "OpenAIStreamDelta" $ \obj ->-    OpenAIStreamDelta-      <$> obj Aeson..:? "content"-      <*> obj Aeson..:? "tool_calls" Aeson..!= []--data OpenAIStreamToolCall = OpenAIStreamToolCall-  { streamToolCallIndex :: Int-  , streamToolCallId :: Maybe Text-  , streamToolCallName :: Maybe Text-  , streamToolCallArguments :: Maybe Text-  }--instance Aeson.FromJSON OpenAIStreamToolCall where-  parseJSON = Aeson.withObject "OpenAIStreamToolCall" $ \obj -> do-    streamToolCallIndex <- obj Aeson..: "index"-    streamToolCallId <- obj Aeson..:? "id"-    streamFunction <- obj Aeson..:? "function"-    let streamToolCallName = streamFunction >>= parseMaybe (Aeson..: "name")-        streamToolCallArguments = streamFunction >>= parseMaybe (Aeson..: "arguments")-    pure-      OpenAIStreamToolCall-        { streamToolCallIndex-        , streamToolCallId-        , streamToolCallName-        , streamToolCallArguments-        }--data PartialToolCall = PartialToolCall-  { partialToolCallId :: Maybe Text-  , partialToolCallName :: Maybe Text-  , partialToolCallArguments :: Text-  }--type OpenAIStreamApi =-  "v1"-    :> "chat"-    :> "completions"-    :> Header "Authorization" Text-    :> ReqBody '[JSON] Value-    :> PostServerSentEvents (ConduitT () OpenAIStreamEvent IO ())--openAIStreamClient ::-  Maybe Text -> Value -> ClientM (ConduitT () OpenAIStreamEvent IO ())-openAIStreamClient = client (Proxy :: Proxy OpenAIStreamApi)--streamRequestBody :: CC.CreateChatCompletion -> Maybe Value -> Value-streamRequestBody request options = case Aeson.toJSON request of-  Object fields ->-    Object $-      KeyMap.insert "stream_options" (object ["include_usage" Aeson..= True]) $-        KeyMap.insert "stream" (Bool True) $-          KeyMap.union fields optionFields-  value -> value-  where-    optionFields = case options of-      Just (Object fields) -> fields-      _ -> mempty---- | Create standard OpenAI provider instance-newOpenAI :: Text -> Text -> OpenAI-newOpenAI key mName =-  OpenAI-    { apiKey = key-    , model = mName-    , baseUrl = "https://api.openai.com"-    , temperature = Just 0.7-    }--{- | Create OpenAICompatible provider instance for OpenRouter/Fireworks/Together.--The @endpoint@ should be the __base URL__ only (e.g.-@"https://openrouter.ai/api"@), not the full chat completions path.-The @openai@ package appends @\/v1\/chat\/completions@ automatically.--}-openAICompatible :: Text -> Text -> Text -> OpenAI-openAICompatible key mName endpoint =-  OpenAI-    { apiKey = key-    , model = mName-    , baseUrl = endpoint-    , temperature = Just 0.7-    }---- ------------------------------------------------------------------------------ Conversion: langchain-hs Message -> openai package Message--- ------------------------------------------------------------------------------- | Convert a langchain 'ContentBlock' to an openai 'CC.Content'.-contentBlockToOAI :: ContentBlock -> CC.Content-contentBlockToOAI (TextBlock t) = CC.Text {CC.text = t}-contentBlockToOAI (ImageBlock ImageContent {imageSource = ImageUrl url}) =-  CC.Image_URL {CC.image_url = CC.ImageURL {CC.url = url, CC.detail = Nothing}}-contentBlockToOAI (ImageBlock ImageContent {imageSource = ImageBase64 (Just mime) b64}) =-  CC.Image_URL-    { CC.image_url =-        CC.ImageURL-          { CC.url = "data:" <> mime <> ";base64," <> b64-          , CC.detail = Nothing-          }-    }-contentBlockToOAI (ImageBlock ImageContent {imageSource = ImageBase64 Nothing b64}) =-  CC.Image_URL-    { CC.image_url =-        CC.ImageURL-          { CC.url = "data:application/octet-stream;base64," <> b64-          , CC.detail = Nothing-          }-    }-contentBlockToOAI (AudioBlock _mime _b64) =-  -- Audio blocks are represented as text placeholders in the request-  CC.Text {CC.text = "[Audio content]"}-contentBlockToOAI (DataBlock _) =-  CC.Text {CC.text = "[Data block]"}---- | Convert a langchain 'Message' to an openai package 'CC.Message'.-toLangchainOAIMessage :: Message -> CC.Message (V.Vector CC.Content)-toLangchainOAIMessage msg =-  let contents = V.fromList $ map contentBlockToOAI (NonEmpty.toList (messageContents msg))-   in case messageRole msg of-        System ->-          CC.System {CC.content = contents, CC.name = messageName msg}-        User ->-          CC.User {CC.content = contents, CC.name = messageName msg}-        Assistant ->-          CC.Assistant-            { CC.assistant_content = Just contents-            , CC.refusal = Nothing-            , CC.name = messageName msg-            , CC.assistant_audio = Nothing-            , CC.tool_calls = V.fromList . map toOAIToolCall <$> messageToolCalls msg-            }-        Tool ->-          CC.Tool-            { CC.content = contents-            , CC.tool_call_id = fromMaybe "" (messageToolId msg)-            }-        -- Developer and Function map to System for the openai package-        Developer ->-          CC.System {CC.content = contents, CC.name = messageName msg}-        Function ->-          CC.System {CC.content = contents, CC.name = messageName msg}-  where-    toOAIToolCall toolCall =-      OTC.ToolCall_Function-        { OTC.id = toolCallId toolCall-        , OTC.function = OTC.Function {OTC.name = toolCallName toolCall, OTC.arguments = arguments toolCall}-        }--    arguments = TE.decodeUtf8 . LBS.toStrict . Aeson.encode . toolCallArguments---- ------------------------------------------------------------------------------ Conversion: openai package response -> langchain-hs Message--- ------------------------------------------------------------------------------- | Convert an openai package 'CC.Choice' response message to a langchain 'Message'.-fromOAIMessage :: CC.Message Text -> Message-fromOAIMessage oaiMsg = case oaiMsg of-  CC.Assistant {CC.assistant_content, CC.tool_calls = oaiToolCalls, CC.name = nm} ->-    let contentText = fromMaybe "" assistant_content-        baseMsg = (assistantMessage contentText) {messageName = nm}-        tcList = case oaiToolCalls of-          Nothing -> Nothing-          Just tcs ->-            Just $-              map-                ( \(OTC.ToolCall_Function {OTC.id = tcId, OTC.function = fn}) ->-                    let argVal = case Aeson.decode (LBS.fromStrict (TE.encodeUtf8 (OTC.arguments fn))) of-                          Just v -> v-                          Nothing -> object []-                     in ToolCall tcId "function" (OTC.name fn) argVal-                )-                (V.toList tcs)-     in baseMsg {messageToolCalls = tcList}-  CC.System {CC.content = c} -> systemMessage c-  CC.User {CC.content = c} -> userMessage c-  CC.Tool {CC.content = c} ->-    textMessage Tool c---- | Convert openai 'OU.Usage' to langchain 'TokenUsage'.-fromOAIUsage :: OU.Usage ctd ptd -> TokenUsage-fromOAIUsage u =-  TokenUsage-    { promptTokens = fromIntegral (OU.prompt_tokens u)-    , completionTokens = fromIntegral (OU.completion_tokens u)-    , totalTokens = fromIntegral (OU.total_tokens u)-    }---- ------------------------------------------------------------------------------ ChatModel instance--- -----------------------------------------------------------------------------instance ChatModel OpenAI where-  type ModelConfig OpenAI = Value--  invoke provider inputMsgs mbOptions = do-    resp <- liftIO $ first asText <$> try createComplention-    case resp of-      Left err -> throwError $ llmError' err-      Right (CC.ChatCompletionObject {CC.choices = choicesVec, CC.usage = oaiUsage}) -> do-        case V.toList choicesVec of-          [] -> throwError $ llmError "Empty choices array in OpenAI response" Nothing Nothing-          (choice : _) -> do-            let respMsg = fromOAIMessage $ CC.message choice-                _usage = fromOAIUsage oaiUsage-            pure respMsg {messageToolCalls = messageToolCalls respMsg}-    where-      createComplention = do-        methods <- getMethods-        let baseBody = reqBody provider inputMsgs-            body = mergeOptions baseBody mbOptions-        OAI.createChatCompletion methods body-      getMethods = do-        clientEnv <- OAI.getClientEnv (normalizeBaseUrl (baseUrl provider))-        pure $ OAI.makeMethods clientEnv (apiKey provider) Nothing Nothing--  stream provider inputMsgs options = do-    yield $ LLMStart rId (model provider) inputMsgs--    (accumulated, toolCalls, usage) <--      callbackSource openAIEvents-        .| receiveChunks "" Map.empty Nothing--    yield $ LLMEnd rId ((assistantMessage accumulated) {messageToolCalls = toolCalls}) usage-    where-      rId = "openai-stream-run"-      receiveChunks accumulated toolCalls usage =-        await >>= \case-          Nothing -> finishStream-          Just (Left err) -> throwError $ llmError' err-          Just (Right OpenAIDone) -> finishStream-          Just (Right (OpenAIChunk OpenAIStreamChunk {streamChoices, streamUsage})) -> do-            let (texts, nextToolCalls) = handleChoice $ choice0 streamChoices-                nextUsage = streamUsage <|> usage-            mapM_ (\text -> yield $ LLMChunk rId text Nothing) texts-            receiveChunks (accumulated <> mconcat texts) nextToolCalls nextUsage-        where-          choice0 = List.find $ (== 0) . streamChoiceIndex--          handleChoice Nothing = ([], toolCalls)-          handleChoice (Just OpenAIStreamChoice {streamChoiceDelta = OpenAIStreamDelta {streamContent, streamToolCalls}}) =-            let nextToolCalls = List.foldl' addToolCall toolCalls streamToolCalls-             in (maybe [] pure streamContent, nextToolCalls)--          addToolCall-            toolCalls'-            OpenAIStreamToolCall-              { streamToolCallIndex-              , streamToolCallId-              , streamToolCallName-              , streamToolCallArguments-              } =-              Map.alter (Just . update) streamToolCallIndex toolCalls'-              where-                update curr =-                  let prevId = curr >>= partialToolCallId-                      prevName = curr >>= partialToolCallName-                      prevArgs = maybe "" partialToolCallArguments curr-                      nextArgs = fromMaybe "" streamToolCallArguments-                   in PartialToolCall-                        { partialToolCallId = streamToolCallId <|> prevId-                        , partialToolCallName = streamToolCallName <|> prevName-                        , partialToolCallArguments = prevArgs <> nextArgs-                        }--          finishStream = do-            let finalizeToolCalls = traverse toToolCall $ Map.elems toolCalls-            finalToolCalls <- lift finalizeToolCalls-            mapM_ (yield . LLMChunk rId "" . Just) finalToolCalls-            pure (accumulated, nonEmpty finalToolCalls, usage)-            where-              nonEmpty [] = Nothing-              nonEmpty xs = Just xs--              toToolCall :: PartialToolCall -> StreamM ToolCall-              toToolCall PartialToolCall {partialToolCallId, partialToolCallName, partialToolCallArguments} = do-                toolCallId <--                  fromMaybeOrThrow "OpenAI stream ended with a tool call missing an id" partialToolCallId-                toolCallName <--                  fromMaybeOrThrow "OpenAI stream ended with a tool call missing a function name" partialToolCallName-                toolCallArguments <--                  either-                    (throwLlmError . ("Invalid JSON arguments in OpenAI tool call: " <>) . T.pack)-                    pure-                    (decode partialToolCallArguments)-                pure ToolCall {toolCallId, toolCallType = "function", toolCallName, toolCallArguments}--              throwLlmError = throwError . llmError'-              fromMaybeOrThrow err = maybe (throwLlmError err) pure-              decode = Aeson.eitherDecode . LBS.fromStrict . TE.encodeUtf8--      -- \| Internal function to handle streaming events from OpenAI.-      openAIEvents emit = do-        result <- try $ do-          manager <- newManager tlsManagerSettings-          let baseUrl' = T.unpack $ normalizeBaseUrl $ baseUrl provider-          clientEnv <- mkClientEnv manager <$> parseBaseUrl baseUrl'-          let body = reqBody provider inputMsgs-              bearerToken = Just $ "Bearer " <> apiKey provider-              request = openAIStreamClient bearerToken $ streamRequestBody body options--          withClientM request clientEnv $ \case-            Left err ->-              emit $ Left $ T.pack $ show err-            Right source ->-              runConduit $-                source .| C.mapM_ (emit . Right)--        case result of-          Left err-            | Just AsyncCancelled <- fromException err -> throwIO err-            | otherwise -> emit $ Left $ asText err-          Right () -> pure ()---- | Convert a 'SomeException' to 'Text' for error reporting.-asText :: SomeException -> Text-asText ex = T.pack $ show (ex :: SomeException)---- | Construct a 'LangchainError' for LLM errors with optional details.-llmError' :: Text -> LangchainError-llmError' msg = llmError msg Nothing Nothing---- | Merge option fields (tools, tool_choice, response_format, etc.) into a 'CreateChatCompletion'.-mergeOptions :: CC.CreateChatCompletion -> Maybe Value -> CC.CreateChatCompletion-mergeOptions body Nothing = body-mergeOptions body (Just opts) =-  case Aeson.toJSON body of-    Object baseFields ->-      let optFields = case opts of-            Object fs -> fs-            _ -> mempty-          merged = KeyMap.union optFields baseFields-       in case Aeson.fromJSON (Object merged) of-            Aeson.Success merged' -> merged'-            _ -> body -- fallback: ignore unparseable options-    _ -> body---- | Construct the request body for OpenAI chat completion.-reqBody :: OpenAI -> [Message] -> CC.CreateChatCompletion-reqBody provider inputMsgs =-  CC._CreateChatCompletion-    { CC.messages = toVec inputMsgs-    , CC.model = OM.Model $ model provider-    , CC.temperature = temperature provider-    }-  where-    toVec = V.fromList . map toLangchainOAIMessage--{- | Normalize base URL to ensure compatibility with the @openai@ package.-Strips any trailing @/v1/chat/completions@, @/chat/completions@, or @/v1@-so that Servant's route constructs the expected URL path.--}-normalizeBaseUrl :: Text -> Text-normalizeBaseUrl rawUrl =-  let u0 = T.dropWhileEnd (== '/') rawUrl-      u1-        | "/v1/chat/completions" `T.isSuffixOf` u0 =-            T.dropEnd (T.length "/v1/chat/completions") u0-        | "/chat/completions" `T.isSuffixOf` u0 =-            T.dropEnd (T.length "/chat/completions") u0-        | "/v1" `T.isSuffixOf` u0 =-            T.dropEnd (T.length "/v1") u0-        | otherwise =-            u0-   in T.dropWhileEnd (== '/') u1---- ------------------------------------------------------------------------------ Backward-compatible parseOpenAIResponse--- -----------------------------------------------------------------------------{- | Parse a raw OpenAI JSON response 'Value' into a langchain 'Message'-and optional 'TokenUsage'.--This function is provided for backward compatibility. New code should use-the typed @openai@ package types directly.--}-parseOpenAIResponse :: Value -> Either String (Message, Maybe TokenUsage)-parseOpenAIResponse = parseEither $ Aeson.withObject "OpenAIResponse" $ \o -> do-  choices <- o Aeson..: "choices"-  usageVal <- o Aeson..:? "usage"-  mbUsage <- case usageVal of-    Nothing -> pure Nothing-    Just u -> flip (Aeson.withObject "Usage") u $ \uo -> do-      pTok <- uo Aeson..:? "prompt_tokens" Aeson..!= 0-      cTok <- uo Aeson..:? "completion_tokens" Aeson..!= 0-      tTok <- uo Aeson..:? "total_tokens" Aeson..!= 0-      pure $ Just $ TokenUsage pTok cTok tTok-  case choices of-    [] -> fail "Empty choices array in OpenAI response"-    (c : _) -> flip (Aeson.withObject "Choice") c $ \ch -> do-      msgObj <- ch Aeson..: "message"-      contentTxt <- msgObj Aeson..:? "content" Aeson..!= ""-      mbToolCalls <- msgObj Aeson..:? "tool_calls"-      cToolCalls <- case mbToolCalls of-        Nothing -> pure Nothing-        Just tcs -> do-          calls <- forM (tcs :: [Value]) $ Aeson.withObject "ToolCall" $ \tcObj -> do-            tcId <- tcObj Aeson..:? "id" Aeson..!= ""-            fnObj <- tcObj Aeson..: "function"-            fnName <- fnObj Aeson..: "name"-            fnArgsVal <- fnObj Aeson..:? "arguments"-            let fnArgs = case fnArgsVal of-                  Just (String s) -> case Aeson.decode (LBS.fromStrict (TE.encodeUtf8 s)) of-                    Just val -> val-                    Nothing -> object []-                  Just obj@(Object _) -> obj-                  _ -> object []-            pure $ ToolCall tcId "function" fnName fnArgs-          pure (Just calls)-      let msg = (assistantMessage contentTxt) {messageToolCalls = cToolCalls}-      pure (msg, mbUsage)---- | Bind tools to an OpenAI model by merging tool definitions into the options Value-instance ToolBinder OpenAI m where-  bindToolsConfig tools mbOpts =-    case tools of-      [] -> mbOpts-      _ ->-        let toolsVal = openAITools tools OpenAIToolAuto-         in Just $ case (mbOpts, toolsVal) of-              (Nothing, v) -> v-              (Just (Object existing), Object newFields) ->-                Object (KeyMap.union newFields existing)-              (Just existing, _) -> existing
− src/Langchain/Resilience/CircuitBreaker.hs
@@ -1,119 +0,0 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}--{- |-Module      : Langchain.Resilience.CircuitBreaker-Description : Circuit breaker pattern for LLM provider failover and graceful degradation-Copyright   : (c) 2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Implements the Circuit Breaker pattern (Closed, Open, HalfOpen) to prevent cascading failures-when upstream LLM APIs or vector stores experience outages.--}-module Langchain.Resilience.CircuitBreaker-  ( CircuitState (..)-  , CircuitBreakerConfig (..)-  , defaultCircuitConfig-  , CircuitBreaker (..)-  , newCircuitBreaker-  , getCircuitState-  , withCircuitBreaker-  ) where--import Control.Concurrent.STM-import Control.Monad.Except (MonadError, catchError, throwError)-import Control.Monad.IO.Class (MonadIO, liftIO)-import Data.Aeson (FromJSON, ToJSON)-import Data.Text (Text)-import Data.Time.Clock (UTCTime, diffUTCTime, getCurrentTime)-import GHC.Generics (Generic)--import Langchain.Core.Error (LangchainError, internalError)---- | State of the circuit breaker-data CircuitState-  = CircuitClosed-  | CircuitOpen !UTCTime -- Timestamp when opened-  | CircuitHalfOpen-  deriving (Show, Eq, Generic, ToJSON, FromJSON)---- | Configuration parameters for the circuit breaker-data CircuitBreakerConfig = CircuitBreakerConfig-  { failureThreshold :: !Int-  , resetTimeoutSec :: !Double-  }-  deriving (Show, Eq, Generic, ToJSON, FromJSON)---- | Sensible default configuration (5 failures to open, 30 seconds reset timeout)-defaultCircuitConfig :: CircuitBreakerConfig-defaultCircuitConfig =-  CircuitBreakerConfig-    { failureThreshold = 5-    , resetTimeoutSec = 30.0-    }---- | Circuit breaker handle backed by STM TVar-data CircuitBreaker = CircuitBreaker-  { circuitName :: !Text-  , circuitConfig :: !CircuitBreakerConfig-  , circuitStateVar :: !(TVar (CircuitState, Int)) -- (State, consecutive failures)-  }---- | Construct a new CircuitBreaker-newCircuitBreaker :: MonadIO m => Text -> CircuitBreakerConfig -> m CircuitBreaker-newCircuitBreaker name cfg = liftIO $ do-  var <- newTVarIO (CircuitClosed, 0)-  pure $ CircuitBreaker name cfg var---- | Query current state of the circuit breaker-getCircuitState :: MonadIO m => CircuitBreaker -> m CircuitState-getCircuitState CircuitBreaker {..} = liftIO $ do-  (st, _) <- readTVarIO circuitStateVar-  pure st---- | Execute a protected action through the circuit breaker-withCircuitBreaker ::-  (MonadIO m, MonadError LangchainError m) =>-  CircuitBreaker ->-  m a ->-  m a-withCircuitBreaker CircuitBreaker {..} action = do-  now <- liftIO getCurrentTime-  canProceed <- liftIO $ atomically $ do-    (st, _) <- readTVar circuitStateVar -- Only the state is needed to gate; count is managed in the error handler below-    case st of-      CircuitClosed -> pure True-      CircuitHalfOpen -> pure True-      CircuitOpen openTime ->-        if diffUTCTime now openTime >= realToFrac (resetTimeoutSec circuitConfig)-          then do-            writeTVar circuitStateVar (CircuitHalfOpen, 0)-            pure True-          else pure False--  if not canProceed-    then-      throwError $-        internalError-          ("Circuit breaker '" <> circuitName <> "' is OPEN. Fast-failing request.")-          (Just circuitName)-          Nothing-    else do-      res <--        action `catchError` \err -> do-          liftIO $ atomically $ do-            (st, fails) <- readTVar circuitStateVar-            let newFails = fails + 1-            if newFails >= failureThreshold circuitConfig-              then writeTVar circuitStateVar (CircuitOpen now, newFails)-              else writeTVar circuitStateVar (st, newFails)-          throwError err--      -- On success, reset circuit to closed and reset failure count-      liftIO $ atomically $ writeTVar circuitStateVar (CircuitClosed, 0)-      pure res
− src/Langchain/Resilience/Retry.hs
@@ -1,114 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE RecordWildCards #-}--{- |-Module      : Langchain.Resilience.Retry-Description : Retry policies with exponential backoff and token-bucket rate limiting-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Resilience combinators for network calls and LLM provider invocations.--}-module Langchain.Resilience.Retry-  ( RetryPolicy (..)-  , defaultRetryPolicy-  , withRetry-  , RateLimiter (..)-  , newRateLimiter-  , withRateLimit-  ) where--import Control.Concurrent (threadDelay)-import Control.Concurrent.STM-import Control.Monad (when)-import Control.Monad.Except (MonadError, catchError, throwError)-import Control.Monad.IO.Class (MonadIO, liftIO)-import Data.Time.Clock-import System.Random (randomRIO)--import Langchain.Core.Error (LangchainError)---- | Exponential backoff retry policy-data RetryPolicy = RetryPolicy-  { maxRetries :: !Int-  , baseDelayMicros :: !Int-  , maxDelayMicros :: !Int-  , useJitter :: !Bool-  }-  deriving (Show, Eq)---- | Default retry policy (3 retries, base 50ms, max 2s, with jitter)-defaultRetryPolicy :: RetryPolicy-defaultRetryPolicy =-  RetryPolicy-    { maxRetries = 3-    , baseDelayMicros = 50000-    , maxDelayMicros = 2000000-    , useJitter = True-    }---- | Execute an action with retry according to RetryPolicy on LangchainError-withRetry ::-  (MonadIO m, MonadError LangchainError m) =>-  RetryPolicy ->-  m a ->-  m a-withRetry policy action = go (maxRetries policy) (baseDelayMicros policy)-  where-    go retriesLeft currentDelay =-      action `catchError` \err ->-        if retriesLeft <= 0-          then throwError err-          else do-            delayWithJitter <--              if useJitter policy-                then liftIO $ randomRIO (currentDelay `div` 2, currentDelay)-                else pure currentDelay-            liftIO $ threadDelay delayWithJitter-            let nextDelay = min (maxDelayMicros policy) (currentDelay * 2)-            go (retriesLeft - 1) nextDelay---- | Token bucket rate limiter backed by STM TVars-data RateLimiter = RateLimiter-  { bucketCapacity :: !Double-  , refillRatePerSec :: !Double-  , tokensVar :: !(TVar Double)-  , lastRefillVar :: !(TVar UTCTime)-  }---- | Construct a new Token Bucket RateLimiter (e.g. capacity = 10 tokens, refill = 5 tokens/sec)-newRateLimiter :: MonadIO m => Double -> Double -> m RateLimiter-newRateLimiter cap rate = liftIO $ do-  now <- getCurrentTime-  tVar <- newTVarIO cap-  rVar <- newTVarIO now-  pure $ RateLimiter cap rate tVar rVar---- | Execute an action subject to token-bucket rate limiting (blocks if bucket empty)-withRateLimit :: (MonadIO m) => RateLimiter -> m a -> m a-withRateLimit RateLimiter {..} action = do-  liftIO $ do-    waitForToken-  action-  where-    waitForToken = do-      now <- getCurrentTime-      waitNeeded <- atomically $ do-        lastTime <- readTVar lastRefillVar-        tokens <- readTVar tokensVar-        let elapsedSecs = realToFrac (diffUTCTime now lastTime) :: Double-            refilledTokens = min bucketCapacity (tokens + elapsedSecs * refillRatePerSec)-        if refilledTokens >= 1.0-          then do-            writeTVar tokensVar (refilledTokens - 1.0)-            writeTVar lastRefillVar now-            pure (0 :: Int)-          else do-            let deficit = 1.0 - refilledTokens-                sleepSecs = deficit / refillRatePerSec-            pure (ceiling (sleepSecs * 1000000) :: Int)-      when (waitNeeded > 0) $ do-        threadDelay waitNeeded-        waitForToken
− src/Langchain/Retriever/BM25.hs
@@ -1,151 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TypeFamilies #-}--{- |-Module      : Langchain.Retriever.BM25-Description : Okapi BM25 Sparse Inverted Index Retriever-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Pure Haskell implementation of the Okapi BM25 ranking algorithm for sparse keyword retrieval.-Supports document addition, customized k1 and b parameters, and fast inverted index scoring.--}-module Langchain.Retriever.BM25-  ( BM25Index (..)-  , newBM25Index-  , newBM25IndexWithParams-  , addDocumentsBM25-  , bm25Search-  , bm25SearchWithScores-  , tokenize-  ) where--import Langchain.Core.Runnable (Runnable (..))--import Data.Char (isAlphaNum)-#if MIN_VERSION_base(4,20,0)-import Data.List (sortBy)-#else-import Data.List (foldl', sortBy)-#endif-import Data.Map.Strict (Map)-import qualified Data.Map.Strict as Map-import Data.Ord (Down (..), comparing)-import Data.Text (Text)-import qualified Data.Text as T-import qualified Data.Text.Lazy as TL-import GHC.Generics (Generic)--import Langchain.DocumentLoader.Core (Document (..))-import Langchain.Retriever.Core (Retriever (..))---- | BM25 Index containing documents, lengths, and inverted index-data BM25Index = BM25Index-  { bm25Docs :: ![Document]-  , bm25DocLens :: !(Map Int Int)-  , bm25AvgDocLen :: !Double-  , bm25InvertedIndex :: !(Map Text (Map Int Int))-  , bm25K1 :: !Double-  , bm25B :: !Double-  }-  deriving (Show, Eq, Generic)--instance Retriever BM25Index where-  getRelevantDocuments index query = pure $ bm25Search index query 5---- | Tokenize text into lowercased alphanumeric terms-tokenize :: Text -> [Text]-tokenize = filter (not . T.null) . map (T.filter isAlphaNum . T.toLower) . T.words---- | Construct a BM25 index with default parameters (k1 = 1.5, b = 0.75)-newBM25Index :: [Document] -> BM25Index-newBM25Index = newBM25IndexWithParams 1.5 0.75---- | Construct a BM25 index with customized k1 and b parameters-newBM25IndexWithParams :: Double -> Double -> [Document] -> BM25Index-newBM25IndexWithParams k1 b docs =-  let indexedDocs = zip [0 ..] docs-      docLensList = [(i, length (tokenize (TL.toStrict (pageContent d)))) | (i, d) <- indexedDocs]-      docLens = Map.fromList docLensList-      totalTokens = sum (map snd docLensList)-      nDocs = length docs-      avgLen = if nDocs > 0 then fromIntegral totalTokens / fromIntegral nDocs else 0.0--      -- Build inverted index: term -> docIndex -> termFrequency-      invIndex = foldl' addDocToInvertedIndex Map.empty indexedDocs-   in BM25Index-        { bm25Docs = docs-        , bm25DocLens = docLens-        , bm25AvgDocLen = avgLen-        , bm25InvertedIndex = invIndex-        , bm25K1 = k1-        , bm25B = b-        }-  where-    addDocToInvertedIndex acc (docIdx, doc) =-      let tokens = tokenize (TL.toStrict (pageContent doc))-          tfs = foldl' (\m t -> Map.insertWith (+) t 1 m) Map.empty tokens-       in Map.foldlWithKey'-            (\accM t count -> Map.insertWith Map.union t (Map.singleton docIdx count) accM)-            acc-            tfs---- | Add new documents to an existing BM25 index-addDocumentsBM25 :: [Document] -> BM25Index -> BM25Index-addDocumentsBM25 newDocs BM25Index {..} =-  newBM25IndexWithParams bm25K1 bm25B (bm25Docs ++ newDocs)---- | Perform BM25 search returning top-k documents sorted by score-bm25Search :: BM25Index -> Text -> Int -> [Document]-bm25Search index query k = map fst (bm25SearchWithScores index query k)---- | Perform BM25 search returning top-k documents with their relevance scores-bm25SearchWithScores :: BM25Index -> Text -> Int -> [(Document, Double)]-bm25SearchWithScores BM25Index {..} query k-  | null bm25Docs || null queryTokens = []-  | otherwise =-      let nTotalDocs = fromIntegral (length bm25Docs)-          -- Accumulate BM25 score per document-          scores = foldl' (scoreTerm nTotalDocs) (Map.empty :: Map Int Double) queryTokens-          indexedDocs = zip [0 ..] bm25Docs-          scoredList =-            [ (doc, score)-            | (idx, doc) <- indexedDocs-            , let score = Map.findWithDefault 0.0 idx scores-            , score > 0.0-            ]-          sorted = sortBy (comparing (Down . snd)) scoredList-       in take k sorted-  where-    queryTokens = tokenize query--    scoreTerm nTotalDocs accScores term =-      case Map.lookup term bm25InvertedIndex of-        Nothing -> accScores-        Just postingMap ->-          let nDocWithTerm = fromIntegral (Map.size postingMap)-              -- Okapi BM25 IDF: ln(1 + (N - n + 0.5) / (n + 0.5))-              idf = log (1.0 + (nTotalDocs - nDocWithTerm + 0.5) / (nDocWithTerm + 0.5))-           in Map.foldlWithKey' (updateDocScore idf) accScores postingMap--    updateDocScore idf acc docIdx tf =-      let docLen = fromIntegral (Map.findWithDefault 1 docIdx bm25DocLens)-          normLen = if bm25AvgDocLen > 0 then docLen / bm25AvgDocLen else 1.0-          tfD = fromIntegral tf-          -- Okapi BM25 TF component-          tfWeight = (tfD * (bm25K1 + 1.0)) / (tfD + bm25K1 * (1.0 - bm25B + bm25B * normLen))-          scoreDelta = idf * tfWeight-       in Map.insertWith (+) docIdx scoreDelta acc---- | 'BM25Index' implements 'Runnable' mapping a search query 'Text' to '[Document]' results.-instance Monad m => Runnable BM25Index m where-  type RunnableInput BM25Index = Text-  type RunnableOutput BM25Index = [Document]-  invoke idx query = pure $ Right (bm25Search idx query 5)
− src/Langchain/Retriever/Core.hs
@@ -1,66 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}--{- |-Module      : Langchain.Retriever.Core-Description : Retrieval mechanism implementation for LangChain Haskell-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Effect-polymorphic document retrieval abstraction.--}-module Langchain.Retriever.Core-  ( Retriever (..)-  , VectorStoreRetriever (..)-  , retrieveWithCallbacks-  , runRetriever-  ) where--import Control.Monad.Except (MonadError, runExceptT)-import Control.Monad.IO.Class (MonadIO, liftIO)-import Data.Text (Text)-import qualified Data.Text.Lazy as TL-import Data.Time.Clock (diffUTCTime, getCurrentTime)--import Langchain.Callback.Manager (CallbackEvent (..), CallbackManager, dispatchEvent)-import Langchain.Core.Error (LangchainError)-import Langchain.Core.Runnable (RunnableTree, runLambda)-import Langchain.DocumentLoader.Core (Document (..))-import Langchain.VectorStore.Core (VectorStore, similaritySearch)---- | Effect-polymorphic Retriever typeclass-class Retriever a where-  getRelevantDocuments ::-    (MonadIO m, MonadError LangchainError m) =>-    a ->-    Text ->-    m [Document]---- | Vector store-backed retriever-newtype VectorStore a => VectorStoreRetriever a = VectorStoreRetriever {vs :: a}-  deriving (Eq, Show)--instance VectorStore a => Retriever (VectorStoreRetriever a) where-  getRelevantDocuments (VectorStoreRetriever v) query = similaritySearch v query 5---- | Retrieve documents with lifecycle callbacks dispatched to CallbackManager-retrieveWithCallbacks ::-  (Retriever a, MonadIO m, MonadError LangchainError m) =>-  CallbackManager ->-  Text ->-  a ->-  Text ->-  m [Document]-retrieveWithCallbacks mgr name ret query = do-  start <- liftIO getCurrentTime-  dispatchEvent mgr (OnRetrieverStart name query start)-  docs <- getRelevantDocuments ret query-  end <- liftIO getCurrentTime-  let durMicros = round (diffUTCTime end start * 1000000)-  dispatchEvent mgr (OnRetrieverEnd name (map (TL.toStrict . pageContent) docs) durMicros end)-  pure docs---- | Lift any 'Retriever' into a 'Text' -> '[Document]' pipeline step in a 'RunnableTree'.-runRetriever :: (Retriever a, MonadIO m) => a -> RunnableTree m Text [Document]-runRetriever ret = runLambda $ \query -> runExceptT (getRelevantDocuments ret query)
− src/Langchain/Retriever/Hybrid.hs
@@ -1,155 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TypeFamilies #-}--{- |-Module      : Langchain.Retriever.Hybrid-Description : Hybrid Dense + Sparse Retriever with Reciprocal Rank Fusion (RRF)-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Combines sparse keyword search (BM25) and dense semantic vector search using-Reciprocal Rank Fusion (RRF) scoring: RRF(d) = sum_i ( weight_i / (k + rank_i(d)) ).--}-module Langchain.Retriever.Hybrid-  ( HybridRetriever (..)-  , newHybridRetriever-  , newHybridRetrieverWithWeights-  , searchHybrid-  , searchHybridWithScores-  , reciprocalRankFusion-  ) where--import Control.Monad.Except (runExceptT)-import Control.Monad.IO.Class (MonadIO, liftIO)-import Langchain.Core.Runnable (Runnable (..))-#if MIN_VERSION_base(4,20,0)-import Data.List (sortBy)-#else-import Data.List (foldl', sortBy)-#endif-import qualified Data.Map.Strict as Map-import Data.Ord (Down (..), comparing)-import Data.Text (Text)--import Langchain.DocumentLoader.Core (Document (..))-import Langchain.Retriever.BM25 (BM25Index, bm25Search)-import Langchain.Retriever.Core (Retriever (..))---- | Configuration and handles for Hybrid Retrieval-data HybridRetriever = HybridRetriever-  { hybridBM25 :: !BM25Index-  , hybridVectorSearch :: !(Text -> Int -> IO [Document])-  , hybridRrfK :: !Double-  , hybridDenseWeight :: !Double-  , hybridSparseWeight :: !Double-  }--instance Show HybridRetriever where-  show HybridRetriever {..} =-    "HybridRetriever { hybridRrfK = "-      ++ show hybridRrfK-      ++ ", hybridDenseWeight = "-      ++ show hybridDenseWeight-      ++ ", hybridSparseWeight = "-      ++ show hybridSparseWeight-      ++ " }"--instance Retriever HybridRetriever where-  getRelevantDocuments hr query = searchHybrid hr query 5---- | Construct a default Hybrid Retriever (rrfK = 60.0, equal weights = 1.0)-newHybridRetriever ::-  BM25Index ->-  (Text -> Int -> IO [Document]) ->-  HybridRetriever-newHybridRetriever bm25 vecSearch =-  newHybridRetrieverWithWeights bm25 vecSearch 60.0 1.0 1.0---- | Construct a Hybrid Retriever with custom RRF smoothing and weights-newHybridRetrieverWithWeights ::-  BM25Index ->-  (Text -> Int -> IO [Document]) ->-  Double ->-  Double ->-  Double ->-  HybridRetriever-newHybridRetrieverWithWeights bm25 vecSearch rrfK denseW sparseW =-  HybridRetriever-    { hybridBM25 = bm25-    , hybridVectorSearch = vecSearch-    , hybridRrfK = rrfK-    , hybridDenseWeight = denseW-    , hybridSparseWeight = sparseW-    }---- | Compute Reciprocal Rank Fusion score for documents across ranked lists-reciprocalRankFusion ::-  Double ->-  [([Document], Double)] -> -- List of (ranked documents, weight)-  [(Document, Double)]-reciprocalRankFusion rrfK rankedLists =-  let scoreMap = foldl' processList Map.empty rankedLists-      docLookup = foldl' buildLookup Map.empty [d | (docs, _) <- rankedLists, d <- docs]-      scoredDocs =-        [ (doc, score)-        | (contentKey, score) <- Map.toList scoreMap-        , Just doc <- [Map.lookup contentKey docLookup]-        ]-   in sortBy (comparing (Down . snd)) scoredDocs-  where-    processList accMap (docs, weight) =-      let indexed = zip [1 ..] docs-       in foldl' (updateScore weight) accMap indexed--    updateScore weight acc (rank, doc) =-      let key = pageContent doc-          delta = weight / (rrfK + rank)-       in Map.insertWith (+) key delta acc--    buildLookup acc doc = Map.insert (pageContent doc) doc acc---- | Execute hybrid search returning top-k documents-searchHybrid ::-  (MonadIO m) =>-  HybridRetriever ->-  Text ->-  Int ->-  m [Document]-searchHybrid hr query k = map fst <$> searchHybridWithScores hr query k---- | Execute hybrid search returning top-k documents with RRF scores-searchHybridWithScores ::-  (MonadIO m) =>-  HybridRetriever ->-  Text ->-  Int ->-  m [(Document, Double)]-searchHybridWithScores HybridRetriever {..} query k = do-  -- 1. Run sparse BM25 search (fetch 2 * k candidates)-  let sparseDocs = bm25Search hybridBM25 query (k * 2)--  -- 2. Run dense vector search (fetch 2 * k candidates)-  denseDocs <- liftIO $ hybridVectorSearch query (k * 2)--  -- 3. Fuse rankings via RRF-  let fused =-        reciprocalRankFusion-          hybridRrfK-          [ (denseDocs, hybridDenseWeight)-          , (sparseDocs, hybridSparseWeight)-          ]--  pure $ take k fused---- | 'HybridRetriever' implements 'Runnable' mapping search query 'Text' to fused '[Document]' results.-instance MonadIO m => Runnable HybridRetriever m where-  type RunnableInput HybridRetriever = Text-  type RunnableOutput HybridRetriever = [Document]-  invoke hr query = runExceptT (searchHybrid hr query 5)
− src/Langchain/TextSplitter/Character.hs
@@ -1,111 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}--{- |-Module      : Langchain.TextSplitter.Character-Description : Character-based text splitting for LLM processing [[10]]-Copyright   : (c) 2025 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Character-based text splitting implementation following LangChain's text splitter concepts.-Splits text into chunks based on separators and maximum chunk sizes, useful for processing-large documents with LLMs.--For more information on text splitting concepts, see the Langchain documentation:-[Langchain TextSplitter](https://python.langchain.com/docs/concepts/text_splitters/).--Example usage:--@--- Split text using default settings (100 char chunks, double newline separator)-splitText defaultCharacterSplitterOps "Long document text..."---- Custom configuration for 500-char chunks with paragraph splitting-customSplit = splitText (CharacterSplitterOps 500 "\n\\s*\n")-@--}-module Langchain.TextSplitter.Character-  ( -- * Configuration-    CharacterSplitterOps (..)-  , defaultCharacterSplitterOps--    -- * Splitting Function-  , splitText-  ) where--import Data.Int (Int64)-import Data.Text.Lazy (Text)-import qualified Data.Text.Lazy as T--{- | Configuration for character-based text splitting-Contains:--- 'chunkSize' : Maximum characters per chunk-- 'separator' : Pattern to split text before chunking--Default values follow LangChain's recommended settings for LLM input preparation.--}-data CharacterSplitterOps = CharacterSplitterOps-  { chunkSize :: Int64-  , separator :: Text-  }-  deriving (Show, Eq)--{- | Default splitter configuration--- 100 character chunks-- Splits on double newlines ("\n\n")-->>> defaultCharacterSplitterOps-CharacterSplitterOps {chunkSize = 100, separator = "\n\n"}--}-defaultCharacterSplitterOps :: CharacterSplitterOps-defaultCharacterSplitterOps =-  CharacterSplitterOps-    { chunkSize = 100-    , separator = "\n\n"-    }--{- | Split text into chunks following LangChain's splitting strategy:- --1. Split by separator first-2. Chunk each segment into specified size-3. Preserve semantic boundaries where possible--Examples:->>> splitText defaultCharacterSplitterOps ""-[]-->>> splitText defaultCharacterSplitterOps "Short text"-["Short text"]-->>> splitText defaultCharacterSplitterOps "Part1\n\nPart2\n\nPart3"-["Part1", "Part2", "Part3"]-->>> splitText (CharacterSplitterOps 20 "\n\n") "Very long text exceeding chunk size..."-["Very long text ex", "ceeding chunk size..."]--}-splitText :: CharacterSplitterOps -> Text -> [Text]-splitText CharacterSplitterOps {..} txt =-  mconcat $-    map-      (T.chunksOf chunkSize . T.strip)-      (if T.null separator then [txt] else T.splitOn separator txt)--{- $examples-Test case patterns demonstrating key behaviors:--1. Empty input handling-   >>> splitText defaultCharacterSplitterOps ""-   []--2. Custom separator usage-   >>> splitText (CharacterSplitterOps 100 "|") "A|B|C"-   ["A", "B", "C"]--3. Combined splitting and chunking-   >>> splitText (CharacterSplitterOps 10 "\n") "1234567890\nABCDEFGHIJ"-   ["1234567890", "ABCDEFGHIJ"]--}
− src/Langchain/TextSplitter/Code.hs
@@ -1,167 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--{- |-Module      : Langchain.TextSplitter.Code-Description : Language-aware code text splitter-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Splits programming language source code at top-level declarations, class/function-boundaries, or language-specific constructs.--}-module Langchain.TextSplitter.Code-  ( Language (..)-  , CodeSplitterOps (..)-  , defaultCodeSplitterOps-  , languageSeparators-  , splitCode-  ) where--import Data.Int (Int64)-import Data.Text.Lazy (Text)--import Langchain.TextSplitter.RecursiveCharacter-  ( RecursiveCharacterSplitterOps (..)-  , splitTextRecursive-  )---- | Supported programming languages for code splitting-data Language-  = Haskell-  | Python-  | JavaScript-  | TypeScript-  | Rust-  | Go-  | Java-  | Cpp-  | CSharp-  | MarkdownCode-  deriving (Show, Eq, Enum, Bounded)---- | Configuration options for code splitting-data CodeSplitterOps = CodeSplitterOps-  { codeLanguage :: Language-  , codeChunkSize :: Int64-  , codeChunkOverlap :: Int64-  }-  deriving (Show, Eq)---- | Return language-specific separator hierarchy-languageSeparators :: Language -> [Text]-languageSeparators Haskell =-  [ "\nmodule "-  , "\ndata "-  , "\nnewtype "-  , "\ntype "-  , "\nclass "-  , "\ninstance "-  , "\n\n"-  , "\n"-  , " "-  , ""-  ]-languageSeparators Python =-  [ "\nclass "-  , "\ndef "-  , "\n\tdef "-  , "\n\n"-  , "\n"-  , " "-  , ""-  ]-languageSeparators JavaScript =-  [ "\nfunction "-  , "\nclass "-  , "\nexport default "-  , "\nexport const "-  , "\nconst "-  , "\nlet "-  , "\nvar "-  , "\n\n"-  , "\n"-  , " "-  , ""-  ]-languageSeparators TypeScript = languageSeparators JavaScript-languageSeparators Rust =-  [ "\nfn "-  , "\npub fn "-  , "\nstruct "-  , "\npub struct "-  , "\nenum "-  , "\npub enum "-  , "\nimpl "-  , "\ntrait "-  , "\n\n"-  , "\n"-  , " "-  , ""-  ]-languageSeparators Go =-  [ "\nfunc "-  , "\ntype "-  , "\n\n"-  , "\n"-  , " "-  , ""-  ]-languageSeparators Java =-  [ "\npublic class "-  , "\nclass "-  , "\npublic interface "-  , "\ninterface "-  , "\npublic enum "-  , "\npublic "-  , "\nprivate "-  , "\nprotected "-  , "\n\n"-  , "\n"-  , " "-  , ""-  ]-languageSeparators Cpp =-  [ "\nclass "-  , "\nstruct "-  , "\nenum "-  , "\ntemplate "-  , "\n\n"-  , "\n"-  , " "-  , ""-  ]-languageSeparators CSharp = languageSeparators Java-languageSeparators MarkdownCode =-  [ "\n# "-  , "\n## "-  , "\n### "-  , "\n#### "-  , "\n```"-  , "\n\n"-  , "\n"-  , " "-  , ""-  ]---- | Default code splitter options for a language-defaultCodeSplitterOps :: Language -> CodeSplitterOps-defaultCodeSplitterOps lang =-  CodeSplitterOps-    { codeLanguage = lang-    , codeChunkSize = 1000-    , codeChunkOverlap = 150-    }---- | Split code using language-specific syntax separators-splitCode :: CodeSplitterOps -> Text -> [Text]-splitCode ops text =-  let seps = languageSeparators (codeLanguage ops)-      recOps =-        RecursiveCharacterSplitterOps-          { chunkSize = codeChunkSize ops-          , chunkOverlap = codeChunkOverlap ops-          , separators = seps-          }-   in splitTextRecursive recOps text
− src/Langchain/TextSplitter/Markdown.hs
@@ -1,137 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--{- |-Module      : Langchain.TextSplitter.Markdown-Description : Markdown document header-aware text splitter-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Splits markdown text based on structural headers (# Header 1, ## Header 2, etc.)-and generates chunks with inherited header context.--}-module Langchain.TextSplitter.Markdown-  ( MarkdownSplitterOps (..)-  , MarkdownChunk (..)-  , defaultMarkdownSplitterOps-  , splitMarkdown-  , splitMarkdownToChunks-  ) where--import Data.Int (Int64)-import Data.Map.Strict (Map)-import qualified Data.Map.Strict as Map-import Data.Text.Lazy (Text)-import qualified Data.Text.Lazy as T--import Langchain.TextSplitter.RecursiveCharacter-  ( RecursiveCharacterSplitterOps (..)-  , defaultRecursiveCharacterSplitterOps-  , splitTextRecursive-  )---- | Represents a chunk of markdown text with its associated header hierarchy-data MarkdownChunk = MarkdownChunk-  { chunkContent :: Text-  , chunkHeaders :: Map Text Text-  }-  deriving (Show, Eq)---- | Configuration options for markdown text splitting-data MarkdownSplitterOps = MarkdownSplitterOps-  { mdChunkSize :: Int64-  , mdChunkOverlap :: Int64-  , headersToSplitOn :: [(Text, Text)] -- e.g. [("#", "Header 1"), ("##", "Header 2"), ("###", "Header 3")]-  }-  deriving (Show, Eq)---- | Default markdown splitter options-defaultMarkdownSplitterOps :: MarkdownSplitterOps-defaultMarkdownSplitterOps =-  MarkdownSplitterOps-    { mdChunkSize = 1000-    , mdChunkOverlap = 100-    , headersToSplitOn =-        [ ("#", "Header 1")-        , ("##", "Header 2")-        , ("###", "Header 3")-        , ("####", "Header 4")-        ]-    }---- | Split markdown document into MarkdownChunks with header metadata-splitMarkdownToChunks :: MarkdownSplitterOps -> Text -> [MarkdownChunk]-splitMarkdownToChunks _ "" = []-splitMarkdownToChunks ops text =-  let rawLines = T.lines text-      sections = groupLinesByHeaders (headersToSplitOn ops) Map.empty rawLines-   in concatMap (subSplitSection ops) sections---- | Split markdown text into plain Text chunks-splitMarkdown :: MarkdownSplitterOps -> Text -> [Text]-splitMarkdown ops text = map chunkContent (splitMarkdownToChunks ops text)---- Group lines into header-annotated sections-groupLinesByHeaders :: [(Text, Text)] -> Map Text Text -> [Text] -> [MarkdownChunk]-groupLinesByHeaders _ _ [] = []-groupLinesByHeaders headerRules currentHeaders ls = go currentHeaders [] ls-  where-    go :: Map Text Text -> [Text] -> [Text] -> [MarkdownChunk]-    go hdrs acc [] = [MarkdownChunk (T.unlines (reverse acc)) hdrs | not (null acc)]-    go hdrs acc (l : rest) =-      case matchHeader headerRules l of-        Just (hPrefix, hName, hTitle) ->-          let currentChunk = [MarkdownChunk (T.unlines (reverse acc)) hdrs | not (null acc)]-              -- update headers: clear deeper headers when higher header occurs-              newHdrs = updateHeaderMap headerRules hPrefix hName hTitle hdrs-           in currentChunk ++ go newHdrs [l] rest-        Nothing ->-          go hdrs (l : acc) rest--matchHeader :: [(Text, Text)] -> Text -> Maybe (Text, Text, Text)-matchHeader rules line =-  let stripped = T.stripStart line-   in findRule rules stripped-  where-    findRule [] _ = Nothing-    findRule ((prefix, name) : rs) s =-      let prefixWithSpace = prefix <> " "-       in if prefixWithSpace `T.isPrefixOf` s-            then Just (prefix, name, T.strip (T.drop (T.length prefixWithSpace) s))-            else findRule rs s--updateHeaderMap :: [(Text, Text)] -> Text -> Text -> Text -> Map Text Text -> Map Text Text-updateHeaderMap rules prefix name title curMap =-  let prefixDepth = T.length prefix-      -- keep only headers with depth < prefixDepth-      filtered =-        Map.filterWithKey-          ( \k _ -> case lookupPrefixKey rules k of-              Just p -> T.length p < prefixDepth-              Nothing -> True-          )-          curMap-   in Map.insert name title filtered--lookupPrefixKey :: [(Text, Text)] -> Text -> Maybe Text-lookupPrefixKey [] _ = Nothing-lookupPrefixKey ((p, n) : rest) name-  | n == name = Just p-  | otherwise = lookupPrefixKey rest name--subSplitSection :: MarkdownSplitterOps -> MarkdownChunk -> [MarkdownChunk]-subSplitSection ops (MarkdownChunk content hdrs) =-  let cSize = mdChunkSize ops-      cOverlap = mdChunkOverlap ops-   in if T.length content <= cSize-        then [MarkdownChunk content hdrs]-        else-          let subOps =-                defaultRecursiveCharacterSplitterOps-                  { chunkSize = cSize-                  , chunkOverlap = cOverlap-                  , separators = ["\n\n", "\n", " ", ""]-                  }-              subPieces = splitTextRecursive subOps content-           in [MarkdownChunk piece hdrs | piece <- subPieces]
− src/Langchain/TextSplitter/RecursiveCharacter.hs
@@ -1,96 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--{- |-Module      : Langchain.TextSplitter.RecursiveCharacter-Description : Hierarchical recursive character text splitting with chunk overlap-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Recursively splits text by trying different separators in order (paragraphs, lines, spaces, characters)-to keep semantically related pieces of text together.--}-module Langchain.TextSplitter.RecursiveCharacter-  ( RecursiveCharacterSplitterOps (..)-  , defaultRecursiveCharacterSplitterOps-  , splitTextRecursive-  )-where--import Data.Int (Int64)-import Data.Text.Lazy (Text)-import qualified Data.Text.Lazy as T---- | Configuration options for recursive character text splitter-data RecursiveCharacterSplitterOps = RecursiveCharacterSplitterOps-  { chunkSize :: Int64-  , chunkOverlap :: Int64-  , separators :: [Text]-  }-  deriving (Show, Eq)---- | Default options: 1000 char chunks, 200 char overlap, standard hierarchy of separators-defaultRecursiveCharacterSplitterOps :: RecursiveCharacterSplitterOps-defaultRecursiveCharacterSplitterOps =-  RecursiveCharacterSplitterOps-    { chunkSize = 1000-    , chunkOverlap = 200-    , separators = ["\n\n", "\n", " ", ""]-    }---- | Split text recursively using the specified separators hierarchy-splitTextRecursive :: RecursiveCharacterSplitterOps -> Text -> [Text]-splitTextRecursive _ "" = []-splitTextRecursive-  RecursiveCharacterSplitterOps-    { chunkSize = maxSize-    , chunkOverlap = maxOverlap-    , separators = ss-    }-  text = splitRecursive ss text-    where-      splitRecursive :: [Text] -> Text -> [Text]-      splitRecursive [] txt = splitByLen txt-      splitRecursive ("" : _) txt = splitByLen txt-      splitRecursive (sep : seps) txt = mergeWithOverlap sep splitParts-        where-          splitParts = concatMap (splitRecursive seps) $ filter (not . T.null) $ T.splitOn sep txt--      splitByLen :: Text -> [Text]-      splitByLen "" = []-      splitByLen txt = chunk : splitByLen remainder-        where-          (chunk, remainder) = T.splitAt maxSize txt--      mergeWithOverlap :: Text -> [Text] -> [Text]-      mergeWithOverlap _ [] = []-      mergeWithOverlap sep parts = reverse $ go [] 0 [] parts-        where-          sepLen = T.length sep-          toText = T.intercalate sep . reverse--          go :: [Text] -> Int64 -> [Text] -> [Text] -> [Text]-          go acc chunkLen chunkParts pss =-            case pss of-              [] -> acc'-              (part : restParts) ->-                let partLen = T.length part-                    chunkLen' = chunkLen + sepBefore chunkParts + partLen-                 in if chunkLen' <= maxSize-                      then go acc chunkLen' (part : chunkParts) restParts-                      else-                        let (overlapParts, overlapLen) = takeWhileOverlap chunkParts 0 []-                            carryLen = overlapLen + sepBefore overlapParts + partLen-                         in go acc' carryLen (part : reverse overlapParts) restParts-            where-              acc' = toText chunkParts : acc--              sepBefore ps = if null ps then 0 else sepLen--              takeWhileOverlap [] overlapLen overlapAcc = (overlapAcc, overlapLen)-              takeWhileOverlap (overlapPart : restOverlap) overlapLen overlapAcc-                | overlapLen' > maxOverlap = (overlapAcc, overlapLen)-                | otherwise = takeWhileOverlap restOverlap overlapLen' (overlapPart : overlapAcc)-                where-                  overlapLen' = overlapLen + sepBefore overlapAcc + T.length overlapPart
− src/Langchain/TextSplitter/Token.hs
@@ -1,90 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--{- |-Module      : Langchain.TextSplitter.Token-Description : Token-based text splitting with configurable token counter-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Splits text into chunks of specified maximum token counts with optional overlap.--}-module Langchain.TextSplitter.Token-  ( TokenSplitterOps (..)-  , defaultTokenSplitterOps-  , splitByTokens-  , countTokensApprox-  ) where--import Data.Text.Lazy (Text)-import qualified Data.Text.Lazy as T---- | Configuration options for token-based text splitting-data TokenSplitterOps = TokenSplitterOps-  { maxTokens :: Int-  , tokenOverlap :: Int-  , tokenCounter :: Text -> Int-  }--instance Show TokenSplitterOps where-  show ops =-    "TokenSplitterOps { maxTokens = "-      ++ show (maxTokens ops)-      ++ ", tokenOverlap = "-      ++ show (tokenOverlap ops)-      ++ " }"---- | Approximate token count (roughly 4 characters per token or word-based heuristic)-countTokensApprox :: Text -> Int-countTokensApprox t =-  let wCount = length (T.words t)-      cCount = fromIntegral (T.length t) `div` 4-   in max wCount cCount---- | Default token splitter options (500 tokens, 50 token overlap)-defaultTokenSplitterOps :: TokenSplitterOps-defaultTokenSplitterOps =-  TokenSplitterOps-    { maxTokens = 500-    , tokenOverlap = 50-    , tokenCounter = countTokensApprox-    }---- | Split text into chunks bounded by maxTokens-splitByTokens :: TokenSplitterOps -> Text -> [Text]-splitByTokens _ "" = []-splitByTokens ops text =-  let wordsList = T.words text-   in if null wordsList-        then []-        else go [] [] wordsList-  where-    maxT = maxTokens ops-    overlapT = tokenOverlap ops-    count = tokenCounter ops--    go :: [Text] -> [Text] -> [Text] -> [Text]-    go acc currentWords [] =-      if null currentWords-        then reverse acc-        else reverse (T.unwords (reverse currentWords) : acc)-    go acc currentWords (w : ws) =-      let candidate = T.unwords (reverse (w : currentWords))-          tokCount = count candidate-       in if tokCount <= maxT-            then go acc (w : currentWords) ws-            else-              let finishedChunk = T.unwords (reverse currentWords)-                  newAcc = finishedChunk : acc-                  -- Overlap words-                  overlapWords = takeOverlap overlapT (reverse currentWords) []-               in go newAcc (w : overlapWords) ws--    takeOverlap :: Int -> [Text] -> [Text] -> [Text]-    takeOverlap _ [] acc = acc-    takeOverlap target (pw : pws) acc =-      let candidate = T.unwords (pw : acc)-       in if count candidate <= target-            then takeOverlap target pws (pw : acc)-            else if null acc then [pw] else acc
− src/Langchain/Tool/Async.hs
@@ -1,70 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}--{- |-Module      : Langchain.Tool.Async-Description : Asynchronous tool execution with timeout, cancellation, and concurrency control-Copyright   : (c) 2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Provides non-blocking, async tool execution primitives with per-tool timeout limits-and batch concurrent execution.--}-module Langchain.Tool.Async-  ( executeToolAsync-  , executeToolWithTimeout-  , executeToolBatchConcurrently-  ) where--import Control.Concurrent (threadDelay)-import Control.Concurrent.Async (Async, async, mapConcurrently, race)-import Control.Monad.Except (MonadError, throwError)-import Control.Monad.IO.Class (MonadIO, liftIO)-import Data.Aeson (Value)-import Data.Text (Text)-import qualified Data.Text as T--import Langchain.Core.Error (LangchainError, toolError)-import Langchain.Tool.Core (Tool (..))---- | Spawn tool execution in an asynchronous background thread-executeToolAsync ::-  (MonadIO m) =>-  Tool IO ->-  Value ->-  m (Async (Either LangchainError Text))-executeToolAsync Tool {..} args = liftIO $ do-  async (toolExecute args)---- | Execute a tool with a strict timeout limit in microseconds-executeToolWithTimeout ::-  (MonadIO m, MonadError LangchainError m) =>-  Tool IO ->-  Value ->-  Int ->-  m Text-executeToolWithTimeout Tool {..} args timeoutMicros = do-  res <- liftIO $ race (threadDelay timeoutMicros) (toolExecute args)-  case res of-    Left () ->-      throwError $-        toolError-          ("Tool '" <> toolName <> "' timed out after " <> T.pack (show timeoutMicros) <> " microseconds")-          (Just toolName)-          Nothing-    Right (Left err) -> throwError err-    Right (Right output) -> pure output---- | Execute a batch of tool calls concurrently in parallel-executeToolBatchConcurrently ::-  (MonadIO m, MonadError LangchainError m) =>-  [(Tool IO, Value)] ->-  m [Text]-executeToolBatchConcurrently toolCalls = do-  results <- liftIO $ mapConcurrently (uncurry toolExecute) toolCalls-  case sequence results of-    Left err -> throwError err-    Right outputs -> pure outputs
− src/Langchain/Tool/Binding.hs
@@ -1,49 +0,0 @@-{-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE UndecidableInstances #-}--{- |-Module      : Langchain.Tool.Binding-Description : Typeclass for attaching tool definitions to provider-specific model configs-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Provides 'ToolBinder' typeclass enabling agents to attach tool definitions-to provider-specific 'ModelConfig' types in a uniform way. This is the bridge-between provider-agnostic agent code and provider-specific tool APIs.--}-module Langchain.Tool.Binding-  ( ToolBinder (..)-  ) where--import Langchain.Core.Model (ChatModel (..))-import Langchain.Core.Tool (Tool)--{- | Typeclass for models that support binding tools into their 'ModelConfig'.--Agents like 'ReActAgent' use this to pass tool definitions to the LLM-provider in a provider-agnostic way.--= Example--@--- Agent code (provider-agnostic):-let cfg = bindToolsConfig tools Nothing-responseMsg <- invoke model history cfg---- The right thing happens automatically:--- For Ollama: builds a ChatRequest with chatTools set--- For OpenAI: builds a Value with "tools" key--- For OllamaWithTools: merges into existing config-@--}-class (ChatModel model) => ToolBinder model m where-  {- | Convert a list of tools into a provider-specific 'ModelConfig',-  optionally merging with an existing config.-  -}-  bindToolsConfig :: [Tool m] -> Maybe (ModelConfig model) -> Maybe (ModelConfig model)
− src/Langchain/Tool/Calculator.hs
@@ -1,68 +0,0 @@-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE OverloadedStrings #-}--{- |-Module      : Langchain.Tool.Calculator-Description : Standard Calculator Tool implementation-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Calculator tool using Langchain.Core.Tool.--}-module Langchain.Tool.Calculator-  ( calculatorTool-  , evaluateExpr-  ) where--import Control.Monad.IO.Class (MonadIO)-import Data.Aeson-import Data.Aeson.Types (parseEither)-import Data.Text (Text)-import qualified Data.Text as T--import Langchain.Core.Error (toolError)-import Langchain.Core.Tool (Tool (..), createTool)---- | Simple expression evaluator for arithmetic strings-evaluateExpr :: Text -> Either String Double-evaluateExpr txt =-  let cleanTxt = T.replace " " "" txt-   in case T.splitOn "+" cleanTxt of-        [a, b] -> case (reads (T.unpack a), reads (T.unpack b)) of-          ([(aNum, "")], [(bNum, "")]) -> Right (aNum + bNum)-          _ -> Left "Failed to parse numbers"-        _ -> case T.splitOn "*" cleanTxt of-          [a, b] -> case (reads (T.unpack a), reads (T.unpack b)) of-            ([(aNum, "")], [(bNum, "")]) -> Right (aNum * bNum)-            _ -> Left "Failed to parse numbers"-          _ -> Left "Unsupported expression format"---- | Standard Calculator Tool instance-calculatorTool :: MonadIO m => Tool m-calculatorTool =-  createTool-    "calculator"-    "Useful for evaluating arithmetic math expressions like '2 + 2' or '3 * 4'"-    ( object-        [ "type" .= ("object" :: Text)-        , "properties"-            .= object-              [ "expression"-                  .= object-                    [ "type" .= ("string" :: Text)-                    , "description" .= ("Arithmetic expression string" :: Text)-                    ]-              ]-        , "required" .= (["expression"] :: [Text])-        ]-    )-    ( \case-        Object o -> case parseEither (.:? "expression") o of-          Right (Just expr) -> case evaluateExpr expr of-            Right num -> pure $ Right (T.pack $ show num)-            Left parseErr -> pure $ Left $ toolError (T.pack parseErr) (Just "calculator") Nothing-          _ -> pure $ Left $ toolError "Missing or invalid 'expression' field" (Just "calculator") Nothing-        _ -> pure $ Left $ toolError "Invalid arguments object" (Just "calculator") Nothing-    )
− src/Langchain/Tool/Core.hs
@@ -1,17 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}--{- |-Module      : Langchain.Tool.Core-Description : Re-exports effect-polymorphic Tool from langchain-hs-core-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Re-exports 'Tool m', 'createTool', and 'toolToValue'.--}-module Langchain.Tool.Core-  ( module Langchain.Core.Tool-  ) where--import Langchain.Core.Tool
− src/Langchain/Tool/FileSystem.hs
@@ -1,107 +0,0 @@-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE OverloadedStrings #-}--{- |-Module      : Langchain.Tool.FileSystem-Description : Standard File System Tools implementation-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--File system tools (readFile, writeFile, listDir) built on Langchain.Core.Tool.--}-module Langchain.Tool.FileSystem-  ( readFileTool-  , writeFileTool-  , listDirTool-  ) where--import Control.Exception (try)-import Control.Monad.IO.Class (MonadIO, liftIO)-import Data.Aeson-import Data.Aeson.Types (parseEither)-import Data.Text (Text)-import qualified Data.Text as T-import qualified Data.Text.IO as TIO-import System.Directory (listDirectory)--import Langchain.Core.Error (toolError)-import Langchain.Core.Tool (Tool (..), createTool)---- | Read file content tool-readFileTool :: MonadIO m => Tool m-readFileTool =-  createTool-    "read_file"-    "Read text contents from a file path"-    ( object-        [ "type" .= ("object" :: Text)-        , "properties"-            .= object-              ["path" .= object ["type" .= ("string" :: Text)]]-        , "required" .= (["path"] :: [Text])-        ]-    )-    ( \case-        Object o -> case parseEither (.:? "path") o of-          Right (Just p) -> do-            eContent <- liftIO $ try (TIO.readFile (T.unpack p))-            case eContent of-              Left err -> pure $ Left $ toolError (T.pack $ show (err :: IOError)) (Just "read_file") Nothing-              Right txt -> pure $ Right txt-          _ -> pure $ Left $ toolError "Missing 'path' field" (Just "read_file") Nothing-        _ -> pure $ Left $ toolError "Invalid arguments object" (Just "read_file") Nothing-    )---- | Write content to file tool-writeFileTool :: MonadIO m => Tool m-writeFileTool =-  createTool-    "write_file"-    "Write text contents to a file path"-    ( object-        [ "type" .= ("object" :: Text)-        , "properties"-            .= object-              [ "path" .= object ["type" .= ("string" :: Text)]-              , "content" .= object ["type" .= ("string" :: Text)]-              ]-        , "required" .= (["path", "content"] :: [Text])-        ]-    )-    ( \case-        Object o -> case (parseEither (.:? "path") o, parseEither (.:? "content") o) of-          (Right (Just p), Right (Just content)) -> do-            eRes <- liftIO $ try (TIO.writeFile (T.unpack p) content)-            case eRes of-              Left err -> pure $ Left $ toolError (T.pack $ show (err :: IOError)) (Just "write_file") Nothing-              Right () -> pure $ Right ("Successfully wrote to " <> p)-          _ -> pure $ Left $ toolError "Missing 'path' or 'content' field" (Just "write_file") Nothing-        _ -> pure $ Left $ toolError "Invalid arguments object" (Just "write_file") Nothing-    )---- | List directory contents tool-listDirTool :: MonadIO m => Tool m-listDirTool =-  createTool-    "list_directory"-    "List files and subdirectories in a directory path"-    ( object-        [ "type" .= ("object" :: Text)-        , "properties"-            .= object-              ["path" .= object ["type" .= ("string" :: Text)]]-        , "required" .= (["path"] :: [Text])-        ]-    )-    ( \case-        Object o -> case parseEither (.:? "path") o of-          Right (Just p) -> do-            eFiles <- liftIO $ try (listDirectory (T.unpack p))-            case eFiles of-              Left err -> pure $ Left $ toolError (T.pack $ show (err :: IOError)) (Just "list_directory") Nothing-              Right files -> pure $ Right (T.unlines $ map T.pack files)-          _ -> pure $ Left $ toolError "Missing 'path' field" (Just "list_directory") Nothing-        _ -> pure $ Left $ toolError "Invalid arguments object" (Just "list_directory") Nothing-    )
− src/Langchain/Tool/GenericSchema.hs
@@ -1,175 +0,0 @@-{-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE DefaultSignatures #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE PolyKinds #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeOperators #-}--{- |-Module      : Langchain.Tool.GenericSchema-Description : Type-safe tool parameter JSON schema derivation using GHC Generics-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Automatically derives OpenAI-compatible tool JSON schema objects from Haskell record types-using GHC Generics at compile time.--}-module Langchain.Tool.GenericSchema-  ( DeriveToolSchema (..)-  , deriveToolParametersSchema-  ) where--import Data.Aeson (Value (..), object, (.=))-import qualified Data.Aeson.Key as Key-import Data.Int (Int16, Int32, Int64, Int8)-import Data.Kind (Type)-import qualified Data.Map.Strict as Map-import Data.Proxy (Proxy (..))-import Data.Scientific (Scientific)-import Data.Text (Text)-import qualified Data.Text as TS-import Data.Time (Day, UTCTime)-import Data.Word (Word16, Word32, Word64, Word8)-import GHC.Generics---- | Typeclass for deriving tool JSON schema parameters-class DeriveToolSchema a where-  deriveToolSchema :: Proxy a -> Value-  default deriveToolSchema :: (GToolRecordSchema (Rep a)) => Proxy a -> Value-  deriveToolSchema _ = deriveToolParametersSchema (Proxy :: Proxy a)---- | Derive OpenAI tool parameter schema object-deriveToolParametersSchema ::-  forall a. (GToolRecordSchema (Rep a)) => Proxy a -> Value-deriveToolParametersSchema _ =-  let (props, reqs) = gToolRecordSchema (Proxy :: Proxy (Rep a))-   in object-        [ "type" .= ("object" :: Text)-        , "properties" .= object props-        , "required" .= reqs-        ]--class GToolRecordSchema (f :: Type -> Type) where-  gToolRecordSchema :: Proxy f -> ([(Key.Key, Value)], [Text])--instance (GToolRecordSchema f, GToolRecordSchema g) => GToolRecordSchema (f :*: g) where-  gToolRecordSchema _ =-    let (p1, r1) = gToolRecordSchema (Proxy :: Proxy f)-        (p2, r2) = gToolRecordSchema (Proxy :: Proxy g)-     in (p1 ++ p2, r1 ++ r2)--instance (GToolRecordSchema f) => GToolRecordSchema (M1 D c f) where-  gToolRecordSchema _ = gToolRecordSchema (Proxy :: Proxy f)--instance (GToolRecordSchema f) => GToolRecordSchema (M1 C c f) where-  gToolRecordSchema _ = gToolRecordSchema (Proxy :: Proxy f)--instance (Selector s, ToolFieldSchema a) => GToolRecordSchema (M1 S s (K1 R a)) where-  gToolRecordSchema _ =-    let selNameStr = selName (undefined :: M1 S s (K1 R a) p)-        propKey = Key.fromString selNameStr-        propSchema = toolFieldSchema (Proxy :: Proxy a)-        req = [TS.pack selNameStr | not (isOptionalField (Proxy :: Proxy a))]-     in ([(propKey, propSchema)], req)--class ToolFieldSchema a where-  toolFieldSchema :: Proxy a -> Value-  default toolFieldSchema :: (GToolRecordSchema (Rep a)) => Proxy a -> Value-  toolFieldSchema _ = deriveToolParametersSchema (Proxy :: Proxy a)--  isOptionalField :: Proxy a -> Bool-  isOptionalField _ = False--instance ToolFieldSchema Text where-  toolFieldSchema _ = object ["type" .= ("string" :: Text)]--instance ToolFieldSchema String where-  toolFieldSchema _ = object ["type" .= ("string" :: Text)]--instance ToolFieldSchema Char where-  toolFieldSchema _ = object ["type" .= ("string" :: Text)]--instance ToolFieldSchema Int where-  toolFieldSchema _ = object ["type" .= ("integer" :: Text)]--instance ToolFieldSchema Int8 where-  toolFieldSchema _ = object ["type" .= ("integer" :: Text)]--instance ToolFieldSchema Int16 where-  toolFieldSchema _ = object ["type" .= ("integer" :: Text)]--instance ToolFieldSchema Int32 where-  toolFieldSchema _ = object ["type" .= ("integer" :: Text)]--instance ToolFieldSchema Int64 where-  toolFieldSchema _ = object ["type" .= ("integer" :: Text)]--instance ToolFieldSchema Integer where-  toolFieldSchema _ = object ["type" .= ("integer" :: Text)]--instance ToolFieldSchema Word where-  toolFieldSchema _ = object ["type" .= ("integer" :: Text)]--instance ToolFieldSchema Word8 where-  toolFieldSchema _ = object ["type" .= ("integer" :: Text)]--instance ToolFieldSchema Word16 where-  toolFieldSchema _ = object ["type" .= ("integer" :: Text)]--instance ToolFieldSchema Word32 where-  toolFieldSchema _ = object ["type" .= ("integer" :: Text)]--instance ToolFieldSchema Word64 where-  toolFieldSchema _ = object ["type" .= ("integer" :: Text)]--instance ToolFieldSchema Double where-  toolFieldSchema _ = object ["type" .= ("number" :: Text)]--instance ToolFieldSchema Float where-  toolFieldSchema _ = object ["type" .= ("number" :: Text)]--instance ToolFieldSchema Scientific where-  toolFieldSchema _ = object ["type" .= ("number" :: Text)]--instance ToolFieldSchema Bool where-  toolFieldSchema _ = object ["type" .= ("boolean" :: Text)]--instance ToolFieldSchema UTCTime where-  toolFieldSchema _ =-    object-      [ "type" .= ("string" :: Text)-      , "format" .= ("date-time" :: Text)-      ]--instance ToolFieldSchema Day where-  toolFieldSchema _ =-    object-      [ "type" .= ("string" :: Text)-      , "format" .= ("date" :: Text)-      ]--instance ToolFieldSchema Value where-  toolFieldSchema _ = object ["type" .= ("object" :: Text)]--instance (ToolFieldSchema a) => ToolFieldSchema (Map.Map Text a) where-  toolFieldSchema _ =-    object-      [ "type" .= ("object" :: Text)-      , "additionalProperties" .= toolFieldSchema (Proxy :: Proxy a)-      ]--instance (ToolFieldSchema a) => ToolFieldSchema (Maybe a) where-  toolFieldSchema _ = toolFieldSchema (Proxy :: Proxy a)-  isOptionalField _ = True--instance {-# OVERLAPPABLE #-} (ToolFieldSchema a) => ToolFieldSchema [a] where-  toolFieldSchema _ =-    object-      [ "type" .= ("array" :: Text)-      , "items" .= toolFieldSchema (Proxy :: Proxy a)-      ]
− src/Langchain/Tool/Shell.hs
@@ -1,71 +0,0 @@-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE OverloadedStrings #-}--{- |-Module      : Langchain.Tool.Shell-Description : Shell command execution tool-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Provides shell command execution capabilities for agents via System.Process.--}-module Langchain.Tool.Shell-  ( shellTool-  ) where--import Control.Exception (SomeException, try)-import Control.Monad.IO.Class (MonadIO, liftIO)-import Data.Aeson (Value (..), object, (.=))-import Data.Aeson.Types (parseEither, (.:?))-import Data.Text (Text)-import qualified Data.Text as T-import System.Exit (ExitCode (..))-import System.Process (readProcessWithExitCode)--import Langchain.Core.Error (toolError)-import Langchain.Core.Tool (Tool (..), createTool)---- | Tool that executes a shell command via @sh -c@ and returns its output-shellTool :: MonadIO m => Tool m-shellTool =-  createTool-    "shell_command"-    "Execute a shell command line (e.g. bash/sh) and return stdout and stderr output."-    ( object-        [ "type" .= ("object" :: Text)-        , "properties"-            .= object-              [ "command"-                  .= object-                    [ "type" .= ("string" :: Text)-                    , "description" .= ("The shell command line to execute" :: Text)-                    ]-              ]-        , "required" .= (["command"] :: [Text])-        ]-    )-    ( \case-        Object o -> case parseEither (.:? "command") o of-          Right (Just cmd) -> do-            eRes <- liftIO $ try (readProcessWithExitCode "sh" ["-c", T.unpack cmd] "")-            case eRes of-              Left err ->-                pure $ Left $ toolError (T.pack $ show (err :: SomeException)) (Just "shell_command") Nothing-              Right (ExitSuccess, stdoutStr, stderrStr) ->-                let out = T.strip (T.pack stdoutStr)-                    err = T.strip (T.pack stderrStr)-                 in if T.null out-                      then if T.null err then pure $ Right "Command completed with no output." else pure $ Right err-                      else pure $ Right out-              Right (ExitFailure code, stdoutStr, stderrStr) ->-                let combined = T.strip (T.pack (stdoutStr <> "\n" <> stderrStr))-                 in pure $-                      Right $-                        "Command exited with code "-                          <> T.pack (show code)-                          <> (if T.null combined then "" else ": " <> combined)-          _ -> pure $ Left $ toolError "Missing 'command' parameter" (Just "shell_command") Nothing-        _ -> pure $ Left $ toolError "Invalid arguments object" (Just "shell_command") Nothing-    )
− src/Langchain/VectorStore/Core.hs
@@ -1,56 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}--{- |-Module      : Langchain.VectorStore.Core-Description : Effect-polymorphic vector store abstraction for semantic search-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Effect-polymorphic VectorStore typeclass supporting document insertion,-deletion, and vector/text similarity search.--}-module Langchain.VectorStore.Core-  ( VectorStore (..)-  ) where--import Control.Monad.Except (MonadError)-import Control.Monad.IO.Class (MonadIO)-import Data.Int (Int64)-import Data.Text (Text)--import Langchain.Core.Error (LangchainError)-import Langchain.DocumentLoader.Core (Document)---- | Effect-polymorphic VectorStore typeclass-class VectorStore vs where-  -- | Add documents with generated embeddings-  addDocuments ::-    (MonadIO m, MonadError LangchainError m) =>-    vs ->-    [Document] ->-    m vs--  -- | Delete documents by unique integer ID-  delete ::-    (MonadIO m, MonadError LangchainError m) =>-    vs ->-    [Int64] ->-    m vs--  -- | Semantic similarity search using text query-  similaritySearch ::-    (MonadIO m, MonadError LangchainError m) =>-    vs ->-    Text ->-    Int ->-    m [Document]--  -- | Direct similarity search using embedding vector-  similaritySearchByVector ::-    (MonadIO m, MonadError LangchainError m) =>-    vs ->-    [Float] ->-    Int ->-    m [Document]
− src/Langchain/VectorStore/InMemory.hs
@@ -1,99 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}--{- |-Module      : Langchain.VectorStore.InMemory-Description : In-memory vector store implementation for LangChain Haskell-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--In-memory vector store implementation supporting cosine similarity search.--}-module Langchain.VectorStore.InMemory-  ( InMemory (..)-  , fromDocuments-  , emptyInMemoryVectorStore-  , norm-  , dotProduct-  , cosineSimilarity-  ) where--import Control.Monad.Except (MonadError)-import Control.Monad.IO.Class (MonadIO)-import Data.Bifunctor (second)-import Data.Int (Int64)-import Data.List (sortBy)-import qualified Data.Map.Strict as Map-import Data.Ord (comparing)--import Langchain.Core.Error (LangchainError)-import Langchain.DocumentLoader.Core (Document)-import Langchain.Embeddings.Core-import Langchain.VectorStore.Core---- | Compute dot product of two vectors-dotProduct :: [Float] -> [Float] -> Float-dotProduct a b = sum $ zipWith (*) a b---- | Calculate Euclidean norm of a vector-norm :: [Float] -> Float-norm a = sqrt $ sum $ map (^ (2 :: Int)) a---- | Calculate cosine similarity between vectors-cosineSimilarity :: [Float] -> [Float] -> Float-cosineSimilarity a b =-  let nA = norm a-      nB = norm b-   in if nA == 0 || nB == 0-        then 0-        else dotProduct a b / (nA * nB)---- | In-memory vector store data type-data InMemory m = InMemory-  { embeddingModel :: m-  , store :: Map.Map Int64 (Document, [Float])-  }-  deriving (Show, Eq)---- | Create empty in-memory store with embedding model-emptyInMemoryVectorStore :: m -> InMemory m-emptyInMemoryVectorStore model = InMemory model Map.empty---- | Initialize store from documents using embeddings-fromDocuments ::-  (Embeddings m, MonadIO monad, MonadError LangchainError monad) =>-  m ->-  [Document] ->-  monad (InMemory m)-fromDocuments model docs = do-  let vs = emptyInMemoryVectorStore model-  addDocuments vs docs--instance Embeddings m => VectorStore (InMemory m) where-  addDocuments inMem docs = do-    floats <- embedDocuments (embeddingModel inMem) docs-    let currStore = store inMem-        mbMaxKey = Map.lookupMax currStore-        startIdx = maybe 1 (\(k, _) -> k + 1) mbMaxKey-        newEntries = Map.fromList $ zip [startIdx ..] (zip docs floats)-        newInMem = inMem {store = Map.union newEntries currStore}-    pure newInMem--  delete inMem ids = do-    let currStore = store inMem-        newStore = foldl (flip Map.delete) currStore ids-    pure inMem {store = newStore}--  similaritySearch vs query k = do-    queryVec <- embedQuery (embeddingModel vs) query-    similaritySearchByVector vs queryVec k--  similaritySearchByVector vs queryVec k = do-    let similarities =-          map-            (second (cosineSimilarity queryVec) . snd)-            (Map.toList $ store vs)-        sorted = sortBy (comparing (negate . snd)) similarities-        topK = take k sorted-    pure $ map fst topK
− src/Langchain/VectorStore/SqliteVec.hs
@@ -1,147 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedStrings #-}--{- |-Module      : Langchain.VectorStore.SqliteVec-Description : SQLite-backed vector store with persistent storage and cosine distance-Copyright   : (c) 2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Stores document text, JSON metadata, and vector embeddings in a local SQLite database.--}-module Langchain.VectorStore.SqliteVec-  ( SqliteVecStore (..)-  , newSqliteVecStore-  , initSqliteVecSchema-  ) where--import Control.Exception (try)-import Control.Monad.Except (MonadError, throwError)-import Control.Monad.IO.Class (MonadIO, liftIO)-import Data.Aeson (decode, encode)-import qualified Data.ByteString.Lazy as LBS-import Data.Int (Int64)-import Data.List (sortOn)-import Data.Maybe (fromMaybe)-import Data.Ord (Down (..))-import qualified Data.Text as TS-import qualified Data.Text.Encoding as TE-import qualified Data.Text.Lazy as TL-import Database.SQLite.Simple--import Langchain.Core.Error (LangchainError, vectorStoreError)-import Langchain.DocumentLoader.Core (Document (..))-import Langchain.Embeddings.Core (Embeddings (..))-import Langchain.VectorStore.Core (VectorStore (..))-import Langchain.VectorStore.InMemory (cosineSimilarity)---- | SQLite vector store container-data SqliteVecStore e = SqliteVecStore-  { sqliteDbPath :: FilePath-  , sqliteEmbeddings :: e-  }---- | Construct a new SqliteVecStore and initialize schema-newSqliteVecStore ::-  (MonadIO m, MonadError LangchainError m) =>-  FilePath ->-  e ->-  m (SqliteVecStore e)-newSqliteVecStore dbPath emb = do-  initSqliteVecSchema dbPath-  pure $ SqliteVecStore dbPath emb---- | Initialize table schema in SQLite database-initSqliteVecSchema :: (MonadIO m, MonadError LangchainError m) => FilePath -> m ()-initSqliteVecSchema dbPath = do-  eRes <- liftIO $ try $ withConnection dbPath $ \conn -> do-    execute_-      conn-      "CREATE TABLE IF NOT EXISTS langchain_vectors (\-      \ id INTEGER PRIMARY KEY AUTOINCREMENT,\-      \ content TEXT NOT NULL,\-      \ metadata TEXT NOT NULL,\-      \ vector BLOB NOT NULL\-      \);"-  case eRes of-    Left err ->-      throwError $-        vectorStoreError-          (TS.pack $ "Failed to initialize SQLite vector database: " ++ show (err :: IOError))-          (Just "SqliteVecStore")-          Nothing-    Right () -> pure ()--instance (Embeddings e) => VectorStore (SqliteVecStore e) where-  addDocuments store docs = do-    vectors <- embedDocuments (sqliteEmbeddings store) docs-    eRes <- liftIO $ try $ withConnection (sqliteDbPath store) $ \conn -> do-      withTransaction conn $ do-        mapM_-          ( \(doc, vec) -> do-              let cTxt = TL.unpack (pageContent doc)-                  mJson = TE.decodeUtf8 $ LBS.toStrict $ encode (metadata doc)-                  vBytes = LBS.toStrict $ encode (vec :: [Float])-              execute-                conn-                "INSERT INTO langchain_vectors (content, metadata, vector) VALUES (?, ?, ?)"-                (cTxt, TS.unpack mJson, vBytes)-          )-          (zip docs vectors)-    case eRes of-      Left err ->-        throwError $-          vectorStoreError-            (TS.pack $ "Failed to insert documents into SQLite vector store: " ++ show (err :: IOError))-            (Just "SqliteVecStore")-            Nothing-      Right () -> pure store--  delete store ids = do-    eRes <- liftIO $ try $ withConnection (sqliteDbPath store) $ \conn -> do-      withTransaction conn $ do-        mapM_-          (\i -> execute conn "DELETE FROM langchain_vectors WHERE id = ?" (Only (i :: Int64)))-          ids-    case eRes of-      Left err ->-        throwError $-          vectorStoreError-            (TS.pack $ "Failed to delete documents from SQLite vector store: " ++ show (err :: IOError))-            (Just "SqliteVecStore")-            Nothing-      Right () -> pure store--  similaritySearch store query0 k = do-    qVec <- embedQuery (sqliteEmbeddings store) query0-    similaritySearchByVector store qVec k--  similaritySearchByVector store qVec k = do-    rowsRes <- liftIO $ try $ withConnection (sqliteDbPath store) $ \conn -> do-      query_ conn "SELECT id, content, metadata, vector FROM langchain_vectors" ::-        IO [(Int64, String, String, LBS.ByteString)]-    rows <- case rowsRes of-      Left err ->-        throwError $-          vectorStoreError-            (TS.pack $ "Failed to query SQLite vector store: " ++ show (err :: IOError))-            (Just "SqliteVecStore")-            Nothing-      Right r -> pure r--    let scoredDocs =-          [ (score, doc)-          | (_, contentStr, metaStr, vBytes) <- rows-          , let mbVec = decode (LBS.fromStrict (LBS.toStrict vBytes)) :: Maybe [Float]-          , Just vec <- [mbVec]-          , let score = cosineSimilarity qVec vec-          , let mbMeta = decode (LBS.fromStrict (TE.encodeUtf8 (TS.pack metaStr)))-          , let meta = fromMaybe mempty mbMeta-          , let doc = Document (TL.pack contentStr) meta-          ]-        topK = take k $ map snd $ sortOn (Down . fst) scoredDocs-    pure topK
test/Spec.hs view
@@ -4,85 +4,45 @@  import Test.Tasty --- Unit Test Modules+-- Unit & Integration Test Modules import qualified Test.Langchain.Agent.AdvancedAgentsSpec as AdvancedAgentsTest import qualified Test.Langchain.Agent.ReAct as ReActTest import qualified Test.Langchain.Cache.CacheSpec as CacheTest-import qualified Test.Langchain.Callback.CallbackManagerSpec as CallbackTest-import qualified Test.Langchain.Chain.ChainsSpec as ChainsTest import qualified Test.Langchain.Chain.RetrievalQASpec as RetrievalQATest import qualified Test.Langchain.DocumentLoader.Core as DocumentLoaderTest-import qualified Test.Langchain.DocumentLoader.CsvSpec as CsvLoaderTest import qualified Test.Langchain.DocumentLoader.DirectoryLoader as DirectoryLoaderTest import qualified Test.Langchain.Error as ErrorTest import qualified Test.Langchain.Graph.CompilationSpec as GraphCompilationTest import qualified Test.Langchain.Guardrail.GuardrailSpec as GuardrailTest-import qualified Test.Langchain.MCP.McpSpec as McpTest import qualified Test.Langchain.Memory.Core as MemoryTest-import qualified Test.Langchain.Memory.EntitySpec as EntityMemoryTest import qualified Test.Langchain.Memory.SummarySpec as SummaryMemoryTest import qualified Test.Langchain.Memory.TokenBufferMemory as TokenBufferMemoryTest-import qualified Test.Langchain.ObservabilitySpec as ObservabilityTest import qualified Test.Langchain.OutputParser.AdvancedParsersSpec as AdvancedParsersTest import qualified Test.Langchain.OutputParser.Core as OutputParserTest-import qualified Test.Langchain.PromptTemplate.Chat.ChatPromptTemplateSpec as ChatPromptTemplateTest-import qualified Test.Langchain.PromptTemplate.Chat.MessagesPlaceholderSpec as MessagesPlaceholderTest-import qualified Test.Langchain.PromptTemplate.FewShotSpec as FewShotPromptTemplateTest-import qualified Test.Langchain.PromptTemplate.PromptSpec as PromptTemplateTest-import qualified Test.Langchain.Provider.FixturesSpec as FixturesTest-import qualified Test.Langchain.Provider.Ollama as OllamaProviderTest-import qualified Test.Langchain.Provider.OllamaConversionSpec as OllamaConversionTest-import qualified Test.Langchain.Provider.OpenAI as OpenAIProviderTest-import qualified Test.Langchain.Resilience.CircuitBreakerSpec as CircuitBreakerTest-import qualified Test.Langchain.Resilience.RetrySpec as RetryTest import qualified Test.Langchain.Retriever.BM25Spec as BM25Test-import qualified Test.Langchain.Retriever.Core as RetrieverTest-import qualified Test.Langchain.Retriever.HybridSpec as HybridRetrieverTest import qualified Test.Langchain.TextSplitter.Character as TextSplitterTest import qualified Test.Langchain.TextSplitter.CodeSpec as CodeSplitterTest import qualified Test.Langchain.TextSplitter.MarkdownSpec as MarkdownSplitterTest import qualified Test.Langchain.TextSplitter.RecursiveCharacterSpec as RecursiveSplitterTest import qualified Test.Langchain.TextSplitter.TokenSpec as TokenSplitterTest import qualified Test.Langchain.Tool.AdvancedToolsSpec as AdvancedToolsTest-import qualified Test.Langchain.Tool.Calculator as CalculatorToolTest import qualified Test.Langchain.Tool.FileSystem as FileSystemToolTest+import qualified Test.Langchain.Tool.ProcessSpec as ProcessToolTest+import qualified Test.Langchain.Tool.SecuritySpec as SecurityToolTest import qualified Test.Langchain.Tool.Shell as ShellToolTest import qualified Test.Langchain.VectorStore.Core as VectorStoreTest-import qualified Test.Langchain.VectorStore.SqliteVecSpec as SqliteVecStoreTest --- Property Test Modules (QuickCheck Laws & Invariants)-import qualified Test.Langchain.Property.CheckpointerSpec as CheckpointerPropTest-import qualified Test.Langchain.Property.ErrorSpec as ErrorPropTest-import qualified Test.Langchain.Property.MessageSpec as MessagePropTest-import qualified Test.Langchain.Property.PromptTemplateSpec as PromptTemplatePropTest-import qualified Test.Langchain.Property.RunnableSpec as RunnablePropTest-import qualified Test.Langchain.Property.TextSplitterSpec as TextSplitterPropTest- -- Regression Test Module import qualified Test.Langchain.RegressionSpec as RegressionTest --- Live Ollama E2E Integration Test Modules-import qualified Test.Langchain.Integration.FullRagE2ESpec as FullRagE2ETest-import qualified Test.Langchain.Integration.OllamaChatSpec as OllamaChatE2ETest-import qualified Test.Langchain.Integration.OllamaEmbeddingSpec as OllamaEmbedE2ETest-import qualified Test.Langchain.Integration.OllamaStreamSpec as OllamaStreamE2ETest-import qualified Test.Langchain.Integration.OllamaToolSpec as OllamaToolE2ETest-import qualified Test.Langchain.Integration.ReActAgentE2ESpec as ReActE2ETest-import qualified Test.Langchain.Integration.StateGraphE2ESpec as StateGraphE2ETest-import qualified Test.Langchain.Integration.StreamingCachingRetryE2ESpec as StreamingCachingRetryE2ETest- main :: IO () main =   defaultMain $     testGroup       "Langchain Test Suite"       [ testGroup-          "Unit Tests"-          [ PromptTemplateTest.tests-          , FewShotPromptTemplateTest.tests-          , ChatPromptTemplateTest.tests-          , MessagesPlaceholderTest.tests-          , OutputParserTest.tests+          "Integration & Component Tests"+          [ OutputParserTest.tests           , AdvancedParsersTest.tests           , TextSplitterTest.tests           , RecursiveSplitterTest.tests@@ -91,60 +51,26 @@           , CodeSplitterTest.tests           , DocumentLoaderTest.tests           , DirectoryLoaderTest.tests-          , CsvLoaderTest.tests           , MemoryTest.tests           , SummaryMemoryTest.tests-          , EntityMemoryTest.tests           , VectorStoreTest.tests-          , SqliteVecStoreTest.tests           , ErrorTest.tests-          , RetrieverTest.tests           , BM25Test.tests-          , HybridRetrieverTest.tests           , RetrievalQATest.tests-          , ChainsTest.tests           , CacheTest.tests-          , RetryTest.tests-          , CircuitBreakerTest.tests           , AdvancedToolsTest.tests           , ReActTest.tests           , AdvancedAgentsTest.tests           , GuardrailTest.tests-          , McpTest.tests-          , ObservabilityTest.tests-          , CallbackTest.tests           , TokenBufferMemoryTest.tests-          , OllamaProviderTest.tests-          , OllamaConversionTest.tests-          , OpenAIProviderTest.tests-          , FixturesTest.tests-          , CalculatorToolTest.tests           , FileSystemToolTest.tests+          , ProcessToolTest.tests+          , SecurityToolTest.tests           , ShellToolTest.tests           , GraphCompilationTest.tests           ]       , testGroup-          "Property Tests (Laws & Invariants)"-          [ MessagePropTest.tests-          , PromptTemplatePropTest.tests-          , TextSplitterPropTest.tests-          , RunnablePropTest.tests-          , CheckpointerPropTest.tests-          , ErrorPropTest.tests-          ]-      , testGroup           "Regression Tests"           [ RegressionTest.tests-          ]-      , testGroup-          "Live E2E Integration Tests (Ollama)"-          [ OllamaChatE2ETest.tests-          , OllamaStreamE2ETest.tests-          , OllamaToolE2ETest.tests-          , OllamaEmbedE2ETest.tests-          , FullRagE2ETest.tests-          , ReActE2ETest.tests-          , StateGraphE2ETest.tests-          , StreamingCachingRetryE2ETest.tests           ]       ]
test/Test/Langchain/Agent/AdvancedAgentsSpec.hs view
@@ -8,7 +8,7 @@ import Test.Tasty.HUnit  import Langchain.Agent.PlanAndExecute-import Langchain.Core.Error (LangchainError)+import Langchain.Error (LangchainError) import Test.Langchain.Provider.Mock (newMockModel)  tests :: TestTree
test/Test/Langchain/Agent/ReAct.hs view
@@ -1,31 +1,36 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-}  module Test.Langchain.Agent.ReAct (tests) where  import Control.Monad.Except (ExceptT, runExceptT)-import Control.Monad.IO.Class (liftIO)+import Control.Monad.IO.Class (MonadIO, liftIO) import Data.Aeson (Value (..), object, (.=))-import qualified Data.Aeson.KeyMap as KeyMap import Data.IORef+import Data.Maybe (listToMaybe) import qualified Data.Text as T import Test.Tasty import Test.Tasty.HUnit  import Langchain.Agent.ReAct-import Langchain.Core.Error (LangchainError)-import Langchain.Core.Model-import Langchain.Core.Tool (Tool)-import Langchain.Provider.Gemini (Gemini)-import Langchain.Provider.Ollama (ChatRequest (..), Ollama, chatTools)-import Langchain.Provider.OpenAI (OpenAI)+import Langchain.Error (LangchainError)+import Langchain.Memory.Core (BaseMemory (..), newWindowBufferMemory)+import Langchain.Model+import Langchain.Tool (Tool, createTool) import Langchain.Tool.Binding (ToolBinder (..))-import Langchain.Tool.Calculator (calculatorTool) import Test.Langchain.Provider.Mock (newMockModel) +-- | Mock calculator tool used for testing agent step dispatching+calculatorTool :: MonadIO m => Tool m+calculatorTool =+  createTool+    "calculator"+    "Mock calculator tool"+    (object [])+    (\_ -> pure $ Right "4")+ -- | Mock model that records the config received by invoke data ConfigRecordingModel = ConfigRecordingModel (IORef (Maybe Value)) T.Text @@ -64,8 +69,8 @@     "Langchain.Agent.ReAct"     [ testCase "reactStep returns AgentFinish when LLM responds with plain text" $ do         let mockModel = newMockModel "The answer is 4."-            agent = createReActAgent mockModel [calculatorTool]-        res <- runExceptT $ reactStep (agentModel agent) (agentTools agent) [userMessage "What is 2+2?"]+            tools = [calculatorTool]+        res <- runExceptT $ reactStep mockModel tools [userMessage "What is 2+2?"] Nothing         case res of           Left err -> assertFailure $ "Unexpected error: " ++ show err           Right step -> case step of@@ -78,8 +83,8 @@         sRef <- newIORef [respWithTools]         hRef <- newIORef []         let model = StepSequenceModel sRef hRef-            agent = createReActAgent model [calculatorTool :: Tool (ExceptT LangchainError IO)]-        res <- runExceptT $ reactStep (agentModel agent) (agentTools agent) [userMessage "Calculate both"]+            tools = [calculatorTool :: Tool (ExceptT LangchainError IO)]+        res <- runExceptT $ reactStep model tools [userMessage "Calculate both"] Nothing         case res of           Left err -> assertFailure $ "Unexpected error: " ++ show err           Right step -> case step of@@ -93,7 +98,7 @@         sRef <- newIORef [respWithTools, finalResp]         hRef <- newIORef []         let model = StepSequenceModel sRef hRef-            agent = createReActAgent model [calculatorTool :: Tool (ExceptT LangchainError IO)]+            agent = defaultReActAgent model [calculatorTool]         res <- runExceptT $ runReActAgent agent [userMessage "Calculate 2+2 and 5*2"]         case res of           Left err -> assertFailure $ "Unexpected error: " ++ show err@@ -114,7 +119,7 @@         sRef <- newIORef [respWithBadTool, finalResp]         hRef <- newIORef []         let model = StepSequenceModel sRef hRef-            agent = createReActAgent model [calculatorTool :: Tool (ExceptT LangchainError IO)]+            agent = defaultReActAgent model [calculatorTool]         res <- runExceptT $ runReActAgent agent [userMessage "Run unknown tool"]         case res of           Left err -> assertFailure $ "Expected recovery but got error: " ++ show err@@ -132,7 +137,7 @@               _ -> assertFailure "Expected 2 invocations"     , testCase "runReActAgent completes full loop on finish" $ do         let mockModel = newMockModel "Finished processing"-            agent = createReActAgent mockModel [calculatorTool]+            agent = defaultReActAgent mockModel [calculatorTool]         res <- runExceptT $ runReActAgent agent [userMessage "Hello"]         case res of           Left err -> assertFailure $ "Unexpected error: " ++ show err@@ -141,30 +146,86 @@         ref <- newIORef Nothing         let recordingModel = ConfigRecordingModel ref "Direct Answer"             tools = [calculatorTool :: Tool (ExceptT LangchainError IO)]-        res <- runExceptT $ reactStep recordingModel tools [userMessage "Calculate 2+2"]+        res <- runExceptT $ reactStep recordingModel tools [userMessage "Calculate 2+2"] Nothing         case res of           Left err -> assertFailure $ "Unexpected error: " ++ show err           Right _ -> do             captured <- readIORef ref             captured @?= Just (object ["tool_count" .= (1 :: Int)])-    , testCase "ToolBinder Ollama attaches tools to ChatRequest config" $ do-        let tools = [calculatorTool :: Tool IO]-            mbCfg = bindToolsConfig @Ollama tools Nothing-        case mbCfg of-          Nothing -> assertFailure "Expected Just ChatRequest"-          Just req -> case chatTools req of-            Nothing -> assertFailure "Expected Just tools in ChatRequest"-            Just ts -> length ts @?= 1-    , testCase "ToolBinder OpenAI attaches tools to JSON config" $ do-        let tools = [calculatorTool :: Tool IO]-            mbCfg = bindToolsConfig @OpenAI tools Nothing-        case mbCfg of-          Just (Object obj) -> assertBool "Has 'tools' key" (KeyMap.member "tools" obj)-          _ -> assertFailure "Expected Just Object with tools"-    , testCase "ToolBinder Gemini attaches tools to JSON config" $ do-        let tools = [calculatorTool :: Tool IO]-            mbCfg = bindToolsConfig @Gemini tools Nothing-        case mbCfg of-          Just (Object obj) -> assertBool "Has 'tools' key" (KeyMap.member "tools" obj)-          _ -> assertFailure "Expected Just Object with tools"+    , testCase "runReActAgentWithTrace returns intermediate steps" $ do+        let tc1 = ToolCall "call_1" "function" "calculator" (object ["expression" .= ("2+2" :: T.Text)])+            respWithTools = (assistantMessage "calculating") {messageToolCalls = Just [tc1]}+            finalResp = assistantMessage "The answer is 4"+        sRef <- newIORef [respWithTools, finalResp]+        hRef <- newIORef []+        let model = StepSequenceModel sRef hRef+            agent = defaultReActAgent model [calculatorTool]+        res <- runExceptT $ runReActAgentWithTrace agent [userMessage "Calculate 2+2"]+        case res of+          Left err -> assertFailure $ "Unexpected error: " ++ show err+          Right trace -> do+            -- Trace should have 2 steps: one action + one finish+            length (traceSteps trace) @?= 2+            T.strip (extractMessageText (traceFinalAnswer trace)) @?= "The answer is 4"+    , testCase "withSystemPrompt prepends system message to history" $ do+        hRef <- newIORef ([] :: [[Message]])+        sRef <- newIORef [assistantMessage "Got it"]+        let model = StepSequenceModel sRef hRef+            agent =+              withSystemPrompt "You are a math tutor." $+                defaultReActAgent model [calculatorTool]+        res <- runExceptT $ runReActAgent agent [userMessage "Hi"]+        case res of+          Left err -> assertFailure $ "Unexpected error: " ++ show err+          Right _ -> do+            histories <- readIORef hRef+            case histories of+              (firstCall : _) ->+                case firstCall of+                  (sysMsg : _) -> do+                    messageRole sysMsg @?= System+                    T.strip (extractMessageText sysMsg) @?= "You are a math tutor."+                  _ -> assertFailure "Expected system message in history"+              _ -> assertFailure "Expected at least one invocation"+    , testCase "withMemory loads and saves conversation history" $ do+        mem <- newWindowBufferMemory 50 []+        hRef <- newIORef ([] :: [[Message]])+        sRef <- newIORef [assistantMessage "Memory works!"]+        let model = StepSequenceModel sRef hRef+            agent =+              withMemory mem $+                defaultReActAgent model [calculatorTool]+        res <- runExceptT $ runReActAgent agent [userMessage "Test memory"]+        case res of+          Left err -> assertFailure $ "Unexpected error: " ++ show err+          Right finalMsg -> do+            T.strip (extractMessageText finalMsg) @?= "Memory works!"+            -- Verify the messages were saved to memory+            memRes <- runExceptT $ messages mem+            case memRes of+              Left err -> assertFailure $ "Memory read failed: " ++ show err+              Right savedMsgs -> do+                -- Should have user message + assistant response+                length savedMsgs @?= 2+                case listToMaybe savedMsgs of+                  Nothing -> assertFailure "Message list is empty"+                  Just firstMessage -> messageRole firstMessage @?= User+                messageRole (savedMsgs !! 1) @?= Assistant+    , testCase "withModelConfig passes config to provider" $ do+        ref <- newIORef Nothing+        let recordingModel = ConfigRecordingModel ref "Configured Answer"+            tools = [calculatorTool]+            agent =+              withModelConfig+                (Just $ object ["temperature" .= (0.7 :: Double)])+                (defaultReActAgent recordingModel tools)+        res <- runExceptT $ runReActAgent agent [userMessage "Test config"]+        case res of+          Left err -> assertFailure $ "Unexpected error: " ++ show err+          Right _ -> do+            captured <- readIORef ref+            -- The config should be the result of bindToolsConfig merging with our config+            case captured of+              Nothing -> assertFailure "Expected config to be passed to model"+              Just _ -> pure () -- Config was passed (exact value depends on ToolBinder instance)     ]
test/Test/Langchain/Cache/CacheSpec.hs view
@@ -5,7 +5,7 @@  import Control.Concurrent.STM (newTVarIO) import Control.Monad.Except (runExceptT)-import Data.Aeson (Value, object, (.=))+import Data.Aeson (object) import Data.List.NonEmpty (NonEmpty (..)) import qualified Data.Map.Strict as Map import Data.Text (Text)@@ -15,7 +15,7 @@ import Test.Tasty.HUnit  import Langchain.Cache.Core-import Langchain.Core.Model+import Langchain.Model   ( ChatModel (..)   , ContentBlock (..)   , ImageContent (..)@@ -27,38 +27,11 @@   , extractMessageText   , userMessage   )-import Langchain.Provider.Gemini (Gemini (..))-import Langchain.Provider.Ollama (Ollama, newOllamaWithClient)-import Langchain.Provider.OpenAI (OpenAI (OpenAI))-import qualified Ollama.API.Chat as OllamaChat-import Ollama.Client (newClient)-import qualified Ollama.Client.Config as OllamaClientConfig-import Ollama.Types.Common (ModelName (..), Think (..))-import Ollama.Types.Format (Format (..))-import qualified Ollama.Types.Message as OllamaMessage-import Ollama.Types.Options (ModelOptions (..), defaultOptions)-import Ollama.Types.Tool (FunctionDef (..))-import qualified Ollama.Types.Tool as OllamaTool import Test.Langchain.Provider.Mock (MockModel (..), newMockModel)  testMessages :: [Message] testMessages = [userMessage "Describe the image"] -baseOllamaRequest :: OllamaChat.ChatRequest-baseOllamaRequest =-  OllamaChat.chatRequest-    (ModelName "llama3.2")-    (OllamaMessage.userMessage "ignored-message" :| [])--newOllamaForEndpoint :: Text -> IO Ollama-newOllamaForEndpoint endpoint = do-  ollamaClient <--    newClient $-      OllamaClientConfig.defaultConfig-        { OllamaClientConfig.configBaseUrl = endpoint-        }-  pure $ newOllamaWithClient "llama3.2" ollamaClient- assertKeysDiffer :: Text -> Text -> Assertion assertKeysDiffer first second =   assertBool "Expected cache keys to differ" (first /= second)@@ -126,63 +99,8 @@         assertKeysDiffer           (computeCacheKey first Nothing testMessages)           (computeCacheKey second Nothing testMessages)-    , testCase "cache key distinguishes OpenAI identity and ignores its config" $ do-        let base = OpenAI "key" "gpt-4o" "https://api.openai.com/v1/chat/completions" (Just 0.7)-            otherModel = OpenAI "key" "gpt-4.1" "https://api.openai.com/v1/chat/completions" (Just 0.7)-            otherEndpoint = OpenAI "key" "gpt-4o" "https://example.com/v1/chat/completions" (Just 0.7)-            otherTemperature = OpenAI "key" "gpt-4o" "https://api.openai.com/v1/chat/completions" (Just 0.2)-            baseKey = computeCacheKey base Nothing testMessages-        assertKeysDiffer baseKey $ computeCacheKey otherModel Nothing testMessages-        assertKeysDiffer baseKey $ computeCacheKey otherEndpoint Nothing testMessages-        assertKeysDiffer baseKey $ computeCacheKey otherTemperature Nothing testMessages-        baseKey @?= computeCacheKey base (Just $ object ["unused" .= True]) testMessages-    , testCase "cache key distinguishes Gemini identity and request config" $ do-        let base = Gemini "key" "gemini-2.0-flash" Nothing-            otherModel = Gemini "key" "gemini-2.5-pro" Nothing-            baseKey = computeCacheKey base Nothing testMessages-        assertKeysDiffer baseKey $ computeCacheKey otherModel Nothing testMessages-        assertKeysDiffer baseKey $-          computeCacheKey base (Just $ object ["tools" .= ([] :: [Value])]) testMessages-    , testCase "cache key distinguishes Gemini custom endpoints" $ do-        let defaultEndpoint = Gemini "key" "gemini-2.0-flash" Nothing-            url1 = Just "http://gemini-one.example.com"-            url2 = Just "http://gemini-two.example.com"-            firstEndpoint = Gemini "key" "gemini-2.0-flash" url1-            sameEndpoint = Gemini "key" "gemini-2.0-flash" url1-            secondEndpoint = Gemini "key" "gemini-2.0-flash" url2-            defaultKey = computeCacheKey defaultEndpoint Nothing testMessages-            firstKey = computeCacheKey firstEndpoint Nothing testMessages-        assertKeysDiffer defaultKey firstKey-        firstKey @?= computeCacheKey sameEndpoint Nothing testMessages-        assertKeysDiffer firstKey $ computeCacheKey secondEndpoint Nothing testMessages     , testCase "cache key ignores MockModel config" $ do         let mockModel = newMockModel "Dynamic Output"         computeCacheKey mockModel Nothing testMessages           @?= computeCacheKey mockModel (Just ()) testMessages-    , testCase "cache key distinguishes Ollama endpoints and effective config" $ do-        firstEndpoint <- newOllamaForEndpoint "http://ollama-one.example.com:11434"-        secondEndpoint <- newOllamaForEndpoint "http://ollama-two.example.com:11434"-        let baseKey = computeCacheKey firstEndpoint (Just baseOllamaRequest) testMessages-            ignoredFieldsRequest =-              baseOllamaRequest-                { OllamaChat.chatMessages = OllamaMessage.userMessage "another-ignored-message" :| []-                , OllamaChat.chStream = Just True-                }-            requestsThatChangeOutput =-              [ baseOllamaRequest {OllamaChat.chatModel = ModelName "different-model"}-              , baseOllamaRequest-                  { OllamaChat.chatTools =-                      Just [OllamaTool.Tool "function" (FunctionDef "get_weather" Nothing Nothing Nothing)]-                  }-              , baseOllamaRequest {OllamaChat.chatFormat = Just JsonFormat}-              , baseOllamaRequest {OllamaChat.chatOptions = Just defaultOptions {optTemperature = Just 0.2}}-              , baseOllamaRequest {OllamaChat.chatKeepAlive = Just "10m"}-              , baseOllamaRequest {OllamaChat.chatThink = Just ThinkEnabled}-              ]-        assertKeysDiffer baseKey $ computeCacheKey secondEndpoint (Just baseOllamaRequest) testMessages-        baseKey @?= computeCacheKey firstEndpoint Nothing testMessages-        baseKey @?= computeCacheKey firstEndpoint (Just ignoredFieldsRequest) testMessages-        mapM_-          (\request -> assertKeysDiffer baseKey $ computeCacheKey firstEndpoint (Just request) testMessages)-          requestsThatChangeOutput     ]
− test/Test/Langchain/Callback/CallbackManagerSpec.hs
@@ -1,27 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Test.Langchain.Callback.CallbackManagerSpec (tests) where--import Control.Concurrent.STM (readTVarIO)-import Data.Time.Clock (getCurrentTime)-import Test.Tasty-import Test.Tasty.HUnit--import Langchain.Callback.Manager--tests :: TestTree-tests =-  testGroup-    "Langchain.Callback.CallbackManagerSpec"-    [ testCase "CallbackManager registers handler and dispatches events" $ do-        mgr <- newCallbackManager-        (handler, logsVar) <- newLoggingCallbackHandler "TestHandler"-        registerHandler mgr handler--        now <- getCurrentTime-        dispatchEvent mgr (OnLLMStart "qwen2.5:7b" ["Hello"] now)-        dispatchEvent mgr (OnLLMEnd "qwen2.5:7b" "Hi there!" 1500 now)--        logged <- readTVarIO logsVar-        length logged @?= 2-    ]
− test/Test/Langchain/Chain/ChainsSpec.hs
@@ -1,27 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Test.Langchain.Chain.ChainsSpec (tests) where--import Control.Monad.Except (runExceptT)-import qualified Data.Map.Strict as Map-import Test.Tasty-import Test.Tasty.HUnit--import Langchain.Chain.MapReduce-import Langchain.Core.Model (extractMessageText)-import Langchain.DocumentLoader.Core (Document (..))-import Test.Langchain.Provider.Mock (newMockModel)--tests :: TestTree-tests =-  testGroup-    "Langchain.Chain.ChainsSpec"-    [ testCase "MapReduceChain maps and reduces across documents" $ do-        let mockModel = newMockModel "Synthesized summary"-            docs = [Document "Doc A" Map.empty, Document "Doc B" Map.empty]-            chain = newMapReduceChain mockModel-        res <- runExceptT $ runMapReduceChain chain docs Map.empty-        case res of-          Left err -> assertFailure ("MapReduceChain failed: " ++ show err)-          Right msg -> extractMessageText msg @?= "Synthesized summary"-    ]
test/Test/Langchain/Chain/RetrievalQASpec.hs view
@@ -9,10 +9,10 @@ import Test.Tasty.HUnit  import Langchain.Chain.RetrievalQA-import Langchain.Core.Model+import Langchain.DocumentLoader.Core (Document (..))+import Langchain.Model   ( extractMessageText   )-import Langchain.DocumentLoader.Core (Document (..)) import Langchain.Retriever.Core (Retriever (..)) import Test.Langchain.Provider.Mock (newMockModel) 
test/Test/Langchain/DocumentLoader/Core.hs view
@@ -15,6 +15,7 @@  import Langchain.DocumentLoader.Core import Langchain.DocumentLoader.FileLoader+import Langchain.TextSplitter.Character  createTestFile :: FilePath -> String -> IO () createTestFile = writeFile@@ -41,6 +42,13 @@         doc <> mempty @?= doc         pageContent mempty @?= ""         metadata mempty @?= empty+    , testCase "splitDocuments preserves metadata across all generated chunks" $ do+        let doc = Document "Chunk 1\n\nChunk 2" (fromList [("source", String "manual.md")])+            chunks = splitDocuments (splitText defaultCharacterSplitterOps) [doc]+        length chunks @?= 2+        map pageContent chunks @?= ["Chunk 1", "Chunk 2"]+        map metadata chunks+          @?= [fromList [("source", String "manual.md")], fromList [("source", String "manual.md")]]     ]  fileLoaderTests :: TestTree
− test/Test/Langchain/DocumentLoader/CsvSpec.hs
@@ -1,35 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Test.Langchain.DocumentLoader.CsvSpec (tests) where--import Control.Monad.Except (runExceptT)-import System.FilePath ((</>))-import System.IO.Temp (withSystemTempDirectory)-import Test.Tasty-import Test.Tasty.HUnit--import Langchain.DocumentLoader.Core (BaseLoader (..))-import Langchain.DocumentLoader.Csv--tests :: TestTree-tests =-  testGroup-    "Langchain.DocumentLoader.CsvSpec"-    [ testCase "parseCsvRows correctly splits quoted and unquoted cells" $ do-        let csvContent = "name,age,city\n\"Alice, Dr.\",30,London\nBob,25,\"New York, NY\""-            rows = parseCsvRows ',' csvContent-        length rows @?= 3-        rows !! 1 @?= ["Alice, Dr.", "30", "London"]-        rows !! 2 @?= ["Bob", "25", "New York, NY"]-    , testCase "CsvLoader loads each row into a Document with metadata" $ do-        withSystemTempDirectory "csv-loader-test" $ \tmpDir -> do-          let filePath = tmpDir </> "people.csv"-              content = "id,name,role\n1,Alice,Engineer\n2,Bob,Manager"-          writeFile filePath content-          let loader = defaultCsvLoader filePath-          res <- runExceptT $ load loader-          case res of-            Left err -> assertFailure ("CsvLoader failed: " ++ show err)-            Right docs -> do-              length docs @?= 2-    ]
test/Test/Langchain/Error.hs view
@@ -4,7 +4,7 @@  import Control.Exception (displayException) import qualified Data.Text as T-import Langchain.Core.Error+import Langchain.Error import Test.Tasty import Test.Tasty.HUnit 
test/Test/Langchain/Graph/CompilationSpec.hs view
@@ -7,7 +7,7 @@ import Test.Tasty import Test.Tasty.HUnit -import Langchain.Core.Error (errorMessage)+import Langchain.Error (errorMessage) import Langchain.Graph.StateGraph  tests :: TestTree
− test/Test/Langchain/Integration/FullRagE2ESpec.hs
@@ -1,65 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--{- |-Module      : Test.Langchain.Integration.FullRagE2ESpec-Description : Full RAG pipeline end-to-end integration tests (Gemini or Ollama LLM, Ollama embeddings)-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Uses Gemini (or Ollama) as the answering LLM and Ollama @nomic-embed-text@ for-embeddings.  If neither is available, the test skips gracefully.--}-module Test.Langchain.Integration.FullRagE2ESpec (tests) where--import Control.Monad.Except (runExceptT)-import qualified Data.Map.Strict as Map-import qualified Data.Text as T-import qualified Data.Text.Lazy as TL-import Test.Tasty-import Test.Tasty.HUnit--import Langchain.Chain.RetrievalQA-import Langchain.Core.Model (ChatModel, extractMessageText)-import Langchain.DocumentLoader.Core (Document (..))-import qualified Langchain.Embeddings.Ollama as Embed-import Langchain.Retriever.Core-import Langchain.TextSplitter.RecursiveCharacter-import Langchain.VectorStore.Core (addDocuments)-import Langchain.VectorStore.InMemory-import Test.Langchain.TestHelpers (withAnyModel)--assertRag :: ChatModel m => m -> IO ()-assertRag llmModel = do-  let longText =-        "Haskell features pure functions, lazy evaluation, and static typing.\n\n"-          <> "Typeclasses in Haskell provide ad-hoc polymorphism.\n\n"-          <> "Monads enable sequencing of effectful computations safely."-      chunks = splitTextRecursive defaultRecursiveCharacterSplitterOps (TL.fromStrict longText)-      docs = [Document c Map.empty | c <- chunks]-      embedder = Embed.OllamaEmbeddings "nomic-embed-text" Nothing Nothing Nothing-      initialStore = emptyInMemoryVectorStore embedder--  eStore <- runExceptT $ addDocuments initialStore docs-  case eStore of-    Left err ->-      -- Embeddings skipped gracefully if nomic-embed-text is not pulled-      putStrLn ("Notice: Embeddings skipped in RAG E2E: " ++ show err)-    Right populatedStore -> do-      let vsRetriever = VectorStoreRetriever populatedStore-          qaChain = newRetrievalQA llmModel vsRetriever--      res <- runExceptT $ runRetrievalQA qaChain "What enables safe effect sequencing in Haskell?"-      case res of-        Left err -> assertFailure ("RAG QA failed: " ++ show err)-        Right answer ->-          assertBool "Answer is non-empty" (not $ T.null (extractMessageText answer))--tests :: TestTree-tests =-  testGroup-    "Langchain.Integration.FullRagE2ESpec"-    [ testCase "Full RAG pipeline (Gemini or Ollama LLM + Ollama embeddings)" $-        withAnyModel assertRag assertRag-    ]
− test/Test/Langchain/Integration/OllamaChatSpec.hs
@@ -1,46 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--{- |-Module      : Test.Langchain.Integration.OllamaChatSpec-Description : Live chat invocation integration tests (Gemini or Ollama)-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--}-module Test.Langchain.Integration.OllamaChatSpec (tests) where--import Control.Monad.Except (runExceptT)-import qualified Data.Text as T-import Test.Tasty-import Test.Tasty.HUnit--import Langchain.Core.Model-import Test.Langchain.TestHelpers (withAnyModel)---- | Shared assertion body: given an invocation function, run a simple arithmetic chat.-assertChat ::-  Show err =>-  ([Message] -> IO (Either err Message)) ->-  IO ()-assertChat doInvoke = do-  let prompt = [userMessage "What is 2+2? Reply with just the digit 4 and nothing else."]-  res <- doInvoke prompt-  case res of-    Left err -> assertFailure ("Chat invocation failed: " ++ show err)-    Right msg -> do-      messageRole msg @?= Assistant-      let txt = extractMessageText msg-      assertBool-        "Response contains 4 or answer"-        ("4" `T.isInfixOf` txt || "four" `T.isInfixOf` T.toLower txt || not (T.null txt))--tests :: TestTree-tests =-  testGroup-    "Langchain.Integration.ChatSpec"-    [ testCase "Basic chat invocation with live model (OpenRouter or Ollama)" $-        withAnyModel-          (\c -> assertChat (\p -> runExceptT $ invoke c p Nothing))-          (\o -> assertChat (\p -> runExceptT $ invoke o p Nothing))-    ]
− test/Test/Langchain/Integration/OllamaEmbeddingSpec.hs
@@ -1,40 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-}--module Test.Langchain.Integration.OllamaEmbeddingSpec (tests) where--import Control.Monad.Except (runExceptT)-import qualified Data.Map.Strict as Map-import qualified Data.Text.Lazy as TL-import Test.Tasty-import Test.Tasty.HUnit--import Langchain.DocumentLoader.Core (Document (..))-import Langchain.Embeddings.Ollama (OllamaEmbeddings (..))-import Langchain.VectorStore.Core (VectorStore (..))-import Langchain.VectorStore.InMemory (InMemory, fromDocuments)-import Test.Langchain.TestHelpers (defaultEmbedModel, withOllamaModel)--tests :: TestTree-tests =-  testGroup-    "Langchain.Integration.OllamaEmbeddingSpec"-    [ testCase "Ollama live embeddings and vector similarity search" $ do-        withOllamaModel defaultEmbedModel $ \mName -> do-          let embedModel = OllamaEmbeddings mName Nothing Nothing Nothing-              docs =-                [ Document "Haskell is a statically typed, purely functional programming language." Map.empty-                , Document "Python is a dynamic programming language commonly used for machine learning." Map.empty-                , Document "Rust is a systems language focused on memory safety without garbage collection." Map.empty-                ]-          resStore <- runExceptT $ fromDocuments embedModel docs-          case resStore of-            Left err -> putStrLn $ " [NOTICE] Ollama embeddings failed (model might need pull): " ++ show err-            Right (store :: InMemory OllamaEmbeddings) -> do-              resSearch <- runExceptT $ similaritySearch store "pure functional language with types" 1-              case resSearch of-                Left err -> assertFailure ("Similarity search failed: " ++ show err)-                Right matches -> case matches of-                  [topMatch] -> assertBool "Top match is Haskell" ("Haskell" `TL.isInfixOf` pageContent topMatch)-                  _ -> assertFailure ("Expected exactly 1 match, got " ++ show (length matches))-    ]
− test/Test/Langchain/Integration/OllamaStreamSpec.hs
@@ -1,49 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--{- |-Module      : Test.Langchain.Integration.OllamaStreamSpec-Description : Live streaming integration tests (Gemini or Ollama)-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--}-module Test.Langchain.Integration.OllamaStreamSpec (tests) where--import Control.Monad.Except (runExceptT)-import Control.Monad.Trans.Resource (runResourceT)-import qualified Data.Text as T-import Test.Tasty-import Test.Tasty.HUnit--import Langchain.Core.Model-import Langchain.Core.Stream-import Test.Langchain.TestHelpers (withAnyModel)--assertStream :: ChatModel m => m -> IO ()-assertStream provider = do-  let prompt = [userMessage "Write a short story about a cat in 3 sentences."]-  res <- runResourceT $ runExceptT $ collectEvents (stream provider prompt Nothing)-  case res of-    Left err -> assertFailure ("Streaming failed: " ++ show err)-    Right events ->-      case events of-        (LLMStart {} : rest) -> case reverse rest of-          (LLMEnd _ finalMsg _ : revMiddle) -> do-            let chunks = [c | LLMChunk _ c _ <- reverse revMiddle]-                accumulated = T.concat chunks-            assertBool-              ("Emitted multiple streaming chunks. Got " ++ show (length chunks) ++ " chunks")-              (length chunks > 1)-            assertBool "Stream produced non-empty output" (not (T.null accumulated))-            extractMessageText finalMsg @?= accumulated-          _ -> assertFailure ("Expected LLMEnd as last event. Got: " ++ show events)-        _ -> assertFailure ("Expected LLMStart as first event. Got: " ++ show events)--tests :: TestTree-tests =-  testGroup-    "Langchain.Integration.StreamSpec"-    [ testCase "Live streaming emits incremental chunks (Gemini or Ollama)" $-        withAnyModel assertStream assertStream-    ]
− test/Test/Langchain/Integration/OllamaToolSpec.hs
@@ -1,92 +0,0 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-}--module Test.Langchain.Integration.OllamaToolSpec (tests) where--import Control.Monad.Except (runExceptT)-import Data.Aeson (FromJSON, ToJSON, decode)-import qualified Data.ByteString.Lazy.Char8 as LBSC-import Data.Proxy (Proxy (..))-import Data.Text (Text)-import qualified Data.Text as T-import qualified Data.Text.Encoding as TE-import GHC.Generics (Generic)-import Test.Tasty-import Test.Tasty.HUnit--import Langchain.Core.Error-import Langchain.Core.Model-import Langchain.Core.Tool (Tool (..), toolExecute)-import Langchain.OutputParser.Structured-  ( StructuredOutput (..)-  , extractJsonFromMarkdown-  , toOllamaSchema-  )-import Langchain.Provider.Ollama-  ( chatRequestFor-  , withJsonFormat-  , withSchemaFormat-  , withTools-  )-import Langchain.Tool.Calculator (calculatorTool)-import Test.Langchain.TestHelpers (defaultTestModel, newTestOllama, withOllamaModel)--data TestMathResult = TestMathResult-  { answer :: Double-  , explanation :: Text-  }-  deriving (Show, Eq, Generic, ToJSON, FromJSON, StructuredOutput)--tests :: TestTree-tests =-  testGroup-    "Langchain.Integration.OllamaToolSpec"-    [ testCase "Ollama tool calling or direct evaluation with live model" $ do-        withOllamaModel defaultTestModel $ \modelName -> do-          provider <- newTestOllama modelName-          let prompt =-                [ systemMessage "You are a math helper. Solve: 15 * 4. You must call the calculator tool."-                , userMessage "What is 15 * 4?"-                ]-              req = withTools [calculatorTool :: Tool IO] (chatRequestFor provider prompt)-          res <- runExceptT $ invoke provider prompt (Just req)-          case res of-            Left err -> assertFailure ("Tool test invocation failed: " ++ show err)-            Right msg -> do-              case messageToolCalls msg of-                Just (tc : _) -> do-                  toolCallName tc @?= "calculator"-                  calcRes <- toolExecute calculatorTool (toolCallArguments tc) :: IO (Either LangchainError Text)-                  case calcRes of-                    Left err -> assertFailure ("Calculator execution error: " ++ show err)-                    Right out -> out @?= "60.0"-                _ -> do-                  let txt = extractMessageText msg-                  assertBool "Response contains 60 or answer" ("60" `T.isInfixOf` txt || not (T.null txt))-    , testCase "Ollama structured output with SchemaFormat extraction" $ do-        withOllamaModel defaultTestModel $ \modelName -> do-          provider <- newTestOllama modelName-          let prompt =-                [ systemMessage "You are a helpful math extractor."-                , userMessage "Calculate 25 + 75 and explain briefly."-                ]-              valSchema = outputSchema (Proxy :: Proxy TestMathResult)-              baseReq = chatRequestFor provider prompt-              req = case toOllamaSchema valSchema of-                Just s -> withSchemaFormat s baseReq-                Nothing -> withJsonFormat baseReq-          res <- runExceptT $ invoke provider prompt (Just req)-          case res of-            Left err -> assertFailure ("Structured Ollama invocation failed: " ++ show err)-            Right msg -> do-              let rawText = extractMessageText msg-                  cleanJson = extractJsonFromMarkdown rawText-                  bs = LBSC.fromStrict (TE.encodeUtf8 cleanJson)-              case decode bs of-                Just (result :: TestMathResult) -> do-                  answer result @?= 100.0-                  assertBool "Explanation is not empty" (not (T.null (explanation result)))-                Nothing -> assertFailure ("Failed to decode response into TestMathResult: " ++ show rawText)-    ]
− test/Test/Langchain/Integration/ReActAgentE2ESpec.hs
@@ -1,42 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedStrings #-}--{- |-Module      : Test.Langchain.Integration.ReActAgentE2ESpec-Description : ReAct agent end-to-end integration tests (Gemini or Ollama)-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--}-module Test.Langchain.Integration.ReActAgentE2ESpec (tests) where--import Control.Monad.Except (ExceptT, runExceptT)-import qualified Data.Text as T-import Test.Tasty-import Test.Tasty.HUnit--import Langchain.Agent.ReAct-import Langchain.Core.Error (LangchainError)-import Langchain.Core.Model-import Langchain.Tool.Binding (ToolBinder)-import Langchain.Tool.Calculator (calculatorTool)-import Test.Langchain.TestHelpers (withAnyModel)--assertReAct :: ToolBinder m (ExceptT LangchainError IO) => m -> IO ()-assertReAct provider = do-  let agent = createReActAgent provider [calculatorTool]-      query = [userMessage "Calculate 12 * 12. Provide the result."]-  res <- runExceptT $ runReActAgent agent query-  case res of-    Left err -> assertFailure ("ReAct agent failed: " ++ show err)-    Right msg ->-      assertBool "Result is non-empty" (not (T.null (extractMessageText msg)))--tests :: TestTree-tests =-  testGroup-    "Langchain.Integration.ReActAgentE2ESpec"-    [ testCase "ReAct agent executes full loop (Gemini or Ollama)" $-        withAnyModel assertReAct assertReAct-    ]
− test/Test/Langchain/Integration/StateGraphE2ESpec.hs
@@ -1,79 +0,0 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedStrings #-}--{- |-Module      : Test.Langchain.Integration.StateGraphE2ESpec-Description : StateGraph multi-node pipeline integration tests (Gemini or Ollama)-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--}-module Test.Langchain.Integration.StateGraphE2ESpec (tests) where--import Control.Monad.Except (runExceptT)-import Data.Aeson (FromJSON, ToJSON)-import Data.Text (Text)-import qualified Data.Text as T-import GHC.Generics (Generic)-import Test.Tasty-import Test.Tasty.HUnit--import Langchain.Core.Model-import Langchain.Graph.StateGraph-import Test.Langchain.TestHelpers (withAnyModel)--data GraphPipelineTestState = GraphPipelineTestState-  { originalPrompt :: Text-  , draftResponse :: Text-  , reviewNotes :: Text-  }-  deriving (Show, Eq, Generic, ToJSON, FromJSON)--graphStateReducer :: StateReducer GraphPipelineTestState-graphStateReducer old new =-  GraphPipelineTestState-    { originalPrompt = if T.null (originalPrompt new) then originalPrompt old else originalPrompt new-    , draftResponse = if T.null (draftResponse new) then draftResponse old else draftResponse new-    , reviewNotes = if T.null (reviewNotes new) then reviewNotes old else reviewNotes new-    }--assertStateGraph :: ChatModel m => m -> IO ()-assertStateGraph provider = do-  let draftNode s = do-        let prompt = [userMessage $ "Answer concisely in one sentence: " <> originalPrompt s]-        res <- invoke provider prompt Nothing-        pure $ Right (s {draftResponse = extractMessageText res})--      reviewNode s = do-        let prompt = [userMessage $ "Review and confirm this answer: " <> draftResponse s]-        res <- invoke provider prompt Nothing-        pure $ Right (s {reviewNotes = extractMessageText res})--      g =-        addEdge "draft" "review" $-          addEdge "review" endNodeId $-            addNode "draft" draftNode $-              addNode "review" reviewNode $-                emptyStateGraph graphStateReducer--  case compileGraph g of-    Left err -> assertFailure ("Graph compilation failed: " ++ show err)-    Right cg -> do-      let initState = GraphPipelineTestState "What is 2 + 2?" "" ""-      res <- runExceptT $ runGraph cg "draft" initState-      case res of-        Left err -> assertFailure ("StateGraph run failed: " ++ show err)-        Right finalState -> do-          assertBool "Draft response generated" (not (T.null $ draftResponse finalState))-          assertBool "Review notes generated" (not (T.null $ reviewNotes finalState))--tests :: TestTree-tests =-  testGroup-    "Langchain.Integration.StateGraphE2ESpec"-    [ testCase "StateGraph multi-node pipeline (Gemini or Ollama)" $-        withAnyModel assertStateGraph assertStateGraph-    ]
− test/Test/Langchain/Integration/StreamingCachingRetryE2ESpec.hs
@@ -1,49 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--{- |-Module      : Test.Langchain.Integration.StreamingCachingRetryE2ESpec-Description : Caching and retry resilience integration tests (Gemini or Ollama)-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--}-module Test.Langchain.Integration.StreamingCachingRetryE2ESpec (tests) where--import Control.Monad.Except (runExceptT)-import qualified Data.Text as T-import Test.Tasty-import Test.Tasty.HUnit--import Langchain.Cache.Core-import Langchain.Core.Model-import Langchain.Resilience.Retry-import Test.Langchain.TestHelpers (withAnyModel)--assertCachingRetry :: CacheableChatModel m => m -> IO ()-assertCachingRetry baseModel = do-  cache <- newInMemoryCache-  let cachedModel = withCaching baseModel cache-      msgs = [userMessage "Respond with the single word 'OK'."]--  -- First call: populates cache-  r1 <- runExceptT $ withRetry defaultRetryPolicy (invoke cachedModel msgs Nothing)-  case r1 of-    Left err -> assertFailure ("First invocation failed: " ++ show err)-    Right msg1 -> do-      assertBool "Response is non-empty" (not $ T.null (extractMessageText msg1))--      -- Second call: hits cache (must return identical result)-      r2 <- runExceptT $ withRetry defaultRetryPolicy (invoke cachedModel msgs Nothing)-      case r2 of-        Left err -> assertFailure ("Cached invocation failed: " ++ show err)-        Right msg2 ->-          extractMessageText msg2 @?= extractMessageText msg1--tests :: TestTree-tests =-  testGroup-    "Langchain.Integration.StreamingCachingRetryE2ESpec"-    [ testCase "Model wrapped in Caching and Retry policies (OpenRouter or Ollama)" $-        withAnyModel assertCachingRetry assertCachingRetry-    ]
− test/Test/Langchain/MCP/McpSpec.hs
@@ -1,36 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Test.Langchain.MCP.McpSpec (tests) where--import qualified Data.Aeson as Aeson-import qualified Data.Text as T-import Test.Tasty-import Test.Tasty.HUnit--import Langchain.MCP.Client-import Langchain.Tool.Core (Tool (..))--tests :: TestTree-tests =-  testGroup-    "Langchain.MCP.McpSpec"-    [ testCase "newStdioMcpClient initializes transport and server name" $ do-        let client = newStdioMcpClient "test-mcp" "npx" ["-y", "@modelcontextprotocol/server-everything"]-        serverName client @?= "test-mcp"-        clientTransport client @?= StdioTransport "npx" ["-y", "@modelcontextprotocol/server-everything"]-    , testCase "mcpToolToLangchainTool converts remote tool to callable local Tool" $ do-        let client = newStdioMcpClient "test-server" "echo" []-            toolInfo =-              McpToolInfo-                { mcpToolName = "echo_tool"-                , mcpToolDescription = "Echoes inputs"-                , mcpToolInputSchema = Aeson.object []-                }-            langchainTool = mcpToolToLangchainTool client toolInfo-        toolName langchainTool @?= "echo_tool"-        toolDescription langchainTool @?= "Echoes inputs"-        res <- toolExecute langchainTool (Aeson.object [])-        case res of-          Left err -> assertFailure ("Tool execution failed: " ++ show err)-          Right out -> assertBool "Executed stdio tool" ("Executed MCP tool" `T.isInfixOf` out)-    ]
test/Test/Langchain/Memory/Core.hs view
@@ -4,16 +4,20 @@  import Control.Concurrent.Async (forConcurrently_) import Control.Monad.Except (runExceptT)+import qualified Data.Map.Strict as Map import qualified Data.Text as T+import System.FilePath ((</>))+import System.IO.Temp (withSystemTempDirectory) import Test.Tasty import Test.Tasty.HUnit -import Langchain.Core.Model+import Langchain.Memory+import Langchain.Model   ( assistantMessage+  , extractMessageText   , systemMessage   , userMessage   )-import Langchain.Memory.Core  tests :: TestTree tests =@@ -21,6 +25,11 @@     "Langchain.Memory.Core Tests"     [ utilityTests     , windowBufferMemoryTests+    , conversationBufferMemoryTests+    , persistenceTests+    , sessionMemoryTests+    , entityMemoryTests+    , combinedMemoryTests     , concurrencyTests     ] @@ -43,6 +52,9 @@               ]             trimmed = trimMessages 2 msgs         trimmed @?= [assistantMessage "AI1", userMessage "User2"]+    , testCase "formatHistory formats roles and messages with default labels" $ do+        let msgs = [userMessage "Hi", assistantMessage "Hello!"]+        formatHistory msgs @?= "Human: Hi\nAI: Hello!"     ]  windowBufferMemoryTests :: TestTree@@ -96,6 +108,183 @@             case msgs of               (m : _) -> m @?= systemMessage "You are a helpful AI assistant"               [] -> assertFailure "Expected non-empty messages"+    , testCase "resetWindowBuffer restores initial seeded messages" $ do+        let initialMsgs = [systemMessage "Initial system prompt"]+        memory <- newWindowBufferMemory 3 initialMsgs+        res <- runExceptT $ do+          saveContext memory "User1" "AI1"+          saveContext memory "User2" "AI2"+          resetWindowBuffer memory+          messages memory+        case res of+          Left err -> assertFailure $ "Failed: " ++ show err+          Right msgs -> msgs @?= initialMsgs+    ]++conversationBufferMemoryTests :: TestTree+conversationBufferMemoryTests =+  testGroup+    "ConversationBufferMemory Tests"+    [ testCase "retains unbounded conversation turns" $ do+        mem <- newConversationBufferMemory [systemMessage "System prompt"]+        res <- runExceptT $ do+          saveContext mem "User Turn 1" "AI Turn 1"+          saveContext mem "User Turn 2" "AI Turn 2"+          saveContext mem "User Turn 3" "AI Turn 3"+          messages mem+        case res of+          Left err -> assertFailure $ "Failed: " ++ show err+          Right msgs -> length msgs @?= 7 -- 1 system + 6 turns+    , testCase "loadMemoryVariables produces formatted history map" $ do+        mem <- newConversationBufferMemory []+        res <- runExceptT $ do+          saveContext mem "Hello" "Hi there!"+          loadMemoryVariables mem+        case res of+          Left err -> assertFailure $ "Failed: " ++ show err+          Right vars -> Map.lookup "history" vars @?= Just "Human: Hello\nAI: Hi there!"+    , testCase "resetConversationBuffer restores initial seeded messages" $ do+        let initialMsgs = [systemMessage "Base prompt"]+        mem <- newConversationBufferMemory initialMsgs+        res <- runExceptT $ do+          saveContext mem "Hello" "World"+          resetConversationBuffer mem+          messages mem+        case res of+          Left err -> assertFailure $ "Failed: " ++ show err+          Right msgs -> msgs @?= initialMsgs+    ]++persistenceTests :: TestTree+persistenceTests =+  testGroup+    "Persistence Tests"+    [ testCase "saveMemoryToFile and loadMemoryFromFile preserves full conversation history" $ do+        withSystemTempDirectory "memory-test" $ \tmpDir -> do+          let filePath = tmpDir </> "conversation.json"+          mem1 <- newConversationBufferMemory [systemMessage "Assistant system"]+          res1 <- runExceptT $ do+            saveContext mem1 "What is Haskell?" "A purely functional programming language."+            saveMemoryToFile filePath mem1++          case res1 of+            Left err -> assertFailure $ "Save failed: " ++ show err+            Right () -> do+              mem2 <- newConversationBufferMemory []+              res2 <- runExceptT $ do+                loadMemoryFromFile filePath mem2+                messages mem2+              case res2 of+                Left err -> assertFailure $ "Load failed: " ++ show err+                Right msgs -> do+                  length msgs @?= 3+                  msgs+                    @?= [ systemMessage "Assistant system"+                        , userMessage "What is Haskell?"+                        , assistantMessage "A purely functional programming language."+                        ]+    ]++sessionMemoryTests :: TestTree+sessionMemoryTests =+  testGroup+    "SessionMemoryStore Tests"+    [ testCase "maintains isolated memory per session ID" $ do+        store <- newSessionMemoryStore (\_ -> newConversationBufferMemory [])+        res <- runExceptT $ do+          memAlice <- getSessionMemory store "user-alice"+          memBob <- getSessionMemory store "user-bob"+          saveContext memAlice "Alice question" "Alice answer"+          saveContext memBob "Bob question" "Bob answer"++          msgsAlice <- messages memAlice+          msgsBob <- messages memBob+          pure (msgsAlice, msgsBob)++        case res of+          Left err -> assertFailure $ "Session test failed: " ++ show err+          Right (aliceMsgs, bobMsgs) -> do+            length aliceMsgs @?= 2+            length bobMsgs @?= 2+            aliceMsgs @?= [userMessage "Alice question", assistantMessage "Alice answer"]+            bobMsgs @?= [userMessage "Bob question", assistantMessage "Bob answer"]+    , testCase "session management: list, count, delete, and clear" $ do+        store <- newSessionMemoryStore (\_ -> newWindowBufferMemory 5 [])+        _ <- getSessionMemory store "s1"+        _ <- getSessionMemory store "s2"+        _ <- getSessionMemory store "s3"++        cnt <- sessionCount store+        cnt @?= 3++        sList <- listSessions store+        length sList @?= 3++        deleteSession store "s2"+        cnt2 <- sessionCount store+        cnt2 @?= 2++        clearAllSessions store+        cnt3 <- sessionCount store+        cnt3 @?= 0+    ]++entityMemoryTests :: TestTree+entityMemoryTests =+  testGroup+    "EntityMemory Tests"+    [ testCase "tracks entity facts and formats them into system message" $ do+        mem <- newEntityMemory (Map.fromList [("user_name", "Alice")])+        setEntity mem "favorite_language" "Haskell"++        val <- getEntity mem "favorite_language"+        val @?= Just "Haskell"++        res <- runExceptT $ messages mem+        case res of+          Left err -> assertFailure $ "Failed: " ++ show err+          Right msgs -> do+            length msgs @?= 1+            case msgs of+              (m : _) -> assertBool "Contains entities" ("favorite_language: Haskell" `T.isInfixOf` extractMessageText m)+              [] -> assertFailure "Expected non-empty message"+    , testCase "resetEntitiesToInitial restores original seeded facts" $ do+        mem <- newEntityMemory (Map.fromList [("role", "Engineer")])+        setEntity mem "role" "Manager"+        setEntity mem "location" "Remote"+        resetEntitiesToInitial mem+        val <- getEntity mem "role"+        val @?= Just "Engineer"+        loc <- getEntity mem "location"+        loc @?= Nothing+    , testCase "deleteEntity and clearEntities remove attributes" $ do+        mem <- newEntityMemory (Map.fromList [("a", "1"), ("b", "2")])+        deleteEntity mem "a"+        valA <- getEntity mem "a"+        valA @?= Nothing+        clearEntities mem+        allEnts <- getAllEntities mem+        assertBool "All entities cleared" (Map.null allEnts)+    ]++combinedMemoryTests :: TestTree+combinedMemoryTests =+  testGroup+    "CombinedMemory Tests"+    [ testCase "aggregates messages from multiple memory stores" $ do+        bufMem <- newWindowBufferMemory 5 [systemMessage "System"]+        entMem <- newEntityMemory (Map.fromList [("role", "Developer")])+        let combined = newCombinedMemory [SomeMemory bufMem, SomeMemory entMem]++        res <- runExceptT $ do+          saveContext combined "Hello" "World"+          messages combined++        case res of+          Left err -> assertFailure $ "Combined failed: " ++ show err+          Right msgs -> do+            -- bufMem has 3 messages (system + user + ai), entMem has 1 entity message+            length msgs @?= 4     ]  concurrencyTests :: TestTree
− test/Test/Langchain/Memory/EntitySpec.hs
@@ -1,33 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Test.Langchain.Memory.EntitySpec (tests) where--import Control.Monad.Except (runExceptT)-import qualified Data.Map.Strict as Map-import Test.Tasty-import Test.Tasty.HUnit--import Langchain.Core.Model (userMessage)-import Langchain.Memory.Core (BaseMemory (..))-import Langchain.Memory.Entity-import Test.Langchain.Provider.Mock (newMockModel)--tests :: TestTree-tests =-  testGroup-    "Langchain.Memory.EntitySpec"-    [ testCase "EntityMemory extracts and injects entities into conversation" $ do-        let mockModel = newMockModel "User: Likes Haskell and functional programming\nProject: Langchain-HS"-        mem <- newEntityMemory mockModel []-        res <- runExceptT $ do-          addMessage mem (userMessage "I am working on Langchain-HS and love functional programming")-          entities <- getEntities mem-          allMsgs <- messages mem-          pure (entities, allMsgs)-        case res of-          Left err -> assertFailure ("EntityMemory failed: " ++ show err)-          Right (entities, msgs) -> do-            Map.lookup "User" entities @?= Just "Likes Haskell and functional programming"-            Map.lookup "Project" entities @?= Just "Langchain-HS"-            assertBool "Includes system message with entities" (length msgs >= 2)-    ]
test/Test/Langchain/Memory/SummarySpec.hs view
@@ -7,10 +7,11 @@ import Test.Tasty import Test.Tasty.HUnit -import Langchain.Core.Model (extractMessageText, userMessage)+import Langchain.Error (LangchainError (..)) import Langchain.Memory.Core (BaseMemory (..)) import Langchain.Memory.Summary-import Test.Langchain.Provider.Mock (newMockModel)+import Langchain.Model (extractMessageText, userMessage)+import Test.Langchain.Provider.Mock (newMockErrorModel, newMockModel)  tests :: TestTree tests =@@ -34,4 +35,15 @@             assertBool               "Messages contains summary in system message"               (any (\m -> "Summary" `T.isInfixOf` extractMessageText m) msgs)+    , testCase "SummaryMemory retains messages without data loss if summarizer fails" $ do+        let failingModel = newMockErrorModel (LLMError "Rate limit exceeded" Nothing)+        mem <- newSummaryMemory failingModel 2 []+        _ <- runExceptT $ do+          addMessage mem (userMessage "Message 1")+          addMessage mem (userMessage "Message 2")+          addMessage mem (userMessage "Message 3") -- triggers summarizer which fails+        allMsgs <- runExceptT $ messages mem+        case allMsgs of+          Right msgs -> length msgs @?= 3 -- History was NOT deleted!+          Left err -> assertFailure ("Failed to retrieve messages: " ++ show err)     ]
test/Test/Langchain/Memory/TokenBufferMemory.hs view
@@ -7,13 +7,13 @@ import Test.Tasty (TestTree, testGroup) import Test.Tasty.HUnit -import Langchain.Core.Error (errorMessage)-import Langchain.Core.Model+import Langchain.Error (errorMessage)+import Langchain.Memory.Core (BaseMemory (..))+import qualified Langchain.Memory.Core as TB+import Langchain.Model   ( systemMessage   , userMessage   )-import Langchain.Memory.Core (BaseMemory (..))-import qualified Langchain.Memory.Core as TB  tests :: TestTree tests =@@ -63,4 +63,14 @@         case res of           Right msgs -> msgs @?= [systemMessage "You are a helpful AI assistant"]           Left _ -> assertFailure "Clear failed unexpectedly"+    , testCase "resetTokenBuffer restores initial seeded messages" $ do+        let initMsgs = [systemMessage "Seeded system prompt"]+        mem <- TB.newTokenBufferMemory 100 initMsgs+        res <- runExceptT $ do+          addMessage mem (userMessage "First message")+          TB.resetTokenBuffer mem+          messages mem+        case res of+          Right msgs -> msgs @?= initMsgs+          Left err -> assertFailure $ "Failed: " ++ show err     ]
− test/Test/Langchain/ObservabilitySpec.hs
@@ -1,80 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Test.Langchain.ObservabilitySpec (tests) where--import Control.Concurrent.STM (atomically, modifyTVar')-import Control.Monad.Except (runExceptT)-import qualified Data.Map.Strict as Map-import Data.Maybe (isJust)-import qualified Data.Text as T-import Test.Tasty-import Test.Tasty.HUnit--import Langchain.Observability--tests :: TestTree-tests =-  testGroup-    "Langchain.Observability"-    [ testGroup-        "Structured Logging"-        [ testCase "InMemoryLogger records events and respects minLevel" $ do-            logger <- newInMemoryLogger InfoLevel-            let logHandler =-                  Logger-                    { minLevel = InfoLevel-                    , writeLog = \ev -> do-                        atomically $ modifyTVar' (inMemoryVar logger) (\ls -> ls ++ [ev])-                    }-            logDebug logHandler "Agent" "This debug log should be ignored"-            logInfo logHandler "Agent" "Starting agent turn"-            logWarn logHandler "Retriever" "Slow response from vector store"-            logError logHandler "Model" "Rate limit reached"--            logs <- getInMemoryLogs logger-            case logs of-              (firstLog : _) -> do-                length logs @?= 3-                logLevel firstLog @?= InfoLevel-                logMessage firstLog @?= "Starting agent turn"-              [] -> assertFailure "Expected logs to be non-empty"-        , testCase "logEvent attaches custom metadata" $ do-            logger <- newInMemoryLogger DebugLevel-            let logHandler =-                  Logger-                    { minLevel = DebugLevel-                    , writeLog = \ev -> do-                        atomically $ modifyTVar' (inMemoryVar logger) (\ls -> ls ++ [ev])-                    }-            let meta = Map.fromList [("model", "qwen2.5:7b"), ("tokens", "128")]-            logEvent logHandler InfoLevel "Provider" "Model invocation complete" meta--            logs <- getInMemoryLogs logger-            case logs of-              [firstLog] -> logMetadata firstLog @?= meta-              _ -> assertFailure ("Expected exactly 1 log, got " ++ show (length logs))-        ]-    , testGroup-        "OpenTelemetry Tracing"-        [ testCase "withSpan wraps computation, records duration and Ok status" $ do-            tracer <- newOTelTracer (Just "trace-100")-            res <- runExceptT $ withSpan tracer "llm_invoke" Nothing ClientSpan (Map.singleton "provider" "ollama") $ do-              pure ("success response" :: T.Text)-            res @?= Right "success response"--            spans <- getSpans tracer-            case spans of-              [sp] -> do-                spanName sp @?= "llm_invoke"-                spanTraceId sp @?= "trace-100"-                spanStatus sp @?= StatusOk-                assertBool "Duration recorded" (isJust (spanDurationMicros sp))-              _ -> assertFailure ("Expected exactly 1 span, got " ++ show (length spans))-        , testCase "exportSpansJson exports valid JSON formatted trace" $ do-            tracer <- newOTelTracer (Just "trace-export")-            _ <- startSpan tracer "step1" Nothing InternalSpan Map.empty-            jsonText <- exportSpansJson tracer-            assertBool "Contains span name" ("step1" `T.isInfixOf` jsonText)-            assertBool "Contains trace-export" ("trace-export" `T.isInfixOf` jsonText)-        ]-    ]
test/Test/Langchain/OutputParser/AdvancedParsersSpec.hs view
@@ -18,7 +18,7 @@ import Test.Tasty import Test.Tasty.HUnit -import Langchain.Core.Model (userMessage)+import Langchain.Model (userMessage) import Langchain.OutputParser.Structured import Test.Langchain.Provider.Mock (newMockModel) @@ -77,12 +77,5 @@                 Just (Object eObj) -> KM.lookup "type" eObj @?= Just (String "array")                 _ -> assertFailure "Expected companyEmployees to be array"             _ -> assertFailure "Expected properties in schema"-          _ -> assertFailure "Expected Object schema"-    , testCase "toOllamaSchema and fromOllamaSchema bridge round-trip" $ do-        let s = outputSchema (Proxy :: Proxy TestCompany)-        case toOllamaSchema s of-          Nothing -> assertFailure "toOllamaSchema failed for TestCompany"-          Just ollamaS -> do-            let rt = fromOllamaSchema ollamaS-            toOllamaSchema rt @?= Just ollamaS+          _ -> assertFailure "Expected Object type"     ]
test/Test/Langchain/OutputParser/Core.hs view
@@ -5,43 +5,26 @@ import Test.Tasty import Test.Tasty.HUnit -import Data.Aeson import Data.Text (Text)-import Langchain.Core.Error (LangchainError)+import Langchain.Error (LangchainError) import Langchain.OutputParser.Core -data Person = Person-  { name :: Text-  , age :: Int-  }-  deriving (Show, Eq)--instance FromJSON Person where-  parseJSON = withObject "Person" $ \v ->-    Person-      <$> v .: "name"-      <*> v .: "age"- tests :: TestTree tests =   testGroup     "OutputParser Tests"-    [ testCase "Bool parser should parse 'true'" $+    [ testCase "Text parser should return input verbatim" $+        (parse "raw output" :: Either LangchainError Text) @?= Right "raw output"+    , testCase "Bool parser should parse affirmative representations" $ do         (parse "true" :: Either LangchainError Bool) @?= Right True-    , testCase "Bool parser should parse 'True'" $         (parse "True" :: Either LangchainError Bool) @?= Right True-    , testCase "Bool parser should parse 'TRUE'" $-        (parse "TRUE" :: Either LangchainError Bool) @?= Right True-    , testCase "Bool parser should parse 'true' with whitespace" $-        (parse "  true  " :: Either LangchainError Bool) @?= Right True-    , testCase "Bool parser should parse 'false'" $+        (parse "yes" :: Either LangchainError Bool) @?= Right True+        (parse "1" :: Either LangchainError Bool) @?= Right True+    , testCase "Bool parser should parse negative representations" $ do         (parse "false" :: Either LangchainError Bool) @?= Right False-    , testCase "Bool parser should parse 'False'" $         (parse "False" :: Either LangchainError Bool) @?= Right False-    , testCase "Bool parser should parse 'FALSE'" $-        (parse "FALSE" :: Either LangchainError Bool) @?= Right False-    , testCase "Bool parser should parse 'false' with whitespace" $-        (parse "  false  " :: Either LangchainError Bool) @?= Right False+        (parse "no" :: Either LangchainError Bool) @?= Right False+        (parse "0" :: Either LangchainError Bool) @?= Right False     , testCase "Bool parser should fail on invalid input" $         case parse "not a boolean" :: Either LangchainError Bool of           Left _ -> assertBool "Should be Left" True@@ -50,34 +33,20 @@         parse "" @?= Right (CommaSeparatedList [""])     , testCase "CommaSeparatedList parser should parse single item" $         parse "item" @?= Right (CommaSeparatedList ["item"])-    , testCase "CommaSeparatedList parser should parse multiple items" $-        parse "item1,item2,item3" @?= Right (CommaSeparatedList ["item1", "item2", "item3"])-    , testCase "CommaSeparatedList parser should trim whitespace" $+    , testCase "CommaSeparatedList parser should parse multiple items and 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))-    , testCase "JSONOutputStructure parser should fail on invalid JSON" $-        case parse "{not valid json}" :: Either LangchainError (JSONOutputStructure Person) of+    , testCase "Int and Integer parser should parse valid numbers" $ do+        (parse "42" :: Either LangchainError Int) @?= Right 42+        (parse " -100 " :: Either LangchainError Int) @?= Right (-100)+        (parse "99999999999999999999" :: Either LangchainError Integer) @?= Right 99999999999999999999+    , testCase "Double parser should parse floating point numbers" $ do+        (parse "3.14159" :: Either LangchainError Double) @?= Right 3.14159+        (parse "-0.5" :: Either LangchainError Double) @?= Right (-0.5)+    , testCase "Numeric parsers should fail on non-numeric input" $ do+        case parse "not a number" :: Either LangchainError Int of           Left _ -> assertBool "Should be Left" True-          Right _ -> assertFailure "Should have failed parsing"-    , testCase "NumberSeparatedList parser should parse numbered list" $-        parse "1. First item\n2. Second item\n3. Third item"-          @?= Right (NumberSeparatedList ["First item", "Second item", "Third item"])-    , testCase "NumberSeparatedList parser should handle whitespace" $-        parse "1.   First item  \n  2.  Second item\n3. Third item"-          @?= Right (NumberSeparatedList ["First item", "Second item", "Third item"])-    , testCase "NumberSeparatedList parser should handle multi-digit numbers" $-        parse "10. First item\n11. Second item\n12. Third item"-          @?= Right (NumberSeparatedList ["First item", "Second item", "Third item"])-    , testCase "NumberSeparatedList parser should handle text before numbered list" $-        parse "Here is a list:\n1. First item\n2. Second item"-          @?= Right (NumberSeparatedList ["First item", "Second item"])-    , testCase "NumberSeparatedList parser should handle whitespace between number and dot" $-        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 LangchainError NumberSeparatedList of+          Right _ -> assertFailure "Should have failed"+        case parse "abc" :: Either LangchainError Double of           Left _ -> assertBool "Should be Left" True-          Right _ -> assertFailure "Should have failed parsing"+          Right _ -> assertFailure "Should have failed"     ]
− test/Test/Langchain/PromptTemplate/Chat/ChatPromptTemplateSpec.hs
@@ -1,661 +0,0 @@-{-# LANGUAGE DuplicateRecordFields #-}-{-# LANGUAGE OverloadedStrings #-}--module Test.Langchain.PromptTemplate.Chat.ChatPromptTemplateSpec (tests) where--import Data.Aeson (decode, encode, object, (.=))-import Data.List.NonEmpty (NonEmpty (..))-import qualified Data.Map.Strict as Map-import Data.Text (Text)-import qualified Data.Text as T-import Test.Tasty-import Test.Tasty.HUnit--import Langchain.Core.Model.Types-  ( ContentBlock (..)-  , ImageContent (..)-  , ImageSource (..)-  , Message (..)-  , Role (..)-  , extractMessageText-  , textMessage-  , userMessage-  )-import Langchain.PromptTemplate.Chat.ChatPromptTemplate-  ( ChatPromptInput (..)-  , ChatPromptMessage-  , ChatPromptTemplate (..)-  , ContentPromptBlock (..)-  , PartialValue (..)-  , append-  , contentMessage-  , extend-  , format-  , formatPrompt-  , fromMessages-  , fromTemplate-  , fromTemplateWithOptions-  , invoke-  , message-  , messagesPlaceholder-  , messagesPlaceholderWithOptions-  , partial-  , templateMessage-  , toMessages-  , toString-  )-import Langchain.PromptTemplate.Chat.MessagesPlaceholder-  ( MessagesPlaceholder (..)-  , MessagesPlaceholderOptions (..)-  )-import Langchain.PromptTemplate.Prompt (PromptTemplateOptions (..), TemplateFormat (..))--tests :: TestTree-tests =-  testGroup-    "ChatPromptTemplate"-    [ fromTemplateTests-    , fromMessagesTests-    , richContentTests-    , formatPromptTests-    , missingVariableTests-    , partialTests-    , appendExtendTests-    , invokeTests-    , serializationTests-    ]--fromTemplateTests :: TestTree-fromTemplateTests =-  testGroup-    "fromTemplate"-    [ testCase "creates a chat prompt template" $ do-        let actual = fromTemplate "hi {foo} {bar}"-            expected =-              ChatPromptTemplate-                { messages =-                    [templateMessage User "hi {foo} {bar}"]-                , inputVariables = ["foo", "bar"]-                }-        actual @?= expected-    , testCase "creates a chat prompt template with partials" $ do-        let actual =-              fromTemplateWithOptions-                "hi {foo} {bar}"-                (PromptTemplateOptions (Map.singleton "foo" "jim"))-        inputVariables actual @?= ["bar"]-        case formatPrompt actual (Map.singleton "bar" "bob") of-          Left err -> assertFailure $ "Expected formatted prompt, got " <> show err-          Right promptValue -> toMessages promptValue @?= [userMessage "hi jim bob"]-    ]--fromMessagesTests :: TestTree-fromMessagesTests =-  testGroup-    "fromMessages"-    [ testCase "preserves static messages" $ do-        let actual =-              fromMessages $-                chatPromptMessages <> [message (userMessage "foo")]-        case actual of-          ChatPromptTemplate {inputVariables = actualInputVariables} ->-            actualInputVariables @?= ["context", "foo", "bar"]-        length (messages actual) @?= 5-        case formatPrompt actual withMessagesVariables of-          Left err -> assertFailure $ "Expected formatted prompt, got " <> show err-          Right promptValue ->-            last (toMessages promptValue) @?= userMessage "foo"-    ]--formatPromptTests :: TestTree-formatPromptTests =-  testGroup-    "formatPrompt / format"-    [ testCase "formats all chat prompt messages" $ do-        let actual = formatPrompt chatPromptTemplate promptVariables-        case actual of-          Left err -> assertFailure $ "Expected formatted prompt, got " <> show err-          Right promptValue -> do-            let promptMessages = toMessages promptValue-            length promptMessages @?= 4-            map extractMessageText promptMessages-              @?= [ "Here's some context: context"-                  , "Hello foo, I'm bar. Thanks for the context"-                  , "I'm an AI. I'm foo. I'm bar."-                  , "I'm a generic message. I'm foo. I'm bar."-                  ]-            toString promptValue @?= expectedFormattedPrompt-        format chatPromptTemplate promptVariables @?= Right expectedFormattedPrompt-    ]--missingVariableTests :: TestTree-missingVariableTests =-  testGroup-    "missing variables"-    [ testCase "fails for missing FString variables in chat messages" $ do-        let template = fromMessages [templateMessage User "Hi {foo}"]-        assertMissingVariable "Parameter not found: foo" (formatPrompt template Map.empty)-    , testCase "fails for missing FString variables in multipart text blocks" $ do-        let template = fromMessages [contentMessage User [TextPromptBlock FString "Hi {foo}"]]-        assertMissingVariable "Parameter not found: foo" (formatPrompt template Map.empty)-    , testCase "fails for missing FString variables in image url blocks" $ do-        let template =-              fromMessages-                [ contentMessage-                    User-                    [ ImagePromptBlock FString $-                        ImageContent (ImageUrl "https://example.com/{foo}") Nothing Nothing-                    ]-                ]-        assertMissingVariable "Parameter not found: foo" (formatPrompt template Map.empty)-    , testCase "fails for missing FString variables in image detail blocks" $ do-        let template =-              fromMessages-                [ contentMessage-                    User-                    [ ImagePromptBlock FString $-                        ImageContent (ImageUrl "https://example.com/image.png") (Just "{foo}") Nothing-                    ]-                ]-        assertMissingVariable "Parameter not found: foo" (formatPrompt template Map.empty)-    , testCase "fails for missing FString variables in image metadata blocks" $ do-        let template =-              fromMessages-                [ contentMessage-                    User-                    [ ImagePromptBlock FString $-                        ImageContent-                          (ImageUrl "https://example.com/image.png")-                          Nothing-                          (Just $ object ["cache_control" .= object ["type" .= ("{foo}" :: Text)]])-                    ]-                ]-        assertMissingVariable "Parameter not found: foo" (formatPrompt template Map.empty)-    ]--richContentTests :: TestTree-richContentTests =-  testGroup-    "rich content"-    [ testCase "formats multipart text blocks" $ do-        let template =-              fromMessages-                [ templateMessage System "You are an AI assistant named {name}."-                , contentMessage-                    User-                    [TextPromptBlock FString "What's in this image?", TextPromptBlock FString "Oh nvm"]-                ]--        case formatPrompt template (Map.singleton "name" "R2D2") of-          Left err -> assertFailure $ "Expected multipart text prompt, got " <> show err-          Right promptValue ->-            toMessages promptValue-              @?= [ textMessage System "You are an AI assistant named R2D2."-                  , Message-                      User-                      (TextBlock "What's in this image?" :| [TextBlock "Oh nvm"])-                      Nothing-                      Nothing-                      Nothing-                      Map.empty-                  ]-    , testCase "formats templated multipart text blocks" $ do-        let template =-              fromMessages-                [ templateMessage System "You are an AI assistant named {name}."-                , contentMessage-                    User-                    [TextPromptBlock FString "What's in this {object_name}?", TextPromptBlock FString "Oh nvm"]-                ]-            variables = Map.fromList [("name", "R2D2"), ("object_name", "image")]--        case formatPrompt template variables of-          Left err -> assertFailure $ "Expected templated multipart text prompt, got " <> show err-          Right promptValue ->-            toMessages promptValue-              @?= [ textMessage System "You are an AI assistant named R2D2."-                  , Message-                      User-                      (TextBlock "What's in this image?" :| [TextBlock "Oh nvm"])-                      Nothing-                      Nothing-                      Nothing-                      Map.empty-                  ]-    , testCase "formats system template with partial variables" $ do-        let graphCreatorContent = "\n    Your instructions are:\n    {instructions}\n    History:\n    {history}\n    "-            template =-              partial-                (fromMessages [templateMessage System graphCreatorContent])-                (Map.singleton "instructions" (PartialText "{}"))--        case formatPrompt template (Map.singleton "history" "history") of-          Left err -> assertFailure $ "Expected system partial prompt, got " <> show err-          Right promptValue ->-            toMessages promptValue-              @?= [ textMessage-                      System-                      "\n    Your instructions are:\n    {}\n    History:\n    history\n    "-                  ]-    , testCase "formats system multipart text template" $ do-        let graphCreatorContent1 = "\n    This is the prompt for the first test:\n    {variables}\n    "-            graphCreatorContent2 = "\n    This is the prompt for the second test:\n        {variables}\n        "-            template =-              fromMessages-                [ contentMessage-                    System-                    [ TextPromptBlock FString graphCreatorContent1-                    , TextPromptBlock FString graphCreatorContent2-                    ]-                ]--        case formatPrompt template (Map.singleton "variables" "foo") of-          Left err -> assertFailure $ "Expected system multipart text prompt, got " <> show err-          Right promptValue ->-            toMessages promptValue-              @?= [ Message-                      System-                      ( TextBlock "\n    This is the prompt for the first test:\n    foo\n    "-                          :| [TextBlock "\n    This is the prompt for the second test:\n        foo\n        "]-                      )-                      Nothing-                      Nothing-                      Nothing-                      Map.empty-                  ]-    , testCase "formats image_url blocks" $ do-        let base64Image = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAA"-            otherBase64Image = "other_iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAA"-            template =-              fromMessages-                [ templateMessage System "You are an AI assistant named {name}."-                , contentMessage-                    User-                    [ TextPromptBlock FString "What's in this image?"-                    , ImagePromptBlock FString $-                        ImageContent (ImageUrl "data:image/jpeg;base64,{my_image}") Nothing Nothing-                    , ImagePromptBlock FString $ ImageContent (ImageUrl "{my_other_image}") Nothing Nothing-                    , ImagePromptBlock FString $ ImageContent (ImageUrl "{my_other_image}") (Just "medium") Nothing-                    , ImagePromptBlock FString $-                        ImageContent (ImageUrl "https://www.langchain.com/image.png") Nothing Nothing-                    ]-                ]-            variables = Map.fromList [("name", "R2D2"), ("my_image", base64Image), ("my_other_image", otherBase64Image)]--        case formatPrompt template variables of-          Left err -> assertFailure $ "Expected image_url prompt, got " <> show err-          Right promptValue ->-            toMessages promptValue-              @?= [ textMessage System "You are an AI assistant named R2D2."-                  , Message-                      User-                      ( TextBlock "What's in this image?"-                          :| [ ImageBlock $ ImageContent (ImageUrl ("data:image/jpeg;base64," <> base64Image)) Nothing Nothing-                             , ImageBlock $ ImageContent (ImageUrl otherBase64Image) Nothing Nothing-                             , ImageBlock $ ImageContent (ImageUrl otherBase64Image) (Just "medium") Nothing-                             , ImageBlock $ ImageContent (ImageUrl "https://www.langchain.com/image.png") Nothing Nothing-                             ]-                      )-                      Nothing-                      Nothing-                      Nothing-                      Map.empty-                  ]-    , testCase "formats image_url blocks with detail" $ do-        let templateWith templateFormat urlTemplate =-              fromMessages-                [ contentMessage-                    User-                    [ ImagePromptBlock templateFormat $-                        ImageContent urlTemplate (Just "low") Nothing-                    ]-                ]-            expected =-              [ Message-                  User-                  ( ImageBlock-                      (ImageContent (ImageUrl "data:image/png;base64, base64data") (Just "low") Nothing)-                      :| []-                  )-                  Nothing-                  Nothing-                  Nothing-                  Map.empty-              ]-            assertFormats template variables =-              case formatPrompt template variables of-                Left err -> assertFailure $ "Expected image_url detail prompt, got " <> show err-                Right promptValue -> toMessages promptValue @?= expected--        assertFormats-          (templateWith FString (ImageUrl "data:{image_type};base64, {image_data}"))-          (Map.fromList [("image_type", "image/png"), ("image_data", "base64data")])-    , testCase "rejects nested f-string replacement fields in image_url blocks" $ do-        let template =-              fromMessages-                [ contentMessage-                    User-                    [ ImagePromptBlock FString $-                        ImageContent (ImageUrl "{img:{img.__class__.__name__}}") Nothing Nothing-                    ]-                ]-        case formatPrompt template (Map.singleton "img" "image-url") of-          Left err ->-            "Nested replacement fields are not allowed" `T.isInfixOf` T.pack (show err)-              @? "Expected nested replacement field error"-          Right _ -> assertFailure "Expected nested replacement field error"-    , testCase "formats image data blocks with metadata" $ do-        let metadata = object ["cache_control" .= object ["type" .= ("{cache_type}" :: Text)]]-            template =-              fromMessages-                [ contentMessage-                    User-                    [ ImagePromptBlock FString $-                        ImageContent (ImageBase64 Nothing "{source_data}") Nothing (Just metadata)-                    ]-                ]-            variables = Map.fromList [("cache_type", "ephemeral"), ("source_data", "base64data")]--        case formatPrompt template variables of-          Left err -> assertFailure $ "Expected image data prompt, got " <> show err-          Right promptValue ->-            toMessages promptValue-              @?= [ Message-                      User-                      ( ImageBlock-                          ( ImageContent-                              (ImageBase64 Nothing "base64data")-                              Nothing-                              (Just $ object ["cache_control" .= object ["type" .= ("ephemeral" :: Text)]])-                          )-                          :| []-                      )-                      Nothing-                      Nothing-                      Nothing-                      Map.empty-                  ]-    , testCase "round-trips rendered image data blocks through json" $ do-        let block = ImageBlock $ ImageContent (ImageUrl "https://example.com/image.png") Nothing Nothing-        decode (encode block) @?= Just block-    ]--partialTests :: TestTree-partialTests =-  testGroup-    "partial"-    [ testCase "formats chat messages with stored variables" $ do-        let template1 =-              fromMessages-                [ templateMessage System "You are an AI assistant named {name}."-                , templateMessage User "Hi I'm {user}"-                , templateMessage Assistant "Hi there, {user}, I'm {name}."-                , templateMessage User "{input}"-                ]-            template2 =-              partial-                template1-                (Map.fromList [("user", PartialText "Lucy"), ("name", PartialText "R2D2")])-            variables = Map.singleton "input" "hello"-            expected =-              [ textMessage System "You are an AI assistant named R2D2."-              , userMessage "Hi I'm Lucy"-              , textMessage Assistant "Hi there, Lucy, I'm R2D2."-              , userMessage "hello"-              ]-            expectedString =-              T.intercalate-                "\n"-                [ "System: You are an AI assistant named R2D2."-                , "Human: Hi I'm Lucy"-                , "AI: Hi there, Lucy, I'm R2D2."-                , "Human: hello"-                ]--        case formatPrompt template1 variables of-          Left _ -> pure ()-          Right promptValue ->-            assertFailure $ "Expected missing variable error, got " <> show promptValue--        case formatPrompt template2 variables of-          Left err -> assertFailure $ "Expected formatted prompt, got " <> show err-          Right promptValue -> toMessages promptValue @?= expected-        format template2 variables @?= Right expectedString-    , testCase "formats role template messages with partial variables" $ do-        let template =-              fromMessages-                [ templateMessage System "You are {name}, a {role} assistant."-                , templateMessage User "{question}"-                ]-            partialTemplate = partial template (Map.fromList [("name", PartialText "Alice"), ("role", PartialText "helpful")])--        inputVariables partialTemplate @?= ["question"]-        case formatPrompt partialTemplate (Map.singleton "question" "What is Python?") of-          Left err -> assertFailure $ "Expected formatted prompt, got " <> show err-          Right promptValue ->-            toMessages promptValue-              @?= [ textMessage System "You are Alice, a helpful assistant."-                  , userMessage "What is Python?"-                  ]-    , testCase "infers required variables after partial variables" $ do-        let template =-              fromMessages-                [ templateMessage User "Do something with {question} using {context} giving it like {formatins}"-                ]-            partialTemplate = partial template (Map.singleton "formatins" (PartialText "some structure"))--        inputVariables partialTemplate @?= ["question", "context"]-    , testCase "composes partially initialized messages" $ do-        let prompt =-              partial-                (fromMessages [templateMessage System "Prompt {x} {y}"])-                (Map.singleton "x" (PartialText "1"))-            appendix = fromMessages [templateMessage System "Appendix {z}"]-            composed = extend prompt (messages appendix)--        case formatPrompt composed (Map.fromList [("y", "2"), ("z", "3")]) of-          Left err -> assertFailure $ "Expected formatted prompt, got " <> show err-          Right promptValue ->-            toMessages promptValue-              @?= [ textMessage System "Prompt 1 2"-                  , textMessage System "Appendix 3"-                  ]-    , testCase "formats messages placeholder with partial messages" $ do-        let prompt = fromMessages [messagesPlaceholder "history"]-            partialPrompt = partial prompt (Map.singleton "history" (PartialMessages [textMessage System "foo"]))--        inputVariables partialPrompt @?= []-        case formatPrompt partialPrompt Map.empty of-          Left err -> assertFailure $ "Expected formatted placeholder, got " <> show err-          Right promptValue -> toMessages promptValue @?= [textMessage System "foo"]--        case invoke-          partialPrompt-          (ChatPromptInputs Map.empty (Map.singleton "history" [textMessage System "bar"])) of-          Left err -> assertFailure $ "Expected runtime placeholder override, got " <> show err-          Right promptValue -> toMessages promptValue @?= [textMessage System "bar"]--        let optionalPrompt =-              fromMessages-                [ messagesPlaceholderWithOptions $-                    MessagesPlaceholderOptions "history" True Nothing-                ]-            partialOptionalPrompt = partial optionalPrompt (Map.singleton "history" (PartialMessages [textMessage System "foo"]))--        case formatPrompt optionalPrompt Map.empty of-          Left err -> assertFailure $ "Expected empty optional placeholder, got " <> show err-          Right promptValue -> toMessages promptValue @?= []-        case formatPrompt partialOptionalPrompt Map.empty of-          Left err -> assertFailure $ "Expected formatted optional placeholder, got " <> show err-          Right promptValue -> toMessages promptValue @?= [textMessage System "foo"]-    ]--appendExtendTests :: TestTree-appendExtendTests =-  testGroup-    "append / extend"-    [ testCase "appends template messages" $ do-        let template =-              fromMessages-                [templateMessage System "You are helpful."]-            template' = append template (templateMessage User "{question}")--        case formatPrompt template' (Map.singleton "question" "What is AI?") of-          Left err -> assertFailure $ "Expected formatted prompt, got " <> show err-          Right promptValue ->-            toMessages promptValue-              @?= [ textMessage System "You are helpful."-                  , userMessage "What is AI?"-                  ]-    , testCase "appends and extends messages" $ do-        let message1 = textMessage System "foo"-            message2 = userMessage "bar"-            message3 = userMessage "baz"-            baseTemplate = fromMessages [message message1]-            template' = append (append baseTemplate (message message2)) (message message3)-            template'' = extend template' [message message2, message message3]-            template''' = append template'' (templateMessage System "hello!")--        length (messages template') @?= 3-        length (messages template'') @?= 5-        messages template''-          @?= [ message message1-              , message message2-              , message message3-              , message message2-              , message message3-              ]-        case formatPrompt template''' Map.empty of-          Left err -> assertFailure $ "Expected formatted prompt, got " <> show err-          Right promptValue ->-            last (toMessages promptValue) @?= textMessage System "hello!"-    ]--invokeTests :: TestTree-invokeTests =-  testGroup-    "invoke"-    [ testCase "formats chat prompt template messages" $ do-        let invokeTemplate =-              fromMessages-                [ templateMessage System "You are {name}."-                , templateMessage User "{question}"-                ]-            variables = ChatPromptVariables $ Map.fromList [("name", "Alice"), ("question", "Hello?")]--        case invoke invokeTemplate variables of-          Left err -> assertFailure $ "Expected formatted prompt, got " <> show err-          Right promptValue ->-            toMessages promptValue-              @?= [ textMessage System "You are Alice."-                  , userMessage "Hello?"-                  ]-    , testCase "accepts message list input for a single messages placeholder" $ do-        let placeholderTemplate =-              fromMessages-                [messagesPlaceholder "history"]-            input = ChatPromptMessageList [userMessage "Hi there"]--        case invoke placeholderTemplate input of-          Left err -> assertFailure $ "Expected placeholder prompt value, got " <> show err-          Right promptValue -> toMessages promptValue @?= [userMessage "Hi there"]-    , testCase "rejects list input for mixed templates" $ do-        let mixedPrompt =-              fromMessages-                [ templateMessage System "You are a {foo}"-                , messagesPlaceholder "history"-                ]-            listInput = ChatPromptMessageList [userMessage "Hi there"]-        case invoke mixedPrompt listInput of-          Left _ -> pure ()-          Right promptValue ->-            assertFailure $ "Expected list input validation error, got " <> show promptValue-    ]--serializationTests :: TestTree-serializationTests =-  testGroup-    "serialization"-    [ testCase "round-trips messages placeholder and chat prompt" $ do-        let placeholder = MessagesPlaceholder "bar" False Nothing-            prompt =-              fromMessages-                [ templateMessage System "foo"-                , messagesPlaceholder "bar"-                , templateMessage User "baz"-                ]--        decode (encode placeholder) @?= Just placeholder-        decode (encode prompt) @?= Just prompt-    , testCase "round-trips rich chat prompt template" $ do-        let prompt =-              fromMessages-                [ templateMessage System "You are an AI assistant named {name}."-                , contentMessage-                    System-                    [TextPromptBlock FString "You are an AI assistant named {name}."]-                , templateMessage System "you are {foo}"-                , contentMessage-                    User-                    [ TextPromptBlock FString "hello"-                    , TextPromptBlock FString "What's in this image?"-                    , TextPromptBlock FString "What's in this image?"-                    , ImagePromptBlock FString $-                        ImageContent (ImageUrl "data:image/jpeg;base64,{my_image}") Nothing Nothing-                    , ImagePromptBlock FString $-                        ImageContent (ImageUrl "{my_other_image}") Nothing Nothing-                    , ImagePromptBlock FString $ ImageContent (ImageUrl "{my_other_image}") (Just "medium") Nothing-                    , ImagePromptBlock FString $-                        ImageContent (ImageUrl "https://www.langchain.com/image.png") Nothing Nothing-                    , ImagePromptBlock FString $-                        ImageContent (ImageUrl "data:image/jpeg;base64,foobar") Nothing Nothing-                    ]-                , messagesPlaceholderWithOptions $ MessagesPlaceholderOptions "history" True (Just 3)-                , messagesPlaceholder "chat_history"-                , messagesPlaceholder "more_history"-                ]--        decode (encode prompt) @?= Just prompt-    ]--assertMissingVariable :: (Show err, Show a) => Text -> Either err a -> Assertion-assertMissingVariable expectedFragment result =-  case result of-    Left err ->-      if T.isInfixOf expectedFragment (T.pack (show err))-        then pure ()-        else assertFailure $ "Expected missing variable error, got " <> show err-    Right value ->-      assertFailure $ "Expected missing variable error, got " <> show value--promptVariables :: Map.Map Text Text-promptVariables = Map.fromList [("foo", "foo"), ("bar", "bar"), ("context", "context")]--withMessagesVariables :: Map.Map Text Text-withMessagesVariables =-  Map.fromList [("context", "see"), ("foo", "this"), ("bar", "magic")]--chatPromptTemplate :: ChatPromptTemplate-chatPromptTemplate =-  ChatPromptTemplate-    { messages = chatPromptMessages-    , inputVariables = ["foo", "bar", "context"]-    }--chatPromptMessages :: [ChatPromptMessage]-chatPromptMessages =-  [ templateMessage System "Here's some context: {context}"-  , templateMessage User "Hello {foo}, I'm {bar}. Thanks for the {context}"-  , templateMessage Assistant "I'm an AI. I'm {foo}. I'm {bar}."-  , templateMessage User "I'm a generic message. I'm {foo}. I'm {bar}."-  ]--expectedFormattedPrompt :: Text-expectedFormattedPrompt =-  T.intercalate-    "\n"-    [ "System: Here's some context: context"-    , "Human: Hello foo, I'm bar. Thanks for the context"-    , "AI: I'm an AI. I'm foo. I'm bar."-    , "Human: I'm a generic message. I'm foo. I'm bar."-    ]
− test/Test/Langchain/PromptTemplate/Chat/MessagesPlaceholderSpec.hs
@@ -1,84 +0,0 @@-{-# LANGUAGE DuplicateRecordFields #-}-{-# LANGUAGE OverloadedStrings #-}--module Test.Langchain.PromptTemplate.Chat.MessagesPlaceholderSpec (tests) where--import qualified Data.Map.Strict as Map-import qualified Data.Text as T-import Test.Tasty-import Test.Tasty.HUnit--import Langchain.Core.Error (errorMessage)-import Langchain.Core.Model.Types (Message, assistantMessage, systemMessage, userMessage)-import Langchain.PromptTemplate.Chat (BaseMessagePromptTemplate (..))-import Langchain.PromptTemplate.Chat.MessagesPlaceholder-  ( MessagesPlaceholder-  , MessagesPlaceholderOptions (..)-  , messagesPlaceholder-  , messagesPlaceholderOptions-  , messagesPlaceholderWithOptions-  )--tests :: TestTree-tests =-  testGroup-    "MessagesPlaceholder"-    [ testCase "required placeholder requires its variable" $ do-        let result = formatMessages (messagesPlaceholder "history") emptyInputs-        case result of-          Left err ->-            "history" `T.isInfixOf` errorMessage err-              @? "Expected error to mention missing history"-          Right _ -> assertFailure "Expected missing history to fail"-    , testCase "optional placeholder formats to an empty list when omitted" $-        formatMessages optionalPlaceholder emptyInputs-          @?= Right []-    , testCase "optional placeholder accepts messages" $-        formatMessages-          optionalPlaceholder-          ( inputs-              [ systemMessage "You are an AI assistant."-              , userMessage "Hello!"-              ]-          )-          @?= Right-            [ systemMessage "You are an AI assistant."-            , userMessage "Hello!"-            ]-    , testCase "placeholder without a message limit returns the whole history" $-        let history = map assistantMessage ["1", "2", "3"]-         in formatMessages-              (messagesPlaceholder "history")-              (inputs history)-              @?= Right history-    , testCase "placeholder with n_messages returns the last messages" $-        let history = map assistantMessage ["1", "2", "3"]-            prompt =-              messagesPlaceholderWithOptions $-                (messagesPlaceholderOptions "history") {nMessages = Just 2}-         in formatMessages-              prompt-              (inputs history)-              @?= Right [assistantMessage "2", assistantMessage "3"]-    , testCase "placeholder rejects non-positive n_messages" $-        let history = map assistantMessage ["1", "2", "3"]-            prompt =-              messagesPlaceholderWithOptions $-                (messagesPlaceholderOptions "history") {nMessages = Just 0}-         in case formatMessages prompt (inputs history) of-              Left err ->-                "n_messages" `T.isInfixOf` errorMessage err-                  @? "Expected error to mention n_messages"-              Right _ -> assertFailure "Expected non-positive n_messages to fail"-    ]--optionalPlaceholder :: MessagesPlaceholder-optionalPlaceholder =-  messagesPlaceholderWithOptions $-    (messagesPlaceholderOptions "history") {optional = True}--emptyInputs :: Map.Map T.Text [Message]-emptyInputs = Map.empty--inputs :: [Message] -> Map.Map T.Text [Message]-inputs history = Map.fromList [("history", history)]
− test/Test/Langchain/PromptTemplate/FewShotSpec.hs
@@ -1,62 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Test.Langchain.PromptTemplate.FewShotSpec (tests) where--import qualified Data.Map.Strict as Map-import qualified Data.Text as T-import Test.Tasty-import Test.Tasty.HUnit--import Langchain.PromptTemplate.FewShot--tests :: TestTree-tests =-  testGroup-    "FewShotPromptTemplate"-    [ testCase "correctly formats a few-shot prompt" $-        let expected =-              "Examples of {type}:\nInput: Hello\nOutput: Bonjour\n\nInput: Goodbye\nOutput: Au revoir\nNow translate: {query}"-         in renderFewShotPrompt fewShotTemplate @?= Right expected-    , testCase "handles empty examples list" $-        let emptyExamples = fewShotTemplate {fsExamples = []}-         in renderFewShotPrompt emptyExamples @?= Right "Examples of {type}:\n\nNow translate: {query}"-    , testCase "handles empty prefix and suffix" $-        let noPreSuf = fewShotTemplate {fsPrefix = "", fsSuffix = ""}-         in renderFewShotPrompt noPreSuf-              @?= Right "Input: Hello\nOutput: Bonjour\n\nInput: Goodbye\nOutput: Au revoir"-    , testCase "returns an error when example variables are missing" $-        let badExamples =-              fewShotTemplate-                { fsExamples = [Map.fromList [("wrong", "value")]]-                , fsExampleTemplate = "{input} translates to {output}"-                }-         in case renderFewShotPrompt badExamples of-              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-              @?= Right-                "Examples of {type}:\nInput: Hello\nOutput: Bonjour ### Input: Goodbye\nOutput: Au revoir\nNow translate: {query}"-    , testCase "renderFewShotPromptWithVars interpolates full template" $ do-        let inputVars = Map.fromList [("type", "Spanish"), ("query", "Thank you")]-            expected =-              "Examples of Spanish:\nInput: Hello\nOutput: Bonjour\n\nInput: Goodbye\nOutput: Au revoir\nNow translate: Thank you"-        renderFewShotPromptWithVars fewShotTemplate inputVars @?= Right expected-    ]-  where-    fewShotTemplate =-      FewShotPromptTemplate-        { fsPrefix = "Examples of {type}:\n"-        , fsExamples =-            [ Map.fromList [("input", "Hello"), ("output", "Bonjour")]-            , Map.fromList [("input", "Goodbye"), ("output", "Au revoir")]-            ]-        , fsExampleTemplate = "Input: {input}\nOutput: {output}"-        , fsExampleSeparator = "\n\n"-        , fsSuffix = "\nNow translate: {query}"-        }
− test/Test/Langchain/PromptTemplate/PromptSpec.hs
@@ -1,59 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Test.Langchain.PromptTemplate.PromptSpec (tests) where--import qualified Data.Map.Strict as Map-import qualified Data.Text as T-import Test.Tasty-import Test.Tasty.HUnit--import Langchain.PromptTemplate.Prompt--tests :: TestTree-tests =-  testGroup-    "PromptTemplate"-    [ testCase "correctly interpolates all variables" $-        renderPrompt greetingTemplate vars @?= Right "Hello, Alice! Welcome to Wonderland."-    , testCase "handles templates with no variables" $-        let noVarTemplate = fromTemplate "Hello, world!"-         in renderPrompt noVarTemplate Map.empty @?= Right "Hello, world!"-    , testCase "handles templates with repeated variables" $-        let repeatTemplate = fromTemplate "{name} likes {food}. {name} eats {food} every day."-            repeatVars = Map.fromList [("name", "Bob"), ("food", "pizza")]-         in renderPrompt repeatTemplate repeatVars @?= Right "Bob likes pizza. Bob eats pizza every day."-    , testCase "returns an error for missing variables" $-        let missingVars = Map.fromList [("name", "Charlie")]-         in case renderPrompt greetingTemplate missingVars of-              Left err -> "place" `T.isInfixOf` T.pack (show err) @? "Expected error to contain 'place'"-              Right _ -> assertFailure "Expected an error for missing variable"-    , testCase "renders escaped f-string braces" $-        let promptTemplate = fromTemplate "Hello {{name}}, {name}!"-         in renderPrompt promptTemplate (Map.singleton "name" "Alice") @?= Right "Hello {name}, Alice!"-    , testCase "renders f-string format specs" $-        let promptTemplate = fromTemplate "Hello, {name:~u}!"-         in renderPrompt promptTemplate (Map.singleton "name" "Alice") @?= Right "Hello, ALICE!"-    , testCase "infers f-string variables without escaped braces" $-        let promptTemplate = fromTemplate "Hello {{name}}, {name}!"-         in inputVariables promptTemplate @?= ["name"]-    , testCase "rejects f-string positional fields" $-        assertRenderErrorContains "Positional arguments are not supported" $-          renderPrompt (fromTemplate "Hello, {0}!") (Map.singleton "0" "Alice")-    , testCase "rejects f-string attribute access" $-        assertRenderErrorContains "Attribute access is not supported" $-          renderPrompt (fromTemplate "Hello, {user.name}!") (Map.singleton "user.name" "Alice")-    , testCase "rejects nested f-string replacement fields" $-        assertRenderErrorContains "Nested replacement fields are not allowed" $-          renderPrompt-            (fromTemplate "Hello, {name:{width}}!")-            (Map.fromList [("name", "Alice"), ("width", "10")])-    ]-  where-    greetingTemplate = fromTemplate "Hello, {name}! Welcome to {place}."-    vars = Map.fromList [("name", "Alice"), ("place", "Wonderland")]--assertRenderErrorContains :: (Show err) => T.Text -> Either err T.Text -> Assertion-assertRenderErrorContains expected result =-  case result of-    Left err -> expected `T.isInfixOf` T.pack (show err) @? "Expected error to contain expected text"-    Right _ -> assertFailure "Expected render error"
− test/Test/Langchain/Property/CheckpointerSpec.hs
@@ -1,56 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Test.Langchain.Property.CheckpointerSpec (tests) where--import qualified Data.Text as T-import System.FilePath ((</>))-import System.IO.Temp (withSystemTempDirectory)-import Test.QuickCheck-import Test.Tasty-import Test.Tasty.QuickCheck--import Langchain.Graph.Checkpointer--newtype SafeThreadId = SafeThreadId T.Text-  deriving (Show, Eq)--instance Arbitrary SafeThreadId where-  arbitrary = SafeThreadId . T.pack <$> listOf1 (elements ['a' .. 'z'])--newtype SafeState = SafeState T.Text-  deriving (Show, Eq)--instance Arbitrary SafeState where-  arbitrary = SafeState . T.pack <$> listOf1 (elements (['a' .. 'z'] ++ ['0' .. '9'] ++ " "))--tests :: TestTree-tests =-  testGroup-    "Langchain.Property.CheckpointerSpec (QuickCheck)"-    [ testProperty "MemoryCheckpointer Save-Load Identity: load after save returns saved state" $-        \(SafeThreadId tid) (SafeState stateVal) -> ioProperty $ do-          cp <- newMemoryCheckpointer-          _ <- saveCheckpoint cp tid "step-1" stateVal-          res <- loadCheckpoint cp tid "step-1"-          pure (res === Right (Just stateVal))-    , testProperty "MemoryCheckpointer Overwrite: save second state updates checkpoint" $-        \(SafeThreadId tid) (SafeState s1) (SafeState s2) -> ioProperty $ do-          cp <- newMemoryCheckpointer-          _ <- saveCheckpoint cp tid "step-1" s1-          _ <- saveCheckpoint cp tid "step-1" s2-          res <- loadCheckpoint cp tid "step-1"-          pure (res === Right (Just s2))-    , testProperty "MemoryCheckpointer Non-existent thread returns Nothing" $-        \(SafeThreadId tid) -> ioProperty $ do-          cp <- newMemoryCheckpointer-          res <- loadCheckpoint cp (tid <> "-nonexistent") "step-1"-          pure (res === Right (Nothing :: Maybe T.Text))-    , testProperty "SQLiteCheckpointer Save-Load Invariant" $-        \(SafeThreadId tid) (SafeState stateVal) -> ioProperty $ do-          withSystemTempDirectory "sqlite-prop-test" $ \tmpDir -> do-            let dbFile = tmpDir </> "checkpoints.db"-            cp <- newSQLiteCheckpointer dbFile-            _ <- saveCheckpoint cp tid "step-1" stateVal-            res <- loadCheckpoint cp tid "step-1"-            pure (res === Right (Just stateVal))-    ]
− test/Test/Langchain/Property/ErrorSpec.hs
@@ -1,48 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--module Test.Langchain.Property.ErrorSpec (tests) where--import Data.Aeson (decode, encode)-import qualified Data.Map.Strict as Map-import qualified Data.Text as T-import Data.Time.Clock.POSIX (posixSecondsToUTCTime)-import Test.QuickCheck-import Test.Tasty-import Test.Tasty.QuickCheck--import Langchain.Core.Error--instance Arbitrary ErrorContext where-  arbitrary = do-    comp <- T.pack <$> listOf1 (elements ['a' .. 'z'])-    op <- T.pack <$> listOf1 (elements ['a' .. 'z'])-    pure $ ErrorContext comp op (posixSecondsToUTCTime 1700000000) Map.empty--instance Arbitrary LangchainError where-  arbitrary = do-    msg <- T.pack <$> listOf1 (elements (['a' .. 'z'] ++ ['0' .. '9'] ++ " ,.-"))-    mbCtx <- oneof [pure Nothing, Just <$> arbitrary]-    elements-      [ LLMError msg mbCtx-      , AgentError msg mbCtx-      , MemoryError msg mbCtx-      , ToolError msg mbCtx-      , VectorStoreError msg mbCtx-      , DocumentLoaderError msg mbCtx-      , EmbeddingError msg mbCtx-      , RunnableError msg mbCtx-      , ParsingError msg mbCtx-      , NetworkError msg mbCtx-      , ConfigurationError msg mbCtx-      , ValidationError msg mbCtx-      , InternalError msg mbCtx-      ]--tests :: TestTree-tests =-  testGroup-    "Langchain.Property.ErrorSpec (QuickCheck)"-    [ testProperty "LangchainError JSON round-trip: decode (encode err) == Just err" $-        \err -> decode (encode (err :: LangchainError)) === Just err-    ]
− test/Test/Langchain/Property/MessageSpec.hs
@@ -1,72 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--module Test.Langchain.Property.MessageSpec (tests) where--import Data.Aeson (decode, encode, toJSON)-import qualified Data.ByteString as BS-import qualified Data.List.NonEmpty as NonEmpty-import qualified Data.Map.Strict as Map-import Data.Text (Text)-import qualified Data.Text as T-import Test.QuickCheck-import Test.Tasty-import Test.Tasty.QuickCheck--import Langchain.Core.Model---- Arbitrary instances for Core Message types--instance Arbitrary Role where-  arbitrary = elements [System, User, Assistant, Tool, Developer, Function]--instance Arbitrary ContentBlock where-  arbitrary =-    oneof-      [ TextBlock . T.pack-          <$> listOf1 (elements (['a' .. 'z'] ++ ['A' .. 'Z'] ++ ['0' .. '9'] ++ " \t\n.,!?-"))-      , ImageBlock-          <$> ( ImageContent-                  <$> ( ImageBase64 . Just-                          <$> elements ["image/png", "image/jpeg", "image/webp"]-                          <*> (T.pack <$> listOf1 (elements ['a' .. 'z']))-                      )-                  <*> pure Nothing-                  <*> pure Nothing-              )-      , AudioBlock <$> elements ["audio/mp3", "audio/wav"] <*> (T.pack <$> listOf1 (elements ['a' .. 'z']))-      , DataBlock . BS.pack <$> listOf1 arbitrary-      ]--instance Arbitrary ToolCall where-  arbitrary = do-    tcId <- T.pack <$> listOf1 (elements ['a' .. 'z'])-    name <- T.pack <$> listOf1 (elements ['a' .. 'z'])-    pure $ ToolCall tcId "function" name (toJSON ("{}" :: Text))--instance Arbitrary Message where-  arbitrary = do-    r <- arbitrary-    blocks <- listOf1 arbitrary-    let neBlocks = NonEmpty.fromList blocks-    mbName <- oneof [pure Nothing, Just . T.pack <$> listOf1 (elements ['a' .. 'z'])]-    mbToolId <- oneof [pure Nothing, Just . T.pack <$> listOf1 (elements ['a' .. 'z'])]-    pure $-      Message r neBlocks mbName Nothing mbToolId (Map.singleton "example" $ toJSON ("value" :: Text))--tests :: TestTree-tests =-  testGroup-    "Langchain.Property.MessageSpec (QuickCheck)"-    [ testProperty "Role JSON round-trip: decode (encode r) == Just r" $-        \r -> decode (encode (r :: Role)) === Just r-    , testProperty "ContentBlock JSON round-trip: decode (encode cb) == Just cb" $-        \cb -> decode (encode (cb :: ContentBlock)) === Just cb-    , testProperty "Message JSON round-trip: decode (encode msg) == Just msg" $-        \msg -> decode (encode (msg :: Message)) === Just msg-    , testProperty "extractMessageText preserves text block contents" $-        \t ->-          let txt = T.pack t-              msg = userMessage txt-           in extractMessageText msg === txt-    ]
− test/Test/Langchain/Property/PromptTemplateSpec.hs
@@ -1,74 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Test.Langchain.Property.PromptTemplateSpec (tests) where--import qualified Data.Map.Strict as Map-import Data.Text (Text)-import qualified Data.Text as T-import Test.QuickCheck-import Test.Tasty-import Test.Tasty.QuickCheck--import Langchain.PromptTemplate.FewShot-import Langchain.PromptTemplate.Prompt---- QuickCheck helper to generate safe variable names [a-z]+-newtype SafeVar = SafeVar Text-  deriving (Show, Eq)--instance Arbitrary SafeVar where-  arbitrary = SafeVar . T.pack <$> listOf1 (elements ['a' .. 'z'])---- Safe text without braces-newtype PlainText = PlainText Text-  deriving (Show, Eq)--instance Arbitrary PlainText where-  arbitrary = PlainText . T.pack <$> listOf1 (elements (['a' .. 'z'] ++ ['0' .. '9'] ++ " ,.!-"))--tests :: TestTree-tests =-  testGroup-    "Langchain.Property.PromptTemplateSpec (QuickCheck)"-    [ testProperty "Static templates without braces render unchanged" $-        \(PlainText txt) ->-          renderPrompt (fromTemplate txt) Map.empty === Right txt-    , testProperty "Single variable interpolation replaces {var} with value" $-        \(SafeVar var) (PlainText val) ->-          let tmpl = "Hello {" <> var <> "}!"-              vars = Map.singleton var val-              expected = "Hello " <> val <> "!"-           in renderPrompt (fromTemplate tmpl) vars === Right expected-    , testProperty "Missing variable causes render error" $-        \(SafeVar var) ->-          let tmpl = "Prefix {" <> var <> "} Suffix"-              vars = Map.empty-           in case renderPrompt (fromTemplate tmpl) vars of-                Left _ -> property True-                Right _ -> property False-    , testProperty "Two variable interpolation succeeds when all vars present" $-        \(SafeVar v1) (SafeVar v2) (PlainText val1) (PlainText val2) ->-          v1 /= v2 ==>-            let tmpl = "{" <> v1 <> "} and {" <> v2 <> "}"-                vars = Map.fromList [(v1, val1), (v2, val2)]-                expected = val1 <> " and " <> val2-             in renderPrompt (fromTemplate tmpl) vars === Right expected-    , testProperty "FewShotPromptTemplate renders all examples" $-        \(PlainText prefix) (PlainText suffix) (PlainText ex1) (PlainText ex2) ->-          let examples =-                [ Map.singleton "content" ex1-                , Map.singleton "content" ex2-                ]-              fewShot =-                FewShotPromptTemplate-                  { fsPrefix = prefix-                  , fsExamples = examples-                  , fsExampleTemplate = "Ex: {content}"-                  , fsExampleSeparator = "\n"-                  , fsSuffix = suffix-                  }-           in case renderFewShotPrompt fewShot of-                Right rendered ->-                  property (prefix `T.isInfixOf` rendered && suffix `T.isInfixOf` rendered)-                Left _ -> property False-    ]
− test/Test/Langchain/Property/RunnableSpec.hs
@@ -1,80 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Test.Langchain.Property.RunnableSpec (tests) where--import Control.Monad.Except (ExceptT, runExceptT)-import Data.Text (Text)-import Test.QuickCheck-import Test.Tasty-import Test.Tasty.QuickCheck--import Langchain.Core.Error-import Langchain.Core.Runnable--type PureMonad = ExceptT LangchainError IO--tests :: TestTree-tests =-  testGroup-    "Langchain.Property.RunnableSpec (QuickCheck)"-    [ testProperty "Left Identity: Id |>> t(x) == t(x)" $-        \n -> ioProperty $ do-          let step :: RunnableTree PureMonad Int Int-              step = runLambda (\i -> pure $ Right (i * 2))-              pipeline = Id |>> step-          res1 <- runExceptT $ interpret pipeline n-          res2 <- runExceptT $ interpret step n-          pure (res1 === res2)-    , testProperty "Right Identity: t(x) |>> Id == t(x)" $-        \n -> ioProperty $ do-          let step :: RunnableTree PureMonad Int Int-              step = runLambda (\i -> pure $ Right (i + 10))-              pipeline = step |>> Id-          res1 <- runExceptT $ interpret pipeline n-          res2 <- runExceptT $ interpret step n-          pure (res1 === res2)-    , testProperty "Associativity: ((f |>> g) |>> h) == (f |>> (g |>> h))" $-        \n -> ioProperty $ do-          let f :: RunnableTree PureMonad Int Int-              f = runLambda (\i -> pure $ Right (i + 1))-              g :: RunnableTree PureMonad Int Int-              g = runLambda (\i -> pure $ Right (i * 3))-              h :: RunnableTree PureMonad Int Int-              h = runLambda (\i -> pure $ Right (i - 5))--              p1 = (f |>> g) |>> h-              p2 = f |>> (g |>> h)-          res1 <- runExceptT $ interpret p1 n-          res2 <- runExceptT $ interpret p2 n-          pure (res1 === res2)-    , testProperty "Branch selects correct branch based on predicate" $-        \n -> ioProperty $ do-          let isPositive :: Int -> PureMonad Bool-              isPositive i = pure (i > 0)-              thenBranch :: RunnableTree PureMonad Int Text-              thenBranch = runLambda (\_ -> pure $ Right "POSITIVE")-              elseBranch :: RunnableTree PureMonad Int Text-              elseBranch = runLambda (\_ -> pure $ Right "NON-POSITIVE")-              branchTree = Branch isPositive thenBranch elseBranch-          res <- runExceptT $ interpret branchTree n-          let expected = if n > 0 then Right "POSITIVE" else Right "NON-POSITIVE"-          pure (res === expected)-    , testProperty "Fallback executes fallback branch on primary error" $-        \n -> ioProperty $ do-          let failingTree :: RunnableTree PureMonad Int Int-              failingTree = runLambda (\_ -> pure $ Left $ internalError "Failed" Nothing Nothing)-              fallbackTree :: RunnableTree PureMonad Int Int-              fallbackTree = runLambda (\i -> pure $ Right (i + 100))-              pipeline = Fallback failingTree fallbackTree-          res <- runExceptT $ interpret pipeline n-          pure (res === Right (n + 100))-    , testProperty "Parallel composition (&>&) produces pair output" $-        \n -> ioProperty $ do-          let doubleStep :: RunnableTree PureMonad Int Int-              doubleStep = runLambda (\i -> pure $ Right (i * 2))-              tripleStep :: RunnableTree PureMonad Int Int-              tripleStep = runLambda (\i -> pure $ Right (i * 3))-              parallelTree = doubleStep &>& tripleStep-          res <- runExceptT $ interpret parallelTree n-          pure (res === Right (n * 2, n * 3))-    ]
− test/Test/Langchain/Property/TextSplitterSpec.hs
@@ -1,50 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Test.Langchain.Property.TextSplitterSpec (tests) where--import Data.Int (Int64)-import qualified Data.Text.Lazy as TL-import Test.QuickCheck-import Test.Tasty-import Test.Tasty.QuickCheck--import Langchain.TextSplitter.Character--newtype SplitterText = SplitterText TL.Text-  deriving (Show, Eq)--instance Arbitrary SplitterText where-  arbitrary = do-    paragraphs <- listOf1 (listOf1 (elements (['a' .. 'z'] ++ ['0' .. '9'] ++ " ")))-    pure $ SplitterText $ TL.pack $ unlines paragraphs--newtype PositiveChunkSize = PositiveChunkSize Int64-  deriving (Show, Eq)--instance Arbitrary PositiveChunkSize where-  arbitrary = PositiveChunkSize . fromIntegral <$> chooseInt (10, 200)--tests :: TestTree-tests =-  testGroup-    "Langchain.Property.TextSplitterSpec (QuickCheck)"-    [ testProperty "Empty text splits into empty list" $-        \(PositiveChunkSize cSize) ->-          let ops = defaultCharacterSplitterOps {chunkSize = cSize}-           in splitText ops "" === []-    , testProperty "No chunk exceeds chunkSize" $-        \(PositiveChunkSize cSize) (SplitterText txt) ->-          let ops = defaultCharacterSplitterOps {chunkSize = cSize}-              chunks = splitText ops txt-           in property (all (\c -> TL.length c <= cSize) chunks)-    , testProperty "All generated chunks are non-empty" $-        \(PositiveChunkSize cSize) (SplitterText txt) ->-          let ops = defaultCharacterSplitterOps {chunkSize = cSize}-              chunks = splitText ops txt-           in property (not (any TL.null chunks))-    , testProperty "Single character chunks never exceed chunkSize 1" $-        \() ->-          let ops = defaultCharacterSplitterOps {chunkSize = 1, separator = ""}-              chunks = splitText ops "abcdef"-           in property (all (\c -> TL.length c <= 1) chunks)-    ]
− test/Test/Langchain/Provider/FixturesSpec.hs
@@ -1,60 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Test.Langchain.Provider.FixturesSpec (tests) where--import Data.Aeson (Value, decode)-import qualified Data.ByteString.Lazy as LBS-import Test.Tasty-import Test.Tasty.HUnit--import Langchain.Core.Model-import Langchain.Core.Stream (TokenUsage (..))-import Langchain.Provider.Gemini (parseGeminiResponse)-import Langchain.Provider.OpenAI (parseOpenAIResponse)--loadFixture :: FilePath -> IO (Either String Value)-loadFixture fp = do-  content <- LBS.readFile fp-  case decode content of-    Nothing -> pure $ Left ("Failed to decode JSON from fixture: " ++ fp)-    Just val -> pure $ Right val--tests :: TestTree-tests =-  testGroup-    "Langchain.Provider.FixturesSpec"-    [ testCase "Parse OpenAI Chat Completion fixture" $ do-        eVal <- loadFixture "test/fixtures/openai_chat_response.json"-        case eVal of-          Left err -> assertFailure err-          Right val -> case parseOpenAIResponse val of-            Left parseErr -> assertFailure ("OpenAI parser error: " ++ parseErr)-            Right (msg, mbUsage) -> do-              messageRole msg @?= Assistant-              extractMessageText msg @?= "Hello! I am OpenAI GPT-4o."-              case mbUsage of-                Nothing -> assertFailure "Expected TokenUsage in response"-                Just usage -> promptTokens usage @?= 9-    , testCase "Parse OpenAI Tool Call fixture" $ do-        eVal <- loadFixture "test/fixtures/openai_tool_call.json"-        case eVal of-          Left err -> assertFailure err-          Right val -> case parseOpenAIResponse val of-            Left parseErr -> assertFailure ("OpenAI parser error: " ++ parseErr)-            Right (msg, _) -> do-              messageRole msg @?= Assistant-              case messageToolCalls msg of-                Just [tc] -> do-                  toolCallName tc @?= "calculator"-                  toolCallId tc @?= "call_abc123"-                _ -> assertFailure "Expected tool call in OpenAI message"-    , testCase "Parse Gemini Chat fixture" $ do-        eVal <- loadFixture "test/fixtures/gemini_response.json"-        case eVal of-          Left err -> assertFailure err-          Right val -> case parseGeminiResponse val of-            Left parseErr -> assertFailure ("Gemini parser error: " ++ parseErr)-            Right msg -> do-              messageRole msg @?= Assistant-              extractMessageText msg @?= "Hello! I am Google Gemini 2.5."-    ]
− test/Test/Langchain/Provider/Gemini.hs
@@ -1,676 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE TypeApplications #-}--module Test.Langchain.Provider.Gemini (tests) where--import Control.Concurrent (newEmptyMVar, putMVar, takeMVar)-import Control.Concurrent.Async (async, poll, wait)-import Control.Concurrent.STM (atomically, modifyTVar', newTVarIO, readTVarIO)-import Control.Monad (forM, void)-import Control.Monad.Except (runExceptT)-import Control.Monad.IO.Class (liftIO)-import Control.Monad.Trans.Resource (runResourceT)-import qualified Data.Aeson as Aeson-import Data.Aeson.QQ (aesonQQ)-import qualified Data.ByteString.Lazy as LBS-import Data.Conduit (await, runConduit, (.|))-import qualified Data.Conduit.Combinators as C-import qualified Data.Map.Strict as Map-import Data.Maybe (fromMaybe, isJust, isNothing)-import qualified Data.Text as T-import Network.HTTP.Types (hContentType, status200, status500)-import Network.Wai-  ( Application-  , Request-  , rawPathInfo-  , rawQueryString-  , requestMethod-  , responseLBS-  , strictRequestBody-  )-import System.Environment (lookupEnv)-import System.Timeout (timeout)-import Test.Tasty-import Test.Tasty.HUnit--import Langchain.Core.Error (LangchainError)-import Langchain.Core.Model-import Langchain.Core.Stream (StreamEvent (..), TokenUsage (..), collectEvents)-import Langchain.Core.Tool (Tool, createTool)-import qualified Langchain.Core.Tool as CoreTool-import Langchain.Provider.Gemini-import Langchain.Tool.Binding (ToolBinder (bindToolsConfig))-import Test.Langchain.Provider.TestSseServer-  ( cancellationAwareSseServer-  , capturingRawSseRequestServer-  , collectModelStream-  , gatedSseServer-  , rawSseServer-  , sseFrame-  , withTestApplication-  )--withGeminiProvider :: T.Text -> (Gemini -> IO a) -> IO a-withGeminiProvider url action = action $ newGemini "test-key" "test-model" (Just url)--withRawTestProvider :: [LBS.ByteString] -> (Gemini -> IO a) -> IO a-withRawTestProvider frames action =-  withTestApplication (rawSseServer frames) $ \url -> withGeminiProvider url action--withGatedProvider :: IO () -> (Gemini -> IO a) -> IO a-withGatedProvider waitForContinuation action =-  withTestApplication-    (gatedSseServer (sseFrame $ chunk "Hel") waitForContinuation [sseFrame $ chunk "lo"])-    $ \url -> withGeminiProvider url action--withCancellationAwareProvider :: IO () -> (Gemini -> IO a) -> IO a-withCancellationAwareProvider signalClientClosed action =-  withTestApplication (cancellationAwareSseServer (sseFrame $ chunk "Hello") signalClientClosed) $ \url ->-    withGeminiProvider url action--errorServer :: Application-errorServer _request respond = respond $ responseLBS status500 [] ""--capturingGenerateContentServer :: (Request -> LBS.ByteString -> IO ()) -> Application-capturingGenerateContentServer capture request respond = do-  body <- strictRequestBody request-  capture request body-  respond $-    responseLBS-      status200-      [(hContentType, "application/json")]-      "{\"candidates\":[{\"content\":{\"parts\":[{\"text\":\"ok\"}]}}]}"--collectRawStream :: [LBS.ByteString] -> IO (Either LangchainError [StreamEvent])-collectRawStream frames =-  withRawTestProvider frames $ \provider ->-    collectModelStream provider [userMessage "Hello"] Nothing--chunk :: LBS.ByteString -> LBS.ByteString-chunk content =-  "{\"candidates\":[{\"index\":0,\"content\":{\"parts\":[{\"text\":\""-    <> content-    <> "\"}]}}]}"--tests :: TestTree-tests =-  testGroup-    "Langchain.Provider.Gemini"-    [ testCase "newGemini initializes provider with model" $ do-        let p = newGemini "ai-key" "gemini-1.5-pro" Nothing-        model p @?= "gemini-1.5-pro"-    , testGroup-        "invoke"-        [ testCase "invoke sends Gemini function declarations" $ do-            let weatherTool :: Tool IO-                weatherTool = createTool "get_weather" "Gets the weather" weatherSchema (const . pure $ Right "sunny")-            capturedRequest <- newEmptyMVar-            withTestApplication-              (capturingGenerateContentServer (curry . putMVar $ capturedRequest))-              $ \url ->-                withGeminiProvider url $ \provider -> do-                  let tools = bindToolsConfig @Gemini [weatherTool] Nothing-                  result <- runExceptT $ invoke provider [userMessage "Hello"] tools-                  case result of-                    Left err -> assertFailure $ "Expected invoke success, got: " ++ show err-                    Right response -> extractMessageText response @?= "ok"-            (request, body) <- takeMVar capturedRequest-            requestMethod request @?= "POST"-            rawPathInfo request @?= "/v1beta/models/test-model:generateContent"-            rawQueryString request @?= "?key=test-key"-            Aeson.decode body-              @?= Just-                [aesonQQ|-              {-                "contents": [{"role": "user", "parts": [{"text": "Hello"}]}],-                "tools": [{"functionDeclarations": [{-                  "name": "get_weather",-                  "description": "Gets the weather",-                  "parameters": {-                    "type": "OBJECT",-                    "properties": {"city": {"type": "STRING"}},-                    "required": ["city"]-                  }-                }]}]-              }-            |]-        , testCase "invoke rejects a non-object Gemini config" $ do-            let gemini = newGemini "test-key" "test-model" Nothing-                modelConfig = Just $ Aeson.String "invalid"-            result <- runExceptT $ invoke gemini [userMessage "Hello"] modelConfig-            case result of-              Left err ->-                assertBool "Expected config error" $-                  "Gemini config must be a JSON object" `T.isInfixOf` T.pack (show err)-              Right _ -> assertFailure "Expected invalid config to fail"-        , testCase "parseGeminiResponse preserves function calls, text, and thought signatures" $ do-            let response =-                  [aesonQQ|-                {-                  "candidates": [-                    {-                      "content": {-                        "parts": [-                          { "text": "Checking weather" },-                          {-                            "thoughtSignature": "signature_1",-                            "functionCall": {-                              "id": "call_1",-                              "name": "get_weather",-                              "args": { "city": "Paris" }-                            }-                          }-                        ]-                      }-                    }-                  ]-                }-              |]-                expectedCall = ToolCall "call_1" "function" "get_weather" [aesonQQ|{"city": "Paris"}|]-            case parseGeminiResponse response of-              Left err -> assertFailure $ "Expected function call response, got: " ++ err-              Right message -> do-                extractMessageText message @?= "Checking weather"-                messageToolCalls message @?= Just [expectedCall]-                Map.lookup "langchain.gemini.thoughtSignatures" (messageMetadata message)-                  @?= Just (Aeson.toJSON [Just ("signature_1" :: T.Text)])-        , testCase "parseGeminiResponse leaves ordinary message metadata empty" $ do-            let response = [aesonQQ|{"candidates": [{"content": {"parts": [{"text": "ok"}]}}]}|]-            case parseGeminiResponse response of-              Left err -> assertFailure $ "Expected text response, got: " ++ err-              Right message -> messageMetadata message @?= Map.empty-        , testCase "invoke replays Gemini thought signatures on function calls" $ do-            let toolCall = ToolCall "call_1" "function" "get_weather" [aesonQQ|{"city": "Paris"}|]-                assistant =-                  (assistantMessage "")-                    { messageToolCalls = Just [toolCall]-                    , messageMetadata =-                        Map.singleton-                          "langchain.gemini.thoughtSignatures"-                          (Aeson.toJSON [Just ("signature_1" :: T.Text)])-                    }-            capturedRequest <- newEmptyMVar-            withTestApplication-              (capturingGenerateContentServer (curry . putMVar $ capturedRequest))-              $ \url ->-                withGeminiProvider url $ \provider -> do-                  result <- runExceptT $ invoke provider [assistant] Nothing-                  case result of-                    Left err -> assertFailure $ "Expected invoke success, got: " ++ show err-                    Right _ -> pure ()-            (_, body) <- takeMVar capturedRequest-            Aeson.decode body-              @?= Just-                [aesonQQ|-              {-                "contents": [{"role": "model", "parts": [{-                  "thoughtSignature": "signature_1",-                  "functionCall": {-                    "id": "call_1", "name": "get_weather", "args": {"city": "Paris"}-                  }-                }]}]-                }-            |]-        , testCase "invoke replays thought signatures for their matching Gemini function calls" $ do-            let response =-                  [aesonQQ|-                {-                  "candidates": [{-                    "content": {-                      "parts": [-                        {-                            "functionCall":-                                {-                                    "id": "call_weather",-                                    "name": "get_weather",-                                    "args": {-                                        "city": "Paris"-                                    }-                                }-                        },-                        {-                            "thoughtSignature": "signature_time",-                            "functionCall": {-                                "id": "call_time",-                                "name": "get_time",-                                "args": {-                                    "zone": "UTC"-                                }-                            }-                        }-                      ]-                    }-                  }]-                }-              |]-            assistant <- case parseGeminiResponse response of-              Left err -> assertFailure ("Expected function call response, got: " ++ err) >> fail "unreachable"-              Right message -> return message-            capturedRequest <- newEmptyMVar-            withTestApplication-              (capturingGenerateContentServer (curry . putMVar $ capturedRequest))-              $ \url ->-                withGeminiProvider url $ \provider -> do-                  result <- runExceptT $ invoke provider [assistant] Nothing-                  case result of-                    Left err -> assertFailure $ "Expected invoke success, got: " ++ show err-                    Right _ -> return ()-            (_, body) <- takeMVar capturedRequest-            Aeson.decode body-              @?= Just-                [aesonQQ|-              {-                "contents": [-                    {-                        "role": "model",-                        "parts": [-                            {-                                "functionCall": {-                                    "id": "call_weather",-                                    "name": "get_weather",-                                    "args": {-                                        "city": "Paris"-                                    }-                                }-                            },-                            {-                                "thoughtSignature": "signature_time",-                                "functionCall": {-                                    "id": "call_time",-                                    "name": "get_time",-                                    "args": {-                                        "zone": "UTC"-                                    }-                                }-                            }-                    ]-                }]-              }-            |]-        , testCase "invoke omits Gemini thought signatures when metadata is malformed" $ do-            let toolCall = ToolCall "call_1" "function" "get_weather" [aesonQQ|{"city": "Paris"}|]-                assistant =-                  (assistantMessage "")-                    { messageToolCalls = Just [toolCall]-                    , messageMetadata = Map.singleton "langchain.gemini.thoughtSignatures" (Aeson.String "invalid")-                    }-            capturedRequest <- newEmptyMVar-            withTestApplication-              (capturingGenerateContentServer (curry . putMVar $ capturedRequest))-              $ \url ->-                withGeminiProvider url $ \provider -> do-                  result <- runExceptT $ invoke provider [assistant] Nothing-                  case result of-                    Left err -> assertFailure $ "Expected invoke success, got: " ++ show err-                    Right _ -> pure ()-            (_, body) <- takeMVar capturedRequest-            Aeson.decode body-              @?= Just-                [aesonQQ|-              {-                "contents": [-                    {-                        "role": "model",-                        "parts": [-                            {-                                "functionCall": {-                                    "id": "call_1",-                                    "name": "get_weather",-                                    "args": {-                                        "city": "Paris"-                                    }-                            }-                        }]-                    }-                ]-              }-            |]-        , testCase "invoke groups adjacent Gemini function responses" $ do-            let weatherResult =-                  (toolMessage "Sunny")-                    { messageName = Just "get_weather"-                    , messageToolId = Just "call_weather"-                    }-                timeResult =-                  (toolMessage "12:00")-                    { messageName = Just "get_time"-                    , messageToolId = Just "call_time"-                    }-            capturedRequest <- newEmptyMVar-            withTestApplication-              (capturingGenerateContentServer (curry . putMVar $ capturedRequest))-              $ \url ->-                withGeminiProvider url $ \provider -> do-                  let messages = [userMessage "Weather?", weatherResult, timeResult]-                  result <- runExceptT $ invoke provider messages Nothing-                  case result of-                    Left err -> assertFailure $ "Expected invoke success, got: " ++ show err-                    Right _ -> return ()-            (_, body) <- takeMVar capturedRequest-            Aeson.decode body-              @?= Just-                [aesonQQ|-              {-                "contents": [-                  {"role": "user", "parts": [{"text": "Weather?"}]},-                  {"role": "user", "parts": [-                    {"functionResponse": {"id": "call_weather", "name": "get_weather", "response": {"result": "Sunny"}}},-                    {"functionResponse": {"id": "call_time", "name": "get_time", "response": {"result": "12:00"}}}-                  ]}-                ]-              }-            |]-        ]-    , testGroup-        "stream"-        [ testCase "live Gemini stream emits text and usage" $ do-            mbApiKey <- lookupEnv "GEMINI_API_KEY"-            case mbApiKey of-              Nothing -> putStrLn " [SKIPPED] GEMINI_API_KEY is not set"-              Just envApiKey -> do-                envModel <- fromMaybe "gemini-3.5-flash-lite" <$> lookupEnv "GEMINI_STREAM_TEST_MODEL"-                let provider = newGemini (T.pack envApiKey) (T.pack envModel) Nothing-                result <--                  timeout 60000000 $-                    runResourceT . runExceptT . collectEvents $-                      stream provider [userMessage "Reply with exactly OK."] Nothing-                case result of-                  Nothing -> assertFailure "Gemini stream timed out"-                  Just (Left err) -> assertFailure $ "Expected stream success, got: " ++ show err-                  Just (Right events) -> case reverse events of-                    LLMEnd _ responseMessage (Just usage) : _ -> do-                      assertBool "Expected non-empty streamed text" $ not $ T.null $ extractMessageText responseMessage-                      assertBool "Expected positive total token usage" $ totalTokens usage > 0-                    _ -> assertFailure $ "Expected LLMEnd with usage, got: " ++ show events-        , testCase "live Gemini stream invokes a tool and continues with its result" $ do-            mbApiKey <- lookupEnv "GEMINI_API_KEY"-            case mbApiKey of-              Nothing -> putStrLn " [SKIPPED] GEMINI_API_KEY is not set"-              Just envApiKey -> do-                envModel <- fromMaybe "gemini-3.5-flash-lite" <$> lookupEnv "GEMINI_STREAM_TEST_MODEL"-                let weatherTool :: Tool IO-                    weatherTool =-                      createTool-                        "get_weather"-                        "Returns the current weather for a city."-                        weatherSchema-                        (const . return $ Right "The weather in Paris is sunny and 22 C.")-                    provider = newGemini (T.pack envApiKey) (T.pack envModel) Nothing-                    runLive messages config =-                      timeout 60000000 $-                        runResourceT . runExceptT . collectEvents $-                          stream provider messages config-                    prompt = userMessage "Use get_weather to look up the weather in Paris, then answer using the tool result."--                firstResult <- runLive [prompt] (bindToolsConfig @Gemini [weatherTool] Nothing)-                firstEvents <- case firstResult of-                  Nothing -> assertFailure "Gemini tool-call stream timed out" >> fail "unreachable"-                  Just (Left err) -> assertFailure ("Expected tool-call stream success, got: " ++ show err) >> fail "unreachable"-                  Just (Right events) -> pure events-                (assistant, toolCalls) <- case reverse firstEvents of-                  LLMEnd _ responseMessage _ : _ -> case messageToolCalls responseMessage of-                    Just calls@[toolCall]-                      | toolCallName toolCall == "get_weather" -> pure (responseMessage, calls)-                    _ -> assertFailure ("Expected Gemini tool call, got: " ++ show firstEvents) >> fail "unreachable"-                  _ -> assertFailure ("Expected tool-call stream end, got: " ++ show firstEvents) >> fail "unreachable"-                toolResults <- forM toolCalls $ \toolCall -> do-                  output <- CoreTool.toolExecute weatherTool (toolCallArguments toolCall)-                  case output of-                    Left err -> assertFailure ("Tool execution failed: " ++ show err) >> fail "unreachable"-                    Right text ->-                      pure $-                        (textMessage Tool text)-                          { messageName = Just (toolCallName toolCall)-                          , messageToolId = Just (toolCallId toolCall)-                          }-                secondResult <- runLive ([prompt, assistant] <> toolResults) Nothing-                case secondResult of-                  Nothing -> assertFailure "Gemini tool-result stream timed out"-                  Just (Left err) -> assertFailure $ "Expected tool-result stream success, got: " ++ show err-                  Just (Right events) -> case reverse events of-                    LLMEnd _ responseMessage (Just usage) : _ -> do-                      assertBool "Expected final text after tool result" $-                        not $-                          T.null $-                            extractMessageText responseMessage-                      assertBool "Expected positive total token usage" $ totalTokens usage > 0-                    _ -> assertFailure $ "Expected LLMEnd with usage, got: " ++ show events-        , testCase "stream sends Gemini function declarations and function responses" $ do-            let weatherTool :: Tool IO-                weatherTool = createTool "get_weather" "Gets the weather" weatherSchema (const $ pure $ Right "sunny")-                toolCall =-                  ToolCall-                    "call_weather"-                    "function"-                    "get_weather"-                    [aesonQQ|{"city": "Paris"}|]-                assistant = (assistantMessage "") {messageToolCalls = Just [toolCall]}-                toolResult = (toolMessage "Sunny") {messageToolId = Just "call_weather"}-            capturedRequest <- newEmptyMVar-            withTestApplication-              ( capturingRawSseRequestServer-                  (curry . putMVar $ capturedRequest)-                  [sseFrame "{}"]-              )-              $ \url -> withGeminiProvider url $ \provider ->-                void . runResourceT . runExceptT . collectEvents $-                  stream-                    provider-                    [userMessage "Weather?", assistant, toolResult]-                    (bindToolsConfig @Gemini [weatherTool] Nothing)-            (request, body) <- takeMVar capturedRequest-            requestMethod request @?= "POST"-            rawPathInfo request @?= "/v1beta/models/test-model:streamGenerateContent"-            Aeson.decode body-              @?= Just-                [aesonQQ|-              {-                "contents": [-                  {"role": "user", "parts": [{"text": "Weather?"}]},-                  {"role": "model", "parts": [{"functionCall": {-                    "id": "call_weather", "name": "get_weather", "args": {"city": "Paris"}-                  }}]},-                  {"role": "user", "parts": [{"functionResponse": {-                    "id": "call_weather", "name": "get_weather", "response": {"result": "Sunny"}-                  }}]}-                ],-                "tools": [{"functionDeclarations": [{-                  "name": "get_weather",-                  "description": "Gets the weather",-                  "parameters": {-                    "type": "OBJECT",-                    "properties": {"city": {"type": "STRING"}},-                    "required": ["city"]-                  }-                }]}]-              }-            |]-        , testCase "stream emits incremental text chunks and ends" $ do-            result <- collectRawStream [sseFrame $ chunk "Hel", sseFrame $ chunk "lo"]-            case result of-              Left err -> assertFailure $ "Expected stream success, got: " ++ show err-              Right events -> case events of-                [LLMStart {}, LLMChunk _ "Hel" Nothing, LLMChunk _ "lo" Nothing, LLMEnd _ responseMessage Nothing] ->-                  do-                    extractMessageText responseMessage @?= "Hello"-                    messageMetadata responseMessage @?= Map.empty-                _ -> assertFailure $ "Unexpected stream events: " ++ show events-        , testCase "stream emits mixed text and function call chunks" $ do-            let frame =-                  Aeson.encode-                    [aesonQQ|-                  {-                    "candidates": [-                      {-                        "index": 0,-                        "content": {-                          "parts": [-                             { "text": "Checking weather" },-                             {-                               "thoughtSignature": "signature_1",-                               "functionCall": {-                                "id": "call_1",-                                "name": "get_weather",-                                "args": { "city": "Paris" }-                              }-                            }-                          ]-                        }-                      }-                    ]-                  }-                |]-                expectedCall = ToolCall "call_1" "function" "get_weather" [aesonQQ|{"city": "Paris"}|]-            result <- collectRawStream [sseFrame frame]-            case result of-              Right [LLMStart {}, LLMChunk _ "Checking weather" (Just toolCall), LLMEnd _ responseMessage Nothing] -> do-                toolCall @?= expectedCall-                extractMessageText responseMessage @?= "Checking weather"-                messageToolCalls responseMessage @?= Just [expectedCall]-                Map.lookup "langchain.gemini.thoughtSignatures" (messageMetadata responseMessage)-                  @?= Just (Aeson.toJSON [Just ("signature_1" :: T.Text)])-              Left err -> assertFailure $ "Expected stream success, got: " ++ show err-              Right events -> assertFailure $ "Unexpected stream events: " ++ show events-        , testCase "stream delivers a chunk before the response completes" $ do-            firstChunkReceived <- newEmptyMVar-            continueResponse <- newEmptyMVar-            receivedEvents <- newTVarIO []-            withGatedProvider (takeMVar continueResponse) $ \provider -> do-              consumer <--                async-                  . runResourceT-                  . runExceptT-                  . runConduit-                  $ stream provider [userMessage "Hello"] Nothing-                    .| C.mapM_-                      ( \event -> do-                          liftIO . atomically $ modifyTVar' receivedEvents (event :)-                          case event of-                            LLMChunk _ "Hel" _ -> liftIO $ putMVar firstChunkReceived ()-                            _ -> pure ()-                      )-              received <- timeout 500000 $ takeMVar firstChunkReceived-              assertBool "expected first chunk before releasing the response" $ isJust received-              stillStreaming <- poll consumer-              assertBool "consumer should wait for the remaining response" $ isNothing stillStreaming-              putMVar continueResponse ()-              result <- timeout 500000 $ wait consumer-              case result of-                Nothing -> assertFailure "stream did not finish after releasing the response"-                Just (Left err) -> assertFailure $ "Expected stream success, got: " ++ show err-                Just (Right ()) -> do-                  events <- reverse <$> readTVarIO receivedEvents-                  case reverse events of-                    LLMEnd _ responseMessage Nothing : _ -> extractMessageText responseMessage @?= "Hello"-                    _ -> assertFailure $ "Expected a completed stream, got: " ++ show events-        , testCase "stream finishes when the SSE connection closes" $ do-            result <- collectRawStream [sseFrame $ chunk "Hello"]-            case result of-              Left err -> assertFailure $ "Expected stream success, got: " ++ show err-              Right events -> case events of-                [LLMStart {}, LLMChunk _ "Hello" Nothing, LLMEnd _ responseMessage Nothing] ->-                  extractMessageText responseMessage @?= "Hello"-                _ -> assertFailure $ "Unexpected stream events: " ++ show events-        , testCase "stream converts malformed SSE data to LangchainError" $ do-            result <- collectRawStream [sseFrame "not JSON"]-            case result of-              Left _ -> pure ()-              Right events -> assertFailure $ "Expected stream failure, got: " ++ show events-        , testCase "stream rejects malformed function calls" $ do-            result <--              collectRawStream-                [ sseFrame $-                    Aeson.encode-                      [aesonQQ|-                    {-                      "candidates": [-                        {-                          "content": {-                            "parts": [-                              {-                                "functionCall": {-                                  "args": {}-                                }-                              }-                            ]-                          }-                        }-                      ]-                    }-                  |]-                ]-            case result of-              Left _ -> pure ()-              Right events -> assertFailure $ "Expected stream failure, got: " ++ show events-        , testCase "stream converts HTTP errors to LangchainError" $ do-            result <- withTestApplication errorServer $ \url ->-              withGeminiProvider url $ \provider ->-                runResourceT $ runExceptT $ collectEvents (stream provider [userMessage "Hello"] Nothing)-            case result of-              Left _ -> pure ()-              Right events -> assertFailure $ "Expected stream failure, got: " ++ show events-        , testCase "stream includes usage metadata on LLMEnd" $ do-            let usage = TokenUsage 7 5 12-                frame =-                  Aeson.encode-                    [aesonQQ|-                      {-                        "candidates": [-                          {-                            "index": 0,-                            "content": {-                              "parts": [{"text": "Hello"}]-                            }-                          }-                        ],-                        "usageMetadata": {-                          "promptTokenCount": 7,-                          "candidatesTokenCount": 5,-                          "totalTokenCount": 12-                        }-                      }-                    |]-            result <- collectRawStream [sseFrame frame]-            case result of-              Right [LLMStart {}, LLMChunk _ "Hello" Nothing, LLMEnd _ responseMessage (Just actualUsage)] -> do-                extractMessageText responseMessage @?= "Hello"-                actualUsage @?= usage-              Left err -> assertFailure $ "Expected stream success, got: " ++ show err-              Right events -> assertFailure $ "Unexpected stream events: " ++ show events-        , testCase "stream uses the Gemini SSE endpoint and contents payload" $ do-            capturedRequest <- newEmptyMVar-            withTestApplication-              ( capturingRawSseRequestServer-                  (curry . putMVar $ capturedRequest)-                  [sseFrame "{}"]-              )-              $ \url -> do-                withGeminiProvider url $ \provider ->-                  void . runResourceT . runExceptT . collectEvents $ stream provider [userMessage "Hello"] Nothing-                (request, body) <- takeMVar capturedRequest-                requestMethod request @?= "POST"-                rawPathInfo request @?= "/v1beta/models/test-model:streamGenerateContent"-                rawQueryString request @?= "?alt=sse&key=test-key"-                Aeson.decode body-                  @?= Just-                    [aesonQQ|{"contents": [{"role": "user", "parts": [{"text": "Hello"}]}]}|]-        , testCase "stream closes the SSE connection when the consumer stops after a chunk" $ do-            clientClosed <- newEmptyMVar-            withCancellationAwareProvider (putMVar clientClosed ()) $ \provider -> do-              void . runResourceT . runExceptT . runConduit $-                stream provider [userMessage "Hello"] Nothing .| (await >> await)-              closed <- timeout 500000 $ takeMVar clientClosed-              assertBool "expected the SSE connection to close" $ isJust closed-        ]-    ]-  where-    weatherSchema :: Aeson.Value-    weatherSchema =-      [aesonQQ|-        {-          "type": "OBJECT",-          "properties": {"city": {"type": "STRING"}},-          "required": ["city"]-        }-      |]
test/Test/Langchain/Provider/Mock.hs view
@@ -17,18 +17,22 @@ module Test.Langchain.Provider.Mock   ( MockModel (..)   , newMockModel+  , MockErrorModel (..)+  , newMockErrorModel   ) where +import Control.Monad.Except (throwError) import Data.Aeson (object, (.=)) import Data.Conduit (yield) import Data.Text (Text)  import Langchain.Cache.Core (CacheableChatModel (..))-import Langchain.Core.Model+import Langchain.Error (LangchainError)+import Langchain.Model   ( ChatModel (..)   , assistantMessage   )-import Langchain.Core.Stream (StreamEvent (..))+import Langchain.Stream (StreamEvent (..)) import Langchain.Tool.Binding (ToolBinder (..))  -- | Mock model implementation for pure monadic testing.@@ -63,3 +67,18 @@       , "model" .= mName       , "response" .= response       ]++-- | Mock model that deterministically throws an error on invocation+newtype MockErrorModel = MockErrorModel+  { mockError :: LangchainError+  }+  deriving (Eq, Show)++newMockErrorModel :: LangchainError -> MockErrorModel+newMockErrorModel = MockErrorModel++instance ChatModel MockErrorModel where+  type ModelConfig MockErrorModel = ()++  invoke model _ _ = throwError (mockError model)+  stream model _ _ = throwError (mockError model)
− test/Test/Langchain/Provider/Ollama.hs
@@ -1,165 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Test.Langchain.Provider.Ollama (tests) where--import Test.Tasty-import Test.Tasty.HUnit--import Control.Monad.Except (runExceptT)-import Data.Text (Text)-import qualified Data.Text as T--import Langchain.Core.Model-import Langchain.Core.Tool (Tool)-import Langchain.Provider.Ollama-import Langchain.Tool.Calculator (calculatorTool)-import Test.Langchain.TestHelpers (withOllamaModel)--import qualified Data.List.NonEmpty as NonEmpty-import qualified Ollama.Client as OC-import qualified Ollama.Types.Format as OFormat-import qualified Ollama.Types.Message as O-import qualified Ollama.Types.Tool as OTool--testModelName :: Text-testModelName = "gemma3:latest"--tests :: TestTree-tests =-  testGroup-    "Langchain.Provider.Ollama"-    [ testCase "newOllama initializes provider with defaultConfig" $ do-        p <- newOllama testModelName defaultConfig-        ollamaModelName p @?= testModelName-    , testCase "newOllama accepts custom OllamaClientConfig" $ do-        let cfg =-              defaultConfig-                { configBaseUrl = "http://custom-host:11434"-                , configTimeout = 120-                }-        p <- newOllama "qwen3.5:2b" cfg-        ollamaModelName p @?= "qwen3.5:2b"-        configBaseUrl (OC.clientConfig (client p)) @?= "http://custom-host:11434"-        configTimeout (OC.clientConfig (client p)) @?= 120-    , testCase "newOllamaWithClient wraps existing OllamaClient" $ do-        c <- OC.defaultClient-        let p = newOllamaWithClient testModelName c-        ollamaModelName p @?= testModelName-    , testCase "invoke returns Assistant message" $ do-        withOllamaModel testModelName $ \modelName -> do-          p <- newOllama modelName defaultConfig-          let input = [userMessage "What is 2 + 2? Answer with just the number."]-          res <- runExceptT $ invoke p input Nothing-          case res of-            Left err -> assertFailure $ "Expected success, got error: " ++ show err-            Right msg -> do-              messageRole msg @?= Assistant-              assertBool "Should contain 4" ("4" `T.isInfixOf` extractMessageText msg)-    , testCase "batch processes multiple inputs" $ do-        withOllamaModel testModelName $ \modelName -> do-          p <- newOllama modelName defaultConfig-          let inputs = [[userMessage "What is 1 + 1?"], [userMessage "What is 2 + 2?"]]-          res <- runExceptT $ batch p inputs Nothing-          case res of-            Left err -> assertFailure $ "Expected success, got error: " ++ show err-            Right msgs -> do-              length msgs @?= 2-    , testCase "withOptions sets ModelOptions on ChatRequest" $ do-        p <- newOllama testModelName defaultConfig-        let opts = defaultOptions {optTemperature = Just 0.3, optNumCtx = Just 4096}-            req = withOptions opts (chatRequestFor p [userMessage "Hello"])-        case chatOptions req of-          Nothing -> assertFailure "Expected chatOptions in ChatRequest"-          Just o -> do-            optTemperature o @?= Just 0.3-            optNumCtx o @?= Just 4096-    , testCase "toOllamaTool converts calculatorTool to Ollama Tool" $ do-        let cTool = calculatorTool :: Tool IO-        case toOllamaTool cTool of-          Nothing -> assertFailure "Failed to convert calculatorTool to Ollama Tool"-          Just ot -> do-            OTool.toolType ot @?= "function"-            OTool.fnName (OTool.toolFunction ot) @?= "calculator"-    , testCase "withTools on ChatRequest sets chatTools" $ do-        p <- newOllama testModelName defaultConfig-        let req = withTools [calculatorTool :: Tool IO] (chatRequestFor p [userMessage "Hello"])-        case chatTools req of-          Nothing -> assertFailure "Expected chatTools in ChatRequest"-          Just ts -> length ts @?= 1-    , testCase "chatRequestFor creates base request" $ do-        p <- newOllama testModelName defaultConfig-        let req = chatRequestFor p [userMessage "Hello"]-        chatModel req @?= ModelName testModelName-    , testCase "invoke propagates chatFormat from mbReq" $ do-        withOllamaModel testModelName $ \modelName -> do-          p <- newOllama modelName defaultConfig-          let input = [userMessage "Return JSON: {\"answer\": 42}"]-              req = withJsonFormat (chatRequestFor p input)-          chatFormat req @?= Just OFormat.JsonFormat-          res <- runExceptT $ invoke p input (Just req)-          case res of-            Left err -> assertFailure $ "Expected success, got error: " ++ show err-            Right msg -> messageRole msg @?= Assistant-    , testGroup-        "Precedence Rules (resolveChatRequest)"-        [ testCase "inputMsgs takes priority over ChatRequest chatMessages when non-empty" $ do-            p <- newOllama "base-model" defaultConfig-            let invokeMsgs = [userMessage "From invoke argument"]-                reqMsgs = [userMessage "From ChatRequest"]-                customReq = chatRequestFor p reqMsgs-                (resolvedReq, resolvedModel, resolvedMsgs) =-                  resolveChatRequest p invokeMsgs (Just customReq)-            resolvedModel @?= "base-model"-            resolvedMsgs @?= invokeMsgs-            NonEmpty.toList (chatMessages resolvedReq) @?= map toOllamaMessage invokeMsgs-        , testCase "fallback to ChatRequest chatMessages when inputMsgs is empty" $ do-            p <- newOllama "base-model" defaultConfig-            let reqMsgs = [userMessage "From ChatRequest only"]-                customReq = chatRequestFor p reqMsgs-                (resolvedReq, resolvedModel, resolvedMsgs) =-                  resolveChatRequest p [] (Just customReq)-            resolvedModel @?= "base-model"-            resolvedMsgs @?= reqMsgs-            NonEmpty.toList (chatMessages resolvedReq) @?= map toOllamaMessage reqMsgs-        , testCase "defaults to single empty message when both inputMsgs and mbReq are empty" $ do-            p <- newOllama "base-model" defaultConfig-            let (resolvedReq, resolvedModel, resolvedMsgs) =-                  resolveChatRequest p [] Nothing-            resolvedModel @?= "base-model"-            resolvedMsgs @?= []-            NonEmpty.toList (chatMessages resolvedReq) @?= [O.userMessage ""]-        , testCase "ChatRequest chatModel overrides provider ollamaModelName when non-empty" $ do-            p <- newOllama "base-model" defaultConfig-            let customReq = (chatRequestFor p [userMessage "hi"]) {chatModel = ModelName "custom-model"}-                (resolvedReq, resolvedModel, _) =-                  resolveChatRequest p [userMessage "hi"] (Just customReq)-            resolvedModel @?= "custom-model"-            chatModel resolvedReq @?= ModelName "custom-model"-        , testCase "falls back to ollamaModelName when ChatRequest chatModel is empty" $ do-            p <- newOllama "base-model" defaultConfig-            let customReq = (chatRequestFor p [userMessage "hi"]) {chatModel = ModelName ""}-                (resolvedReq, resolvedModel, _) =-                  resolveChatRequest p [userMessage "hi"] (Just customReq)-            resolvedModel @?= "base-model"-            chatModel resolvedReq @?= ModelName "base-model"-        , testCase "falls back to ollamaModelName when mbReq is Nothing" $ do-            p <- newOllama "base-model" defaultConfig-            let (resolvedReq, resolvedModel, _) =-                  resolveChatRequest p [userMessage "hi"] Nothing-            resolvedModel @?= "base-model"-            chatModel resolvedReq @?= ModelName "base-model"-        , testCase "preserves options, tools, format, and keep-alive from ChatRequest" $ do-            p <- newOllama "base-model" defaultConfig-            let opts = defaultOptions {optTemperature = Just 0.5}-                customReq =-                  (withOptions opts (chatRequestFor p [userMessage "dummy"]))-                    { chatKeepAlive = Just "5m"-                    , chatFormat = Just OFormat.JsonFormat-                    }-                (resolvedReq, _, _) =-                  resolveChatRequest p [userMessage "override message"] (Just customReq)-            chatOptions resolvedReq @?= Just opts-            chatKeepAlive resolvedReq @?= Just "5m"-            chatFormat resolvedReq @?= Just OFormat.JsonFormat-        ]-    ]
− test/Test/Langchain/Provider/OllamaConversionSpec.hs
@@ -1,75 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Test.Langchain.Provider.OllamaConversionSpec (tests) where--import qualified Data.List.NonEmpty as NonEmpty-import qualified Data.Map.Strict as Map-import Test.Tasty-import Test.Tasty.HUnit--import Langchain.Core.Model-import Langchain.Provider.Ollama-  ( fromOllamaMessage-  , fromOllamaRole-  , toOllamaMessage-  , toOllamaRole-  )-import Ollama.Types.Common (Base64Image (..))-import qualified Ollama.Types.Message as O--tests :: TestTree-tests =-  testGroup-    "Langchain.Provider.OllamaConversionSpec"-    [ testGroup-        "Role Mapping Tests"-        [ testCase "Standard roles map to Ollama equivalents" $ do-            toOllamaRole System @?= O.System-            toOllamaRole User @?= O.User-            toOllamaRole Assistant @?= O.Assistant-            toOllamaRole Tool @?= O.Tool-        , testCase "Developer and Function roles map to System/Tool fallbacks" $ do-            toOllamaRole Developer @?= O.System-            toOllamaRole Function @?= O.Tool-        , testCase "fromOllamaRole inverts toOllamaRole for core roles" $ do-            fromOllamaRole O.System @?= System-            fromOllamaRole O.User @?= User-            fromOllamaRole O.Assistant @?= Assistant-            fromOllamaRole O.Tool @?= Tool-        ]-    , testGroup-        "Message Conversion Tests"-        [ testCase "toOllamaMessage extracts base64 image data" $ do-            let msg = imageMessage User "image/png" "iVBORw0KGgoAAAANSUhEUg=="-                (O.Message r _ imgs _ _ _) = toOllamaMessage msg-            r @?= O.User-            case imgs of-              Just [Base64Image b64] -> b64 @?= "iVBORw0KGgoAAAANSUhEUg=="-              _ -> assertFailure "Expected single base64 image in Ollama message"-        , testCase "fromOllamaMessage parses role and text content" $ do-            let oMsg = O.Message O.Assistant "Response content" Nothing Nothing Nothing Nothing-                msg = fromOllamaMessage oMsg-            messageRole msg @?= Assistant-            extractMessageText msg @?= "Response content"-        , testCase "Round-trip preserves user text message" $ do-            let msg = userMessage "What is pure functional programming?"-                roundTripped = fromOllamaMessage (toOllamaMessage msg)-            roundTripped @?= msg-        , testCase "Multi-modal message with text and image converts correctly" $ do-            let msg =-                  Message-                    User-                    ( TextBlock "Analyze this:"-                        NonEmpty.:| [ImageBlock $ ImageContent (ImageBase64 (Just "image/jpeg") "dGVzdA==") Nothing Nothing]-                    )-                    Nothing-                    Nothing-                    Nothing-                    Map.empty-                (O.Message _ txt imgs _ _ _) = toOllamaMessage msg-            txt @?= "Analyze this:"-            case imgs of-              Just [Base64Image b64] -> b64 @?= "dGVzdA=="-              _ -> assertFailure "Expected image block conversion"-        ]-    ]
− test/Test/Langchain/Provider/OpenAI.hs
@@ -1,414 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Test.Langchain.Provider.OpenAI (tests) where--import Control.Concurrent (newEmptyMVar, putMVar, takeMVar)-import Control.Concurrent.Async (async, poll, wait)-import Control.Concurrent.STM-  ( atomically-  , modifyTVar'-  , newTVarIO-  , readTVarIO-  )-import Control.Monad (forM, void)-import Control.Monad.Except (runExceptT)-import Control.Monad.IO.Class (liftIO)-import Control.Monad.Trans.Resource (runResourceT)-import Data.Aeson (Value)-import qualified Data.Aeson as Aeson-import qualified Data.Aeson.KeyMap as KeyMap-import qualified Data.ByteString.Lazy as LBS-import Data.Conduit (await, runConduit, (.|))-import qualified Data.Conduit.Combinators as C-import Data.Maybe (fromMaybe, isJust, isNothing)-import qualified Data.Text as T-import qualified Data.Vector as V-import Network.HTTP.Types (status500)-import Network.Wai (Application, responseLBS)-import System.Environment (lookupEnv)-import System.Timeout (timeout)-import Test.Tasty-import Test.Tasty.HUnit--import Langchain.Core.Error (LangchainError)-import Langchain.Core.Model-import Langchain.Core.Stream (StreamEvent (..), TokenUsage (..), collectEvents)-import Langchain.Core.Tool (Tool, createTool, toolToValue)-import qualified Langchain.Core.Tool as CoreTool--import Langchain.Provider.OpenAI-import Test.Langchain.Provider.TestSseServer-  ( cancellationAwareSseServer-  , capturingRawSseServer-  , collectModelStream-  , gatedSseServer-  , rawSseServer-  , sseFrame-  , withTestApplication-  )--withErrorProvider :: (OpenAI -> IO a) -> IO a-withErrorProvider action =-  withTestApplication errorServer $ \url -> withOpenAIProvider url action--withRawTestProvider :: [LBS.ByteString] -> (OpenAI -> IO a) -> IO a-withRawTestProvider frames action =-  withTestApplication (rawSseServer frames) $ \url -> withOpenAIProvider url action--withRequestCapturingProvider :: (Maybe Value -> IO ()) -> (OpenAI -> IO a) -> IO a-withRequestCapturingProvider captureRequest action =-  withTestApplication (capturingRawSseServer (captureRequest . Aeson.decode) [sseFrame "[DONE]"]) $ \url ->-    withOpenAIProvider url action--withCancellationAwareProvider :: IO () -> (OpenAI -> IO a) -> IO a-withCancellationAwareProvider signalClientClosed action =-  withTestApplication (cancellationAwareSseServer (sseFrame $ chunk "Hello") signalClientClosed) $ \url ->-    withOpenAIProvider url action--withGatedProvider :: IO () -> (OpenAI -> IO a) -> IO a-withGatedProvider waitForContinuation action =-  withTestApplication-    ( gatedSseServer-        (sseFrame $ chunk "Hel")-        waitForContinuation-        [sseFrame (chunk "lo"), sseFrame "[DONE]"]-    )-    $ \url -> withOpenAIProvider url action--withOpenAIProvider :: T.Text -> (OpenAI -> IO a) -> IO a-withOpenAIProvider url action =-  action $ (newOpenAI "test-key" "test-model") {baseUrl = url}--errorServer :: Application-errorServer _request respond = respond $ responseLBS status500 [] ""--collectRawStream :: [LBS.ByteString] -> IO (Either LangchainError [StreamEvent])-collectRawStream frames =-  withRawTestProvider frames $ \provider ->-    collectModelStream provider [userMessage "Hello"] Nothing--chunk :: LBS.ByteString -> LBS.ByteString-chunk content =-  "{\"id\":\"chatcmpl-test\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"test-model\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\""-    <> content-    <> "\"},\"finish_reason\":null}]}"--emptyChoices :: LBS.ByteString-emptyChoices =-  "{\"id\":\"chatcmpl-test\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"test-model\",\"choices\":[]}"--tests :: TestTree-tests =-  testGroup-    "Langchain.Provider.OpenAI"-    [ testCase "newOpenAI initializes default provider" $ do-        let p = newOpenAI "sk-test" "gpt-4o"-        model p @?= "gpt-4o"-        baseUrl p @?= "https://api.openai.com"-    , testCase "openAICompatible initializes custom endpoint" $ do-        let p = openAICompatible "sk-test" "custom-llm" "https://custom-ai.example.com"-        model p @?= "custom-llm"-        baseUrl p @?= "https://custom-ai.example.com"-    , testCase "live OpenAI stream emits text and usage" $ do-        mbApiKey <- lookupEnv "OPENAI_API_KEY"-        case mbApiKey of-          Nothing -> putStrLn " [SKIPPED] OPENAI_API_KEY is not set"-          Just envApiKey -> do-            envModel <- fromMaybe "gpt-4o-mini" <$> lookupEnv "OPENAI_STREAM_TEST_MODEL"-            result <--              timeout 60000000 $-                runResourceT $-                  runExceptT $-                    collectEvents $-                      stream-                        (newOpenAI (T.pack envApiKey) (T.pack envModel))-                        [userMessage "Reply with exactly OK."]-                        Nothing-            case result of-              Nothing -> assertFailure "OpenAI stream timed out"-              Just (Left err) -> assertFailure $ "Expected stream success, got: " ++ show err-              Just (Right events) -> do-                print events-                case reverse events of-                  LLMEnd _ responseMessage (Just usage) : _ -> do-                    assertBool "Expected non-empty streamed text" $ not $ T.null $ extractMessageText responseMessage-                    assertBool "Expected positive total token usage" $ totalTokens usage > 0-                  _ -> assertFailure $ "Expected LLMEnd with usage, got: " ++ show events-    , testCase "live OpenAI stream invokes a tool and continues with its result" $ do-        mbApiKey <- lookupEnv "OPENAI_API_KEY"-        case mbApiKey of-          Nothing -> putStrLn " [SKIPPED] OPENAI_API_KEY is not set"-          Just envApiKey -> do-            envModel <- fromMaybe "gpt-4o-mini" <$> lookupEnv "OPENAI_STREAM_TEST_MODEL"-            let weatherTool :: Tool IO-                weatherTool =-                  createTool-                    "get_weather"-                    "Returns the current weather for a city."-                    ( Aeson.object-                        [ "type" Aeson..= ("object" :: T.Text)-                        , "properties"-                            Aeson..= Aeson.object-                              [ "city" Aeson..= Aeson.object ["type" Aeson..= ("string" :: T.Text)]-                              ]-                        , "required" Aeson..= ["city" :: T.Text]-                        , "additionalProperties" Aeson..= False-                        ]-                    )-                    (const $ pure $ Right "The weather in Paris is sunny and 22 C.")-                provider = newOpenAI (T.pack envApiKey) (T.pack envModel)-                runLive messages config =-                  timeout 60000000 $-                    runResourceT $-                      runExceptT $-                        collectEvents $-                          stream provider messages config-                prompt = userMessage "Use get_weather to look up the weather in Paris, then answer using the tool result."--            firstResult <--              runLive [prompt] (Just $ openAITools [weatherTool] (OpenAIToolFunction "get_weather"))-            firstEvents <- case firstResult of-              Nothing -> assertFailure "OpenAI tool-call stream timed out" >> fail "unreachable"-              Just (Left err) -> assertFailure ("Expected tool-call stream success, got: " ++ show err) >> fail "unreachable"-              Just (Right events) -> pure events-            (assistant, toolCalls) <- case reverse firstEvents of-              LLMEnd _ responseMessage _ : _ -> case messageToolCalls responseMessage of-                Just calls@[toolCall]-                  | toolCallName toolCall == "get_weather" -> pure (responseMessage, calls)-                _ -> assertFailure ("Expected OpenAI tool call, got: " ++ show firstEvents) >> fail "unreachable"-              _ -> assertFailure ("Expected tool-call stream end, got: " ++ show firstEvents) >> fail "unreachable"-            toolResults <- forM toolCalls $ \toolCall -> do-              output <- CoreTool.toolExecute weatherTool (toolCallArguments toolCall)-              case output of-                Left err -> assertFailure ("Tool execution failed: " ++ show err) >> fail "unreachable"-                Right text ->-                  pure $-                    (textMessage Tool text)-                      { messageName = Just (toolCallName toolCall)-                      , messageToolId = Just (toolCallId toolCall)-                      }-            secondResult <- runLive ([prompt, assistant] <> toolResults) Nothing-            case secondResult of-              Nothing -> assertFailure "OpenAI tool-result stream timed out"-              Just (Left err) -> assertFailure $ "Expected tool-result stream success, got: " ++ show err-              Just (Right events) -> case reverse events of-                LLMEnd _ responseMessage (Just usage) : _ -> do-                  assertBool "Expected final text after tool result" $-                    not $-                      T.null $-                        extractMessageText responseMessage-                  assertBool "Expected positive total token usage" $ totalTokens usage > 0-                _ -> assertFailure $ "Expected LLMEnd with usage, got: " ++ show events-    , testCase "normalizeBaseUrl strips endpoint paths for servant compatibility" $ do-        normalizeBaseUrl "https://api.openai.com" @?= "https://api.openai.com"-        normalizeBaseUrl "https://api.openai.com/" @?= "https://api.openai.com"-        normalizeBaseUrl "https://api.openai.com/v1" @?= "https://api.openai.com"-        normalizeBaseUrl "https://api.openai.com/v1/" @?= "https://api.openai.com"-        normalizeBaseUrl "https://api.openai.com/v1/chat/completions" @?= "https://api.openai.com"-        normalizeBaseUrl "https://openrouter.ai/api" @?= "https://openrouter.ai/api"-        normalizeBaseUrl "https://openrouter.ai/api/v1" @?= "https://openrouter.ai/api"-        normalizeBaseUrl "https://openrouter.ai/api/v1/chat/completions" @?= "https://openrouter.ai/api"-        normalizeBaseUrl "http://localhost:11434/v1" @?= "http://localhost:11434"-    , testCase "stream emits chunks and ends at [DONE]" $ do-        result <- collectRawStream [sseFrame $ chunk "Hel", sseFrame $ chunk "lo", sseFrame "[DONE]"]-        case result of-          Left err -> assertFailure $ "Expected stream success, got: " ++ show err-          Right events -> case events of-            [ LLMStart {}-              , LLMChunk _ "Hel" Nothing-              , LLMChunk _ "lo" Nothing-              , LLMEnd _ responseMessage Nothing-              ] -> extractMessageText responseMessage @?= "Hello"-            _ -> assertFailure $ "Unexpected stream events: " ++ show events-    , testCase "stream delivers a chunk before the response completes" $ do-        firstChunkReceived <- newEmptyMVar-        continueResponse <- newEmptyMVar-        receivedEvents <- newTVarIO []-        withGatedProvider (takeMVar continueResponse) $ \provider -> do-          consumer <--            async-              . runResourceT-              . runExceptT-              . runConduit-              $ stream provider [userMessage "Hello"] Nothing-                .| C.mapM_-                  ( \event -> do-                      liftIO . atomically $ modifyTVar' receivedEvents (event :)-                      case event of-                        LLMChunk _ "Hel" _ -> liftIO $ putMVar firstChunkReceived ()-                        _ -> pure ()-                  )-          received <- timeout 500000 $ takeMVar firstChunkReceived-          assertBool "expected first chunk before releasing the response" $ isJust received-          stillStreaming <- poll consumer-          assertBool "consumer should wait for the remaining response" $ isNothing stillStreaming-          putMVar continueResponse ()-          result <- timeout 500000 $ wait consumer-          case result of-            Nothing -> assertFailure "stream did not finish after releasing the response"-            Just (Left err) -> assertFailure $ "Expected stream success, got: " ++ show err-            Just (Right ()) -> do-              events <- reverse <$> readTVarIO receivedEvents-              case reverse events of-                LLMEnd _ responseMessage Nothing : _ ->-                  extractMessageText responseMessage @?= "Hello"-                _ -> assertFailure $ "Expected a completed stream, got: " ++ show events-    , testCase "stream finishes when the SSE connection closes" $ do-        result <- collectRawStream [sseFrame $ chunk "Hello"]-        case result of-          Left err -> assertFailure $ "Expected stream success, got: " ++ show err-          Right events -> case events of-            [LLMStart {}, LLMChunk _ "Hello" Nothing, LLMEnd _ responseMessage Nothing] ->-              extractMessageText responseMessage @?= "Hello"-            _ -> assertFailure $ "Unexpected stream events: " ++ show events-    , testCase "stream ignores chunks without choices" $ do-        result <- collectRawStream [sseFrame emptyChoices, sseFrame "[DONE]"]-        case result of-          Left err -> assertFailure $ "Expected stream success, got: " ++ show err-          Right events -> case events of-            [LLMStart {}, LLMEnd _ responseMessage Nothing] ->-              extractMessageText responseMessage @?= ""-            _ -> assertFailure $ "Unexpected stream events: " ++ show events-    , testCase "stream converts malformed SSE data to LangchainError" $ do-        result <- collectRawStream [sseFrame "not JSON"]-        case result of-          Left _ -> pure ()-          Right events -> assertFailure $ "Expected stream failure, got: " ++ show events-    , testCase "stream converts HTTP errors to LangchainError" $ do-        result <- withErrorProvider $ \provider ->-          runResourceT $ runExceptT $ collectEvents (stream provider [userMessage "Hello"] Nothing)-        case result of-          Left _ -> pure ()-          Right events -> assertFailure $ "Expected stream failure, got: " ++ show events-    , testCase "stream handles SSE frames written in multiple pieces" $ do-        let frame = sseFrame $ chunk "Hello"-            splitPoint = LBS.length frame `div` 2-            fragments = [LBS.take splitPoint frame, LBS.drop splitPoint frame, "data: [DONE]\n\n"]-        result <- collectRawStream fragments-        case result of-          Left err -> assertFailure $ "Expected stream success, got: " ++ show err-          Right events -> case events of-            [LLMStart {}, LLMChunk _ "Hello" Nothing, LLMEnd _ responseMessage Nothing] ->-              extractMessageText responseMessage @?= "Hello"-            _ -> assertFailure $ "Unexpected stream events: " ++ show events-    , testCase "stream requests usage in stream options" $ do-        requestBody <- newEmptyMVar-        withRequestCapturingProvider (putMVar requestBody) $ \provider -> do-          void . runResourceT . runExceptT $ collectEvents (stream provider [userMessage "Hello"] Nothing)-        mbRequest <- takeMVar requestBody-        case mbRequest of-          Nothing -> assertFailure "Expected JSON request body"-          Just (Aeson.Object fields) -> do-            KeyMap.lookup "stream" fields @?= Just (Aeson.Bool True)-            KeyMap.lookup "stream_options" fields @?= Just (Aeson.object ["include_usage" Aeson..= True])-          Just request -> assertFailure $ "Expected JSON object, got: " ++ show request-    , testCase "stream sends tool definitions and tool choice" $ do-        let weatherTool :: Tool IO-            weatherTool = createTool "get_weather" "Gets the weather" (Aeson.object []) (const $ pure $ Right "sunny")-            config = openAITools [weatherTool] (OpenAIToolFunction "get_weather")-        requestBody <- newEmptyMVar-        withRequestCapturingProvider (putMVar requestBody) $ \provider -> do-          void . runResourceT . runExceptT $-            collectEvents (stream provider [userMessage "Hello"] (Just config))-        mbRequest <- takeMVar requestBody-        case mbRequest of-          Just (Aeson.Object fields) -> do-            KeyMap.lookup "tools" fields @?= Just (Aeson.toJSON [toolToValue weatherTool])-            KeyMap.lookup "tool_choice" fields-              @?= Just-                ( Aeson.object-                    [ "type" Aeson..= ("function" :: T.Text)-                    , "function" Aeson..= Aeson.object ["name" Aeson..= ("get_weather" :: T.Text)]-                    ]-                )-          Just request -> assertFailure $ "Expected JSON object, got: " ++ show request-          Nothing -> assertFailure "Expected JSON request body"-    , testCase "stream sends assistant tool calls before tool results" $ do-        let toolCall =-              ToolCall-                "call_weather"-                "function"-                "get_weather"-                (Aeson.object ["city" Aeson..= ("Paris" :: T.Text)])-            assistant = (assistantMessage "") {messageToolCalls = Just [toolCall]}-            toolResult = (textMessage Tool "Sunny") {messageToolId = Just "call_weather"}-        requestBody <- newEmptyMVar-        withRequestCapturingProvider (putMVar requestBody) $ \provider -> do-          void . runResourceT . runExceptT $-            collectEvents (stream provider [userMessage "Weather?", assistant, toolResult] Nothing)-        mbRequest <- takeMVar requestBody-        case mbRequest of-          Just (Aeson.Object fields) -> case KeyMap.lookup "messages" fields of-            Just (Aeson.Array messages) -> case V.toList messages of-              [_, Aeson.Object assistantFields, Aeson.Object toolResultFields] -> do-                KeyMap.lookup "tool_calls" assistantFields-                  @?= Just-                    ( Aeson.toJSON-                        [ Aeson.object-                            [ "id" Aeson..= ("call_weather" :: T.Text)-                            , "type" Aeson..= ("function" :: T.Text)-                            , "function"-                                Aeson..= Aeson.object-                                  [ "name" Aeson..= ("get_weather" :: T.Text)-                                  , "arguments" Aeson..= ("{\"city\":\"Paris\"}" :: T.Text)-                                  ]-                            ]-                        ]-                    )-                KeyMap.lookup "tool_call_id" toolResultFields @?= Just (Aeson.String "call_weather")-              messages' -> assertFailure $ "Expected three request messages, got: " ++ show messages'-            request -> assertFailure $ "Expected messages array, got: " ++ show request-          Just request -> assertFailure $ "Expected JSON object, got: " ++ show request-          Nothing -> assertFailure "Expected JSON request body"-    , testCase "stream accumulates text, fragmented tool calls, and usage" $ do-        let frames =-              [ sseFrame-                  "{\"id\":\"chatcmpl-test\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"test-model\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"Checking weather...\"},\"finish_reason\":null}]}"-              , sseFrame-                  "{\"id\":\"chatcmpl-test\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"test-model\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"id\":\"call_1\",\"type\":\"function\",\"function\":{\"name\":\"get_weather\",\"arguments\":\"{\\\"city\\\":\\\"\"}}]},\"finish_reason\":null}]}"-              , sseFrame-                  "{\"id\":\"chatcmpl-test\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"test-model\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"Paris\\\"}\"}}]},\"finish_reason\":\"tool_calls\"}]}"-              , sseFrame-                  "{\"id\":\"chatcmpl-test\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"test-model\",\"choices\":[],\"usage\":{\"prompt_tokens\":7,\"completion_tokens\":5,\"total_tokens\":12}}"-              , sseFrame "[DONE]"-              ]-            expectedToolCall =-              ToolCall-                { toolCallId = "call_1"-                , toolCallType = "function"-                , toolCallName = "get_weather"-                , toolCallArguments = Aeson.object ["city" Aeson..= ("Paris" :: T.Text)]-                }-            expectedUsage = TokenUsage 7 5 12-        result <- collectRawStream frames-        case result of-          Left err -> assertFailure $ "Expected stream success, got: " ++ show err-          Right events -> case events of-            [ LLMStart {}-              , LLMChunk _ "Checking weather..." Nothing-              , LLMChunk _ "" (Just toolCall)-              , LLMEnd _ responseMessage (Just usage)-              ] -> do-                toolCall @?= expectedToolCall-                extractMessageText responseMessage @?= "Checking weather..."-                messageToolCalls responseMessage @?= Just [expectedToolCall]-                usage @?= expectedUsage-            _ -> assertFailure $ "Unexpected stream events: " ++ show events-    , testCase "stream rejects invalid completed tool arguments" $ do-        let frames =-              [ sseFrame-                  "{\"id\":\"chatcmpl-test\",\"object\":\"chat.completion.chunk\",\"created\":0,\"model\":\"test-model\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"id\":\"call_1\",\"type\":\"function\",\"function\":{\"name\":\"get_weather\",\"arguments\":\"not-json\"}}]},\"finish_reason\":\"tool_calls\"}]}"-              , sseFrame "[DONE]"-              ]-        result <- collectRawStream frames-        case result of-          Left _ -> pure ()-          Right events -> assertFailure $ "Expected stream failure, got: " ++ show events-    , testCase "stream closes the SSE connection when the consumer stops after a chunk" $ do-        clientClosed <- newEmptyMVar-        withCancellationAwareProvider (putMVar clientClosed ()) $ \provider -> do-          void . runResourceT . runExceptT . runConduit $-            stream provider [userMessage "Hello"] Nothing .| (await >> await)-          closed <- timeout 500000 $ takeMVar clientClosed-          assertBool "expected the SSE connection to close" $ isJust closed-    ]
− test/Test/Langchain/Provider/TestSseServer.hs
@@ -1,85 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TypeFamilies #-}--module Test.Langchain.Provider.TestSseServer-  ( withTestApplication-  , rawSseServer-  , capturingRawSseServer-  , capturingRawSseRequestServer-  , sseFrame-  , gatedSseServer-  , cancellationAwareSseServer-  , collectModelStream-  ) where--import Control.Concurrent (threadDelay)-import Control.Exception (SomeException, catch)-import Control.Monad.Except (runExceptT)-import Control.Monad.Trans.Resource (runResourceT)-import qualified Data.ByteString.Builder as Builder-import qualified Data.ByteString.Lazy as LBS-import qualified Data.Text as T-import Network.HTTP.Types (hContentType, status200)-import Network.Wai (Application, Request, responseStream, strictRequestBody)-import Network.Wai.Handler.Warp (testWithApplication)--import Langchain.Core.Error (LangchainError)-import Langchain.Core.Model (ChatModel (..), Message)-import Langchain.Core.Stream (StreamEvent, collectEvents)--withTestApplication :: Application -> (T.Text -> IO a) -> IO a-withTestApplication app action =-  testWithApplication (pure app) $ \port ->-    action $ "http://127.0.0.1:" <> T.pack (show port)--rawSseServer :: [LBS.ByteString] -> Application-rawSseServer frames _request respond =-  respond $-    responseStream status200 [(hContentType, "text/event-stream")] $ \write flush ->-      mapM_ (\frame -> write (Builder.lazyByteString frame) >> flush) frames--capturingRawSseServer :: (LBS.ByteString -> IO ()) -> [LBS.ByteString] -> Application-capturingRawSseServer captureRequest frames request respond = do-  captureRequest =<< strictRequestBody request-  rawSseServer frames request respond--capturingRawSseRequestServer ::-  (Request -> LBS.ByteString -> IO ()) -> [LBS.ByteString] -> Application-capturingRawSseRequestServer captureRequest frames request respond = do-  body <- strictRequestBody request-  captureRequest request body-  rawSseServer frames request respond--sseFrame :: LBS.ByteString -> LBS.ByteString-sseFrame payload = "data: " <> payload <> "\n\n"--gatedSseServer :: LBS.ByteString -> IO () -> [LBS.ByteString] -> Application-gatedSseServer firstFrame waitForContinuation remainingFrames _request respond =-  respond $-    responseStream status200 [(hContentType, "text/event-stream")] $ \write flush -> do-      write $ Builder.lazyByteString firstFrame-      flush-      waitForContinuation-      mapM_ (write . Builder.lazyByteString) remainingFrames-      flush--cancellationAwareSseServer :: LBS.ByteString -> IO () -> Application-cancellationAwareSseServer firstFrame signalClientClosed _request respond =-  respond $-    responseStream status200 [(hContentType, "text/event-stream")] $ \write flush -> do-      let keepAlive = do-            write ": keepalive\n\n"-            flush-            threadDelay 1000-            keepAlive-          onDisconnect :: SomeException -> IO ()-          onDisconnect _ = signalClientClosed-      write $ Builder.lazyByteString firstFrame-      flush-      keepAlive `catch` onDisconnect--collectModelStream ::-  ChatModel model =>-  model -> [Message] -> Maybe (ModelConfig model) -> IO (Either LangchainError [StreamEvent])-collectModelStream provider messages config =-  runResourceT $ runExceptT $ collectEvents (stream provider messages config)
test/Test/Langchain/RegressionSpec.hs view
@@ -4,18 +4,14 @@  import Control.Monad.Except (runExceptT) import Control.Monad.Trans.Resource (runResourceT)-import Data.Aeson (decode)-import qualified Data.ByteString.Lazy.Char8 as LBSC import Test.Tasty import Test.Tasty.HUnit  import Langchain.Agent.ReAct-import Langchain.Core.Model-import Langchain.Core.Stream import Langchain.Memory.Core (BaseMemory (..), newWindowBufferMemory) import qualified Langchain.Memory.Core as TB-import Langchain.Provider.OpenAI (parseOpenAIResponse)-import Langchain.Tool.Calculator (calculatorTool)+import Langchain.Model+import Langchain.Stream import Test.Langchain.Provider.Mock (newMockModel)  tests :: TestTree@@ -33,17 +29,9 @@             case last events of               LLMEnd _ finalMsg _ -> extractMessageText finalMsg @?= "Streaming chunk data"               _ -> assertFailure "Expected LLMEnd as last event in stream"-    , testCase "regression_system_fingerprint_nullable: OpenAI JSON parses without fingerprint" $ do-        let jsonWithoutFingerprint =-              "{\"id\":\"cmpl-1\",\"object\":\"chat.completion\",\"created\":1600000000,\"model\":\"gpt-4o\",\"choices\":[{\"index\":0,\"message\":{\"role\":\"assistant\",\"content\":\"OK\"},\"finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":1,\"completion_tokens\":1,\"total_tokens\":2}}"-        case decode (LBSC.pack jsonWithoutFingerprint) of-          Nothing -> assertFailure "Failed to decode JSON value"-          Just val -> case parseOpenAIResponse val of-            Left err -> assertFailure ("OpenAI parsing failed on nullable fingerprint: " ++ err)-            Right (msg, _) -> extractMessageText msg @?= "OK"     , testCase "regression_react_agent_plain_response: Completes immediately when no tool calls" $ do         let mockModel = newMockModel "Direct Answer without tool calls"-            agent = createReActAgent mockModel [calculatorTool]+            agent = defaultReActAgent mockModel []         res <- runExceptT $ runReActAgent agent [userMessage "What is the capital of France?"]         case res of           Left err -> assertFailure ("ReAct agent failed: " ++ show err)
− test/Test/Langchain/Resilience/CircuitBreakerSpec.hs
@@ -1,40 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Test.Langchain.Resilience.CircuitBreakerSpec (tests) where--import Control.Monad.Except (runExceptT, throwError)-import Test.Tasty-import Test.Tasty.HUnit--import Langchain.Core.Error (internalError)-import Langchain.Resilience.CircuitBreaker--tests :: TestTree-tests =-  testGroup-    "Langchain.Resilience.CircuitBreakerSpec"-    [ testCase "CircuitBreaker starts in Closed state and passes successful requests" $ do-        cb <- newCircuitBreaker "test-cb" defaultCircuitConfig-        st <- getCircuitState cb-        st @?= CircuitClosed-        res <- runExceptT $ withCircuitBreaker cb (pure ("ok" :: String))-        res @?= Right "ok"-    , testCase "CircuitBreaker transitions to Open after exceeding failure threshold" $ do-        let cfg = CircuitBreakerConfig {failureThreshold = 2, resetTimeoutSec = 0.1}-        cb <- newCircuitBreaker "failing-cb" cfg-        -- First failure-        _ <- runExceptT $ withCircuitBreaker cb (throwError (internalError "fail 1" Nothing Nothing))-        st1 <- getCircuitState cb-        st1 @?= CircuitClosed-        -- Second failure -> should open-        _ <- runExceptT $ withCircuitBreaker cb (throwError (internalError "fail 2" Nothing Nothing))-        st2 <- getCircuitState cb-        case st2 of-          CircuitOpen _ -> pure ()-          _ -> assertFailure "Expected CircuitOpen state"-        -- Third request fast-fails without executing action-        resFastFail <- runExceptT $ withCircuitBreaker cb (pure ("should not execute" :: String))-        case resFastFail of-          Left _ -> pure ()-          Right _ -> assertFailure "Expected circuit breaker fast-fail error"-    ]
− test/Test/Langchain/Resilience/RetrySpec.hs
@@ -1,35 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Test.Langchain.Resilience.RetrySpec (tests) where--import Control.Concurrent.STM-import Control.Monad.Except (runExceptT, throwError)-import Control.Monad.IO.Class (liftIO)-import Test.Tasty-import Test.Tasty.HUnit--import Langchain.Core.Error (internalError)-import Langchain.Resilience.Retry--tests :: TestTree-tests =-  testGroup-    "Langchain.Resilience.RetrySpec"-    [ testCase "withRetry succeeds after failing attempts" $ do-        attemptVar <- newTVarIO (0 :: Int)-        let policy = defaultRetryPolicy {maxRetries = 3, baseDelayMicros = 1000, useJitter = False}-            action = do-              curr <- liftIO $ atomically $ do-                c <- readTVar attemptVar-                writeTVar attemptVar (c + 1)-                pure c-              if curr < 2-                then throwError $ internalError "Temporary failure" Nothing Nothing-                else pure ("Success on attempt " ++ show (curr + 1))-        res <- runExceptT $ withRetry policy action-        res @?= Right "Success on attempt 3"-    , testCase "RateLimiter consumes tokens and executes action" $ do-        limiter <- newRateLimiter 5.0 5.0-        res <- withRateLimit limiter (pure (42 :: Int))-        res @?= 42-    ]
test/Test/Langchain/Retriever/BM25Spec.hs view
@@ -15,28 +15,7 @@ tests =   testGroup     "Langchain.Retriever.BM25"-    [ testCase "BM25 finds exact matching document" $ do-        let doc1 =-              Document-                { pageContent = "Haskell is a functional programming language with strong static types."-                , metadata = Map.empty-                }-            doc2 =-              Document-                { pageContent = "Python is a dynamic language used for machine learning and web scripts."-                , metadata = Map.empty-                }-            doc3 =-              Document-                { pageContent = "Rust guarantees memory safety without garbage collection."-                , metadata = Map.empty-                }-            index = newBM25Index [doc1, doc2, doc3]-            results = bm25Search index "functional static types" 2-        case results of-          (topResult : _) -> topResult @?= doc1-          [] -> assertFailure "Expected non-empty search results"-    , testCase "BM25 scoring gives highest score to relevant passage" $ do+    [ testCase "BM25 scoring gives highest score to relevant passage" $ do         let doc1 =               Document                 { pageContent = "Deep research agent explores web pages and validates claims."
− test/Test/Langchain/Retriever/Core.hs
@@ -1,30 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Test.Langchain.Retriever.Core (tests) where--import Control.Monad.Except (runExceptT)-import qualified Data.Map.Strict as HM-import qualified Data.Text.Lazy as TL-import Test.Tasty-import Test.Tasty.HUnit--import Langchain.DocumentLoader.Core (Document (..))-import Langchain.Retriever.Core (Retriever (..))--data DummyRetriever = DummyRetriever-  deriving (Show, Eq)--instance Retriever DummyRetriever where-  getRelevantDocuments _ query =-    pure [Document (TL.fromStrict $ query <> " result") HM.empty]--tests :: TestTree-tests =-  testGroup-    "Retriever Tests"-    [ testCase "DummyRetriever retrieves documents" $ do-        res <- runExceptT $ getRelevantDocuments DummyRetriever "test"-        case res of-          Left err -> assertFailure ("Error: " ++ show err)-          Right docs -> map pageContent docs @?= ["test result"]-    ]
− test/Test/Langchain/Retriever/HybridSpec.hs
@@ -1,40 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Test.Langchain.Retriever.HybridSpec (tests) where--import qualified Data.Map.Strict as Map-import Test.Tasty-import Test.Tasty.HUnit--import Langchain.DocumentLoader.Core (Document (..))-import Langchain.Retriever.BM25 (newBM25Index)-import Langchain.Retriever.Hybrid--tests :: TestTree-tests =-  testGroup-    "Langchain.Retriever.Hybrid"-    [ testCase "reciprocalRankFusion prioritizes documents appearing in both lists" $ do-        let docA = Document {pageContent = "Document A about quantum algorithms", metadata = Map.empty}-            docB = Document {pageContent = "Document B about classical physics", metadata = Map.empty}-            docC = Document {pageContent = "Document C about neural networks", metadata = Map.empty}-            denseList = [docA, docB]-            sparseList = [docC, docA]-            fused = reciprocalRankFusion 60.0 [(denseList, 1.0), (sparseList, 1.0)]-        -- docA appears in both dense (rank 1) and sparse (rank 2) -> highest combined score-        length fused @?= 3-        case fused of-          ((topDoc, _) : _) -> topDoc @?= docA-          [] -> assertFailure "Expected non-empty fused results"-    , testCase "searchHybrid executes dense and sparse searches" $ do-        let doc1 = Document {pageContent = "Haskell state monad and effects", metadata = Map.empty}-            doc2 = Document {pageContent = "Rust borrow checker and lifetimes", metadata = Map.empty}-            bm25 = newBM25Index [doc1, doc2]-            mockVecSearch _ _ = pure [doc2, doc1]-            hybrid = newHybridRetriever bm25 mockVecSearch-        results <- searchHybrid hybrid "Haskell" 2-        length results @?= 2-        case results of-          (topDoc : _) -> topDoc @?= doc1-          [] -> assertFailure "Expected non-empty results"-    ]
− test/Test/Langchain/TestHelpers.hs
@@ -1,225 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-}--{- |-Module      : Test.Langchain.TestHelpers-Description : Test helpers, environment filtering, and provider selection utilities-Copyright   : (c) 2025-2026 Tushar Adhatrao-License     : MIT-Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability   : experimental--Provides smart model selection: uses OpenRouter when API key is present,-and falls back to a local Ollama instance otherwise.--}-module Test.Langchain.TestHelpers-  ( -- * Provider selection-    withAnyModel-  , withOpenRouterOrOllama--    -- * OpenRouter helpers-  , getOpenRouterApiKey-  , newTestOpenRouter-  , defaultOpenRouterModel-  , defaultOpenRouterEndpoint--    -- * Ollama helpers-  , isOllamaInstalled-  , isOllamaRunning-  , isModelAvailable-  , newTestOllama-  , withOllamaModel--    -- * Shared defaults-  , defaultTestModel-  , defaultEmbedModel-  , ollamaModelName--    -- * TestLevel-  , TestLevel (..)-  ) where--import Control.Exception (SomeException, try)-import Control.Monad.IO.Class (MonadIO)-import Data.Aeson (Value, decode)-import Data.Maybe (isJust)-import Data.Text (Text)-import qualified Data.Text as T-import System.Directory (findExecutable)--import Langchain.Provider.Ollama (Ollama, configTimeout, defaultConfig, newOllama)-import Langchain.Provider.OpenAI (OpenAI, openAICompatible)-import Network.HTTP.Simple-  ( getResponseBody-  , getResponseStatusCode-  , httpLBS-  , parseRequest_-  , setRequestCheckStatus-  )-import System.Environment (lookupEnv)---- ------------------------------------------------------------------------------ Types--- ------------------------------------------------------------------------------- | Test categorization levels configured via LANGCHAIN_TEST_LEVEL environment variable-data TestLevel-  = UnitLevel-  | PropertyLevel-  | IntegrationLevel-  | E2ELevel-  deriving (Eq, Ord, Show, Read)---- ------------------------------------------------------------------------------ Shared defaults--- ------------------------------------------------------------------------------- | Default Ollama model for integration tests-defaultTestModel :: Text-defaultTestModel = "qwen3.5:2b"---- | Fallback Ollama model-ollamaModelName :: Text-ollamaModelName = "gemma3:latest"---- | Default embedding model (Ollama)-defaultEmbedModel :: Text-defaultEmbedModel = "nomic-embed-text"---- ------------------------------------------------------------------------------ OpenRouter helpers--- ------------------------------------------------------------------------------- | Default OpenRouter model for integration tests-defaultOpenRouterModel :: Text-defaultOpenRouterModel = "nex-agi/nex-n2.5-mini:free"---- | Default OpenRouter base URL-defaultOpenRouterEndpoint :: Text-defaultOpenRouterEndpoint = "https://openrouter.ai/api"---- | Read OpenRouter API key from the @OPEN_ROUTER_API_KEY@ environment variable.-getOpenRouterApiKey :: IO (Maybe Text)-getOpenRouterApiKey = do-  mv <- lookupEnv "OPEN_ROUTER_API_KEY"-  case mv of-    Just v | not (T.null (T.strip (T.pack v))) -> pure $ Just (T.strip (T.pack v))-    _ -> pure Nothing---- | Build an 'OpenAI' provider pointing at OpenRouter with @openrouter/free@.-newTestOpenRouter :: Text -> OpenAI-newTestOpenRouter apiKey =-  openAICompatible apiKey defaultOpenRouterModel defaultOpenRouterEndpoint---- ------------------------------------------------------------------------------ Ollama helpers--- ------------------------------------------------------------------------------- | Check if the Ollama CLI executable is installed on the system PATH-isOllamaInstalled :: IO Bool-isOllamaInstalled = isJust <$> findExecutable "ollama"---- | Check if Ollama daemon is running on localhost:11434-isOllamaRunning :: IO Bool-isOllamaRunning = do-  eRes <- try (httpLBS $ setRequestCheckStatus $ parseRequest_ "GET http://localhost:11434/api/tags")-  case eRes of-    Left (_ :: SomeException) -> pure False-    Right res -> pure (getResponseStatusCode res == 200)---- | Check if a specific model tag is available in local Ollama-isModelAvailable :: Text -> IO Bool-isModelAvailable targetModel = do-  eRes <- try (httpLBS $ setRequestCheckStatus $ parseRequest_ "GET http://localhost:11434/api/tags")-  case eRes of-    Left (_ :: SomeException) -> pure False-    Right res -> do-      let body = getResponseBody res-      case decode body :: Maybe Value of-        Nothing -> pure False-        Just _ -> pure $ T.isInfixOf targetModel (T.pack $ show body)---- | Execute an action with an Ollama model if available, otherwise skip cleanly.-withOllamaModel :: Text -> (Text -> IO ()) -> IO ()-withOllamaModel preferredModel action = do-  running <- isOllamaRunning-  if not running-    then do-      installed <- isOllamaInstalled-      if not installed-        then putStrLn " [SKIPPED] Ollama is not installed"-        else putStrLn " [SKIPPED] Ollama daemon is not running on http://localhost:11434"-    else do-      hasPref <- isModelAvailable preferredModel-      if hasPref-        then action preferredModel-        else do-          hasDef <- isModelAvailable defaultTestModel-          if hasDef-            then action defaultTestModel-            else do-              hasFallback <- isModelAvailable ollamaModelName-              if hasFallback-                then action ollamaModelName-                else-                  putStrLn $-                    " [SKIPPED] Neither "-                      ++ T.unpack preferredModel-                      ++ ", "-                      ++ T.unpack defaultTestModel-                      ++ ", nor "-                      ++ T.unpack ollamaModelName-                      ++ " is available in Ollama."---- | Build an Ollama provider with a generous timeout.-newTestOllama :: MonadIO m => Text -> m Ollama-newTestOllama modelName =-  newOllama-    modelName-    defaultConfig-      { configTimeout = 600-      }---- ------------------------------------------------------------------------------ Combined provider selection--- -----------------------------------------------------------------------------{- | Run @openRouterAction@ if an OpenRouter API key is available,-  otherwise fall back to @ollamaAction@.--}-withOpenRouterOrOllama ::-  -- | Action when neither OpenRouter key nor Ollama is available (e.g. skip)-  IO () ->-  -- | Action given an OpenRouter 'OpenAI' provider-  (OpenAI -> IO ()) ->-  -- | Action given an 'Ollama' provider-  (Ollama -> IO ()) ->-  IO ()-withOpenRouterOrOllama onMissing openRouterAction ollamaAction = do-  mbKey <- getOpenRouterApiKey-  case mbKey of-    Just key -> openRouterAction (newTestOpenRouter key)-    Nothing -> do-      running <- isOllamaRunning-      if running-        then withOllamaModel defaultTestModel (\mName -> do o <- newTestOllama mName; ollamaAction o)-        else onMissing--{- | Run a test action with OpenRouter (OpenAI-compatible) when an API key is-  present in the @OPEN_ROUTER_API_KEY@ environment variable,-  otherwise fall back to Ollama.--}-withAnyModel ::-  -- | Action when OpenRouter key is available-  (OpenAI -> IO ()) ->-  -- | Action when falling back to Ollama-  (Ollama -> IO ()) ->-  IO ()-withAnyModel =-  withOpenRouterOrOllama-    ( do-        installed <- isOllamaInstalled-        if not installed-          then putStrLn " [SKIPPED] No OpenRouter key and Ollama is not installed — skipping test"-          else putStrLn " [SKIPPED] No OpenRouter key and no Ollama daemon — skipping test"-    )
test/Test/Langchain/Tool/AdvancedToolsSpec.hs view
@@ -4,17 +4,13 @@  module Test.Langchain.Tool.AdvancedToolsSpec (tests) where -import Control.Concurrent.Async (wait)-import Control.Monad.Except (runExceptT)-import Data.Aeson (FromJSON, ToJSON, Value (..), object)+import Data.Aeson (FromJSON, ToJSON, Value (..)) import Data.Proxy (Proxy (..)) import Data.Text (Text) import GHC.Generics (Generic) import Test.Tasty import Test.Tasty.HUnit -import Langchain.Tool.Async-import Langchain.Tool.Core (createTool) import Langchain.Tool.GenericSchema  data SearchArgs = SearchArgs@@ -33,23 +29,4 @@         case schemaVal of           Object o -> assertBool "Schema contains type or properties" (not $ null o)           _ -> assertFailure "Expected Object schema"-    , testCase "executeToolAsync runs tool in background thread" $ do-        let sampleTool =-              createTool-                "async_sample"-                "Async test"-                (object [])-                (\_ -> pure $ Right "Completed async")-        asyncHandle <- executeToolAsync sampleTool (object [])-        res <- wait asyncHandle-        res @?= Right "Completed async"-    , testCase "executeToolBatchConcurrently runs multiple tool calls concurrently" $ do-        let sampleTool =-              createTool-                "batch_sample"-                "Batch test"-                (object [])-                (\_ -> pure $ Right "Batch OK")-        res <- runExceptT $ executeToolBatchConcurrently [(sampleTool, object []), (sampleTool, object [])]-        res @?= Right ["Batch OK", "Batch OK"]     ]
− test/Test/Langchain/Tool/Calculator.hs
@@ -1,22 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Test.Langchain.Tool.Calculator (tests) where--import Data.Aeson (object, (.=))-import Data.Text (Text)-import Langchain.Core.Tool (toolExecute)-import Langchain.Tool.Calculator-import Test.Tasty-import Test.Tasty.HUnit--tests :: TestTree-tests =-  testGroup-    "Langchain.Tool.Calculator"-    [ testCase "calculatorTool evaluates expression via Tool interface" $ do-        res <- toolExecute calculatorTool (object ["expression" .= ("2 + 2" :: Text)])-        res @?= Right "4.0"-    , testCase "calculatorTool handles multiplication" $ do-        res <- toolExecute calculatorTool (object ["expression" .= ("3 * 4" :: Text)])-        res @?= Right "12.0"-    ]
test/Test/Langchain/Tool/FileSystem.hs view
@@ -5,8 +5,12 @@ import Data.Aeson (object, (.=)) import Data.Text (Text) import qualified Data.Text as T-import Langchain.Core.Tool (toolExecute)+import qualified Data.Text.IO as TIO+import Langchain.Tool (toolExecute)+import Langchain.Tool.Checkpoint (newCheckpointRegistry, undoFileEditTool) import Langchain.Tool.FileSystem+import Langchain.Tool.Security (defaultSecurityPolicy)+import System.Directory (createDirectoryIfMissing) import System.FilePath ((</>)) import System.IO.Temp (withSystemTempDirectory) import Test.Tasty@@ -16,20 +20,149 @@ tests =   testGroup     "Langchain.Tool.FileSystem"-    [ testCase "writeFileTool and readFileTool perform I/O correctly" $ do+    [ testCase "writeFileTool and readFileTool perform paginated I/O correctly" $ do         withSystemTempDirectory "tool-test" $ \dir -> do-          let filePath = T.pack (dir </> "test.txt")-              content = "Hello, langchain-hs!"-          wRes <- toolExecute writeFileTool (object ["path" .= filePath, "content" .= content])+          reg <- newCheckpointRegistry+          let policy = defaultSecurityPolicy+              filePath = T.pack (dir </> "test.txt")+              content = "Line 1\nLine 2\nLine 3\nLine 4" :: Text++          -- Write initial file+          wRes <-+            toolExecute (writeFileTool (Just reg) policy) (object ["path" .= filePath, "content" .= content])           assertBool "Write should succeed" (case wRes of Right _ -> True; _ -> False) -          rRes <- toolExecute readFileTool (object ["path" .= filePath])-          rRes @?= Right content+          -- Read with pagination (lines 2 to 3)+          rRes <-+            toolExecute+              (readFileTool (Just reg) policy)+              (object ["path" .= filePath, "start_line" .= (2 :: Int), "end_line" .= (3 :: Int)])+          case rRes of+            Left err -> assertFailure $ "Failed reading paginated lines: " ++ show err+            Right txt -> do+              assertBool "Contains Line 2" ("2: Line 2" `T.isInfixOf` txt)+              assertBool "Contains Line 3" ("3: Line 3" `T.isInfixOf` txt)+              assertBool "Does not contain Line 1" (not $ "1: Line 1" `T.isInfixOf` txt)+    , testCase "writeFileTool prevents accidental overwrite without overwrite=true" $ do+        withSystemTempDirectory "tool-test" $ \dir -> do+          reg <- newCheckpointRegistry+          let policy = defaultSecurityPolicy+              filePath = T.pack (dir </> "immutable.txt")++          _ <-+            toolExecute+              (writeFileTool (Just reg) policy)+              (object ["path" .= filePath, "content" .= ("initial" :: Text)])+          wRes2 <-+            toolExecute+              (writeFileTool (Just reg) policy)+              (object ["path" .= filePath, "content" .= ("new" :: Text)])+          case wRes2 of+            Left _ -> pure () -- Expect failure+            Right _ -> assertFailure "Expected failure when overwriting without overwrite=true"+    , testCase "editFileTool enforces read-before-edit and unique replacement with undo" $ do+        withSystemTempDirectory "tool-test" $ \dir -> do+          reg <- newCheckpointRegistry+          let policy = defaultSecurityPolicy+              fpStr = dir </> "code.hs"+              filePath = T.pack fpStr+              initialCode = "module Main where\n\nval = 10\n"++          -- Write directly via TIO so the registry hasn't tracked a read yet+          TIO.writeFile fpStr initialCode++          -- Attempt edit before reading -> Should fail read-before-edit+          editRes1 <-+            toolExecute+              (editFileTool (Just reg) policy)+              ( object+                  ["path" .= filePath, "old_string" .= ("val = 10" :: Text), "new_string" .= ("val = 42" :: Text)]+              )+          case editRes1 of+            Left _ -> pure () -- Read-before-edit enforced+            Right _ -> assertFailure "Expected edit_file to fail when file has not been read yet"++          -- Inspect file using read_file+          _ <- toolExecute (readFileTool (Just reg) policy) (object ["path" .= filePath])++          -- Now edit should succeed+          editRes2 <-+            toolExecute+              (editFileTool (Just reg) policy)+              ( object+                  ["path" .= filePath, "old_string" .= ("val = 10" :: Text), "new_string" .= ("val = 42" :: Text)]+              )+          case editRes2 of+            Left err -> assertFailure $ "Edit failed after reading: " ++ show err+            Right msg -> assertBool "Check message" ("Successfully edited" `T.isInfixOf` msg)++          -- Verify content changed+          newContent <- TIO.readFile fpStr+          assertBool "Value updated" ("val = 42" `T.isInfixOf` newContent)++          -- Test undo checkpoint+          undoRes <- toolExecute (undoFileEditTool reg) (object ["path" .= filePath])+          case undoRes of+            Left err -> assertFailure $ "Undo failed: " ++ show err+            Right _ -> do+              revertedContent <- TIO.readFile fpStr+              assertBool "Reverted back" ("val = 10" `T.isInfixOf` revertedContent)+    , testCase "findFilesTool finds files by glob and obeys max_depth" $ do+        withSystemTempDirectory "tool-test" $ \dir -> do+          let policy = defaultSecurityPolicy+              subDir = dir </> "nested" </> "deep"+          createDirectoryIfMissing True subDir+          writeFile (dir </> "app.hs") "main = pure ()"+          writeFile (subDir </> "lib.hs") "module Lib where"+          writeFile (dir </> "README.md") "# Title"++          -- Search for *.hs with depth 0+          resDepth0 <-+            toolExecute+              (findFilesTool policy)+              (object ["path" .= T.pack dir, "pattern" .= ("*.hs" :: Text), "max_depth" .= (0 :: Int)])+          case resDepth0 of+            Left err -> assertFailure $ "Find failed: " ++ show err+            Right txt -> do+              assertBool "Found root app.hs" ("app.hs" `T.isInfixOf` txt)+              assertBool "Did not find deep lib.hs at depth 0" (not $ "lib.hs" `T.isInfixOf` txt)++          -- Search for *.hs with depth 3+          resDepth3 <-+            toolExecute+              (findFilesTool policy)+              (object ["path" .= T.pack dir, "pattern" .= ("*.hs" :: Text), "max_depth" .= (3 :: Int)])+          case resDepth3 of+            Left err -> assertFailure $ "Find failed: " ++ show err+            Right txt -> do+              assertBool "Found root app.hs" ("app.hs" `T.isInfixOf` txt)+              assertBool "Found nested lib.hs" ("lib.hs" `T.isInfixOf` txt)+              assertBool "Excluded README.md" (not $ "README.md" `T.isInfixOf` txt)+    , testCase "grepSearchTool finds content with line numbers" $ do+        withSystemTempDirectory "tool-test" $ \dir -> do+          let policy = defaultSecurityPolicy+          writeFile (dir </> "a.txt") "apple\nbanana\ncherry"+          writeFile (dir </> "b.txt") "date\nelderberry\nfig\napple pie"++          res <-+            toolExecute+              (grepSearchTool policy)+              (object ["path" .= T.pack dir, "query" .= ("apple" :: Text)])+          case res of+            Left err -> assertFailure $ "Grep failed: " ++ show err+            Right txt -> do+              assertBool "Contains match header" ("Found 2 matches" `T.isInfixOf` txt)+              assertBool "Contains line number" (":1:" `T.isInfixOf` txt || ":4:" `T.isInfixOf` txt)     , testCase "listDirTool lists created files" $ do         withSystemTempDirectory "tool-test" $ \dir -> do-          let filePath = T.pack (dir </> "sample.txt")-          _ <- toolExecute writeFileTool (object ["path" .= filePath, "content" .= ("content" :: Text)])-          lRes <- toolExecute listDirTool (object ["path" .= T.pack dir])+          reg <- newCheckpointRegistry+          let policy = defaultSecurityPolicy+              filePath = T.pack (dir </> "sample.txt")+          _ <-+            toolExecute+              (writeFileTool (Just reg) policy)+              (object ["path" .= filePath, "content" .= ("content" :: Text)])+          lRes <- toolExecute (listDirTool policy) (object ["path" .= T.pack dir])           case lRes of             Left err -> assertFailure $ "Unexpected error: " ++ show err             Right filesTxt -> assertBool "Should contain sample.txt" ("sample.txt" `T.isInfixOf` filesTxt)
+ test/Test/Langchain/Tool/ProcessSpec.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE OverloadedStrings #-}++module Test.Langchain.Tool.ProcessSpec (tests) where++import Data.Aeson (object, (.=))+import Data.Text (Text)+import qualified Data.Text as T+import Langchain.Tool (toolExecute)+import Langchain.Tool.Process+  ( commandKillTool+  , commandRunTool+  , commandStatusTool+  , newProcessManager+  )+import Langchain.Tool.Security (defaultSecurityPolicy)+import Test.Tasty+import Test.Tasty.HUnit++tests :: TestTree+tests =+  testGroup+    "Langchain.Tool.Process"+    [ testCase "commandRunTool executes short command synchronously" $ do+        pm <- newProcessManager+        let policy = defaultSecurityPolicy+        res <-+          toolExecute+            (commandRunTool pm policy)+            (object ["command" .= ("echo 'Hello from Process!'" :: Text), "timeout_ms" .= (2000 :: Int)])+        case res of+          Left err -> assertFailure $ "Command failed: " ++ show err+          Right txt -> do+            assertBool "Contains command output" ("Hello from Process!" `T.isInfixOf` txt)+            assertBool "Indicates exit code 0" ("exit code 0" `T.isInfixOf` txt)+    , testCase "commandRunTool transitions long command to background after timeout" $ do+        pm <- newProcessManager+        let policy = defaultSecurityPolicy+        -- Run sleep 2 with 200ms timeout+        res <-+          toolExecute+            (commandRunTool pm policy)+            (object ["command" .= ("sleep 2" :: Text), "timeout_ms" .= (200 :: Int)])+        case res of+          Left err -> assertFailure $ "Command launch failed: " ++ show err+          Right txt -> do+            assertBool+              "Mentions background execution"+              ("background" `T.isInfixOf` txt || "proc-" `T.isInfixOf` txt)++            -- Check status using commandStatusTool+            sRes <-+              toolExecute+                (commandStatusTool pm policy)+                (object ["process_id" .= ("proc-1" :: Text)])+            case sRes of+              Left err -> assertFailure $ "Status check failed: " ++ show err+              Right sTxt -> assertBool "Has process status output" ("proc-1" `T.isInfixOf` sTxt)++            -- Kill background process+            kRes <-+              toolExecute+                (commandKillTool pm policy)+                (object ["process_id" .= ("proc-1" :: Text)])+            case kRes of+              Left err -> assertFailure $ "Kill failed: " ++ show err+              Right kTxt -> assertBool "Terminated successfully" ("terminated" `T.isInfixOf` T.toLower kTxt)+    ]
+ test/Test/Langchain/Tool/SecuritySpec.hs view
@@ -0,0 +1,62 @@+{-# LANGUAGE OverloadedStrings #-}++module Test.Langchain.Tool.SecuritySpec (tests) where++import Data.Aeson (object)+import qualified Data.Text as T+import Langchain.Tool (createTool, toolExecute)+import Langchain.Tool.Security+  ( ToolTier (..)+  , defaultSecurityPolicy+  , readOnlySecurityPolicy+  , validateCommandSafety+  , validateSandboxPath+  , withSecurity+  , workspaceSandboxPolicy+  )+import System.FilePath ((</>))+import System.IO.Temp (withSystemTempDirectory)+import Test.Tasty+import Test.Tasty.HUnit++tests :: TestTree+tests =+  testGroup+    "Langchain.Tool.Security"+    [ testCase "validateSandboxPath prevents path traversal outside sandbox" $ do+        withSystemTempDirectory "sandbox-test" $ \dir -> do+          let policy = workspaceSandboxPolicy dir+          -- Valid path within sandbox+          eValid <- validateSandboxPath policy (dir </> "file.txt")+          case eValid of+            Left err -> assertFailure $ "Expected valid path: " ++ show err+            Right p -> assertBool "Path cleaned" (T.pack dir `T.isPrefixOf` T.pack p)++          -- Invalid path outside sandbox+          eInvalid <- validateSandboxPath policy (dir </> ".." </> "outside.txt")+          case eInvalid of+            Left _ -> pure ()+            Right p -> assertFailure $ "Expected path traversal to be blocked: " ++ p+    , testCase "validateCommandSafety blocks destructive and fork-bomb commands" $ do+        let policy = defaultSecurityPolicy+        case validateCommandSafety policy "rm -rf /" of+          Left _ -> pure ()+          Right () -> assertFailure "Expected 'rm -rf /' to be blocked"++        case validateCommandSafety policy ":(){ :|:& };:" of+          Left _ -> pure ()+          Right () -> assertFailure "Expected fork bomb to be blocked"++        case validateCommandSafety policy "ls -la src" of+          Left err -> assertFailure $ "Safe command should not be blocked: " ++ show err+          Right () -> pure ()+    , testCase "withSecurity enforces tier restrictions (read-only blocks writes)" $ do+        let readOnlyPolicy = readOnlySecurityPolicy+            dummyWriteTool =+              withSecurity TierWorkspaceWrite readOnlyPolicy $+                createTool "dummy_write" "Dummy write" (object []) (\_ -> pure $ Right "written")+        res <- toolExecute dummyWriteTool (object [])+        case res of+          Left _ -> pure () -- Blocked+          Right _ -> assertFailure "Expected write tool to be blocked by read-only policy"+    ]
test/Test/Langchain/Tool/Shell.hs view
@@ -5,7 +5,7 @@ import Data.Aeson (object, (.=)) import Data.Text (Text) import qualified Data.Text as T-import Langchain.Core.Tool (toolExecute)+import Langchain.Tool (toolExecute) import Langchain.Tool.Shell (shellTool) import Test.Tasty import Test.Tasty.HUnit
− test/Test/Langchain/VectorStore/SqliteVecSpec.hs
@@ -1,55 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedStrings #-}--module Test.Langchain.VectorStore.SqliteVecSpec (tests) where--import Control.Monad.Except (runExceptT)-import qualified Data.Map.Strict as Map-import Data.Text (Text)-import qualified Data.Text as T-import qualified Data.Text.Lazy as TL-import System.FilePath ((</>))-import System.IO.Temp (withSystemTempDirectory)-import Test.Tasty-import Test.Tasty.HUnit--import Langchain.DocumentLoader.Core (Document (..))-import Langchain.Embeddings.Core (Embeddings (..))-import Langchain.VectorStore.Core (VectorStore (..))-import Langchain.VectorStore.SqliteVec--data DeterministicMockEmbeddings = DeterministicMockEmbeddings--instance Embeddings DeterministicMockEmbeddings where-  embedDocuments _ docs = pure $ map (mockEmbed . TL.toStrict . pageContent) docs-  embedQuery _ q = pure $ mockEmbed q--mockEmbed :: Text -> [Float]-mockEmbed t =-  let len = fromIntegral (T.length t) :: Float-      isHaskell = if "Haskell" `T.isInfixOf` t then 1.0 else 0.0-   in [isHaskell, len / 100.0, 0.5]--tests :: TestTree-tests =-  testGroup-    "Langchain.VectorStore.SqliteVecSpec"-    [ testCase "SqliteVecStore adds documents and performs similarity search" $ do-        withSystemTempDirectory "sqlite-vec-test" $ \tmpDir -> do-          let dbPath = tmpDir </> "vectors.db"-              emb = DeterministicMockEmbeddings-          res <- runExceptT $ do-            store <- newSqliteVecStore dbPath emb-            let doc1 = Document "Haskell is purely functional" Map.empty-                doc2 = Document "Python is dynamically typed" Map.empty-            _ <- addDocuments store [doc1, doc2]-            similaritySearch store "Haskell programming" 1-          case res of-            Left err -> assertFailure ("SqliteVecStore failed: " ++ show err)-            Right [topDoc] ->-              pageContent topDoc @?= "Haskell is purely functional"-            Right docs ->-              assertFailure ("Expected exactly 1 document, got: " ++ show (length docs))-    ]