mirror of
https://github.com/MervinPraison/PraisonAI.git
synced 2026-11-08 08:11:07 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
099e4ccfcb |
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)
|
||||
Reference in new issue
Block a user