langchain-hs-0.0.5.0: test/Test/Langchain/Retriever/HybridSpec.hs
{-# LANGUAGE OverloadedStrings #-}
module Test.Langchain.Retriever.HybridSpec (tests) where
import qualified Data.Map.Strict as Map
import Test.Tasty
import Test.Tasty.HUnit
import Langchain.DocumentLoader.Core (Document (..))
import Langchain.Retriever.BM25 (newBM25Index)
import Langchain.Retriever.Hybrid
tests :: TestTree
tests =
testGroup
"Langchain.Retriever.Hybrid"
[ testCase "reciprocalRankFusion prioritizes documents appearing in both lists" $ do
let docA = Document {pageContent = "Document A about quantum algorithms", metadata = Map.empty}
docB = Document {pageContent = "Document B about classical physics", metadata = Map.empty}
docC = Document {pageContent = "Document C about neural networks", metadata = Map.empty}
denseList = [docA, docB]
sparseList = [docC, docA]
fused = reciprocalRankFusion 60.0 [(denseList, 1.0), (sparseList, 1.0)]
-- docA appears in both dense (rank 1) and sparse (rank 2) -> highest combined score
length fused @?= 3
case fused of
((topDoc, _) : _) -> topDoc @?= docA
[] -> assertFailure "Expected non-empty fused results"
, testCase "searchHybrid executes dense and sparse searches" $ do
let doc1 = Document {pageContent = "Haskell state monad and effects", metadata = Map.empty}
doc2 = Document {pageContent = "Rust borrow checker and lifetimes", metadata = Map.empty}
bm25 = newBM25Index [doc1, doc2]
mockVecSearch _ _ = pure [doc2, doc1]
hybrid = newHybridRetriever bm25 mockVecSearch
results <- searchHybrid hybrid "Haskell" 2
length results @?= 2
case results of
(topDoc : _) -> topDoc @?= doc1
[] -> assertFailure "Expected non-empty results"
]