Compare commits

...
Author SHA1 Message Date
github-actions[bot]andMervin Praison 099e4ccfcb feat: implement ReasoningAgent and DualBrainAgent with enhanced reasoning capabilities
- Add ReasoningAgent class with configurable step-by-step reasoning
- Add DualBrainAgent class with separate reasoning and conversational models
- Implement ReasoningConfig for configurable reasoning behavior
- Add ReasoningFlow for reasoning flow control and validation
- Integrate existing Chain of Thought (GenerateCOT) infrastructure
- Support confidence scoring and reasoning validation
- Maintain full backward compatibility with existing Agent class
- Add comprehensive test suite to verify functionality

Resolves #968

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Mervin Praison <[email protected]>
2025-07-17 00:23:50 +00:00
6 changed files with 1375 additions and 1 deletions

No files matched your search

@@ -28,6 +28,9 @@ logging.getLogger("httpcore").setLevel(logging.WARNING)
from .agent.agent import Agent
from .agent.image_agent import ImageAgent
from .agent.reasoning_agent import ReasoningAgent
from .agent.dual_brain_agent import DualBrainAgent
from .agent.reasoning import ReasoningConfig, ReasoningFlow, ActionState
from .agents.agents import PraisonAIAgents
from .task.task import Task
from .tools.tools import Tools
@@ -102,6 +105,11 @@ if _telemetry_available:
__all__ = [
'Agent',
'ImageAgent',
'ReasoningAgent',
'DualBrainAgent',
'ReasoningConfig',
'ReasoningFlow',
'ActionState',
'PraisonAIAgents',
'Agents',
'Tools',
@@ -3,5 +3,22 @@ from .agent import Agent
from .image_agent import ImageAgent
from .handoff import Handoff, handoff, handoff_filters, RECOMMENDED_PROMPT_PREFIX, prompt_with_handoff_instructions
from .router_agent import RouterAgent
from .reasoning_agent import ReasoningAgent
from .dual_brain_agent import DualBrainAgent
from .reasoning import ReasoningConfig, ReasoningFlow, ActionState
__all__ = ['Agent', 'ImageAgent', 'Handoff', 'handoff', 'handoff_filters', 'RECOMMENDED_PROMPT_PREFIX', 'prompt_with_handoff_instructions', 'RouterAgent']
__all__ = [
'Agent',
'ImageAgent',
'Handoff',
'handoff',
'handoff_filters',
'RECOMMENDED_PROMPT_PREFIX',
'prompt_with_handoff_instructions',
'RouterAgent',
'ReasoningAgent',
'DualBrainAgent',
'ReasoningConfig',
'ReasoningFlow',
'ActionState'
]
@@ -0,0 +1,387 @@
"""
DualBrainAgent - Agent with separate reasoning and conversational models.
This agent extends the ReasoningAgent to use different LLM models for
conversational interaction and analytical reasoning, enabling optimized
performance for different cognitive tasks.
"""
import logging
from typing import Optional, Dict, Any, Union, List
from .reasoning_agent import ReasoningAgent
from .reasoning import ReasoningConfig, ReasoningFlow
from ..tools.train.data.generatecot import GenerateCOT
logger = logging.getLogger(__name__)
class DualBrainAgent(ReasoningAgent):
"""
Agent with dual LLM models - one for conversation, one for reasoning.
This agent uses:
- Main LLM for conversational interactions and general responses
- Reasoning LLM for analytical thinking and complex problem solving
Inherits all ReasoningAgent functionality while adding dual-model support.
"""
def __init__(
self,
name: Optional[str] = None,
role: Optional[str] = None,
goal: Optional[str] = None,
backstory: Optional[str] = None,
instructions: Optional[str] = None,
# Dual brain configuration
llm: Optional[Union[str, Any]] = None, # Main conversational model
reasoning_llm: Optional[Union[str, Any]] = None, # Analytical reasoning model
# LLM configuration dictionaries
llm_config: Optional[Dict[str, Any]] = None,
reasoning_config: Optional[Union[ReasoningConfig, Dict[str, Any]]] = None,
# Other parameters
tools: Optional[List[Any]] = None,
reasoning: bool = True,
reasoning_flow: Optional[ReasoningFlow] = None,
min_confidence: float = 0.7,
show_reasoning: bool = True,
# Pass through all other parameters
**kwargs
):
"""
Initialize a DualBrainAgent.
Args:
llm: Main conversational model (e.g., "gpt-4-turbo")
reasoning_llm: Analytical reasoning model (e.g., "o1-preview")
llm_config: Configuration for main LLM (temperature, etc.)
reasoning_config: Configuration for reasoning behavior and model
**kwargs: All other parameters passed to ReasoningAgent
"""
# Set default models if not provided
main_llm = llm or "gpt-4o-mini"
self.reasoning_llm_model = reasoning_llm or "gpt-4o-mini"
# Store LLM configurations
self.llm_config = llm_config or {"temperature": 0.7}
# Handle reasoning_config - can be ReasoningConfig or dict with model info
if isinstance(reasoning_config, dict):
# Extract model info if present
reasoning_model_config = reasoning_config.get("model", self.reasoning_llm_model)
reasoning_temp = reasoning_config.get("temperature", 0.1)
reasoning_prompt = reasoning_config.get("system_prompt")
# Create ReasoningConfig from remaining parameters
config_params = {k: v for k, v in reasoning_config.items()
if k not in ["model", "temperature", "system_prompt"]}
config_params.setdefault("temperature", reasoning_temp)
if reasoning_prompt:
config_params["system_prompt"] = reasoning_prompt
final_reasoning_config = ReasoningConfig(**config_params)
self.reasoning_llm_model = reasoning_model_config
elif reasoning_config is None:
final_reasoning_config = ReasoningConfig(
confidence_threshold=min_confidence,
style="analytical",
temperature=0.1
)
else:
final_reasoning_config = reasoning_config
# Initialize parent with main LLM
super().__init__(
name=name or "DualBrainAgent",
role=role,
goal=goal,
backstory=backstory,
instructions=instructions,
llm=main_llm,
tools=tools,
reasoning=reasoning,
reasoning_config=final_reasoning_config,
reasoning_flow=reasoning_flow,
min_confidence=min_confidence,
show_reasoning=show_reasoning,
**kwargs
)
# Setup reasoning-specific LLM
self.reasoning_llm = self.reasoning_llm_model
# Override COT generator to use reasoning LLM
if self.reasoning_enabled:
try:
self.cot_generator = GenerateCOT(
model=self.reasoning_llm_model,
temperature=self.reasoning_config.temperature,
verbose=self.show_reasoning
)
logger.info(f"DualBrainAgent initialized - Main: {main_llm}, Reasoning: {self.reasoning_llm_model}")
except Exception as e:
logger.warning(f"Could not initialize COT generator with reasoning LLM: {e}")
# Store model information for introspection
self.model_info = {
"conversational_model": main_llm,
"reasoning_model": self.reasoning_llm_model,
"llm_config": self.llm_config,
"reasoning_config": final_reasoning_config
}
def _chat_with_reasoning(
self,
message: str,
temperature: float,
tools: Optional[List[Any]],
task_name: Optional[str],
task_description: Optional[str],
task_id: Optional[str]
) -> str:
"""
Enhanced reasoning chat that uses dual models.
"""
# Step 1: Use reasoning LLM for initial analysis
analysis_step = self.reasoning_engine.create_step(
title="Deep Analysis",
action="Analyze problem using specialized reasoning model",
thought=f"Using {self.reasoning_llm_model} for analysis: {message}"
)
# Use reasoning LLM for Chain of Thought
reasoning_result = None
if self.cot_generator:
try:
reasoning_result = self.cot_generator.cot_generate_dict(message)
analysis_step.thought = reasoning_result.get("thought_process", "")
analysis_step.confidence = self._estimate_reasoning_confidence(reasoning_result)
self.reasoning_engine.update_step(
analysis_step.id,
thought=analysis_step.thought,
confidence=analysis_step.confidence
)
if self.show_reasoning:
logger.info(f"Reasoning model analysis: {analysis_step.confidence:.2f} confidence")
except Exception as e:
logger.warning(f"Reasoning LLM analysis failed: {e}")
analysis_step.confidence = 0.4
# Step 2: Use main LLM for conversational synthesis
synthesis_step = self.reasoning_engine.create_step(
title="Response Synthesis",
action="Synthesize conversational response using main model",
thought="",
confidence=0.0
)
# Build synthesis prompt that includes reasoning context
synthesis_prompt = self._build_dual_brain_prompt(message, reasoning_result)
# Use main LLM for synthesis (parent's chat method)
response = super(ReasoningAgent, self).chat(
synthesis_prompt,
self.llm_config.get("temperature", temperature),
tools,
task_name,
task_description,
task_id
)
synthesis_step.thought = response
synthesis_step.confidence = self._estimate_confidence(response)
# Step 3: Validation using reasoning model if confidence is low
if synthesis_step.confidence < self.min_confidence and self.cot_generator:
validation_step = self.reasoning_engine.create_step(
title="Response Validation",
action="Validate response using reasoning model",
thought="",
confidence=0.0
)
try:
validation_prompt = f"""
Original Problem: {message}
Proposed Response: {response}
Evaluate this response for accuracy, completeness, and correctness.
Provide a rating from 0 to 1 and suggest improvements if needed.
"""
validation_result = self.cot_generator.cot_generate_dict(validation_prompt)
validation_step.thought = validation_result.get("thought_process", "")
validation_step.confidence = self._extract_validation_score(validation_result)
# If validation suggests improvement, regenerate
if validation_step.confidence < 0.7:
logger.info("Validation suggests improvement, regenerating response")
improvement_prompt = f"""
{synthesis_prompt}
Validation feedback: {validation_step.thought}
Please improve your response based on this feedback.
"""
response = super(ReasoningAgent, self).chat(
improvement_prompt,
self.llm_config.get("temperature", temperature),
tools,
task_name,
task_description,
task_id
)
synthesis_step.thought = response
synthesis_step.confidence = max(synthesis_step.confidence, validation_step.confidence)
except Exception as e:
logger.warning(f"Response validation failed: {e}")
validation_step.confidence = synthesis_step.confidence
# Add model information to response if showing reasoning
if self.show_reasoning:
model_info = f"\n\n--- Dual Brain Process ---\n"
model_info += f"Analysis Model: {self.reasoning_llm_model}\n"
model_info += f"Response Model: {self.llm}\n"
model_info += self._format_reasoning_summary()
response = f"{response}{model_info}"
return response
def _build_dual_brain_prompt(self, original_message: str, reasoning_result: Optional[Dict]) -> str:
"""Build a prompt that incorporates reasoning model insights."""
base_prompt = original_message
if reasoning_result:
thought_process = reasoning_result.get("thought_process", "")
final_answer = reasoning_result.get("final_answer", "")
base_prompt = f"""
Original Question: {original_message}
Reasoning Analysis (from specialized reasoning model):
{thought_process}
Reasoning Conclusion: {final_answer}
Based on this analytical foundation, provide a clear, conversational response that:
1. Addresses the original question directly
2. Incorporates the insights from the reasoning analysis
3. Uses an appropriate tone and style for the user
4. Ensures accuracy while remaining accessible
"""
return base_prompt
def _estimate_reasoning_confidence(self, reasoning_result: Dict) -> float:
"""Estimate confidence from reasoning model output."""
if not reasoning_result:
return 0.0
thought_process = reasoning_result.get("thought_process", "")
final_answer = reasoning_result.get("final_answer", "")
# Base confidence on structure and content
confidence = 0.5
# Detailed thought process increases confidence
if len(thought_process) > 200:
confidence += 0.2
if len(thought_process) > 500:
confidence += 0.1
# Clear final answer increases confidence
if final_answer and len(final_answer) > 10:
confidence += 0.1
# Look for reasoning indicators
reasoning_indicators = ["because", "therefore", "analysis", "step", "consider", "evaluate"]
indicator_count = sum(1 for indicator in reasoning_indicators
if indicator in thought_process.lower())
confidence += min(0.2, indicator_count * 0.05)
return max(0.0, min(1.0, confidence))
def _extract_validation_score(self, validation_result: Dict) -> float:
"""Extract numerical validation score from validation result."""
thought_process = validation_result.get("thought_process", "")
# Look for explicit ratings
import re
# Look for patterns like "rating: 0.8" or "score: 7/10"
rating_patterns = [
r"rating[:\s]+([0-9]*\.?[0-9]+)",
r"score[:\s]+([0-9]*\.?[0-9]+)",
r"confidence[:\s]+([0-9]*\.?[0-9]+)",
r"([0-9]*\.?[0-9]+)\s*out\s*of\s*[0-9]+",
r"([0-9]*\.?[0-9]+)/[0-9]+"
]
for pattern in rating_patterns:
matches = re.findall(pattern, thought_process.lower())
if matches:
try:
score = float(matches[0])
# Normalize to 0-1 range
if score > 1:
score = score / 10.0
return max(0.0, min(1.0, score))
except ValueError:
continue
# Default confidence based on content analysis
return self._estimate_confidence(thought_process)
def get_model_info(self) -> Dict[str, Any]:
"""Get information about the dual models being used."""
return self.model_info.copy()
def switch_reasoning_model(self, new_model: str):
"""Switch the reasoning model at runtime."""
self.reasoning_llm_model = new_model
self.reasoning_llm = new_model
self.model_info["reasoning_model"] = new_model
# Reinitialize COT generator
if self.reasoning_enabled:
try:
self.cot_generator = GenerateCOT(
model=new_model,
temperature=self.reasoning_config.temperature,
verbose=self.show_reasoning
)
logger.info(f"Switched reasoning model to: {new_model}")
except Exception as e:
logger.error(f"Failed to switch reasoning model: {e}")
def switch_conversational_model(self, new_model: str):
"""Switch the conversational model at runtime."""
self.llm = new_model
self.model_info["conversational_model"] = new_model
logger.info(f"Switched conversational model to: {new_model}")
@property
def dual_brain_summary(self) -> Dict[str, Any]:
"""Get summary including dual brain statistics."""
base_summary = self.reasoning_summary
base_summary.update({
"conversational_model": self.model_info["conversational_model"],
"reasoning_model": self.model_info["reasoning_model"],
"dual_brain_mode": True
})
return base_summary
@@ -0,0 +1,204 @@
"""
Reasoning configuration and support classes for PraisonAI Agents.
This module provides configuration classes and utilities for enhanced reasoning
capabilities in agents, including step-by-step reasoning, confidence scoring,
and reasoning flow control.
"""
from typing import Dict, Optional, Any, Literal, Callable, List
from dataclasses import dataclass, field
from enum import Enum
import uuid
import time
import logging
logger = logging.getLogger(__name__)
class ActionState(Enum):
"""Action states for reasoning flow control."""
CONTINUE = "continue"
VALIDATE = "validate"
RESET = "reset"
FINAL_ANSWER = "final_answer"
@dataclass
class ReasoningStep:
"""Represents a single step in the reasoning process."""
id: str = field(default_factory=lambda: str(uuid.uuid4())[:8])
title: str = ""
action: str = ""
thought: str = ""
confidence: float = 0.0
timestamp: float = field(default_factory=time.time)
retries: int = 0
validation_passed: bool = False
metadata: Dict[str, Any] = field(default_factory=dict)
@dataclass
class ReasoningConfig:
"""Configuration for reasoning capabilities."""
min_steps: int = 2
max_steps: int = 10
style: Literal["analytical", "creative", "systematic"] = "analytical"
confidence_threshold: float = 0.7
enable_validation: bool = True
enable_improvement: bool = True
enable_error_detection: bool = True
temperature: float = 0.3
max_retries: int = 3
validation_model: Optional[str] = None
system_prompt: Optional[str] = None
@dataclass
class ReasoningFlow:
"""Configuration for reasoning flow control."""
on_validate: Optional[Callable[[ReasoningStep], bool]] = None
on_reset: Optional[Callable[[ReasoningStep], bool]] = None
auto_validate_critical: bool = True
validation_rules: Dict[str, Callable] = field(default_factory=dict)
def should_validate(self, step: ReasoningStep) -> bool:
"""Determine if a step should be validated."""
if self.on_validate:
return self.on_validate(step)
return step.confidence < 0.9
def should_reset(self, step: ReasoningStep) -> bool:
"""Determine if reasoning should reset."""
if self.on_reset:
return self.on_reset(step)
return step.retries >= 3
def apply_validation_rules(self, step: ReasoningStep) -> bool:
"""Apply custom validation rules to a step."""
for rule_name, rule_func in self.validation_rules.items():
try:
if not rule_func(step):
logger.debug(f"Validation rule '{rule_name}' failed for step {step.id}")
return False
except Exception as e:
logger.warning(f"Validation rule '{rule_name}' raised exception: {e}")
return False
return True
class ReasoningEngine:
"""Core reasoning engine that orchestrates the reasoning process."""
def __init__(self, config: ReasoningConfig, flow: Optional[ReasoningFlow] = None):
self.config = config
self.flow = flow or ReasoningFlow()
self.reasoning_steps: List[ReasoningStep] = []
self.current_step = 0
def create_step(self, title: str, action: str, thought: str = "", confidence: float = 0.0) -> ReasoningStep:
"""Create a new reasoning step."""
step = ReasoningStep(
title=title,
action=action,
thought=thought,
confidence=confidence
)
self.reasoning_steps.append(step)
return step
def update_step(self, step_id: str, **kwargs) -> bool:
"""Update an existing reasoning step."""
for step in self.reasoning_steps:
if step.id == step_id:
for key, value in kwargs.items():
if hasattr(step, key):
setattr(step, key, value)
return True
return False
def get_step(self, step_id: str) -> Optional[ReasoningStep]:
"""Get a reasoning step by ID."""
for step in self.reasoning_steps:
if step.id == step_id:
return step
return None
def validate_step(self, step: ReasoningStep) -> bool:
"""Validate a reasoning step."""
if not self.config.enable_validation:
return True
# Check confidence threshold
if step.confidence < self.config.confidence_threshold:
logger.debug(f"Step {step.id} failed confidence threshold: {step.confidence} < {self.config.confidence_threshold}")
return False
# Apply flow validation rules
if not self.flow.apply_validation_rules(step):
return False
step.validation_passed = True
return True
def should_continue(self) -> ActionState:
"""Determine the next action based on current reasoning state."""
if len(self.reasoning_steps) < self.config.min_steps:
return ActionState.CONTINUE
if len(self.reasoning_steps) >= self.config.max_steps:
return ActionState.FINAL_ANSWER
last_step = self.reasoning_steps[-1] if self.reasoning_steps else None
if last_step:
if self.flow.should_reset(last_step):
return ActionState.RESET
elif self.flow.should_validate(last_step):
return ActionState.VALIDATE
return ActionState.CONTINUE
def get_reasoning_trace(self) -> List[Dict[str, Any]]:
"""Get the full reasoning trace as a serializable format."""
return [
{
"id": step.id,
"title": step.title,
"action": step.action,
"thought": step.thought,
"confidence": step.confidence,
"timestamp": step.timestamp,
"retries": step.retries,
"validation_passed": step.validation_passed,
"metadata": step.metadata
}
for step in self.reasoning_steps
]
def reset(self):
"""Reset the reasoning engine."""
self.reasoning_steps.clear()
self.current_step = 0
def get_reasoning_summary(self) -> Dict[str, Any]:
"""Get a summary of the reasoning process."""
if not self.reasoning_steps:
return {
"total_steps": 0,
"avg_confidence": 0.0,
"total_retries": 0,
"validation_rate": 0.0
}
total_confidence = sum(step.confidence for step in self.reasoning_steps)
total_retries = sum(step.retries for step in self.reasoning_steps)
validated_steps = sum(1 for step in self.reasoning_steps if step.validation_passed)
return {
"total_steps": len(self.reasoning_steps),
"avg_confidence": total_confidence / len(self.reasoning_steps),
"total_retries": total_retries,
"validation_rate": validated_steps / len(self.reasoning_steps),
"min_confidence": min(step.confidence for step in self.reasoning_steps),
"max_confidence": max(step.confidence for step in self.reasoning_steps)
}
@@ -0,0 +1,400 @@
"""
ReasoningAgent - Enhanced agent with advanced reasoning capabilities.
This agent extends the base Agent class with structured reasoning, confidence
scoring, and step-by-step problem solving using the existing Chain of Thought
infrastructure.
"""
import logging
from typing import Optional, Dict, Any, Union, List, Callable, Tuple
from .agent import Agent
from .reasoning import ReasoningConfig, ReasoningFlow, ReasoningEngine, ActionState
from ..tools.train.data.generatecot import GenerateCOT
logger = logging.getLogger(__name__)
class ReasoningAgent(Agent):
"""
Agent with enhanced reasoning capabilities including step-by-step thinking,
confidence scoring, and reasoning flow control.
Inherits all functionality from the base Agent class while adding:
- Configurable reasoning steps
- Confidence-based validation
- Chain of thought integration
- Reasoning flow control
"""
def __init__(
self,
name: Optional[str] = None,
role: Optional[str] = None,
goal: Optional[str] = None,
backstory: Optional[str] = None,
instructions: Optional[str] = None,
llm: Optional[Union[str, Any]] = None,
tools: Optional[List[Any]] = None,
# Reasoning-specific parameters
reasoning: bool = True,
reasoning_config: Optional[Union[ReasoningConfig, Dict[str, Any]]] = None,
reasoning_flow: Optional[ReasoningFlow] = None,
min_confidence: float = 0.7,
show_reasoning: bool = True,
# Pass through all other Agent parameters
**kwargs
):
"""
Initialize a ReasoningAgent.
Args:
reasoning: Enable reasoning capabilities (default: True)
reasoning_config: Configuration for reasoning behavior
reasoning_flow: Flow control for reasoning process
min_confidence: Minimum confidence threshold for steps
show_reasoning: Whether to display reasoning steps
**kwargs: All other parameters passed to base Agent
"""
# Force enable reasoning steps and self-reflection if reasoning is enabled
if reasoning:
kwargs.setdefault('reasoning_steps', True)
kwargs.setdefault('self_reflect', True)
# Initialize base Agent
super().__init__(
name=name or "ReasoningAgent",
role=role,
goal=goal,
backstory=backstory,
instructions=instructions,
llm=llm,
tools=tools,
**kwargs
)
# Setup reasoning capabilities
self.reasoning_enabled = reasoning
self.min_confidence = min_confidence
self.show_reasoning = show_reasoning
# Initialize reasoning config
if isinstance(reasoning_config, dict):
self.reasoning_config = ReasoningConfig(**reasoning_config)
elif reasoning_config is None:
self.reasoning_config = ReasoningConfig(
confidence_threshold=min_confidence,
style="analytical"
)
else:
self.reasoning_config = reasoning_config
# Initialize reasoning flow
self.reasoning_flow = reasoning_flow or ReasoningFlow()
# Initialize reasoning engine
self.reasoning_engine = ReasoningEngine(
config=self.reasoning_config,
flow=self.reasoning_flow
)
# Initialize Chain of Thought generator
self.cot_generator = None
if self.reasoning_enabled:
try:
self.cot_generator = GenerateCOT(
model=self.llm if isinstance(self.llm, str) else "gpt-4o-mini",
temperature=self.reasoning_config.temperature,
verbose=self.show_reasoning
)
except Exception as e:
logger.warning(f"Could not initialize COT generator: {e}")
# Store reasoning trace
self.last_reasoning_steps = []
self.reasoning_trace = []
def chat(
self,
message: str,
temperature: float = 0.1,
tools: Optional[List[Any]] = None,
task_name: Optional[str] = None,
task_description: Optional[str] = None,
task_id: Optional[str] = None
) -> str:
"""
Enhanced chat method with reasoning capabilities.
Args:
message: The input message/problem to solve
temperature: Temperature for LLM calls
tools: Available tools for the agent
task_name: Optional task name for context
task_description: Optional task description
task_id: Optional task ID for tracking
Returns:
Response with reasoning trace included
"""
if not self.reasoning_enabled:
return super().chat(message, temperature, tools, task_name, task_description, task_id)
try:
# Reset reasoning engine for new conversation
self.reasoning_engine.reset()
# Generate reasoning-enhanced response
response = self._chat_with_reasoning(
message, temperature, tools, task_name, task_description, task_id
)
# Store reasoning trace
self.last_reasoning_steps = self.reasoning_engine.reasoning_steps.copy()
self.reasoning_trace.append({
"message": message,
"response": response,
"steps": self.reasoning_engine.get_reasoning_trace(),
"summary": self.reasoning_engine.get_reasoning_summary()
})
return response
except Exception as e:
logger.error(f"Error in reasoning chat: {e}")
# Fallback to base agent chat
return super().chat(message, temperature, tools, task_name, task_description, task_id)
def _chat_with_reasoning(
self,
message: str,
temperature: float,
tools: Optional[List[Any]],
task_name: Optional[str],
task_description: Optional[str],
task_id: Optional[str]
) -> str:
"""
Internal method to handle reasoning-enhanced chat.
"""
# Initial reasoning step: problem analysis
analysis_step = self.reasoning_engine.create_step(
title="Problem Analysis",
action="Analyze the problem and break it down",
thought=f"Analyzing: {message}"
)
# Use Chain of Thought if available
if self.cot_generator:
try:
cot_result = self.cot_generator.cot_generate_dict(message)
analysis_step.thought = cot_result.get("thought_process", "")
analysis_step.confidence = 0.8 # Default confidence for COT
# Update step with COT results
self.reasoning_engine.update_step(
analysis_step.id,
thought=analysis_step.thought,
confidence=analysis_step.confidence
)
except Exception as e:
logger.warning(f"COT generation failed: {e}")
analysis_step.confidence = 0.5
# Determine next action
action_state = self.reasoning_engine.should_continue()
# Process based on action state
response = ""
step_count = 0
max_reasoning_steps = self.reasoning_config.max_steps
while step_count < max_reasoning_steps:
step_count += 1
if action_state == ActionState.FINAL_ANSWER:
# Generate final response using base agent
response = super().chat(
self._build_reasoning_prompt(message),
temperature,
tools,
task_name,
task_description,
task_id
)
# Create final step
final_step = self.reasoning_engine.create_step(
title="Final Answer",
action="Generate final response",
thought=response,
confidence=0.9
)
break
elif action_state == ActionState.VALIDATE:
# Validate current reasoning
last_step = self.reasoning_engine.reasoning_steps[-1]
is_valid = self.reasoning_engine.validate_step(last_step)
if is_valid:
action_state = ActionState.CONTINUE
else:
# Retry or improve
last_step.retries += 1
if last_step.retries >= self.reasoning_config.max_retries:
action_state = ActionState.RESET
else:
action_state = ActionState.CONTINUE
elif action_state == ActionState.RESET:
# Reset reasoning and start over
self.reasoning_engine.reset()
analysis_step = self.reasoning_engine.create_step(
title="Problem Re-analysis",
action="Re-analyze the problem with a different approach",
thought=f"Re-analyzing: {message}",
confidence=0.6
)
action_state = ActionState.CONTINUE
elif action_state == ActionState.CONTINUE:
# Continue with next reasoning step
next_step = self.reasoning_engine.create_step(
title=f"Reasoning Step {step_count}",
action="Continue reasoning process",
thought="",
confidence=0.7
)
# Use base agent for intermediate reasoning
intermediate_prompt = self._build_intermediate_reasoning_prompt(message, step_count)
intermediate_response = super().chat(
intermediate_prompt,
temperature,
tools,
task_name,
task_description,
task_id
)
next_step.thought = intermediate_response
next_step.confidence = self._estimate_confidence(intermediate_response)
# Determine next action
action_state = self.reasoning_engine.should_continue()
# If we haven't generated a response yet, generate it now
if not response:
response = super().chat(
self._build_reasoning_prompt(message),
temperature,
tools,
task_name,
task_description,
task_id
)
# Add reasoning summary to response if enabled
if self.show_reasoning:
reasoning_summary = self._format_reasoning_summary()
response = f"{response}\n\n--- Reasoning Process ---\n{reasoning_summary}"
return response
def _build_reasoning_prompt(self, original_message: str) -> str:
"""Build a prompt that includes reasoning context."""
reasoning_context = ""
if self.reasoning_engine.reasoning_steps:
reasoning_context = "\n\nReasoning Process:\n"
for i, step in enumerate(self.reasoning_engine.reasoning_steps, 1):
reasoning_context += f"{i}. {step.title}: {step.action}\n"
if step.thought:
reasoning_context += f" Thought: {step.thought[:200]}...\n"
reasoning_context += f" Confidence: {step.confidence:.2f}\n\n"
return f"{original_message}{reasoning_context}\n\nBased on the above reasoning, provide your final answer:"
def _build_intermediate_reasoning_prompt(self, original_message: str, step_number: int) -> str:
"""Build a prompt for intermediate reasoning steps."""
return f"""Continue reasoning about this problem step by step:
Original Problem: {original_message}
This is step {step_number} of your reasoning process. Consider what you've analyzed so far and continue with the next logical step. Be specific and show your thinking process.
Focus on: {"analytical thinking" if self.reasoning_config.style == "analytical" else "creative exploration" if self.reasoning_config.style == "creative" else "systematic breakdown"}"""
def _estimate_confidence(self, response: str) -> float:
"""Estimate confidence based on response characteristics."""
# Simple heuristic - can be enhanced
confidence = 0.5
# Increase confidence for longer, more detailed responses
if len(response) > 100:
confidence += 0.1
if len(response) > 300:
confidence += 0.1
# Look for certainty indicators
certainty_words = ["definitely", "clearly", "obviously", "certainly", "confirmed"]
uncertainty_words = ["maybe", "perhaps", "possibly", "might", "unclear"]
certainty_count = sum(1 for word in certainty_words if word in response.lower())
uncertainty_count = sum(1 for word in uncertainty_words if word in response.lower())
confidence += (certainty_count * 0.1) - (uncertainty_count * 0.1)
return max(0.0, min(1.0, confidence))
def _format_reasoning_summary(self) -> str:
"""Format the reasoning process for display."""
if not self.reasoning_engine.reasoning_steps:
return "No reasoning steps recorded."
summary = []
for i, step in enumerate(self.reasoning_engine.reasoning_steps, 1):
summary.append(f"{i}. {step.title}: {step.action} (confidence: {step.confidence:.2f})")
if step.retries > 0:
summary.append(f" └─ Retries: {step.retries}")
stats = self.reasoning_engine.get_reasoning_summary()
summary.append(f"\nSummary: {stats['total_steps']} steps, avg confidence: {stats['avg_confidence']:.2f}")
return "\n".join(summary)
async def achat(
self,
message: str,
temperature: float = 0.1,
tools: Optional[List[Any]] = None,
task_name: Optional[str] = None,
task_description: Optional[str] = None,
task_id: Optional[str] = None
) -> str:
"""Async version of reasoning chat."""
# For now, use sync version - can be enhanced for true async reasoning
return self.chat(message, temperature, tools, task_name, task_description, task_id)
@property
def reasoning_summary(self) -> Dict[str, Any]:
"""Get current reasoning summary."""
return self.reasoning_engine.get_reasoning_summary()
def get_reasoning_trace(self) -> List[Dict[str, Any]]:
"""Get the full reasoning trace for all conversations."""
return self.reasoning_trace.copy()
def clear_reasoning_history(self):
"""Clear reasoning history."""
self.reasoning_trace.clear()
self.last_reasoning_steps.clear()
self.reasoning_engine.reset()
@@ -0,0 +1,358 @@
#!/usr/bin/env python3
"""
Test script for ReasoningAgent and DualBrainAgent implementations.
This script tests the new reasoning capabilities to ensure they work correctly
and maintain backward compatibility with the existing Agent class.
"""
import os
import sys
import logging
# Add the praisonaiagents directory to the path
sys.path.insert(0, '/home/runner/work/PraisonAI/PraisonAI/src/praisonai-agents')
# Test imports
print("Testing imports...")
try:
from praisonaiagents import Agent, ReasoningAgent, DualBrainAgent
from praisonaiagents import ReasoningConfig, ReasoningFlow, ActionState
print("✓ All imports successful")
except ImportError as e:
print(f"✗ Import failed: {e}")
sys.exit(1)
# Set up logging for testing
logging.basicConfig(level=logging.INFO)
def test_basic_agent_compatibility():
"""Test that basic Agent functionality still works."""
print("\n=== Testing Basic Agent Compatibility ===")
try:
agent = Agent(
name="TestAgent",
role="Tester",
goal="Test basic functionality",
llm="gpt-4o-mini"
)
# Test basic chat (without API call for now)
print("✓ Basic Agent creation successful")
print(f" - Agent name: {agent.name}")
print(f" - Agent role: {agent.role}")
print(f" - Agent goal: {agent.goal}")
return True
except Exception as e:
print(f"✗ Basic Agent test failed: {e}")
return False
def test_reasoning_agent_creation():
"""Test ReasoningAgent creation and configuration."""
print("\n=== Testing ReasoningAgent Creation ===")
try:
# Test with default configuration
agent1 = ReasoningAgent(
name="ReasoningAgent1",
role="Problem Solver",
llm="gpt-4o-mini"
)
print("✓ ReasoningAgent with defaults created successfully")
print(f" - Reasoning enabled: {agent1.reasoning_enabled}")
print(f" - Min confidence: {agent1.min_confidence}")
print(f" - Reasoning steps: {agent1.reasoning_steps}")
print(f" - Self reflection: {agent1.self_reflect}")
# Test with custom ReasoningConfig
config = ReasoningConfig(
min_steps=3,
max_steps=8,
style="analytical",
confidence_threshold=0.8
)
agent2 = ReasoningAgent(
name="ReasoningAgent2",
role="Analytical Solver",
reasoning_config=config,
min_confidence=0.85,
llm="gpt-4o-mini"
)
print("✓ ReasoningAgent with custom config created successfully")
print(f" - Min steps: {agent2.reasoning_config.min_steps}")
print(f" - Max steps: {agent2.reasoning_config.max_steps}")
print(f" - Style: {agent2.reasoning_config.style}")
print(f" - Confidence threshold: {agent2.reasoning_config.confidence_threshold}")
# Test with dictionary config
agent3 = ReasoningAgent(
name="ReasoningAgent3",
role="Creative Solver",
reasoning_config={
"min_steps": 2,
"max_steps": 6,
"style": "creative",
"confidence_threshold": 0.7
},
llm="gpt-4o-mini"
)
print("✓ ReasoningAgent with dict config created successfully")
print(f" - Style: {agent3.reasoning_config.style}")
return True
except Exception as e:
print(f"✗ ReasoningAgent creation test failed: {e}")
import traceback
traceback.print_exc()
return False
def test_dual_brain_agent_creation():
"""Test DualBrainAgent creation and configuration."""
print("\n=== Testing DualBrainAgent Creation ===")
try:
# Test with default models
agent1 = DualBrainAgent(
name="DualBrainAgent1",
role="Dual Model Solver"
)
print("✓ DualBrainAgent with defaults created successfully")
model_info = agent1.get_model_info()
print(f" - Conversational model: {model_info['conversational_model']}")
print(f" - Reasoning model: {model_info['reasoning_model']}")
# Test with specified models
agent2 = DualBrainAgent(
name="DualBrainAgent2",
role="Specialized Solver",
llm="gpt-4o-mini",
reasoning_llm="gpt-4o-mini",
llm_config={"temperature": 0.7},
reasoning_config={
"model": "gpt-4o-mini",
"temperature": 0.1,
"system_prompt": "You are a step-by-step analytical reasoner"
}
)
print("✓ DualBrainAgent with custom models created successfully")
model_info = agent2.get_model_info()
print(f" - Conversational model: {model_info['conversational_model']}")
print(f" - Reasoning model: {model_info['reasoning_model']}")
print(f" - LLM config: {model_info['llm_config']}")
return True
except Exception as e:
print(f"✗ DualBrainAgent creation test failed: {e}")
import traceback
traceback.print_exc()
return False
def test_reasoning_engine():
"""Test the reasoning engine functionality."""
print("\n=== Testing Reasoning Engine ===")
try:
config = ReasoningConfig(min_steps=2, max_steps=5)
flow = ReasoningFlow()
agent = ReasoningAgent(
name="EngineTestAgent",
reasoning_config=config,
reasoning_flow=flow,
llm="gpt-4o-mini"
)
# Test reasoning engine methods
engine = agent.reasoning_engine
# Create test steps
step1 = engine.create_step(
title="Analysis",
action="Analyze the problem",
thought="This is a test thought",
confidence=0.8
)
step2 = engine.create_step(
title="Solution",
action="Generate solution",
thought="This is a solution thought",
confidence=0.9
)
print("✓ Reasoning steps created successfully")
print(f" - Step 1 ID: {step1.id}")
print(f" - Step 2 ID: {step2.id}")
# Test validation
is_valid = engine.validate_step(step1)
print(f" - Step 1 validation: {is_valid}")
# Test action state determination
action_state = engine.should_continue()
print(f" - Next action state: {action_state}")
# Test reasoning trace
trace = engine.get_reasoning_trace()
print(f" - Reasoning trace length: {len(trace)}")
# Test summary
summary = engine.get_reasoning_summary()
print(f" - Total steps: {summary['total_steps']}")
print(f" - Avg confidence: {summary['avg_confidence']:.2f}")
return True
except Exception as e:
print(f"✗ Reasoning engine test failed: {e}")
import traceback
traceback.print_exc()
return False
def test_backward_compatibility():
"""Test that existing Agent usage patterns still work."""
print("\n=== Testing Backward Compatibility ===")
try:
# Test that ReasoningAgent can be used like a regular Agent
reasoning_agent = ReasoningAgent(
name="CompatibilityAgent",
role="Test Agent",
reasoning=False, # Disable reasoning to test regular Agent behavior
llm="gpt-4o-mini"
)
# Should behave like regular Agent when reasoning is disabled
print("✓ ReasoningAgent with reasoning disabled created successfully")
print(f" - Reasoning enabled: {reasoning_agent.reasoning_enabled}")
# Test DualBrainAgent with single model (should work like regular agent)
dual_agent = DualBrainAgent(
name="CompatibilityDualAgent",
role="Test Agent",
llm="gpt-4o-mini",
reasoning_llm="gpt-4o-mini", # Same model for both
reasoning=False
)
print("✓ DualBrainAgent with same models created successfully")
return True
except Exception as e:
print(f"✗ Backward compatibility test failed: {e}")
import traceback
traceback.print_exc()
return False
def test_configuration_classes():
"""Test the configuration classes."""
print("\n=== Testing Configuration Classes ===")
try:
# Test ReasoningConfig
config = ReasoningConfig(
min_steps=3,
max_steps=10,
style="systematic",
confidence_threshold=0.8
)
print("✓ ReasoningConfig created successfully")
print(f" - Min steps: {config.min_steps}")
print(f" - Max steps: {config.max_steps}")
print(f" - Style: {config.style}")
# Test ReasoningFlow
def custom_validator(step):
return step.confidence > 0.7
def custom_reset_condition(step):
return step.retries > 2
flow = ReasoningFlow(
on_validate=custom_validator,
on_reset=custom_reset_condition,
auto_validate_critical=True
)
print("✓ ReasoningFlow created successfully")
print(f" - Auto validate critical: {flow.auto_validate_critical}")
# Test ActionState enum
states = [ActionState.CONTINUE, ActionState.VALIDATE, ActionState.RESET, ActionState.FINAL_ANSWER]
print("✓ ActionState enum accessible")
print(f" - Available states: {[state.value for state in states]}")
return True
except Exception as e:
print(f"✗ Configuration classes test failed: {e}")
import traceback
traceback.print_exc()
return False
def run_all_tests():
"""Run all tests and report results."""
print("Starting ReasoningAgent and DualBrainAgent Tests...\n")
tests = [
("Basic Agent Compatibility", test_basic_agent_compatibility),
("ReasoningAgent Creation", test_reasoning_agent_creation),
("DualBrainAgent Creation", test_dual_brain_agent_creation),
("Reasoning Engine", test_reasoning_engine),
("Backward Compatibility", test_backward_compatibility),
("Configuration Classes", test_configuration_classes),
]
results = []
for test_name, test_func in tests:
try:
success = test_func()
results.append((test_name, success))
except Exception as e:
print(f"✗ {test_name} failed with exception: {e}")
results.append((test_name, False))
# Print summary
print("\n" + "="*50)
print("TEST SUMMARY")
print("="*50)
passed = 0
failed = 0
for test_name, success in results:
status = "PASS" if success else "FAIL"
print(f"{status:>6}: {test_name}")
if success:
passed += 1
else:
failed += 1
print(f"\nTotal: {len(results)} tests")
print(f"Passed: {passed}")
print(f"Failed: {failed}")
if failed == 0:
print("\n🎉 All tests passed! Implementation appears to be working correctly.")
else:
print(f"\n⚠️ {failed} test(s) failed. Review the errors above.")
return failed == 0
if __name__ == "__main__":
success = run_all_tests()
sys.exit(0 if success else 1)