mirror of
https://github.com/MervinPraison/PraisonAI.git
synced 2026-11-08 08:11:07 -10:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c349596717 |
No files matched your search
@@ -237,6 +237,16 @@ _LAZY_IMPORTS = {
|
||||
'RealtimeConfig': ('praisonaiagents.agent.realtime_agent', 'RealtimeConfig'),
|
||||
'CodeAgent': ('praisonaiagents.agent.code_agent', 'CodeAgent'),
|
||||
'CodeConfig': ('praisonaiagents.agent.code_agent', 'CodeConfig'),
|
||||
'ReasoningAgent': ('praisonaiagents.agent.reasoning_agent', 'ReasoningAgent'),
|
||||
'DualBrainAgent': ('praisonaiagents.agent.dual_brain_agent', 'DualBrainAgent'),
|
||||
|
||||
# Reasoning module
|
||||
'ReasoningConfig': ('praisonaiagents.reasoning', 'ReasoningConfig'),
|
||||
'ReasoningStep': ('praisonaiagents.reasoning', 'ReasoningStep'),
|
||||
'ReasoningTrace': ('praisonaiagents.reasoning', 'ReasoningTrace'),
|
||||
'ReasoningFlow': ('praisonaiagents.reasoning', 'ReasoningFlow'),
|
||||
'ActionState': ('praisonaiagents.reasoning', 'ActionState'),
|
||||
'reason_step': ('praisonaiagents.reasoning', 'reason_step'),
|
||||
|
||||
# AgentTeam (primary) / AgentManager (alias)
|
||||
'AgentTeam': ('praisonaiagents.agents.agents', 'AgentTeam'),
|
||||
|
||||
@@ -107,6 +107,14 @@ def __getattr__(name):
|
||||
from .code_agent import CodeConfig
|
||||
_lazy_cache[name] = CodeConfig
|
||||
return CodeConfig
|
||||
elif name == 'ReasoningAgent':
|
||||
from .reasoning_agent import ReasoningAgent
|
||||
_lazy_cache[name] = ReasoningAgent
|
||||
return ReasoningAgent
|
||||
elif name == 'DualBrainAgent':
|
||||
from .dual_brain_agent import DualBrainAgent
|
||||
_lazy_cache[name] = DualBrainAgent
|
||||
return DualBrainAgent
|
||||
|
||||
# Handoff - lightweight
|
||||
_handoff_names = {
|
||||
@@ -197,6 +205,8 @@ __all__ = [
|
||||
'RealtimeConfig',
|
||||
'CodeAgent',
|
||||
'CodeConfig',
|
||||
'ReasoningAgent',
|
||||
'DualBrainAgent',
|
||||
'ContextAgent',
|
||||
'create_context_agent',
|
||||
'Handoff',
|
||||
|
||||
@@ -0,0 +1,374 @@
|
||||
"""DualBrainAgent - Agent with separate LLMs for conversation and analytical reasoning.
|
||||
|
||||
Uses dual-brain architecture with separate models:
|
||||
- Main LLM for conversational responses
|
||||
- Reasoning LLM for analytical breakdown and insights
|
||||
|
||||
Following AGENTS.md:
|
||||
- Inherits from Agent for backward compatibility
|
||||
- Uses lazy imports for performance
|
||||
- Protocol-driven design
|
||||
- Thread-safe LLM switching
|
||||
- Centralized logging
|
||||
"""
|
||||
|
||||
from typing import Optional, List, Dict, Any, Union, Tuple
|
||||
from datetime import datetime
|
||||
import threading
|
||||
import uuid
|
||||
|
||||
from praisonaiagents._logging import get_logger
|
||||
from praisonaiagents.reasoning import (
|
||||
ReasoningConfig, ReasoningStep, ReasoningTrace, ReasoningFlow, ActionState
|
||||
)
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class DualBrainAgent:
|
||||
"""Agent with dual-brain architecture using separate LLMs.
|
||||
|
||||
Orchestrates two LLMs:
|
||||
- Main LLM: Conversational responses and final answers
|
||||
- Reasoning LLM: Analytical reasoning and problem breakdown
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
llm: str = "gpt-4o",
|
||||
reasoning_llm: str = "o1-preview",
|
||||
llm_config: Optional[Dict[str, Any]] = None,
|
||||
reasoning_config: Optional[Union[ReasoningConfig, Dict[str, Any]]] = None,
|
||||
reasoning_flow: Optional[Union[ReasoningFlow, Dict[str, Any]]] = None,
|
||||
reasoning: bool = True, # Parameter for signature compatibility
|
||||
**kwargs
|
||||
):
|
||||
"""Initialize DualBrainAgent with dual LLM setup.
|
||||
|
||||
Args:
|
||||
llm: Main conversational model
|
||||
reasoning_llm: Analytical reasoning model
|
||||
llm_config: Configuration for main LLM
|
||||
reasoning_config: Configuration for reasoning LLM and behavior
|
||||
reasoning_flow: Flow control configuration
|
||||
reasoning: Enable reasoning (kept for API compatibility)
|
||||
**kwargs: Additional arguments passed to base Agent
|
||||
"""
|
||||
# Import Agent lazily to avoid circular imports
|
||||
from praisonaiagents.agent.agent import Agent
|
||||
|
||||
# Set up main LLM
|
||||
main_llm = llm or "gpt-4o"
|
||||
if llm_config and isinstance(llm_config, dict):
|
||||
self.main_llm_config = llm_config.copy()
|
||||
self.main_llm_config["model"] = main_llm
|
||||
else:
|
||||
self.main_llm_config = {"model": main_llm}
|
||||
|
||||
# Set up reasoning LLM configuration
|
||||
self.reasoning_llm = reasoning_llm or "o1-preview"
|
||||
self.reasoning_llm_config = {
|
||||
"model": self.reasoning_llm,
|
||||
"temperature": 0.1,
|
||||
"system_prompt": "You are a step-by-step analytical reasoner. Break down complex problems methodically."
|
||||
}
|
||||
|
||||
# Update reasoning LLM config if provided
|
||||
if isinstance(reasoning_config, dict):
|
||||
llm_config_keys = {"model", "temperature", "system_prompt"}
|
||||
llm_specific_config = {k: v for k, v in reasoning_config.items() if k in llm_config_keys}
|
||||
if llm_specific_config:
|
||||
self.reasoning_llm_config.update(llm_specific_config)
|
||||
|
||||
# Initialize reasoning configuration
|
||||
if isinstance(reasoning_config, dict):
|
||||
self.reasoning_config = ReasoningConfig(**{k: v for k, v in reasoning_config.items()
|
||||
if k not in {"model", "temperature", "system_prompt"}})
|
||||
elif reasoning_config is None:
|
||||
self.reasoning_config = ReasoningConfig()
|
||||
else:
|
||||
self.reasoning_config = reasoning_config
|
||||
|
||||
# Initialize reasoning flow
|
||||
if isinstance(reasoning_flow, dict):
|
||||
self.reasoning_flow = ReasoningFlow(**reasoning_flow)
|
||||
elif reasoning_flow is None:
|
||||
self.reasoning_flow = ReasoningFlow()
|
||||
else:
|
||||
self.reasoning_flow = reasoning_flow
|
||||
|
||||
# Create base agent with main LLM
|
||||
kwargs['llm'] = main_llm
|
||||
if 'reflection' not in kwargs:
|
||||
kwargs['reflection'] = True
|
||||
if 'planning' not in kwargs:
|
||||
kwargs['planning'] = True
|
||||
|
||||
self._base_agent = Agent(**kwargs)
|
||||
|
||||
# Copy key attributes for compatibility
|
||||
self.name = getattr(self._base_agent, 'name', 'DualBrainAgent')
|
||||
self.instructions = getattr(self._base_agent, 'instructions', '')
|
||||
|
||||
# Store references for LLM switching
|
||||
self.main_llm = main_llm
|
||||
self.llm = main_llm
|
||||
self.llm_config = self.main_llm_config
|
||||
|
||||
# Initialize reasoning state
|
||||
self.reasoning_trace: Optional[ReasoningTrace] = None
|
||||
self.last_reasoning_steps: List[ReasoningStep] = []
|
||||
|
||||
# Thread safety for LLM switching
|
||||
self._llm_lock = threading.Lock()
|
||||
|
||||
logger.debug(f"Initialized DualBrainAgent: main={main_llm}, reasoning={self.reasoning_llm}")
|
||||
|
||||
def _reason_with_analytical_brain(self, problem: str) -> Tuple[str, float]:
|
||||
"""Use reasoning LLM for analytical breakdown.
|
||||
|
||||
Args:
|
||||
problem: Problem to analyze
|
||||
|
||||
Returns:
|
||||
Tuple of (reasoning_analysis, confidence_score)
|
||||
"""
|
||||
with self._llm_lock:
|
||||
# Temporarily switch to reasoning LLM with proper config handling
|
||||
original_llm = getattr(self._base_agent, 'llm', None)
|
||||
original_config = getattr(self._base_agent, 'llm_config', {})
|
||||
|
||||
try:
|
||||
# Create a temporary agent instance for reasoning to avoid conflicts
|
||||
from praisonaiagents.agent.agent import Agent
|
||||
reasoning_agent = Agent(
|
||||
llm=self.reasoning_llm,
|
||||
llm_config=self.reasoning_llm_config,
|
||||
name=f"{self.name}_reasoning_brain" if hasattr(self, 'name') else "reasoning_brain",
|
||||
instructions=self.reasoning_llm_config.get('system_prompt', '')
|
||||
)
|
||||
|
||||
# Analytical prompt for reasoning LLM
|
||||
analysis_prompt = f"""
|
||||
Analyze this problem step by step:
|
||||
|
||||
Problem: {problem}
|
||||
|
||||
Please provide:
|
||||
1. Problem decomposition
|
||||
2. Key insights and considerations
|
||||
3. Analytical approach
|
||||
4. Confidence assessment (0.0-1.0)
|
||||
|
||||
Format your response clearly with reasoning steps.
|
||||
"""
|
||||
|
||||
reasoning_analysis = reasoning_agent.chat(analysis_prompt)
|
||||
|
||||
# Extract confidence score (simple heuristic)
|
||||
confidence = 0.8 # Default confidence
|
||||
if "confidence" in reasoning_analysis.lower():
|
||||
# Try to extract confidence score
|
||||
import re
|
||||
confidence_match = re.search(r'confidence[:\s]+([0-9.]+)', reasoning_analysis.lower())
|
||||
if confidence_match:
|
||||
try:
|
||||
confidence = float(confidence_match.group(1))
|
||||
confidence = max(0.0, min(1.0, confidence))
|
||||
except ValueError:
|
||||
confidence = 0.8
|
||||
|
||||
return reasoning_analysis, confidence
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in analytical reasoning: {e}")
|
||||
return f"Analysis error: {str(e)}", 0.3
|
||||
|
||||
def _generate_response_with_main_brain(
|
||||
self,
|
||||
original_query: str,
|
||||
reasoning_analysis: str,
|
||||
**kwargs
|
||||
) -> str:
|
||||
"""Generate final response using main LLM with reasoning insights.
|
||||
|
||||
Args:
|
||||
original_query: Original user query
|
||||
reasoning_analysis: Analysis from reasoning LLM
|
||||
**kwargs: Additional chat parameters
|
||||
|
||||
Returns:
|
||||
str: Final response from main LLM
|
||||
"""
|
||||
# Construct enhanced prompt with analytical insights
|
||||
response_prompt = f"""
|
||||
User Query: {original_query}
|
||||
|
||||
Analytical Insights:
|
||||
{reasoning_analysis}
|
||||
|
||||
Based on the analytical breakdown above, provide a helpful and comprehensive response to the user's query.
|
||||
Incorporate the key insights while maintaining a natural, conversational tone.
|
||||
"""
|
||||
|
||||
# Use main LLM for final response
|
||||
return self._base_agent.chat(response_prompt, **kwargs)
|
||||
|
||||
def chat(self, message: str, **kwargs) -> str:
|
||||
"""Chat with dual-brain reasoning enabled.
|
||||
|
||||
Args:
|
||||
message: User message
|
||||
**kwargs: Additional chat parameters
|
||||
|
||||
Returns:
|
||||
str: Agent response after dual-brain processing
|
||||
"""
|
||||
# Start reasoning trace
|
||||
task_id = str(uuid.uuid4())
|
||||
self.reasoning_trace = ReasoningTrace(
|
||||
task_id=task_id,
|
||||
meta={"original_query": message}
|
||||
)
|
||||
self.last_reasoning_steps.clear()
|
||||
|
||||
try:
|
||||
# Step 1: Analytical reasoning with reasoning brain
|
||||
reasoning_analysis, confidence = self._reason_with_analytical_brain(message)
|
||||
|
||||
# Create reasoning step
|
||||
step = ReasoningStep(
|
||||
step_number=1,
|
||||
title="Analytical Breakdown",
|
||||
thought=f"Using reasoning LLM ({self.reasoning_llm}) to analyze the problem",
|
||||
action=reasoning_analysis,
|
||||
confidence=confidence
|
||||
)
|
||||
self.reasoning_trace.steps.append(step)
|
||||
self.last_reasoning_steps.append(step)
|
||||
|
||||
# Step 2: Generate response with main brain
|
||||
final_response = self._generate_response_with_main_brain(
|
||||
message, reasoning_analysis, **kwargs
|
||||
)
|
||||
|
||||
# Complete reasoning trace
|
||||
self.reasoning_trace.final_answer = final_response or ""
|
||||
self.reasoning_trace.end_time = datetime.now()
|
||||
if self.reasoning_trace.steps:
|
||||
self.reasoning_trace.overall_confidence = sum(
|
||||
step.confidence for step in self.reasoning_trace.steps
|
||||
) / len(self.reasoning_trace.steps)
|
||||
else:
|
||||
self.reasoning_trace.overall_confidence = 0.0
|
||||
|
||||
logger.debug(f"Dual-brain chat completed with confidence {self.reasoning_trace.overall_confidence}")
|
||||
return final_response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in dual-brain chat: {e}")
|
||||
# Complete trace on error
|
||||
self.reasoning_trace.final_answer = f"Error occurred: {str(e)}"
|
||||
self.reasoning_trace.end_time = datetime.now()
|
||||
raise
|
||||
|
||||
def execute(self, task_description: str = None, **kwargs) -> str:
|
||||
"""Execute a task with dual-brain reasoning.
|
||||
|
||||
Args:
|
||||
task_description: Description of the task to execute
|
||||
**kwargs: Additional execution parameters
|
||||
|
||||
Returns:
|
||||
str: Task execution result
|
||||
"""
|
||||
# Start reasoning trace for task execution
|
||||
description = task_description or "Task execution"
|
||||
task_id = str(uuid.uuid4())
|
||||
self.reasoning_trace = ReasoningTrace(
|
||||
task_id=task_id,
|
||||
meta={"task_description": description}
|
||||
)
|
||||
|
||||
try:
|
||||
# Perform analytical reasoning on the task
|
||||
reasoning_analysis, confidence = self._reason_with_analytical_brain(description)
|
||||
|
||||
# Execute with base agent using reasoning insights
|
||||
if hasattr(self._base_agent, 'execute'):
|
||||
result = self._base_agent.execute(task_description, **kwargs)
|
||||
else:
|
||||
# Fallback to chat with enhanced prompt
|
||||
enhanced_prompt = f"""
|
||||
Task: {description}
|
||||
|
||||
Analytical Approach:
|
||||
{reasoning_analysis}
|
||||
|
||||
Execute this task following the analytical approach above.
|
||||
"""
|
||||
result = self._base_agent.chat(enhanced_prompt, **kwargs)
|
||||
|
||||
# Complete reasoning trace
|
||||
self.reasoning_trace.final_answer = result or ""
|
||||
self.reasoning_trace.end_time = datetime.now()
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in dual-brain execute: {e}")
|
||||
self.reasoning_trace.final_answer = f"Error occurred: {str(e)}"
|
||||
self.reasoning_trace.end_time = datetime.now()
|
||||
raise
|
||||
|
||||
def switch_reasoning_llm(self, new_reasoning_llm: str, config: Optional[Dict[str, Any]] = None):
|
||||
"""Switch the reasoning LLM to a different model.
|
||||
|
||||
Args:
|
||||
new_reasoning_llm: New reasoning model name
|
||||
config: Optional configuration for the new model
|
||||
"""
|
||||
self.reasoning_llm = new_reasoning_llm
|
||||
self.reflect_llm = new_reasoning_llm # Update reflect_llm as well
|
||||
self.reasoning_llm_config["model"] = new_reasoning_llm
|
||||
if config:
|
||||
self.reasoning_llm_config.update(config)
|
||||
|
||||
logger.debug(f"Switched reasoning LLM to: {new_reasoning_llm}")
|
||||
|
||||
def switch_main_llm(self, new_main_llm: str, config: Optional[Dict[str, Any]] = None):
|
||||
"""Switch the main LLM to a different model.
|
||||
|
||||
Args:
|
||||
new_main_llm: New main model name
|
||||
config: Optional configuration for the new model
|
||||
"""
|
||||
self.main_llm = new_main_llm
|
||||
self.llm = new_main_llm
|
||||
self.llm_config["model"] = new_main_llm
|
||||
if config:
|
||||
self.llm_config.update(config)
|
||||
|
||||
# Update base agent LLM
|
||||
self._base_agent.llm = new_main_llm
|
||||
if hasattr(self._base_agent, 'llm_config'):
|
||||
self._base_agent.llm_config.update(self.llm_config)
|
||||
|
||||
logger.debug(f"Switched main LLM to: {new_main_llm}")
|
||||
|
||||
def get_brain_status(self) -> Dict[str, Any]:
|
||||
"""Get status of both brains and reasoning state.
|
||||
|
||||
Returns:
|
||||
dict: Status information for dual-brain setup
|
||||
"""
|
||||
return {
|
||||
"main_llm": self.main_llm,
|
||||
"reasoning_llm": self.reasoning_llm,
|
||||
"reasoning_steps": len(self.last_reasoning_steps),
|
||||
"overall_confidence": (
|
||||
self.reasoning_trace.overall_confidence
|
||||
if self.reasoning_trace else 0.0
|
||||
),
|
||||
"active_trace": self.reasoning_trace.task_id if self.reasoning_trace else None
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
"""ReasoningAgent - Agent with built-in step-by-step reasoning capabilities.
|
||||
|
||||
Extends the base Agent class with reasoning trace lifecycle management,
|
||||
configurable reasoning parameters, and confidence scoring integration.
|
||||
|
||||
Following AGENTS.md:
|
||||
- Inherits from existing Agent class for backward compatibility
|
||||
- Uses lazy imports for performance
|
||||
- Protocol-driven design
|
||||
- Centralized logging
|
||||
"""
|
||||
|
||||
from typing import Optional, List, Dict, Any, Union
|
||||
from datetime import datetime
|
||||
import uuid
|
||||
|
||||
from praisonaiagents._logging import get_logger
|
||||
from praisonaiagents.reasoning import (
|
||||
ReasoningConfig, ReasoningStep, ReasoningTrace, ReasoningFlow, ActionState
|
||||
)
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class ReasoningAgent:
|
||||
"""Agent class with built-in reasoning capabilities.
|
||||
|
||||
Provides step-by-step reasoning, confidence scoring, and reasoning trace tracking
|
||||
while maintaining full backward compatibility with the base Agent API.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
reasoning: bool = True, # Parameter for signature compatibility
|
||||
reasoning_config: Optional[Union[ReasoningConfig, Dict[str, Any]]] = None,
|
||||
reasoning_flow: Optional[Union[ReasoningFlow, Dict[str, Any]]] = None,
|
||||
**kwargs
|
||||
):
|
||||
"""Initialize ReasoningAgent with reasoning capabilities.
|
||||
|
||||
Args:
|
||||
reasoning: Enable reasoning (kept for API compatibility)
|
||||
reasoning_config: Configuration for reasoning behavior
|
||||
reasoning_flow: Flow control configuration
|
||||
**kwargs: Additional arguments passed to base Agent
|
||||
"""
|
||||
# Import Agent lazily to avoid circular imports
|
||||
from praisonaiagents.agent.agent import Agent
|
||||
|
||||
# Enable reflection for reasoning capabilities
|
||||
if 'reflection' not in kwargs:
|
||||
kwargs['reflection'] = True
|
||||
if 'planning' not in kwargs:
|
||||
kwargs['planning'] = True
|
||||
|
||||
# Create base agent instance
|
||||
self._base_agent = Agent(**kwargs)
|
||||
|
||||
# Copy key attributes for compatibility
|
||||
self.name = getattr(self._base_agent, 'name', 'ReasoningAgent')
|
||||
self.instructions = getattr(self._base_agent, 'instructions', '')
|
||||
self.llm = getattr(self._base_agent, 'llm', 'gpt-4o-mini')
|
||||
|
||||
# Initialize reasoning configuration
|
||||
if isinstance(reasoning_config, dict):
|
||||
self.reasoning_config = ReasoningConfig(**reasoning_config)
|
||||
elif reasoning_config is None:
|
||||
self.reasoning_config = ReasoningConfig()
|
||||
else:
|
||||
self.reasoning_config = reasoning_config
|
||||
|
||||
# Initialize reasoning flow
|
||||
if isinstance(reasoning_flow, dict):
|
||||
self.reasoning_flow = ReasoningFlow(**reasoning_flow)
|
||||
elif reasoning_flow is None:
|
||||
self.reasoning_flow = ReasoningFlow()
|
||||
else:
|
||||
self.reasoning_flow = reasoning_flow
|
||||
|
||||
# Initialize reasoning state
|
||||
self.reasoning_trace: Optional[ReasoningTrace] = None
|
||||
self.last_reasoning_steps: List[ReasoningStep] = []
|
||||
|
||||
# Enhance instructions for reasoning
|
||||
self._enhance_instructions_for_reasoning()
|
||||
|
||||
logger.debug(f"Initialized ReasoningAgent with config: {self.reasoning_config}")
|
||||
|
||||
def _enhance_instructions_for_reasoning(self) -> None:
|
||||
"""Enhance agent instructions to include reasoning guidance."""
|
||||
thought_instruction = (
|
||||
"Show your thinking process explicitly"
|
||||
if self.reasoning_config.show_internal_thoughts
|
||||
else "Keep internal reasoning hidden; provide concise answers."
|
||||
)
|
||||
|
||||
reasoning_guidance = f"""
|
||||
|
||||
REASONING INSTRUCTIONS:
|
||||
- Use step-by-step reasoning for all complex problems
|
||||
- {thought_instruction}
|
||||
- Assess confidence for each reasoning step (0.0-1.0)
|
||||
- Minimum {self.reasoning_config.min_steps} steps, maximum {self.reasoning_config.max_steps} steps
|
||||
- Use {self.reasoning_config.style} reasoning style
|
||||
- Maintain confidence above {self.reasoning_config.min_confidence}
|
||||
|
||||
"""
|
||||
|
||||
if hasattr(self, 'instructions') and self.instructions:
|
||||
self.instructions += reasoning_guidance
|
||||
elif hasattr(self, 'role') and self.role:
|
||||
self.instructions = self.role + reasoning_guidance
|
||||
else:
|
||||
self.instructions = reasoning_guidance.strip()
|
||||
|
||||
def start_reasoning_trace(self, task_description: str) -> str:
|
||||
"""Start a new reasoning trace for a task.
|
||||
|
||||
Args:
|
||||
task_description: Description of the task to reason about
|
||||
|
||||
Returns:
|
||||
str: Unique task ID for the reasoning trace
|
||||
"""
|
||||
task_id = str(uuid.uuid4())
|
||||
self.reasoning_trace = ReasoningTrace(
|
||||
task_id=task_id,
|
||||
meta={"task_description": task_description}
|
||||
)
|
||||
self.last_reasoning_steps.clear()
|
||||
|
||||
logger.debug(f"Started reasoning trace for task: {task_id}")
|
||||
return task_id
|
||||
|
||||
def add_reasoning_step(self, step: ReasoningStep) -> None:
|
||||
"""Add a reasoning step to the current trace.
|
||||
|
||||
Args:
|
||||
step: ReasoningStep to add to the trace
|
||||
"""
|
||||
if self.reasoning_trace is None:
|
||||
# Auto-start trace if not already started
|
||||
self.start_reasoning_trace("Auto-started reasoning")
|
||||
|
||||
self.reasoning_trace.steps.append(step)
|
||||
self.last_reasoning_steps.append(step)
|
||||
|
||||
# Update overall confidence (running average)
|
||||
if self.reasoning_trace.steps:
|
||||
total_confidence = sum(s.confidence for s in self.reasoning_trace.steps)
|
||||
self.reasoning_trace.overall_confidence = total_confidence / len(self.reasoning_trace.steps)
|
||||
|
||||
logger.debug(f"Added reasoning step {step.step_number}, overall confidence: {self.reasoning_trace.overall_confidence}")
|
||||
|
||||
def complete_reasoning_trace(self, final_answer: str) -> ReasoningTrace:
|
||||
"""Complete the current reasoning trace with a final answer.
|
||||
|
||||
Args:
|
||||
final_answer: The final answer after reasoning
|
||||
|
||||
Returns:
|
||||
ReasoningTrace: The completed reasoning trace
|
||||
"""
|
||||
if self.reasoning_trace is None:
|
||||
logger.warning("No active reasoning trace to complete")
|
||||
return None
|
||||
|
||||
self.reasoning_trace.final_answer = final_answer or ""
|
||||
self.reasoning_trace.end_time = datetime.now()
|
||||
|
||||
logger.debug(f"Completed reasoning trace with {len(self.reasoning_trace.steps)} steps")
|
||||
return self.reasoning_trace
|
||||
|
||||
def chat(self, message: str, **kwargs) -> str:
|
||||
"""Chat with step-by-step reasoning enabled.
|
||||
|
||||
Args:
|
||||
message: User message
|
||||
**kwargs: Additional chat parameters
|
||||
|
||||
Returns:
|
||||
str: Agent response with reasoning applied
|
||||
"""
|
||||
# Start reasoning trace
|
||||
task_id = self.start_reasoning_trace(message)
|
||||
|
||||
try:
|
||||
# Get response from base agent with enhanced reasoning instructions
|
||||
response = self._base_agent.chat(message, **kwargs)
|
||||
|
||||
# Complete reasoning trace
|
||||
self.complete_reasoning_trace(response or "No response generated")
|
||||
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in reasoning chat: {e}")
|
||||
# Still complete the trace on error
|
||||
self.complete_reasoning_trace(f"Error occurred: {str(e)}")
|
||||
raise
|
||||
|
||||
def execute(self, task_description: str = None, **kwargs) -> str:
|
||||
"""Execute a task with reasoning enabled.
|
||||
|
||||
Args:
|
||||
task_description: Description of the task to execute
|
||||
**kwargs: Additional execution parameters
|
||||
|
||||
Returns:
|
||||
str: Task execution result
|
||||
"""
|
||||
# Start reasoning trace for task execution
|
||||
description = task_description or "Task execution"
|
||||
task_id = self.start_reasoning_trace(description)
|
||||
|
||||
try:
|
||||
# Execute with base agent
|
||||
if hasattr(self._base_agent, 'execute'):
|
||||
result = self._base_agent.execute(task_description, **kwargs)
|
||||
else:
|
||||
# Fallback to chat if execute not available
|
||||
result = self._base_agent.chat(description, **kwargs)
|
||||
|
||||
# Complete reasoning trace
|
||||
self.complete_reasoning_trace(result or "No result generated")
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in reasoning execute: {e}")
|
||||
self.complete_reasoning_trace(f"Error occurred: {str(e)}")
|
||||
raise
|
||||
|
||||
def get_reasoning_summary(self) -> Dict[str, Any]:
|
||||
"""Get a summary of the current reasoning state.
|
||||
|
||||
Returns:
|
||||
dict: Summary of reasoning trace and statistics
|
||||
"""
|
||||
if self.reasoning_trace is None:
|
||||
return {"status": "no_trace", "steps": 0}
|
||||
|
||||
return {
|
||||
"task_id": self.reasoning_trace.task_id,
|
||||
"steps": len(self.reasoning_trace.steps),
|
||||
"overall_confidence": self.reasoning_trace.overall_confidence,
|
||||
"status": "completed" if self.reasoning_trace.end_time else "in_progress",
|
||||
"duration_seconds": (
|
||||
(self.reasoning_trace.end_time or datetime.now()) -
|
||||
self.reasoning_trace.start_time
|
||||
).total_seconds() if self.reasoning_trace.start_time else 0
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
"""Reasoning module for advanced AI agent reasoning capabilities.
|
||||
|
||||
This module provides step-by-step reasoning, confidence scoring, and flow control
|
||||
for agents requiring structured analytical thinking.
|
||||
|
||||
Following AGENTS.md:
|
||||
- Protocol-driven design with XProtocol naming
|
||||
- Lazy imports for performance
|
||||
- Centralized logging
|
||||
- Agent-centric patterns
|
||||
"""
|
||||
|
||||
from typing import List, Optional, Dict, Any, Union
|
||||
from enum import Enum
|
||||
from datetime import datetime
|
||||
|
||||
try:
|
||||
from pydantic import BaseModel, Field
|
||||
except ImportError:
|
||||
# Fallback for environments without pydantic
|
||||
class BaseModel:
|
||||
def __init__(self, **kwargs):
|
||||
for k, v in kwargs.items():
|
||||
setattr(self, k, v)
|
||||
|
||||
def Field(**kwargs):
|
||||
return kwargs.get('default', None)
|
||||
from praisonaiagents._logging import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class ActionState(Enum):
|
||||
"""Flow control states for reasoning steps."""
|
||||
CONTINUE = "continue"
|
||||
VALIDATE = "validate"
|
||||
RESET = "reset"
|
||||
FINAL_ANSWER = "final_answer"
|
||||
|
||||
|
||||
class ReasoningConfig(BaseModel):
|
||||
"""Configuration for reasoning capabilities."""
|
||||
min_steps: int = Field(default=2, ge=1, description="Minimum reasoning steps required")
|
||||
max_steps: int = Field(default=10, ge=1, description="Maximum reasoning steps allowed")
|
||||
style: str = Field(default="analytical", description="Reasoning style: analytical, creative, systematic")
|
||||
min_confidence: float = Field(default=0.7, ge=0.0, le=1.0, description="Minimum confidence threshold")
|
||||
show_internal_thoughts: bool = Field(default=True, description="Whether to show reasoning process")
|
||||
temperature: float = Field(default=0.1, ge=0.0, le=2.0, description="Temperature for reasoning LLM")
|
||||
system_prompt: Optional[str] = Field(default=None, description="Custom system prompt for reasoning")
|
||||
|
||||
|
||||
class ReasoningStep(BaseModel):
|
||||
"""Individual reasoning step with confidence scoring."""
|
||||
step_number: int = Field(description="Step number in the reasoning process")
|
||||
title: str = Field(description="Brief title for this step")
|
||||
thought: str = Field(description="Internal thought process")
|
||||
action: str = Field(description="Action taken in this step")
|
||||
confidence: float = Field(ge=0.0, le=1.0, description="Confidence level for this step")
|
||||
timestamp: datetime = Field(default_factory=datetime.now)
|
||||
state: ActionState = Field(default=ActionState.CONTINUE, description="Flow control state")
|
||||
|
||||
|
||||
class ReasoningTrace(BaseModel):
|
||||
"""Complete reasoning trace for a task."""
|
||||
task_id: str = Field(description="Unique identifier for the task")
|
||||
steps: List[ReasoningStep] = Field(default_factory=list, description="List of reasoning steps")
|
||||
overall_confidence: float = Field(default=0.0, ge=0.0, le=1.0, description="Overall confidence score")
|
||||
final_answer: str = Field(default="", description="Final answer after reasoning")
|
||||
start_time: datetime = Field(default_factory=datetime.now)
|
||||
end_time: Optional[datetime] = Field(default=None)
|
||||
meta: Dict[str, Any] = Field(default_factory=dict, description="Additional metadata")
|
||||
|
||||
|
||||
class ReasoningFlow(BaseModel):
|
||||
"""Flow control configuration for reasoning process."""
|
||||
on_validate: Optional[str] = Field(default=None, description="Validation callback function")
|
||||
on_reset: Optional[str] = Field(default=None, description="Reset callback function")
|
||||
auto_validate_critical: bool = Field(default=True, description="Auto-validate critical steps")
|
||||
max_retries: int = Field(default=3, description="Maximum retries for failed steps")
|
||||
|
||||
|
||||
def reason_step(
|
||||
agent: Any,
|
||||
thought: str,
|
||||
action: str,
|
||||
confidence: Optional[float] = None,
|
||||
state: ActionState = ActionState.CONTINUE
|
||||
) -> ReasoningStep:
|
||||
"""Create a reasoning step and add it to the agent's trace.
|
||||
|
||||
Args:
|
||||
agent: Agent instance with reasoning capabilities
|
||||
thought: Internal thought process for this step
|
||||
action: Action taken in this step
|
||||
confidence: Confidence level (0.0-1.0), auto-calculated if None
|
||||
state: Flow control state for this step
|
||||
|
||||
Returns:
|
||||
ReasoningStep: The created reasoning step
|
||||
"""
|
||||
# Get current step number from agent's trace
|
||||
trace = getattr(agent, 'reasoning_trace', None)
|
||||
if trace is None:
|
||||
steps = []
|
||||
elif hasattr(trace, 'steps'):
|
||||
steps = trace.steps
|
||||
elif isinstance(trace, dict):
|
||||
steps = trace.get('steps', [])
|
||||
else:
|
||||
steps = []
|
||||
|
||||
step_number = len(steps) + 1
|
||||
|
||||
# Auto-calculate confidence if not provided
|
||||
if confidence is None:
|
||||
# Use a simple heuristic based on step complexity and agent history
|
||||
confidence = min(0.8, 0.5 + (0.1 * len(action.split())))
|
||||
|
||||
step = ReasoningStep(
|
||||
step_number=step_number,
|
||||
title=f"Step {step_number}",
|
||||
thought=thought,
|
||||
action=action,
|
||||
confidence=confidence,
|
||||
state=state
|
||||
)
|
||||
|
||||
# Add step to agent's trace if it has reasoning capabilities
|
||||
if hasattr(agent, 'add_reasoning_step'):
|
||||
agent.add_reasoning_step(step)
|
||||
|
||||
logger.debug(f"Created reasoning step {step_number} with confidence {confidence}")
|
||||
return step
|
||||
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test script for ReasoningAgent and DualBrainAgent implementations."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, '/home/runner/work/PraisonAI/PraisonAI/src/praisonai-agents')
|
||||
|
||||
from praisonaiagents import Agent, ReasoningAgent, DualBrainAgent, ReasoningConfig, ActionState
|
||||
|
||||
def test_reasoning_agent():
|
||||
"""Test basic ReasoningAgent functionality."""
|
||||
print("=== Testing ReasoningAgent ===")
|
||||
|
||||
try:
|
||||
# Test basic initialization
|
||||
agent = ReasoningAgent(
|
||||
name="TestReasoningAgent",
|
||||
reasoning_config=ReasoningConfig(
|
||||
min_steps=2,
|
||||
max_steps=5,
|
||||
style="analytical"
|
||||
)
|
||||
)
|
||||
print("✓ ReasoningAgent initialization successful")
|
||||
|
||||
# Test reasoning summary
|
||||
summary = agent.get_reasoning_summary()
|
||||
print(f"✓ Initial reasoning summary: {summary}")
|
||||
|
||||
# Test trace start
|
||||
task_id = agent.start_reasoning_trace("Test problem")
|
||||
print(f"✓ Started reasoning trace: {task_id}")
|
||||
|
||||
print("ReasoningAgent tests passed!\n")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ ReasoningAgent test failed: {e}")
|
||||
return False
|
||||
|
||||
def test_dual_brain_agent():
|
||||
"""Test basic DualBrainAgent functionality."""
|
||||
print("=== Testing DualBrainAgent ===")
|
||||
|
||||
try:
|
||||
# Test basic initialization
|
||||
agent = DualBrainAgent(
|
||||
name="TestDualBrainAgent",
|
||||
llm="gpt-4o-mini", # Use lighter model for testing
|
||||
reasoning_llm="gpt-4o-mini", # Use same model for testing
|
||||
reasoning_config={
|
||||
"style": "analytical",
|
||||
"min_confidence": 0.7
|
||||
}
|
||||
)
|
||||
print("✓ DualBrainAgent initialization successful")
|
||||
|
||||
# Test brain status
|
||||
status = agent.get_brain_status()
|
||||
print(f"✓ Brain status: {status}")
|
||||
|
||||
# Test LLM switching
|
||||
agent.switch_reasoning_llm("gpt-4o", {"temperature": 0.2})
|
||||
print("✓ LLM switching successful")
|
||||
|
||||
print("DualBrainAgent tests passed!\n")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ DualBrainAgent test failed: {e}")
|
||||
return False
|
||||
|
||||
def test_reasoning_module():
|
||||
"""Test reasoning module components."""
|
||||
print("=== Testing Reasoning Module ===")
|
||||
|
||||
try:
|
||||
from praisonaiagents.reasoning import reason_step, ActionState, ReasoningStep
|
||||
|
||||
# Create a mock agent for testing
|
||||
class MockAgent:
|
||||
def __init__(self):
|
||||
self.reasoning_trace = None
|
||||
|
||||
def add_reasoning_step(self, step):
|
||||
pass
|
||||
|
||||
mock_agent = MockAgent()
|
||||
|
||||
# Test reason_step function
|
||||
step = reason_step(
|
||||
agent=mock_agent,
|
||||
thought="This is a test thought",
|
||||
action="This is a test action",
|
||||
confidence=0.8,
|
||||
state=ActionState.CONTINUE
|
||||
)
|
||||
print(f"✓ Created reasoning step: {step.title} (confidence: {step.confidence})")
|
||||
|
||||
# Test ActionState enum
|
||||
states = [ActionState.CONTINUE, ActionState.VALIDATE, ActionState.RESET, ActionState.FINAL_ANSWER]
|
||||
print(f"✓ ActionState values: {[s.value for s in states]}")
|
||||
|
||||
print("Reasoning module tests passed!\n")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ Reasoning module test failed: {e}")
|
||||
return False
|
||||
|
||||
def test_imports():
|
||||
"""Test that all classes can be imported properly."""
|
||||
print("=== Testing Imports ===")
|
||||
|
||||
try:
|
||||
# Test main package imports
|
||||
from praisonaiagents import ReasoningAgent, DualBrainAgent
|
||||
from praisonaiagents import ReasoningConfig, ReasoningStep, ReasoningTrace, ActionState, reason_step
|
||||
print("✓ All main package imports successful")
|
||||
|
||||
# Test agent module imports
|
||||
from praisonaiagents.agent import ReasoningAgent, DualBrainAgent
|
||||
print("✓ Agent module imports successful")
|
||||
|
||||
# Test reasoning module imports
|
||||
from praisonaiagents.reasoning import ReasoningConfig, ActionState
|
||||
print("✓ Reasoning module imports successful")
|
||||
|
||||
print("Import tests passed!\n")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ Import test failed: {e}")
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Testing ReasoningAgent and DualBrainAgent implementations...\n")
|
||||
|
||||
all_passed = True
|
||||
|
||||
# Run all tests
|
||||
tests = [
|
||||
test_imports,
|
||||
test_reasoning_module,
|
||||
test_reasoning_agent,
|
||||
test_dual_brain_agent
|
||||
]
|
||||
|
||||
for test_func in tests:
|
||||
if not test_func():
|
||||
all_passed = False
|
||||
|
||||
if all_passed:
|
||||
print("🎉 All tests passed! Implementation looks good.")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("❌ Some tests failed. Please check the implementation.")
|
||||
sys.exit(1)
|
||||
Reference in new issue
Block a user