langchain-hs-core 0.0.5.0 → 0.0.6.0
raw patch · 89 files changed
+9319/−1237 lines, 89 filesdep +directorydep +filepathdep +format-heavydep −deepseqdep ~resourcetPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: directory, filepath, format-heavy, process, random, scientific, sqlite-simple
Dependencies removed: deepseq
Dependency ranges changed: resourcet
API changes (from Hackage documentation)
- Langchain.Core.Error: AgentError :: Text -> Maybe ErrorContext -> LangchainError
- Langchain.Core.Error: ConfigurationError :: Text -> Maybe ErrorContext -> LangchainError
- Langchain.Core.Error: DocumentLoaderError :: Text -> Maybe ErrorContext -> LangchainError
- Langchain.Core.Error: EmbeddingError :: Text -> Maybe ErrorContext -> LangchainError
- Langchain.Core.Error: ErrorContext :: Text -> Text -> UTCTime -> Map Text Text -> ErrorContext
- Langchain.Core.Error: InternalError :: Text -> Maybe ErrorContext -> LangchainError
- Langchain.Core.Error: LLMError :: Text -> Maybe ErrorContext -> LangchainError
- Langchain.Core.Error: MemoryError :: Text -> Maybe ErrorContext -> LangchainError
- Langchain.Core.Error: NetworkError :: Text -> Maybe ErrorContext -> LangchainError
- Langchain.Core.Error: ParsingError :: Text -> Maybe ErrorContext -> LangchainError
- Langchain.Core.Error: RunnableError :: Text -> Maybe ErrorContext -> LangchainError
- Langchain.Core.Error: ToolError :: Text -> Maybe ErrorContext -> LangchainError
- Langchain.Core.Error: ValidationError :: Text -> Maybe ErrorContext -> LangchainError
- Langchain.Core.Error: VectorStoreError :: Text -> Maybe ErrorContext -> LangchainError
- Langchain.Core.Error: [component] :: ErrorContext -> Text
- Langchain.Core.Error: [details] :: ErrorContext -> Map Text Text
- Langchain.Core.Error: [operation] :: ErrorContext -> Text
- Langchain.Core.Error: [timestamp] :: ErrorContext -> UTCTime
- Langchain.Core.Error: agentError :: Text -> Maybe Text -> Maybe Text -> LangchainError
- Langchain.Core.Error: configurationError :: Text -> Maybe Text -> Maybe Text -> LangchainError
- Langchain.Core.Error: data ErrorContext
- Langchain.Core.Error: data LangchainError
- Langchain.Core.Error: documentLoaderError :: Text -> Maybe Text -> Maybe Text -> LangchainError
- Langchain.Core.Error: embeddingError :: Text -> Maybe Text -> Maybe Text -> LangchainError
- Langchain.Core.Error: errorMessage :: LangchainError -> Text
- Langchain.Core.Error: instance Control.DeepSeq.NFData Langchain.Core.Error.ErrorContext
- Langchain.Core.Error: instance Control.DeepSeq.NFData Langchain.Core.Error.LangchainError
- Langchain.Core.Error: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Core.Error.ErrorContext
- Langchain.Core.Error: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Core.Error.LangchainError
- Langchain.Core.Error: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Core.Error.ErrorContext
- Langchain.Core.Error: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Core.Error.LangchainError
- Langchain.Core.Error: instance GHC.Classes.Eq Langchain.Core.Error.ErrorContext
- Langchain.Core.Error: instance GHC.Classes.Eq Langchain.Core.Error.LangchainError
- Langchain.Core.Error: instance GHC.Exception.Type.Exception Langchain.Core.Error.LangchainError
- Langchain.Core.Error: instance GHC.Generics.Generic Langchain.Core.Error.ErrorContext
- Langchain.Core.Error: instance GHC.Generics.Generic Langchain.Core.Error.LangchainError
- Langchain.Core.Error: instance GHC.Show.Show Langchain.Core.Error.ErrorContext
- Langchain.Core.Error: instance GHC.Show.Show Langchain.Core.Error.LangchainError
- Langchain.Core.Error: internalError :: Text -> Maybe Text -> Maybe Text -> LangchainError
- Langchain.Core.Error: llmError :: Text -> Maybe Text -> Maybe Text -> LangchainError
- Langchain.Core.Error: memoryError :: Text -> Maybe Text -> Maybe Text -> LangchainError
- Langchain.Core.Error: mkContext :: Text -> Text -> Map Text Text -> ErrorContext
- Langchain.Core.Error: mkContextIO :: MonadIO m => Text -> Text -> Map Text Text -> m ErrorContext
- Langchain.Core.Error: networkError :: Text -> Maybe Text -> Maybe Text -> LangchainError
- Langchain.Core.Error: parsingError :: Text -> Maybe Text -> Maybe Text -> LangchainError
- Langchain.Core.Error: runnableError :: Text -> Maybe Text -> Maybe Text -> LangchainError
- Langchain.Core.Error: toolError :: Text -> Maybe Text -> Maybe Text -> LangchainError
- Langchain.Core.Error: type LangchainResult a = Either LangchainError a
- Langchain.Core.Error: validationError :: Text -> Maybe Text -> Maybe Text -> LangchainError
- Langchain.Core.Error: vectorStoreError :: Text -> Maybe Text -> Maybe Text -> LangchainError
- Langchain.Core.Model: batch :: (ChatModel model, MonadIO m, MonadError LangchainError m) => model -> [[Message]] -> Maybe (ModelConfig model) -> m [Message]
- Langchain.Core.Model: class ChatModel model where {
- Langchain.Core.Model: invoke :: (ChatModel model, MonadIO m, MonadError LangchainError m) => model -> [Message] -> Maybe (ModelConfig model) -> m Message
- Langchain.Core.Model: stream :: ChatModel model => model -> [Message] -> Maybe (ModelConfig model) -> ChatStream
- Langchain.Core.Model: type ModelConfig model;
- Langchain.Core.Model: }
- Langchain.Core.Model.Types: Assistant :: Role
- Langchain.Core.Model.Types: AudioBlock :: Text -> Text -> ContentBlock
- Langchain.Core.Model.Types: DataBlock :: ByteString -> ContentBlock
- Langchain.Core.Model.Types: Developer :: Role
- Langchain.Core.Model.Types: Function :: Role
- Langchain.Core.Model.Types: ImageBase64 :: Maybe Text -> Text -> ImageSource
- Langchain.Core.Model.Types: ImageBlock :: ImageContent -> ContentBlock
- Langchain.Core.Model.Types: ImageContent :: ImageSource -> Maybe Text -> Maybe Value -> ImageContent
- Langchain.Core.Model.Types: ImageUrl :: Text -> ImageSource
- Langchain.Core.Model.Types: Message :: Role -> NonEmpty ContentBlock -> Maybe Text -> Maybe [ToolCall] -> Maybe Text -> Map Text Value -> Message
- Langchain.Core.Model.Types: System :: Role
- Langchain.Core.Model.Types: TextBlock :: Text -> ContentBlock
- Langchain.Core.Model.Types: Tool :: Role
- Langchain.Core.Model.Types: ToolCall :: Text -> Text -> Text -> Value -> ToolCall
- Langchain.Core.Model.Types: User :: Role
- Langchain.Core.Model.Types: [blockBase64] :: ContentBlock -> Text
- Langchain.Core.Model.Types: [blockBytes] :: ContentBlock -> ByteString
- Langchain.Core.Model.Types: [blockMimeType] :: ContentBlock -> Text
- Langchain.Core.Model.Types: [blockText] :: ContentBlock -> Text
- Langchain.Core.Model.Types: [imageData] :: ImageSource -> Text
- Langchain.Core.Model.Types: [imageDetail] :: ImageContent -> Maybe Text
- Langchain.Core.Model.Types: [imageMetadata] :: ImageContent -> Maybe Value
- Langchain.Core.Model.Types: [imageMimeType] :: ImageSource -> Maybe Text
- Langchain.Core.Model.Types: [imageSource] :: ImageContent -> ImageSource
- Langchain.Core.Model.Types: [imageUrl] :: ImageSource -> Text
- Langchain.Core.Model.Types: [messageContents] :: Message -> NonEmpty ContentBlock
- Langchain.Core.Model.Types: [messageMetadata] :: Message -> Map Text Value
- Langchain.Core.Model.Types: [messageName] :: Message -> Maybe Text
- Langchain.Core.Model.Types: [messageRole] :: Message -> Role
- Langchain.Core.Model.Types: [messageToolCalls] :: Message -> Maybe [ToolCall]
- Langchain.Core.Model.Types: [messageToolId] :: Message -> Maybe Text
- Langchain.Core.Model.Types: [toolCallArguments] :: ToolCall -> Value
- Langchain.Core.Model.Types: [toolCallId] :: ToolCall -> Text
- Langchain.Core.Model.Types: [toolCallName] :: ToolCall -> Text
- Langchain.Core.Model.Types: [toolCallType] :: ToolCall -> Text
- Langchain.Core.Model.Types: assistantMessage :: Text -> Message
- Langchain.Core.Model.Types: data ContentBlock
- Langchain.Core.Model.Types: data ImageContent
- Langchain.Core.Model.Types: data ImageSource
- Langchain.Core.Model.Types: data Message
- Langchain.Core.Model.Types: data Role
- Langchain.Core.Model.Types: data ToolCall
- Langchain.Core.Model.Types: extractMessageText :: Message -> Text
- Langchain.Core.Model.Types: formatMessageString :: Message -> Text
- Langchain.Core.Model.Types: imageMessage :: Role -> Text -> Text -> Message
- Langchain.Core.Model.Types: instance Control.DeepSeq.NFData Langchain.Core.Model.Types.ContentBlock
- Langchain.Core.Model.Types: instance Control.DeepSeq.NFData Langchain.Core.Model.Types.ImageContent
- Langchain.Core.Model.Types: instance Control.DeepSeq.NFData Langchain.Core.Model.Types.ImageSource
- Langchain.Core.Model.Types: instance Control.DeepSeq.NFData Langchain.Core.Model.Types.Message
- Langchain.Core.Model.Types: instance Control.DeepSeq.NFData Langchain.Core.Model.Types.Role
- Langchain.Core.Model.Types: instance Control.DeepSeq.NFData Langchain.Core.Model.Types.ToolCall
- Langchain.Core.Model.Types: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Core.Model.Types.ContentBlock
- Langchain.Core.Model.Types: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Core.Model.Types.Message
- Langchain.Core.Model.Types: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Core.Model.Types.Role
- Langchain.Core.Model.Types: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Core.Model.Types.ToolCall
- Langchain.Core.Model.Types: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Core.Model.Types.ContentBlock
- Langchain.Core.Model.Types: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Core.Model.Types.Message
- Langchain.Core.Model.Types: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Core.Model.Types.Role
- Langchain.Core.Model.Types: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Core.Model.Types.ToolCall
- Langchain.Core.Model.Types: instance GHC.Classes.Eq Langchain.Core.Model.Types.ContentBlock
- Langchain.Core.Model.Types: instance GHC.Classes.Eq Langchain.Core.Model.Types.ImageContent
- Langchain.Core.Model.Types: instance GHC.Classes.Eq Langchain.Core.Model.Types.ImageSource
- Langchain.Core.Model.Types: instance GHC.Classes.Eq Langchain.Core.Model.Types.Message
- Langchain.Core.Model.Types: instance GHC.Classes.Eq Langchain.Core.Model.Types.Role
- Langchain.Core.Model.Types: instance GHC.Classes.Eq Langchain.Core.Model.Types.ToolCall
- Langchain.Core.Model.Types: instance GHC.Classes.Ord Langchain.Core.Model.Types.Role
- Langchain.Core.Model.Types: instance GHC.Enum.Bounded Langchain.Core.Model.Types.Role
- Langchain.Core.Model.Types: instance GHC.Enum.Enum Langchain.Core.Model.Types.Role
- Langchain.Core.Model.Types: instance GHC.Generics.Generic Langchain.Core.Model.Types.ContentBlock
- Langchain.Core.Model.Types: instance GHC.Generics.Generic Langchain.Core.Model.Types.ImageContent
- Langchain.Core.Model.Types: instance GHC.Generics.Generic Langchain.Core.Model.Types.ImageSource
- Langchain.Core.Model.Types: instance GHC.Generics.Generic Langchain.Core.Model.Types.Message
- Langchain.Core.Model.Types: instance GHC.Generics.Generic Langchain.Core.Model.Types.Role
- Langchain.Core.Model.Types: instance GHC.Generics.Generic Langchain.Core.Model.Types.ToolCall
- Langchain.Core.Model.Types: instance GHC.Show.Show Langchain.Core.Model.Types.ContentBlock
- Langchain.Core.Model.Types: instance GHC.Show.Show Langchain.Core.Model.Types.ImageContent
- Langchain.Core.Model.Types: instance GHC.Show.Show Langchain.Core.Model.Types.ImageSource
- Langchain.Core.Model.Types: instance GHC.Show.Show Langchain.Core.Model.Types.Message
- Langchain.Core.Model.Types: instance GHC.Show.Show Langchain.Core.Model.Types.Role
- Langchain.Core.Model.Types: instance GHC.Show.Show Langchain.Core.Model.Types.ToolCall
- Langchain.Core.Model.Types: roleLabel :: Role -> Text
- Langchain.Core.Model.Types: systemMessage :: Text -> Message
- Langchain.Core.Model.Types: textMessage :: Role -> Text -> Message
- Langchain.Core.Model.Types: toolMessage :: Text -> Message
- Langchain.Core.Model.Types: userMessage :: Text -> Message
- Langchain.Core.Monad: runLangchainT :: r -> LangchainT r m a -> m (Either LangchainError a)
- Langchain.Core.Monad: throwLangchainError :: MonadError LangchainError m => LangchainError -> m a
- Langchain.Core.Monad: type LangchainT r (m :: Type -> Type) = ReaderT r ExceptT LangchainError m
- Langchain.Core.Runnable: (&>&) :: RunnableTree (ExceptT LangchainError IO) a b -> RunnableTree (ExceptT LangchainError IO) a c -> RunnableTree (ExceptT LangchainError IO) a (b, c)
- Langchain.Core.Runnable: (|>>) :: forall (m :: Type -> Type) a b c. RunnableTree m a b -> RunnableTree m b c -> RunnableTree m a c
- Langchain.Core.Runnable: ModelRunnable :: c -> ModelRunnable c
- Langchain.Core.Runnable: TextModelRunnable :: c -> TextModelRunnable c
- Langchain.Core.Runnable: [Branch] :: forall i (m :: Type -> Type) o. (i -> m Bool) -> RunnableTree m i o -> RunnableTree m i o -> RunnableTree m i o
- Langchain.Core.Runnable: [Fallback] :: forall (m :: Type -> Type) i o. RunnableTree m i o -> RunnableTree m i o -> RunnableTree m i o
- Langchain.Core.Runnable: [Id] :: forall (m :: Type -> Type) i. RunnableTree m i i
- Langchain.Core.Runnable: [Lambda] :: forall i (m :: Type -> Type) o. (i -> m (Either LangchainError o)) -> RunnableTree m i o
- Langchain.Core.Runnable: [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.Core.Runnable: [Prim] :: forall r (m :: Type -> Type) i o. (Runnable r m, RunnableInput r ~ i, RunnableOutput r ~ o) => r -> RunnableTree m i o
- Langchain.Core.Runnable: [Seq] :: forall (m :: Type -> Type) i mid o. RunnableTree m i mid -> RunnableTree m mid o -> RunnableTree m i o
- Langchain.Core.Runnable: class Runnable r (m :: Type -> Type) where {
- Langchain.Core.Runnable: data RunnableTree (m :: Type -> Type) i o
- Langchain.Core.Runnable: infixl 1 |>>
- Langchain.Core.Runnable: infixl 2 &>&
- Langchain.Core.Runnable: instance (Langchain.Core.Model.ChatModel c, Control.Monad.IO.Class.MonadIO m) => Langchain.Core.Runnable.Runnable (Langchain.Core.Runnable.ModelRunnable c) m
- Langchain.Core.Runnable: instance (Langchain.Core.Model.ChatModel c, Control.Monad.IO.Class.MonadIO m) => Langchain.Core.Runnable.Runnable (Langchain.Core.Runnable.TextModelRunnable c) m
- Langchain.Core.Runnable: instance GHC.Base.Monad m => Langchain.Core.Runnable.Runnable (Langchain.Core.Tool.Tool m) m
- Langchain.Core.Runnable: instance GHC.Classes.Eq c => GHC.Classes.Eq (Langchain.Core.Runnable.ModelRunnable c)
- Langchain.Core.Runnable: instance GHC.Classes.Eq c => GHC.Classes.Eq (Langchain.Core.Runnable.TextModelRunnable c)
- Langchain.Core.Runnable: instance GHC.Show.Show c => GHC.Show.Show (Langchain.Core.Runnable.ModelRunnable c)
- Langchain.Core.Runnable: instance GHC.Show.Show c => GHC.Show.Show (Langchain.Core.Runnable.TextModelRunnable c)
- Langchain.Core.Runnable: interpret :: (MonadIO m, MonadError LangchainError m) => RunnableTree m i o -> i -> m o
- Langchain.Core.Runnable: invoke :: Runnable r m => r -> RunnableInput r -> m (Either LangchainError (RunnableOutput r))
- Langchain.Core.Runnable: newtype ModelRunnable c
- Langchain.Core.Runnable: newtype TextModelRunnable c
- Langchain.Core.Runnable: runBranch :: (i -> m Bool) -> RunnableTree m i o -> RunnableTree m i o -> RunnableTree m i o
- Langchain.Core.Runnable: runChat :: forall c (m :: Type -> Type). (ChatModel c, MonadIO m) => c -> RunnableTree m Text Text
- Langchain.Core.Runnable: runFallback :: forall (m :: Type -> Type) i o. RunnableTree m i o -> RunnableTree m i o -> RunnableTree m i o
- Langchain.Core.Runnable: runIdent :: forall (m :: Type -> Type) a. RunnableTree m a a
- Langchain.Core.Runnable: runLambda :: (i -> m (Either LangchainError o)) -> RunnableTree m i o
- Langchain.Core.Runnable: runModel :: forall c (m :: Type -> Type). (ChatModel c, MonadIO m) => c -> RunnableTree m [Message] Message
- Langchain.Core.Runnable: runPassthrough :: forall (m :: Type -> Type) a. RunnableTree m a a
- Langchain.Core.Runnable: runPrim :: forall r (m :: Type -> Type) i o. (Runnable r m, RunnableInput r ~ i, RunnableOutput r ~ o) => r -> RunnableTree m i o
- Langchain.Core.Runnable: runPure :: forall (m :: Type -> Type) i o. Monad m => (i -> o) -> RunnableTree m i o
- Langchain.Core.Runnable: type RunnableInput r;
- Langchain.Core.Runnable: type RunnableOutput r;
- Langchain.Core.Runnable: }
- Langchain.Core.Stream: ChainEnd :: Text -> Text -> Value -> StreamEvent
- Langchain.Core.Stream: ChainStart :: Text -> Text -> Value -> StreamEvent
- Langchain.Core.Stream: LLMChunk :: Text -> Text -> Maybe ToolCall -> StreamEvent
- Langchain.Core.Stream: LLMEnd :: Text -> Message -> Maybe TokenUsage -> StreamEvent
- Langchain.Core.Stream: LLMStart :: Text -> Text -> [Message] -> StreamEvent
- Langchain.Core.Stream: NodeEnd :: Text -> Text -> Value -> StreamEvent
- Langchain.Core.Stream: NodeStart :: Text -> Text -> Value -> StreamEvent
- Langchain.Core.Stream: TokenUsage :: Int -> Int -> Int -> TokenUsage
- Langchain.Core.Stream: ToolEnd :: Text -> Text -> Value -> StreamEvent
- Langchain.Core.Stream: ToolErrorEvent :: Text -> Text -> LangchainError -> StreamEvent
- Langchain.Core.Stream: ToolStart :: Text -> Text -> Value -> StreamEvent
- Langchain.Core.Stream: [chainInput] :: StreamEvent -> Value
- Langchain.Core.Stream: [chainName] :: StreamEvent -> Text
- Langchain.Core.Stream: [chainOutput] :: StreamEvent -> Value
- Langchain.Core.Stream: [chunkText] :: StreamEvent -> Text
- Langchain.Core.Stream: [completionTokens] :: TokenUsage -> Int
- Langchain.Core.Stream: [finalMessage] :: StreamEvent -> Message
- Langchain.Core.Stream: [inputMessages] :: StreamEvent -> [Message]
- Langchain.Core.Stream: [modelName] :: StreamEvent -> Text
- Langchain.Core.Stream: [nodeId] :: StreamEvent -> Text
- Langchain.Core.Stream: [nodeState] :: StreamEvent -> Value
- Langchain.Core.Stream: [promptTokens] :: TokenUsage -> Int
- Langchain.Core.Stream: [runId] :: StreamEvent -> Text
- Langchain.Core.Stream: [tokenUsage] :: StreamEvent -> Maybe TokenUsage
- Langchain.Core.Stream: [toolCallDelta] :: StreamEvent -> Maybe ToolCall
- Langchain.Core.Stream: [toolErrorPayload] :: StreamEvent -> LangchainError
- Langchain.Core.Stream: [toolInput] :: StreamEvent -> Value
- Langchain.Core.Stream: [toolName] :: StreamEvent -> Text
- Langchain.Core.Stream: [toolOutput] :: StreamEvent -> Value
- Langchain.Core.Stream: [totalTokens] :: TokenUsage -> Int
- Langchain.Core.Stream: callbackSource :: StreamCallback a -> StreamSource a
- Langchain.Core.Stream: collectEvents :: Monad m => EventStream m -> m [StreamEvent]
- Langchain.Core.Stream: data StreamEvent
- Langchain.Core.Stream: data TokenUsage
- Langchain.Core.Stream: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Core.Stream.StreamEvent
- Langchain.Core.Stream: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Core.Stream.TokenUsage
- Langchain.Core.Stream: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Core.Stream.StreamEvent
- Langchain.Core.Stream: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Core.Stream.TokenUsage
- Langchain.Core.Stream: instance GHC.Classes.Eq Langchain.Core.Stream.StreamEvent
- Langchain.Core.Stream: instance GHC.Classes.Eq Langchain.Core.Stream.TokenUsage
- Langchain.Core.Stream: instance GHC.Generics.Generic Langchain.Core.Stream.StreamEvent
- Langchain.Core.Stream: instance GHC.Generics.Generic Langchain.Core.Stream.TokenUsage
- Langchain.Core.Stream: instance GHC.Show.Show Langchain.Core.Stream.StreamEvent
- Langchain.Core.Stream: instance GHC.Show.Show Langchain.Core.Stream.TokenUsage
- Langchain.Core.Stream: printEvents :: EventStream (ExceptT LangchainError (ResourceT IO)) -> IO (Either LangchainError ())
- Langchain.Core.Stream: type ChatStream = EventStream StreamM
- Langchain.Core.Stream: type EventStream (m :: Type -> Type) = ConduitT () StreamEvent m ()
- Langchain.Core.Stream: type StreamCallback a = a -> IO () -> IO ()
- Langchain.Core.Stream: type StreamM = ExceptT LangchainError ResourceT IO
- Langchain.Core.Stream: type StreamSource a = ConduitT () a StreamM ()
- Langchain.Core.Tool: Tool :: Text -> Text -> Value -> (Value -> m (Either LangchainError Text)) -> Tool (m :: Type -> Type)
- Langchain.Core.Tool: [toolDescription] :: Tool (m :: Type -> Type) -> Text
- Langchain.Core.Tool: [toolExecute] :: Tool (m :: Type -> Type) -> Value -> m (Either LangchainError Text)
- Langchain.Core.Tool: [toolName] :: Tool (m :: Type -> Type) -> Text
- Langchain.Core.Tool: [toolSchema] :: Tool (m :: Type -> Type) -> Value
- Langchain.Core.Tool: createTool :: Text -> Text -> Value -> (Value -> m (Either LangchainError Text)) -> Tool m
- Langchain.Core.Tool: data Tool (m :: Type -> Type)
- Langchain.Core.Tool: instance GHC.Show.Show (Langchain.Core.Tool.Tool m)
- Langchain.Core.Tool: toolToValue :: forall (m :: Type -> Type). Tool m -> Value
+ 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.Model.ChatModel model, Control.Monad.IO.Class.MonadIO m, Control.Monad.Error.Class.MonadError Langchain.Error.LangchainError m) => Langchain.Agent.PlanAndExecute.StepExecutor model m
+ Langchain.Agent.PlanAndExecute: instance (Langchain.Tool.Binding.ToolBinder model n, Control.Monad.IO.Class.MonadIO n, Control.Monad.Error.Class.MonadError Langchain.Error.LangchainError n) => Langchain.Agent.PlanAndExecute.StepExecutor (Langchain.Agent.ReAct.ReActAgent model n) n
+ Langchain.Agent.PlanAndExecute: instance (Langchain.Tool.Binding.ToolBinder model n, Control.Monad.IO.Class.MonadIO n, Control.Monad.Error.Class.MonadError Langchain.Error.LangchainError n) => Langchain.Agent.PlanAndExecute.StepExecutor (model, [Langchain.Tool.Tool n]) n
+ 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 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: AgentTrace :: [(AgentStep, [Message])] -> Message -> AgentTrace
+ Langchain.Agent.ReAct: ContinueOnError :: ToolErrorStrategy
+ Langchain.Agent.ReAct: FailOnError :: ToolErrorStrategy
+ Langchain.Agent.ReAct: ReActAgent :: model -> [Tool m] -> Int -> Maybe Text -> Maybe (ModelConfig model) -> Maybe SomeMemory -> Maybe CallbackManager -> Maybe (Message -> Bool) -> ToolErrorStrategy -> ReActAgent model (m :: Type -> Type)
+ Langchain.Agent.ReAct: [agentCallbackManager] :: ReActAgent model (m :: Type -> Type) -> Maybe CallbackManager
+ Langchain.Agent.ReAct: [agentMaxIterations] :: ReActAgent model (m :: Type -> Type) -> Int
+ Langchain.Agent.ReAct: [agentMemory] :: ReActAgent model (m :: Type -> Type) -> Maybe SomeMemory
+ Langchain.Agent.ReAct: [agentModelConfig] :: ReActAgent model (m :: Type -> Type) -> Maybe (ModelConfig model)
+ Langchain.Agent.ReAct: [agentModel] :: ReActAgent model (m :: Type -> Type) -> model
+ Langchain.Agent.ReAct: [agentStopCondition] :: ReActAgent model (m :: Type -> Type) -> Maybe (Message -> Bool)
+ Langchain.Agent.ReAct: [agentSystemPrompt] :: ReActAgent model (m :: Type -> Type) -> Maybe Text
+ Langchain.Agent.ReAct: [agentToolErrorStrategy] :: ReActAgent model (m :: Type -> Type) -> ToolErrorStrategy
+ Langchain.Agent.ReAct: [agentTools] :: ReActAgent model (m :: Type -> Type) -> [Tool m]
+ Langchain.Agent.ReAct: [traceFinalAnswer] :: AgentTrace -> Message
+ Langchain.Agent.ReAct: [traceSteps] :: AgentTrace -> [(AgentStep, [Message])]
+ Langchain.Agent.ReAct: data AgentStep
+ Langchain.Agent.ReAct: data AgentTrace
+ Langchain.Agent.ReAct: data ReActAgent model (m :: Type -> Type)
+ Langchain.Agent.ReAct: data ToolErrorStrategy
+ Langchain.Agent.ReAct: defaultReActAgent :: forall model (m :: Type -> Type). model -> [Tool m] -> ReActAgent model m
+ Langchain.Agent.ReAct: instance GHC.Classes.Eq Langchain.Agent.ReAct.AgentStep
+ Langchain.Agent.ReAct: instance GHC.Classes.Eq Langchain.Agent.ReAct.AgentTrace
+ Langchain.Agent.ReAct: instance GHC.Classes.Eq Langchain.Agent.ReAct.ToolErrorStrategy
+ Langchain.Agent.ReAct: instance GHC.Show.Show Langchain.Agent.ReAct.AgentStep
+ Langchain.Agent.ReAct: instance GHC.Show.Show Langchain.Agent.ReAct.AgentTrace
+ Langchain.Agent.ReAct: instance GHC.Show.Show Langchain.Agent.ReAct.ToolErrorStrategy
+ Langchain.Agent.ReAct: reactStep :: (ToolBinder model m, MonadIO m, MonadError LangchainError m) => model -> [Tool m] -> [Message] -> Maybe (ModelConfig model) -> m AgentStep
+ Langchain.Agent.ReAct: runReActAgent :: (ToolBinder model m, MonadIO m, MonadError LangchainError m) => ReActAgent model m -> [Message] -> m Message
+ Langchain.Agent.ReAct: runReActAgentWithTrace :: (ToolBinder model m, MonadIO m, MonadError LangchainError m) => ReActAgent model m -> [Message] -> m AgentTrace
+ Langchain.Agent.ReAct: withCallbackManager :: forall model (m :: Type -> Type). CallbackManager -> ReActAgent model m -> ReActAgent model m
+ Langchain.Agent.ReAct: withMaxIterations :: forall model (m :: Type -> Type). Int -> ReActAgent model m -> ReActAgent model m
+ Langchain.Agent.ReAct: withMemory :: forall mem model (m :: Type -> Type). BaseMemory mem => mem -> ReActAgent model m -> ReActAgent model m
+ Langchain.Agent.ReAct: withModelConfig :: forall model (m :: Type -> Type). Maybe (ModelConfig model) -> ReActAgent model m -> ReActAgent model m
+ Langchain.Agent.ReAct: withStopCondition :: forall model (m :: Type -> Type). (Message -> Bool) -> ReActAgent model m -> ReActAgent model m
+ Langchain.Agent.ReAct: withSystemPrompt :: forall model (m :: Type -> Type). Text -> ReActAgent model m -> ReActAgent model m
+ Langchain.Agent.ReAct: withToolErrorStrategy :: forall model (m :: Type -> Type). ToolErrorStrategy -> ReActAgent model m -> ReActAgent model m
+ Langchain.Agent.ReAct: withoutMemory :: forall model (m :: Type -> Type). ReActAgent model m -> ReActAgent model m
+ 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.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: 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.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.Core: splitDocuments :: (Text -> [Text]) -> [Document] -> [Document]
+ 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: class Embeddings embed
+ Langchain.Embeddings: embedDocuments :: (Embeddings embed, MonadIO m, MonadError LangchainError m) => embed -> [Document] -> m [[Float]]
+ Langchain.Embeddings: embedQuery :: (Embeddings embed, MonadIO m, MonadError LangchainError m) => embed -> Text -> m [Float]
+ Langchain.Error: AgentError :: Text -> Maybe ErrorContext -> LangchainError
+ Langchain.Error: ConfigurationError :: Text -> Maybe ErrorContext -> LangchainError
+ Langchain.Error: DocumentLoaderError :: Text -> Maybe ErrorContext -> LangchainError
+ Langchain.Error: EmbeddingError :: Text -> Maybe ErrorContext -> LangchainError
+ Langchain.Error: ErrorContext :: Text -> Text -> UTCTime -> Map Text Text -> ErrorContext
+ Langchain.Error: InternalError :: Text -> Maybe ErrorContext -> LangchainError
+ Langchain.Error: LLMError :: Text -> Maybe ErrorContext -> LangchainError
+ Langchain.Error: MemoryError :: Text -> Maybe ErrorContext -> LangchainError
+ Langchain.Error: NetworkError :: Text -> Maybe ErrorContext -> LangchainError
+ Langchain.Error: ParsingError :: Text -> Maybe ErrorContext -> LangchainError
+ Langchain.Error: RunnableError :: Text -> Maybe ErrorContext -> LangchainError
+ Langchain.Error: ToolError :: Text -> Maybe ErrorContext -> LangchainError
+ Langchain.Error: ValidationError :: Text -> Maybe ErrorContext -> LangchainError
+ Langchain.Error: VectorStoreError :: Text -> Maybe ErrorContext -> LangchainError
+ Langchain.Error: [component] :: ErrorContext -> Text
+ Langchain.Error: [details] :: ErrorContext -> Map Text Text
+ Langchain.Error: [operation] :: ErrorContext -> Text
+ Langchain.Error: [timestamp] :: ErrorContext -> UTCTime
+ Langchain.Error: agentError :: Text -> Maybe Text -> Maybe Text -> LangchainError
+ Langchain.Error: configurationError :: Text -> Maybe Text -> Maybe Text -> LangchainError
+ Langchain.Error: data ErrorContext
+ Langchain.Error: data LangchainError
+ Langchain.Error: documentLoaderError :: Text -> Maybe Text -> Maybe Text -> LangchainError
+ Langchain.Error: embeddingError :: Text -> Maybe Text -> Maybe Text -> LangchainError
+ Langchain.Error: errorMessage :: LangchainError -> Text
+ Langchain.Error: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Error.ErrorContext
+ Langchain.Error: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Error.LangchainError
+ Langchain.Error: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Error.ErrorContext
+ Langchain.Error: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Error.LangchainError
+ Langchain.Error: instance GHC.Classes.Eq Langchain.Error.ErrorContext
+ Langchain.Error: instance GHC.Classes.Eq Langchain.Error.LangchainError
+ Langchain.Error: instance GHC.Exception.Type.Exception Langchain.Error.LangchainError
+ Langchain.Error: instance GHC.Generics.Generic Langchain.Error.ErrorContext
+ Langchain.Error: instance GHC.Generics.Generic Langchain.Error.LangchainError
+ Langchain.Error: instance GHC.Show.Show Langchain.Error.ErrorContext
+ Langchain.Error: instance GHC.Show.Show Langchain.Error.LangchainError
+ Langchain.Error: internalError :: Text -> Maybe Text -> Maybe Text -> LangchainError
+ Langchain.Error: llmError :: Text -> Maybe Text -> Maybe Text -> LangchainError
+ Langchain.Error: memoryError :: Text -> Maybe Text -> Maybe Text -> LangchainError
+ Langchain.Error: mkContext :: Text -> Text -> Map Text Text -> ErrorContext
+ Langchain.Error: mkContextIO :: MonadIO m => Text -> Text -> Map Text Text -> m ErrorContext
+ Langchain.Error: networkError :: Text -> Maybe Text -> Maybe Text -> LangchainError
+ Langchain.Error: parsingError :: Text -> Maybe Text -> Maybe Text -> LangchainError
+ Langchain.Error: runnableError :: Text -> Maybe Text -> Maybe Text -> LangchainError
+ Langchain.Error: toolError :: Text -> Maybe Text -> Maybe Text -> LangchainError
+ Langchain.Error: type LangchainResult a = Either LangchainError a
+ Langchain.Error: validationError :: Text -> Maybe Text -> Maybe Text -> LangchainError
+ Langchain.Error: vectorStoreError :: Text -> Maybe Text -> Maybe Text -> LangchainError
+ 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.Memory.Combined: CombinedMemory :: [SomeMemory] -> CombinedMemory
+ Langchain.Memory.Combined: [memories] :: CombinedMemory -> [SomeMemory]
+ Langchain.Memory.Combined: instance Langchain.Memory.Core.BaseMemory Langchain.Memory.Combined.CombinedMemory
+ Langchain.Memory.Combined: newCombinedMemory :: [SomeMemory] -> CombinedMemory
+ Langchain.Memory.Combined: newtype CombinedMemory
+ Langchain.Memory.Core: ConversationBufferMemory :: !TVar [Message] -> ![Message] -> ConversationBufferMemory
+ Langchain.Memory.Core: EvictOldest :: OverflowStrategy
+ Langchain.Memory.Core: RejectOnOverflow :: OverflowStrategy
+ Langchain.Memory.Core: TokenBufferMemory :: !Int -> !TVar [Message] -> !OverflowStrategy -> !TokenCounter -> ![Message] -> TokenBufferMemory
+ Langchain.Memory.Core: WindowBufferMemory :: !Int -> !TVar [Message] -> ![Message] -> WindowBufferMemory
+ Langchain.Memory.Core: [SomeMemory] :: forall mem. BaseMemory mem => mem -> SomeMemory
+ Langchain.Memory.Core: [initialMessagesList] :: TokenBufferMemory -> ![Message]
+ Langchain.Memory.Core: [maxTokens] :: TokenBufferMemory -> !Int
+ Langchain.Memory.Core: [maxWindowSize] :: WindowBufferMemory -> !Int
+ Langchain.Memory.Core: [memVar] :: TokenBufferMemory -> !TVar [Message]
+ Langchain.Memory.Core: [overflowStrategy] :: TokenBufferMemory -> !OverflowStrategy
+ Langchain.Memory.Core: [tokenCounter] :: TokenBufferMemory -> !TokenCounter
+ 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: approximateTokenCount :: [Message] -> Int
+ 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 ConversationBufferMemory
+ Langchain.Memory.Core: data OverflowStrategy
+ Langchain.Memory.Core: data SomeMemory
+ Langchain.Memory.Core: data TokenBufferMemory
+ Langchain.Memory.Core: data WindowBufferMemory
+ Langchain.Memory.Core: exportMessages :: (BaseMemory mem, MonadIO m, MonadError LangchainError m) => mem -> m [Message]
+ Langchain.Memory.Core: formatHistory :: [Message] -> Text
+ Langchain.Memory.Core: formatHistoryWithLabels :: Text -> Text -> [Message] -> Text
+ Langchain.Memory.Core: importMessages :: (BaseMemory mem, MonadIO m, MonadError LangchainError m) => mem -> [Message] -> m ()
+ Langchain.Memory.Core: initialMessages :: Text -> [Message]
+ Langchain.Memory.Core: instance GHC.Classes.Eq Langchain.Memory.Core.ConversationBufferMemory
+ Langchain.Memory.Core: instance GHC.Classes.Eq Langchain.Memory.Core.OverflowStrategy
+ 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.Generics.Generic Langchain.Memory.Core.OverflowStrategy
+ Langchain.Memory.Core: instance GHC.Show.Show Langchain.Memory.Core.ConversationBufferMemory
+ Langchain.Memory.Core: instance GHC.Show.Show Langchain.Memory.Core.OverflowStrategy
+ 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: instance Langchain.Memory.Core.BaseMemory Langchain.Memory.Core.ConversationBufferMemory
+ Langchain.Memory.Core: instance Langchain.Memory.Core.BaseMemory Langchain.Memory.Core.SomeMemory
+ 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: loadMemoryFromFile :: (BaseMemory mem, MonadIO m, MonadError LangchainError m) => FilePath -> mem -> m ()
+ Langchain.Memory.Core: loadMemoryVariables :: (BaseMemory mem, MonadIO m, MonadError LangchainError m) => mem -> m (Map Text Text)
+ Langchain.Memory.Core: messages :: (BaseMemory mem, MonadIO m, MonadError LangchainError m) => mem -> m [Message]
+ Langchain.Memory.Core: newConversationBufferMemory :: MonadIO m => [Message] -> m ConversationBufferMemory
+ Langchain.Memory.Core: newTokenBufferMemory :: MonadIO m => Int -> [Message] -> m TokenBufferMemory
+ Langchain.Memory.Core: newTokenBufferMemoryWithStrategy :: MonadIO m => Int -> [Message] -> OverflowStrategy -> TokenCounter -> m TokenBufferMemory
+ Langchain.Memory.Core: newWindowBufferMemory :: MonadIO m => Int -> [Message] -> m WindowBufferMemory
+ Langchain.Memory.Core: resetConversationBuffer :: MonadIO m => ConversationBufferMemory -> m ()
+ Langchain.Memory.Core: resetToInitial :: MonadIO m => TVar [Message] -> [Message] -> m ()
+ Langchain.Memory.Core: resetTokenBuffer :: MonadIO m => TokenBufferMemory -> m ()
+ Langchain.Memory.Core: resetWindowBuffer :: MonadIO m => WindowBufferMemory -> m ()
+ Langchain.Memory.Core: saveContext :: (BaseMemory mem, MonadIO m, MonadError LangchainError m) => mem -> Text -> Text -> m ()
+ Langchain.Memory.Core: saveMemoryToFile :: (BaseMemory mem, MonadIO m, MonadError LangchainError m) => FilePath -> mem -> m ()
+ Langchain.Memory.Core: trimMessages :: Int -> [Message] -> [Message]
+ Langchain.Memory.Core: type TokenCounter = [Message] -> Int
+ Langchain.Memory.Entity: EntityMemory :: !TVar (Map Text Text) -> !Map Text Text -> EntityMemory
+ Langchain.Memory.Entity: [entityVar] :: EntityMemory -> !TVar (Map Text Text)
+ Langchain.Memory.Entity: [initialEntities] :: EntityMemory -> !Map Text Text
+ Langchain.Memory.Entity: clearEntities :: MonadIO m => EntityMemory -> m ()
+ Langchain.Memory.Entity: data EntityMemory
+ Langchain.Memory.Entity: deleteEntity :: MonadIO m => EntityMemory -> Text -> m ()
+ Langchain.Memory.Entity: formatEntities :: Map Text Text -> Text
+ Langchain.Memory.Entity: getAllEntities :: MonadIO m => EntityMemory -> m (Map Text Text)
+ Langchain.Memory.Entity: getEntity :: MonadIO m => EntityMemory -> Text -> m (Maybe Text)
+ Langchain.Memory.Entity: instance Langchain.Memory.Core.BaseMemory Langchain.Memory.Entity.EntityMemory
+ Langchain.Memory.Entity: newEntityMemory :: MonadIO m => Map Text Text -> m EntityMemory
+ Langchain.Memory.Entity: resetEntitiesToInitial :: MonadIO m => EntityMemory -> m ()
+ Langchain.Memory.Entity: setEntity :: MonadIO m => EntityMemory -> Text -> Text -> m ()
+ Langchain.Memory.Session: SessionMemoryStore :: !TVar (Map SessionId (mem, UTCTime)) -> !SessionId -> IO mem -> !Maybe NominalDiffTime -> SessionMemoryStore mem
+ Langchain.Memory.Session: [memFactory] :: SessionMemoryStore mem -> !SessionId -> IO mem
+ Langchain.Memory.Session: [sessionTTL] :: SessionMemoryStore mem -> !Maybe NominalDiffTime
+ Langchain.Memory.Session: [storeVar] :: SessionMemoryStore mem -> !TVar (Map SessionId (mem, UTCTime))
+ Langchain.Memory.Session: cleanupExpiredSessions :: MonadIO m => SessionMemoryStore mem -> m Int
+ Langchain.Memory.Session: clearAllSessions :: MonadIO m => SessionMemoryStore mem -> m ()
+ Langchain.Memory.Session: data SessionMemoryStore mem
+ Langchain.Memory.Session: deleteSession :: MonadIO m => SessionMemoryStore mem -> SessionId -> m ()
+ Langchain.Memory.Session: exportAllSessions :: MonadIO m => (mem -> m [Message]) -> SessionMemoryStore mem -> m (Map SessionId [Message])
+ Langchain.Memory.Session: getSessionMemory :: MonadIO m => SessionMemoryStore mem -> SessionId -> m mem
+ Langchain.Memory.Session: hasSession :: MonadIO m => SessionMemoryStore mem -> SessionId -> m Bool
+ Langchain.Memory.Session: listSessions :: MonadIO m => SessionMemoryStore mem -> m [SessionId]
+ Langchain.Memory.Session: newSessionMemoryStore :: MonadIO m => (SessionId -> IO mem) -> m (SessionMemoryStore mem)
+ Langchain.Memory.Session: newSessionMemoryStoreWithTTL :: MonadIO m => NominalDiffTime -> (SessionId -> IO mem) -> m (SessionMemoryStore mem)
+ Langchain.Memory.Session: sessionCount :: MonadIO m => SessionMemoryStore mem -> m Int
+ Langchain.Memory.Session: type SessionId = Text
+ Langchain.Memory.Summary: SummaryMemory :: model -> !Int -> !TVar Text -> !TVar [Message] -> !Text -> [Message] -> Text -> ![Message] -> SummaryMemory model
+ Langchain.Memory.Summary: [initialMessagesList] :: SummaryMemory model -> ![Message]
+ Langchain.Memory.Summary: [maxMessageThreshold] :: SummaryMemory model -> !Int
+ Langchain.Memory.Summary: [promptBuilder] :: SummaryMemory model -> !Text -> [Message] -> Text
+ 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: defaultSummaryPrompt :: Text -> [Message] -> Text
+ Langchain.Memory.Summary: formatMessages :: [Message] -> Text
+ Langchain.Memory.Summary: getSummary :: MonadIO m => SummaryMemory model -> m Text
+ Langchain.Memory.Summary: instance Langchain.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.Memory.Summary: newSummaryMemoryWithPrompt :: MonadIO m => model -> Int -> [Message] -> (Text -> [Message] -> Text) -> m (SummaryMemory model)
+ Langchain.Model: batch :: (ChatModel model, MonadIO m, MonadError LangchainError m) => model -> [[Message]] -> Maybe (ModelConfig model) -> m [Message]
+ Langchain.Model: class ChatModel model where {
+ Langchain.Model: invoke :: (ChatModel model, MonadIO m, MonadError LangchainError m) => model -> [Message] -> Maybe (ModelConfig model) -> m Message
+ Langchain.Model: stream :: ChatModel model => model -> [Message] -> Maybe (ModelConfig model) -> ChatStream
+ Langchain.Model: type ModelConfig model;
+ Langchain.Model: }
+ Langchain.Model.Types: Assistant :: Role
+ Langchain.Model.Types: AudioBlock :: Text -> Text -> ContentBlock
+ Langchain.Model.Types: DataBlock :: ByteString -> ContentBlock
+ Langchain.Model.Types: Developer :: Role
+ Langchain.Model.Types: Function :: Role
+ Langchain.Model.Types: ImageBase64 :: Maybe Text -> Text -> ImageSource
+ Langchain.Model.Types: ImageBlock :: ImageContent -> ContentBlock
+ Langchain.Model.Types: ImageContent :: ImageSource -> Maybe Text -> Maybe Value -> ImageContent
+ Langchain.Model.Types: ImageUrl :: Text -> ImageSource
+ Langchain.Model.Types: Message :: Role -> NonEmpty ContentBlock -> Maybe Text -> Maybe [ToolCall] -> Maybe Text -> Map Text Value -> Message
+ Langchain.Model.Types: System :: Role
+ Langchain.Model.Types: TextBlock :: Text -> ContentBlock
+ Langchain.Model.Types: Tool :: Role
+ Langchain.Model.Types: ToolCall :: Text -> Text -> Text -> Value -> ToolCall
+ Langchain.Model.Types: User :: Role
+ Langchain.Model.Types: [blockBase64] :: ContentBlock -> Text
+ Langchain.Model.Types: [blockBytes] :: ContentBlock -> ByteString
+ Langchain.Model.Types: [blockMimeType] :: ContentBlock -> Text
+ Langchain.Model.Types: [blockText] :: ContentBlock -> Text
+ Langchain.Model.Types: [imageData] :: ImageSource -> Text
+ Langchain.Model.Types: [imageDetail] :: ImageContent -> Maybe Text
+ Langchain.Model.Types: [imageMetadata] :: ImageContent -> Maybe Value
+ Langchain.Model.Types: [imageMimeType] :: ImageSource -> Maybe Text
+ Langchain.Model.Types: [imageSource] :: ImageContent -> ImageSource
+ Langchain.Model.Types: [imageUrl] :: ImageSource -> Text
+ Langchain.Model.Types: [messageContents] :: Message -> NonEmpty ContentBlock
+ Langchain.Model.Types: [messageMetadata] :: Message -> Map Text Value
+ Langchain.Model.Types: [messageName] :: Message -> Maybe Text
+ Langchain.Model.Types: [messageRole] :: Message -> Role
+ Langchain.Model.Types: [messageToolCalls] :: Message -> Maybe [ToolCall]
+ Langchain.Model.Types: [messageToolId] :: Message -> Maybe Text
+ Langchain.Model.Types: [toolCallArguments] :: ToolCall -> Value
+ Langchain.Model.Types: [toolCallId] :: ToolCall -> Text
+ Langchain.Model.Types: [toolCallName] :: ToolCall -> Text
+ Langchain.Model.Types: [toolCallType] :: ToolCall -> Text
+ Langchain.Model.Types: assistantMessage :: Text -> Message
+ Langchain.Model.Types: data ContentBlock
+ Langchain.Model.Types: data ImageContent
+ Langchain.Model.Types: data ImageSource
+ Langchain.Model.Types: data Message
+ Langchain.Model.Types: data Role
+ Langchain.Model.Types: data ToolCall
+ Langchain.Model.Types: extractMessageText :: Message -> Text
+ Langchain.Model.Types: formatMessageString :: Message -> Text
+ Langchain.Model.Types: imageMessage :: Role -> Text -> Text -> Message
+ Langchain.Model.Types: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Model.Types.ContentBlock
+ Langchain.Model.Types: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Model.Types.Message
+ Langchain.Model.Types: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Model.Types.Role
+ Langchain.Model.Types: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Model.Types.ToolCall
+ Langchain.Model.Types: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Model.Types.ContentBlock
+ Langchain.Model.Types: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Model.Types.Message
+ Langchain.Model.Types: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Model.Types.Role
+ Langchain.Model.Types: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Model.Types.ToolCall
+ Langchain.Model.Types: instance GHC.Classes.Eq Langchain.Model.Types.ContentBlock
+ Langchain.Model.Types: instance GHC.Classes.Eq Langchain.Model.Types.ImageContent
+ Langchain.Model.Types: instance GHC.Classes.Eq Langchain.Model.Types.ImageSource
+ Langchain.Model.Types: instance GHC.Classes.Eq Langchain.Model.Types.Message
+ Langchain.Model.Types: instance GHC.Classes.Eq Langchain.Model.Types.Role
+ Langchain.Model.Types: instance GHC.Classes.Eq Langchain.Model.Types.ToolCall
+ Langchain.Model.Types: instance GHC.Classes.Ord Langchain.Model.Types.Role
+ Langchain.Model.Types: instance GHC.Enum.Bounded Langchain.Model.Types.Role
+ Langchain.Model.Types: instance GHC.Enum.Enum Langchain.Model.Types.Role
+ Langchain.Model.Types: instance GHC.Generics.Generic Langchain.Model.Types.ContentBlock
+ Langchain.Model.Types: instance GHC.Generics.Generic Langchain.Model.Types.ImageContent
+ Langchain.Model.Types: instance GHC.Generics.Generic Langchain.Model.Types.ImageSource
+ Langchain.Model.Types: instance GHC.Generics.Generic Langchain.Model.Types.Message
+ Langchain.Model.Types: instance GHC.Generics.Generic Langchain.Model.Types.Role
+ Langchain.Model.Types: instance GHC.Generics.Generic Langchain.Model.Types.ToolCall
+ Langchain.Model.Types: instance GHC.Show.Show Langchain.Model.Types.ContentBlock
+ Langchain.Model.Types: instance GHC.Show.Show Langchain.Model.Types.ImageContent
+ Langchain.Model.Types: instance GHC.Show.Show Langchain.Model.Types.ImageSource
+ Langchain.Model.Types: instance GHC.Show.Show Langchain.Model.Types.Message
+ Langchain.Model.Types: instance GHC.Show.Show Langchain.Model.Types.Role
+ Langchain.Model.Types: instance GHC.Show.Show Langchain.Model.Types.ToolCall
+ Langchain.Model.Types: roleLabel :: Role -> Text
+ Langchain.Model.Types: systemMessage :: Text -> Message
+ Langchain.Model.Types: textMessage :: Role -> Text -> Message
+ Langchain.Model.Types: toolMessage :: Text -> Message
+ Langchain.Model.Types: userMessage :: Text -> Message
+ Langchain.Monad: runLangchainT :: r -> LangchainT r m a -> m (Either LangchainError a)
+ Langchain.Monad: throwLangchainError :: MonadError LangchainError m => LangchainError -> m a
+ Langchain.Monad: type LangchainT r (m :: Type -> Type) = ReaderT r ExceptT LangchainError m
+ 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: class OutputParser a
+ Langchain.OutputParser.Core: instance GHC.Classes.Eq Langchain.OutputParser.Core.CommaSeparatedList
+ Langchain.OutputParser.Core: instance GHC.Show.Show Langchain.OutputParser.Core.CommaSeparatedList
+ Langchain.OutputParser.Core: instance Langchain.OutputParser.Core.OutputParser Data.Text.Internal.Text
+ Langchain.OutputParser.Core: instance Langchain.OutputParser.Core.OutputParser GHC.Num.Integer.Integer
+ Langchain.OutputParser.Core: instance Langchain.OutputParser.Core.OutputParser GHC.Types.Bool
+ Langchain.OutputParser.Core: instance Langchain.OutputParser.Core.OutputParser GHC.Types.Double
+ Langchain.OutputParser.Core: instance Langchain.OutputParser.Core.OutputParser GHC.Types.Int
+ Langchain.OutputParser.Core: instance Langchain.OutputParser.Core.OutputParser Langchain.OutputParser.Core.CommaSeparatedList
+ Langchain.OutputParser.Core: newtype CommaSeparatedList
+ 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: 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: 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: AgentTrace :: [(AgentStep, [Message])] -> Message -> AgentTrace
+ 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: CombinedMemory :: [SomeMemory] -> CombinedMemory
+ Langchain.Prelude: CommaSeparatedList :: [Text] -> CommaSeparatedList
+ Langchain.Prelude: ConfigurationError :: Text -> Maybe ErrorContext -> LangchainError
+ Langchain.Prelude: ConfiguredModelRunnable :: c -> ConfiguredModelRunnable c
+ Langchain.Prelude: ConsumerSpan :: SpanKind
+ Langchain.Prelude: ContinueOnError :: ToolErrorStrategy
+ Langchain.Prelude: ConversationBufferMemory :: !TVar [Message] -> ![Message] -> ConversationBufferMemory
+ Langchain.Prelude: Cpp :: Language
+ 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 :: !TVar (Map Text Text) -> !Map Text Text -> EntityMemory
+ Langchain.Prelude: ErrorContext :: Text -> Text -> UTCTime -> Map Text Text -> ErrorContext
+ Langchain.Prelude: ErrorLevel :: LogLevel
+ Langchain.Prelude: FString :: TemplateFormat
+ Langchain.Prelude: FailOnError :: ToolErrorStrategy
+ 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: 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: 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: MarkdownCode :: Language
+ Langchain.Prelude: MarkdownSplitterOps :: Int64 -> Int64 -> [(Text, Text)] -> MarkdownSplitterOps
+ 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: ModelRunnable :: !c -> !Maybe (ModelConfig c) -> ModelRunnable c
+ Langchain.Prelude: NetworkError :: Text -> Maybe ErrorContext -> LangchainError
+ Langchain.Prelude: NodeEnd :: Text -> Text -> Value -> StreamEvent
+ Langchain.Prelude: NodeStart :: Text -> Text -> Value -> StreamEvent
+ Langchain.Prelude: OTelTracer :: !Text -> !TVar [Span] -> OTelTracer
+ 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: 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 -> Maybe Text -> Maybe (ModelConfig model) -> Maybe SomeMemory -> Maybe CallbackManager -> Maybe (Message -> Bool) -> ToolErrorStrategy -> 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: SecurityPolicy :: !Set ToolTier -> !Maybe FilePath -> ![Text] -> !Maybe [Text] -> ![Text] -> !Maybe (ApprovalRequest -> IO ApprovalDecision) -> SecurityPolicy
+ Langchain.Prelude: ServerSpan :: SpanKind
+ Langchain.Prelude: SessionMemoryStore :: !TVar (Map SessionId (mem, UTCTime)) -> !SessionId -> IO mem -> !Maybe NominalDiffTime -> SessionMemoryStore mem
+ Langchain.Prelude: Span :: !Text -> !Text -> !Text -> !Maybe Text -> !SpanKind -> !UTCTime -> !Maybe UTCTime -> !Maybe Int -> !Map Text Text -> !SpanStatus -> Span
+ Langchain.Prelude: StatusError :: !Text -> SpanStatus
+ Langchain.Prelude: StatusOk :: SpanStatus
+ Langchain.Prelude: StatusUnset :: SpanStatus
+ Langchain.Prelude: SummaryMemory :: model -> !Int -> !TVar Text -> !TVar [Message] -> !Text -> [Message] -> Text -> ![Message] -> SummaryMemory model
+ Langchain.Prelude: TextBlock :: Text -> ContentBlock
+ Langchain.Prelude: TextModelRunnable :: !c -> !Maybe (ModelConfig c) -> TextModelRunnable c
+ Langchain.Prelude: TierAdmin :: ToolTier
+ Langchain.Prelude: TierExecution :: ToolTier
+ Langchain.Prelude: TierReadOnly :: ToolTier
+ Langchain.Prelude: TierWorkspaceWrite :: ToolTier
+ Langchain.Prelude: TokenBufferMemory :: !Int -> !TVar [Message] -> !OverflowStrategy -> !TokenCounter -> ![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] -> ![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: [SomeMemory] :: forall mem. BaseMemory mem => mem -> SomeMemory
+ 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: [completionTokens] :: TokenUsage -> Int
+ Langchain.Prelude: [component] :: ErrorContext -> Text
+ 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: [entityVar] :: EntityMemory -> !TVar (Map Text Text)
+ Langchain.Prelude: [excludeHidden] :: DirectoryLoaderOptions -> Bool
+ Langchain.Prelude: [extensions] :: DirectoryLoaderOptions -> [String]
+ Langchain.Prelude: [failureThreshold] :: CircuitBreakerConfig -> !Int
+ 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: [initialEntities] :: EntityMemory -> !Map Text Text
+ Langchain.Prelude: [initialMessagesList] :: SummaryMemory model -> ![Message]
+ Langchain.Prelude: [inputVariables] :: PromptTemplate -> [Text]
+ 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: [maxMessageThreshold] :: SummaryMemory model -> !Int
+ Langchain.Prelude: [maxTokens] :: TokenBufferMemory -> !Int
+ Langchain.Prelude: [maxWindowSize] :: WindowBufferMemory -> !Int
+ Langchain.Prelude: [memCacheVar] :: InMemoryCache -> TVar (Map Text Message)
+ Langchain.Prelude: [memFactory] :: SessionMemoryStore mem -> !SessionId -> IO mem
+ Langchain.Prelude: [memVar] :: TokenBufferMemory -> !TVar [Message]
+ Langchain.Prelude: [memories] :: CombinedMemory -> [SomeMemory]
+ 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: [modelRunnableConfig] :: ModelRunnable c -> !Maybe (ModelConfig c)
+ Langchain.Prelude: [modelRunnableModel] :: ModelRunnable c -> !c
+ Langchain.Prelude: [operation] :: ErrorContext -> Text
+ Langchain.Prelude: [overflowStrategy] :: TokenBufferMemory -> !OverflowStrategy
+ 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: [policyAllowedTiers] :: SecurityPolicy -> !Set ToolTier
+ Langchain.Prelude: [policyApprovalHook] :: SecurityPolicy -> !Maybe (ApprovalRequest -> IO ApprovalDecision)
+ Langchain.Prelude: [policyCommandBlacklist] :: SecurityPolicy -> ![Text]
+ Langchain.Prelude: [policyCommandWhitelist] :: SecurityPolicy -> !Maybe [Text]
+ Langchain.Prelude: [policySensitivePatterns] :: SecurityPolicy -> ![Text]
+ Langchain.Prelude: [policyWorkspaceRoot] :: SecurityPolicy -> !Maybe FilePath
+ Langchain.Prelude: [promptBuilder] :: SummaryMemory model -> !Text -> [Message] -> Text
+ Langchain.Prelude: [promptTokens] :: TokenUsage -> Int
+ Langchain.Prelude: [recentMessagesVar] :: SummaryMemory model -> !TVar [Message]
+ Langchain.Prelude: [recursiveDepth] :: DirectoryLoaderOptions -> Maybe Int
+ Langchain.Prelude: [refillRatePerSec] :: RateLimiter -> !Double
+ Langchain.Prelude: [resetTimeoutSec] :: CircuitBreakerConfig -> !Double
+ Langchain.Prelude: [sessionTTL] :: SessionMemoryStore mem -> !Maybe NominalDiffTime
+ 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: [stepDescription] :: PlanStep -> !Text
+ Langchain.Prelude: [stepExecutor] :: PlanAndExecuteAgent planner executor -> executor
+ Langchain.Prelude: [stepNumber] :: PlanStep -> !Int
+ Langchain.Prelude: [storeVar] :: SessionMemoryStore mem -> !TVar (Map SessionId (mem, UTCTime))
+ 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: [textModelRunnableConfig] :: TextModelRunnable c -> !Maybe (ModelConfig c)
+ Langchain.Prelude: [textModelRunnableModel] :: TextModelRunnable c -> !c
+ Langchain.Prelude: [timestamp] :: ErrorContext -> UTCTime
+ Langchain.Prelude: [tokenCounter] :: TokenBufferMemory -> !TokenCounter
+ 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: [traceFinalAnswer] :: AgentTrace -> Message
+ Langchain.Prelude: [traceSteps] :: AgentTrace -> [(AgentStep, [Message])]
+ 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: addDocuments :: (VectorStore vs, MonadIO m, MonadError LangchainError m) => vs -> [Document] -> m vs
+ Langchain.Prelude: addDocumentsBM25 :: [Document] -> BM25Index -> BM25Index
+ Langchain.Prelude: addMessage :: (BaseMemory mem, MonadIO m, MonadError LangchainError m) => mem -> Message -> 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: assistantMessage :: Text -> Message
+ Langchain.Prelude: batch :: (ChatModel model, MonadIO m, MonadError LangchainError m) => model -> [[Message]] -> Maybe (ModelConfig model) -> m [Message]
+ Langchain.Prelude: bindConfig :: ModelConfig c -> ModelRunnable c -> ModelRunnable c
+ Langchain.Prelude: bindTextConfig :: ModelConfig c -> TextModelRunnable c -> TextModelRunnable c
+ 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: cacheModelIdentity :: CacheableChatModel model => model -> Maybe (ModelConfig model) -> Value
+ Langchain.Prelude: class BaseLoader loader
+ Langchain.Prelude: class BaseMemory mem
+ Langchain.Prelude: class CacheBackend cb
+ Langchain.Prelude: class ChatModel model => CacheableChatModel model
+ Langchain.Prelude: class ChatModel model where {
+ 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: cleanupExpiredSessions :: MonadIO m => SessionMemoryStore mem -> m Int
+ Langchain.Prelude: clear :: (BaseMemory mem, MonadIO m, MonadError LangchainError m) => mem -> m ()
+ Langchain.Prelude: clearAllSessions :: MonadIO m => SessionMemoryStore mem -> m ()
+ Langchain.Prelude: clearCache :: (CacheBackend cb, MonadIO m) => cb -> m ()
+ Langchain.Prelude: clearEntities :: MonadIO m => EntityMemory -> m ()
+ Langchain.Prelude: collectEvents :: Monad m => EventStream m -> m [StreamEvent]
+ Langchain.Prelude: commandKillTool :: forall (m :: Type -> Type). MonadIO m => ProcessManager -> SecurityPolicy -> Tool m
+ Langchain.Prelude: commandRunTool :: forall (m :: Type -> Type). MonadIO m => ProcessManager -> SecurityPolicy -> Tool m
+ Langchain.Prelude: commandStatusTool :: forall (m :: Type -> Type). MonadIO m => ProcessManager -> SecurityPolicy -> Tool 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: createTool :: Text -> Text -> Value -> (Value -> m (Either LangchainError Text)) -> Tool m
+ Langchain.Prelude: data AgentStep
+ Langchain.Prelude: data AgentTrace
+ 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 ConversationBufferMemory
+ Langchain.Prelude: data DirectoryLoader
+ Langchain.Prelude: data DirectoryLoaderOptions
+ Langchain.Prelude: data Document
+ Langchain.Prelude: data EntityMemory
+ Langchain.Prelude: data ErrorContext
+ 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 MarkdownSplitterOps
+ Langchain.Prelude: data Message
+ Langchain.Prelude: data ModelRunnable c
+ Langchain.Prelude: data OTelTracer
+ 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 SecurityPolicy
+ Langchain.Prelude: data SessionMemoryStore mem
+ Langchain.Prelude: data SomeMemory
+ Langchain.Prelude: data Span
+ Langchain.Prelude: data SpanKind
+ Langchain.Prelude: data SpanStatus
+ Langchain.Prelude: data StreamEvent
+ Langchain.Prelude: data SummaryMemory model
+ Langchain.Prelude: data TemplateFormat
+ Langchain.Prelude: data TextModelRunnable c
+ 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 ToolErrorStrategy
+ Langchain.Prelude: data ToolTier
+ Langchain.Prelude: data WindowBufferMemory
+ Langchain.Prelude: defaultCharacterSplitterOps :: CharacterSplitterOps
+ Langchain.Prelude: defaultCircuitConfig :: CircuitBreakerConfig
+ Langchain.Prelude: defaultDirectoryLoaderOptions :: DirectoryLoaderOptions
+ Langchain.Prelude: defaultMarkdownSplitterOps :: MarkdownSplitterOps
+ Langchain.Prelude: defaultPromptTemplateOptions :: PromptTemplateOptions
+ Langchain.Prelude: defaultReActAgent :: forall model (m :: Type -> Type). model -> [Tool m] -> ReActAgent model m
+ Langchain.Prelude: defaultRecursiveCharacterSplitterOps :: RecursiveCharacterSplitterOps
+ Langchain.Prelude: defaultRetryPolicy :: RetryPolicy
+ Langchain.Prelude: defaultSecurityPolicy :: SecurityPolicy
+ Langchain.Prelude: defaultTokenSplitterOps :: TokenSplitterOps
+ Langchain.Prelude: delete :: (VectorStore vs, MonadIO m, MonadError LangchainError m) => vs -> [Int64] -> m vs
+ Langchain.Prelude: deleteEntity :: MonadIO m => EntityMemory -> Text -> m ()
+ Langchain.Prelude: deleteSession :: MonadIO m => SessionMemoryStore mem -> SessionId -> m ()
+ 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: editFileTool :: forall (m :: Type -> Type). MonadIO m => Maybe CheckpointRegistry -> SecurityPolicy -> Tool m
+ 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: embeddingError :: Text -> Maybe Text -> Maybe Text -> LangchainError
+ Langchain.Prelude: emptyInMemoryVectorStore :: m -> InMemory m
+ 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: exportAllSessions :: MonadIO m => (mem -> m [Message]) -> SessionMemoryStore mem -> m (Map SessionId [Message])
+ Langchain.Prelude: exportSpansJson :: MonadIO m => OTelTracer -> m Text
+ Langchain.Prelude: extractMessageText :: Message -> Text
+ Langchain.Prelude: findFilesTool :: forall (m :: Type -> Type). MonadIO m => SecurityPolicy -> Tool m
+ Langchain.Prelude: formatEntities :: Map Text Text -> Text
+ Langchain.Prelude: fromDocuments :: (Embeddings m, MonadIO monad, MonadError LangchainError monad) => m -> [Document] -> monad (InMemory m)
+ Langchain.Prelude: fromTemplate :: Text -> PromptTemplate
+ Langchain.Prelude: fromTemplateWithFormat :: Text -> TemplateFormat -> Map Text Text -> PromptTemplate
+ Langchain.Prelude: fromTemplateWithOptions :: Text -> PromptTemplateOptions -> PromptTemplate
+ Langchain.Prelude: getAllEntities :: MonadIO m => EntityMemory -> m (Map Text Text)
+ 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: getEntity :: MonadIO m => EntityMemory -> Text -> m (Maybe Text)
+ Langchain.Prelude: getInMemoryLogs :: MonadIO m => InMemoryLogger -> m [LogEvent]
+ Langchain.Prelude: getRelevantDocuments :: (Retriever a, MonadIO m, MonadError LangchainError m) => a -> Text -> m [Document]
+ Langchain.Prelude: getSessionMemory :: MonadIO m => SessionMemoryStore mem -> SessionId -> m mem
+ Langchain.Prelude: getSpans :: MonadIO m => OTelTracer -> m [Span]
+ Langchain.Prelude: grepSearchTool :: forall (m :: Type -> Type). MonadIO m => SecurityPolicy -> Tool m
+ Langchain.Prelude: hasSession :: MonadIO m => SessionMemoryStore mem -> SessionId -> m Bool
+ Langchain.Prelude: imageMessage :: Role -> Text -> Text -> Message
+ Langchain.Prelude: importMessages :: (BaseMemory mem, MonadIO m, MonadError LangchainError m) => mem -> [Message] -> m ()
+ 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: listDirTool :: forall (m :: Type -> Type). MonadIO m => SecurityPolicy -> Tool m
+ Langchain.Prelude: listSessions :: MonadIO m => SessionMemoryStore mem -> m [SessionId]
+ 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: 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: 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: modelRunnable :: c -> ModelRunnable c
+ Langchain.Prelude: modelRunnableWithConfig :: c -> ModelConfig c -> ModelRunnable c
+ 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: newCombinedMemory :: [SomeMemory] -> CombinedMemory
+ Langchain.Prelude: newConversationBufferMemory :: MonadIO m => [Message] -> m ConversationBufferMemory
+ Langchain.Prelude: newEntityMemory :: MonadIO m => Map Text Text -> m EntityMemory
+ Langchain.Prelude: newFileSystemSuite :: forall (m :: Type -> Type). MonadIO m => SecurityPolicy -> IO [Tool m]
+ 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: newOTelTracer :: MonadIO m => Maybe Text -> m OTelTracer
+ 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: newProcessSuite :: forall (m :: Type -> Type). MonadIO m => SecurityPolicy -> IO [Tool 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: newSessionMemoryStore :: MonadIO m => (SessionId -> IO mem) -> m (SessionMemoryStore mem)
+ Langchain.Prelude: newSessionMemoryStoreWithTTL :: MonadIO m => NominalDiffTime -> (SessionId -> IO mem) -> m (SessionMemoryStore mem)
+ 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 CombinedMemory
+ Langchain.Prelude: newtype CommaSeparatedList
+ Langchain.Prelude: newtype ConfiguredModelRunnable c
+ Langchain.Prelude: newtype FileLoader
+ Langchain.Prelude: newtype InMemoryCache
+ Langchain.Prelude: newtype Plan
+ Langchain.Prelude: newtype PromptTemplateOptions
+ Langchain.Prelude: newtype SQLiteCache
+ 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: 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] -> Maybe (ModelConfig model) -> m AgentStep
+ Langchain.Prelude: readFileTool :: forall (m :: Type -> Type). MonadIO m => Maybe CheckpointRegistry -> SecurityPolicy -> Tool m
+ Langchain.Prelude: readOnlySecurityPolicy :: SecurityPolicy
+ Langchain.Prelude: reciprocalRankFusion :: Double -> [([Document], Double)] -> [(Document, Double)]
+ Langchain.Prelude: registerHandler :: MonadIO m => CallbackManager -> CallbackHandler -> m ()
+ Langchain.Prelude: renderPrompt :: PromptTemplate -> Map Text Text -> Either LangchainError Text
+ Langchain.Prelude: resetConversationBuffer :: MonadIO m => ConversationBufferMemory -> m ()
+ Langchain.Prelude: resetEntitiesToInitial :: MonadIO m => EntityMemory -> m ()
+ Langchain.Prelude: resetToInitial :: MonadIO m => TVar [Message] -> [Message] -> m ()
+ Langchain.Prelude: resetTokenBuffer :: MonadIO m => TokenBufferMemory -> m ()
+ Langchain.Prelude: resetWindowBuffer :: MonadIO m => WindowBufferMemory -> m ()
+ 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: runChatWithConfig :: forall c (m :: Type -> Type). (ChatModel c, MonadIO m) => c -> ModelConfig c -> RunnableTree m Text Text
+ Langchain.Prelude: runConfiguredModel :: forall c (m :: Type -> Type). (ChatModel c, MonadIO m) => c -> RunnableTree m ([Message], Maybe (ModelConfig c)) Message
+ Langchain.Prelude: runFallback :: forall (m :: Type -> Type) i o. RunnableTree m i o -> RunnableTree m i o -> RunnableTree m i o
+ 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: runModel :: forall c (m :: Type -> Type). (ChatModel c, MonadIO m) => c -> RunnableTree m [Message] Message
+ Langchain.Prelude: runModelWithConfig :: forall c (m :: Type -> Type). (ChatModel c, MonadIO m) => c -> ModelConfig 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: runReActAgentWithTrace :: (ToolBinder model m, MonadIO m, MonadError LangchainError m) => ReActAgent model m -> [Message] -> m AgentTrace
+ 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: searchHybrid :: MonadIO m => HybridRetriever -> Text -> Int -> m [Document]
+ Langchain.Prelude: searchHybridWithScores :: MonadIO m => HybridRetriever -> Text -> Int -> m [(Document, Double)]
+ Langchain.Prelude: sessionCount :: MonadIO m => SessionMemoryStore mem -> m Int
+ Langchain.Prelude: setEntity :: MonadIO m => EntityMemory -> Text -> Text -> m ()
+ 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: splitDocuments :: (Text -> [Text]) -> [Document] -> [Document]
+ 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: 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: systemMessage :: Text -> Message
+ Langchain.Prelude: textMessage :: Role -> Text -> Message
+ Langchain.Prelude: textModelRunnable :: c -> TextModelRunnable c
+ Langchain.Prelude: textModelRunnableWithConfig :: c -> ModelConfig c -> TextModelRunnable c
+ Langchain.Prelude: throwLangchainError :: MonadError LangchainError m => LangchainError -> m a
+ 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 SessionId = Text
+ 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: withCallbackManager :: forall model (m :: Type -> Type). CallbackManager -> ReActAgent model m -> ReActAgent model m
+ 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: withMaxIterations :: forall model (m :: Type -> Type). Int -> ReActAgent model m -> ReActAgent model m
+ Langchain.Prelude: withMemory :: forall mem model (m :: Type -> Type). BaseMemory mem => mem -> ReActAgent model m -> ReActAgent model m
+ Langchain.Prelude: withModelConfig :: forall model (m :: Type -> Type). Maybe (ModelConfig model) -> ReActAgent model m -> ReActAgent model m
+ 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: withSecurity :: forall (m :: Type -> Type). MonadIO m => ToolTier -> SecurityPolicy -> Tool m -> Tool m
+ Langchain.Prelude: withSpan :: (MonadIO m, MonadError LangchainError m) => OTelTracer -> Text -> Maybe Text -> SpanKind -> Map Text Text -> m a -> m a
+ Langchain.Prelude: withStopCondition :: forall model (m :: Type -> Type). (Message -> Bool) -> ReActAgent model m -> ReActAgent model m
+ Langchain.Prelude: withSystemPrompt :: forall model (m :: Type -> Type). Text -> ReActAgent model m -> ReActAgent model m
+ Langchain.Prelude: withToolErrorStrategy :: forall model (m :: Type -> Type). ToolErrorStrategy -> ReActAgent model m -> ReActAgent model m
+ Langchain.Prelude: withoutMemory :: forall model (m :: Type -> Type). ReActAgent model m -> ReActAgent model m
+ Langchain.Prelude: workspaceSandboxPolicy :: FilePath -> SecurityPolicy
+ Langchain.Prelude: writeFileTool :: forall (m :: Type -> Type). MonadIO m => Maybe CheckpointRegistry -> SecurityPolicy -> Tool m
+ Langchain.Prelude: }
+ 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.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.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.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.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.Runnable: (&>&) :: RunnableTree (ExceptT LangchainError IO) a b -> RunnableTree (ExceptT LangchainError IO) a c -> RunnableTree (ExceptT LangchainError IO) a (b, c)
+ Langchain.Runnable: (|>>) :: forall (m :: Type -> Type) a b c. RunnableTree m a b -> RunnableTree m b c -> RunnableTree m a c
+ Langchain.Runnable: ConfiguredModelRunnable :: c -> ConfiguredModelRunnable c
+ Langchain.Runnable: ModelRunnable :: !c -> !Maybe (ModelConfig c) -> ModelRunnable c
+ Langchain.Runnable: TextModelRunnable :: !c -> !Maybe (ModelConfig c) -> TextModelRunnable c
+ Langchain.Runnable: [Branch] :: forall i (m :: Type -> Type) o. (i -> m Bool) -> RunnableTree m i o -> RunnableTree m i o -> RunnableTree m i o
+ Langchain.Runnable: [Fallback] :: forall (m :: Type -> Type) i o. RunnableTree m i o -> RunnableTree m i o -> RunnableTree m i o
+ Langchain.Runnable: [Id] :: forall (m :: Type -> Type) i. RunnableTree m i i
+ Langchain.Runnable: [Lambda] :: forall i (m :: Type -> Type) o. (i -> m (Either LangchainError o)) -> RunnableTree m i o
+ Langchain.Runnable: [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.Runnable: [Prim] :: forall r (m :: Type -> Type) i o. (Runnable r m, RunnableInput r ~ i, RunnableOutput r ~ o) => r -> RunnableTree m i o
+ Langchain.Runnable: [Seq] :: forall (m :: Type -> Type) i mid o. RunnableTree m i mid -> RunnableTree m mid o -> RunnableTree m i o
+ Langchain.Runnable: [modelRunnableConfig] :: ModelRunnable c -> !Maybe (ModelConfig c)
+ Langchain.Runnable: [modelRunnableModel] :: ModelRunnable c -> !c
+ Langchain.Runnable: [textModelRunnableConfig] :: TextModelRunnable c -> !Maybe (ModelConfig c)
+ Langchain.Runnable: [textModelRunnableModel] :: TextModelRunnable c -> !c
+ Langchain.Runnable: bindConfig :: ModelConfig c -> ModelRunnable c -> ModelRunnable c
+ Langchain.Runnable: bindTextConfig :: ModelConfig c -> TextModelRunnable c -> TextModelRunnable c
+ Langchain.Runnable: class Runnable r (m :: Type -> Type) where {
+ Langchain.Runnable: data ModelRunnable c
+ Langchain.Runnable: data RunnableTree (m :: Type -> Type) i o
+ Langchain.Runnable: data TextModelRunnable c
+ Langchain.Runnable: infixl 1 |>>
+ Langchain.Runnable: infixl 2 &>&
+ Langchain.Runnable: instance (GHC.Classes.Eq c, GHC.Classes.Eq (Langchain.Model.ModelConfig c)) => GHC.Classes.Eq (Langchain.Runnable.ModelRunnable c)
+ Langchain.Runnable: instance (GHC.Classes.Eq c, GHC.Classes.Eq (Langchain.Model.ModelConfig c)) => GHC.Classes.Eq (Langchain.Runnable.TextModelRunnable c)
+ Langchain.Runnable: instance (GHC.Show.Show c, GHC.Show.Show (Langchain.Model.ModelConfig c)) => GHC.Show.Show (Langchain.Runnable.ModelRunnable c)
+ Langchain.Runnable: instance (GHC.Show.Show c, GHC.Show.Show (Langchain.Model.ModelConfig c)) => GHC.Show.Show (Langchain.Runnable.TextModelRunnable c)
+ Langchain.Runnable: instance (Langchain.Model.ChatModel c, Control.Monad.IO.Class.MonadIO m) => Langchain.Runnable.Runnable (Langchain.Runnable.ConfiguredModelRunnable c) m
+ Langchain.Runnable: instance (Langchain.Model.ChatModel c, Control.Monad.IO.Class.MonadIO m) => Langchain.Runnable.Runnable (Langchain.Runnable.ModelRunnable c) m
+ Langchain.Runnable: instance (Langchain.Model.ChatModel c, Control.Monad.IO.Class.MonadIO m) => Langchain.Runnable.Runnable (Langchain.Runnable.TextModelRunnable c) m
+ Langchain.Runnable: instance GHC.Classes.Eq c => GHC.Classes.Eq (Langchain.Runnable.ConfiguredModelRunnable c)
+ Langchain.Runnable: instance GHC.Show.Show c => GHC.Show.Show (Langchain.Runnable.ConfiguredModelRunnable c)
+ Langchain.Runnable: instance Langchain.Runnable.Runnable (Langchain.Tool.Tool m) m
+ Langchain.Runnable: interpret :: (MonadIO m, MonadError LangchainError m) => RunnableTree m i o -> i -> m o
+ Langchain.Runnable: invoke :: Runnable r m => r -> RunnableInput r -> m (Either LangchainError (RunnableOutput r))
+ Langchain.Runnable: modelRunnable :: c -> ModelRunnable c
+ Langchain.Runnable: modelRunnableWithConfig :: c -> ModelConfig c -> ModelRunnable c
+ Langchain.Runnable: newtype ConfiguredModelRunnable c
+ Langchain.Runnable: runBranch :: (i -> m Bool) -> RunnableTree m i o -> RunnableTree m i o -> RunnableTree m i o
+ Langchain.Runnable: runChat :: forall c (m :: Type -> Type). (ChatModel c, MonadIO m) => c -> RunnableTree m Text Text
+ Langchain.Runnable: runChatWithConfig :: forall c (m :: Type -> Type). (ChatModel c, MonadIO m) => c -> ModelConfig c -> RunnableTree m Text Text
+ Langchain.Runnable: runConfiguredModel :: forall c (m :: Type -> Type). (ChatModel c, MonadIO m) => c -> RunnableTree m ([Message], Maybe (ModelConfig c)) Message
+ Langchain.Runnable: runFallback :: forall (m :: Type -> Type) i o. RunnableTree m i o -> RunnableTree m i o -> RunnableTree m i o
+ Langchain.Runnable: runIdent :: forall (m :: Type -> Type) a. RunnableTree m a a
+ Langchain.Runnable: runLambda :: (i -> m (Either LangchainError o)) -> RunnableTree m i o
+ Langchain.Runnable: runModel :: forall c (m :: Type -> Type). (ChatModel c, MonadIO m) => c -> RunnableTree m [Message] Message
+ Langchain.Runnable: runModelWithConfig :: forall c (m :: Type -> Type). (ChatModel c, MonadIO m) => c -> ModelConfig c -> RunnableTree m [Message] Message
+ Langchain.Runnable: runPassthrough :: forall (m :: Type -> Type) a. RunnableTree m a a
+ Langchain.Runnable: runPrim :: forall r (m :: Type -> Type) i o. (Runnable r m, RunnableInput r ~ i, RunnableOutput r ~ o) => r -> RunnableTree m i o
+ Langchain.Runnable: runPure :: forall (m :: Type -> Type) i o. Monad m => (i -> o) -> RunnableTree m i o
+ Langchain.Runnable: textModelRunnable :: c -> TextModelRunnable c
+ Langchain.Runnable: textModelRunnableWithConfig :: c -> ModelConfig c -> TextModelRunnable c
+ Langchain.Runnable: type RunnableInput r;
+ Langchain.Runnable: type RunnableOutput r;
+ Langchain.Runnable: }
+ Langchain.Stream: ChainEnd :: Text -> Text -> Value -> StreamEvent
+ Langchain.Stream: ChainStart :: Text -> Text -> Value -> StreamEvent
+ Langchain.Stream: LLMChunk :: Text -> Text -> Maybe ToolCall -> StreamEvent
+ Langchain.Stream: LLMEnd :: Text -> Message -> Maybe TokenUsage -> StreamEvent
+ Langchain.Stream: LLMStart :: Text -> Text -> [Message] -> StreamEvent
+ Langchain.Stream: NodeEnd :: Text -> Text -> Value -> StreamEvent
+ Langchain.Stream: NodeStart :: Text -> Text -> Value -> StreamEvent
+ Langchain.Stream: TokenUsage :: Int -> Int -> Int -> TokenUsage
+ Langchain.Stream: ToolEnd :: Text -> Text -> Value -> StreamEvent
+ Langchain.Stream: ToolErrorEvent :: Text -> Text -> LangchainError -> StreamEvent
+ Langchain.Stream: ToolStart :: Text -> Text -> Value -> StreamEvent
+ Langchain.Stream: [chainInput] :: StreamEvent -> Value
+ Langchain.Stream: [chainName] :: StreamEvent -> Text
+ Langchain.Stream: [chainOutput] :: StreamEvent -> Value
+ Langchain.Stream: [chunkText] :: StreamEvent -> Text
+ Langchain.Stream: [completionTokens] :: TokenUsage -> Int
+ Langchain.Stream: [finalMessage] :: StreamEvent -> Message
+ Langchain.Stream: [inputMessages] :: StreamEvent -> [Message]
+ Langchain.Stream: [modelName] :: StreamEvent -> Text
+ Langchain.Stream: [nodeId] :: StreamEvent -> Text
+ Langchain.Stream: [nodeState] :: StreamEvent -> Value
+ Langchain.Stream: [promptTokens] :: TokenUsage -> Int
+ Langchain.Stream: [runId] :: StreamEvent -> Text
+ Langchain.Stream: [tokenUsage] :: StreamEvent -> Maybe TokenUsage
+ Langchain.Stream: [toolCallDelta] :: StreamEvent -> Maybe ToolCall
+ Langchain.Stream: [toolErrorPayload] :: StreamEvent -> LangchainError
+ Langchain.Stream: [toolInput] :: StreamEvent -> Value
+ Langchain.Stream: [toolName] :: StreamEvent -> Text
+ Langchain.Stream: [toolOutput] :: StreamEvent -> Value
+ Langchain.Stream: [totalTokens] :: TokenUsage -> Int
+ Langchain.Stream: callbackSource :: StreamCallback a -> StreamSource a
+ Langchain.Stream: collectEvents :: Monad m => EventStream m -> m [StreamEvent]
+ Langchain.Stream: data StreamEvent
+ Langchain.Stream: data TokenUsage
+ Langchain.Stream: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Stream.StreamEvent
+ Langchain.Stream: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Stream.TokenUsage
+ Langchain.Stream: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Stream.StreamEvent
+ Langchain.Stream: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Stream.TokenUsage
+ Langchain.Stream: instance GHC.Classes.Eq Langchain.Stream.StreamEvent
+ Langchain.Stream: instance GHC.Classes.Eq Langchain.Stream.TokenUsage
+ Langchain.Stream: instance GHC.Generics.Generic Langchain.Stream.StreamEvent
+ Langchain.Stream: instance GHC.Generics.Generic Langchain.Stream.TokenUsage
+ Langchain.Stream: instance GHC.Show.Show Langchain.Stream.StreamEvent
+ Langchain.Stream: instance GHC.Show.Show Langchain.Stream.TokenUsage
+ Langchain.Stream: printEvents :: EventStream (ExceptT LangchainError (ResourceT IO)) -> IO (Either LangchainError ())
+ Langchain.Stream: type ChatStream = EventStream StreamM
+ Langchain.Stream: type EventStream (m :: Type -> Type) = ConduitT () StreamEvent m ()
+ Langchain.Stream: type StreamCallback a = a -> IO () -> IO ()
+ Langchain.Stream: type StreamM = ExceptT LangchainError ResourceT IO
+ Langchain.Stream: type StreamSource a = ConduitT () a StreamM ()
+ 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: Tool :: Text -> Text -> Value -> (Value -> m (Either LangchainError Text)) -> Tool (m :: Type -> Type)
+ Langchain.Tool: [toolDescription] :: Tool (m :: Type -> Type) -> Text
+ Langchain.Tool: [toolExecute] :: Tool (m :: Type -> Type) -> Value -> m (Either LangchainError Text)
+ Langchain.Tool: [toolName] :: Tool (m :: Type -> Type) -> Text
+ Langchain.Tool: [toolSchema] :: Tool (m :: Type -> Type) -> Value
+ Langchain.Tool: createTool :: Text -> Text -> Value -> (Value -> m (Either LangchainError Text)) -> Tool m
+ Langchain.Tool: data Tool (m :: Type -> Type)
+ Langchain.Tool: instance GHC.Show.Show (Langchain.Tool.Tool m)
+ Langchain.Tool: toolToValue :: forall (m :: Type -> Type). Tool m -> Value
+ 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.Checkpoint: CheckpointRegistry :: !TVar [FileCheckpoint] -> !TVar [ToolAuditEntry] -> !TVar (Set FilePath) -> !TVar Int -> CheckpointRegistry
+ Langchain.Tool.Checkpoint: FileCheckpoint :: !Text -> !UTCTime -> !FilePath -> !Text -> !Text -> FileCheckpoint
+ Langchain.Tool.Checkpoint: ToolAuditEntry :: !Int -> !UTCTime -> !Text -> !Value -> !Either Text Text -> !ToolTier -> ToolAuditEntry
+ Langchain.Tool.Checkpoint: [auditArguments] :: ToolAuditEntry -> !Value
+ Langchain.Tool.Checkpoint: [auditResult] :: ToolAuditEntry -> !Either Text Text
+ Langchain.Tool.Checkpoint: [auditStep] :: ToolAuditEntry -> !Int
+ Langchain.Tool.Checkpoint: [auditTier] :: ToolAuditEntry -> !ToolTier
+ Langchain.Tool.Checkpoint: [auditTimestamp] :: ToolAuditEntry -> !UTCTime
+ Langchain.Tool.Checkpoint: [auditToolName] :: ToolAuditEntry -> !Text
+ Langchain.Tool.Checkpoint: [cpDescription] :: FileCheckpoint -> !Text
+ Langchain.Tool.Checkpoint: [cpFilePath] :: FileCheckpoint -> !FilePath
+ Langchain.Tool.Checkpoint: [cpId] :: FileCheckpoint -> !Text
+ Langchain.Tool.Checkpoint: [cpPreviousContent] :: FileCheckpoint -> !Text
+ Langchain.Tool.Checkpoint: [cpTimestamp] :: FileCheckpoint -> !UTCTime
+ Langchain.Tool.Checkpoint: [regAuditLog] :: CheckpointRegistry -> !TVar [ToolAuditEntry]
+ Langchain.Tool.Checkpoint: [regFileCheckpoints] :: CheckpointRegistry -> !TVar [FileCheckpoint]
+ Langchain.Tool.Checkpoint: [regNextId] :: CheckpointRegistry -> !TVar Int
+ Langchain.Tool.Checkpoint: [regReadFiles] :: CheckpointRegistry -> !TVar (Set FilePath)
+ Langchain.Tool.Checkpoint: createFileCheckpoint :: CheckpointRegistry -> FilePath -> Text -> Text -> IO Text
+ Langchain.Tool.Checkpoint: data CheckpointRegistry
+ Langchain.Tool.Checkpoint: data FileCheckpoint
+ Langchain.Tool.Checkpoint: data ToolAuditEntry
+ Langchain.Tool.Checkpoint: getAuditHistory :: CheckpointRegistry -> IO [ToolAuditEntry]
+ Langchain.Tool.Checkpoint: hasReadFile :: CheckpointRegistry -> FilePath -> IO Bool
+ Langchain.Tool.Checkpoint: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Tool.Checkpoint.FileCheckpoint
+ Langchain.Tool.Checkpoint: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Tool.Checkpoint.ToolAuditEntry
+ Langchain.Tool.Checkpoint: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Tool.Checkpoint.FileCheckpoint
+ Langchain.Tool.Checkpoint: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Tool.Checkpoint.ToolAuditEntry
+ Langchain.Tool.Checkpoint: instance GHC.Classes.Eq Langchain.Tool.Checkpoint.FileCheckpoint
+ Langchain.Tool.Checkpoint: instance GHC.Classes.Eq Langchain.Tool.Checkpoint.ToolAuditEntry
+ Langchain.Tool.Checkpoint: instance GHC.Generics.Generic Langchain.Tool.Checkpoint.FileCheckpoint
+ Langchain.Tool.Checkpoint: instance GHC.Generics.Generic Langchain.Tool.Checkpoint.ToolAuditEntry
+ Langchain.Tool.Checkpoint: instance GHC.Show.Show Langchain.Tool.Checkpoint.FileCheckpoint
+ Langchain.Tool.Checkpoint: instance GHC.Show.Show Langchain.Tool.Checkpoint.ToolAuditEntry
+ Langchain.Tool.Checkpoint: listCheckpoints :: CheckpointRegistry -> IO [FileCheckpoint]
+ Langchain.Tool.Checkpoint: listCheckpointsTool :: forall (m :: Type -> Type). MonadIO m => CheckpointRegistry -> Tool m
+ Langchain.Tool.Checkpoint: newCheckpointRegistry :: IO CheckpointRegistry
+ Langchain.Tool.Checkpoint: recordAuditEntry :: CheckpointRegistry -> Text -> Value -> Either Text Text -> ToolTier -> IO ()
+ Langchain.Tool.Checkpoint: recordFileRead :: CheckpointRegistry -> FilePath -> IO ()
+ Langchain.Tool.Checkpoint: rollbackCheckpoint :: CheckpointRegistry -> Maybe Text -> IO (Either LangchainError Text)
+ Langchain.Tool.Checkpoint: undoFileEditTool :: forall (m :: Type -> Type). MonadIO m => CheckpointRegistry -> Tool m
+ Langchain.Tool.FileSystem: editFileTool :: forall (m :: Type -> Type). MonadIO m => Maybe CheckpointRegistry -> SecurityPolicy -> Tool m
+ Langchain.Tool.FileSystem: findFilesTool :: forall (m :: Type -> Type). MonadIO m => SecurityPolicy -> Tool m
+ Langchain.Tool.FileSystem: grepSearchTool :: forall (m :: Type -> Type). MonadIO m => SecurityPolicy -> Tool m
+ Langchain.Tool.FileSystem: isDefaultIgnored :: FilePath -> Bool
+ Langchain.Tool.FileSystem: listDirTool :: forall (m :: Type -> Type). MonadIO m => SecurityPolicy -> Tool m
+ Langchain.Tool.FileSystem: matchGlobPattern :: String -> String -> Bool
+ Langchain.Tool.FileSystem: newFileSystemSuite :: forall (m :: Type -> Type). MonadIO m => SecurityPolicy -> IO [Tool m]
+ Langchain.Tool.FileSystem: readFileTool :: forall (m :: Type -> Type). MonadIO m => Maybe CheckpointRegistry -> SecurityPolicy -> Tool m
+ Langchain.Tool.FileSystem: writeFileTool :: forall (m :: Type -> Type). MonadIO m => Maybe CheckpointRegistry -> SecurityPolicy -> 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.Process: ManagedProcess :: !Text -> !Text -> !Maybe FilePath -> !UTCTime -> !TVar ProcessStatus -> !TVar [Text] -> !ProcessHandle -> !Async (ExitCode, NominalDiffTime) -> ManagedProcess
+ Langchain.Tool.Process: ProcessManager :: !TVar (Map Text ManagedProcess) -> !TVar Int -> ProcessManager
+ Langchain.Tool.Process: StatusFinished :: !ExitCode -> !UTCTime -> !NominalDiffTime -> ProcessStatus
+ Langchain.Tool.Process: StatusKilled :: !UTCTime -> !NominalDiffTime -> ProcessStatus
+ Langchain.Tool.Process: StatusRunning :: ProcessStatus
+ Langchain.Tool.Process: [pmNextId] :: ProcessManager -> !TVar Int
+ Langchain.Tool.Process: [pmProcesses] :: ProcessManager -> !TVar (Map Text ManagedProcess)
+ Langchain.Tool.Process: [procCommand] :: ManagedProcess -> !Text
+ Langchain.Tool.Process: [procCwd] :: ManagedProcess -> !Maybe FilePath
+ Langchain.Tool.Process: [procHandle] :: ManagedProcess -> !ProcessHandle
+ Langchain.Tool.Process: [procId] :: ManagedProcess -> !Text
+ Langchain.Tool.Process: [procOutputVar] :: ManagedProcess -> !TVar [Text]
+ Langchain.Tool.Process: [procStartTime] :: ManagedProcess -> !UTCTime
+ Langchain.Tool.Process: [procStatusVar] :: ManagedProcess -> !TVar ProcessStatus
+ Langchain.Tool.Process: [procWaiter] :: ManagedProcess -> !Async (ExitCode, NominalDiffTime)
+ Langchain.Tool.Process: commandKillTool :: forall (m :: Type -> Type). MonadIO m => ProcessManager -> SecurityPolicy -> Tool m
+ Langchain.Tool.Process: commandRunTool :: forall (m :: Type -> Type). MonadIO m => ProcessManager -> SecurityPolicy -> Tool m
+ Langchain.Tool.Process: commandStatusTool :: forall (m :: Type -> Type). MonadIO m => ProcessManager -> SecurityPolicy -> Tool m
+ Langchain.Tool.Process: data ManagedProcess
+ Langchain.Tool.Process: data ProcessManager
+ Langchain.Tool.Process: data ProcessStatus
+ Langchain.Tool.Process: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Tool.Process.ProcessStatus
+ Langchain.Tool.Process: instance GHC.Classes.Eq Langchain.Tool.Process.ProcessStatus
+ Langchain.Tool.Process: instance GHC.Generics.Generic Langchain.Tool.Process.ProcessStatus
+ Langchain.Tool.Process: instance GHC.Show.Show Langchain.Tool.Process.ProcessStatus
+ Langchain.Tool.Process: newProcessManager :: IO ProcessManager
+ Langchain.Tool.Process: newProcessSuite :: forall (m :: Type -> Type). MonadIO m => SecurityPolicy -> IO [Tool m]
+ Langchain.Tool.Security: ApprovalRequest :: !Text -> !ToolTier -> !Value -> !Text -> ApprovalRequest
+ Langchain.Tool.Security: Approved :: ApprovalDecision
+ Langchain.Tool.Security: Denied :: !Text -> ApprovalDecision
+ Langchain.Tool.Security: SecurityPolicy :: !Set ToolTier -> !Maybe FilePath -> ![Text] -> !Maybe [Text] -> ![Text] -> !Maybe (ApprovalRequest -> IO ApprovalDecision) -> SecurityPolicy
+ Langchain.Tool.Security: TierAdmin :: ToolTier
+ Langchain.Tool.Security: TierExecution :: ToolTier
+ Langchain.Tool.Security: TierReadOnly :: ToolTier
+ Langchain.Tool.Security: TierWorkspaceWrite :: ToolTier
+ Langchain.Tool.Security: [policyAllowedTiers] :: SecurityPolicy -> !Set ToolTier
+ Langchain.Tool.Security: [policyApprovalHook] :: SecurityPolicy -> !Maybe (ApprovalRequest -> IO ApprovalDecision)
+ Langchain.Tool.Security: [policyCommandBlacklist] :: SecurityPolicy -> ![Text]
+ Langchain.Tool.Security: [policyCommandWhitelist] :: SecurityPolicy -> !Maybe [Text]
+ Langchain.Tool.Security: [policySensitivePatterns] :: SecurityPolicy -> ![Text]
+ Langchain.Tool.Security: [policyWorkspaceRoot] :: SecurityPolicy -> !Maybe FilePath
+ Langchain.Tool.Security: [reqArguments] :: ApprovalRequest -> !Value
+ Langchain.Tool.Security: [reqDescription] :: ApprovalRequest -> !Text
+ Langchain.Tool.Security: [reqTier] :: ApprovalRequest -> !ToolTier
+ Langchain.Tool.Security: [reqToolName] :: ApprovalRequest -> !Text
+ Langchain.Tool.Security: checkTierPermission :: SecurityPolicy -> ToolTier -> Either LangchainError ()
+ Langchain.Tool.Security: data ApprovalDecision
+ Langchain.Tool.Security: data ApprovalRequest
+ Langchain.Tool.Security: data SecurityPolicy
+ Langchain.Tool.Security: data ToolTier
+ Langchain.Tool.Security: defaultSecurityPolicy :: SecurityPolicy
+ Langchain.Tool.Security: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Tool.Security.ApprovalDecision
+ Langchain.Tool.Security: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Tool.Security.ApprovalRequest
+ Langchain.Tool.Security: instance Data.Aeson.Types.FromJSON.FromJSON Langchain.Tool.Security.ToolTier
+ Langchain.Tool.Security: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Tool.Security.ApprovalDecision
+ Langchain.Tool.Security: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Tool.Security.ApprovalRequest
+ Langchain.Tool.Security: instance Data.Aeson.Types.ToJSON.ToJSON Langchain.Tool.Security.ToolTier
+ Langchain.Tool.Security: instance GHC.Classes.Eq Langchain.Tool.Security.ApprovalDecision
+ Langchain.Tool.Security: instance GHC.Classes.Eq Langchain.Tool.Security.ApprovalRequest
+ Langchain.Tool.Security: instance GHC.Classes.Eq Langchain.Tool.Security.ToolTier
+ Langchain.Tool.Security: instance GHC.Classes.Ord Langchain.Tool.Security.ToolTier
+ Langchain.Tool.Security: instance GHC.Enum.Bounded Langchain.Tool.Security.ToolTier
+ Langchain.Tool.Security: instance GHC.Enum.Enum Langchain.Tool.Security.ToolTier
+ Langchain.Tool.Security: instance GHC.Generics.Generic Langchain.Tool.Security.ApprovalDecision
+ Langchain.Tool.Security: instance GHC.Generics.Generic Langchain.Tool.Security.ApprovalRequest
+ Langchain.Tool.Security: instance GHC.Generics.Generic Langchain.Tool.Security.SecurityPolicy
+ Langchain.Tool.Security: instance GHC.Generics.Generic Langchain.Tool.Security.ToolTier
+ Langchain.Tool.Security: instance GHC.Read.Read Langchain.Tool.Security.ToolTier
+ Langchain.Tool.Security: instance GHC.Show.Show Langchain.Tool.Security.ApprovalDecision
+ Langchain.Tool.Security: instance GHC.Show.Show Langchain.Tool.Security.ApprovalRequest
+ Langchain.Tool.Security: instance GHC.Show.Show Langchain.Tool.Security.ToolTier
+ Langchain.Tool.Security: isSensitivePath :: SecurityPolicy -> FilePath -> Bool
+ Langchain.Tool.Security: readOnlySecurityPolicy :: SecurityPolicy
+ Langchain.Tool.Security: unrestrictedSecurityPolicy :: SecurityPolicy
+ Langchain.Tool.Security: validateCommandSafety :: SecurityPolicy -> Text -> Either LangchainError ()
+ Langchain.Tool.Security: validateSandboxPath :: MonadIO m => SecurityPolicy -> FilePath -> m (Either LangchainError FilePath)
+ Langchain.Tool.Security: withSecurity :: forall (m :: Type -> Type). MonadIO m => ToolTier -> SecurityPolicy -> Tool m -> Tool m
+ Langchain.Tool.Security: workspaceSandboxPolicy :: FilePath -> SecurityPolicy
+ Langchain.Tool.Shell: secureShellTool :: forall (m :: Type -> Type). MonadIO m => SecurityPolicy -> Tool m
+ 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.Embeddings m => Langchain.VectorStore.Core.VectorStore (Langchain.VectorStore.InMemory.InMemory m)
+ Langchain.VectorStore.InMemory: norm :: [Float] -> Float
Files
- CHANGELOG.md +5/−0
- README.md +35/−11
- langchain-hs-core.cabal +88/−17
- src/Langchain/Agent.hs +18/−0
- src/Langchain/Agent/PlanAndExecute.hs +215/−0
- src/Langchain/Agent/ReAct.hs +481/−0
- src/Langchain/Cache.hs +15/−0
- src/Langchain/Cache/Core.hs +203/−0
- src/Langchain/Callback.hs +15/−0
- src/Langchain/Callback/Manager.hs +100/−0
- src/Langchain/Chain.hs +15/−0
- src/Langchain/Chain/RetrievalQA.hs +75/−0
- src/Langchain/Core/Error.hs +0/−178
- src/Langchain/Core/Model.hs +0/−53
- src/Langchain/Core/Model/Types.hs +0/−199
- src/Langchain/Core/Monad.hs +0/−55
- src/Langchain/Core/Runnable.hs +0/−194
- src/Langchain/Core/Stream.hs +0/−169
- src/Langchain/Core/Tool.hs +0/−65
- src/Langchain/DocumentLoader.hs +17/−0
- src/Langchain/DocumentLoader/Core.hs +77/−0
- src/Langchain/DocumentLoader/DirectoryLoader.hs +120/−0
- src/Langchain/DocumentLoader/FileLoader.hs +55/−0
- src/Langchain/Embeddings.hs +38/−0
- src/Langchain/Embeddings/Core.hs +13/−0
- src/Langchain/Error.hs +190/−0
- src/Langchain/Guardrail.hs +16/−0
- src/Langchain/Guardrail/Core.hs +143/−0
- src/Langchain/Memory.hs +33/−0
- src/Langchain/Memory/Combined.hs +49/−0
- src/Langchain/Memory/Core.hs +455/−0
- src/Langchain/Memory/Entity.hs +103/−0
- src/Langchain/Memory/Session.hs +159/−0
- src/Langchain/Memory/Summary.hs +134/−0
- src/Langchain/Model.hs +53/−0
- src/Langchain/Model/Types.hs +200/−0
- src/Langchain/Monad.hs +55/−0
- src/Langchain/Observability.hs +275/−0
- src/Langchain/OutputParser.hs +18/−0
- src/Langchain/OutputParser/Core.hs +84/−0
- src/Langchain/OutputParser/Structured.hs +279/−0
- src/Langchain/Prelude.hs +382/−0
- src/Langchain/PromptTemplate.hs +17/−0
- src/Langchain/PromptTemplate/Prompt.hs +104/−0
- src/Langchain/PromptTemplate/String.hs +110/−0
- src/Langchain/Resilience.hs +18/−0
- src/Langchain/Resilience/CircuitBreaker.hs +119/−0
- src/Langchain/Resilience/Retry.hs +114/−0
- src/Langchain/Retriever.hs +20/−0
- src/Langchain/Retriever/BM25.hs +151/−0
- src/Langchain/Retriever/Core.hs +66/−0
- src/Langchain/Retriever/Hybrid.hs +155/−0
- src/Langchain/Runnable.hs +273/−0
- src/Langchain/Stream.hs +169/−0
- src/Langchain/TextSplitter/Character.hs +111/−0
- src/Langchain/TextSplitter/Code.hs +167/−0
- src/Langchain/TextSplitter/Markdown.hs +137/−0
- src/Langchain/TextSplitter/RecursiveCharacter.hs +96/−0
- src/Langchain/TextSplitter/Token.hs +90/−0
- src/Langchain/Tool.hs +65/−0
- src/Langchain/Tool/Binding.hs +49/−0
- src/Langchain/Tool/Checkpoint.hs +308/−0
- src/Langchain/Tool/Core.hs +17/−0
- src/Langchain/Tool/FileSystem.hs +914/−0
- src/Langchain/Tool/GenericSchema.hs +175/−0
- src/Langchain/Tool/Process.hs +473/−0
- src/Langchain/Tool/Security.hs +304/−0
- src/Langchain/Tool/Shell.hs +87/−0
- src/Langchain/VectorStore.hs +15/−0
- src/Langchain/VectorStore/Core.hs +56/−0
- src/Langchain/VectorStore/InMemory.hs +99/−0
- test/Spec.hs +15/−5
- test/Test/Langchain/Callback.hs +27/−0
- test/Test/Langchain/Core/Model.hs +0/−60
- test/Test/Langchain/Core/Monad.hs +0/−30
- test/Test/Langchain/Core/Runnable.hs +0/−100
- test/Test/Langchain/Core/Stream.hs +0/−33
- test/Test/Langchain/Core/TestModel.hs +0/−29
- test/Test/Langchain/Core/Tool.hs +0/−39
- test/Test/Langchain/Model.hs +60/−0
- test/Test/Langchain/Monad.hs +30/−0
- test/Test/Langchain/Observability.hs +79/−0
- test/Test/Langchain/PromptTemplate.hs +68/−0
- test/Test/Langchain/Resilience.hs +65/−0
- test/Test/Langchain/Retriever.hs +82/−0
- test/Test/Langchain/Runnable.hs +100/−0
- test/Test/Langchain/Stream.hs +33/−0
- test/Test/Langchain/TestModel.hs +29/−0
- test/Test/Langchain/Tool.hs +39/−0
CHANGELOG.md view
@@ -2,6 +2,11 @@ All notable changes to this package will be documented in this file. +## 0.0.6.0 - 2026-10-02++- Updated bounds for GHC 9.12 and modern Stackage LTS releases.+- Standalone stack configuration and verified test suite.+ ## 0.0.5.0 - 2026-09-10 - Initial standalone Hackage release of `langchain-hs-core`.
README.md view
@@ -1,24 +1,48 @@ # `langchain-hs-core` -> Zero-dependency pure core of the `langchain-hs` ecosystem.+> The foundational, effect-polymorphic core framework of the `langchain-hs` AI ecosystem. -`langchain-hs-core` provides pure GADT abstract syntax trees, effect-polymorphic chat models, streaming protocols, tools, and decoupled monads without ANY HTTP or network dependencies.+`langchain-hs-core` provides pure GADT abstract syntax trees, effect-polymorphic chat models, streaming protocols, tools, agents, vector stores, retrievers, document loaders, memory, cache, output parsers, guardrails, resilience, observability, and the canonical `Langchain.Prelude` without concrete provider-specific dependencies. -## Key Primitives+## Key Primitives & Modules -- **`RunnableTree m a b`**: Pure GADT abstract syntax tree for pipeline composition via `|>>` (sequential), `&>&` (parallel), and `>>>#` (fallback).-- **`ChatModel m`**: Effect-polymorphic typeclass for LLMs.-- **`ContentBlock`**: Multi-modal message content (text, image, tool calls, tool results).-- **`Tool` & `FunctionDefinition`**: Strongly-typed tool execution and JSON parameter schemas.-- **`StreamEvent` & `LLMChunk`**: Conduit-based incremental token streaming.-- **`LangchainT env m a`**: Decoupled reader monad transformer parameterized over custom environment `env`.+- **Runnables & Pure ASTs (`Langchain.Runnable`)**: Pure GADT abstract syntax tree (`RunnableTree m i o`) for pipeline composition via `|>>` (sequential), `&>&` (parallel fan-out), `runLambda`, `runPrim`, and deferred evaluation via `interpret`.+- **Chat Models & Types (`Langchain.Model`, `Langchain.Model.Types`)**: Effect-polymorphic `ChatModel` typeclass, `Message`, `Role`, `ContentBlock` (multi-modal text, images, tool calls, tool results).+- **Streaming Protocols (`Langchain.Stream`)**: Conduit-based incremental token streaming via `StreamEvent` and `LLMChunk`.+- **Tools & Dynamic Calling (`Langchain.Tool`, `Langchain.Tool.Binding`)**: Effect-polymorphic `Tool m`, `createTool`, Generic Schemas, and built-in tools (`Calculator`, `FileSystem`, `Shell`). Provider-agnostic tool binding via `ToolBinder`.+- **Agents (`Langchain.Agent.ReAct`, `Langchain.Agent.PlanAndExecute`)**:+ - **ReAct Agent**: Full reasoning + acting agent loop with tool inspection, memory integration, callbacks, and step traces.+ - **Plan-and-Execute Agent**: Two-phase agent decomposing complex user goals into discrete execution plans.+- **Prompt Templates (`Langchain.PromptTemplate`)**: String prompt templates with `{var}` f-string interpolation and pre-bound partial variables.+- **Vector Stores (`Langchain.VectorStore`)**: Abstract `VectorStore` typeclass and thread-safe `InMemoryVectorStore`.+- **Retrievers (`Langchain.Retriever`)**: BM25 keyword search (`BM25Retriever`), vector similarity retriever, and Reciprocal Rank Fusion (`HybridRetriever`).+- **Document Loaders & Text Splitters (`Langchain.DocumentLoader`, `Langchain.TextSplitter`)**: `Document` metadata abstraction and splitters for character chunks, Markdown, programming code, and tokens.+- **Memory & Cache (`Langchain.Memory`, `Langchain.Cache`)**: `BaseMemory`, `ConversationBufferMemory`, `WindowBufferMemory`, and STM-backed in-memory `LLMCache`.+- **Output Parsers (`Langchain.OutputParser`)**: Strongly typed JSON Schema generation (`JsonOutputParser`) and structured parsing.+- **Guardrails & Safety (`Langchain.Guardrails`)**: Input and output content moderation, PII redaction, and semantic safety validations.+- **Resilience & Fault Tolerance (`Langchain.Resilience`)**: State-machine `CircuitBreaker`, exponential backoff `RetryConfig`, and fallback handlers.+- **Observability & Tracing (`Langchain.Observability`, `Langchain.Callback`)**: OpenTelemetry distributed tracing spans, structured JSON event logging, and lifecycle event callbacks.+- **Chains (`Langchain.Chain.RetrievalQA`)**: End-to-end question answering pipeline over indexed knowledge bases.+- **Langchain Monad (`Langchain.Monad`)**: Decoupled `LangchainT env m a` reader and error transformer.+- **Canonical Prelude (`Langchain.Prelude`)**: Batteries-included single-import entry point providing the full core framework API. ## Installation +Add to your `cabal` or `package.yaml` dependencies:+ ```cabal-build-depends: langchain-hs-core >= 0.0.5 && < 0.0.6+build-depends:+ langchain-hs-core >= 0.0.6.0 && < 0.0.7.0 ``` +To connect concrete LLMs, combine with satellite provider packages:+- `langchain-hs-ollama`: Local & offline models (Llama 3, DeepSeek, Gemma).+- `langchain-hs-openai`: OpenAI, OpenRouter, and OpenAI-compatible cloud endpoints.+- `langchain-hs-gemini`: Google Gemini with multi-modal support.+- `langchain-hs-mcp`: Model Context Protocol tool client.++For cyclic state machines, checkpointers, and multi-agent coordination, add `langchain-hs-graph` (or use the umbrella `langchain-hs` package).+ ## License -MIT License. See [LICENSE](LICENSE).+Distributed under the **MIT License**. See [LICENSE](LICENSE) for details.
langchain-hs-core.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: langchain-hs-core-version: 0.0.5.0+version: 0.0.6.0 synopsis: Core typeclasses and pure AST primitives for langchain-hs description: Zero HTTP dependency core package containing ChatModel, RunnableTree, ContentBlock, Tool, and StreamEvent primitives. category: Web, AI, Control@@ -31,13 +31,67 @@ library exposed-modules:- Langchain.Core.Error- Langchain.Core.Model- Langchain.Core.Model.Types- Langchain.Core.Monad- Langchain.Core.Stream- Langchain.Core.Runnable- Langchain.Core.Tool+ 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 other-modules: Paths_langchain_hs_core hs-source-dirs:@@ -62,9 +116,15 @@ , bytestring >=0.10 && <0.13 , conduit ==1.3.* , containers >=0.6 && <0.9- , deepseq >=1.4 && <1.6+ , directory ==1.3.*+ , filepath >=1.4 && <2+ , format-heavy ==0.1.* , mtl >=2.2 && <2.4+ , process ==1.6.*+ , random >=1.2 && <1.4 , resourcet >=1.2 && <1.4+ , scientific ==0.3.*+ , sqlite-simple >=0.4.18 && <0.5 , stm ==2.5.* , text >=1.2 && <3 , time >=1.9 && <1.15@@ -74,12 +134,17 @@ type: exitcode-stdio-1.0 main-is: Spec.hs other-modules:- Test.Langchain.Core.Model- Test.Langchain.Core.Monad- Test.Langchain.Core.Runnable- Test.Langchain.Core.Stream- Test.Langchain.Core.TestModel- Test.Langchain.Core.Tool+ Test.Langchain.Callback+ Test.Langchain.Model+ Test.Langchain.Monad+ Test.Langchain.Observability+ Test.Langchain.PromptTemplate+ Test.Langchain.Resilience+ Test.Langchain.Retriever+ Test.Langchain.Runnable+ Test.Langchain.Stream+ Test.Langchain.TestModel+ Test.Langchain.Tool Paths_langchain_hs_core hs-source-dirs: test@@ -105,10 +170,16 @@ , bytestring >=0.10 && <0.13 , conduit ==1.3.* , containers >=0.6 && <0.9- , deepseq >=1.4 && <1.6+ , directory ==1.3.*+ , filepath >=1.4 && <2+ , format-heavy ==0.1.* , langchain-hs-core , mtl >=2.2 && <2.4- , resourcet+ , process ==1.6.*+ , random >=1.2 && <1.4+ , resourcet >=1.2 && <1.4+ , scientific ==0.3.*+ , sqlite-simple >=0.4.18 && <0.5 , stm ==2.5.* , tasty >=1.4 , tasty-hunit >=0.10
+ src/Langchain/Agent.hs view
@@ -0,0 +1,18 @@+{- |+Module : Langchain.Agent+Description : Autonomous agents+Copyright : (c) 2025-2026 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++Autonomous agent architectures including ReAct (Reasoning and Acting)+and Plan-and-Execute multi-step workflows.+-}+module Langchain.Agent+ ( module Langchain.Agent.ReAct+ , module Langchain.Agent.PlanAndExecute+ ) where++import Langchain.Agent.PlanAndExecute+import Langchain.Agent.ReAct
+ src/Langchain/Agent/PlanAndExecute.hs view
@@ -0,0 +1,215 @@+{-# 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, defaultReActAgent, runReActAgent)+import Langchain.Error (LangchainError, agentError)+import Langchain.Model+ ( ChatModel (..)+ , extractMessageText+ , userMessage+ )+import Langchain.OutputParser.Structured (StructuredOutput, TypeSchema, structuredInvoke)+import Langchain.Tool (Tool)+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 ::+ -- | Step executor instance (agent, model with tools, or custom runner)+ e ->+ -- | Step instruction prompt to execute+ Text ->+ -- | Textual result produced by executing the step+ m Text++instance+ {-# OVERLAPPING #-}+ (ToolBinder model n, MonadIO n, MonadError LangchainError n) =>+ StepExecutor (ReActAgent model n) n+ where+ executeStep agent prompt = do+ msg <- runReActAgent agent [userMessage prompt]+ pure $ extractMessageText msg++instance+ {-# OVERLAPPING #-}+ (ToolBinder model n, MonadIO n, MonadError LangchainError n) =>+ StepExecutor (model, [Tool n]) n+ where+ executeStep (model, tools) =+ executeStep (defaultReActAgent model tools)++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 ::+ -- | LLM model responsible for creating the execution plan+ planner ->+ -- | StepExecutor instance used to carry out each plan step+ executor ->+ -- | Optional custom prompt template for the planner+ Maybe Text ->+ -- | Configured PlanAndExecuteAgent instance+ PlanAndExecuteAgent planner executor+newPlanAndExecuteAgent = PlanAndExecuteAgent++-- | Construct a PlanAndExecuteAgent with tools using a ReActAgent as the step executor+newPlanAndExecuteAgentWithTools ::+ -- | LLM model responsible for creating the execution plan+ planner ->+ -- | LLM model for the ReAct step executor+ model ->+ -- | Available tools for the ReAct step executor+ [Tool m] ->+ -- | Optional custom prompt template for the planner+ Maybe Text ->+ -- | Configured PlanAndExecuteAgent with tool-enabled ReAct executor+ PlanAndExecuteAgent planner (ReActAgent model m)+newPlanAndExecuteAgentWithTools planner model tools =+ PlanAndExecuteAgent planner (defaultReActAgent 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) =>+ -- | Configured PlanAndExecuteAgent instance+ PlanAndExecuteAgent planner executor ->+ -- | High-level user goal to plan and solve+ Text ->+ -- | Final synthesized answer solving the user goal+ 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 view
@@ -0,0 +1,481 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}++{- |+Module : Langchain.Agent.ReAct+Description : Effect-polymorphic ReAct (Reasoning + Acting) Agent+Copyright : (c) 2025-2026 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++ReAct (Reasoning and Acting) agent loop that supports:++* __Effect-polymorphic tools__: Tools operate in the agent's monad @m@.+* __Memory integration__: Pluggable 'BaseMemory' via 'withMemory'.+* __Model-specific configs__: Merged with tool definitions via 'withModelConfig'.+* __Observability__: Lifecycle hooks via 'withCallbackManager'.+* __Error resilience__: Configurable 'ToolErrorStrategy'.+* __Iteration limits__: Prevents infinite loops via 'withMaxIterations'.+* __Early stop conditions__: User-defined stopping predicates via 'withStopCondition'.+* __Reasoning traces__: Inspection via 'runReActAgentWithTrace'.++=== Quick Start++@+let tools = [shellTool]+let agent = defaultReActAgent model tools+result <- runReActAgent agent [userMessage "What is 2+2?"]+@++=== With Memory and Tracing++@+mem <- newWindowBufferMemory 10 []+let agent =+ withMemory mem $+ withSystemPrompt "You are a helpful math tutor." $+ defaultReActAgent model tools+trace <- runReActAgentWithTrace agent [userMessage "What is the square root of 144?"]+@+-}+module Langchain.Agent.ReAct+ ( -- * Core Types+ AgentStep (..)+ , AgentTrace (..)+ , ReActAgent (..)+ , ToolErrorStrategy (..)++ -- * Construction+ , defaultReActAgent++ -- * Configuration (builder-style)+ , withMemory+ , withoutMemory+ , withSystemPrompt+ , withMaxIterations+ , withModelConfig+ , withCallbackManager+ , withStopCondition+ , withToolErrorStrategy++ -- * Execution+ , reactStep+ , runReActAgent+ , runReActAgentWithTrace+ ) where++import Control.Monad (forM, when)+import Control.Monad.Except (MonadError, catchError, throwError)+import Control.Monad.IO.Class (MonadIO, liftIO)+import Data.List (find)+import Data.Text (Text)+import qualified Data.Text as T+import Data.Time.Clock (diffUTCTime, getCurrentTime)++import Langchain.Callback.Manager (CallbackEvent (..), CallbackManager, dispatchEvent)+import Langchain.Error (LangchainError, agentError, errorMessage)+import Langchain.Memory.Core (BaseMemory (..), SomeMemory (..))+import Langchain.Model+import qualified Langchain.Model.Types as M+import Langchain.Tool+import Langchain.Tool.Binding (ToolBinder (..))++-- | How the agent handles tool execution failures.+data ToolErrorStrategy+ = -- | Feed the error message back to the LLM as a Tool observation so it can retry or recover.+ ContinueOnError+ | -- | Abort the entire agent loop immediately on any tool error.+ FailOnError+ deriving (Eq, Show)++-- | A single step in the ReAct reasoning chain.+data AgentStep+ = -- | The model decided to call one or more tools.+ AgentAction Message [ToolCall]+ | -- | The model produced a final answer (no tool calls).+ AgentFinish Message+ deriving (Eq, Show)++-- | Complete reasoning trace: intermediate steps + final answer.+data AgentTrace = AgentTrace+ { traceSteps :: [(AgentStep, [Message])]+ -- ^ Each pair is (the step, observation messages produced)+ , traceFinalAnswer :: Message+ -- ^ The final answer message+ }+ deriving (Eq, Show)++{- | ReAct Agent configuration.++Holds the LLM model, the effect-polymorphic tools @[Tool m]@, and optional+configuration for memory, callbacks, iterations, model config, and error strategy.+Use 'defaultReActAgent' to construct, then configure with the @with*@ builder functions.+-}+data ReActAgent model m = ReActAgent+ { agentModel :: model+ -- ^ The LLM model backing this agent+ , agentTools :: [Tool m]+ -- ^ Available tools the agent can invoke in monad @m@+ , agentMaxIterations :: Int+ -- ^ Maximum reasoning iterations before giving up (default: 100)+ , agentSystemPrompt :: Maybe Text+ -- ^ Optional system prompt prepended to every invocation+ , agentModelConfig :: Maybe (ModelConfig model)+ -- ^ Optional provider-specific model configuration (temperature, top_p, etc.)+ , agentMemory :: Maybe SomeMemory+ -- ^ Optional 'BaseMemory' for persistent conversation history+ , agentCallbackManager :: Maybe CallbackManager+ -- ^ Optional callback manager for observability hooks+ , agentStopCondition :: Maybe (Message -> Bool)+ -- ^ Optional early-stop predicate checked after each LLM response+ , agentToolErrorStrategy :: ToolErrorStrategy+ -- ^ How to handle tool execution errors (default: 'ContinueOnError')+ }++{- | Construct a ReAct agent with sensible defaults.++All optional fields start as 'Nothing' / defaults. Use the @with*@ functions to configure.+-}+defaultReActAgent ::+ -- | The LLM chat model backing this agent+ model ->+ -- | Available effect-polymorphic tools+ [Tool m] ->+ -- | Initialized ReActAgent with default settings+ ReActAgent model m+defaultReActAgent model tools =+ ReActAgent+ { agentModel = model+ , agentTools = tools+ , agentMaxIterations = 100+ , agentSystemPrompt = Nothing+ , agentModelConfig = Nothing+ , agentMemory = Nothing+ , agentCallbackManager = Nothing+ , agentStopCondition = Nothing+ , agentToolErrorStrategy = ContinueOnError+ }++{- | Attach a 'BaseMemory' instance for persistent conversation history.++When memory is set, 'runReActAgent' will:++1. Load existing messages from memory before the first LLM call.+2. Save the user query + final answer to memory after completion.+-}+withMemory ::+ (BaseMemory mem) =>+ -- | BaseMemory instance to store conversation messages+ mem ->+ -- | Current agent instance+ ReActAgent model m ->+ -- | Agent configured with persistent memory+ ReActAgent model m+withMemory mem agent = agent {agentMemory = Just (SomeMemory mem)}++-- | Remove memory from the agent.+withoutMemory ::+ -- | Current agent instance+ ReActAgent model m ->+ -- | Agent with memory detached+ ReActAgent model m+withoutMemory agent = agent {agentMemory = Nothing}++-- | Set the system prompt that instructs the agent's persona and behavior.+withSystemPrompt ::+ -- | System instructions / persona text+ Text ->+ -- | Current agent instance+ ReActAgent model m ->+ -- | Agent configured with system prompt+ ReActAgent model m+withSystemPrompt prompt agent = agent {agentSystemPrompt = Just prompt}++-- | Override the maximum number of reasoning iterations (default: 10).+withMaxIterations ::+ -- | Maximum reasoning loop iterations before aborting+ Int ->+ -- | Current agent instance+ ReActAgent model m ->+ -- | Agent configured with iteration limit+ ReActAgent model m+withMaxIterations n agent = agent {agentMaxIterations = n}++{- | Attach a provider-specific model configuration (temperature, top_p, etc.).++This config is merged with the tool-binding config on every 'invoke' call,+giving you full control over LLM parameters without losing tool support.+-}+withModelConfig ::+ -- | Provider-specific model configuration (temperature, top_p, etc.)+ Maybe (ModelConfig model) ->+ -- | Current agent instance+ ReActAgent model m ->+ -- | Agent configured with model options+ ReActAgent model m+withModelConfig cfg agent = agent {agentModelConfig = cfg}++{- | Attach a 'CallbackManager' for observability.++When set, the agent emits 'OnToolStart' / 'OnToolEnd' / 'OnError' events+for each tool invocation in the reasoning loop.+-}+withCallbackManager ::+ -- | Callback manager for observability event hooks+ CallbackManager ->+ -- | Current agent instance+ ReActAgent model m ->+ -- | Agent configured with callback manager+ ReActAgent model m+withCallbackManager mgr agent = agent {agentCallbackManager = Just mgr}++{- | Set a custom stop condition checked after each LLM response.++If the predicate returns 'True' for the LLM's response message, the agent+immediately returns that message as the final answer, even if the LLM+included tool calls.+-}+withStopCondition ::+ -- | Predicate checked after each LLM response for early exit+ (Message -> Bool) ->+ -- | Current agent instance+ ReActAgent model m ->+ -- | Agent configured with custom stopping condition+ ReActAgent model m+withStopCondition cond agent = agent {agentStopCondition = Just cond}++{- | Set the tool error handling strategy.++* 'ContinueOnError' (default): feed error text back to the LLM as a Tool observation.+* 'FailOnError': abort the entire agent loop on the first tool error.+-}+withToolErrorStrategy ::+ -- | Strategy on tool failure ('ContinueOnError' or 'FailOnError')+ ToolErrorStrategy ->+ -- | Current agent instance+ ReActAgent model m ->+ -- | Agent configured with error handling strategy+ ReActAgent model m+withToolErrorStrategy strategy agent = agent {agentToolErrorStrategy = strategy}++{- | Run a single step of ReAct reasoning.++Sends the current message history (with tools bound) to the LLM and+classifies the response as either 'AgentAction' (tool calls requested)+or 'AgentFinish' (final answer).+-}+reactStep ::+ forall model m.+ (ToolBinder model m, MonadIO m, MonadError LangchainError m) =>+ -- | The chat model to invoke+ model ->+ -- | Available tools to bind to the model+ [Tool m] ->+ -- | Current conversation message history+ [Message] ->+ -- | Optional provider-specific model configuration+ Maybe (ModelConfig model) ->+ -- | Step classification: 'AgentAction' or 'AgentFinish'+ m AgentStep+reactStep model tools history userCfg = do+ let toolCfg = bindToolsConfig @model tools userCfg+ responseMsg <- invoke model history toolCfg+ case messageToolCalls responseMsg of+ Just tcs@(_ : _) -> pure $ AgentAction responseMsg tcs+ _ -> pure $ AgentFinish responseMsg++{- | Execute the full ReAct reasoning loop, returning only the final answer.++For the full reasoning trace, use 'runReActAgentWithTrace'.++When the agent has a 'BaseMemory' attached:++1. Existing conversation history is loaded from memory.+2. The user's input messages are appended.+3. After the loop finishes, both the user input and final answer are saved to memory.+-}+runReActAgent ::+ forall model m.+ (ToolBinder model m, MonadIO m, MonadError LangchainError m) =>+ -- | Configured ReAct agent instance+ ReActAgent model m ->+ -- | Input user messages for this turn+ [Message] ->+ -- | The final assistant answer message+ m Message+runReActAgent agent userMsgs = traceFinalAnswer <$> runReActAgentWithTrace agent userMsgs++{- | Execute the full ReAct reasoning loop, returning both the final answer+and the complete intermediate reasoning trace.++Tool calls within a single step are executed in sequence within the @m@ monad,+and the observations are fed back to the LLM for the next reasoning step.++The trace includes every 'AgentStep' and the observation messages produced+at each iteration, which is invaluable for debugging and observability.+-}+runReActAgentWithTrace ::+ forall model m.+ (ToolBinder model m, MonadIO m, MonadError LangchainError m) =>+ -- | Configured ReAct agent instance+ ReActAgent model m ->+ -- | Input user messages for this turn+ [Message] ->+ -- | Full reasoning trace containing all intermediate steps and final answer+ m AgentTrace+runReActAgentWithTrace ReActAgent {..} userMsgs = do+ -- 1. Build initial history: system prompt + memory + user messages+ memoryMsgs <- case agentMemory of+ Nothing -> pure []+ Just (SomeMemory mem) -> messages mem++ -- When an explicit agent system prompt is provided, avoid duplicate system prompts+ -- by stripping any default system prompt that may already exist in memory.+ let memWithoutSystem = case agentSystemPrompt of+ Just _ -> filter (\m -> messageRole m /= System) memoryMsgs+ Nothing -> memoryMsgs+ sysPrefix = maybe [] (\p -> [systemMessage p]) agentSystemPrompt+ initialHistory = sysPrefix ++ memWithoutSystem ++ userMsgs++ when (null initialHistory) $+ throwError $+ agentError+ "Cannot invoke ReAct agent with empty message history (no user query, memory, or system prompt)"+ (Just "ReActAgent")+ (Just "runReActAgentWithTrace")++ -- 2. Run the reasoning loop+ trace <- go initialHistory agentMaxIterations []++ -- 3. Persist to memory if available+ case agentMemory of+ Nothing -> pure ()+ Just (SomeMemory mem) -> do+ -- Save each user message+ mapM_ (addMessage mem) userMsgs+ -- Save the final answer+ addMessage mem (traceFinalAnswer trace)++ pure trace+ where+ go :: [Message] -> Int -> [(AgentStep, [Message])] -> m AgentTrace+ go history maxIter accSteps+ | maxIter <= 0 = do+ let maxErr =+ "ReAct Agent exceeded maximum iterations ("+ <> T.pack (show agentMaxIterations)+ <> ")"+ case agentCallbackManager of+ Nothing -> pure ()+ Just mgr -> do+ now <- liftIO getCurrentTime+ dispatchEvent mgr (OnError "ReActAgent" maxErr now)+ throwError $+ agentError+ maxErr+ (Just "ReActAgent")+ (Just "runReActAgent")+ | otherwise = do+ -- Dispatch OnLLMStart callback before calling model+ llmStartTime <- liftIO getCurrentTime+ case agentCallbackManager of+ Nothing -> pure ()+ Just mgr ->+ dispatchEvent mgr (OnLLMStart "ReActAgent" (map extractMessageText history) llmStartTime)++ -- Invoke the model with error interception for observability+ eStep <-+ (Right <$> reactStep agentModel agentTools history agentModelConfig)+ `catchError` (pure . Left)+ llmEndTime <- liftIO getCurrentTime+ let llmLatency = round (realToFrac (diffUTCTime llmEndTime llmStartTime) * 1000000 :: Double) :: Int+ case eStep of+ Left err -> do+ case agentCallbackManager of+ Nothing -> pure ()+ Just mgr ->+ dispatchEvent mgr (OnError "ReActAgent" (errorMessage err) llmEndTime)+ throwError err+ Right step -> case step of+ AgentFinish finalMsg -> do+ case agentCallbackManager of+ Nothing -> pure ()+ Just mgr ->+ dispatchEvent mgr (OnLLMEnd "ReActAgent" (extractMessageText finalMsg) llmLatency llmEndTime)+ let allSteps = accSteps ++ [(step, [])]+ pure $ AgentTrace allSteps finalMsg+ AgentAction respMsg tcs -> do+ case agentCallbackManager of+ Nothing -> pure ()+ Just mgr ->+ dispatchEvent mgr (OnLLMEnd "ReActAgent" (extractMessageText respMsg) llmLatency llmEndTime)++ -- Check custom stop condition+ case agentStopCondition of+ Just cond | cond respMsg -> do+ let allSteps = accSteps ++ [(step, [])]+ pure $ AgentTrace allSteps respMsg+ _ -> do+ obsMsgs <- forM tcs executeTool+ let newHistory = history ++ [respMsg] ++ obsMsgs+ newSteps = accSteps ++ [(step, obsMsgs)]+ go newHistory (maxIter - 1) newSteps++ executeTool :: ToolCall -> m Message+ executeTool tc = do+ let tName = toolCallName tc+ case agentCallbackManager of+ Nothing -> pure ()+ Just mgr -> do+ now <- liftIO getCurrentTime+ dispatchEvent mgr (OnToolStart tName (toolCallArguments tc) now)++ case find (\t -> toolName t == tName) agentTools of+ Nothing -> do+ let notFoundMsg = "Error: Tool not found: " <> tName+ case agentCallbackManager of+ Nothing -> pure ()+ Just mgr -> do+ now <- liftIO getCurrentTime+ dispatchEvent mgr (OnError tName notFoundMsg now)+ when (agentToolErrorStrategy == FailOnError) $+ throwError $+ agentError notFoundMsg (Just "ReActAgent") (Just $ "executeTool:" <> tName)+ pure $+ (textMessage M.Tool notFoundMsg)+ { M.messageName = Just tName+ , M.messageToolId = Just (toolCallId tc)+ }+ Just tool -> do+ startTime <- liftIO getCurrentTime+ eRes <- toolExecute tool (toolCallArguments tc)+ endTime <- liftIO getCurrentTime+ let durationMicros = round (realToFrac (diffUTCTime endTime startTime) * 1000000 :: Double) :: Int+ case eRes of+ Left err -> do+ let errTxt = "Error executing tool " <> tName <> ": " <> errorMessage err+ case agentCallbackManager of+ Nothing -> pure ()+ Just mgr -> dispatchEvent mgr (OnError tName errTxt endTime)+ when (agentToolErrorStrategy == FailOnError) $+ throwError $+ agentError errTxt (Just "ReActAgent") (Just $ "executeTool:" <> tName)+ pure $+ (textMessage M.Tool errTxt)+ { M.messageName = Just tName+ , M.messageToolId = Just (toolCallId tc)+ }+ Right outTxt -> do+ case agentCallbackManager of+ Nothing -> pure ()+ Just mgr -> dispatchEvent mgr (OnToolEnd tName outTxt durationMicros endTime)+ pure $+ (textMessage M.Tool outTxt)+ { M.messageName = Just tName+ , M.messageToolId = Just (toolCallId tc)+ }
+ src/Langchain/Cache.hs view
@@ -0,0 +1,15 @@+{- |+Module : Langchain.Cache+Description : LLM response caching layer+Copyright : (c) 2025-2026 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++Transparent caching for ChatModel invocations with in-memory and SQLite backends.+-}+module Langchain.Cache+ ( module Langchain.Cache.Core+ ) where++import Langchain.Cache.Core
+ src/Langchain/Cache/Core.hs view
@@ -0,0 +1,203 @@+{-# 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.Text (Text)+import qualified Data.Text as TS+import qualified Data.Text.Encoding as TE+import Database.SQLite.Simple+import Langchain.Model+ ( ChatModel (..)+ , Message (..)+ )+import Langchain.Tool.Binding (ToolBinder (..))++-- | 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.++Instances for the built-in providers live in their respective sub-packages:++ * @langchain-hs-openai@: @CacheableChatModel OpenAI@+ * @langchain-hs-ollama@: @CacheableChatModel Ollama@+ * @langchain-hs-gemini@: @CacheableChatModel Gemini@+-}+class (ChatModel model) => CacheableChatModel model where+ -- | Return the JSON identity used to distinguish this model's cache entries.+ cacheModelIdentity :: model -> Maybe (ModelConfig model) -> Value++{- | Compute a canonical cache key from a model identity and complete input messages.++The key includes all fields of each t'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.hs view
@@ -0,0 +1,15 @@+{- |+Module : Langchain.Callback+Description : Typed event-driven callback system+Copyright : (c) 2025-2026 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++Typed event-driven callback system with synchronous and asynchronous dispatch.+-}+module Langchain.Callback+ ( module Langchain.Callback.Manager+ ) where++import Langchain.Callback.Manager
+ src/Langchain/Callback/Manager.hs view
@@ -0,0 +1,100 @@+{-# 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.hs view
@@ -0,0 +1,15 @@+{- |+Module : Langchain.Chain+Description : LLM chains+Copyright : (c) 2025-2026 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++Chains combining retrieval, prompt templates, and chat models.+-}+module Langchain.Chain+ ( module Langchain.Chain.RetrievalQA+ ) where++import Langchain.Chain.RetrievalQA
+ src/Langchain/Chain/RetrievalQA.hs view
@@ -0,0 +1,75 @@+{-# 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.DocumentLoader.Core (Document (..))+import Langchain.Error (LangchainError)+import Langchain.Model+ ( ChatModel (..)+ , Message+ , systemMessage+ , userMessage+ )+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/Core/Error.hs
@@ -1,178 +0,0 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE OverloadedStrings #-}--{- |-Module : Langchain.Core.Error-Description : Core structured error types and context metadata-Copyright : (c) 2025-2026 Tushar Adhatrao-License : MIT-Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability : experimental--Structured error handling without String-based error dropping.--}-module Langchain.Core.Error- ( LangchainError (..)- , ErrorContext (..)- , LangchainResult- , errorMessage- , mkContext- , mkContextIO- , llmError- , agentError- , memoryError- , toolError- , vectorStoreError- , documentLoaderError- , embeddingError- , runnableError- , parsingError- , networkError- , configurationError- , validationError- , internalError- ) where--import Control.DeepSeq (NFData)-import Control.Exception (Exception (..))-import Control.Monad.IO.Class (MonadIO, liftIO)-import Data.Aeson (FromJSON, ToJSON)-import Data.Map (Map)-import Data.Text (Text)-import qualified Data.Text as T-import Data.Time (UTCTime, getCurrentTime)-import Data.Time.Clock.POSIX (posixSecondsToUTCTime)-import GHC.Generics (Generic)---- | Detailed context metadata attached to every error.-data ErrorContext = ErrorContext- { component :: Text- , operation :: Text- , timestamp :: UTCTime- , details :: Map Text Text- }- deriving (Show, Eq, Generic, ToJSON, FromJSON, NFData)---- | Pure context constructor.-mkContext :: Text -> Text -> Map Text Text -> ErrorContext-mkContext comp op = ErrorContext comp op (posixSecondsToUTCTime 0)---- | IO context constructor with real timestamp.-mkContextIO :: MonadIO m => Text -> Text -> Map Text Text -> m ErrorContext-mkContextIO comp op dt = do- now <- liftIO getCurrentTime- pure $ ErrorContext comp op now dt---- | Core framework error type.-data LangchainError- = LLMError Text (Maybe ErrorContext)- | AgentError Text (Maybe ErrorContext)- | MemoryError Text (Maybe ErrorContext)- | ToolError Text (Maybe ErrorContext)- | VectorStoreError Text (Maybe ErrorContext)- | DocumentLoaderError Text (Maybe ErrorContext)- | EmbeddingError Text (Maybe ErrorContext)- | RunnableError Text (Maybe ErrorContext)- | ParsingError Text (Maybe ErrorContext)- | NetworkError Text (Maybe ErrorContext)- | ConfigurationError Text (Maybe ErrorContext)- | ValidationError Text (Maybe ErrorContext)- | InternalError Text (Maybe ErrorContext)- deriving (Show, Eq, Generic, ToJSON, FromJSON, NFData)---- | Type alias for Either LangchainError a-type LangchainResult a = Either LangchainError a---- | Extract human-readable error message text from a LangchainError-errorMessage :: LangchainError -> Text-errorMessage (LLMError msg _) = msg-errorMessage (AgentError msg _) = msg-errorMessage (MemoryError msg _) = msg-errorMessage (ToolError msg _) = msg-errorMessage (VectorStoreError msg _) = msg-errorMessage (DocumentLoaderError msg _) = msg-errorMessage (EmbeddingError msg _) = msg-errorMessage (RunnableError msg _) = msg-errorMessage (ParsingError msg _) = msg-errorMessage (NetworkError msg _) = msg-errorMessage (ConfigurationError msg _) = msg-errorMessage (ValidationError msg _) = msg-errorMessage (InternalError msg _) = msg--instance Exception LangchainError where- displayException err = case err of- LLMError msg ctx -> formatError "LLMError" msg ctx- AgentError msg ctx -> formatError "AgentError" msg ctx- MemoryError msg ctx -> formatError "MemoryError" msg ctx- ToolError msg ctx -> formatError "ToolError" msg ctx- VectorStoreError msg ctx -> formatError "VectorStoreError" msg ctx- DocumentLoaderError msg ctx -> formatError "DocumentLoaderError" msg ctx- EmbeddingError msg ctx -> formatError "EmbeddingError" msg ctx- RunnableError msg ctx -> formatError "RunnableError" msg ctx- ParsingError msg ctx -> formatError "ParsingError" msg ctx- NetworkError msg ctx -> formatError "NetworkError" msg ctx- ConfigurationError msg ctx -> formatError "ConfigurationError" msg ctx- ValidationError msg ctx -> formatError "ValidationError" msg ctx- InternalError msg ctx -> formatError "InternalError" msg ctx- where- formatError errType msg Nothing = errType ++ ": " ++ T.unpack msg- formatError errType msg (Just ctx) =- errType- ++ ": "- ++ T.unpack msg- ++ " [Component: "- ++ T.unpack (component ctx)- ++ ", Operation: "- ++ T.unpack (operation ctx)- ++ "]"---- | Helper constructors-mkErrorCtx ::- (Text -> Maybe ErrorContext -> LangchainError) -> Text -> Maybe Text -> Maybe Text -> LangchainError-mkErrorCtx ctor msg mbComp mbOp =- let mbCtx = case (mbComp, mbOp) of- (Nothing, Nothing) -> Nothing- (Just c, Just o) -> Just $ mkContext c o mempty- (Just c, Nothing) -> Just $ mkContext c "unspecified" mempty- (Nothing, Just o) -> Just $ mkContext "unspecified" o mempty- in ctor msg mbCtx--llmError :: Text -> Maybe Text -> Maybe Text -> LangchainError-llmError = mkErrorCtx LLMError--agentError :: Text -> Maybe Text -> Maybe Text -> LangchainError-agentError = mkErrorCtx AgentError--memoryError :: Text -> Maybe Text -> Maybe Text -> LangchainError-memoryError = mkErrorCtx MemoryError--toolError :: Text -> Maybe Text -> Maybe Text -> LangchainError-toolError = mkErrorCtx ToolError--vectorStoreError :: Text -> Maybe Text -> Maybe Text -> LangchainError-vectorStoreError = mkErrorCtx VectorStoreError--documentLoaderError :: Text -> Maybe Text -> Maybe Text -> LangchainError-documentLoaderError = mkErrorCtx DocumentLoaderError--embeddingError :: Text -> Maybe Text -> Maybe Text -> LangchainError-embeddingError = mkErrorCtx EmbeddingError--runnableError :: Text -> Maybe Text -> Maybe Text -> LangchainError-runnableError = mkErrorCtx RunnableError--parsingError :: Text -> Maybe Text -> Maybe Text -> LangchainError-parsingError = mkErrorCtx ParsingError--networkError :: Text -> Maybe Text -> Maybe Text -> LangchainError-networkError = mkErrorCtx NetworkError--configurationError :: Text -> Maybe Text -> Maybe Text -> LangchainError-configurationError = mkErrorCtx ConfigurationError--validationError :: Text -> Maybe Text -> Maybe Text -> LangchainError-validationError = mkErrorCtx ValidationError--internalError :: Text -> Maybe Text -> Maybe Text -> LangchainError-internalError = mkErrorCtx InternalError
− src/Langchain/Core/Model.hs
@@ -1,53 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE TypeFamilies #-}--{- |-Module : Langchain.Core.Model-Description : Central ChatModel typeclass for LLM providers-Copyright : (c) 2025-2026 Tushar Adhatrao-License : MIT-Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability : experimental--Provides effect-polymorphic 'ChatModel' interface.--}-module Langchain.Core.Model- ( ChatModel (..)- , module Langchain.Core.Model.Types- ) where--import Control.Monad.Except (MonadError)-import Control.Monad.IO.Class (MonadIO)-import Data.Kind (Type)--import Langchain.Core.Error (LangchainError)-import Langchain.Core.Model.Types-import Langchain.Core.Stream (ChatStream)---- | Effect-polymorphic ChatModel typeclass for LLM providers-class ChatModel model where- type ModelConfig model :: Type-- -- | Single synchronous invocation- invoke ::- (MonadIO m, MonadError LangchainError m) =>- model ->- [Message] ->- Maybe (ModelConfig model) ->- m Message-- -- | Batch invocations (default: sequential)- batch ::- (MonadIO m, MonadError LangchainError m) =>- model ->- [[Message]] ->- Maybe (ModelConfig model) ->- m [Message]- batch model msgs cfg = mapM (\m -> invoke model m cfg) msgs-- -- | Streaming invocation yielding structured StreamEvents via Conduit- stream ::- model ->- [Message] ->- Maybe (ModelConfig model) ->- ChatStream
− src/Langchain/Core/Model/Types.hs
@@ -1,199 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE OverloadedStrings #-}-{-# OPTIONS_GHC -Wno-partial-fields #-}--{- |-Module : Langchain.Core.Model.Types-Description : Multi-modal ContentBlock and Message data types-Copyright : (c) 2025-2026 Tushar Adhatrao-License : MIT-Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability : experimental--Data types for multi-modal messages, content blocks, roles, and tool calls.--}-module Langchain.Core.Model.Types- ( ContentBlock (..)- , ImageContent (..)- , ImageSource (..)- , Role (..)- , ToolCall (..)- , Message (..)- , textMessage- , userMessage- , systemMessage- , assistantMessage- , toolMessage- , imageMessage- , extractMessageText- , roleLabel- , formatMessageString- ) where--import Control.DeepSeq (NFData)-import Data.Aeson (FromJSON (..), ToJSON (..), Value (..), object, withObject, (.:), (.:?), (.=))-import Data.ByteString (ByteString)-import qualified Data.ByteString.Base64 as Base64-import Data.List.NonEmpty (NonEmpty (..))-import qualified Data.List.NonEmpty as NonEmpty-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.Encoding as TE-import GHC.Generics (Generic)---- | A single content block within a multi-modal message.-data ContentBlock- = TextBlock {blockText :: Text}- | ImageBlock ImageContent- | AudioBlock {blockMimeType :: Text, blockBase64 :: Text}- | DataBlock {blockBytes :: ByteString}- deriving (Eq, Show, Generic, NFData)--data ImageContent = ImageContent- { imageSource :: ImageSource- , imageDetail :: Maybe Text- , imageMetadata :: Maybe Value- }- deriving (Eq, Show, Generic, NFData)--data ImageSource- = ImageBase64 {imageMimeType :: Maybe Text, imageData :: Text}- | ImageUrl {imageUrl :: Text}- deriving (Eq, Show, Generic, NFData)--instance ToJSON ContentBlock where- toJSON (TextBlock t) = object ["type" .= ("text" :: Text), "text" .= t]- toJSON (ImageBlock ImageContent {imageSource = ImageUrl url, imageDetail = detail}) =- object- [ "type" .= ("image_url" :: Text)- , "image_url" .= object (maybe id ((:) . ("detail" .=)) detail ["url" .= url])- ]- toJSON (ImageBlock ImageContent {imageSource = ImageBase64 (Just mime) imageData, imageMetadata = metadata}) =- object $- ["type" .= ("image" :: Text), "mime_type" .= mime, "data" .= imageData]- <> maybe [] (pure . ("metadata" .=)) metadata- toJSON (ImageBlock ImageContent {imageSource = ImageBase64 _ imageData, imageMetadata = metadata}) =- object $- ["type" .= ("image" :: Text), "source_type" .= ("base64" :: Text), "data" .= imageData]- <> maybe [] (pure . ("metadata" .=)) metadata- toJSON (AudioBlock mime b64) = object ["type" .= ("audio" :: Text), "mime_type" .= mime, "data" .= b64]- toJSON (DataBlock bs) = object ["type" .= ("data" :: Text), "data" .= TE.decodeUtf8 (Base64.encode bs)]--instance FromJSON ContentBlock where- parseJSON = withObject "ContentBlock" $ \v -> do- typ <- v .: "type"- case (typ :: Text) of- "text" -> TextBlock <$> v .: "text"- "image" -> do- mimeType <- v .:? "mime_type"- case mimeType of- Just mime -> do- imageData <- v .: "data"- metadata <- v .:? "metadata"- pure $ ImageBlock $ ImageContent (ImageBase64 (Just mime) imageData) Nothing metadata- Nothing -> do- sourceType <- v .: "source_type"- imageData <- v .: "data"- metadata <- v .:? "metadata"- let source =- case (sourceType :: Text) of- "url" -> ImageUrl imageData- _ -> ImageBase64 Nothing imageData- pure $ ImageBlock $ ImageContent source Nothing metadata- "image_url" -> do- imageUrl <- v .: "image_url"- url <- imageUrl .: "url"- detail <- imageUrl .:? "detail"- pure $ ImageBlock $ ImageContent (ImageUrl url) detail Nothing- "audio" -> AudioBlock <$> v .: "mime_type" <*> v .: "data"- "data" -> do- b64Text <- v .: "data"- case Base64.decode (TE.encodeUtf8 b64Text) of- Left err -> fail $ "Invalid base64 data block: " ++ err- Right bs -> pure $ DataBlock bs- other -> fail $ "Unknown ContentBlock type: " ++ show other---- | Complete set of conversation roles supported across LLM providers.-data Role- = System- | User- | Assistant- | Tool- | Developer- | Function- deriving (Eq, Ord, Show, Bounded, Enum, Generic, ToJSON, FromJSON, NFData)---- | Structured tool call from an LLM response.-data ToolCall = ToolCall- { toolCallId :: Text- , toolCallType :: Text- -- ^ Always "function" for current providers- , toolCallName :: Text- , toolCallArguments :: Value- -- ^ Parsed JSON Value arguments- }- deriving (Eq, Show, Generic, ToJSON, FromJSON, NFData)---- | Structured chat message supporting multi-modal content blocks.-data Message = Message- { messageRole :: Role- , messageContents :: NonEmpty ContentBlock- , messageName :: Maybe Text- , messageToolCalls :: Maybe [ToolCall]- , messageToolId :: Maybe Text- -- ^ Associated tool call ID for Tool role- , messageMetadata :: Map Text Value- -- ^ Opaque provider-specific metadata.- }- deriving (Eq, Show, Generic, ToJSON, FromJSON, NFData)---- | Create a message with a single text content block.-textMessage :: Role -> Text -> Message-textMessage r t = Message r (TextBlock t :| []) Nothing Nothing Nothing Map.empty---- | Create a User role text message.-userMessage :: Text -> Message-userMessage = textMessage User---- | Create a System role text message.-systemMessage :: Text -> Message-systemMessage = textMessage System---- | Create an Assistant role text message.-assistantMessage :: Text -> Message-assistantMessage = textMessage Assistant---- | Create a Tool role text message.-toolMessage :: Text -> Message-toolMessage = textMessage Tool---- | Create an Image content block message.-imageMessage :: Role -> Text -> Text -> Message-imageMessage r mime b64 =- Message- r- (ImageBlock (ImageContent (ImageBase64 (Just mime) b64) Nothing Nothing) :| [])- Nothing- Nothing- Nothing- Map.empty---- | Extract all text content blocks concatenated into a single Text string.-extractMessageText :: Message -> Text-extractMessageText msg = T.intercalate "\n" [t | TextBlock t <- NonEmpty.toList (messageContents msg)]---- | Label used when rendering a chat message role as plain text.-roleLabel :: Role -> Text-roleLabel System = "System"-roleLabel User = "Human"-roleLabel Assistant = "AI"-roleLabel Tool = "Tool"-roleLabel Developer = "Developer"-roleLabel Function = "Function"---- | Render a message as a single plain-text line with its role label.-formatMessageString :: Message -> Text-formatMessageString chatMessage =- roleLabel (messageRole chatMessage) <> ": " <> extractMessageText chatMessage
− src/Langchain/Core/Monad.hs
@@ -1,55 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}--{- |-Module : Langchain.Core.Monad-Description : Core LangchainT monad transformer-Copyright : (c) 2025-2026 Tushar Adhatrao-License : MIT-Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability : experimental--Provides the canonical monad transformer stack 'LangchainT' and its execution-runner 'runLangchainT'.--'LangchainT' is parameterised over the reader environment @r@, so each-provider (or application) can supply its own config type rather than being-forced into a one-size-fits-all 'LangchainConfig'. Use @r = ()@ when you do-not need a shared environment at all.--@--- With a custom config-type App a = LangchainT MyConfig IO a--runApp :: MyConfig -> App a -> IO (Either LangchainError a)-runApp = runLangchainT---- Without any config-runSimple :: LangchainT () IO a -> IO (Either LangchainError a)-runSimple = runLangchainT ()-@--}-module Langchain.Core.Monad- ( LangchainT- , runLangchainT- , throwLangchainError- ) where--import Control.Monad.Except (ExceptT, MonadError, runExceptT, throwError)-import Control.Monad.Reader (ReaderT, runReaderT)--import Langchain.Core.Error (LangchainError)--{- | Standard framework monad transformer stack: ReaderT over ExceptT.--The type variable @r@ is the reader environment — pass your own provider-config, application context, or @()@ when none is needed.--}-type LangchainT r m = ReaderT r (ExceptT LangchainError m)---- | Execute a 'LangchainT' computation with a given environment.-runLangchainT :: r -> LangchainT r m a -> m (Either LangchainError a)-runLangchainT env action = runExceptT (runReaderT action env)---- | Throw a 'LangchainError' inside any 'MonadError' context.-throwLangchainError :: MonadError LangchainError m => LangchainError -> m a-throwLangchainError = throwError
− src/Langchain/Core/Runnable.hs
@@ -1,194 +0,0 @@-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE TypeFamilies #-}--{- |-Module : Langchain.Core.Runnable-Description : Pure pipeline GADT (RunnableTree) and algebraic composition-Copyright : (c) 2025-2026 Tushar Adhatrao-License : MIT-Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability : experimental--Pure, AST-based pipeline representation ('RunnableTree') where building pipelines performs-NO side effects. Execution is strictly deferred to 'interpret'.--}-module Langchain.Core.Runnable- ( Runnable (..)- , RunnableTree (..)- , (|>>)- , (&>&)- , interpret- , runLambda- , runPrim- , runPure- , runPassthrough- , runIdent- , runBranch- , runFallback- , runChat- , runModel- , ModelRunnable (..)- , TextModelRunnable (..)- ) where--import Control.Concurrent.Async (concurrently)-import Control.Monad.Except (ExceptT, MonadError, catchError, runExceptT, throwError)-import Control.Monad.IO.Class (MonadIO, liftIO)-import Data.Aeson (Value)-import Data.Kind (Type)-import Data.Text (Text)--import Langchain.Core.Error (LangchainError)-import Langchain.Core.Model (ChatModel, extractMessageText, userMessage)-import qualified Langchain.Core.Model as M (invoke)-import Langchain.Core.Model.Types (Message)-import Langchain.Core.Tool (Tool (..))---- | Fundamental Runnable interface for components wrapped in 'Prim'.-class Runnable r m where- type RunnableInput r :: Type- type RunnableOutput r :: Type- invoke :: r -> RunnableInput r -> m (Either LangchainError (RunnableOutput r))---- | Any Tool can be executed as a primitive Runnable taking JSON 'Value' to 'Text'.-instance Monad m => Runnable (Tool m) m where- type RunnableInput (Tool m) = Value- type RunnableOutput (Tool m) = Text- invoke = toolExecute---- | Wrapper to treat any 'ChatModel' as a Runnable over '[Message]' -> 'Message'.-newtype ModelRunnable c = ModelRunnable c- deriving (Eq, Show)--instance (ChatModel c, MonadIO m) => Runnable (ModelRunnable c) m where- type RunnableInput (ModelRunnable c) = [Message]- type RunnableOutput (ModelRunnable c) = Message- invoke (ModelRunnable c) msgs = runExceptT (M.invoke c msgs Nothing)---- | Wrapper to treat any 'ChatModel' as a simple 'Text' -> 'Text' Runnable.-newtype TextModelRunnable c = TextModelRunnable c- deriving (Eq, Show)--instance (ChatModel c, MonadIO m) => Runnable (TextModelRunnable c) m where- type RunnableInput (TextModelRunnable c) = Text- type RunnableOutput (TextModelRunnable c) = Text- invoke (TextModelRunnable c) prompt = do- res <- runExceptT $ M.invoke c [userMessage prompt] Nothing- pure (extractMessageText <$> res)--{- | Pure GADT representing a composable pipeline AST.-'i' = input type, 'o' = output type, 'm' = monad context.--}-data RunnableTree m i o where- -- | Identity: passes input through unchanged- Id :: RunnableTree m a a- -- | Lift a component implementing Runnable into the tree- Prim ::- (Runnable r m, RunnableInput r ~ i, RunnableOutput r ~ o) =>- r ->- RunnableTree m i o- -- | Lift a monadic function into the tree- Lambda :: (i -> m (Either LangchainError o)) -> RunnableTree m i o- -- | Sequential composition AST node- Seq :: RunnableTree m i mid -> RunnableTree m mid o -> RunnableTree m i o- -- | Parallel composition AST node- Par ::- RunnableTree (ExceptT LangchainError IO) i o1 ->- RunnableTree (ExceptT LangchainError IO) i o2 ->- RunnableTree (ExceptT LangchainError IO) i (o1, o2)- -- | Conditional branching AST node- Branch ::- (i -> m Bool) ->- -- | True branch- RunnableTree m i o ->- -- | False branch- RunnableTree m i o ->- RunnableTree m i o- -- | Fallback node: if primary fails, executes fallback- Fallback :: RunnableTree m i o -> RunnableTree m i o -> RunnableTree m i o---- | Sequential composition operator — PURE AST builder.-(|>>) :: RunnableTree m a b -> RunnableTree m b c -> RunnableTree m a c-(|>>) = Seq--infixl 1 |>>---- | Parallel composition operator — PURE AST builder.-(&>&) ::- RunnableTree (ExceptT LangchainError IO) a b ->- RunnableTree (ExceptT LangchainError IO) a c ->- RunnableTree (ExceptT LangchainError IO) a (b, c)-(&>&) = Par--infixl 2 &>&---- | Helper to create a lambda runnable node.-runLambda :: (i -> m (Either LangchainError o)) -> RunnableTree m i o-runLambda = Lambda---- | Helper to create a primitive runnable node.-runPrim ::- (Runnable r m, RunnableInput r ~ i, RunnableOutput r ~ o) =>- r ->- RunnableTree m i o-runPrim = Prim---- | Helper to convert Either to MonadError-liftEither :: MonadError LangchainError m => Either LangchainError a -> m a-liftEither (Left err) = throwError err-liftEither (Right x) = pure x---- | Sole execution engine for 'RunnableTree' AST pipelines.-interpret ::- (MonadIO m, MonadError LangchainError m) =>- RunnableTree m i o ->- i ->- m o-interpret Id input = pure input-interpret (Prim r) input = invoke r input >>= liftEither-interpret (Lambda f) input = f input >>= liftEither-interpret (Seq t1 t2) input = interpret t1 input >>= interpret t2-interpret (Par t1 t2) input = do- (r1, r2) <-- liftIO $- concurrently- (runExceptT $ interpret t1 input)- (runExceptT $ interpret t2 input)- o1 <- liftEither r1- o2 <- liftEither r2- pure (o1, o2)-interpret (Branch cond tTrue tFalse) input = do- b <- cond input- if b then interpret tTrue input else interpret tFalse input-interpret (Fallback t1 t2) input =- catchError (interpret t1 input) (\_ -> interpret t2 input)---- | Lift a pure function into a 'RunnableTree' node.-runPure :: Monad m => (i -> o) -> RunnableTree m i o-runPure f = runLambda (pure . Right . f)---- | Identity node in a 'RunnableTree' (passes input through unchanged, like LangChain's 'RunnablePassthrough').-runPassthrough :: RunnableTree m a a-runPassthrough = Id---- | Alias for 'runPassthrough'.-runIdent :: RunnableTree m a a-runIdent = Id---- | Construct a conditional branch AST node.-runBranch :: (i -> m Bool) -> RunnableTree m i o -> RunnableTree m i o -> RunnableTree m i o-runBranch = Branch---- | Construct a self-healing fallback AST node (tries first, catches errors and runs second).-runFallback :: RunnableTree m i o -> RunnableTree m i o -> RunnableTree m i o-runFallback = Fallback---- | Lift any 'ChatModel' into a simple 'Text' -> 'Text' pipeline step.-runChat :: (ChatModel c, MonadIO m) => c -> RunnableTree m Text Text-runChat c = runPrim (TextModelRunnable c)---- | Lift any 'ChatModel' into a structured '[Message]' -> 'Message' pipeline step.-runModel :: (ChatModel c, MonadIO m) => c -> RunnableTree m [Message] Message-runModel c = runPrim (ModelRunnable c)
− src/Langchain/Core/Stream.hs
@@ -1,169 +0,0 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# OPTIONS_GHC -Wno-partial-fields #-}--{- |-Module : Langchain.Core.Stream-Description : Standardized StreamEvent protocol and utilities-Copyright : (c) 2025-2026 Tushar Adhatrao-License : MIT-Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability : experimental--Structured streaming event protocol for tracking LLM, tool, chain, and graph lifecycle events.--}-module Langchain.Core.Stream- ( TokenUsage (..)- , StreamEvent (..)- , StreamM- , EventStream- , ChatStream- , StreamCallback- , StreamSource- , callbackSource- , collectEvents- , printEvents- ) where--import Control.Concurrent.Async (async, cancel)-import Control.Concurrent.STM- ( atomically- , newEmptyTMVarIO- , newTBQueueIO- , orElse- , putTMVar- , readTBQueue- , readTMVar- , writeTBQueue- )-import Control.Exception (finally)-import Control.Monad.Except (ExceptT, runExceptT)-import Control.Monad.IO.Class (liftIO)-import Control.Monad.Trans.Resource (ResourceT, runResourceT)-import Data.Aeson (FromJSON, ToJSON, Value)-import Data.Conduit (ConduitT, bracketP, runConduit, yield, (.|))-import qualified Data.Conduit.List as CL-import Data.Text (Text)-import GHC.Generics (Generic)-import Langchain.Core.Error (LangchainError)-import Langchain.Core.Model.Types (Message, ToolCall)---- | Token usage accounting for LLM execution.-data TokenUsage = TokenUsage- { promptTokens :: Int- , completionTokens :: Int- , totalTokens :: Int- }- deriving (Eq, Show, Generic, ToJSON, FromJSON)--{- | All streaming events emitted across the framework execution lifecycle.-Every event carries a 'runId' for correlation.--}-data StreamEvent- = -- | LLM lifecycle start- LLMStart- { runId :: Text- , modelName :: Text- , inputMessages :: [Message]- }- | -- | LLM incremental streaming chunk- LLMChunk- { runId :: Text- , chunkText :: Text- , toolCallDelta :: Maybe ToolCall- }- | -- | LLM lifecycle completion- LLMEnd- { runId :: Text- , finalMessage :: Message- , tokenUsage :: Maybe TokenUsage- }- | -- | Tool execution start- ToolStart- { runId :: Text- , toolName :: Text- , toolInput :: Value- }- | -- | Tool execution completion- ToolEnd- { runId :: Text- , toolName :: Text- , toolOutput :: Value- }- | -- | Tool execution failure- ToolErrorEvent- { runId :: Text- , toolName :: Text- , toolErrorPayload :: LangchainError- }- | -- | Chain execution start- ChainStart- { runId :: Text- , chainName :: Text- , chainInput :: Value- }- | -- | Chain execution completion- ChainEnd- { runId :: Text- , chainName :: Text- , chainOutput :: Value- }- | -- | Graph node execution start- NodeStart- { runId :: Text- , nodeId :: Text- , nodeState :: Value- }- | -- | Graph node execution completion- NodeEnd- { runId :: Text- , nodeId :: Text- , nodeState :: Value- }- deriving (Eq, Show, Generic, ToJSON, FromJSON)---- | Canonical event stream type using Conduit.-type EventStream m = ConduitT () StreamEvent m ()---- | Effects used by resource-safe chat model streams.-type StreamM = ExceptT LangchainError (ResourceT IO)---- | A resource-safe stream of chat model events.-type ChatStream = EventStream StreamM---- | A callback function that produces values of type @a@.-type StreamCallback a = (a -> IO ()) -> IO ()---- | A Conduit source that produces values of type @a@ in the 'StreamM' monad.-type StreamSource a = ConduitT () a StreamM ()---- | Convert a callback-based streaming function into a Conduit source.-callbackSource :: StreamCallback a -> StreamSource a-callbackSource produce = bracketP start (cancel . third) consume- where- start = do- queue <- newTBQueueIO 64- finished <- newEmptyTMVarIO- worker <-- async $ produce (atomically . writeTBQueue queue) `finally` atomically (putTMVar finished ())- pure (queue, finished, worker)-- consume (queue, finished, _worker) = loop- where- loop = do- let waitForFinished = Nothing <$ readTMVar finished- readEvent = Just <$> readTBQueue queue- next <- liftIO . atomically $ readEvent `orElse` waitForFinished- case next of- Just item -> yield item >> loop- Nothing -> pure ()-- third (_, _, worker) = worker---- | Collect all emitted events from a stream into a list.-collectEvents :: Monad m => EventStream m -> m [StreamEvent]-collectEvents streamSrc = runConduit (streamSrc .| CL.consume)---- | Debug helper: print all stream events to stdout.-printEvents :: EventStream (ExceptT LangchainError (ResourceT IO)) -> IO (Either LangchainError ())-printEvents streamSrc = runResourceT $ runExceptT $ runConduit (streamSrc .| CL.mapM_ (liftIO . print))
− src/Langchain/Core/Tool.hs
@@ -1,65 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--{- |-Module : Langchain.Core.Tool-Description : Effect-polymorphic Tool specification and parameter validation-Copyright : (c) 2025-2026 Tushar Adhatrao-License : MIT-Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>-Stability : experimental--Provides effect-polymorphic 'Tool m' representation, schema generation, and argument parsing.--}-module Langchain.Core.Tool- ( Tool (..)- , createTool- , toolToValue- ) where--import Data.Aeson-import Data.Text (Text)--import Langchain.Core.Error (LangchainError)---- | Effect-polymorphic Tool abstraction-data Tool m = Tool- { toolName :: Text- -- ^ Unique identifier for the tool- , toolDescription :: Text- -- ^ Description explaining when and how to use the tool- , toolSchema :: Value- -- ^ JSON Schema describing expected parameters- , toolExecute :: Value -> m (Either LangchainError Text)- -- ^ Monadic execution function accepting JSON arguments and returning text output- }--instance Show (Tool m) where- show t = "Tool { toolName = " ++ show (toolName t) ++ " }"---- | Helper to create a Tool from a name, description, schema, and execution function-createTool ::- Text ->- Text ->- Value ->- (Value -> m (Either LangchainError Text)) ->- Tool m-createTool name desc schema execFn =- Tool- { toolName = name- , toolDescription = desc- , toolSchema = schema- , toolExecute = execFn- }---- | Convert Tool definition to OpenAI/Ollama compatible function definition JSON object-toolToValue :: Tool m -> Value-toolToValue Tool {..} =- object- [ "type" .= ("function" :: Text)- , "function"- .= object- [ "name" .= toolName- , "description" .= toolDescription- , "parameters" .= toolSchema- ]- ]
+ src/Langchain/DocumentLoader.hs view
@@ -0,0 +1,17 @@+{- |+Module : Langchain.DocumentLoader+Description : Document loading abstractions and standard implementations+Copyright : (c) 2025-2026 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental+-}+module Langchain.DocumentLoader+ ( module Langchain.DocumentLoader.Core+ , module Langchain.DocumentLoader.FileLoader+ , module Langchain.DocumentLoader.DirectoryLoader+ ) where++import Langchain.DocumentLoader.Core+import Langchain.DocumentLoader.DirectoryLoader+import Langchain.DocumentLoader.FileLoader
+ src/Langchain/DocumentLoader/Core.hs view
@@ -0,0 +1,77 @@+{-# 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 (..)+ , splitDocuments+ ) 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.Error (LangchainError)+import Langchain.TextSplitter.Character (defaultCharacterSplitterOps, splitText)++-- | 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 into chunks using 'defaultCharacterSplitterOps'.+ loadAndSplit ::+ (MonadIO m, MonadError LangchainError m) =>+ loader ->+ m [Text]+ loadAndSplit loader = do+ docs <- load loader+ pure $ concatMap (splitText defaultCharacterSplitterOps . pageContent) docs++{- | Split a list of documents using any text splitting function, preserving metadata on each chunk.++Example:++@+chunks = splitDocuments (splitTextRecursive defaultRecursiveCharacterSplitterOps) docs+@+-}+splitDocuments :: (Text -> [Text]) -> [Document] -> [Document]+splitDocuments splitFn docs =+ [ Document chunk (metadata doc)+ | doc <- docs+ , chunk <- splitFn (pageContent doc)+ ]
+ src/Langchain/DocumentLoader/DirectoryLoader.hs view
@@ -0,0 +1,120 @@+{-# 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.DocumentLoader.Core+import Langchain.DocumentLoader.FileLoader (FileLoader (FileLoader))+import Langchain.Error (documentLoaderError)++-- | 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)
+ src/Langchain/DocumentLoader/FileLoader.hs view
@@ -0,0 +1,55 @@+{-# 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 (Value (..))+import Data.Map (fromList)+import qualified Data.Text as T+import qualified Data.Text.Lazy.IO as TLIO+import System.Directory (doesFileExist)++import Langchain.DocumentLoader.Core (BaseLoader (..), Document (..))+import Langchain.Error (documentLoaderError)++-- | 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 (TLIO.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 content meta]+ else+ throwError $+ documentLoaderError+ (T.pack $ "File not found: " ++ path)+ (Just "FileLoader")+ (Just $ T.pack path)
+ src/Langchain/Embeddings.hs view
@@ -0,0 +1,38 @@+{-# LANGUAGE FlexibleContexts #-}++{- |+Module : Langchain.Embeddings+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+ ( Embeddings (..)+ ) where++import Control.Monad.Except (MonadError)+import Control.Monad.IO.Class (MonadIO)+import Data.Text (Text)++import Langchain.DocumentLoader.Core (Document)+import Langchain.Error (LangchainError)++-- | 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/Core.hs view
@@ -0,0 +1,13 @@+{- |+Module : Langchain.Embeddings.Core+Description : Re-export of Langchain.Embeddings for backward compatibility+Copyright : (c) 2025-2026 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental+-}+module Langchain.Embeddings.Core+ ( module Langchain.Embeddings+ ) where++import Langchain.Embeddings
+ src/Langchain/Error.hs view
@@ -0,0 +1,190 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-}++{- |+Module : Langchain.Error+Description : Core structured error types and context metadata+Copyright : (c) 2025-2026 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++Structured error handling without String-based error dropping.+-}+module Langchain.Error+ ( LangchainError (..)+ , ErrorContext (..)+ , LangchainResult+ , errorMessage+ , mkContext+ , mkContextIO+ , llmError+ , agentError+ , memoryError+ , toolError+ , vectorStoreError+ , documentLoaderError+ , embeddingError+ , runnableError+ , parsingError+ , networkError+ , configurationError+ , validationError+ , internalError+ ) where++import Control.Exception (Exception (..))+import Control.Monad.IO.Class (MonadIO, liftIO)+import Data.Aeson (FromJSON, ToJSON)+import Data.Map (Map)+import Data.Text (Text)+import qualified Data.Text as T+import Data.Time (UTCTime, getCurrentTime)+import Data.Time.Clock.POSIX (posixSecondsToUTCTime)+import GHC.Generics (Generic)++-- | Detailed context metadata attached to every error.+data ErrorContext = ErrorContext+ { component :: Text+ , operation :: Text+ , timestamp :: UTCTime+ , details :: Map Text Text+ }+ deriving (Show, Eq, Generic, ToJSON, FromJSON)++-- | Pure context constructor.+mkContext :: Text -> Text -> Map Text Text -> ErrorContext+mkContext comp op = ErrorContext comp op (posixSecondsToUTCTime 0)++-- | IO context constructor with real timestamp.+mkContextIO :: MonadIO m => Text -> Text -> Map Text Text -> m ErrorContext+mkContextIO comp op dt = do+ now <- liftIO getCurrentTime+ pure $ ErrorContext comp op now dt++-- | Core framework error type.+data LangchainError+ = LLMError Text (Maybe ErrorContext)+ | AgentError Text (Maybe ErrorContext)+ | MemoryError Text (Maybe ErrorContext)+ | ToolError Text (Maybe ErrorContext)+ | VectorStoreError Text (Maybe ErrorContext)+ | DocumentLoaderError Text (Maybe ErrorContext)+ | EmbeddingError Text (Maybe ErrorContext)+ | RunnableError Text (Maybe ErrorContext)+ | ParsingError Text (Maybe ErrorContext)+ | NetworkError Text (Maybe ErrorContext)+ | ConfigurationError Text (Maybe ErrorContext)+ | ValidationError Text (Maybe ErrorContext)+ | InternalError Text (Maybe ErrorContext)+ deriving (Show, Eq, Generic, ToJSON, FromJSON)++-- | Type alias for Either LangchainError a+type LangchainResult a = Either LangchainError a++-- | Extract human-readable error message text from a LangchainError+errorMessage :: LangchainError -> Text+errorMessage (LLMError msg _) = msg+errorMessage (AgentError msg _) = msg+errorMessage (MemoryError msg _) = msg+errorMessage (ToolError msg _) = msg+errorMessage (VectorStoreError msg _) = msg+errorMessage (DocumentLoaderError msg _) = msg+errorMessage (EmbeddingError msg _) = msg+errorMessage (RunnableError msg _) = msg+errorMessage (ParsingError msg _) = msg+errorMessage (NetworkError msg _) = msg+errorMessage (ConfigurationError msg _) = msg+errorMessage (ValidationError msg _) = msg+errorMessage (InternalError msg _) = msg++instance Exception LangchainError where+ displayException err = case err of+ LLMError msg ctx -> formatError "LLMError" msg ctx+ AgentError msg ctx -> formatError "AgentError" msg ctx+ MemoryError msg ctx -> formatError "MemoryError" msg ctx+ ToolError msg ctx -> formatError "ToolError" msg ctx+ VectorStoreError msg ctx -> formatError "VectorStoreError" msg ctx+ DocumentLoaderError msg ctx -> formatError "DocumentLoaderError" msg ctx+ EmbeddingError msg ctx -> formatError "EmbeddingError" msg ctx+ RunnableError msg ctx -> formatError "RunnableError" msg ctx+ ParsingError msg ctx -> formatError "ParsingError" msg ctx+ NetworkError msg ctx -> formatError "NetworkError" msg ctx+ ConfigurationError msg ctx -> formatError "ConfigurationError" msg ctx+ ValidationError msg ctx -> formatError "ValidationError" msg ctx+ InternalError msg ctx -> formatError "InternalError" msg ctx+ where+ formatError errType msg Nothing = errType ++ ": " ++ T.unpack msg+ formatError errType msg (Just ctx) =+ errType+ ++ ": "+ ++ T.unpack msg+ ++ " [Component: "+ ++ T.unpack (component ctx)+ ++ ", Operation: "+ ++ T.unpack (operation ctx)+ ++ "]"++-- | Helper constructors+mkErrorCtx ::+ (Text -> Maybe ErrorContext -> LangchainError) -> Text -> Maybe Text -> Maybe Text -> LangchainError+mkErrorCtx ctor msg mbComp mbOp =+ let mbCtx = case (mbComp, mbOp) of+ (Nothing, Nothing) -> Nothing+ (Just c, Just o) -> Just $ mkContext c o mempty+ (Just c, Nothing) -> Just $ mkContext c "unspecified" mempty+ (Nothing, Just o) -> Just $ mkContext "unspecified" o mempty+ in ctor msg mbCtx++-- | Construct an 'LLMError' with optional component and operation.+llmError :: Text -> Maybe Text -> Maybe Text -> LangchainError+llmError = mkErrorCtx LLMError++-- | Construct an 'AgentError' with optional component and operation.+agentError :: Text -> Maybe Text -> Maybe Text -> LangchainError+agentError = mkErrorCtx AgentError++-- | Construct a 'MemoryError' with optional component and operation.+memoryError :: Text -> Maybe Text -> Maybe Text -> LangchainError+memoryError = mkErrorCtx MemoryError++-- | Construct a 'ToolError' with optional component and operation.+toolError :: Text -> Maybe Text -> Maybe Text -> LangchainError+toolError = mkErrorCtx ToolError++-- | Construct a 'VectorStoreError' with optional component and operation.+vectorStoreError :: Text -> Maybe Text -> Maybe Text -> LangchainError+vectorStoreError = mkErrorCtx VectorStoreError++-- | Construct a 'DocumentLoaderError' with optional component and operation.+documentLoaderError :: Text -> Maybe Text -> Maybe Text -> LangchainError+documentLoaderError = mkErrorCtx DocumentLoaderError++-- | Construct an 'EmbeddingError' with optional component and operation.+embeddingError :: Text -> Maybe Text -> Maybe Text -> LangchainError+embeddingError = mkErrorCtx EmbeddingError++-- | Construct a 'RunnableError' with optional component and operation.+runnableError :: Text -> Maybe Text -> Maybe Text -> LangchainError+runnableError = mkErrorCtx RunnableError++-- | Construct a 'ParsingError' with optional component and operation.+parsingError :: Text -> Maybe Text -> Maybe Text -> LangchainError+parsingError = mkErrorCtx ParsingError++-- | Construct a 'NetworkError' with optional component and operation.+networkError :: Text -> Maybe Text -> Maybe Text -> LangchainError+networkError = mkErrorCtx NetworkError++-- | Construct a 'ConfigurationError' with optional component and operation.+configurationError :: Text -> Maybe Text -> Maybe Text -> LangchainError+configurationError = mkErrorCtx ConfigurationError++-- | Construct a 'ValidationError' with optional component and operation.+validationError :: Text -> Maybe Text -> Maybe Text -> LangchainError+validationError = mkErrorCtx ValidationError++-- | Construct an 'InternalError' with optional component and operation.+internalError :: Text -> Maybe Text -> Maybe Text -> LangchainError+internalError = mkErrorCtx InternalError
+ src/Langchain/Guardrail.hs view
@@ -0,0 +1,16 @@+{- |+Module : Langchain.Guardrail+Description : 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+ ( module Langchain.Guardrail.Core+ ) where++import Langchain.Guardrail.Core
+ src/Langchain/Guardrail/Core.hs view
@@ -0,0 +1,143 @@+{-# 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.Error (LangchainError, agentError)+import Langchain.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/Memory.hs view
@@ -0,0 +1,33 @@+{-# LANGUAGE DuplicateRecordFields #-}++{- |+Module : Langchain.Memory+Description : Conversation memory interfaces and implementations+Copyright : (c) 2025-2026 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++Conversation memory management including:+* Unbounded buffer memory ('ConversationBufferMemory')+* Sliding window memory ('WindowBufferMemory')+* Token-budgeted sliding buffer ('TokenBufferMemory')+* Progressive transactional summarization memory ('SummaryMemory')+* Multi-session memory stores with TTL eviction ('SessionMemoryStore')+* Structured entity and attribute fact memory ('EntityMemory')+* Composite memory combinations ('CombinedMemory')+* JSON file persistence and variable loading+-}+module Langchain.Memory+ ( module Langchain.Memory.Core+ , module Langchain.Memory.Summary+ , module Langchain.Memory.Session+ , module Langchain.Memory.Entity+ , module Langchain.Memory.Combined+ ) where++import Langchain.Memory.Combined+import Langchain.Memory.Core+import Langchain.Memory.Entity+import Langchain.Memory.Session+import Langchain.Memory.Summary
+ src/Langchain/Memory/Combined.hs view
@@ -0,0 +1,49 @@+{- |+Module : Langchain.Memory.Combined+Description : Composition of multiple heterogeneous memory stores+Copyright : (c) 2025-2026 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++Composite memory combining multiple distinct memory backends (e.g. WindowBufferMemory + EntityMemory)+into a single unified BaseMemory interface. Broadcasts inputs to all child memories and aggregates+their history and variable outputs.+-}+module Langchain.Memory.Combined+ ( CombinedMemory (..)+ , newCombinedMemory+ ) where++import Control.Monad (forM_)++import Langchain.Memory.Core (BaseMemory (..), SomeMemory (..))++-- | Composite memory multiplexing over a list of existential 'SomeMemory' instances+newtype CombinedMemory = CombinedMemory+ { memories :: [SomeMemory]+ }++-- | Construct a new CombinedMemory from a list of 'SomeMemory' wrappers+newCombinedMemory :: [SomeMemory] -> CombinedMemory+newCombinedMemory = CombinedMemory++instance BaseMemory CombinedMemory where+ messages (CombinedMemory mems) = do+ listOfLists <- mapM messages mems+ pure $ concat listOfLists++ addMessage (CombinedMemory mems) msg =+ forM_ mems (`addMessage` msg)++ addUserMessage (CombinedMemory mems) txt =+ forM_ mems (`addUserMessage` txt)++ addAiMessage (CombinedMemory mems) txt =+ forM_ mems (`addAiMessage` txt)++ clear (CombinedMemory mems) =+ forM_ mems clear++ importMessages (CombinedMemory mems) msgs =+ forM_ mems (`importMessages` msgs)
+ src/Langchain/Memory/Core.hs view
@@ -0,0 +1,455 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-}++{- |+Module : Langchain.Memory.Core+Description : Effect-polymorphic, thread-safe conversation memory interfaces+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.+Includes sliding window buffers, token-budgeted buffers, unbounded conversation buffers,+context saving, variable loading for prompt interpolation, and JSON persistence.+-}+module Langchain.Memory.Core+ ( -- * Base Memory Interface+ BaseMemory (..)+ , SomeMemory (..)++ -- * Context Saving & Variable Loading+ , saveContext+ , loadMemoryVariables+ , formatHistory+ , formatHistoryWithLabels++ -- * Memory Implementations+ , ConversationBufferMemory (..)+ , newConversationBufferMemory+ , WindowBufferMemory (..)+ , newWindowBufferMemory+ , TokenBufferMemory (..)+ , newTokenBufferMemory+ , newTokenBufferMemoryWithStrategy+ , OverflowStrategy (..)+ , TokenCounter++ -- * Persistence & Serialization+ , exportMessages+ , saveMemoryToFile+ , loadMemoryFromFile++ -- * Utility Functions+ , countTokens+ , approximateTokenCount+ , trimMessages+ , initialMessages+ , resetToInitial+ , resetConversationBuffer+ , resetWindowBuffer+ , resetTokenBuffer+ ) 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.Aeson (decode, encode)+import qualified Data.ByteString.Lazy as BSL+import Data.Map.Strict (Map)+import qualified Data.Map.Strict as Map+import Data.Text (Text)+import qualified Data.Text as T+import GHC.Generics (Generic)++import Langchain.Error (LangchainError, memoryError)+import Langchain.Model+ ( Message (..)+ , Role (..)+ , assistantMessage+ , extractMessageText+ , systemMessage+ , userMessage+ )++-- ---------------------------------------------------------------------------+-- Base Memory Interface+-- ---------------------------------------------------------------------------++-- | Effect-polymorphic BaseMemory typeclass providing full conversation management.+class BaseMemory mem where+ -- | Retrieve current conversation messages+ messages ::+ (MonadIO m, MonadError LangchainError m) =>+ mem ->+ m [Message]++ -- | Add a structured message to history+ addMessage ::+ (MonadIO m, MonadError LangchainError m) =>+ mem ->+ Message ->+ m ()++ -- | 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)++ -- | Reset memory to initial state+ clear ::+ (MonadIO m, MonadError LangchainError m) =>+ mem ->+ m ()++ -- | Import and replace conversation messages completely+ importMessages ::+ (MonadIO m, MonadError LangchainError m) =>+ mem ->+ [Message] ->+ m ()+ importMessages mem msgs = do+ clear mem+ mapM_ (addMessage mem) msgs++-- | Atomically record both user input and AI response in a single step+saveContext ::+ (BaseMemory mem, MonadIO m, MonadError LangchainError m) =>+ mem ->+ Text ->+ Text ->+ m ()+saveContext mem userTxt aiTxt = do+ addUserMessage mem userTxt+ addAiMessage mem aiTxt++-- | Load memory variables as a Map for direct prompt template interpolation+loadMemoryVariables ::+ (BaseMemory mem, MonadIO m, MonadError LangchainError m) =>+ mem ->+ m (Map Text Text)+loadMemoryVariables mem = do+ msgs <- messages mem+ pure $ Map.singleton "history" (formatHistory msgs)++-- | Format conversation messages to standard string representation+formatHistory :: [Message] -> Text+formatHistory = formatHistoryWithLabels "Human" "AI"++-- | Format conversation messages with custom role labels+formatHistoryWithLabels :: Text -> Text -> [Message] -> Text+formatHistoryWithLabels userLabel aiLabel msgs =+ T.intercalate "\n" (map formatMsg msgs)+ where+ formatMsg m =+ let prefix = case messageRole m of+ User -> userLabel <> ": "+ Assistant -> aiLabel <> ": "+ System -> "System: "+ Tool -> "Tool: "+ Function -> "Function: "+ Developer -> "Developer: "+ in prefix <> extractMessageText m++-- ---------------------------------------------------------------------------+-- Existential Wrapper+-- ---------------------------------------------------------------------------++-- | Existential wrapper for any 'BaseMemory' instance+data SomeMemory where+ SomeMemory :: (BaseMemory mem) => mem -> SomeMemory++instance BaseMemory SomeMemory where+ messages (SomeMemory m) = messages m+ addMessage (SomeMemory m) = addMessage m+ clear (SomeMemory m) = clear m+ addUserMessage (SomeMemory m) = addUserMessage m+ addAiMessage (SomeMemory m) = addAiMessage m+ importMessages (SomeMemory m) = importMessages m++-- | No-op BaseMemory instance for unit+instance BaseMemory () where+ messages () = pure []+ addMessage () _ = pure ()+ clear () = pure ()++-- ---------------------------------------------------------------------------+-- ConversationBufferMemory (Unbounded)+-- ---------------------------------------------------------------------------++-- | Unbounded conversation memory keeping the full message history in a thread-safe STM TVar+data ConversationBufferMemory = ConversationBufferMemory+ { memVar :: !(TVar [Message])+ , initialMessagesList :: ![Message]+ }++instance Show ConversationBufferMemory where+ show _ = "ConversationBufferMemory"++instance Eq ConversationBufferMemory where+ (ConversationBufferMemory tv1 _) == (ConversationBufferMemory tv2 _) = tv1 == tv2++-- | Construct a new unbounded ConversationBufferMemory+newConversationBufferMemory :: (MonadIO m) => [Message] -> m ConversationBufferMemory+newConversationBufferMemory initMsgs = liftIO $ do+ tv <- newTVarIO initMsgs+ pure $ ConversationBufferMemory tv initMsgs++instance BaseMemory ConversationBufferMemory where+ messages (ConversationBufferMemory tv _) = liftIO $ readTVarIO tv++ addMessage (ConversationBufferMemory tv _) newMsg = liftIO $ atomically $ do+ modifyTVar' tv (\curr -> curr ++ [newMsg])++ clear (ConversationBufferMemory tv inits) = liftIO $ atomically $ do+ let resetMsgs = if null inits then [systemMessage "You are a helpful AI assistant"] else inits+ writeTVar tv resetMsgs++ importMessages (ConversationBufferMemory tv _) msgs = liftIO $ atomically $ do+ writeTVar tv msgs++-- ---------------------------------------------------------------------------+-- WindowBufferMemory (Sliding Window by Count)+-- ---------------------------------------------------------------------------++-- | Sliding window memory backed by thread-safe STM TVar+data WindowBufferMemory = WindowBufferMemory+ { maxWindowSize :: !Int+ , memVar :: !(TVar [Message])+ , initialMessagesList :: ![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) =>+ -- | Maximum message window size+ Int ->+ -- | Initial messages to seed the window+ [Message] ->+ -- | Initialized thread-safe WindowBufferMemory+ m WindowBufferMemory+newWindowBufferMemory sz initMsgs = liftIO $ do+ tv <- newTVarIO initMsgs+ pure $ WindowBufferMemory sz tv initMsgs++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"]++ importMessages (WindowBufferMemory _ tv _) msgs = liftIO $ atomically $ do+ writeTVar tv msgs++-- ---------------------------------------------------------------------------+-- TokenBufferMemory (Sliding Window by Token Budget)+-- ---------------------------------------------------------------------------++-- | Strategy for handling incoming messages that exceed token capacity+data OverflowStrategy+ = EvictOldest+ | RejectOnOverflow+ deriving (Show, Eq, Generic)++-- | Function type for counting tokens in a list of messages+type TokenCounter = [Message] -> Int++-- | Approximate token count: 4 characters ≈ 1 token+approximateTokenCount :: [Message] -> Int+approximateTokenCount =+ sum . map (\m -> ceiling (fromIntegral (T.length (extractMessageText m)) / (4.0 :: Double)))++-- | Approximate token count alias for backwards compatibility+countTokens :: [Message] -> Int+countTokens = approximateTokenCount++-- | Token-based sliding window memory type+data TokenBufferMemory = TokenBufferMemory+ { maxTokens :: !Int+ , memVar :: !(TVar [Message])+ , overflowStrategy :: !OverflowStrategy+ , tokenCounter :: !TokenCounter+ , initialMessagesList :: ![Message]+ }++instance Show TokenBufferMemory where+ show (TokenBufferMemory maxT _ strat _ _) =+ "TokenBufferMemory { maxTokens = " ++ show maxT ++ ", overflowStrategy = " ++ show strat ++ " }"++instance Eq TokenBufferMemory where+ (TokenBufferMemory t1 tv1 s1 _ _) == (TokenBufferMemory t2 tv2 s2 _ _) =+ t1 == t2 && tv1 == tv2 && s1 == s2++-- | Construct a new TokenBufferMemory with default eviction strategy and approximate counting+newTokenBufferMemory ::+ (MonadIO m) =>+ -- | Maximum allowed tokens in history+ Int ->+ -- | Initial messages to seed the buffer+ [Message] ->+ -- | Initialized thread-safe TokenBufferMemory+ m TokenBufferMemory+newTokenBufferMemory maxT initMsgs =+ newTokenBufferMemoryWithStrategy maxT initMsgs EvictOldest approximateTokenCount++-- | Construct a TokenBufferMemory with explicit overflow strategy and custom token counter+newTokenBufferMemoryWithStrategy ::+ (MonadIO m) =>+ Int ->+ [Message] ->+ OverflowStrategy ->+ TokenCounter ->+ m TokenBufferMemory+newTokenBufferMemoryWithStrategy maxT initMsgs strat counter = liftIO $ do+ tv <- newTVarIO initMsgs+ pure $ TokenBufferMemory maxT tv strat counter initMsgs++instance BaseMemory TokenBufferMemory where+ messages (TokenBufferMemory _ tv _ _ _) = liftIO $ readTVarIO tv++ addMessage (TokenBufferMemory maxT tv _ counter _) newMsg = do+ let newMsgTokens = counter [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 counter 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"]++ importMessages (TokenBufferMemory _ tv _ _ _) msgs = liftIO $ atomically $ do+ writeTVar tv msgs++-- ---------------------------------------------------------------------------+-- Persistence & Serialization+-- ---------------------------------------------------------------------------++-- | Export all conversation messages from memory+exportMessages ::+ (BaseMemory mem, MonadIO m, MonadError LangchainError m) =>+ mem ->+ m [Message]+exportMessages = messages++-- | Persist memory state to a JSON file+saveMemoryToFile ::+ (BaseMemory mem, MonadIO m, MonadError LangchainError m) =>+ FilePath ->+ mem ->+ m ()+saveMemoryToFile path mem = do+ msgs <- messages mem+ liftIO $ BSL.writeFile path (encode msgs)++-- | Load memory state from a JSON file+loadMemoryFromFile ::+ (BaseMemory mem, MonadIO m, MonadError LangchainError m) =>+ FilePath ->+ mem ->+ m ()+loadMemoryFromFile path mem = do+ content <- liftIO $ BSL.readFile path+ case decode content of+ Nothing ->+ throwError $+ memoryError+ ("Failed to parse memory JSON from file: " <> T.pack path)+ (Just "loadMemoryFromFile")+ Nothing+ Just msgs -> importMessages mem msgs++-- ---------------------------------------------------------------------------+-- Utilities+-- ---------------------------------------------------------------------------++-- | Reset memory back to its exact initial messages+resetToInitial ::+ (MonadIO m) =>+ TVar [Message] ->+ [Message] ->+ m ()+resetToInitial tv inits = liftIO $ atomically $ writeTVar tv inits++-- | Reset a 'ConversationBufferMemory' back to its initial seed messages+resetConversationBuffer :: (MonadIO m) => ConversationBufferMemory -> m ()+resetConversationBuffer (ConversationBufferMemory tv inits) = resetToInitial tv inits++-- | Reset a 'WindowBufferMemory' back to its initial seed messages+resetWindowBuffer :: (MonadIO m) => WindowBufferMemory -> m ()+resetWindowBuffer (WindowBufferMemory _ tv inits) = resetToInitial tv inits++-- | Reset a 'TokenBufferMemory' back to its initial seed messages+resetTokenBuffer :: (MonadIO m) => TokenBufferMemory -> m ()+resetTokenBuffer (TokenBufferMemory _ tv _ _ inits) = resetToInitial tv inits++-- | Pure helper to trim messages to last N+trimMessages ::+ -- | Number of most recent messages to keep+ Int ->+ -- | Input message list+ [Message] ->+ -- | Trimmed message list+ [Message]+trimMessages n msgs = drop (max 0 (length msgs - n)) msgs++-- | Pure helper to construct initial system message history+initialMessages ::+ -- | System prompt text+ Text ->+ -- | Initial message history with system message+ [Message]+initialMessages sysPrompt = [systemMessage sysPrompt]
+ src/Langchain/Memory/Entity.hs view
@@ -0,0 +1,103 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++{- |+Module : Langchain.Memory.Entity+Description : Structured key-value and entity fact memory+Copyright : (c) 2025-2026 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++Thread-safe entity and fact memory.+Retains user attributes, preferences, and extracted facts across conversation turns+for injection into prompt variables and system messages.+-}+module Langchain.Memory.Entity+ ( EntityMemory (..)+ , newEntityMemory+ , setEntity+ , getEntity+ , getAllEntities+ , deleteEntity+ , clearEntities+ , formatEntities+ , resetEntitiesToInitial+ ) where++import Control.Concurrent.STM+ ( TVar+ , atomically+ , modifyTVar'+ , newTVarIO+ , readTVarIO+ , writeTVar+ )+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.Memory.Core (BaseMemory (..))+import Langchain.Model (systemMessage)++-- | Entity memory holding key-value facts and attributes in STM+data EntityMemory = EntityMemory+ { entityVar :: !(TVar (Map Text Text))+ , initialEntities :: !(Map Text Text)+ }++-- | Construct a new EntityMemory with optional initial facts+newEntityMemory :: MonadIO m => Map Text Text -> m EntityMemory+newEntityMemory inits = liftIO $ do+ var <- newTVarIO inits+ pure $ EntityMemory var inits++-- | Set or update a specific entity attribute+setEntity :: MonadIO m => EntityMemory -> Text -> Text -> m ()+setEntity EntityMemory {..} key val = liftIO $ atomically $ do+ modifyTVar' entityVar (Map.insert key val)++-- | Look up a specific entity attribute+getEntity :: MonadIO m => EntityMemory -> Text -> m (Maybe Text)+getEntity EntityMemory {..} key = liftIO $ do+ m <- readTVarIO entityVar+ pure $ Map.lookup key m++-- | Retrieve all current entity facts+getAllEntities :: MonadIO m => EntityMemory -> m (Map Text Text)+getAllEntities EntityMemory {..} = liftIO $ readTVarIO entityVar++-- | Delete an entity attribute+deleteEntity :: MonadIO m => EntityMemory -> Text -> m ()+deleteEntity EntityMemory {..} key = liftIO $ atomically $ do+ modifyTVar' entityVar (Map.delete key)++-- | Clear all entity attributes+clearEntities :: MonadIO m => EntityMemory -> m ()+clearEntities EntityMemory {..} = liftIO $ atomically $ do+ writeTVar entityVar Map.empty++-- | Reset entity memory back to its initial seed facts+resetEntitiesToInitial :: MonadIO m => EntityMemory -> m ()+resetEntitiesToInitial EntityMemory {..} = liftIO $ atomically $ do+ writeTVar entityVar initialEntities++-- | Format key-value entities into bullet points+formatEntities :: Map Text Text -> Text+formatEntities m+ | Map.null m = ""+ | otherwise =+ T.intercalate "\n" ["- " <> k <> ": " <> v | (k, v) <- Map.toList m]++instance BaseMemory EntityMemory where+ messages EntityMemory {..} = liftIO $ do+ m <- readTVarIO entityVar+ if Map.null m+ then pure []+ else pure [systemMessage ("Relevant context facts:\n" <> formatEntities m)]++ addMessage _ _ = pure ()++ clear = clearEntities
+ src/Langchain/Memory/Session.hs view
@@ -0,0 +1,159 @@+{-# LANGUAGE RecordWildCards #-}++{- |+Module : Langchain.Memory.Session+Description : Multi-session conversation memory management with TTL eviction+Copyright : (c) 2025-2026 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++Thread-safe multi-session conversation store.+Provides per-session memory isolation for web servers, chatbots, and multi-tenant agents+with configurable Time-To-Live (TTL) expiration to avoid unbounded memory leaks.+-}+module Langchain.Memory.Session+ ( SessionId+ , SessionMemoryStore (..)+ , newSessionMemoryStore+ , newSessionMemoryStoreWithTTL+ , getSessionMemory+ , listSessions+ , hasSession+ , deleteSession+ , clearAllSessions+ , sessionCount+ , cleanupExpiredSessions+ , exportAllSessions+ ) where++import Control.Concurrent.STM+ ( TVar+ , atomically+ , modifyTVar'+ , newTVarIO+ , readTVar+ , readTVarIO+ , writeTVar+ )+import Control.Monad.IO.Class (MonadIO, liftIO)+import Data.Map.Strict (Map)+import qualified Data.Map.Strict as Map+import Data.Text (Text)+import Data.Time (NominalDiffTime, UTCTime, diffUTCTime, getCurrentTime)++import Langchain.Model (Message)++-- | Unique identifier for a conversation session (e.g. user ID, session token, or UUID)+type SessionId = Text++-- | Thread-safe multi-session memory repository+data SessionMemoryStore mem = SessionMemoryStore+ { storeVar :: !(TVar (Map SessionId (mem, UTCTime)))+ , memFactory :: !(SessionId -> IO mem)+ , sessionTTL :: !(Maybe NominalDiffTime)+ }++-- | Create a new SessionMemoryStore with no TTL (sessions persist until explicitly deleted)+newSessionMemoryStore ::+ MonadIO m =>+ -- | Factory creating a fresh memory instance for a given SessionId+ (SessionId -> IO mem) ->+ m (SessionMemoryStore mem)+newSessionMemoryStore factory = liftIO $ do+ var <- newTVarIO Map.empty+ pure $ SessionMemoryStore var factory Nothing++-- | Create a new SessionMemoryStore with automatic Time-To-Live expiration+newSessionMemoryStoreWithTTL ::+ MonadIO m =>+ -- | Maximum idle time before session is considered expired+ NominalDiffTime ->+ -- | Factory creating a fresh memory instance for a given SessionId+ (SessionId -> IO mem) ->+ m (SessionMemoryStore mem)+newSessionMemoryStoreWithTTL ttl factory = liftIO $ do+ var <- newTVarIO Map.empty+ pure $ SessionMemoryStore var factory (Just ttl)++-- | Retrieve (or create) the memory instance for a specific session, updating its last access timestamp+getSessionMemory ::+ MonadIO m =>+ SessionMemoryStore mem ->+ SessionId ->+ m mem+getSessionMemory SessionMemoryStore {..} sId = liftIO $ do+ now <- getCurrentTime+ mExisting <- atomically $ do+ m <- readTVar storeVar+ case Map.lookup sId m of+ Just (mem, lastAccess) ->+ case sessionTTL of+ Just ttl | diffUTCTime now lastAccess > ttl -> pure Nothing+ _ -> do+ modifyTVar' storeVar (Map.insert sId (mem, now))+ pure (Just mem)+ Nothing -> pure Nothing++ case mExisting of+ Just mem -> pure mem+ Nothing -> do+ newMem <- memFactory sId+ atomically $ modifyTVar' storeVar (Map.insert sId (newMem, now))+ pure newMem++-- | List all active session IDs+listSessions :: MonadIO m => SessionMemoryStore mem -> m [SessionId]+listSessions SessionMemoryStore {..} = liftIO $ do+ m <- readTVarIO storeVar+ pure $ Map.keys m++-- | Check if a session exists and is not expired+hasSession :: MonadIO m => SessionMemoryStore mem -> SessionId -> m Bool+hasSession SessionMemoryStore {..} sId = liftIO $ do+ now <- getCurrentTime+ m <- readTVarIO storeVar+ pure $ case Map.lookup sId m of+ Just (_, lastAccess) ->+ case sessionTTL of+ Just ttl -> diffUTCTime now lastAccess <= ttl+ Nothing -> True+ Nothing -> False++-- | Delete a specific session from the store+deleteSession :: MonadIO m => SessionMemoryStore mem -> SessionId -> m ()+deleteSession SessionMemoryStore {..} sId = liftIO $ atomically $ do+ modifyTVar' storeVar (Map.delete sId)++-- | Remove all sessions from the store+clearAllSessions :: MonadIO m => SessionMemoryStore mem -> m ()+clearAllSessions SessionMemoryStore {..} = liftIO $ atomically $ do+ writeTVar storeVar Map.empty++-- | Count active sessions+sessionCount :: MonadIO m => SessionMemoryStore mem -> m Int+sessionCount SessionMemoryStore {..} = liftIO $ do+ m <- readTVarIO storeVar+ pure $ Map.size m++-- | Purge all sessions that have exceeded the TTL limit. Returns the number of evicted sessions.+cleanupExpiredSessions :: MonadIO m => SessionMemoryStore mem -> m Int+cleanupExpiredSessions SessionMemoryStore {..} = liftIO $ case sessionTTL of+ Nothing -> pure 0+ Just ttl -> do+ now <- getCurrentTime+ atomically $ do+ m <- readTVar storeVar+ let (active, expired) = Map.partition (\(_, lastAccess) -> diffUTCTime now lastAccess <= ttl) m+ writeTVar storeVar active+ pure (Map.size expired)++-- | Export all sessions' message histories+exportAllSessions ::+ MonadIO m =>+ (mem -> m [Message]) ->+ SessionMemoryStore mem ->+ m (Map SessionId [Message])+exportAllSessions getMsgs SessionMemoryStore {..} = do+ sMap <- liftIO $ readTVarIO storeVar+ Map.traverseWithKey (\_ (mem, _) -> getMsgs mem) sMap
+ src/Langchain/Memory/Summary.hs view
@@ -0,0 +1,134 @@+{-# 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.+Guarantees transaction safety: conversation history is only pruned after the LLM summarization+call successfully completes, preventing irreversible data loss on network or model failures.+-}+module Langchain.Memory.Summary+ ( SummaryMemory (..)+ , newSummaryMemory+ , newSummaryMemoryWithPrompt+ , getSummary+ , defaultSummaryPrompt+ , formatMessages+ ) where++import Control.Concurrent.STM+ ( TVar+ , atomically+ , modifyTVar'+ , newTVarIO+ , readTVar+ , readTVarIO+ , writeTVar+ )+import Control.Monad (when)+import Control.Monad.IO.Class (MonadIO, liftIO)+import Data.List (partition)+import Data.Text (Text)+import qualified Data.Text as T++import Langchain.Memory.Core (BaseMemory (..), formatHistory)+import Langchain.Model+ ( ChatModel (..)+ , Message (..)+ , Role (..)+ , extractMessageText+ , systemMessage+ , userMessage+ )++-- | Progressive summarization memory backed by thread-safe STM TVars+data SummaryMemory model = SummaryMemory+ { summaryModel :: model+ , maxMessageThreshold :: !Int+ , summaryBufferVar :: !(TVar Text)+ , recentMessagesVar :: !(TVar [Message])+ , promptBuilder :: !(Text -> [Message] -> Text)+ , initialMessagesList :: ![Message]+ }++-- | Default summarization prompt generator+defaultSummaryPrompt :: Text -> [Message] -> Text+defaultSummaryPrompt currentSummary toSummarize =+ "Current summary:\n"+ <> currentSummary+ <> "\n\nNew lines to summarize:\n"+ <> formatMessages toSummarize+ <> "\n\nPlease provide an updated, concise summary of the conversation above."++-- | Construct a new SummaryMemory instance with default prompt+newSummaryMemory :: MonadIO m => model -> Int -> [Message] -> m (SummaryMemory model)+newSummaryMemory model threshold initMsgs =+ newSummaryMemoryWithPrompt model threshold initMsgs defaultSummaryPrompt++-- | Construct a SummaryMemory instance with custom summarization prompt generator+newSummaryMemoryWithPrompt ::+ MonadIO m =>+ model ->+ Int ->+ [Message] ->+ (Text -> [Message] -> Text) ->+ m (SummaryMemory model)+newSummaryMemoryWithPrompt model threshold initMsgs pBuilder = liftIO $ do+ sVar <- newTVarIO ""+ mVar <- newTVarIO initMsgs+ pure $ SummaryMemory model threshold sVar mVar pBuilder initMsgs++-- | 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 do+ -- Preserve any system instructions at the head, followed by the summary, followed by recent turns+ let (sysMsgs, nonSysMsgs) = partition (\m -> messageRole m == System) recent+ summaryMsg = systemMessage ("Summary of previous conversation:\n" <> sumTxt)+ pure (sysMsgs ++ [summaryMsg] ++ nonSysMsgs)++ addMessage SummaryMemory {..} newMsg = do+ -- 1. Append message to recent buffer+ (shouldSummarize, toSummarize, keepCount) <- liftIO $ atomically $ do+ modifyTVar' recentMessagesVar (\msgs -> msgs ++ [newMsg])+ currentMsgs <- readTVar recentMessagesVar+ if length currentMsgs > maxMessageThreshold+ then do+ let keep = max 2 (maxMessageThreshold `div` 2)+ (old, _) = splitAt (length currentMsgs - keep) currentMsgs+ pure (True, old, keep)+ else pure (False, [], length currentMsgs)++ -- 2. If summarization threshold reached, execute LLM summarization.+ -- Crucial: Pruning ONLY happens after the LLM invoke succeeds!+ when (shouldSummarize && not (null toSummarize)) $ do+ currentSummary <- liftIO $ readTVarIO summaryBufferVar+ let summaryPrompt = promptBuilder currentSummary toSummarize+ aiResp <- invoke summaryModel [userMessage summaryPrompt] Nothing+ let newSummary = extractMessageText aiResp++ -- Atomically commit new summary and prune summarized messages+ liftIO $ atomically $ do+ writeTVar summaryBufferVar newSummary+ modifyTVar' recentMessagesVar (\msgs -> drop (max 0 (length msgs - keepCount)) msgs)++ clear SummaryMemory {..} = liftIO $ atomically $ do+ writeTVar summaryBufferVar ""+ writeTVar recentMessagesVar []++formatMessages :: [Message] -> Text+formatMessages = formatHistory
+ src/Langchain/Model.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE TypeFamilies #-}++{- |+Module : Langchain.Model+Description : Central ChatModel typeclass for LLM providers+Copyright : (c) 2025-2026 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++Provides effect-polymorphic 'ChatModel' interface.+-}+module Langchain.Model+ ( ChatModel (..)+ , module Langchain.Model.Types+ ) where++import Control.Monad.Except (MonadError)+import Control.Monad.IO.Class (MonadIO)+import Data.Kind (Type)++import Langchain.Error (LangchainError)+import Langchain.Model.Types+import Langchain.Stream (ChatStream)++-- | Effect-polymorphic ChatModel typeclass for LLM providers+class ChatModel model where+ type ModelConfig model :: Type++ -- | Single synchronous invocation+ invoke ::+ (MonadIO m, MonadError LangchainError m) =>+ model ->+ [Message] ->+ Maybe (ModelConfig model) ->+ m Message++ -- | Batch invocations (default: sequential)+ batch ::+ (MonadIO m, MonadError LangchainError m) =>+ model ->+ [[Message]] ->+ Maybe (ModelConfig model) ->+ m [Message]+ batch model msgs cfg = mapM (\m -> invoke model m cfg) msgs++ -- | Streaming invocation yielding structured StreamEvents via Conduit+ stream ::+ model ->+ [Message] ->+ Maybe (ModelConfig model) ->+ ChatStream
+ src/Langchain/Model/Types.hs view
@@ -0,0 +1,200 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -Wno-partial-fields #-}++{- |+Module : Langchain.Model.Types+Description : Multi-modal ContentBlock and Message data types+Copyright : (c) 2025-2026 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++Data types for multi-modal messages, content blocks, roles, and tool calls.+-}+module Langchain.Model.Types+ ( ContentBlock (..)+ , ImageContent (..)+ , ImageSource (..)+ , Role (..)+ , ToolCall (..)+ , Message (..)+ , textMessage+ , userMessage+ , systemMessage+ , assistantMessage+ , toolMessage+ , imageMessage+ , extractMessageText+ , roleLabel+ , formatMessageString+ ) where++import Data.Aeson (FromJSON (..), ToJSON (..), Value (..), object, withObject, (.:), (.:?), (.=))+import Data.ByteString (ByteString)+import qualified Data.ByteString.Base64 as Base64+import Data.List.NonEmpty (NonEmpty (..))+import qualified Data.List.NonEmpty as NonEmpty+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.Encoding as TE+import GHC.Generics (Generic)++-- | A single content block within a multi-modal message.+data ContentBlock+ = TextBlock {blockText :: Text}+ | ImageBlock ImageContent+ | AudioBlock {blockMimeType :: Text, blockBase64 :: Text}+ | DataBlock {blockBytes :: ByteString}+ deriving (Eq, Show, Generic)++-- | Multi-modal image content with source, optional detail, and metadata.+data ImageContent = ImageContent+ { imageSource :: ImageSource+ , imageDetail :: Maybe Text+ , imageMetadata :: Maybe Value+ }+ deriving (Eq, Show, Generic)++-- | Underlying image data source: either raw base64 or a remote URL.+data ImageSource+ = ImageBase64 {imageMimeType :: Maybe Text, imageData :: Text}+ | ImageUrl {imageUrl :: Text}+ deriving (Eq, Show, Generic)++instance ToJSON ContentBlock where+ toJSON (TextBlock t) = object ["type" .= ("text" :: Text), "text" .= t]+ toJSON (ImageBlock ImageContent {imageSource = ImageUrl url, imageDetail = detail}) =+ object+ [ "type" .= ("image_url" :: Text)+ , "image_url" .= object (maybe id ((:) . ("detail" .=)) detail ["url" .= url])+ ]+ toJSON (ImageBlock ImageContent {imageSource = ImageBase64 (Just mime) imageData, imageMetadata = metadata}) =+ object $+ ["type" .= ("image" :: Text), "mime_type" .= mime, "data" .= imageData]+ <> maybe [] (pure . ("metadata" .=)) metadata+ toJSON (ImageBlock ImageContent {imageSource = ImageBase64 _ imageData, imageMetadata = metadata}) =+ object $+ ["type" .= ("image" :: Text), "source_type" .= ("base64" :: Text), "data" .= imageData]+ <> maybe [] (pure . ("metadata" .=)) metadata+ toJSON (AudioBlock mime b64) = object ["type" .= ("audio" :: Text), "mime_type" .= mime, "data" .= b64]+ toJSON (DataBlock bs) = object ["type" .= ("data" :: Text), "data" .= TE.decodeUtf8 (Base64.encode bs)]++instance FromJSON ContentBlock where+ parseJSON = withObject "ContentBlock" $ \v -> do+ typ <- v .: "type"+ case (typ :: Text) of+ "text" -> TextBlock <$> v .: "text"+ "image" -> do+ mimeType <- v .:? "mime_type"+ case mimeType of+ Just mime -> do+ imageData <- v .: "data"+ metadata <- v .:? "metadata"+ pure $ ImageBlock $ ImageContent (ImageBase64 (Just mime) imageData) Nothing metadata+ Nothing -> do+ sourceType <- v .: "source_type"+ imageData <- v .: "data"+ metadata <- v .:? "metadata"+ let source =+ case (sourceType :: Text) of+ "url" -> ImageUrl imageData+ _ -> ImageBase64 Nothing imageData+ pure $ ImageBlock $ ImageContent source Nothing metadata+ "image_url" -> do+ imageUrl <- v .: "image_url"+ url <- imageUrl .: "url"+ detail <- imageUrl .:? "detail"+ pure $ ImageBlock $ ImageContent (ImageUrl url) detail Nothing+ "audio" -> AudioBlock <$> v .: "mime_type" <*> v .: "data"+ "data" -> do+ b64Text <- v .: "data"+ case Base64.decode (TE.encodeUtf8 b64Text) of+ Left err -> fail $ "Invalid base64 data block: " ++ err+ Right bs -> pure $ DataBlock bs+ other -> fail $ "Unknown ContentBlock type: " ++ show other++-- | Complete set of conversation roles supported across LLM providers.+data Role+ = System+ | User+ | Assistant+ | Tool+ | Developer+ | Function+ deriving (Eq, Ord, Show, Bounded, Enum, Generic, ToJSON, FromJSON)++-- | Structured tool call from an LLM response.+data ToolCall = ToolCall+ { toolCallId :: Text+ , toolCallType :: Text+ -- ^ Always "function" for current providers+ , toolCallName :: Text+ , toolCallArguments :: Value+ -- ^ Parsed JSON Value arguments+ }+ deriving (Eq, Show, Generic, ToJSON, FromJSON)++-- | Structured chat message supporting multi-modal content blocks.+data Message = Message+ { messageRole :: Role+ , messageContents :: NonEmpty ContentBlock+ , messageName :: Maybe Text+ , messageToolCalls :: Maybe [ToolCall]+ , messageToolId :: Maybe Text+ -- ^ Associated tool call ID for Tool role+ , messageMetadata :: Map Text Value+ -- ^ Opaque provider-specific metadata.+ }+ deriving (Eq, Show, Generic, ToJSON, FromJSON)++-- | Create a message with a single text content block.+textMessage :: Role -> Text -> Message+textMessage r t = Message r (TextBlock t :| []) Nothing Nothing Nothing Map.empty++-- | Create a User role text message.+userMessage :: Text -> Message+userMessage = textMessage User++-- | Create a System role text message.+systemMessage :: Text -> Message+systemMessage = textMessage System++-- | Create an Assistant role text message.+assistantMessage :: Text -> Message+assistantMessage = textMessage Assistant++-- | Create a Tool role text message.+toolMessage :: Text -> Message+toolMessage = textMessage Tool++-- | Create an Image content block message.+imageMessage :: Role -> Text -> Text -> Message+imageMessage r mime b64 =+ Message+ r+ (ImageBlock (ImageContent (ImageBase64 (Just mime) b64) Nothing Nothing) :| [])+ Nothing+ Nothing+ Nothing+ Map.empty++-- | Extract all text content blocks concatenated into a single Text string.+extractMessageText :: Message -> Text+extractMessageText msg = T.intercalate "\n" [t | TextBlock t <- NonEmpty.toList (messageContents msg)]++-- | Label used when rendering a chat message role as plain text.+roleLabel :: Role -> Text+roleLabel System = "System"+roleLabel User = "Human"+roleLabel Assistant = "AI"+roleLabel Tool = "Tool"+roleLabel Developer = "Developer"+roleLabel Function = "Function"++-- | Render a message as a single plain-text line with its role label.+formatMessageString :: Message -> Text+formatMessageString chatMessage =+ roleLabel (messageRole chatMessage) <> ": " <> extractMessageText chatMessage
+ src/Langchain/Monad.hs view
@@ -0,0 +1,55 @@+{-# LANGUAGE FlexibleContexts #-}++{- |+Module : Langchain.Monad+Description : Core LangchainT monad transformer+Copyright : (c) 2025-2026 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++Provides the canonical monad transformer stack 'LangchainT' and its execution+runner 'runLangchainT'.++'LangchainT' is parameterised over the reader environment @r@, so each+provider (or application) can supply its own config type rather than being+forced into a one-size-fits-all @LangchainConfig@. Use @r = ()@ when you do+not need a shared environment at all.++@+-- With a custom config+type App a = LangchainT MyConfig IO a++runApp :: MyConfig -> App a -> IO (Either LangchainError a)+runApp = runLangchainT++-- Without any config+runSimple :: LangchainT () IO a -> IO (Either LangchainError a)+runSimple = runLangchainT ()+@+-}+module Langchain.Monad+ ( LangchainT+ , runLangchainT+ , throwLangchainError+ ) where++import Control.Monad.Except (ExceptT, MonadError, runExceptT, throwError)+import Control.Monad.Reader (ReaderT, runReaderT)++import Langchain.Error (LangchainError)++{- | Standard framework monad transformer stack: ReaderT over ExceptT.++The type variable @r@ is the reader environment — pass your own provider+config, application context, or @()@ when none is needed.+-}+type LangchainT r m = ReaderT r (ExceptT LangchainError m)++-- | Execute a 'LangchainT' computation with a given environment.+runLangchainT :: r -> LangchainT r m a -> m (Either LangchainError a)+runLangchainT env action = runExceptT (runReaderT action env)++-- | Throw a 'LangchainError' inside any 'MonadError' context.+throwLangchainError :: MonadError LangchainError m => LangchainError -> m a+throwLangchainError = throwError
+ src/Langchain/Observability.hs view
@@ -0,0 +1,275 @@+{-# 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.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.hs view
@@ -0,0 +1,18 @@+{- |+Module : Langchain.OutputParser+Description : Output parsers and structured data extraction+Copyright : (c) 2025-2026 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++Output parsers to transform raw LLM text into structured formats including+lists, JSON, and typed Haskell data structures via JSON Schema generation.+-}+module Langchain.OutputParser+ ( module Langchain.OutputParser.Core+ , module Langchain.OutputParser.Structured+ ) where++import Langchain.OutputParser.Core+import Langchain.OutputParser.Structured
+ src/Langchain/OutputParser/Core.hs view
@@ -0,0 +1,84 @@+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE OverloadedStrings #-}++{- |+Module: Langchain.OutputParser.Core+Copyright: (c) 2025-2026 Tushar Adhatrao+License: MIT+Maintainer: Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability: experimental++Core output parser typeclass and standard instances for basic text transformations.+For typed JSON and schema-driven parsing, see "Langchain.OutputParser.Structured".+-}+module Langchain.OutputParser.Core+ ( -- * Typeclass+ OutputParser (..)++ -- * Parsers+ , CommaSeparatedList (..)+ ) where++import Data.Text (Text)+import qualified Data.Text as T+import qualified Text.Read as TR++import Langchain.Error (LangchainResult, parsingError)++{- | Typeclass for parsing output from language models into specific types.+Instances of this class define how to convert a 'Text' output into a value of type @a@.+-}+class OutputParser a where+ {- | Parse the given text into a value of type @a@.+ Returns 'Left' with an error message if parsing fails, or 'Right' with the parsed value.+ -}+ parse :: Text -> LangchainResult a++-- | Identity parser returning the raw text output unchanged.+instance OutputParser Text where+ parse = Right++{- | Parse a boolean value from text using case-insensitive normalization.++Accepts @"true"@, @"t"@, @"yes"@, @"1"@ as 'True', and @"false"@, @"f"@, @"no"@, @"0"@ as 'False'.+-}+instance OutputParser Bool where+ parse txt =+ case T.toLower (T.strip txt) of+ "true" -> Right True+ "t" -> Right True+ "yes" -> Right True+ "1" -> Right True+ "false" -> Right False+ "f" -> Right False+ "no" -> Right False+ "0" -> Right False+ _ -> Left $ parsingError "Invalid boolean value" (Just "Bool") (Just txt)++-- | Represents a list of text items separated by commas.+newtype CommaSeparatedList = CommaSeparatedList [Text]+ deriving (Show, Eq)++instance OutputParser CommaSeparatedList where+ parse txt = Right $ CommaSeparatedList $ map T.strip $ T.splitOn "," txt++-- | Parse an 'Int' value from trimmed text output.+instance OutputParser Int where+ parse txt =+ case TR.readMaybe (T.unpack (T.strip txt)) of+ Just n -> Right n+ Nothing -> Left $ parsingError "Failed to parse Int from output" (Just "Int") (Just txt)++-- | Parse an 'Integer' value from trimmed text output.+instance OutputParser Integer where+ parse txt =+ case TR.readMaybe (T.unpack (T.strip txt)) of+ Just n -> Right n+ Nothing -> Left $ parsingError "Failed to parse Integer from output" (Just "Integer") (Just txt)++-- | Parse a 'Double' value from trimmed text output.+instance OutputParser Double where+ parse txt =+ case TR.readMaybe (T.unpack (T.strip txt)) of+ Just n -> Right n+ Nothing -> Left $ parsingError "Failed to parse Double from output" (Just "Double") (Just txt)
+ src/Langchain/OutputParser/Structured.hs view
@@ -0,0 +1,279 @@+{-# 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+ , 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.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 Data.Word (Word16, Word32, Word64, Word8)+import GHC.Generics++import Langchain.Error (LangchainError, parsingError)+import Langchain.Model+ ( ChatModel (..)+ , Message (..)+ , extractMessageText+ , systemMessage+ , userMessage+ )++-- | 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+ ]++-- | Generic record schema inspector for deriving JSON Schemas.+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)+ ]++{- | 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. For strict token-level schema enforcement with Ollama, use+@withStructuredOutput@ from @langchain-hs-ollama@.+-}+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 view
@@ -0,0 +1,382 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleContexts #-}++{- |+Module : Langchain.Prelude+Description : Canonical umbrella re-export module for langchain-hs-core+Copyright : (c) 2025-2026 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++Exports all core data types, typeclasses, vector stores, memory stores,+advanced agents, guardrails, observability, structured logging,+circuit breakers, callbacks, output parsers, and pipeline DSLs.+-}+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+ , runChatWithConfig+ , runModel+ , runModelWithConfig+ , runConfiguredModel+ , runRetriever+ , ModelRunnable (..)+ , modelRunnable+ , modelRunnableWithConfig+ , bindConfig+ , TextModelRunnable (..)+ , textModelRunnable+ , textModelRunnableWithConfig+ , bindTextConfig+ , ConfiguredModelRunnable (..)++ -- * Effect-Polymorphic Tools+ , Tool (..)+ , createTool+ , toolToValue+ , ToolBinder (..)+ , DeriveToolSchema (..)+ , deriveToolParametersSchema++ -- * Advanced Agent Patterns+ , PlanStep (..)+ , Plan (..)+ , StepExecutor (..)+ , PlanAndExecuteAgent (..)+ , newPlanAndExecuteAgent+ , newPlanAndExecuteAgentWithTools+ , runPlanAndExecute++ -- * Guardrails & Safety+ , GuardrailResult (..)+ , Guardrail (..)+ , contentSafetyGuardrail+ , topicGuardrail+ , outputLengthGuardrail+ , composeGuardrails+ , withGuardrails++ -- * 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 (..)+ , SomeMemory (..)+ , ConversationBufferMemory (..)+ , newConversationBufferMemory+ , resetConversationBuffer+ , WindowBufferMemory (..)+ , newWindowBufferMemory+ , resetWindowBuffer+ , TokenBufferMemory (..)+ , newTokenBufferMemory+ , resetTokenBuffer+ , countTokens+ , SummaryMemory (..)+ , newSummaryMemory+ , SessionId+ , SessionMemoryStore (..)+ , newSessionMemoryStore+ , newSessionMemoryStoreWithTTL+ , getSessionMemory+ , listSessions+ , hasSession+ , deleteSession+ , clearAllSessions+ , sessionCount+ , cleanupExpiredSessions+ , exportAllSessions+ , EntityMemory (..)+ , newEntityMemory+ , setEntity+ , getEntity+ , getAllEntities+ , deleteEntity+ , clearEntities+ , formatEntities+ , resetEntitiesToInitial+ , CombinedMemory (..)+ , newCombinedMemory+ , initialMessages+ , trimMessages+ , resetToInitial++ -- * Vector Stores & Retrieval+ , VectorStore (..)+ , InMemory (..)+ , emptyInMemoryVectorStore+ , fromDocuments+ , Retriever (..)+ , VectorStoreRetriever (..)+ , retrieveWithCallbacks++ -- * Embeddings (provider-agnostic typeclass)+ , Embeddings (..)++ -- * Document Loaders+ , Document (..)+ , BaseLoader (..)+ , FileLoader (..)+ , DirectoryLoader (..)+ , DirectoryLoaderOptions (..)+ , defaultDirectoryLoaderOptions+ , splitDocuments++ -- * Prompt Templates+ , PromptTemplate (..)+ , PromptTemplateOptions (..)+ , TemplateFormat (..)+ , defaultPromptTemplateOptions+ , fromTemplate+ , fromTemplateWithOptions+ , fromTemplateWithFormat+ , partialPromptTemplate+ , renderPrompt++ -- * 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 (..)+ , CacheableChatModel (..)+ , withCaching+ , RetryPolicy (RetryPolicy)+ , defaultRetryPolicy+ , withRetry+ , RateLimiter (..)+ , newRateLimiter+ , withRateLimit++ -- * Chains+ , RetrievalQA (RetrievalQA)+ , newRetrievalQA+ , runRetrievalQA++ -- * Structured Output & Parsers+ , OutputParser (..)+ , CommaSeparatedList (..)+ , StructuredOutput (..)+ , TypeSchema (..)+ , structuredInvoke+ , structuredInvokeWithRetries++ -- * Agents & Execution+ , ReActAgent (ReActAgent)+ , AgentStep (..)+ , AgentTrace (..)+ , ToolErrorStrategy (..)+ , defaultReActAgent+ , withMemory+ , withoutMemory+ , withSystemPrompt+ , withMaxIterations+ , withModelConfig+ , withCallbackManager+ , withStopCondition+ , withToolErrorStrategy+ , reactStep+ , runReActAgent+ , runReActAgentWithTrace++ -- * Tool Suite & Security+ , ToolTier (..)+ , SecurityPolicy (..)+ , defaultSecurityPolicy+ , readOnlySecurityPolicy+ , workspaceSandboxPolicy+ , withSecurity+ , newFileSystemSuite+ , findFilesTool+ , grepSearchTool+ , readFileTool+ , listDirTool+ , editFileTool+ , writeFileTool+ , newProcessSuite+ , commandRunTool+ , commandStatusTool+ , commandKillTool+ , shellTool++ -- * Hybrid Retrieval & BM25+ , BM25Index (..)+ , newBM25Index+ , newBM25IndexWithParams+ , addDocumentsBM25+ , bm25Search+ , bm25SearchWithScores+ , HybridRetriever (..)+ , newHybridRetriever+ , newHybridRetrieverWithWeights+ , searchHybrid+ , searchHybridWithScores+ , reciprocalRankFusion+ ) where++import Langchain.Agent.PlanAndExecute+import Langchain.Agent.ReAct+import Langchain.Cache.Core+import Langchain.Callback.Manager+import Langchain.Chain.RetrievalQA+import Langchain.DocumentLoader.Core+import Langchain.DocumentLoader.DirectoryLoader+import Langchain.DocumentLoader.FileLoader+import Langchain.Embeddings.Core+import Langchain.Error+import Langchain.Guardrail.Core+import Langchain.Memory+import Langchain.Model+import Langchain.Monad+import Langchain.Observability+import Langchain.OutputParser.Core+import Langchain.OutputParser.Structured+import Langchain.PromptTemplate.Prompt+import Langchain.Resilience.CircuitBreaker+import Langchain.Resilience.Retry+import Langchain.Retriever.BM25+import Langchain.Retriever.Core+import Langchain.Retriever.Hybrid+import Langchain.Runnable hiding (invoke)+import Langchain.Stream+import Langchain.TextSplitter.Character+import Langchain.TextSplitter.Code+import Langchain.TextSplitter.Markdown+import Langchain.TextSplitter.RecursiveCharacter+import Langchain.TextSplitter.Token+import Langchain.Tool+import Langchain.Tool.Binding+import Langchain.Tool.FileSystem+import Langchain.Tool.GenericSchema+import Langchain.Tool.Process+import Langchain.Tool.Security+import Langchain.Tool.Shell (shellTool)+import Langchain.VectorStore.Core+import Langchain.VectorStore.InMemory
+ src/Langchain/PromptTemplate.hs view
@@ -0,0 +1,17 @@+{- |+Module : Langchain.PromptTemplate+Description : Prompt templates and variable interpolation+Copyright : (c) 2025-2026 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++String-based prompt templates with variable interpolation and Runnable integration.+-}+module Langchain.PromptTemplate+ ( module Langchain.PromptTemplate.Prompt+ , module Langchain.PromptTemplate.String+ ) where++import Langchain.PromptTemplate.Prompt+import Langchain.PromptTemplate.String
+ src/Langchain/PromptTemplate/Prompt.hs view
@@ -0,0 +1,104 @@+{-# 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.Error (LangchainError)+import Langchain.PromptTemplate.String+ ( TemplateFormat (..)+ , extractTemplateVariables+ , extractTemplateVariablesWithFormat+ , renderFStringTemplate+ , renderTemplateWithFormat+ )+import Langchain.Runnable (Runnable (..))++-- | 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++-- | t'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 view
@@ -0,0 +1,110 @@+{-# 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.Error (LangchainError, validationError)++-- | Template format specification.+data TemplateFormat+ = -- | Python-style f-string formatting using @{variable}@ placeholders.+ FString+ deriving (Show, Eq, Generic, ToJSON, FromJSON)++-- | Render a template string using the specified 'TemplateFormat'.+renderTemplateWithFormat ::+ TemplateFormat -> Map.Map Text Text -> Text -> Either LangchainError Text+renderTemplateWithFormat FString = renderFStringTemplate++-- | Render a Python-style f-string template using provided variable mappings.+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 ()++-- | Extract variable names present in an f-string template.+extractTemplateVariables :: Text -> [Text]+extractTemplateVariables = extractTemplateVariablesWithFormat FString++-- | Extract variable names present in a template of the given 'TemplateFormat'.+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/Resilience.hs view
@@ -0,0 +1,18 @@+{- |+Module : Langchain.Resilience+Description : Resilience patterns for LLM invocations and tool calls+Copyright : (c) 2025-2026 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++Resilience combinators including exponential backoff retries, token-bucket rate limiting,+and circuit breakers for fault-tolerant LLM pipelines.+-}+module Langchain.Resilience+ ( module Langchain.Resilience.CircuitBreaker+ , module Langchain.Resilience.Retry+ ) where++import Langchain.Resilience.CircuitBreaker+import Langchain.Resilience.Retry
+ src/Langchain/Resilience/CircuitBreaker.hs view
@@ -0,0 +1,119 @@+{-# 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.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 view
@@ -0,0 +1,114 @@+{-# 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.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.hs view
@@ -0,0 +1,20 @@+{- |+Module : Langchain.Retriever+Description : Document retrieval mechanisms+Copyright : (c) 2025-2026 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++Retrieval mechanisms including vector store retrieval, Okapi BM25 sparse retrieval,+and Reciprocal Rank Fusion hybrid retrieval.+-}+module Langchain.Retriever+ ( module Langchain.Retriever.Core+ , module Langchain.Retriever.BM25+ , module Langchain.Retriever.Hybrid+ ) where++import Langchain.Retriever.BM25+import Langchain.Retriever.Core+import Langchain.Retriever.Hybrid
+ src/Langchain/Retriever/BM25.hs view
@@ -0,0 +1,151 @@+{-# 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.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++-- | t'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 view
@@ -0,0 +1,66 @@+{-# 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.DocumentLoader.Core (Document (..))+import Langchain.Error (LangchainError)+import Langchain.Runnable (RunnableTree, runLambda)+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 view
@@ -0,0 +1,155 @@+{-# 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.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++-- | t'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/Runnable.hs view
@@ -0,0 +1,273 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++{- |+Module : Langchain.Runnable+Description : Pure pipeline GADT (RunnableTree) and algebraic composition+Copyright : (c) 2025-2026 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++Pure, AST-based pipeline representation ('RunnableTree') where building pipelines performs+NO side effects. Execution is strictly deferred to 'interpret'.+Provides full configuration binding for 'ChatModel' instances.+-}+module Langchain.Runnable+ ( Runnable (..)+ , RunnableTree (..)+ , (|>>)+ , (&>&)+ , interpret+ , runLambda+ , runPrim+ , runPure+ , runPassthrough+ , runIdent+ , runBranch+ , runFallback+ , runChat+ , runChatWithConfig+ , runModel+ , runModelWithConfig+ , runConfiguredModel+ , ModelRunnable (..)+ , modelRunnable+ , modelRunnableWithConfig+ , bindConfig+ , TextModelRunnable (..)+ , textModelRunnable+ , textModelRunnableWithConfig+ , bindTextConfig+ , ConfiguredModelRunnable (..)+ ) where++import Control.Concurrent.Async (concurrently)+import Control.Monad.Except (ExceptT, MonadError, catchError, runExceptT, throwError)+import Control.Monad.IO.Class (MonadIO, liftIO)+import Data.Aeson (Value)+import Data.Kind (Type)+import Data.Text (Text)++import Langchain.Error (LangchainError)+import Langchain.Model (ChatModel, ModelConfig, extractMessageText, userMessage)+import qualified Langchain.Model as M (invoke)+import Langchain.Model.Types (Message)+import Langchain.Tool (Tool (..))++-- | Fundamental Runnable interface for components wrapped in 'Prim'.+class Runnable r m where+ type RunnableInput r :: Type+ type RunnableOutput r :: Type+ invoke :: r -> RunnableInput r -> m (Either LangchainError (RunnableOutput r))++-- | Any Tool can be executed as a primitive Runnable taking JSON 'Value' to 'Text'.+instance Runnable (Tool m) m where+ type RunnableInput (Tool m) = Value+ type RunnableOutput (Tool m) = Text+ invoke = toolExecute++-- ---------------------------------------------------------------------------+-- Model Runnables with Configuration Support+-- ---------------------------------------------------------------------------++-- | Wrapper to treat any 'ChatModel' as a Runnable over '[Message]' -> 'Message' with optional bound configuration.+data ModelRunnable c = ModelRunnable+ { modelRunnableModel :: !c+ , modelRunnableConfig :: !(Maybe (ModelConfig c))+ }++deriving instance (Show c, Show (ModelConfig c)) => Show (ModelRunnable c)+deriving instance (Eq c, Eq (ModelConfig c)) => Eq (ModelRunnable c)++-- | Construct a ModelRunnable without pre-set configuration.+modelRunnable :: c -> ModelRunnable c+modelRunnable c = ModelRunnable c Nothing++-- | Construct a ModelRunnable with pre-bound model configuration.+modelRunnableWithConfig :: c -> ModelConfig c -> ModelRunnable c+modelRunnableWithConfig c cfg = ModelRunnable c (Just cfg)++-- | Bind or replace the configuration on a ModelRunnable (equivalent to LangChain's model.bind).+bindConfig :: ModelConfig c -> ModelRunnable c -> ModelRunnable c+bindConfig cfg (ModelRunnable c _) = ModelRunnable c (Just cfg)++instance (ChatModel c, MonadIO m) => Runnable (ModelRunnable c) m where+ type RunnableInput (ModelRunnable c) = [Message]+ type RunnableOutput (ModelRunnable c) = Message+ invoke (ModelRunnable c mCfg) msgs = runExceptT (M.invoke c msgs mCfg)++-- | Wrapper to treat any 'ChatModel' as a simple 'Text' -> 'Text' Runnable with optional bound configuration.+data TextModelRunnable c = TextModelRunnable+ { textModelRunnableModel :: !c+ , textModelRunnableConfig :: !(Maybe (ModelConfig c))+ }++deriving instance (Show c, Show (ModelConfig c)) => Show (TextModelRunnable c)+deriving instance (Eq c, Eq (ModelConfig c)) => Eq (TextModelRunnable c)++-- | Construct a TextModelRunnable without pre-set configuration.+textModelRunnable :: c -> TextModelRunnable c+textModelRunnable c = TextModelRunnable c Nothing++-- | Construct a TextModelRunnable with pre-bound model configuration.+textModelRunnableWithConfig :: c -> ModelConfig c -> TextModelRunnable c+textModelRunnableWithConfig c cfg = TextModelRunnable c (Just cfg)++-- | Bind or replace the configuration on a TextModelRunnable.+bindTextConfig :: ModelConfig c -> TextModelRunnable c -> TextModelRunnable c+bindTextConfig cfg (TextModelRunnable c _) = TextModelRunnable c (Just cfg)++instance (ChatModel c, MonadIO m) => Runnable (TextModelRunnable c) m where+ type RunnableInput (TextModelRunnable c) = Text+ type RunnableOutput (TextModelRunnable c) = Text+ invoke (TextModelRunnable c mCfg) prompt = do+ res <- runExceptT $ M.invoke c [userMessage prompt] mCfg+ pure (extractMessageText <$> res)++-- | Dynamic model runnable allowing model configuration to be passed per-request.+newtype ConfiguredModelRunnable c = ConfiguredModelRunnable c+ deriving (Eq, Show)++instance (ChatModel c, MonadIO m) => Runnable (ConfiguredModelRunnable c) m where+ type RunnableInput (ConfiguredModelRunnable c) = ([Message], Maybe (ModelConfig c))+ type RunnableOutput (ConfiguredModelRunnable c) = Message+ invoke (ConfiguredModelRunnable c) (msgs, mCfg) = runExceptT (M.invoke c msgs mCfg)++-- ---------------------------------------------------------------------------+-- RunnableTree Pipeline AST+-- ---------------------------------------------------------------------------++{- | Pure GADT representing a composable pipeline AST.+@i@ = input type, @o@ = output type, @m@ = monad context.+-}+data RunnableTree m i o where+ -- | Identity: passes input through unchanged+ Id :: RunnableTree m a a+ -- | Lift a component implementing Runnable into the tree+ Prim ::+ (Runnable r m, RunnableInput r ~ i, RunnableOutput r ~ o) =>+ r ->+ RunnableTree m i o+ -- | Lift a monadic function into the tree+ Lambda :: (i -> m (Either LangchainError o)) -> RunnableTree m i o+ -- | Sequential composition AST node+ Seq :: RunnableTree m i mid -> RunnableTree m mid o -> RunnableTree m i o+ -- | Parallel composition AST node+ Par ::+ RunnableTree (ExceptT LangchainError IO) i o1 ->+ RunnableTree (ExceptT LangchainError IO) i o2 ->+ RunnableTree (ExceptT LangchainError IO) i (o1, o2)+ -- | Conditional branching AST node+ Branch ::+ (i -> m Bool) ->+ -- | True branch+ RunnableTree m i o ->+ -- | False branch+ RunnableTree m i o ->+ RunnableTree m i o+ -- | Fallback node: if primary fails, executes fallback+ Fallback :: RunnableTree m i o -> RunnableTree m i o -> RunnableTree m i o++-- | Sequential composition operator — PURE AST builder.+(|>>) :: RunnableTree m a b -> RunnableTree m b c -> RunnableTree m a c+(|>>) = Seq++infixl 1 |>>++-- | Parallel composition operator — PURE AST builder.+(&>&) ::+ RunnableTree (ExceptT LangchainError IO) a b ->+ RunnableTree (ExceptT LangchainError IO) a c ->+ RunnableTree (ExceptT LangchainError IO) a (b, c)+(&>&) = Par++infixl 2 &>&++-- | Helper to create a lambda runnable node.+runLambda :: (i -> m (Either LangchainError o)) -> RunnableTree m i o+runLambda = Lambda++-- | Helper to create a primitive runnable node.+runPrim ::+ (Runnable r m, RunnableInput r ~ i, RunnableOutput r ~ o) =>+ r ->+ RunnableTree m i o+runPrim = Prim++-- | Helper to convert Either to MonadError+liftEither :: MonadError LangchainError m => Either LangchainError a -> m a+liftEither (Left err) = throwError err+liftEither (Right x) = pure x++-- | Sole execution engine for 'RunnableTree' AST pipelines.+interpret ::+ (MonadIO m, MonadError LangchainError m) =>+ RunnableTree m i o ->+ i ->+ m o+interpret Id input = pure input+interpret (Prim r) input = invoke r input >>= liftEither+interpret (Lambda f) input = f input >>= liftEither+interpret (Seq t1 t2) input = interpret t1 input >>= interpret t2+interpret (Par t1 t2) input = do+ (r1, r2) <-+ liftIO $+ concurrently+ (runExceptT $ interpret t1 input)+ (runExceptT $ interpret t2 input)+ o1 <- liftEither r1+ o2 <- liftEither r2+ pure (o1, o2)+interpret (Branch cond tTrue tFalse) input = do+ b <- cond input+ if b then interpret tTrue input else interpret tFalse input+interpret (Fallback t1 t2) input =+ catchError (interpret t1 input) (\_ -> interpret t2 input)++-- | Lift a pure function into a 'RunnableTree' node.+runPure :: Monad m => (i -> o) -> RunnableTree m i o+runPure f = runLambda (pure . Right . f)++-- | Identity node in a 'RunnableTree' (passes input through unchanged, like LangChain's @RunnablePassthrough@).+runPassthrough :: RunnableTree m a a+runPassthrough = Id++-- | Alias for 'runPassthrough'.+runIdent :: RunnableTree m a a+runIdent = Id++-- | Construct a conditional branch AST node.+runBranch :: (i -> m Bool) -> RunnableTree m i o -> RunnableTree m i o -> RunnableTree m i o+runBranch = Branch++-- | Construct a self-healing fallback AST node (tries first, catches errors and runs second).+runFallback :: RunnableTree m i o -> RunnableTree m i o -> RunnableTree m i o+runFallback = Fallback++-- | Lift any 'ChatModel' into a simple 'Text' -> 'Text' pipeline step (without configuration).+runChat :: (ChatModel c, MonadIO m) => c -> RunnableTree m Text Text+runChat c = runPrim (TextModelRunnable c Nothing)++-- | Lift any 'ChatModel' into a simple 'Text' -> 'Text' pipeline step with bound model configuration.+runChatWithConfig :: (ChatModel c, MonadIO m) => c -> ModelConfig c -> RunnableTree m Text Text+runChatWithConfig c cfg = runPrim (TextModelRunnable c (Just cfg))++-- | Lift any 'ChatModel' into a structured '[Message]' -> 'Message' pipeline step (without configuration).+runModel :: (ChatModel c, MonadIO m) => c -> RunnableTree m [Message] Message+runModel c = runPrim (ModelRunnable c Nothing)++-- | Lift any 'ChatModel' into a structured '[Message]' -> 'Message' pipeline step with bound model configuration.+runModelWithConfig ::+ (ChatModel c, MonadIO m) => c -> ModelConfig c -> RunnableTree m [Message] Message+runModelWithConfig c cfg = runPrim (ModelRunnable c (Just cfg))++-- | Lift any 'ChatModel' into a pipeline step taking dynamic messages and configuration per request.+runConfiguredModel ::+ (ChatModel c, MonadIO m) => c -> RunnableTree m ([Message], Maybe (ModelConfig c)) Message+runConfiguredModel c = runPrim (ConfiguredModelRunnable c)
+ src/Langchain/Stream.hs view
@@ -0,0 +1,169 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# OPTIONS_GHC -Wno-partial-fields #-}++{- |+Module : Langchain.Stream+Description : Standardized StreamEvent protocol and utilities+Copyright : (c) 2025-2026 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++Structured streaming event protocol for tracking LLM, tool, chain, and graph lifecycle events.+-}+module Langchain.Stream+ ( TokenUsage (..)+ , StreamEvent (..)+ , StreamM+ , EventStream+ , ChatStream+ , StreamCallback+ , StreamSource+ , callbackSource+ , collectEvents+ , printEvents+ ) where++import Control.Concurrent.Async (async, cancel)+import Control.Concurrent.STM+ ( atomically+ , newEmptyTMVarIO+ , newTBQueueIO+ , orElse+ , putTMVar+ , readTBQueue+ , readTMVar+ , writeTBQueue+ )+import Control.Exception (finally)+import Control.Monad.Except (ExceptT, runExceptT)+import Control.Monad.IO.Class (liftIO)+import Control.Monad.Trans.Resource (ResourceT, runResourceT)+import Data.Aeson (FromJSON, ToJSON, Value)+import Data.Conduit (ConduitT, bracketP, runConduit, yield, (.|))+import qualified Data.Conduit.List as CL+import Data.Text (Text)+import GHC.Generics (Generic)+import Langchain.Error (LangchainError)+import Langchain.Model.Types (Message, ToolCall)++-- | Token usage accounting for LLM execution.+data TokenUsage = TokenUsage+ { promptTokens :: Int+ , completionTokens :: Int+ , totalTokens :: Int+ }+ deriving (Eq, Show, Generic, ToJSON, FromJSON)++{- | All streaming events emitted across the framework execution lifecycle.+Every event carries a 'runId' for correlation.+-}+data StreamEvent+ = -- | LLM lifecycle start+ LLMStart+ { runId :: Text+ , modelName :: Text+ , inputMessages :: [Message]+ }+ | -- | LLM incremental streaming chunk+ LLMChunk+ { runId :: Text+ , chunkText :: Text+ , toolCallDelta :: Maybe ToolCall+ }+ | -- | LLM lifecycle completion+ LLMEnd+ { runId :: Text+ , finalMessage :: Message+ , tokenUsage :: Maybe TokenUsage+ }+ | -- | Tool execution start+ ToolStart+ { runId :: Text+ , toolName :: Text+ , toolInput :: Value+ }+ | -- | Tool execution completion+ ToolEnd+ { runId :: Text+ , toolName :: Text+ , toolOutput :: Value+ }+ | -- | Tool execution failure+ ToolErrorEvent+ { runId :: Text+ , toolName :: Text+ , toolErrorPayload :: LangchainError+ }+ | -- | Chain execution start+ ChainStart+ { runId :: Text+ , chainName :: Text+ , chainInput :: Value+ }+ | -- | Chain execution completion+ ChainEnd+ { runId :: Text+ , chainName :: Text+ , chainOutput :: Value+ }+ | -- | Graph node execution start+ NodeStart+ { runId :: Text+ , nodeId :: Text+ , nodeState :: Value+ }+ | -- | Graph node execution completion+ NodeEnd+ { runId :: Text+ , nodeId :: Text+ , nodeState :: Value+ }+ deriving (Eq, Show, Generic, ToJSON, FromJSON)++-- | Canonical event stream type using Conduit.+type EventStream m = ConduitT () StreamEvent m ()++-- | Effects used by resource-safe chat model streams.+type StreamM = ExceptT LangchainError (ResourceT IO)++-- | A resource-safe stream of chat model events.+type ChatStream = EventStream StreamM++-- | A callback function that produces values of type @a@.+type StreamCallback a = (a -> IO ()) -> IO ()++-- | A Conduit source that produces values of type @a@ in the 'StreamM' monad.+type StreamSource a = ConduitT () a StreamM ()++-- | Convert a callback-based streaming function into a Conduit source.+callbackSource :: StreamCallback a -> StreamSource a+callbackSource produce = bracketP start (cancel . third) consume+ where+ start = do+ queue <- newTBQueueIO 64+ finished <- newEmptyTMVarIO+ worker <-+ async $ produce (atomically . writeTBQueue queue) `finally` atomically (putTMVar finished ())+ pure (queue, finished, worker)++ consume (queue, finished, _worker) = loop+ where+ loop = do+ let waitForFinished = Nothing <$ readTMVar finished+ readEvent = Just <$> readTBQueue queue+ next <- liftIO . atomically $ readEvent `orElse` waitForFinished+ case next of+ Just item -> yield item >> loop+ Nothing -> pure ()++ third (_, _, worker) = worker++-- | Collect all emitted events from a stream into a list.+collectEvents :: Monad m => EventStream m -> m [StreamEvent]+collectEvents streamSrc = runConduit (streamSrc .| CL.consume)++-- | Debug helper: print all stream events to stdout.+printEvents :: EventStream (ExceptT LangchainError (ResourceT IO)) -> IO (Either LangchainError ())+printEvents streamSrc = runResourceT $ runExceptT $ runConduit (streamSrc .| CL.mapM_ (liftIO . print))
+ src/Langchain/TextSplitter/Character.hs view
@@ -0,0 +1,111 @@+{-# 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 view
@@ -0,0 +1,167 @@+{-# 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 view
@@ -0,0 +1,137 @@+{-# 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 view
@@ -0,0 +1,96 @@+{-# 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 view
@@ -0,0 +1,90 @@+{-# 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.hs view
@@ -0,0 +1,65 @@+{-# LANGUAGE OverloadedStrings #-}++{- |+Module : Langchain.Tool+Description : Effect-polymorphic Tool specification and parameter validation+Copyright : (c) 2025-2026 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++Provides effect-polymorphic 'Tool m' representation, schema generation, and argument parsing.+-}+module Langchain.Tool+ ( Tool (..)+ , createTool+ , toolToValue+ ) where++import Data.Aeson+import Data.Text (Text)++import Langchain.Error (LangchainError)++-- | Effect-polymorphic Tool abstraction+data Tool m = Tool+ { toolName :: Text+ -- ^ Unique identifier for the tool+ , toolDescription :: Text+ -- ^ Description explaining when and how to use the tool+ , toolSchema :: Value+ -- ^ JSON Schema describing expected parameters+ , toolExecute :: Value -> m (Either LangchainError Text)+ -- ^ Monadic execution function accepting JSON arguments and returning text output+ }++instance Show (Tool m) where+ show t = "Tool { toolName = " ++ show (toolName t) ++ " }"++-- | Helper to create a Tool from a name, description, schema, and execution function+createTool ::+ Text ->+ Text ->+ Value ->+ (Value -> m (Either LangchainError Text)) ->+ Tool m+createTool name desc schema execFn =+ Tool+ { toolName = name+ , toolDescription = desc+ , toolSchema = schema+ , toolExecute = execFn+ }++-- | Convert Tool definition to OpenAI/Ollama compatible function definition JSON object+toolToValue :: Tool m -> Value+toolToValue Tool {..} =+ object+ [ "type" .= ("function" :: Text)+ , "function"+ .= object+ [ "name" .= toolName+ , "description" .= toolDescription+ , "parameters" .= toolSchema+ ]+ ]
+ src/Langchain/Tool/Binding.hs view
@@ -0,0 +1,49 @@+{-# 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.Model (ChatModel (..))+import Langchain.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 an OllamaConfig with ollamaTools 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/Checkpoint.hs view
@@ -0,0 +1,308 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++{- |+Module : Langchain.Tool.Checkpoint+Description : Undo checkpoints, file snapshots, and audit log tracking for tool calls+Copyright : (c) 2025-2026 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++Provides transactional undo snapshots before file modifications, audit logging of tool calls,+and read-before-edit verification.+-}+module Langchain.Tool.Checkpoint+ ( -- * Types+ FileCheckpoint (..)+ , ToolAuditEntry (..)+ , CheckpointRegistry (..)++ -- * Registry Operations+ , newCheckpointRegistry+ , recordFileRead+ , hasReadFile+ , createFileCheckpoint+ , rollbackCheckpoint+ , recordAuditEntry+ , getAuditHistory+ , listCheckpoints++ -- * Tools+ , undoFileEditTool+ , listCheckpointsTool+ ) where++import Control.Concurrent.STM+ ( TVar+ , atomically+ , modifyTVar'+ , newTVarIO+ , readTVar+ , readTVarIO+ , writeTVar+ )+import Control.Exception (try)+import Control.Monad.IO.Class (MonadIO, liftIO)+import Data.Aeson (FromJSON, ToJSON, Value (..), object, (.:?), (.=))+import Data.Aeson.Types (parseEither)+import qualified Data.Set as Set+import Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.IO as TIO+import Data.Time.Clock (UTCTime, getCurrentTime)+import GHC.Generics (Generic)+import System.Directory (canonicalizePath, doesFileExist)+import System.FilePath (normalise)++import Langchain.Error (LangchainError, toolError)+import Langchain.Tool (Tool (..), createTool)+import Langchain.Tool.Security (ToolTier (..))++-- | Represents an automatic undo snapshot taken before a file was modified.+data FileCheckpoint = FileCheckpoint+ { cpId :: !Text+ -- ^ Unique checkpoint identifier (e.g. @"cp-1"@)+ , cpTimestamp :: !UTCTime+ -- ^ Time when the snapshot was recorded+ , cpFilePath :: !FilePath+ -- ^ Canonical path of the modified file+ , cpPreviousContent :: !Text+ -- ^ File content before the modification+ , cpDescription :: !Text+ -- ^ Operation description (e.g. @"edit_file: replace 'foo' with 'bar'"@)+ }+ deriving (Eq, Show, Generic, ToJSON, FromJSON)++-- | Audit trail record for a single tool call.+data ToolAuditEntry = ToolAuditEntry+ { auditStep :: !Int+ -- ^ Step index of the tool call+ , auditTimestamp :: !UTCTime+ -- ^ Timestamp of invocation+ , auditToolName :: !Text+ -- ^ Name of tool executed+ , auditArguments :: !Value+ -- ^ Arguments supplied to the tool+ , auditResult :: !(Either Text Text)+ -- ^ Execution result (Left error or Right output)+ , auditTier :: !ToolTier+ -- ^ Permission tier of the tool+ }+ deriving (Eq, Show, Generic, ToJSON, FromJSON)++-- | Stateful in-memory STM registry tracking file checkpoints, audit history, and inspected files.+data CheckpointRegistry = CheckpointRegistry+ { regFileCheckpoints :: !(TVar [FileCheckpoint])+ , regAuditLog :: !(TVar [ToolAuditEntry])+ , regReadFiles :: !(TVar (Set.Set FilePath))+ , regNextId :: !(TVar Int)+ }++-- | Initialize a new in-memory checkpoint and audit registry.+newCheckpointRegistry :: IO CheckpointRegistry+newCheckpointRegistry = do+ cpsVar <- newTVarIO []+ audVar <- newTVarIO []+ readVar <- newTVarIO Set.empty+ idVar <- newTVarIO 1+ pure $ CheckpointRegistry cpsVar audVar readVar idVar++-- | Normalize a path for read-tracking lookups.+normalizeForTracking :: FilePath -> IO FilePath+normalizeForTracking fp = do+ exists <- doesFileExist fp+ if exists+ then canonicalizePath fp+ else pure (normalise fp)++-- | Record that a file was read by the agent (for read-before-edit verification).+recordFileRead :: CheckpointRegistry -> FilePath -> IO ()+recordFileRead CheckpointRegistry {..} rawPath = do+ normPath <- normalizeForTracking rawPath+ atomically $ modifyTVar' regReadFiles (Set.insert normPath)++-- | Check whether a file was previously inspected via read_file.+hasReadFile :: CheckpointRegistry -> FilePath -> IO Bool+hasReadFile CheckpointRegistry {..} rawPath = do+ normPath <- normalizeForTracking rawPath+ readsSet <- readTVarIO regReadFiles+ pure $ Set.member normPath readsSet++-- | Capture an undo snapshot before modifying or writing a file.+createFileCheckpoint ::+ CheckpointRegistry ->+ FilePath ->+ Text ->+ Text ->+ IO Text+createFileCheckpoint CheckpointRegistry {..} rawPath prevContent desc = do+ normPath <- normalizeForTracking rawPath+ now <- getCurrentTime+ atomically $ do+ currId <- readTVar regNextId+ writeTVar regNextId (currId + 1)+ let cpIdentifier = "cp-" <> T.pack (show currId)+ checkpoint =+ FileCheckpoint+ { cpId = cpIdentifier+ , cpTimestamp = now+ , cpFilePath = normPath+ , cpPreviousContent = prevContent+ , cpDescription = desc+ }+ modifyTVar' regFileCheckpoints (checkpoint :)+ pure cpIdentifier++{- | Roll back a file to its previous checkpoint content.++If no 'cpId' is specified, reverts the most recent checkpoint.+-}+rollbackCheckpoint ::+ CheckpointRegistry ->+ Maybe Text ->+ IO (Either LangchainError Text)+rollbackCheckpoint CheckpointRegistry {..} mbTargetId = do+ mCp <- atomically $ do+ cps <- readTVar regFileCheckpoints+ case mbTargetId of+ Nothing -> case cps of+ [] -> pure Nothing+ (latest : rest) -> do+ writeTVar regFileCheckpoints rest+ pure (Just latest)+ Just targetId ->+ case break (\c -> cpId c == targetId) cps of+ (_, []) -> pure Nothing+ (before, target : after) -> do+ writeTVar regFileCheckpoints (before ++ after)+ pure (Just target)++ case mCp of+ Nothing ->+ pure $+ Left $+ toolError+ ( case mbTargetId of+ Nothing -> "No checkpoints available to roll back."+ Just tid -> "Checkpoint '" <> tid <> "' not found in registry."+ )+ (Just "undo_file_edit")+ Nothing+ Just FileCheckpoint {..} -> do+ eWrite <- try (TIO.writeFile cpFilePath cpPreviousContent)+ case eWrite of+ Left err ->+ pure $+ Left $+ toolError+ ("Failed to restore file during rollback: " <> T.pack (show (err :: IOError)))+ (Just "undo_file_edit")+ Nothing+ Right () ->+ pure $+ Right $+ "Successfully restored '"+ <> T.pack cpFilePath+ <> "' to checkpoint ["+ <> cpId+ <> "] ("+ <> cpDescription+ <> ")."++-- | Record a completed tool call into the audit log.+recordAuditEntry ::+ CheckpointRegistry ->+ Text ->+ Value ->+ Either Text Text ->+ ToolTier ->+ IO ()+recordAuditEntry CheckpointRegistry {..} toolNm args res tier = do+ now <- getCurrentTime+ atomically $ do+ step <- readTVar regNextId+ let entry =+ ToolAuditEntry+ { auditStep = step+ , auditTimestamp = now+ , auditToolName = toolNm+ , auditArguments = args+ , auditResult = res+ , auditTier = tier+ }+ modifyTVar' regAuditLog (entry :)++-- | Retrieve all audit log entries in chronological order.+getAuditHistory :: CheckpointRegistry -> IO [ToolAuditEntry]+getAuditHistory CheckpointRegistry {..} = reverse <$> readTVarIO regAuditLog++-- | Retrieve all available file checkpoints.+listCheckpoints :: CheckpointRegistry -> IO [FileCheckpoint]+listCheckpoints CheckpointRegistry {..} = readTVarIO regFileCheckpoints++-- | Tool that rolls back a file modification to a previous checkpoint.+undoFileEditTool :: MonadIO m => CheckpointRegistry -> Tool m+undoFileEditTool registry =+ createTool+ "undo_file_edit"+ "Revert a file to its previous state before an edit or write operation was performed. Reverts the most recent edit by default, or a specific checkpoint_id."+ ( object+ [ "type" .= ("object" :: Text)+ , "properties"+ .= object+ [ "checkpoint_id"+ .= object+ [ "type" .= ("string" :: Text)+ , "description"+ .= ( "Optional checkpoint ID (e.g. 'cp-1') to revert to. If omitted, rolls back the most recent edit." ::+ Text+ )+ ]+ ]+ ]+ )+ ( \case+ Object o -> do+ let mbId = case parseEither (.:? "checkpoint_id") o of+ Right m -> m+ _ -> Nothing+ liftIO $ rollbackCheckpoint registry mbId+ _ -> liftIO $ rollbackCheckpoint registry Nothing+ )++-- | Tool that lists all active undo checkpoints.+listCheckpointsTool :: MonadIO m => CheckpointRegistry -> Tool m+listCheckpointsTool registry =+ createTool+ "list_checkpoints"+ "List all available file modification checkpoints that can be reverted."+ ( object+ [ "type" .= ("object" :: Text)+ , "properties" .= object []+ ]+ )+ ( \_ -> liftIO $ do+ cps <- listCheckpoints registry+ if null cps+ then pure $ Right "No active file checkpoints."+ else+ let linesFormatted =+ map+ ( \FileCheckpoint {..} ->+ "["+ <> cpId+ <> "] "+ <> T.pack cpFilePath+ <> " — "+ <> cpDescription+ <> " ("+ <> T.pack (show cpTimestamp)+ <> ")"+ )+ cps+ in pure $ Right $ T.unlines ("Active Checkpoints:" : linesFormatted)+ )
+ src/Langchain/Tool/Core.hs view
@@ -0,0 +1,17 @@+{-# 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.Tool+ ) where++import Langchain.Tool
+ src/Langchain/Tool/FileSystem.hs view
@@ -0,0 +1,914 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}++{- |+Module : Langchain.Tool.FileSystem+Description : Observational and state-modifying filesystem tools+Copyright : (c) 2025-2026 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++Provides comprehensive, agent-optimized filesystem tools:+* 'findFilesTool' ('find_files'): Concurrent directory search respecting .gitignore, depth, and patterns.+* 'grepSearchTool' ('grep_search'): High-performance ripgrep-style content search with line numbers and context-window protection.+* 'readFileTool' ('read_file'): Paginated reading with line numbers, context-window awareness, and read-before-edit tracking.+* 'editFileTool' ('edit_file'): Precise string replacement with read-before-edit verification, uniqueness check, and automatic undo snapshots.+* 'writeFileTool' ('write_file'): Safe file creation with overwrite protection and automatic checkpointing.+* 'listDirTool' ('list_directory'): Directory inspection with file sizes and type indicators.+* 'newFileSystemSuite': Factory creating the complete suite sharing a checkpoint registry and security policy.+-}+module Langchain.Tool.FileSystem+ ( -- * Complete Suite Factory+ newFileSystemSuite++ -- * Observational Tools+ , findFilesTool+ , grepSearchTool+ , readFileTool+ , listDirTool++ -- * Modification Tools+ , editFileTool+ , writeFileTool++ -- * Helper / Pure Matching+ , matchGlobPattern+ , isDefaultIgnored+ ) where++import Control.Concurrent.Async (mapConcurrently)+import Control.Exception (SomeException, try)+import Control.Monad.IO.Class (MonadIO, liftIO)+import Data.Aeson (FromJSON, Key, Object, Value (..), object, (.:?), (.=))+import Data.Aeson.Types (parseEither)+import Data.Char (toLower)+import Data.List (isInfixOf, isPrefixOf, isSuffixOf, sort)+import Data.Maybe (fromMaybe)+import Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.IO as TIO++import System.Directory+ ( createDirectoryIfMissing+ , doesDirectoryExist+ , doesFileExist+ , getFileSize+ , listDirectory+ )+import System.FilePath+ ( takeDirectory+ , takeFileName+ , (</>)+ )++import Langchain.Error (toolError)+import Langchain.Tool (Tool (..), createTool)+import Langchain.Tool.Checkpoint+ ( CheckpointRegistry+ , createFileCheckpoint+ , hasReadFile+ , listCheckpointsTool+ , newCheckpointRegistry+ , recordFileRead+ , undoFileEditTool+ )+import Langchain.Tool.Security+ ( SecurityPolicy+ , ToolTier (..)+ , validateSandboxPath+ , withSecurity+ )++-- | Extract an optional JSON field with a fallback default.+optField :: FromJSON a => Key -> a -> Object -> a+optField k def o = case parseEither (.:? k) o of+ Right (Just v) -> v+ _ -> def++-- | Extract an optional JSON field as Maybe.+mbField :: FromJSON a => Key -> Object -> Maybe a+mbField k o = case parseEither (.:? k) o of+ Right mv -> mv+ _ -> Nothing++-- ============================================================================+-- Pure Pattern & Gitignore Matching+-- ============================================================================++-- | Directories and file suffixes automatically ignored in find and search.+defaultIgnoredNames :: [String]+defaultIgnoredNames =+ [ ".git"+ , ".stack-work"+ , "dist-newstyle"+ , "node_modules"+ , ".cabal-sandbox"+ , ".idea"+ , ".vscode"+ , ".DS_Store"+ ]++-- | Check if a directory or filename is in the default ignore set.+isDefaultIgnored :: FilePath -> Bool+isDefaultIgnored name =+ let fn = takeFileName name+ in fn `elem` defaultIgnoredNames || ".git" `isPrefixOf` fn++-- | Simple glob matcher supporting '*' wildcard and case-insensitivity.+matchGlobPattern :: String -> String -> Bool+matchGlobPattern pat str+ | null pat = True+ | pat == "*" = True+ | "*" `isPrefixOf` pat && "*" `isSuffixOf` pat =+ let sub = drop 1 (take (length pat - 1) pat)+ in map toLower sub `isInfixOf` map toLower str+ | "*" `isPrefixOf` pat =+ let sfx = drop 1 pat+ in map toLower sfx `isSuffixOf` map toLower str+ | "*" `isSuffixOf` pat =+ let pfx = take (length pat - 1) pat+ in map toLower pfx `isPrefixOf` map toLower str+ | "*" `isInfixOf` pat =+ let (before, afterStar) = span (/= '*') pat+ after = drop 1 afterStar+ in (map toLower before `isPrefixOf` map toLower str)+ && (map toLower after `isSuffixOf` map toLower str)+ | otherwise =+ map toLower pat `isInfixOf` map toLower str++-- ============================================================================+-- 1. Find Files Tool+-- ============================================================================++-- | Create a concurrent file/directory search tool.+findFilesTool ::+ MonadIO m =>+ SecurityPolicy ->+ Tool m+findFilesTool policy =+ withSecurity TierReadOnly policy $+ createTool+ "find_files"+ "Search for files and directories matching name patterns with recursion depth control, gitignore filtering, and concurrent scanning."+ ( object+ [ "type" .= ("object" :: Text)+ , "properties"+ .= object+ [ "path"+ .= object+ [ "type" .= ("string" :: Text)+ , "description" .= ("Root directory to start searching from (default: '.')" :: Text)+ ]+ , "pattern"+ .= object+ [ "type" .= ("string" :: Text)+ , "description" .= ("Filename or glob pattern to match (e.g. '*.hs', 'Spec', 'test*')" :: Text)+ ]+ , "max_depth"+ .= object+ [ "type" .= ("integer" :: Text)+ , "description" .= ("Maximum recursion directory depth (default: unlimited)" :: Text)+ ]+ , "file_type"+ .= object+ [ "type" .= ("string" :: Text)+ , "description" .= ("Filter results by 'file', 'directory', or 'any' (default: 'any')" :: Text)+ ]+ , "max_results"+ .= object+ [ "type" .= ("integer" :: Text)+ , "description" .= ("Maximum number of matching paths to return (default: 100)" :: Text)+ ]+ ]+ ]+ )+ ( \case+ Object o -> do+ let rawPath = optField "path" "." o :: String+ pat = optField "pattern" "" o :: String+ mbMaxDepth = mbField "max_depth" o :: Maybe Int+ fType = optField "file_type" "any" o :: Text+ maxResults = optField "max_results" 100 o :: Int++ eCleanPath <- validateSandboxPath policy rawPath+ case eCleanPath of+ Left err -> pure $ Left err+ Right cleanPath -> do+ eMatches <- liftIO $ try (walkFind cleanPath pat mbMaxDepth fType maxResults 0)+ case eMatches of+ Left err ->+ pure $+ Left $+ toolError+ ("Failed searching directory: " <> T.pack (show (err :: SomeException)))+ (Just "find_files")+ Nothing+ Right (found, truncated) ->+ let count = length found+ header =+ "Found "+ <> T.pack (show count)+ <> " items"+ <> (if truncated then " (truncated at " <> T.pack (show maxResults) <> ")" else "")+ <> ":"+ formatted = map T.pack found+ in pure $ Right $ T.unlines (header : formatted)+ _ -> pure $ Left $ toolError "Invalid arguments object" (Just "find_files") Nothing+ )+ where+ walkFind :: FilePath -> String -> Maybe Int -> Text -> Int -> Int -> IO ([FilePath], Bool)+ walkFind dir pat mbMaxDepth fType limit currentDepth = do+ isDir <- doesDirectoryExist dir+ if not isDir+ then pure ([], False)+ else do+ entries <- listDirectory dir+ let filtered = filter (not . isDefaultIgnored) entries+ let fullPaths = map (dir </>) filtered++ let matchesPat p = null pat || matchGlobPattern pat (takeFileName p)++ -- Separate files and subdirectories+ (files, subdirs) <- partitionPaths fullPaths++ let matchingFiles =+ if fType `elem` ["any", "file"]+ then filter matchesPat files+ else []+ matchingDirs =+ if fType `elem` ["any", "directory"]+ then filter matchesPat subdirs+ else []+ currentMatches = matchingFiles ++ matchingDirs++ let canRecurse = maybe True (currentDepth <) mbMaxDepth+ if not canRecurse || length currentMatches >= limit+ then pure (take limit currentMatches, length currentMatches >= limit)+ else do+ let remainingLimit = limit - length currentMatches+ subResults <-+ mapConcurrently+ (\sd -> walkFind sd pat mbMaxDepth fType remainingLimit (currentDepth + 1))+ subdirs+ let (allSubMatches, anyTrunc) = mconcatResults subResults+ combined = currentMatches ++ allSubMatches+ pure (take limit combined, anyTrunc || length combined >= limit)++ partitionPaths :: [FilePath] -> IO ([FilePath], [FilePath])+ partitionPaths paths = do+ dirFlags <- mapConcurrently doesDirectoryExist paths+ let (dirs, files) =+ foldr+ ( \(p, isD) (ds, fs) ->+ if isD then (p : ds, fs) else (ds, p : fs)+ )+ ([], [])+ (zip paths dirFlags)+ pure (files, dirs)++ mconcatResults :: [([FilePath], Bool)] -> ([FilePath], Bool)+ mconcatResults xs = (concatMap fst xs, any snd xs)++-- ============================================================================+-- 2. Grep Search Tool+-- ============================================================================++-- | Ripgrep-style content search tool.+grepSearchTool ::+ MonadIO m =>+ SecurityPolicy ->+ Tool m+grepSearchTool policy =+ withSecurity TierReadOnly policy $+ createTool+ "grep_search"+ "Search file contents across the workspace with line numbers, regex or literal search, context lines, and context-window protection."+ ( object+ [ "type" .= ("object" :: Text)+ , "properties"+ .= object+ [ "path"+ .= object+ [ "type" .= ("string" :: Text)+ , "description" .= ("Directory or file path to search (default: '.')" :: Text)+ ]+ , "query"+ .= object+ [ "type" .= ("string" :: Text)+ , "description" .= ("The text string to find" :: Text)+ ]+ , "case_sensitive"+ .= object+ [ "type" .= ("boolean" :: Text)+ , "description" .= ("Perform case-sensitive search (default: false)" :: Text)+ ]+ , "include_glob"+ .= object+ [ "type" .= ("string" :: Text)+ , "description" .= ("Optional filename glob filter, e.g. '*.hs' or '*.md'" :: Text)+ ]+ , "context_lines"+ .= object+ [ "type" .= ("integer" :: Text)+ , "description"+ .= ("Surrounding context lines to include before/after each match (default: 0)" :: Text)+ ]+ , "max_results"+ .= object+ [ "type" .= ("integer" :: Text)+ , "description" .= ("Maximum total matches to return before truncation (default: 50)" :: Text)+ ]+ ]+ , "required" .= (["query"] :: [Text])+ ]+ )+ ( \case+ Object o -> do+ let rawPath = optField "path" "." o :: String+ query = optField "query" "" o :: Text+ caseSens = optField "case_sensitive" False o :: Bool+ mbGlob = mbField "include_glob" o :: Maybe String+ contextLines = optField "context_lines" 0 o :: Int+ maxResults = optField "max_results" 50 o :: Int++ if T.null query+ then pure $ Left $ toolError "Search 'query' cannot be empty." (Just "grep_search") Nothing+ else do+ eCleanPath <- validateSandboxPath policy rawPath+ case eCleanPath of+ Left err -> pure $ Left err+ Right cleanPath -> do+ eRes <- liftIO $ try (runGrep cleanPath query caseSens mbGlob contextLines maxResults)+ case eRes of+ Left err ->+ pure $+ Left $+ toolError+ ("Grep search failed: " <> T.pack (show (err :: SomeException)))+ (Just "grep_search")+ Nothing+ Right resultText -> pure $ Right resultText+ _ -> pure $ Left $ toolError "Invalid arguments object" (Just "grep_search") Nothing+ )+ where+ runGrep :: FilePath -> Text -> Bool -> Maybe String -> Int -> Int -> IO Text+ runGrep root query caseSens mbGlob ctxLines limit = do+ isDir <- doesDirectoryExist root+ files <-+ if isDir+ then collectSearchFiles root mbGlob+ else pure [root]++ fileResults <-+ mapConcurrently+ (\f -> searchSingleFile f query caseSens ctxLines)+ files++ let allMatches = concatMap snd fileResults+ totalCount = length allMatches+ shownMatches = take limit allMatches+ wasTruncated = totalCount > limit+ header =+ "Found "+ <> T.pack (show totalCount)+ <> " match"+ <> (if totalCount == 1 then "" else "es")+ <> (if wasTruncated then " (showing first " <> T.pack (show limit) <> ")" else "")+ <> ":"++ if null allMatches+ then pure ("No matches found for '" <> query <> "' in " <> T.pack root)+ else pure $ T.unlines (header : shownMatches)++ collectSearchFiles :: FilePath -> Maybe String -> IO [FilePath]+ collectSearchFiles dir mbGlob = do+ entries <- listDirectory dir+ let valid = filter (not . isDefaultIgnored) entries+ fulls = map (dir </>) valid+ (files, subdirs) <- partitionPaths fulls+ let matchingFiles = case mbGlob of+ Nothing -> files+ Just glob -> filter (matchGlobPattern glob . takeFileName) files+ subFiles <- mapConcurrently (`collectSearchFiles` mbGlob) subdirs+ pure (matchingFiles ++ concat subFiles)++ partitionPaths :: [FilePath] -> IO ([FilePath], [FilePath])+ partitionPaths paths = do+ flags <- mapConcurrently doesDirectoryExist paths+ let (ds, fs) =+ foldr+ (\(p, isD) (dAcc, fAcc) -> if isD then (p : dAcc, fAcc) else (dAcc, p : fAcc))+ ([], [])+ (zip paths flags)+ pure (fs, ds)++ searchSingleFile :: FilePath -> Text -> Bool -> Int -> IO (FilePath, [Text])+ searchSingleFile path query caseSens ctx = do+ eTxt <- try (TIO.readFile path)+ case eTxt of+ Left (_ :: IOError) -> pure (path, [])+ Right content -> do+ let allLines = zip [1 :: Int ..] (T.lines content)+ isMatch (_, line) =+ if caseSens+ then query `T.isInfixOf` line+ else T.toLower query `T.isInfixOf` T.toLower line+ matchingIndices = [idx | (idx, l) <- allLines, isMatch (idx, l)]++ if null matchingIndices+ then pure (path, [])+ else do+ let blocks =+ map+ ( \matchIdx ->+ if ctx == 0+ then case lookup matchIdx allLines of+ Just l -> T.pack path <> ":" <> T.pack (show matchIdx) <> ": " <> l+ Nothing -> T.pack path <> ":" <> T.pack (show matchIdx) <> ":"+ else+ let start = max 1 (matchIdx - ctx)+ end = min (length allLines) (matchIdx + ctx)+ slice = filter (\(i, _) -> i >= start && i <= end) allLines+ in T.unlines+ ( (T.pack path <> ":")+ : [ " "+ <> T.pack (show lineNum)+ <> (if lineNum == matchIdx then ": " else "| ")+ <> lineTxt+ | (lineNum, lineTxt) <- slice+ ]+ )+ )+ matchingIndices+ pure (path, blocks)++-- ============================================================================+-- 3. Read File Tool (Paginated, Line-Numbered, Read-before-edit Enforced)+-- ============================================================================++-- | Read file tool with line-based pagination and context-window protection.+readFileTool ::+ MonadIO m =>+ Maybe CheckpointRegistry ->+ SecurityPolicy ->+ Tool m+readFileTool mbRegistry policy =+ withSecurity TierReadOnly policy $+ createTool+ "read_file"+ "Read text contents of a file with line-based pagination, context-window awareness, line numbering, and security checks."+ ( object+ [ "type" .= ("object" :: Text)+ , "properties"+ .= object+ [ "path"+ .= object+ [ "type" .= ("string" :: Text)+ , "description" .= ("Path to the file to read" :: Text)+ ]+ , "start_line"+ .= object+ [ "type" .= ("integer" :: Text)+ , "description" .= ("Optional 1-indexed starting line number (default: 1)" :: Text)+ ]+ , "end_line"+ .= object+ [ "type" .= ("integer" :: Text)+ , "description" .= ("Optional 1-indexed ending line number (default: start_line + 250)" :: Text)+ ]+ ]+ , "required" .= (["path"] :: [Text])+ ]+ )+ ( \case+ Object o -> do+ let rawPath = optField "path" "" o :: String+ mbStart = mbField "start_line" o :: Maybe Int+ mbEnd = mbField "end_line" o :: Maybe Int++ if null rawPath+ then pure $ Left $ toolError "Missing 'path' parameter" (Just "read_file") Nothing+ else do+ eCleanPath <- validateSandboxPath policy rawPath+ case eCleanPath of+ Left err -> pure $ Left err+ Right cleanPath -> do+ eContent <- liftIO $ try (TIO.readFile cleanPath)+ case eContent of+ Left err ->+ pure $+ Left $+ toolError+ ("Failed reading file: " <> T.pack (show (err :: IOError)))+ (Just "read_file")+ Nothing+ Right fullContent -> do+ -- Record file inspection in CheckpointRegistry for read-before-edit verification+ case mbRegistry of+ Nothing -> pure ()+ Just reg -> liftIO $ recordFileRead reg cleanPath++ let fileLines = T.lines fullContent+ totalLines = length fileLines+ start = max 1 (fromMaybe 1 mbStart)+ defaultEnd = min totalLines (start + 249)+ end = min totalLines (fromMaybe defaultEnd mbEnd)+ selectedLines =+ take (end - start + 1) $+ drop (start - 1) fileLines+ numbered =+ zipWith+ (\n l -> T.pack (show n) <> ": " <> l)+ [start .. end]+ selectedLines++ hasMore = end < totalLines+ header =+ "[File: "+ <> T.pack cleanPath+ <> " | Lines "+ <> T.pack (show start)+ <> "-"+ <> T.pack (show end)+ <> " of "+ <> T.pack (show totalLines)+ <> "]"+ footer =+ if hasMore+ then+ "\n[Showing "+ <> T.pack (show (end - start + 1))+ <> " lines. Call read_file with start_line="+ <> T.pack (show (end + 1))+ <> " to view subsequent lines]"+ else ""+ pure $ Right $ T.unlines (header : numbered) <> footer+ _ -> pure $ Left $ toolError "Invalid arguments object" (Just "read_file") Nothing+ )++-- ============================================================================+-- 4. Edit File Tool (Uniqueness Check, Read-Before-Edit, Checkpointing)+-- ============================================================================++-- | Precise string replacement tool with uniqueness check and automatic checkpointing.+editFileTool ::+ MonadIO m =>+ Maybe CheckpointRegistry ->+ SecurityPolicy ->+ Tool m+editFileTool mbRegistry policy =+ withSecurity TierWorkspaceWrite policy $+ createTool+ "edit_file"+ "Perform precise string replacement in a file. Enforces read-before-edit verification, exact uniqueness check, and automatic undo checkpointing."+ ( object+ [ "type" .= ("object" :: Text)+ , "properties"+ .= object+ [ "path"+ .= object+ [ "type" .= ("string" :: Text)+ , "description" .= ("Path to the file to edit" :: Text)+ ]+ , "old_string"+ .= object+ [ "type" .= ("string" :: Text)+ , "description"+ .= ("Exact character sequence to be replaced (must match uniquely in the file)" :: Text)+ ]+ , "new_string"+ .= object+ [ "type" .= ("string" :: Text)+ , "description" .= ("Replacement text to insert" :: Text)+ ]+ , "expected_occurrences"+ .= object+ [ "type" .= ("integer" :: Text)+ , "description" .= ("Expected number of occurrences to replace (default: 1)" :: Text)+ ]+ ]+ , "required" .= (["path", "old_string", "new_string"] :: [Text])+ ]+ )+ ( \case+ Object o -> do+ let rawPath = optField "path" "" o :: String+ oldStr = optField "old_string" "" o :: Text+ newStr = optField "new_string" "" o :: Text+ expectedOccs = optField "expected_occurrences" 1 o :: Int++ if null rawPath+ then pure $ Left $ toolError "Missing 'path' parameter" (Just "edit_file") Nothing+ else+ if T.null oldStr+ then pure $ Left $ toolError "'old_string' cannot be empty" (Just "edit_file") Nothing+ else do+ eCleanPath <- validateSandboxPath policy rawPath+ case eCleanPath of+ Left err -> pure $ Left err+ Right cleanPath -> do+ -- 1. Read-before-edit verification+ hasRead <- case mbRegistry of+ Nothing -> pure True+ Just reg -> liftIO $ hasReadFile reg cleanPath++ if not hasRead+ then+ pure $+ Left $+ toolError+ ( "Read-before-edit policy violation: You must inspect '"+ <> T.pack cleanPath+ <> "' using 'read_file' before editing it. This ensures exact line content and surrounding context."+ )+ (Just "edit_file")+ Nothing+ else do+ -- 2. Read existing content+ eCurrent <- liftIO $ try (TIO.readFile cleanPath)+ case eCurrent of+ Left err ->+ pure $+ Left $+ toolError+ ("Failed reading target file: " <> T.pack (show (err :: IOError)))+ (Just "edit_file")+ Nothing+ Right currentTxt -> do+ -- 3. Uniqueness and occurrence check+ let occCount = countOccurrences oldStr currentTxt+ if occCount == 0+ then+ pure $+ Left $+ toolError+ ( "Target content not found in '"+ <> T.pack cleanPath+ <> "'. Ensure exact whitespace and line indentation by checking 'read_file'."+ )+ (Just "edit_file")+ Nothing+ else+ if occCount /= expectedOccs+ then do+ let lineIndices = findOccurrencesLineIndices oldStr currentTxt+ pure $+ Left $+ toolError+ ( "Ambiguity error: Expected "+ <> T.pack (show expectedOccs)+ <> " occurrence(s), but found "+ <> T.pack (show occCount)+ <> " occurrences in '"+ <> T.pack cleanPath+ <> "' at lines: "+ <> T.pack (show lineIndices)+ <> ". Please include more surrounding lines in 'old_string' to make it unique."+ )+ (Just "edit_file")+ Nothing+ else do+ -- 4. Automatic undo snapshot checkpoint+ cpIdText <- case mbRegistry of+ Nothing -> pure "no-registry"+ Just reg ->+ liftIO $+ createFileCheckpoint+ reg+ cleanPath+ currentTxt+ ("edit_file (" <> T.pack (show expectedOccs) <> " replacements)")++ -- 5. Apply replacement+ let replacedTxt = T.replace oldStr newStr currentTxt+ eWrite <- liftIO $ try (TIO.writeFile cleanPath replacedTxt)+ case eWrite of+ Left err ->+ pure $+ Left $+ toolError+ ("Failed writing edited file: " <> T.pack (show (err :: IOError)))+ (Just "edit_file")+ Nothing+ Right () ->+ pure $+ Right $+ "Successfully edited '"+ <> T.pack cleanPath+ <> "' (replaced "+ <> T.pack (show occCount)+ <> " occurrence(s)). Checkpoint ID: ["+ <> cpIdText+ <> "]. Use 'undo_file_edit' to revert if needed."+ _ -> pure $ Left $ toolError "Invalid arguments object" (Just "edit_file") Nothing+ )+ where+ countOccurrences needle haystack+ | T.null needle = 0+ | otherwise = T.count needle haystack++ findOccurrencesLineIndices needle haystack =+ let lns = zip [1 :: Int ..] (T.lines haystack)+ in [ln | (ln, lineTxt) <- lns, needle `T.isInfixOf` lineTxt]++-- ============================================================================+-- 5. Write File Tool+-- ============================================================================++-- | Write text to file with overwrite protection and automatic checkpointing.+writeFileTool ::+ MonadIO m =>+ Maybe CheckpointRegistry ->+ SecurityPolicy ->+ Tool m+writeFileTool mbRegistry policy =+ withSecurity TierWorkspaceWrite policy $+ createTool+ "write_file"+ "Write text contents to a file path. Automatically creates parent directories. Protects against accidental overwrites and takes undo checkpoints."+ ( object+ [ "type" .= ("object" :: Text)+ , "properties"+ .= object+ [ "path"+ .= object+ [ "type" .= ("string" :: Text)+ , "description" .= ("Target destination file path" :: Text)+ ]+ , "content"+ .= object+ [ "type" .= ("string" :: Text)+ , "description" .= ("Full content to write" :: Text)+ ]+ , "overwrite"+ .= object+ [ "type" .= ("boolean" :: Text)+ , "description" .= ("Allow overwriting if file already exists (default: false)" :: Text)+ ]+ ]+ , "required" .= (["path", "content"] :: [Text])+ ]+ )+ ( \case+ Object o -> do+ let rawPath = optField "path" "" o :: String+ content = optField "content" "" o :: Text+ overwrite = optField "overwrite" False o :: Bool++ if null rawPath+ then pure $ Left $ toolError "Missing 'path' parameter" (Just "write_file") Nothing+ else do+ eCleanPath <- validateSandboxPath policy rawPath+ case eCleanPath of+ Left err -> pure $ Left err+ Right cleanPath -> do+ exists <- liftIO $ doesFileExist cleanPath+ if exists && not overwrite+ then+ pure $+ Left $+ toolError+ ( "File '"+ <> T.pack cleanPath+ <> "' already exists. Set 'overwrite=true' to overwrite, or use 'edit_file' for targeted modifications."+ )+ (Just "write_file")+ Nothing+ else do+ -- Automatic checkpoint before overwrite+ cpIdText <-+ if exists+ then case mbRegistry of+ Nothing -> pure "no-registry"+ Just reg -> do+ prevTxt <- liftIO $ TIO.readFile cleanPath+ liftIO $ createFileCheckpoint reg cleanPath prevTxt "write_file overwrite"+ else pure "new-file"++ liftIO $ createDirectoryIfMissing True (takeDirectory cleanPath)+ eWrite <- liftIO $ try (TIO.writeFile cleanPath content)+ case eWrite of+ Left err ->+ pure $+ Left $+ toolError+ ("Failed writing file: " <> T.pack (show (err :: IOError)))+ (Just "write_file")+ Nothing+ Right () -> do+ -- Mark file as read so subsequent edits can proceed+ case mbRegistry of+ Nothing -> pure ()+ Just reg -> liftIO $ recordFileRead reg cleanPath++ pure $+ Right $+ "Successfully wrote file '"+ <> T.pack cleanPath+ <> "'"+ <> (if exists then " (Checkpoint: [" <> cpIdText <> "])" else "")+ _ -> pure $ Left $ toolError "Invalid arguments object" (Just "write_file") Nothing+ )++-- ============================================================================+-- 6. List Directory Tool+-- ============================================================================++-- | List directory contents with entry types and file sizes.+listDirTool ::+ MonadIO m =>+ SecurityPolicy ->+ Tool m+listDirTool policy =+ withSecurity TierReadOnly policy $+ createTool+ "list_directory"+ "List files and subdirectories in a directory path with file sizes and type indicators."+ ( object+ [ "type" .= ("object" :: Text)+ , "properties"+ .= object+ [ "path"+ .= object+ [ "type" .= ("string" :: Text)+ , "description" .= ("Path to directory (default: '.')" :: Text)+ ]+ ]+ ]+ )+ ( \case+ Object o -> do+ let rawPath = optField "path" "." o :: String+ eCleanPath <- validateSandboxPath policy rawPath+ case eCleanPath of+ Left err -> pure $ Left err+ Right cleanPath -> do+ eList <- liftIO $ try (listDirectory cleanPath)+ case eList of+ Left err ->+ pure $+ Left $+ toolError+ ("Failed listing directory: " <> T.pack (show (err :: IOError)))+ (Just "list_directory")+ Nothing+ Right entries -> do+ let sorted = sort (filter (not . isDefaultIgnored) entries)+ formattedEntries <- liftIO $ mapConcurrently (formatEntry cleanPath) sorted+ let count = length sorted+ header =+ "Directory '"+ <> T.pack cleanPath+ <> "' ("+ <> T.pack (show count)+ <> " items):"+ pure $ Right $ T.unlines (header : formattedEntries)+ _ -> pure $ Left $ toolError "Invalid arguments object" (Just "list_directory") Nothing+ )+ where+ formatEntry :: FilePath -> FilePath -> IO Text+ formatEntry parent name = do+ let full = parent </> name+ isDir <- doesDirectoryExist full+ if isDir+ then pure $ " [DIR] " <> T.pack name <> "/"+ else do+ sz <- getFileSize full+ pure $+ " [FILE] "+ <> T.pack name+ <> " ("+ <> formatBytes sz+ <> ")"++ formatBytes :: Integer -> Text+ formatBytes b+ | b < 1024 = T.pack (show b) <> " B"+ | b < 1024 * 1024 = T.pack (show (b `div` 1024)) <> " KB"+ | otherwise = T.pack (show (b `div` (1024 * 1024))) <> " MB"++-- ============================================================================+-- Complete FileSystem Toolkit Suite Factory+-- ============================================================================++{- | Instantiate a complete filesystem toolkit bound to a shared+'CheckpointRegistry' and 'SecurityPolicy'.+-}+newFileSystemSuite ::+ MonadIO m =>+ SecurityPolicy ->+ IO [Tool m]+newFileSystemSuite policy = do+ registry <- newCheckpointRegistry+ pure+ [ findFilesTool policy+ , grepSearchTool policy+ , readFileTool (Just registry) policy+ , listDirTool policy+ , writeFileTool (Just registry) policy+ , editFileTool (Just registry) policy+ , undoFileEditTool registry+ , listCheckpointsTool registry+ ]
+ src/Langchain/Tool/GenericSchema.hs view
@@ -0,0 +1,175 @@+{-# 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/Process.hs view
@@ -0,0 +1,473 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}++{- |+Module : Langchain.Tool.Process+Description : Asynchronous process manager and shell execution tools+Copyright : (c) 2025-2026 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++Provides process execution:+* 'commandRunTool' ('command_run'): Runs commands with synchronous wait timeouts,+ automatic backgrounding for long-running processes, security checks, and output truncation.+* 'commandStatusTool' ('command_status'): Checks runtime, status, and output logs for running/completed processes.+* 'commandKillTool' ('command_kill'): Cleanly terminates long-running background processes.+* 'newProcessSuite': Instantiates a shared process manager and returns the tool suite.+-}+module Langchain.Tool.Process+ ( -- * Process Manager+ ProcessManager (..)+ , ProcessStatus (..)+ , ManagedProcess (..)+ , newProcessManager++ -- * Tools+ , commandRunTool+ , commandStatusTool+ , commandKillTool+ , newProcessSuite+ ) where++import Control.Concurrent.Async (Async, async, cancel, wait)+import Control.Concurrent.STM+ ( TVar+ , atomically+ , modifyTVar'+ , newTVarIO+ , readTVar+ , readTVarIO+ , writeTVar+ )+import Control.Exception (SomeException, try)+import Control.Monad.IO.Class (MonadIO, liftIO)+import Data.Aeson (FromJSON (..), Key, Object, ToJSON (..), Value (..), object, (.:?), (.=))+import Data.Aeson.Types (parseEither)+import qualified Data.Map.Strict as Map+import Data.Text (Text)+import qualified Data.Text as T+import Data.Time.Clock (NominalDiffTime, UTCTime, diffUTCTime, getCurrentTime)+import GHC.Generics (Generic)+import System.Exit (ExitCode (..))+import System.IO (hGetLine)+import System.Process+ ( CreateProcess (..)+ , ProcessHandle+ , StdStream (..)+ , createProcess+ , shell+ , terminateProcess+ , waitForProcess+ )+import System.Timeout (timeout)++import Langchain.Error (toolError)+import Langchain.Tool (Tool (..), createTool)+import Langchain.Tool.Security+ ( SecurityPolicy+ , ToolTier (..)+ , validateCommandSafety+ , withSecurity+ )++-- | Execution state of a managed system process.+data ProcessStatus+ = StatusRunning+ | StatusFinished !ExitCode !UTCTime !NominalDiffTime+ | StatusKilled !UTCTime !NominalDiffTime+ deriving (Eq, Show, Generic)++instance ToJSON ProcessStatus where+ toJSON StatusRunning = object ["status" .= ("running" :: Text)]+ toJSON (StatusFinished code time duration) =+ let codeInt = case code of+ ExitSuccess -> 0 :: Int+ ExitFailure n -> n+ in object+ [ "status" .= ("finished" :: Text)+ , "exit_code" .= codeInt+ , "finished_at" .= time+ , "duration_seconds" .= (realToFrac duration :: Double)+ ]+ toJSON (StatusKilled time duration) =+ object+ [ "status" .= ("killed" :: Text)+ , "killed_at" .= time+ , "duration_seconds" .= (realToFrac duration :: Double)+ ]++-- | Tracked handle and log buffers for a process.+data ManagedProcess = ManagedProcess+ { procId :: !Text+ , procCommand :: !Text+ , procCwd :: !(Maybe FilePath)+ , procStartTime :: !UTCTime+ , procStatusVar :: !(TVar ProcessStatus)+ , procOutputVar :: !(TVar [Text])+ , procHandle :: !ProcessHandle+ , procWaiter :: !(Async (ExitCode, NominalDiffTime))+ }++-- | In-memory registry for active and past background processes.+data ProcessManager = ProcessManager+ { pmProcesses :: !(TVar (Map.Map Text ManagedProcess))+ , pmNextId :: !(TVar Int)+ }++-- | Initialize a new process manager instance.+newProcessManager :: IO ProcessManager+newProcessManager = do+ procsVar <- newTVarIO Map.empty+ idVar <- newTVarIO 1+ pure $ ProcessManager procsVar idVar++-- | Extract an optional JSON field with a fallback default.+optField :: FromJSON a => Key -> a -> Object -> a+optField k def o = case parseEither (.:? k) o of+ Right (Just v) -> v+ _ -> def++-- | Extract an optional JSON field as Maybe.+mbField :: FromJSON a => Key -> Object -> Maybe a+mbField k o = case parseEither (.:? k) o of+ Right mv -> mv+ _ -> Nothing++-- | Format elapsed seconds into human-readable text.+formatDuration :: NominalDiffTime -> Text+formatDuration dt =+ let secs = (fromRational (toRational dt) :: Double)+ in T.pack (take 5 (show secs)) <> "s"++-- | Truncate lines if total lines or byte count exceeds limits.+truncateOutput :: Int -> [Text] -> (Text, Bool)+truncateOutput maxLines linesList =+ let total = length linesList+ truncated = total > maxLines+ selected = if truncated then drop (total - maxLines) linesList else linesList+ body = T.unlines selected+ in (body, truncated)++-- ============================================================================+-- 1. Command Run Tool+-- ============================================================================++-- | Command run tool with synchronous wait, backgrounding, and security safety checks.+commandRunTool ::+ MonadIO m =>+ ProcessManager ->+ SecurityPolicy ->+ Tool m+commandRunTool pm policy =+ withSecurity TierExecution policy $+ createTool+ "command_run"+ "Execute a shell command. Waits synchronously up to 'timeout_ms' (default: 5000ms). If the command is long-running, automatically transitions to a background process that can be monitored with 'command_status' or stopped with 'command_kill'."+ ( object+ [ "type" .= ("object" :: Text)+ , "properties"+ .= object+ [ "command"+ .= object+ [ "type" .= ("string" :: Text)+ , "description" .= ("Shell command line to execute" :: Text)+ ]+ , "cwd"+ .= object+ [ "type" .= ("string" :: Text)+ , "description" .= ("Optional working directory for the command" :: Text)+ ]+ , "timeout_ms"+ .= object+ [ "type" .= ("integer" :: Text)+ , "description" .= ("Milliseconds to wait synchronously before backgrounding (default: 5000)" :: Text)+ ]+ ]+ , "required" .= (["command"] :: [Text])+ ]+ )+ ( \case+ Object o -> do+ let rawCmd = optField "command" "" o :: Text+ mbCwd = mbField "cwd" o :: Maybe FilePath+ timeoutMs = optField "timeout_ms" 5000 o :: Int++ if T.null rawCmd+ then pure $ Left $ toolError "Missing 'command' parameter." (Just "command_run") Nothing+ else case validateCommandSafety policy rawCmd of+ Left err -> pure $ Left err+ Right () -> do+ eProc <- liftIO $ spawnCommand pm rawCmd mbCwd+ case eProc of+ Left err ->+ pure $+ Left $+ toolError+ ("Failed to launch process: " <> T.pack (show (err :: SomeException)))+ (Just "command_run")+ Nothing+ Right mp@ManagedProcess {..} -> do+ -- Wait synchronously up to timeoutMs+ let waitMicros = timeoutMs * 1000+ mDone <- liftIO $ timeout waitMicros (waitForCompletion mp)+ case mDone of+ Just () -> do+ -- Process finished within timeoutMs+ status <- liftIO $ readTVarIO procStatusVar+ logs <- liftIO $ readTVarIO procOutputVar+ let (outTxt, wasTruncated) = truncateOutput 100 logs+ case status of+ StatusFinished exitCode _ duration ->+ let header =+ "Command completed in "+ <> formatDuration duration+ <> " with "+ <> ( case exitCode of+ ExitSuccess -> "exit code 0 (Success)"+ ExitFailure code -> "exit code " <> T.pack (show code) <> " (Failure)"+ )+ <> ":"+ <> (if wasTruncated then " [showing last 100 lines]" else "")+ in pure $ Right $ header <> "\n" <> outTxt+ StatusKilled _ duration ->+ pure $ Right $ "Command was terminated after " <> formatDuration duration <> "."+ StatusRunning ->+ pure $ Right $ "Command is running (ID: [" <> procId <> "])."+ Nothing -> do+ -- Still running: background notification+ now <- liftIO getCurrentTime+ let elapsed = diffUTCTime now procStartTime+ pure $+ Right $+ "Command is still running in background (process_id: \""+ <> procId+ <> "\"). Elapsed: "+ <> formatDuration elapsed+ <> ".\nUse 'command_status' with process_id=\""+ <> procId+ <> "\" to inspect output logs, or 'command_kill' to terminate."+ _ -> pure $ Left $ toolError "Invalid arguments object" (Just "command_run") Nothing+ )+ where+ spawnCommand :: ProcessManager -> Text -> Maybe FilePath -> IO (Either SomeException ManagedProcess)+ spawnCommand ProcessManager {..} cmd mbCwd = try $ do+ now <- getCurrentTime+ pId <- atomically $ do+ curId <- readTVar pmNextId+ writeTVar pmNextId (curId + 1)+ pure $ "proc-" <> T.pack (show curId)++ statusVar <- newTVarIO StatusRunning+ outputVar <- newTVarIO []++ let cp =+ (shell (T.unpack cmd))+ { cwd = mbCwd+ , std_out = CreatePipe+ , std_err = CreatePipe+ }++ (Nothing, Just hOut, Just hErr, ph) <- createProcess cp++ -- Spawn async readers for stdout and stderr+ outAsync <- async $ readStreamLines hOut outputVar+ errAsync <- async $ readStreamLines hErr outputVar++ -- Spawn async waiter thread that waits for process and stream drainage+ waiterAsync <- async $ do+ exitCode <- waitForProcess ph+ _ <- wait outAsync+ _ <- wait errAsync+ end <- getCurrentTime+ let dur = diffUTCTime end now+ atomically $ writeTVar statusVar (StatusFinished exitCode end dur)+ pure (exitCode, dur)++ let mp =+ ManagedProcess+ { procId = pId+ , procCommand = cmd+ , procCwd = mbCwd+ , procStartTime = now+ , procStatusVar = statusVar+ , procOutputVar = outputVar+ , procHandle = ph+ , procWaiter = waiterAsync+ }++ atomically $ modifyTVar' pmProcesses (Map.insert pId mp)+ pure mp++ readStreamLines hnd var = loop+ where+ loop = do+ eLine <- try (hGetLine hnd)+ case eLine of+ Left (_ :: SomeException) -> pure ()+ Right l -> do+ atomically $ modifyTVar' var (\acc -> acc ++ [T.pack l])+ loop++ waitForCompletion ManagedProcess {..} = do+ _ <- wait procWaiter+ pure ()++-- ============================================================================+-- 2. Command Status Tool+-- ============================================================================++-- | Inspect status, runtime, and output logs of a running or completed process.+commandStatusTool ::+ MonadIO m =>+ ProcessManager ->+ SecurityPolicy ->+ Tool m+commandStatusTool ProcessManager {..} policy =+ withSecurity TierReadOnly policy $+ createTool+ "command_status"+ "Inspect the execution status, runtime duration, and latest output logs for a background process."+ ( object+ [ "type" .= ("object" :: Text)+ , "properties"+ .= object+ [ "process_id"+ .= object+ [ "type" .= ("string" :: Text)+ , "description" .= ("ID of the process to inspect (e.g. 'proc-1')" :: Text)+ ]+ , "tail_lines"+ .= object+ [ "type" .= ("integer" :: Text)+ , "description" .= ("Number of latest output lines to view (default: 50)" :: Text)+ ]+ ]+ , "required" .= (["process_id"] :: [Text])+ ]+ )+ ( \case+ Object o -> do+ let pId = optField "process_id" "" o :: Text+ tailLines = optField "tail_lines" 50 o :: Int++ if T.null pId+ then pure $ Left $ toolError "Missing 'process_id' parameter." (Just "command_status") Nothing+ else do+ procs <- liftIO $ readTVarIO pmProcesses+ case Map.lookup pId procs of+ Nothing ->+ pure $+ Left $+ toolError+ ("Process '" <> pId <> "' not found in active process registry.")+ (Just "command_status")+ Nothing+ Just ManagedProcess {..} -> do+ now <- liftIO getCurrentTime+ status <- liftIO $ readTVarIO procStatusVar+ logs <- liftIO $ readTVarIO procOutputVar+ let totalLines = length logs+ (outTxt, wasTruncated) = truncateOutput tailLines logs+ statusDesc = case status of+ StatusRunning ->+ "Status: Running (elapsed: " <> formatDuration (diffUTCTime now procStartTime) <> ")"+ StatusFinished exitCode _ duration ->+ "Status: Finished in "+ <> formatDuration duration+ <> " with "+ <> ( case exitCode of+ ExitSuccess -> "exit code 0 (Success)"+ ExitFailure code -> "exit code " <> T.pack (show code)+ )+ StatusKilled _ duration ->+ "Status: Terminated after " <> formatDuration duration++ header =+ "=== Process ["+ <> procId+ <> "] ===\nCommand: "+ <> procCommand+ <> "\n"+ <> statusDesc+ <> "\nOutput ("+ <> T.pack (show totalLines)+ <> " lines"+ <> (if wasTruncated then ", showing last " <> T.pack (show tailLines) else "")+ <> "):\n"+ pure $ Right $ header <> outTxt+ _ -> pure $ Left $ toolError "Invalid arguments object" (Just "command_status") Nothing+ )++-- ============================================================================+-- 3. Command Kill Tool+-- ============================================================================++-- | Cleanly terminate a background process.+commandKillTool ::+ MonadIO m =>+ ProcessManager ->+ SecurityPolicy ->+ Tool m+commandKillTool ProcessManager {..} policy =+ withSecurity TierExecution policy $+ createTool+ "command_kill"+ "Terminate a running background process."+ ( object+ [ "type" .= ("object" :: Text)+ , "properties"+ .= object+ [ "process_id"+ .= object+ [ "type" .= ("string" :: Text)+ , "description" .= ("ID of the process to terminate (e.g. 'proc-1')" :: Text)+ ]+ ]+ , "required" .= (["process_id"] :: [Text])+ ]+ )+ ( \case+ Object o -> do+ let pId = optField "process_id" "" o :: Text+ if T.null pId+ then pure $ Left $ toolError "Missing 'process_id' parameter." (Just "command_kill") Nothing+ else do+ procs <- liftIO $ readTVarIO pmProcesses+ case Map.lookup pId procs of+ Nothing ->+ pure $+ Left $+ toolError+ ("Process '" <> pId <> "' not found.")+ (Just "command_kill")+ Nothing+ Just ManagedProcess {..} -> do+ now <- liftIO getCurrentTime+ liftIO $ terminateProcess procHandle+ liftIO $ cancel procWaiter+ let dur = diffUTCTime now procStartTime+ liftIO $ atomically $ writeTVar procStatusVar (StatusKilled now dur)+ pure $ Right $ "Successfully terminated process [" <> pId <> "]."+ _ -> pure $ Left $ toolError "Invalid arguments object" (Just "command_kill") Nothing+ )++-- ============================================================================+-- Complete Process Suite Factory+-- ============================================================================++-- | Create the complete command execution and process control tool suite.+newProcessSuite ::+ MonadIO m =>+ SecurityPolicy ->+ IO [Tool m]+newProcessSuite policy = do+ pm <- newProcessManager+ pure+ [ commandRunTool pm policy+ , commandStatusTool pm policy+ , commandKillTool pm policy+ ]
+ src/Langchain/Tool/Security.hs view
@@ -0,0 +1,304 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++{- |+Module : Langchain.Tool.Security+Description : Security, permission tiers, sandbox validation, and HITL hooks+Copyright : (c) 2025-2026 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental++Provides tiered permission policies for tools (ReadOnly, WorkspaceWrite, Execution, Admin),+filesystem path sandboxing, sensitive credential filtering, dangerous command prevention,+and Human-in-the-Loop (HITL) approval hooks.+-}+module Langchain.Tool.Security+ ( -- * Permission Tiers+ ToolTier (..)++ -- * Security Policy+ , SecurityPolicy (..)+ , defaultSecurityPolicy+ , readOnlySecurityPolicy+ , workspaceSandboxPolicy+ , unrestrictedSecurityPolicy++ -- * Approval & HITL+ , ApprovalRequest (..)+ , ApprovalDecision (..)++ -- * Validation Functions+ , checkTierPermission+ , validateSandboxPath+ , isSensitivePath+ , validateCommandSafety++ -- * Tool Security Combinator+ , withSecurity+ ) where++import Control.Monad.IO.Class (MonadIO, liftIO)+import Data.Aeson (FromJSON, ToJSON, Value)+import Data.Char (toLower)+import Data.List (isInfixOf, isPrefixOf)+import qualified Data.Set as Set+import Data.Text (Text)+import qualified Data.Text as T+import GHC.Generics (Generic)+import System.Directory (canonicalizePath, doesDirectoryExist, doesFileExist)+import System.FilePath (isAbsolute, normalise, pathSeparator, takeDirectory, takeFileName, (</>))++import Langchain.Error (LangchainError, toolError)+import Langchain.Tool (Tool (..))++-- | Tier levels representing the risk and side-effects of tool execution.+data ToolTier+ = -- | Safe, observational queries (find, grep, read, list, status)+ TierReadOnly+ | -- | Modifies files within workspace sandbox (write, edit, rollback)+ TierWorkspaceWrite+ | -- | Spawns system processes and shell commands+ TierExecution+ | -- | Unrestricted or system-level modifications+ TierAdmin+ deriving (Eq, Ord, Show, Read, Enum, Bounded, Generic, ToJSON, FromJSON)++-- | Human-in-the-Loop approval request structure.+data ApprovalRequest = ApprovalRequest+ { reqToolName :: !Text+ , reqTier :: !ToolTier+ , reqArguments :: !Value+ , reqDescription :: !Text+ }+ deriving (Eq, Show, Generic, ToJSON, FromJSON)++-- | Decision result from an approval hook.+data ApprovalDecision+ = Approved+ | Denied !Text+ deriving (Eq, Show, Generic, ToJSON, FromJSON)++-- | Security policy controlling tool authorization and sandboxing bounds.+data SecurityPolicy = SecurityPolicy+ { policyAllowedTiers :: !(Set.Set ToolTier)+ -- ^ Set of authorized tiers+ , policyWorkspaceRoot :: !(Maybe FilePath)+ -- ^ If set, restricts file operations to within this directory tree+ , policySensitivePatterns :: ![Text]+ -- ^ File names or substrings blocked from reading/writing (e.g. .env, id_rsa, keys)+ , policyCommandWhitelist :: !(Maybe [Text])+ -- ^ Optional strict whitelist of allowed command binaries+ , policyCommandBlacklist :: ![Text]+ -- ^ Blocked dangerous command substrings (e.g. rm -rf /, fork bombs)+ , policyApprovalHook :: !(Maybe (ApprovalRequest -> IO ApprovalDecision))+ -- ^ Optional Human-in-the-Loop approval callback for sensitive actions+ }+ deriving (Generic)++{- | Default standard security policy:++* Allows 'TierReadOnly', 'TierWorkspaceWrite', and 'TierExecution'.+* Protects sensitive secret files (.env, private keys, credential files).+* Blocks destructive shell command patterns.+-}+defaultSecurityPolicy :: SecurityPolicy+defaultSecurityPolicy =+ SecurityPolicy+ { policyAllowedTiers = Set.fromList [TierReadOnly, TierWorkspaceWrite, TierExecution]+ , policyWorkspaceRoot = Nothing+ , policySensitivePatterns =+ [ ".env"+ , "id_rsa"+ , "id_ed25519"+ , "id_ecdsa"+ , ".aws/credentials"+ , ".gemini_api_key"+ , "shadow"+ , "master.passwd"+ ]+ , policyCommandWhitelist = Nothing+ , policyCommandBlacklist =+ [ "rm -rf /"+ , "rm -fr /"+ , ":(){ :|:& };:"+ , "mkfs"+ , "dd if=/dev/zero"+ , "chmod -R 777 /"+ , "chown -R"+ , "> /dev/sda"+ ]+ , policyApprovalHook = Nothing+ }++-- | Restrictive read-only security policy (prevents any state mutations).+readOnlySecurityPolicy :: SecurityPolicy+readOnlySecurityPolicy =+ defaultSecurityPolicy+ { policyAllowedTiers = Set.singleton TierReadOnly+ }++-- | Sandbox security policy restricting operations strictly to a workspace directory.+workspaceSandboxPolicy :: FilePath -> SecurityPolicy+workspaceSandboxPolicy root =+ defaultSecurityPolicy+ { policyWorkspaceRoot = Just root+ }++-- | Unrestricted security policy allowing all tiers and bypassing sensitive file filters.+unrestrictedSecurityPolicy :: SecurityPolicy+unrestrictedSecurityPolicy =+ SecurityPolicy+ { policyAllowedTiers = Set.fromList [TierReadOnly, TierWorkspaceWrite, TierExecution, TierAdmin]+ , policyWorkspaceRoot = Nothing+ , policySensitivePatterns = []+ , policyCommandWhitelist = Nothing+ , policyCommandBlacklist = []+ , policyApprovalHook = Nothing+ }++-- | Check whether a tool tier is permitted under the policy.+checkTierPermission :: SecurityPolicy -> ToolTier -> Either LangchainError ()+checkTierPermission SecurityPolicy {..} tier+ | tier `Set.member` policyAllowedTiers = Right ()+ | otherwise =+ Left $+ toolError+ ( "Permission denied: Tool requires "+ <> T.pack (show tier)+ <> " authorization, which is not granted by the current security policy."+ )+ Nothing+ Nothing++-- | Check if a path refers to a sensitive file (keys, credentials, secrets).+isSensitivePath :: SecurityPolicy -> FilePath -> Bool+isSensitivePath SecurityPolicy {..} path =+ let lowerPath = map toLower (normalise path)+ in any (\pat -> T.unpack (T.toLower pat) `isInfixOf` lowerPath) policySensitivePatterns++-- | Validate that a file path remains safely within the configured workspace sandbox.+validateSandboxPath ::+ MonadIO m =>+ SecurityPolicy ->+ FilePath ->+ m (Either LangchainError FilePath)+validateSandboxPath SecurityPolicy {..} rawPath = liftIO $ do+ -- 1. Check sensitive path filter+ if isSensitivePath+ ( SecurityPolicy+ policyAllowedTiers+ policyWorkspaceRoot+ policySensitivePatterns+ policyCommandWhitelist+ policyCommandBlacklist+ policyApprovalHook+ )+ rawPath+ then+ pure $+ Left $+ toolError+ ("Access denied: Path matches sensitive/secret file policy: " <> T.pack rawPath)+ Nothing+ Nothing+ else case policyWorkspaceRoot of+ Nothing -> pure $ Right (normalise rawPath)+ Just root -> do+ canonRoot <- canonicalizePath root+ let targetPath =+ if isAbsolute rawPath+ then rawPath+ else canonRoot </> rawPath+ canonTarget <- canonicalizeAncestor targetPath+ let prefix = canonRoot ++ [pathSeparator]+ isInside = canonTarget == canonRoot || prefix `isPrefixOf` canonTarget+ if isInside+ then pure $ Right canonTarget+ else+ pure $+ Left $+ toolError+ ( "Sandbox violation: Path '"+ <> T.pack rawPath+ <> "' escapes the workspace root '"+ <> T.pack canonRoot+ <> "'."+ )+ Nothing+ Nothing+ where+ canonicalizeAncestor :: FilePath -> IO FilePath+ canonicalizeAncestor p = do+ ex <- doesFileExist p+ dx <- doesDirectoryExist p+ if ex || dx+ then canonicalizePath p+ else do+ let parent = takeDirectory p+ if parent == p+ then canonicalizePath p+ else do+ canonParent <- canonicalizeAncestor parent+ pure (canonParent </> takeFileName p)++-- | Validate that a shell command does not contain dangerous destructive patterns.+validateCommandSafety :: SecurityPolicy -> Text -> Either LangchainError ()+validateCommandSafety SecurityPolicy {..} cmd = do+ -- Check blacklist+ let lowerCmd = T.toLower cmd+ mapM_+ ( \bl ->+ if bl `T.isInfixOf` lowerCmd+ then+ Left $+ toolError+ ("Command blocked by security policy (prohibited pattern: '" <> bl <> "')")+ Nothing+ Nothing+ else Right ()+ )+ policyCommandBlacklist++ -- Check whitelist if present+ case policyCommandWhitelist of+ Nothing -> Right ()+ Just wl ->+ let firstWord = case T.words cmd of+ (w : _) -> w+ [] -> ""+ in if firstWord `elem` wl+ then Right ()+ else+ Left $+ toolError+ ("Command '" <> firstWord <> "' is not in the authorized command whitelist.")+ Nothing+ Nothing++-- | Wrap a 'Tool m' with permission tier enforcement, sandboxing, and optional HITL approval.+withSecurity ::+ MonadIO m =>+ ToolTier ->+ SecurityPolicy ->+ Tool m ->+ Tool m+withSecurity tier policy tool@Tool {..} =+ tool+ { toolExecute = \args -> do+ case checkTierPermission policy tier of+ Left err -> pure $ Left err+ Right () -> do+ -- Check HITL approval if hook is defined+ mApproval <- case policyApprovalHook policy of+ Nothing -> pure Approved+ Just hook -> liftIO $ hook (ApprovalRequest toolName tier args toolDescription)+ case mApproval of+ Denied reason ->+ pure $+ Left $+ toolError ("Execution rejected by approval policy: " <> reason) (Just toolName) Nothing+ Approved -> toolExecute args+ }
+ src/Langchain/Tool/Shell.hs view
@@ -0,0 +1,87 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}++{- |+Module : Langchain.Tool.Shell+Description : Shell command execution tool with safety and security policy enforcement+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+with safety verification and permission checks.+-}+module Langchain.Tool.Shell+ ( shellTool+ , secureShellTool+ ) 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.Error (toolError)+import Langchain.Tool (Tool (..), createTool)+import Langchain.Tool.Security+ ( SecurityPolicy+ , ToolTier (..)+ , defaultSecurityPolicy+ , validateCommandSafety+ , withSecurity+ )++-- | Tool that executes a shell command with default security policy.+shellTool :: MonadIO m => Tool m+shellTool = secureShellTool defaultSecurityPolicy++-- | Tool that executes a shell command under a specific 'SecurityPolicy'.+secureShellTool :: MonadIO m => SecurityPolicy -> Tool m+secureShellTool policy =+ withSecurity TierExecution policy $+ 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) -> case validateCommandSafety policy cmd of+ Left err -> pure $ Left err+ Right () -> 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.hs view
@@ -0,0 +1,15 @@+{- |+Module : Langchain.VectorStore+Description : Vector store abstraction and implementations+Copyright : (c) 2025-2026 Tushar Adhatrao+License : MIT+Maintainer : Tushar Adhatrao <tusharadhatrao@gmail.com>+Stability : experimental+-}+module Langchain.VectorStore+ ( module Langchain.VectorStore.Core+ , module Langchain.VectorStore.InMemory+ ) where++import Langchain.VectorStore.Core+import Langchain.VectorStore.InMemory
+ src/Langchain/VectorStore/Core.hs view
@@ -0,0 +1,56 @@+{-# 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.DocumentLoader.Core (Document)+import Langchain.Error (LangchainError)++-- | 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 view
@@ -0,0 +1,99 @@+{-# 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.DocumentLoader.Core (Document)+import Langchain.Embeddings.Core+import Langchain.Error (LangchainError)+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
test/Spec.hs view
@@ -2,11 +2,16 @@ import Test.Tasty -import qualified Test.Langchain.Core.Model as ModelTest-import qualified Test.Langchain.Core.Monad as MonadTest-import qualified Test.Langchain.Core.Runnable as RunnableTest-import qualified Test.Langchain.Core.Stream as StreamTest-import qualified Test.Langchain.Core.Tool as ToolTest+import qualified Test.Langchain.Callback as CallbackTest+import qualified Test.Langchain.Model as ModelTest+import qualified Test.Langchain.Monad as MonadTest+import qualified Test.Langchain.Observability as ObservabilityTest+import qualified Test.Langchain.PromptTemplate as PromptTemplateTest+import qualified Test.Langchain.Resilience as ResilienceTest+import qualified Test.Langchain.Retriever as RetrieverTest+import qualified Test.Langchain.Runnable as RunnableTest+import qualified Test.Langchain.Stream as StreamTest+import qualified Test.Langchain.Tool as ToolTest main :: IO () main = defaultMain tests@@ -20,4 +25,9 @@ , StreamTest.tests , ToolTest.tests , MonadTest.tests+ , PromptTemplateTest.tests+ , ObservabilityTest.tests+ , CallbackTest.tests+ , RetrieverTest.tests+ , ResilienceTest.tests ]
+ test/Test/Langchain/Callback.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE OverloadedStrings #-}++module Test.Langchain.Callback (tests) where++import Control.Concurrent.STM (readTVarIO)+import Data.Time.Clock (getCurrentTime)+import Test.Tasty+import Test.Tasty.HUnit++import Langchain.Callback++tests :: TestTree+tests =+ testGroup+ "Langchain.Callback"+ [ 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/Core/Model.hs
@@ -1,60 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Test.Langchain.Core.Model (tests) where--import Test.Tasty-import Test.Tasty.HUnit--import Control.Monad.Except (runExceptT)-import Data.List.NonEmpty (NonEmpty (..))-import qualified Data.Map.Strict as Map--import Langchain.Core.Model-import Test.Langchain.Core.TestModel (TestChatModel (..))--tests :: TestTree-tests =- testGroup- "Langchain.Core.Model"- [ testGroup- "Multi-Modal ContentBlock & Message"- [ testCase "textMessage creates User message with TextBlock" $ do- let msg = userMessage "Hello AI"- messageRole msg @?= User- extractMessageText msg @?= "Hello AI"- messageMetadata msg @?= Map.empty- , testCase "systemMessage creates System message" $ do- let msg = systemMessage "You are a assistant"- messageRole msg @?= System- extractMessageText msg @?= "You are a assistant"- , testCase "imageMessage creates ImageBlock message" $ do- let msg = imageMessage User "image/png" "base64data=="- messageRole msg @?= User- case messageContents msg of- (ImageBlock (ImageContent (ImageBase64 (Just mime) b64) Nothing Nothing) :| []) -> do- mime @?= "image/png"- b64 @?= "base64data=="- _ -> assertFailure "Expected ImageBlock"- ]- , testGroup- "Effect-Polymorphic ChatModel"- [ testCase "invoke returns Assistant response" $ do- let model = TestChatModel "Hello human" "mock-gpt"- input = [userMessage "Hi"]- res <- runExceptT $ invoke model input Nothing- case res of- Left err -> assertFailure $ "Unexpected error: " ++ show err- Right msg -> do- messageRole msg @?= Assistant- extractMessageText msg @?= "Hello human"- , testCase "batch processes multiple inputs sequentially" $ do- let model = TestChatModel "Pong" "mock-gpt"- inputs = [[userMessage "Ping 1"], [userMessage "Ping 2"]]- res <- runExceptT $ batch model inputs Nothing- case res of- Left err -> assertFailure $ "Unexpected error: " ++ show err- Right msgs -> do- length msgs @?= 2- map extractMessageText msgs @?= ["Pong", "Pong"]- ]- ]
− test/Test/Langchain/Core/Monad.hs
@@ -1,30 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Test.Langchain.Core.Monad (tests) where--import Control.Monad.Reader (ask)-import Test.Tasty-import Test.Tasty.HUnit--import Langchain.Core.Error (internalError)-import Langchain.Core.Monad--tests :: TestTree-tests =- testGroup- "Test.Langchain.Core.Monad"- [ testCase "runLangchainT executes pure computations successfully" $ do- res <- runLangchainT () (pure ("hello" :: String))- res @?= Right "hello"- , testCase "runLangchainT propagates errors via throwLangchainError" $ do- res <- runLangchainT () $ do- throwLangchainError (internalError "test fail" Nothing Nothing)- case res of- Left _ -> pure ()- Right _ -> assertFailure "Expected error"- , testCase "runLangchainT threads custom env through ask" $ do- -- Developers use ask / asks from mtl directly with their own r- let customEnv = (42 :: Int)- res <- runLangchainT customEnv ask- res @?= Right 42- ]
− test/Test/Langchain/Core/Runnable.hs
@@ -1,100 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-}--module Test.Langchain.Core.Runnable (tests) where--import Test.Tasty-import Test.Tasty.HUnit-import Test.Tasty.QuickCheck as QC--import Control.Monad.Except (ExceptT, runExceptT)-import qualified Data.Text as T--import Langchain.Core.Error-import Langchain.Core.Runnable--type TestMonad = ExceptT LangchainError IO---- Pure test lambda helpers-addOneLambda :: RunnableTree TestMonad Int Int-addOneLambda = runLambda $ \x -> pure (Right (x + 1))--doubleLambda :: RunnableTree TestMonad Int Int-doubleLambda = runLambda $ \x -> pure (Right (x * 2))--failLambda :: RunnableTree TestMonad Int Int-failLambda = runLambda $ \_ -> pure (Left $ runnableError "Pipeline failed" Nothing Nothing)--tests :: TestTree-tests =- testGroup- "Langchain.Core.Runnable"- [ testGroup- "Identity Laws"- [ testCase "Left Identity: Id |>> t == t" $ do- let t = addOneLambda- r1 <- runExceptT $ interpret (Id |>> t) 5- r2 <- runExceptT $ interpret t 5- r1 @?= Right 6- r1 @?= r2- , testCase "Right Identity: t |>> Id == t" $ do- let t = addOneLambda- r1 <- runExceptT $ interpret (t |>> Id) 5- r2 <- runExceptT $ interpret t 5- r1 @?= Right 6- r1 @?= r2- ]- , testGroup- "Sequential Composition Laws"- [ testCase "Seq executes in left-to-right order" $ do- let pipeline = addOneLambda |>> doubleLambda -- (5 + 1) * 2 = 12- res <- runExceptT $ interpret pipeline 5- res @?= Right 12- , testCase "Seq propagates errors early" $ do- let pipeline = failLambda |>> doubleLambda- res <- runExceptT $ interpret pipeline 5- case res of- Left (RunnableError msg _) -> assertBool "Should contain error message" ("failed" `T.isInfixOf` msg)- _ -> assertFailure "Expected RunnableError"- ]- , testGroup- "Parallel Composition Laws (&>&)"- [ testCase "Par executes branches concurrently" $ do- let pipeline = addOneLambda &>& doubleLambda- res <- runExceptT $ interpret pipeline 10- res @?= Right (11, 20)- ]- , testGroup- "Branching and Fallback"- [ testCase "Branch selects True branch" $ do- let pipeline = Branch (\x -> pure (x > 0)) addOneLambda doubleLambda- res <- runExceptT $ interpret pipeline 5- res @?= Right 6- , testCase "Branch selects False branch" $ do- let pipeline = Branch (\x -> pure (x > 0)) addOneLambda doubleLambda- res <- runExceptT $ interpret pipeline (-5)- res @?= Right (-10)- , testCase "Fallback executes secondary when primary fails" $ do- let pipeline = Fallback failLambda doubleLambda- res <- runExceptT $ interpret pipeline 7- res @?= Right 14- ]- , testGroup- "Property Tests (QuickCheck)"- [ QC.testProperty "Identity Law: Id |>> t(x) == t(x)" $ \(x :: Int) ->- QC.ioProperty $ do- r1 <- runExceptT $ interpret (Id |>> addOneLambda) x- r2 <- runExceptT $ interpret addOneLambda x- pure (r1 == r2)- , QC.testProperty "Associativity: (f |>> g) |>> h == f |>> (g |>> h)" $ \(x :: Int) ->- QC.ioProperty $ do- let f = addOneLambda- g = doubleLambda- h = addOneLambda- p1 = (f |>> g) |>> h- p2 = f |>> (g |>> h)- r1 <- runExceptT $ interpret p1 x- r2 <- runExceptT $ interpret p2 x- pure (r1 == r2)- ]- ]
− test/Test/Langchain/Core/Stream.hs
@@ -1,33 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Test.Langchain.Core.Stream (tests) where--import Test.Tasty-import Test.Tasty.HUnit--import Control.Monad.Except (runExceptT)-import Control.Monad.Trans.Resource (runResourceT)--import Langchain.Core.Model-import Langchain.Core.Stream-import Test.Langchain.Core.TestModel (TestChatModel (..))--tests :: TestTree-tests =- testGroup- "Langchain.Core.Stream"- [ testCase "stream emits LLMStart, LLMChunk, LLMEnd events" $ do- let model = TestChatModel "Streamed content" "mock-gpt"- input = [userMessage "Stream test"]- res <- runResourceT $ runExceptT $ collectEvents (stream model input Nothing)- case res of- Left err -> assertFailure $ "Unexpected stream error: " ++ show err- Right events -> do- length events @?= 3- case events of- [s@LLMStart {}, c@LLMChunk {}, e@LLMEnd {}] -> do- modelName s @?= "mock-gpt"- chunkText c @?= "Streamed content"- extractMessageText (finalMessage e) @?= "Streamed content"- _ -> assertFailure $ "Unexpected event sequence: " ++ show events- ]
− test/Test/Langchain/Core/TestModel.hs
@@ -1,29 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TypeFamilies #-}--module Test.Langchain.Core.TestModel- ( TestChatModel (..)- ) where--import Data.Conduit (yield)-import Data.Text (Text)--import Langchain.Core.Model-import Langchain.Core.Stream (StreamEvent (..))--data TestChatModel = TestChatModel- { testResponse :: Text- , testModelName :: Text- }- deriving (Eq, Show)--instance ChatModel TestChatModel where- type ModelConfig TestChatModel = ()-- invoke model _ _ = pure $ assistantMessage (testResponse model)-- stream model inputMsgs _ = do- let rId = "test-run-id"- yield $ LLMStart rId (testModelName model) inputMsgs- yield $ LLMChunk rId (testResponse model) Nothing- yield $ LLMEnd rId (assistantMessage $ testResponse model) Nothing
− test/Test/Langchain/Core/Tool.hs
@@ -1,39 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-}--module Test.Langchain.Core.Tool (tests) where--import Data.Aeson-import Data.Aeson.Types (parseEither)-import Data.Text (Text)-import Langchain.Core.Error-import Langchain.Core.Tool-import Test.Tasty-import Test.Tasty.HUnit--calcExec :: Value -> IO (Either LangchainError Text)-calcExec (Object _) = pure $ Right "42"-calcExec _ = pure $ Left $ toolError "Invalid arguments" (Just "calc") Nothing--tests :: TestTree-tests =- testGroup- "Langchain.Core.Tool"- [ testCase "createTool initializes tool attributes" $ do- let t = createTool "calculator" "Performs math" (object []) calcExec- toolName t @?= "calculator"- toolDescription t @?= "Performs math"- , testCase "toolToValue generates correct JSON schema structure" $ do- let t = createTool "calculator" "Performs math" (object ["type" .= ("object" :: Text)]) calcExec- val = toolToValue t- case val of- Object o -> do- case parseEither (.: "type") o of- Right ("function" :: Text) -> pure ()- res -> assertFailure $ "Expected type = function, got: " ++ show res- _ -> assertFailure "Expected JSON Object"- , testCase "toolExecute runs successfully on valid input" $ do- let t = createTool "calculator" "Performs math" (object []) calcExec- res <- toolExecute t (object [])- res @?= Right "42"- ]
+ test/Test/Langchain/Model.hs view
@@ -0,0 +1,60 @@+{-# LANGUAGE OverloadedStrings #-}++module Test.Langchain.Model (tests) where++import Test.Tasty+import Test.Tasty.HUnit++import Control.Monad.Except (runExceptT)+import Data.List.NonEmpty (NonEmpty (..))+import qualified Data.Map.Strict as Map++import Langchain.Model+import Test.Langchain.TestModel (TestChatModel (..))++tests :: TestTree+tests =+ testGroup+ "Langchain.Model"+ [ testGroup+ "Multi-Modal ContentBlock & Message"+ [ testCase "textMessage creates User message with TextBlock" $ do+ let msg = userMessage "Hello AI"+ messageRole msg @?= User+ extractMessageText msg @?= "Hello AI"+ messageMetadata msg @?= Map.empty+ , testCase "systemMessage creates System message" $ do+ let msg = systemMessage "You are a assistant"+ messageRole msg @?= System+ extractMessageText msg @?= "You are a assistant"+ , testCase "imageMessage creates ImageBlock message" $ do+ let msg = imageMessage User "image/png" "base64data=="+ messageRole msg @?= User+ case messageContents msg of+ (ImageBlock (ImageContent (ImageBase64 (Just mime) b64) Nothing Nothing) :| []) -> do+ mime @?= "image/png"+ b64 @?= "base64data=="+ _ -> assertFailure "Expected ImageBlock"+ ]+ , testGroup+ "Effect-Polymorphic ChatModel"+ [ testCase "invoke returns Assistant response" $ do+ let model = TestChatModel "Hello human" "mock-gpt"+ input = [userMessage "Hi"]+ res <- runExceptT $ invoke model input Nothing+ case res of+ Left err -> assertFailure $ "Unexpected error: " ++ show err+ Right msg -> do+ messageRole msg @?= Assistant+ extractMessageText msg @?= "Hello human"+ , testCase "batch processes multiple inputs sequentially" $ do+ let model = TestChatModel "Pong" "mock-gpt"+ inputs = [[userMessage "Ping 1"], [userMessage "Ping 2"]]+ res <- runExceptT $ batch model inputs Nothing+ case res of+ Left err -> assertFailure $ "Unexpected error: " ++ show err+ Right msgs -> do+ length msgs @?= 2+ map extractMessageText msgs @?= ["Pong", "Pong"]+ ]+ ]
+ test/Test/Langchain/Monad.hs view
@@ -0,0 +1,30 @@+{-# LANGUAGE OverloadedStrings #-}++module Test.Langchain.Monad (tests) where++import Control.Monad.Reader (ask)+import Test.Tasty+import Test.Tasty.HUnit++import Langchain.Error (internalError)+import Langchain.Monad++tests :: TestTree+tests =+ testGroup+ "Test.Langchain.Monad"+ [ testCase "runLangchainT executes pure computations successfully" $ do+ res <- runLangchainT () (pure ("hello" :: String))+ res @?= Right "hello"+ , testCase "runLangchainT propagates errors via throwLangchainError" $ do+ res <- runLangchainT () $ do+ throwLangchainError (internalError "test fail" Nothing Nothing)+ case res of+ Left _ -> pure ()+ Right _ -> assertFailure "Expected error"+ , testCase "runLangchainT threads custom env through ask" $ do+ -- Developers use ask / asks from mtl directly with their own r+ let customEnv = (42 :: Int)+ res <- runLangchainT customEnv ask+ res @?= Right 42+ ]
+ test/Test/Langchain/Observability.hs view
@@ -0,0 +1,79 @@+{-# LANGUAGE OverloadedStrings #-}++module Test.Langchain.Observability (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 "Agent" "Model rate limit approaching"+ logError logHandler "Agent" "Tool execution failed"++ logs <- getInMemoryLogs logger+ length logs @?= 3+ map logLevel logs @?= [InfoLevel, WarnLevel, ErrorLevel]+ , 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 customMeta = Map.fromList [("runId", "run-123"), ("attempt", "1")]+ logEvent logHandler DebugLevel "LLM" "Prompt sent" customMeta+ logs <- getInMemoryLogs logger+ case logs of+ [ev] -> do+ logComponent ev @?= "LLM"+ logMetadata ev @?= customMeta+ _ -> assertFailure "Expected exactly 1 log"+ ]+ , testGroup+ "OpenTelemetry Tracing"+ [ testCase "withSpan wraps computation, records duration and Ok status" $ do+ tracer <- newOTelTracer (Just "trace-unit-1")+ res <-+ runExceptT $+ withSpan tracer "test-operation" Nothing InternalSpan Map.empty (pure ("success" :: String))+ res @?= Right "success"++ spans <- getSpans tracer+ case spans of+ [sp] -> do+ spanName sp @?= "test-operation"+ spanTraceId sp @?= "trace-unit-1"+ spanStatus sp @?= StatusOk+ isJust (spanEndTime sp) @? "Expected span end time"+ isJust (spanDurationMicros sp) @? "Expected duration"+ _ -> assertFailure "Expected exactly 1 span"+ , testCase "exportSpansJson exports valid JSON formatted trace" $ do+ tracer <- newOTelTracer (Just "trace-unit-2")+ _ <- runExceptT $ withSpan tracer "child-span" Nothing ClientSpan Map.empty (pure (42 :: Int))+ jsonOutput <- exportSpansJson tracer+ "child-span" `T.isInfixOf` jsonOutput @? "Expected JSON to contain span name"+ "trace-unit-2" `T.isInfixOf` jsonOutput @? "Expected JSON to contain trace ID"+ ]+ ]
+ test/Test/Langchain/PromptTemplate.hs view
@@ -0,0 +1,68 @@+{-# LANGUAGE OverloadedStrings #-}++module Test.Langchain.PromptTemplate (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+import Langchain.Runnable (invoke)++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")])+ , testCase "partialPromptTemplate binds partial variables" $+ let pt = fromTemplate "Hello, {name}! Welcome to {place}."+ ptPartial = partialPromptTemplate pt (Map.singleton "place" "Wonderland")+ in renderPrompt ptPartial (Map.singleton "name" "Alice")+ @?= Right "Hello, Alice! Welcome to Wonderland."+ , testCase "Runnable instance invokes successfully" $ do+ res <- invoke greetingTemplate vars+ res @?= Right "Hello, Alice! Welcome to Wonderland."+ ]+ 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/Resilience.hs view
@@ -0,0 +1,65 @@+{-# LANGUAGE OverloadedStrings #-}++module Test.Langchain.Resilience (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.Error (internalError)+import Langchain.Resilience++tests :: TestTree+tests =+ testGroup+ "Langchain.Resilience"+ [ testGroup+ "CircuitBreaker"+ [ 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"+ ]+ , testGroup+ "Retry"+ [ 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.hs view
@@ -0,0 +1,82 @@+{-# LANGUAGE OverloadedStrings #-}++module Test.Langchain.Retriever (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.Retriever++data DummyRetriever = DummyRetriever+ deriving (Show, Eq)++instance Retriever DummyRetriever where+ getRelevantDocuments _ query =+ pure [Document (TL.fromStrict $ query <> " result") Map.empty]++tests :: TestTree+tests =+ testGroup+ "Langchain.Retriever"+ [ testGroup+ "Core"+ [ 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"]+ ]+ , testGroup+ "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"+ ]+ , testGroup+ "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)]+ 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/Runnable.hs view
@@ -0,0 +1,100 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Test.Langchain.Runnable (tests) where++import Test.Tasty+import Test.Tasty.HUnit+import Test.Tasty.QuickCheck as QC++import Control.Monad.Except (ExceptT, runExceptT)+import qualified Data.Text as T++import Langchain.Error+import Langchain.Runnable++type TestMonad = ExceptT LangchainError IO++-- Pure test lambda helpers+addOneLambda :: RunnableTree TestMonad Int Int+addOneLambda = runLambda $ \x -> pure (Right (x + 1))++doubleLambda :: RunnableTree TestMonad Int Int+doubleLambda = runLambda $ \x -> pure (Right (x * 2))++failLambda :: RunnableTree TestMonad Int Int+failLambda = runLambda $ \_ -> pure (Left $ runnableError "Pipeline failed" Nothing Nothing)++tests :: TestTree+tests =+ testGroup+ "Langchain.Runnable"+ [ testGroup+ "Identity Laws"+ [ testCase "Left Identity: Id |>> t == t" $ do+ let t = addOneLambda+ r1 <- runExceptT $ interpret (Id |>> t) 5+ r2 <- runExceptT $ interpret t 5+ r1 @?= Right 6+ r1 @?= r2+ , testCase "Right Identity: t |>> Id == t" $ do+ let t = addOneLambda+ r1 <- runExceptT $ interpret (t |>> Id) 5+ r2 <- runExceptT $ interpret t 5+ r1 @?= Right 6+ r1 @?= r2+ ]+ , testGroup+ "Sequential Composition Laws"+ [ testCase "Seq executes in left-to-right order" $ do+ let pipeline = addOneLambda |>> doubleLambda -- (5 + 1) * 2 = 12+ res <- runExceptT $ interpret pipeline 5+ res @?= Right 12+ , testCase "Seq propagates errors early" $ do+ let pipeline = failLambda |>> doubleLambda+ res <- runExceptT $ interpret pipeline 5+ case res of+ Left (RunnableError msg _) -> assertBool "Should contain error message" ("failed" `T.isInfixOf` msg)+ _ -> assertFailure "Expected RunnableError"+ ]+ , testGroup+ "Parallel Composition Laws (&>&)"+ [ testCase "Par executes branches concurrently" $ do+ let pipeline = addOneLambda &>& doubleLambda+ res <- runExceptT $ interpret pipeline 10+ res @?= Right (11, 20)+ ]+ , testGroup+ "Branching and Fallback"+ [ testCase "Branch selects True branch" $ do+ let pipeline = Branch (\x -> pure (x > 0)) addOneLambda doubleLambda+ res <- runExceptT $ interpret pipeline 5+ res @?= Right 6+ , testCase "Branch selects False branch" $ do+ let pipeline = Branch (\x -> pure (x > 0)) addOneLambda doubleLambda+ res <- runExceptT $ interpret pipeline (-5)+ res @?= Right (-10)+ , testCase "Fallback executes secondary when primary fails" $ do+ let pipeline = Fallback failLambda doubleLambda+ res <- runExceptT $ interpret pipeline 7+ res @?= Right 14+ ]+ , testGroup+ "Property Tests (QuickCheck)"+ [ QC.testProperty "Identity Law: Id |>> t(x) == t(x)" $ \(x :: Int) ->+ QC.ioProperty $ do+ r1 <- runExceptT $ interpret (Id |>> addOneLambda) x+ r2 <- runExceptT $ interpret addOneLambda x+ pure (r1 == r2)+ , QC.testProperty "Associativity: (f |>> g) |>> h == f |>> (g |>> h)" $ \(x :: Int) ->+ QC.ioProperty $ do+ let f = addOneLambda+ g = doubleLambda+ h = addOneLambda+ p1 = (f |>> g) |>> h+ p2 = f |>> (g |>> h)+ r1 <- runExceptT $ interpret p1 x+ r2 <- runExceptT $ interpret p2 x+ pure (r1 == r2)+ ]+ ]
+ test/Test/Langchain/Stream.hs view
@@ -0,0 +1,33 @@+{-# LANGUAGE OverloadedStrings #-}++module Test.Langchain.Stream (tests) where++import Test.Tasty+import Test.Tasty.HUnit++import Control.Monad.Except (runExceptT)+import Control.Monad.Trans.Resource (runResourceT)++import Langchain.Model+import Langchain.Stream+import Test.Langchain.TestModel (TestChatModel (..))++tests :: TestTree+tests =+ testGroup+ "Langchain.Stream"+ [ testCase "stream emits LLMStart, LLMChunk, LLMEnd events" $ do+ let model = TestChatModel "Streamed content" "mock-gpt"+ input = [userMessage "Stream test"]+ res <- runResourceT $ runExceptT $ collectEvents (stream model input Nothing)+ case res of+ Left err -> assertFailure $ "Unexpected stream error: " ++ show err+ Right events -> do+ length events @?= 3+ case events of+ [s@LLMStart {}, c@LLMChunk {}, e@LLMEnd {}] -> do+ modelName s @?= "mock-gpt"+ chunkText c @?= "Streamed content"+ extractMessageText (finalMessage e) @?= "Streamed content"+ _ -> assertFailure $ "Unexpected event sequence: " ++ show events+ ]
+ test/Test/Langchain/TestModel.hs view
@@ -0,0 +1,29 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeFamilies #-}++module Test.Langchain.TestModel+ ( TestChatModel (..)+ ) where++import Data.Conduit (yield)+import Data.Text (Text)++import Langchain.Model+import Langchain.Stream (StreamEvent (..))++data TestChatModel = TestChatModel+ { testResponse :: Text+ , testModelName :: Text+ }+ deriving (Eq, Show)++instance ChatModel TestChatModel where+ type ModelConfig TestChatModel = ()++ invoke model _ _ = pure $ assistantMessage (testResponse model)++ stream model inputMsgs _ = do+ let rId = "test-run-id"+ yield $ LLMStart rId (testModelName model) inputMsgs+ yield $ LLMChunk rId (testResponse model) Nothing+ yield $ LLMEnd rId (assistantMessage $ testResponse model) Nothing
+ test/Test/Langchain/Tool.hs view
@@ -0,0 +1,39 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Test.Langchain.Tool (tests) where++import Data.Aeson+import Data.Aeson.Types (parseEither)+import Data.Text (Text)+import Langchain.Error+import Langchain.Tool+import Test.Tasty+import Test.Tasty.HUnit++calcExec :: Value -> IO (Either LangchainError Text)+calcExec (Object _) = pure $ Right "42"+calcExec _ = pure $ Left $ toolError "Invalid arguments" (Just "calc") Nothing++tests :: TestTree+tests =+ testGroup+ "Langchain.Tool"+ [ testCase "createTool initializes tool attributes" $ do+ let t = createTool "calculator" "Performs math" (object []) calcExec+ toolName t @?= "calculator"+ toolDescription t @?= "Performs math"+ , testCase "toolToValue generates correct JSON schema structure" $ do+ let t = createTool "calculator" "Performs math" (object ["type" .= ("object" :: Text)]) calcExec+ val = toolToValue t+ case val of+ Object o -> do+ case parseEither (.: "type") o of+ Right ("function" :: Text) -> pure ()+ res -> assertFailure $ "Expected type = function, got: " ++ show res+ _ -> assertFailure "Expected JSON Object"+ , testCase "toolExecute runs successfully on valid input" $ do+ let t = createTool "calculator" "Performs math" (object []) calcExec+ res <- toolExecute t (object [])+ res @?= Right "42"+ ]