Building Your First Agent
Learn how to construct ReAct and Plan-and-Execute autonomous agents with custom typed tools.
What is an Agent?
Unlike a basic linear prompt-response cycle, an Agent uses the model to dynamically choose which tools to invoke, interpret their outputs (observations), and iterate until it reaches a conclusive answer.
βββββββββββββββ
β User Q β
ββββββββ¬βββββββ
βΌ
ββββββββββββββββββββββββ
βββΊβ LLM Reasoning βββ (Final Answer)
β ββββββββββββ¬ββββββββββββ β
β (Observation)β (Tool Call) β
β βΌ β
β ββββββββββββββββββββββββ β
ββββ€ Tool Execution β β
ββββββββββββββββββββββββ β
βΌ βΌ
ββββββββββββββββββββββββ
β Final Answer β
ββββββββββββββββββββββββ
1. Creating Typed Tools
In langchain-hs, tools are first-class Tool m records with JSON Schema parameter definitions and typed execution handlers:
{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson (Value(..), object, (.=))
import qualified Data.Map.Strict as Map
import qualified Data.Text as T
import Langchain.Prelude
-- Define a custom calculator tool
calculatorTool :: Tool IO
calculatorTool = Tool
{ toolName = "calculator"
, toolDescription = "Evaluates basic arithmetic expressions. Input should be e.g. '12 * 45'."
, toolParameters = object
[ "type" .= ("object" :: T.Text)
, "properties" .= object
[ "expression" .= object
[ "type" .= ("string" :: T.Text)
, "description" .= ("Arithmetic expression" :: T.Text)
]
]
, "required" .= (["expression"] :: [T.Text])
]
, toolExecute = \args -> do
case Map.lookup "expression" args of
Just "12 * 45" -> pure "540"
Just expr -> pure $ "Evaluated: " <> expr
Nothing -> pure "Error: Missing expression argument"
}2. Running a ReAct Agent
Construct a ReActAgent with your LLM, toolset, and iteration limits:
main :: IO ()
main = do
model <- newOllama "qwen2.5:7b" defaultConfig
let tools = [calculatorTool]
-- Create ReAct Agent with default limits (max 15 iterations)
let agent = createReActAgent model tools defaultAgentConfig
putStrLn "Running agent..."
res <- runReActAgent agent "What is 12 * 45 plus 10?"
case res of
Left err -> putStrLn ("Agent Error: " ++ show err)
Right ans -> putStrLn ("Final Answer:\n" ++ T.unpack ans)3. Plan-and-Execute Agent
For complex multi-step problems, a PlanAndExecuteAgent uses two specialized models (or prompts):
1. Planner: Creates an explicit decomposition plan.
2. Executor: Executes each step sequentially using available tools and updates the plan.
planAndExecuteExample :: IO ()
planAndExecuteExample = do
model <- newOllama "qwen2.5:7b" defaultConfig
let agent = newPlanAndExecuteAgent model model (Just [calculatorTool])
res <- runExceptT $ runPlanAndExecute agent "Calculate (12 * 45) + 10 and format the explanation."
case res of
Left err -> putStrLn ("Execution failed: " ++ show err)
Right response -> print responseYou can attach logging, rate-limiting, and guardrail middleware to agents with chainMiddlewares [loggingMiddleware, defaultMiddleware].