packages feed

langchain-hs-0.0.5.0: test/Test/Langchain/Property/RunnableSpec.hs

{-# LANGUAGE OverloadedStrings #-}

module Test.Langchain.Property.RunnableSpec (tests) where

import Control.Monad.Except (ExceptT, runExceptT)
import Data.Text (Text)
import Test.QuickCheck
import Test.Tasty
import Test.Tasty.QuickCheck

import Langchain.Core.Error
import Langchain.Core.Runnable

type PureMonad = ExceptT LangchainError IO

tests :: TestTree
tests =
  testGroup
    "Langchain.Property.RunnableSpec (QuickCheck)"
    [ testProperty "Left Identity: Id |>> t(x) == t(x)" $
        \n -> ioProperty $ do
          let step :: RunnableTree PureMonad Int Int
              step = runLambda (\i -> pure $ Right (i * 2))
              pipeline = Id |>> step
          res1 <- runExceptT $ interpret pipeline n
          res2 <- runExceptT $ interpret step n
          pure (res1 === res2)
    , testProperty "Right Identity: t(x) |>> Id == t(x)" $
        \n -> ioProperty $ do
          let step :: RunnableTree PureMonad Int Int
              step = runLambda (\i -> pure $ Right (i + 10))
              pipeline = step |>> Id
          res1 <- runExceptT $ interpret pipeline n
          res2 <- runExceptT $ interpret step n
          pure (res1 === res2)
    , testProperty "Associativity: ((f |>> g) |>> h) == (f |>> (g |>> h))" $
        \n -> ioProperty $ do
          let f :: RunnableTree PureMonad Int Int
              f = runLambda (\i -> pure $ Right (i + 1))
              g :: RunnableTree PureMonad Int Int
              g = runLambda (\i -> pure $ Right (i * 3))
              h :: RunnableTree PureMonad Int Int
              h = runLambda (\i -> pure $ Right (i - 5))

              p1 = (f |>> g) |>> h
              p2 = f |>> (g |>> h)
          res1 <- runExceptT $ interpret p1 n
          res2 <- runExceptT $ interpret p2 n
          pure (res1 === res2)
    , testProperty "Branch selects correct branch based on predicate" $
        \n -> ioProperty $ do
          let isPositive :: Int -> PureMonad Bool
              isPositive i = pure (i > 0)
              thenBranch :: RunnableTree PureMonad Int Text
              thenBranch = runLambda (\_ -> pure $ Right "POSITIVE")
              elseBranch :: RunnableTree PureMonad Int Text
              elseBranch = runLambda (\_ -> pure $ Right "NON-POSITIVE")
              branchTree = Branch isPositive thenBranch elseBranch
          res <- runExceptT $ interpret branchTree n
          let expected = if n > 0 then Right "POSITIVE" else Right "NON-POSITIVE"
          pure (res === expected)
    , testProperty "Fallback executes fallback branch on primary error" $
        \n -> ioProperty $ do
          let failingTree :: RunnableTree PureMonad Int Int
              failingTree = runLambda (\_ -> pure $ Left $ internalError "Failed" Nothing Nothing)
              fallbackTree :: RunnableTree PureMonad Int Int
              fallbackTree = runLambda (\i -> pure $ Right (i + 100))
              pipeline = Fallback failingTree fallbackTree
          res <- runExceptT $ interpret pipeline n
          pure (res === Right (n + 100))
    , testProperty "Parallel composition (&>&) produces pair output" $
        \n -> ioProperty $ do
          let doubleStep :: RunnableTree PureMonad Int Int
              doubleStep = runLambda (\i -> pure $ Right (i * 2))
              tripleStep :: RunnableTree PureMonad Int Int
              tripleStep = runLambda (\i -> pure $ Right (i * 3))
              parallelTree = doubleStep &>& tripleStep
          res <- runExceptT $ interpret parallelTree n
          pure (res === Right (n * 2, n * 3))
    ]