add react vs custom

This commit is contained in:
Gaetan Hurel 2025-06-25 15:48:07 +02:00
parent 9858ffe289
commit 8a0b8b2ba8
No known key found for this signature in database

299
react_vs_custom.md Normal file
View File

@ -0,0 +1,299 @@
# ReAct Agent vs Custom StateGraph: Architectural Decision Guide
This document explores the two main approaches for building LangGraph agents: using the prebuilt `create_react_agent` vs implementing a custom `StateGraph`.
## TL;DR Recommendation
**Use `create_react_agent` for most use cases**. Only migrate to custom `StateGraph` when you hit specific limitations of the ReAct pattern.
## Option 1: `create_react_agent` (Current Implementation)
### What it is
```python
# Simple 5-line agent creation
llm = init_chat_model("openai:gpt-4o-mini")
tools = [shell_tool, analyze_log_file]
agent = create_react_agent(llm, tools, prompt=system_prompt)
```
### Under the Hood
`create_react_agent` uses a predefined `StateGraph` with this structure:
```
START → agent → tools → agent → END
↑________________↓
```
- **`agent` node**: LLM reasoning (decides what to do)
- **`tools` node**: Tool execution (acting)
- **Conditional loop**: Continues until final response
### Advantages ✅
**Simplicity & Speed**
- Minimal code to get started
- Battle-tested ReAct pattern
- Automatic reasoning/acting cycles
**Maintenance**
- Automatic updates with LangGraph improvements
- Less code to debug and maintain
- Well-documented pattern
**Perfect for Standard Use Cases**
- Tool-based interactions
- Conversational interfaces
- Analysis workflows
- System administration tasks
### Limitations ⚠️
- Fixed ReAct pattern only
- Limited state management
- No custom routing logic
- No parallel tool execution
- No complex workflow orchestration
## Option 2: Custom StateGraph Implementation
### What it looks like
```python
from typing import TypedDict, Annotated, Literal
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_core.messages import BaseMessage
class AgentState(TypedDict):
messages: Annotated[list[BaseMessage], add_messages]
current_task: str # "log_analysis", "shell_command", "general"
log_context: dict # Remember previous analyses
safety_mode: bool # Control dangerous commands
def classify_request(state: AgentState) -> AgentState:
"""Classify user request type"""
last_message = state["messages"][-1].content.lower()
if any(word in last_message for word in ["log", "analyze", "error", "pattern"]):
state["current_task"] = "log_analysis"
elif any(word in last_message for word in ["command", "shell", "run", "execute"]):
state["current_task"] = "shell_command"
else:
state["current_task"] = "general"
return state
def route_request(state: AgentState) -> Literal["log_analyzer", "shell_executor", "general_chat"]:
"""Route to appropriate node based on request type"""
return {
"log_analysis": "log_analyzer",
"shell_command": "shell_executor",
"general": "general_chat"
}[state["current_task"]]
def analyze_logs_node(state: AgentState) -> AgentState:
"""Specialized node for log analysis"""
llm = init_chat_model("openai:gpt-4o-mini")
# Custom logic for log analysis
# - Parallel file processing
# - Context from previous analyses
# - Specialized prompting
prompt = f"""You are a log analysis expert.
Previous context: {state.get("log_context", {})}
Use analyze_log_file tool for the requested analysis.
"""
response = llm.invoke([HumanMessage(content=prompt)] + state["messages"][-3:])
state["messages"].append(response)
# Update context for future analyses
state["log_context"]["last_analysis"] = "completed"
return state
def execute_shell_node(state: AgentState) -> AgentState:
"""Specialized node for shell commands with safety checks"""
llm = init_chat_model("openai:gpt-4o-mini")
# Safety validation before execution
dangerous_commands = ["rm -rf", "sudo rm", "format", "dd if="]
last_message = state["messages"][-1].content.lower()
if any(cmd in last_message for cmd in dangerous_commands):
state["messages"].append(
AIMessage(content="⚠️ Potentially dangerous command detected. Please confirm.")
)
state["safety_mode"] = True
return state
# Normal execution with ShellTool
# Custom logic for command validation and execution
return state
def general_chat_node(state: AgentState) -> AgentState:
"""Handle general conversation"""
llm = init_chat_model("openai:gpt-4o-mini")
prompt = """You are a helpful system administration assistant.
Provide guidance and suggestions for system debugging tasks.
"""
response = llm.invoke([HumanMessage(content=prompt)] + state["messages"][-5:])
state["messages"].append(response)
return state
def create_advanced_agent():
"""Create custom agent with StateGraph"""
# Define workflow
workflow = StateGraph(AgentState)
# Add nodes
workflow.add_node("classifier", classify_request)
workflow.add_node("log_analyzer", analyze_logs_node)
workflow.add_node("shell_executor", execute_shell_node)
workflow.add_node("general_chat", general_chat_node)
# Define edges
workflow.add_edge(START, "classifier")
workflow.add_conditional_edges(
"classifier",
route_request,
{
"log_analyzer": "log_analyzer",
"shell_executor": "shell_executor",
"general_chat": "general_chat"
}
)
# All terminal nodes lead to END
workflow.add_edge("log_analyzer", END)
workflow.add_edge("shell_executor", END)
workflow.add_edge("general_chat", END)
return workflow.compile()
```
### Advantages ✅
**Complete Control**
- Custom business logic
- Complex state management
- Advanced routing and validation
- Parallel processing capabilities
**Specialized Workflows**
- Different handling per task type
- Memory between interactions
- Safety checks and validation
- Custom error handling
**Performance Optimization**
- Optimized tool selection
- Reduced unnecessary LLM calls
- Parallel execution where possible
### Disadvantages ❌
**Complexity**
- 50+ lines vs 5 lines
- More potential bugs
- Custom maintenance required
**Development Time**
- Slower initial development
- More testing needed
- Complex debugging
## Comparison Matrix
| Aspect | `create_react_agent` | Custom `StateGraph` |
|--------|---------------------|-------------------|
| **Lines of Code** | ~5 | ~50+ |
| **Development Time** | Minutes | Hours/Days |
| **Flexibility** | ReAct pattern only | Complete freedom |
| **Maintenance** | Automatic | Manual |
| **Performance** | Good, optimized | Depends on implementation |
| **Debugging** | Limited visibility | Full control |
| **State Management** | Basic messages | Rich custom state |
| **Routing Logic** | Tool-based only | Custom conditional |
| **Parallel Execution** | No | Yes |
| **Safety Checks** | Tool-level only | Custom validation |
| **Use Cases Coverage** | 80% | 100% |
## When to Use Each Approach
### Stick with `create_react_agent` when:
**Tool-based interactions** (your current use case)
✅ **Standard conversational AI**
✅ **Rapid prototyping**
✅ **Simple reasoning/acting cycles**
✅ **Maintenance is a priority**
✅ **Team has limited LangGraph experience**
### Migrate to Custom `StateGraph` when:
🔄 **Complex business logic** required
🔄 **Multi-step workflows** with different paths
🔄 **Advanced state management** needed
🔄 **Parallel processing** requirements
🔄 **Custom validation/safety** logic
🔄 **Performance optimization** critical
🔄 **Specialized routing** based on context
## Migration Strategy
If you decide to eventually migrate to custom StateGraph:
### Phase 1: Enhance Current Implementation
```python
# Add more sophisticated tools to your current setup
def create_enhanced_react_agent():
tools = [
shell_tool,
analyze_log_file,
safety_validator_tool, # New: safety checks
parallel_log_analyzer, # New: batch processing
context_manager_tool # New: conversation context
]
return create_react_agent(llm, tools, enhanced_prompt)
```
### Phase 2: Hybrid Approach
```python
# Use create_react_agent for some tasks, custom StateGraph for others
def create_hybrid_agent():
# Route complex workflows to custom graph
# Keep simple interactions with ReAct agent
pass
```
### Phase 3: Full Custom Implementation
- Implement complete StateGraph when requirements demand it
## Recommendation for Your Project
**Keep `create_react_agent` for now** because:
1. ✅ Your use case (log analysis + shell commands) fits perfectly
2. ✅ Current implementation is clean and working
3. ✅ Maintenance overhead is minimal
4. ✅ Team can focus on improving tools rather than framework
**Consider custom StateGraph later** if you need:
- Advanced workflow orchestration
- Complex state management between analyses
- Parallel processing of multiple log files
- Sophisticated safety validation
- Performance optimization for large-scale deployments
## Conclusion
Your current `create_react_agent` implementation is excellent for an MVP and likely covers 80% of system administration use cases. The ReAct pattern provides a solid foundation for tool-based AI interactions.
Only migrate to custom StateGraph when you have specific requirements that the ReAct pattern cannot handle efficiently. Focus on enhancing your tools (`log_analyzer.py`, additional custom tools) rather than changing the underlying agent framework.
**The best architecture is the one that solves your current problems without overengineering for hypothetical future needs.**