packages feed

langchain-hs-0.0.5.0: src/Langchain/Retriever/Core.hs

{-# LANGUAGE FlexibleContexts #-}

{- |
Module      : Langchain.Retriever.Core
Description : Retrieval mechanism implementation for LangChain Haskell
Copyright   : (c) 2025-2026 Tushar Adhatrao
License     : MIT
Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>
Stability   : experimental

Effect-polymorphic document retrieval abstraction.
-}
module Langchain.Retriever.Core
  ( Retriever (..)
  , VectorStoreRetriever (..)
  , retrieveWithCallbacks
  , runRetriever
  ) where

import Control.Monad.Except (MonadError, runExceptT)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.Text (Text)
import qualified Data.Text.Lazy as TL
import Data.Time.Clock (diffUTCTime, getCurrentTime)

import Langchain.Callback.Manager (CallbackEvent (..), CallbackManager, dispatchEvent)
import Langchain.Core.Error (LangchainError)
import Langchain.Core.Runnable (RunnableTree, runLambda)
import Langchain.DocumentLoader.Core (Document (..))
import Langchain.VectorStore.Core (VectorStore, similaritySearch)

-- | Effect-polymorphic Retriever typeclass
class Retriever a where
  getRelevantDocuments ::
    (MonadIO m, MonadError LangchainError m) =>
    a ->
    Text ->
    m [Document]

-- | Vector store-backed retriever
newtype VectorStore a => VectorStoreRetriever a = VectorStoreRetriever {vs :: a}
  deriving (Eq, Show)

instance VectorStore a => Retriever (VectorStoreRetriever a) where
  getRelevantDocuments (VectorStoreRetriever v) query = similaritySearch v query 5

-- | Retrieve documents with lifecycle callbacks dispatched to CallbackManager
retrieveWithCallbacks ::
  (Retriever a, MonadIO m, MonadError LangchainError m) =>
  CallbackManager ->
  Text ->
  a ->
  Text ->
  m [Document]
retrieveWithCallbacks mgr name ret query = do
  start <- liftIO getCurrentTime
  dispatchEvent mgr (OnRetrieverStart name query start)
  docs <- getRelevantDocuments ret query
  end <- liftIO getCurrentTime
  let durMicros = round (diffUTCTime end start * 1000000)
  dispatchEvent mgr (OnRetrieverEnd name (map (TL.toStrict . pageContent) docs) durMicros end)
  pure docs

-- | Lift any 'Retriever' into a 'Text' -> '[Document]' pipeline step in a 'RunnableTree'.
runRetriever :: (Retriever a, MonadIO m) => a -> RunnableTree m Text [Document]
runRetriever ret = runLambda $ \query -> runExceptT (getRelevantDocuments ret query)