Skip to main content

What is a ReAct agent?

A ReAct agent (Reason + Act) is an AI agent architecture where the LLM alternates between reasoning steps and action steps — calling tools, observing results, and reasoning again — until it reaches a final answer. ReAct was introduced in the paper "ReAct: Synergizing Reasoning and Acting in Language Models" by Yao et al. (2022, arXiv:2210.03629) and is now the default architecture for most tool-calling AI agents.

The name combines Reasoning and Acting. The key insight: LLMs are better at completing tasks when they can interleave reasoning ("I need to find the current weather") with actions ("call get_weather('Tokyo')") rather than planning everything upfront.

How the ReAct loop works

User input

[Reason] LLM thinks: "I need the weather in Tokyo. I'll call get_weather."

[Act] Agent calls: get_weather(city="Tokyo")

[Observe] Result: "22°C, sunny"

[Reason] LLM thinks: "I have the answer. I can now respond."

Final answer to user

Each cycle is a "thought → action → observation" triplet. The loop continues until the LLM decides it has enough information to respond, or a recursion limit is hit.

ReAct vs plain LLM call

Plain LLM callReAct agent
Steps1Multiple (until done)
Tool callsNoYes
Can look things upNoYes
Can write and run codeNoYes
Handles multi-step tasksNoYes
Stops when?After one responseWhen goal is met or limit hit

Build a ReAct agent in Python

AgentFlow ships a prebuilt ReactAgent that implements the full Reason-Act loop. You only supply the tools.

from agentflow.prebuilt.agent import ReactAgent
from agentflow.core.state import AgentState, Message

def get_stock_price(ticker: str) -> str:
"""Get the current stock price for a ticker symbol."""
# Replace with a real API call
prices = {"AAPL": "182.50", "GOOG": "175.30", "MSFT": "420.10"}
return f"${prices.get(ticker.upper(), 'not found')}"

def calculate(expression: str) -> str:
"""Evaluate a mathematical expression. E.g. '182.50 * 100'."""
try:
return str(eval(expression, {"__builtins__": {}}, {}))
except Exception as e:
return f"Error: {e}"

agent = ReactAgent(
model="google/gemini-2.5-flash",
system_prompt=[{"role": "system", "content": "You are a financial assistant. Use tools to look up real data."}],
tools=[get_stock_price, calculate],
)

app = agent.compile()

result = app.invoke(
{"messages": [Message.text_message("What is 100 shares of AAPL worth?")]},
config={"thread_id": "finance-1", "recursion_limit": 10},
)
print(result["messages"][-1].text())
# Agent will: call get_stock_price("AAPL") → get $182.50
# Then call: calculate("182.50 * 100") → get 18250.0
# Then respond: "100 shares of AAPL are worth $18,250."

Building a ReAct agent with a custom graph

For full control over routing logic, build the graph manually:

from agentflow.core.graph import Agent, StateGraph, ToolNode
from agentflow.core.state import AgentState, Message
from agentflow.utils import END

tools = [get_stock_price, calculate]
tool_node = ToolNode(tools)

agent_node = Agent(
model="google/gemini-2.5-flash",
system_prompt=[{"role": "system", "content": "Financial assistant."}],
tool_node="TOOLS",
)

def route(state: AgentState) -> str:
last = state.context[-1] if state.context else None
if last and getattr(last, "tools_calls", None) and last.role == "assistant":
return "TOOLS"
return END

graph = StateGraph(AgentState)
graph.add_node("AGENT", agent_node)
graph.add_node("TOOLS", tool_node)
graph.add_conditional_edges("AGENT", route, {"TOOLS": "TOOLS", END: END})
graph.add_edge("TOOLS", "AGENT")
graph.set_entry_point("AGENT")

app = graph.compile()

The conditional edge route is the heart of the ReAct loop: if the LLM returned tool calls, route to the tool node; otherwise, the agent is done.

Serving a ReAct agent

Point agentflow.json at your compiled app and start the server:

agentflow.json
{
"agent": "graph.agent:app",
"env": ".env"
}
agentflow api

You now have a production REST + SSE API for your ReAct agent with no extra code.

When to use a ReAct agent

ReAct is the right pattern when:

  • The task requires looking up information from external tools
  • The number of tool calls is not known in advance
  • The agent needs to adapt its plan based on intermediate results

ReAct is not the right pattern when:

  • You need a guaranteed sequence of steps (use a fixed graph instead)
  • You need structured output in a specific schema (use StructuredOutputAgent)
  • You are orchestrating multiple specialized agents (use SupervisorTeamAgent or SwarmAgent)

Frequently asked questions

Where does the name ReAct come from?
ReAct stands for Reasoning and Acting. It was introduced in the paper 'ReAct: Synergizing Reasoning and Acting in Language Models' by Shunyu Yao et al., published in 2022 (arXiv:2210.03629). The paper showed that combining chain-of-thought reasoning with tool-use actions significantly outperforms either approach alone.
How is a ReAct agent different from chain-of-thought prompting?
Chain-of-thought prompting lets an LLM reason step-by-step but only in text — it cannot take actions or look up external information. A ReAct agent can call real tools (APIs, databases, code execution) between reasoning steps and incorporate the real results into its next reasoning step.
What happens if a ReAct agent calls too many tools?
Agent loops are bounded by a recursion limit (the maximum number of Reason-Act cycles). In AgentFlow, you set this via config={'recursion_limit': 10} in the invoke call. When the limit is hit, the agent stops and returns its last state. The default limit is typically 25 cycles.
Can a ReAct agent call multiple tools in parallel?
Yes. When the LLM returns multiple tool calls in a single response, AgentFlow's ToolNode executes them concurrently using asyncio.gather. The results come back in the original call order. This significantly speeds up tasks that require multiple lookups.
Is ReactAgent in AgentFlow the same as create_react_agent in LangGraph?
They implement the same ReAct loop pattern. AgentFlow's ReactAgent uses AgentFlow's own AgentState and Message types without any LangChain dependency. The prebuilt is simpler to configure and the full production stack (API server, TypeScript client) is included without extra setup.

Next steps