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 ChatGoogleGenerativeAIfrom langchain.agents import tool, create_react_agentimport datetimefrom langchain_community.tools import TavilySearchResultsfrom langchain import hubfrom dotenv import load_dotenvload_dotenv()import oskey = 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')@tooldef 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_timetools = [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 operatorfrom typing import Annotated, TypedDict, Unionfrom langchain_core.agents import AgentAction, AgentFinishclass 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_dotenvfrom agent_reason_runnable import react_agent_runnable, toolsfrom react_state import AgentStateload_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_dotenvload_dotenv()from langchain_core.agents import AgentFinish, AgentActionfrom langgraph.graph import END, StateGraphfrom nodes import reason_node, act_nodefrom react_state import AgentStateREASON_NODE = "reason_node"ACT_NODE = "act_node"def should_continue(state: AgentState) -> str: if isinstance(state["agent_outcome"], AgentFinish): return END else: return ACT_NODEgraph = 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")