Skip to content

ReAct Agent

The ReAct pattern (Yao et al., 2022) interleaves reasoning (Thought) and acting (Action) steps. The agent iterates until it reaches a Final Answer or exhausts max_steps.

Usage

from brain.patterns import ReActAgent

agent = ReActAgent(
    tools={"search": lambda q: f"[result: {q}]"},
    max_steps=6,
)
result = agent.run("What is the capital of France?")
print(result.answer)

API Reference

brain.patterns.react.ReActAgent

ReActAgent(tools: dict[str, Tool] | None = None, provider: LLMProvider | None = None, max_steps: int = 8, system_prompt: str | None = None)

Bases: BasePattern

ReAct agent: reason and act in alternating steps.

Parameters:

Name Type Description Default
tools dict[str, Tool] | None

mapping of tool name → callable(str) -> str.

None
provider LLMProvider | None

LLMProvider instance; if None, uses LocalEchoProvider (offline-safe).

None
max_steps int

maximum Thought/Action/Observation cycles before giving up.

8
system_prompt str | None

override the default ReAct system prompt.

None
Source code in brain/patterns/react.py
def __init__(
    self,
    tools: dict[str, Tool] | None = None,
    provider: LLMProvider | None = None,
    max_steps: int = 8,
    system_prompt: str | None = None,
) -> None:
    self.tools: dict[str, Tool] = tools or {}
    self.max_steps = max_steps
    self._system_prompt = system_prompt
    self._provider = provider

run

run(task: str, **kwargs: Any) -> PatternResult
Source code in brain/patterns/react.py
def run(self, task: str, **kwargs: Any) -> PatternResult:
    provider = self._get_provider()
    tool_names = ", ".join(self.tools.keys()) if self.tools else "none"
    system = (self._system_prompt or _SYSTEM_PROMPT).format(tool_names=tool_names)

    messages: list[dict[str, Any]] = [
        {"role": "system", "content": system, "cache_stable": True},
        {"role": "user", "content": task},
    ]
    steps: list[Step] = []

    for i in range(self.max_steps):
        try:
            result = provider.generate(messages, tools=[], tool_choice="none")
        except Exception as exc:  # noqa: BLE001
            logger.warning("ReActAgent: provider error at step %d: %s", i, exc)
            return PatternResult(
                answer="",
                steps=steps,
                iterations=i,
                ok=False,
                error=str(exc),
            )

        text = result.text.strip()
        thought, action, action_input, final = _parse(text)

        step = Step(
            index=i,
            thought=thought,
            action=action,
            action_input=action_input,
            text=text,
        )

        if final is not None:
            step.observation = ""
            steps.append(step)
            return PatternResult(answer=final, steps=steps, iterations=i + 1)

        # Execute tool
        observation: str
        if action and action in self.tools:
            try:
                observation = str(self.tools[action](action_input))
            except Exception as exc:  # noqa: BLE001  # silent-ok: fail-soft, surface via metric/log elsewhere
                observation = f"Tool error: {exc}"
        elif action:
            observation = f"Unknown tool '{action}'. Available: {tool_names}"
        else:
            # LLM gave a plain response — treat as final answer
            step.observation = ""
            steps.append(step)
            return PatternResult(answer=text, steps=steps, iterations=i + 1)

        step.observation = observation
        steps.append(step)

        messages.append({"role": "assistant", "content": text})
        messages.append({"role": "user", "content": f"Observation: {observation}"})

    return PatternResult(
        answer=steps[-1].text if steps else "",
        steps=steps,
        iterations=self.max_steps,
        ok=False,
        error="max_steps reached without Final Answer",
    )

Step Trace

Each iteration is captured in result.steps:

for step in result.steps:
    print(f"[{step.index}] Thought: {step.thought}")
    print(f"       Action: {step.action}({step.action_input})")
    print(f"       Obs:    {step.observation}")

When to Use

Situation Recommendation
Need real-time tool calls ReAct
Output depends on external data ReAct
Pure reasoning / no tools needed Reflexion
Multiple data sources MultiAgentOrchestrator