Nous.ReActAgent (nous v0.16.2)
View SourceReAct (Reasoning and Acting) Agent wrapper with built-in planning and todo management.
ReAct is a prompting paradigm where AI agents interleave:
- Reasoning: Thinking about what to do next
- Acting: Using tools to gather information or perform actions
- Observing: Processing results to inform the next step
This module wraps Nous.Agent with enhanced capabilities:
- Structured planning with facts survey
- Built-in todo list management
- Note-taking for observations
- Loop prevention (warns on duplicate tool calls)
- Mandatory
final_answerfor task completion
Built-in Tools
plan- Create structured plan before taking actionnote- Record observations and insightsadd_todo- Track subtaskscomplete_todo- Mark tasks donelist_todos- View current todosfinal_answer- Complete the task (required)
Example
# Create ReAct agent with custom tools
agent = ReActAgent.new("lmstudio:qwen3-vl-4b-thinking-mlx",
instructions: "You are a research assistant",
tools: [&MyTools.search/2, &MyTools.calculate/2]
)
# Run with initial context
{:ok, result} = ReActAgent.run(agent,
"Find the oldest F1 driver and when they won their first championship",
deps: %{database: MyDB}
)
# The agent will automatically:
# 1. Create a plan
# 2. Add todos for each step
# 3. Use tools to gather info
# 4. Complete todos as it progresses
# 5. Call final_answer when done
IO.puts(result.output)
# Access metadata
IO.inspect(result.metadata)Comparison to Standard Agent
# Standard Agent
agent = Agent.new("model", tools: [tool1, tool2])
# ReAct Agent (enhanced)
agent = ReActAgent.new("model", tools: [tool1, tool2])
# Automatically includes: plan, note, add_todo, complete_todo,
# list_todos, final_answerLoop Prevention
The ReAct agent tracks tool call history and warns if the same tool is called with identical arguments, helping prevent infinite loops.
Based on Research
This implementation draws from:
- "ReAct: Synergizing Reasoning and Acting in Language Models" (Yao et al., 2023)
- HuggingFace smolagents toolcalling_agent patterns
Summary
Functions
Create a new ReAct agent.
Run the ReAct agent synchronously.
Run the ReAct agent with streaming.
Types
@type t() :: Nous.Agent.t()
Functions
Create a new ReAct agent.
Wraps Nous.Agent.new/2 with ReAct behaviour module.
Parameters
model_string- Model in format "provider:model-name"opts- Configuration options (same asNous.Agent.new/2)
Options
All standard Agent options are supported, plus:
:react_system_prompt- Override the default ReAct system prompt:require_planning- Whether to enforce planning step (default: false):track_history- Track tool call history for loop detection (default: true)
Examples
# Basic ReAct agent
agent = ReActAgent.new("openai:gpt-4")
# With custom tools
agent = ReActAgent.new("anthropic:claude-3-5-sonnet",
tools: [&MyTools.search/2, &MyTools.calculate/2],
instructions: "You are a research assistant"
)
# With custom model settings
agent = ReActAgent.new("lmstudio:qwen3-vl-4b-thinking-mlx",
model_settings: %{temperature: 0.3, max_tokens: 2000}
)Returns
A configured Agent struct with ReAct behaviour.
Run the ReAct agent synchronously.
Wraps Nous.Agent.run/3 with additional ReAct context initialization.
Options
Same as Nous.Agent.run/3, with automatic initialization of:
todos: []- Empty todo listplans: []- Empty plans listnotes: []- Empty notes listtool_history: []- Tool call history for loop detection
Examples
{:ok, result} = ReActAgent.run(agent,
"What is the capital of France and what's its population?"
)
# With dependencies
{:ok, result} = ReActAgent.run(agent,
"Search for recent AI developments",
deps: %{api_key: "..."}
)
# Continue conversation
{:ok, result2} = ReActAgent.run(agent,
"Tell me more about that",
message_history: result1.new_messages
)Returns
Same as Nous.Agent.run/3:
{:ok, %{
output: "Final answer text...",
usage: %Usage{...},
all_messages: [...],
new_messages: [...],
metadata: %{
todos_completed: 3,
todos_pending: 0,
plans_count: 1,
notes_count: 5
}
}}
@spec run_stream(t(), String.t(), keyword()) :: {:ok, Enumerable.t()} | {:error, term()}
Run the ReAct agent with streaming.
Wraps Nous.Agent.run_stream/3 with ReAct context initialization.
Example
{:ok, stream} = ReActAgent.run_stream(agent, "Solve this problem...")
stream
|> Stream.each(fn
{:text_delta, text} -> IO.write(text)
{:tool_call, call} -> IO.puts("Using tool")
{:tool_result, result} -> IO.puts("Got result")
{:complete, result} -> IO.puts("Done!")
end)
|> Stream.run()