packages feed

langchain-hs-0.0.6.2: test/Test/Langchain/Agent/AdvancedAgentsSpec.hs

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}

module Test.Langchain.Agent.AdvancedAgentsSpec (tests) where

import Control.Monad.Except (ExceptT, runExceptT)
import Control.Monad.IO.Class (liftIO)
import Data.IORef (modifyIORef', newIORef, readIORef)
import qualified Data.Text as T
import Test.Tasty
import Test.Tasty.HUnit

import Langchain.Agent.PlanAndExecute
import Langchain.Error (LangchainError)
import Langchain.Stream (StreamEvent (ChainEnd, ChainStart, NodeEnd, NodeStart))
import Test.Langchain.Provider.Mock (newMockModel)

tests :: TestTree
tests =
  testGroup
    "Langchain.Agent.AdvancedAgentsSpec"
    [ testCase "PlanAndExecuteAgent plans with JSON structured output and executes sequentially" $ do
        let planner =
              newMockModel
                "{\"planSteps\": [{\"stepNumber\": 1, \"stepDescription\": \"Research Haskell\"}, {\"stepNumber\": 2, \"stepDescription\": \"Write code\"}, {\"stepNumber\": 3, \"stepDescription\": \"Run tests\"}]}"
            executor = newMockModel "Executed step successfully."
            agent = newPlanAndExecuteAgent planner executor Nothing
        res <- runExceptT $ runPlanAndExecute agent "Build a Haskell library"
        case res of
          Left err -> assertFailure ("PlanAndExecute failed: " ++ show err)
          Right ans -> ans @?= "Executed step successfully."
    , testCase "PlanAndExecuteAgent executes agent step executor" $ do
        let planner = newMockModel "{\"planSteps\": [{\"stepNumber\": 1, \"stepDescription\": \"Calculate sum\"}]}"
            agentExecutor :: T.Text -> ExceptT LangchainError IO T.Text
            agentExecutor _ = pure "Result: 42"
            agent = newPlanAndExecuteAgent planner agentExecutor Nothing
        res <- runExceptT $ runPlanAndExecute agent "Compute answer"
        case res of
          Left err -> assertFailure ("PlanAndExecute failed: " ++ show err)
          Right ans -> ans @?= "Result: 42"
    , testCase "runPlanAndExecuteStream emits Chain and Node lifecycle events" $ do
        let planner =
              newMockModel
                "{\"planSteps\": [{\"stepNumber\": 1, \"stepDescription\": \"Step one\"}]}"
            executor = newMockModel "Streamed plan result."
            agent = newPlanAndExecuteAgent planner executor Nothing
        eventsRef <- newIORef ([] :: [StreamEvent])
        res <- runExceptT $
          runPlanAndExecuteStream agent "Solve goal" $ \ev ->
            liftIO $ modifyIORef' eventsRef (++ [ev])
        case res of
          Left err -> assertFailure ("Stream PlanAndExecute failed: " ++ show err)
          Right ans -> do
            ans @?= "Streamed plan result."
            events <- readIORef eventsRef
            assertBool "Has ChainStart" (any (\case ChainStart {} -> True; _ -> False) events)
            assertBool "Has NodeStart" (any (\case NodeStart {} -> True; _ -> False) events)
            assertBool "Has NodeEnd" (any (\case NodeEnd {} -> True; _ -> False) events)
            assertBool "Has ChainEnd" (any (\case ChainEnd {} -> True; _ -> False) events)
    ]