• Think: LLM first thinks about the user prompt/problem
  • Action: LLM decides if it can answer by itself or if it should use a tool
  • Action Input: LLM provides the input argument for the tool Here, LangChain executes the tool and returns the output to the LLM
  • Observer: LLM observe the result of the tool
  • The above goes in a loop until a proper response is ready..
  • Final Answer: This is your final answer.

LangChain

  • In LangChain, we used initialize_agent as an all-in-one solution.
agent = initialize_agent(tools=tools, llm=llm, agent="zero-shot-react-description", verbose=True)
  • It combines two key components:
    1. create_react_agent
    2. AgentExecutor (We will eliminate the need for this in LangGraph)

1. create_react_agent: (One that creates the agent)

- Takes each tool's name and description.
- Formats them into a standardized way the LLM can understand.
- Inserts them into specific placeholders in the ReAct prompt template.
- It makes the LLM call + takes the LLMs response + parses it.
```txt
Thought: I need to find out when SpaceX's last launch was and calculate days since.
Action: search
Action Input: "SpaceX most resent launch date"
```
- It parses the response of the LLM into one of these two classes: **AgentAction** and **AgentFinish**.

AgentAction

  • This is a LangChain class that represents an action the agent wants to take. It typically contains:
action = AgentAction(
	tool = "calculator",
	tool_input = "2+2",
	log = "I need to calculate the sum of 2 and 2"
)

AgentFinist

  • This represents the agent completing its task with a final answer. It typically contains:
finish = AgentFinish(
	return_values = {"output": "The answer is 4"},
	log = "I've calculated 2+2 and determinined the answer is 4".
)

2. AgentExecutor

  • Takes the agent from create_react_agent and manages the execution loop.
  • Receives the user’s question and feeds it to the agent.
  • Identifies which tool to run based on the agent’s output (AgentActon or No tool if AgentFinish).
  • Executes the tool and captures the result.
  • Feeds the result back to the agent for the next decision.
  • Continues this loop until the agent produces an AgentFinish.
  • Returns the final answer to the user.

Disadvantage of LangChain implementation of ReAct

  • If the proper tool is not provided to the AgentExecutor, it goes into infinite looping problem.

LangGraph

  • LangGraph turns the hidden “black box” loop into a visible, editable workflow.
  • You can add custom nodes, modify the flow, or insert additional logic.

  • The “reason” node does what create_react_agent did - it thinks and decides.
  • If the reason node output an AgentAction, then “act” node executes the tool.
  • Results from the tool flow back to “reason” node for the next decision.
  • When the agent has the final answer, it takes the right path to “end”.
  • This visualization makes the “black box” of AgentExecutor transparent and modifiable.

Implementation

#############################################################################
# agent_reason_runnable.py
#############################################################################
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain.agents import tool, create_react_agent
import datetime
from langchain_community.tools import TavilySearchResults
from langchain import hub
from dotenv import load_dotenv
load_dotenv()
 
import os
key = os.getenv("GOOGLE_API_KEY")
 
# llm = ChatGoogleGenerativeAI(model='gemini-1.5-pro', api_key=api_key)
llm = ChatGoogleGenerativeAI(model='gemini-2.0-flash', api_key = key)
 
search_tool = TavilySearchResults(search_depth='basic')
 
 
@tool
def get_system_time(format: str = "%Y-%m-%d %H:%M:%S"):
    """Returns the current system time formatted according to the provided format string."""
    current_time = datetime.datetime.now()
    formatted_time = current_time.strftime(format)
    return formatted_time
 
 
tools = [search_tool, get_system_time]
 
react_prompt = hub.pull("hwchase17/react")
 
react_agent_runnable = create_react_agent(tools=tools, llm=llm, prompt=react_prompt)
 
 
#############################################################################
# react_state.py
#############################################################################
import operator
from typing import Annotated, TypedDict, Union
 
from langchain_core.agents import AgentAction, AgentFinish
 
class AgentState(TypedDict):
    input: str
    agent_outcome: Union[AgentAction, AgentFinish, None]
    intermediate_steps: Annotated[list[tuple[AgentAction, str]], operator.add]
 
#############################################################################
# nodes.py
#############################################################################
from dotenv import load_dotenv
 
from agent_reason_runnable import react_agent_runnable, tools
from react_state import AgentState
 
load_dotenv()
 
def reason_node(state: AgentState):
    agent_outcome = react_agent_runnable.invoke(state)
    return {"agent_outcome": agent_outcome}
 
 
def act_node(state: AgentState):
    agent_action = state["agent_outcome"]
 
    # Extract tool name and input from AgentAction
    tool_name = agent_action.tool
    tool_input = agent_action.tool_input
 
    # Find the matching tool function
    tool_function = None
    for tool in tools:
        if tool.name == tool_name:
            tool_function = tool
            break
 
    # Execute the tool with the input
    if tool_function:
        if isinstance(tool_input, dict):
            output = tool_function.invoke(**tool_input)
        else:
            output = tool_function.invoke(tool_input)
    else:
        output = f"Tool '{tool_name}' not found"
 
    return {"intermediate_steps": [(agent_action, str(output))]}
 
 
#############################################################################
# react_graph.py
#############################################################################
from dotenv import load_dotenv
 
load_dotenv()
 
from langchain_core.agents import AgentFinish, AgentAction
from langgraph.graph import END, StateGraph
 
from nodes import reason_node, act_node
from react_state import AgentState
 
REASON_NODE = "reason_node"
ACT_NODE = "act_node"
 
def should_continue(state: AgentState) -> str:
    if isinstance(state["agent_outcome"], AgentFinish):
        return END
    else:
        return ACT_NODE
 
graph = StateGraph(AgentState)
 
graph.add_node(REASON_NODE, reason_node)
graph.set_entry_point(REASON_NODE)
 
graph.add_node(ACT_NODE, act_node)
 
graph.add_conditional_edges(
    REASON_NODE,
    should_continue
)
 
graph.add_edge(ACT_NODE, REASON_NODE)
 
app = graph.compile()
 
result = app.invoke(
    {
        "input": "How many days ago was the latest SpaceX launch?",
    }
)
 
print(result)
print("########################################################################")
print(result["agent_outcome"].return_values["output"], "final result")