ReAct Pattern
The ReAct (Reasoning and Acting) pattern is one of the most popular and versatile agent patterns. It combines reasoning (thinking about what to do) with acting (using tools to accomplish tasks) in an iterative loop.
Overview
ReAct agents follow a simple but powerful loop:
- Reason - The LLM thinks about the current state and decides what action to take
- Act - Execute a tool based on the reasoning
- Observe - Receive the tool's output
- Repeat - Continue until the task is complete
This pattern is inspired by the ReAct paper from Princeton and Google.
When to Use ReAct
✅ Good for:
- General-purpose task solving
- Tasks requiring multiple tool calls
- Exploratory or research tasks
- When you need flexibility and adaptability
- Interactive applications
❌ Not ideal for:
- Tasks requiring complex multi-step planning (use Plan-Execute instead)
- When you need guaranteed execution order (use Plan-Execute instead)
- Tasks that benefit from self-critique and revision (use Reflection instead)
Pattern Comparison
Not sure which pattern to use? See the Agent Patterns Overview for a detailed comparison of all patterns.
Basic Usage
import { createReActAgent } from '@agentforge/patterns';
import { ChatOpenAI } from '@langchain/openai';
import { calculator, webScraper, fileReader } from '@agentforge/tools';
const agent = createReActAgent({
model: new ChatOpenAI({
model: 'gpt-4',
temperature: 0
}),
tools: [calculator, webScraper, fileReader],
maxIterations: 10
});
const result = await agent.invoke({
messages: [{
role: 'user',
content: 'Calculate 15 * 7 and read the contents of data.txt'
}]
});
console.log(result.messages[result.messages.length - 1].content);Configuration Options
Core Options
interface ReActAgentConfig {
// Required
model: BaseChatModel; // The language model
tools: ToolRegistry | Tool[]; // Available tools
// Optional
systemPrompt?: string; // System prompt (default: "You are a helpful assistant...")
maxIterations?: number; // Max reasoning loops (default: 10)
returnIntermediateSteps?: boolean; // Include reasoning steps (default: false)
stopCondition?: (state: ReActStateType) => boolean; // Custom stop condition
checkpointer?: BaseCheckpointSaver; // For state persistence and human-in-the-loop
}Advanced Configuration
const agent = createReActAgent({
model: new ChatOpenAI({ model: 'gpt-4' }),
tools: [calculator, currentDateTime],
// Custom system prompt
systemPrompt: 'You are a helpful assistant that uses tools to solve problems.',
// Limit iterations to prevent infinite loops
maxIterations: 20,
// Return intermediate steps for debugging
returnIntermediateSteps: true,
// Custom stop condition
stopCondition: (state) => {
return state.iteration >= 15 ||
state.scratchpad.some(entry => entry.thought?.includes('FINAL_ANSWER'));
},
// Add checkpointer for state persistence
checkpointer: new MemorySaver()
});Customization
Custom System Prompt
import { createReActAgent } from '@agentforge/patterns';
const agent = createReActAgent({
model,
tools,
systemPrompt: `You are a helpful research assistant.
When solving tasks:
1. Break down complex questions into steps
2. Use tools to gather information
3. Synthesize findings into clear answers
4. Cite your sources
Always be thorough and accurate.`
});Tool Selection Strategy
// Conditional tool availability - build tool list before creating agent
function getToolsForTask(taskType: string): Tool[] {
const basicTools = [calculator, dateTime];
if (taskType === 'web-scraping') {
return [...basicTools, webScraper, htmlParser];
}
if (taskType === 'data-analysis') {
return [...basicTools, jsonParser, csvParser];
}
return basicTools;
}
// Create agent with appropriate tools
const agent = createReActAgent({
model,
tools: getToolsForTask('web-scraping')
});State Persistence with Checkpointer
Enable conversation continuity and human-in-the-loop workflows with checkpointers:
import { MemorySaver } from '@langchain/langgraph';
import { createReActAgent } from '@agentforge/patterns';
import { createAskHumanTool } from '@agentforge/tools';
// Create agent with checkpointer
const agent = createReActAgent({
model,
tools: [createAskHumanTool(), calculator, webSearch],
checkpointer: new MemorySaver()
});
// Start a conversation with a thread ID
const result1 = await agent.invoke(
{ messages: [{ role: 'user', content: 'What is 2+2?' }] },
{ configurable: { thread_id: 'user-123' } }
);
// Resume the same conversation later
const result2 = await agent.invoke(
{ messages: [{ role: 'user', content: 'And what about 3+3?' }] },
{ configurable: { thread_id: 'user-123' } } // Same thread ID
);For nested agents in multi-agent systems:
import { ReActAgentBuilder } from '@agentforge/patterns';
import { createAskHumanTool, sendSlackMessage } from '@agentforge/tools';
// Worker agent that uses parent's checkpointer
const hrAgent = new ReActAgentBuilder()
.withModel(model)
.withTools([createAskHumanTool(), sendSlackMessage])
.withCheckpointer(true) // Use parent's checkpointer with separate namespace
.build();
// When used in a multi-agent system:
// - The agent stores state in a separate namespace (e.g., thread_abc:worker:hr)
// - Supports interrupts (askHuman tool) without causing infinite loops
// - Can be resumed independently from other workersBenefits:
- Conversation continuity: Resume conversations across sessions
- Human-in-the-loop: Request user input mid-execution with
askHumantool - Multi-agent coordination: Enable worker agents to interrupt and resume independently
- Error recovery: Resume from failures without losing progress
Streaming
ReAct agents support streaming for real-time updates:
const stream = await agent.stream({
messages: [{ role: 'user', content: 'Research quantum computing' }]
});
for await (const chunk of stream) {
// Access ReAct state fields
if (chunk.thoughts && chunk.thoughts.length > 0) {
console.log('Thinking:', chunk.thoughts[chunk.thoughts.length - 1]);
}
if (chunk.actions && chunk.actions.length > 0) {
console.log('Using tool:', chunk.actions[chunk.actions.length - 1]);
}
if (chunk.observations && chunk.observations.length > 0) {
console.log('Observation:', chunk.observations[chunk.observations.length - 1]);
}
}Error Handling
import { createReActAgent } from '@agentforge/patterns';
const agent = createReActAgent({
model,
tools,
maxIterations: 10,
// Use custom stop condition to handle errors
stopCondition: (state) => {
// Stop if we've hit max iterations
if (state.iteration >= 10) return true;
// Stop if we have a response
if (state.response) return true;
// Stop if last observation indicates an error
const lastObs = state.observations[state.observations.length - 1];
if (lastObs?.error) {
console.error('Tool error:', lastObs.error);
return true;
}
return false;
}
});Best Practices
1. Limit Iterations
Always set a reasonable maxIterations to prevent infinite loops:
const agent = createReActAgent({
model,
tools,
maxIterations: 15 // Prevent runaway loops
});2. Provide Clear Tool Descriptions
The agent relies on tool descriptions to decide which tool to use:
import { toolBuilder, ToolCategory } from '@agentforge/core';
const weatherTool = toolBuilder()
.name('get-weather')
.description('Get current weather for a city. Use this when the user asks about weather, temperature, or conditions.')
.category(ToolCategory.WEB)
.schema(z.object({
city: z.string().describe('The city name, e.g., "San Francisco"'),
units: z.enum(['celsius', 'fahrenheit']).default('celsius').describe('Temperature units (default: celsius)')
}))
.implement(async ({ city, units }) => {
// Implementation
})
.build();3. Use Temperature = 0 for Consistency
For deterministic behavior, use temperature 0:
const agent = createReActAgent({
model: new ChatOpenAI({
model: 'gpt-4',
temperature: 0 // Deterministic reasoning
}),
tools
});4. Monitor Token Usage
Track token consumption to optimize costs:
const result = await agent.invoke(input, {
callbacks: [{
handleLLMEnd: (output) => {
console.log('Tokens used:', output.llmOutput?.tokenUsage);
}
}]
});Common Patterns
Research Assistant
import { createReActAgent } from '@agentforge/patterns';
import { webScraper, htmlParser, calculator } from '@agentforge/tools';
const researchAgent = createReActAgent({
model: new ChatOpenAI({ model: 'gpt-4' }),
tools: [webScraper, htmlParser, calculator],
maxIterations: 20,
systemPrompt: `You are a thorough research assistant.
For each query:
1. Search multiple sources
2. Cross-reference information
3. Provide citations
4. Summarize findings clearly`
});Data Analysis Agent
import { fileReader, csvParser, calculator } from '@agentforge/tools';
const dataAgent = createReActAgent({
model: new ChatOpenAI({ model: 'gpt-4' }),
tools: [fileReader, csvParser, calculator],
systemPrompt: `You are a data analysis expert.
When analyzing data:
1. Read the data file first
2. Use Python for complex calculations
3. Explain your methodology
4. Provide visualizations when helpful`
});Customer Support Agent
import { knowledgeBaseSearch, ticketCreate, emailSend } from './custom-tools';
const supportAgent = createReActAgent({
model: new ChatOpenAI({ model: 'gpt-4' }),
tools: [knowledgeBaseSearch, ticketCreate, emailSend],
maxIterations: 10,
systemPrompt: `You are a helpful customer support agent.
Always:
- Search the knowledge base first
- Be polite and professional
- Create tickets for unresolved issues
- Confirm actions with the user`
});Debugging
Enable Verbose Logging
const agent = createReActAgent({
model,
tools,
returnIntermediateSteps: true
});
const result = await agent.invoke(input);
// Inspect reasoning steps from state
if (result.actions && result.observations) {
result.actions.forEach((action, i) => {
console.log(`Step ${i + 1}:`);
console.log('Action:', action);
console.log('Observation:', result.observations[i]);
});
}
// Or inspect the scratchpad
console.log('Scratchpad:', result.scratchpad);Debug Agent State
const agent = createReActAgent({
model,
tools,
returnIntermediateSteps: true
});
const result = await agent.invoke(input);
// Access state fields
console.log('Iteration count:', result.iteration);
console.log('Actions taken:', result.actions);
console.log('Observations:', result.observations);
console.log('Scratchpad:', result.scratchpad);Performance Optimization
1. Limit Iterations
Reduce token usage by limiting the number of reasoning loops:
const agent = createReActAgent({
model,
tools,
maxIterations: 5, // Limit to 5 reasoning loops
// Or use a custom stop condition
stopCondition: (state) => {
// Stop early if we have enough information
return state.iteration >= 3 && state.response !== undefined;
}
});2. Use Faster Models for Simple Tasks
const agent = createReActAgent({
model: new ChatOpenAI({
model: 'gpt-3.5-turbo', // Faster and cheaper
temperature: 0
}),
tools,
maxIterations: 10
});3. Cache Tool Results
import { withCache } from '@agentforge/core';
const cachedWebScraper = withCache(webScraper, {
ttl: 3600, // Cache for 1 hour
keyFn: (input) => input.url
});
const agent = createReActAgent({
model,
tools: [cachedWebScraper, calculator]
});Next Steps
- Plan-Execute Pattern - For complex multi-step tasks
- Reflection Pattern - For self-improving agents
- Multi-Agent Pattern - For collaborative agents
- API Reference - Complete API documentation
Further Reading
- ReAct Paper - Original research paper
- LangGraph ReAct - LangGraph tutorial
- Examples - Working code examples