Recovered state: integrated TrendSurferAgent, restored frontend/backend files, and cleaned up recovery scripts
This commit is contained in:
73
backend/services/intelligence/agents/__init__.py
Normal file
73
backend/services/intelligence/agents/__init__.py
Normal file
@@ -0,0 +1,73 @@
|
||||
"""
|
||||
ALwrity Autonomous Marketing Agents Module
|
||||
|
||||
This module provides autonomous marketing agents built on txtai's native agent framework.
|
||||
The agents work together to monitor market conditions, analyze competitor activities,
|
||||
and execute coordinated marketing strategies without human intervention.
|
||||
"""
|
||||
|
||||
# Core agent framework
|
||||
from .core_agent_framework import (
|
||||
BaseALwrityAgent,
|
||||
AgentAction,
|
||||
AgentPerformance,
|
||||
StrategyOrchestratorAgent
|
||||
)
|
||||
|
||||
# Market signal detection
|
||||
from .market_signal_detector import (
|
||||
MarketSignal,
|
||||
MarketSignalDetector,
|
||||
MarketTrendAnalyzer
|
||||
)
|
||||
|
||||
# Performance monitoring
|
||||
from .performance_monitor import (
|
||||
PerformanceMonitor,
|
||||
performance_monitor,
|
||||
PerformanceMetric,
|
||||
AgentPerformanceMetrics
|
||||
)
|
||||
|
||||
# Specialized agents
|
||||
from .specialized_agents import (
|
||||
ContentGuardianAgent,
|
||||
LinkGraphAgent,
|
||||
StrategyArchitectAgent,
|
||||
ContentStrategyAgent,
|
||||
CompetitorResponseAgent,
|
||||
SEOOptimizationAgent,
|
||||
SocialAmplificationAgent
|
||||
)
|
||||
|
||||
from .trend_surfer_agent import TrendSurferAgent
|
||||
|
||||
# Agent Orchestrator
|
||||
from .agent_orchestrator import (
|
||||
ALwrityAgentOrchestrator,
|
||||
orchestration_service
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'BaseALwrityAgent',
|
||||
'AgentAction',
|
||||
'AgentPerformance',
|
||||
'StrategyOrchestratorAgent',
|
||||
'MarketSignal',
|
||||
'MarketSignalDetector',
|
||||
'MarketTrendAnalyzer',
|
||||
'PerformanceMonitor',
|
||||
'performance_monitor',
|
||||
'PerformanceMetric',
|
||||
'AgentPerformanceMetrics',
|
||||
'ContentGuardianAgent',
|
||||
'LinkGraphAgent',
|
||||
'StrategyArchitectAgent',
|
||||
'ContentStrategyAgent',
|
||||
'CompetitorResponseAgent',
|
||||
'SEOOptimizationAgent',
|
||||
'SocialAmplificationAgent',
|
||||
'TrendSurferAgent',
|
||||
'ALwrityAgentOrchestrator',
|
||||
'orchestration_service'
|
||||
]
|
||||
429
backend/services/intelligence/agents/agent_orchestrator.py
Normal file
429
backend/services/intelligence/agents/agent_orchestrator.py
Normal file
@@ -0,0 +1,429 @@
|
||||
"""
|
||||
ALwrity Agent Orchestration System
|
||||
Main orchestration system that coordinates all autonomous marketing agents
|
||||
Built on txtai's native agent framework
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Any, Optional
|
||||
from dataclasses import dataclass, asdict
|
||||
|
||||
# txtai imports for native agent framework
|
||||
try:
|
||||
from txtai import Agent, LLM
|
||||
TXTAI_AVAILABLE = Agent.__module__ != "txtai.agent.placeholder"
|
||||
except ImportError:
|
||||
TXTAI_AVAILABLE = False
|
||||
logging.warning("txtai not available, using fallback implementation")
|
||||
|
||||
from utils.logger_utils import get_service_logger
|
||||
from services.intelligence.agents.core_agent_framework import (
|
||||
BaseALwrityAgent, AgentAction, AgentPerformance, StrategyOrchestratorAgent
|
||||
)
|
||||
from services.intelligence.agents.specialized_agents import (
|
||||
ContentStrategyAgent, CompetitorResponseAgent, SEOOptimizationAgent, SocialAmplificationAgent
|
||||
)
|
||||
from services.intelligence.agents.trend_surfer_agent import TrendSurferAgent
|
||||
from services.intelligence.agents.market_signal_detector import (
|
||||
MarketSignal, MarketSignalDetector
|
||||
)
|
||||
from services.intelligence.agents.safety_framework import (
|
||||
SafetyConstraintManager, RollbackManager, UserApprovalSystem, get_safety_framework
|
||||
)
|
||||
from services.intelligence.agents.performance_monitor import (
|
||||
PerformanceMetric, AgentStatus, AgentPerformanceMonitor, performance_service
|
||||
)
|
||||
|
||||
logger = get_service_logger(__name__)
|
||||
|
||||
@dataclass
|
||||
class AgentTeamConfiguration:
|
||||
"""Configuration for the complete agent team"""
|
||||
user_id: str
|
||||
shared_llm: str = "Qwen/Qwen3-4B-Instruct-2507"
|
||||
max_iterations: int = 15
|
||||
enable_safety: bool = True
|
||||
enable_performance_monitoring: bool = True
|
||||
enable_market_signals: bool = True
|
||||
created_at: str = None
|
||||
|
||||
def __post_init__(self):
|
||||
if self.created_at is None:
|
||||
self.created_at = datetime.utcnow().isoformat()
|
||||
|
||||
class ALwrityAgentOrchestrator:
|
||||
"""Main orchestrator for ALwrity autonomous marketing agents"""
|
||||
|
||||
def __init__(self, config: AgentTeamConfiguration):
|
||||
self.config = config
|
||||
self.user_id = config.user_id
|
||||
self.agents: Dict[str, BaseALwrityAgent] = {}
|
||||
self.orchestrator_agent: Optional[Agent] = None
|
||||
self.market_detector: Optional[MarketSignalDetector] = None
|
||||
self.performance_monitor: Optional[AgentPerformanceMonitor] = None
|
||||
self.safety_framework: Optional[Dict[str, Any]] = None
|
||||
|
||||
# Initialize components
|
||||
self._initialize_components()
|
||||
|
||||
logger.info(f"Initialized ALwrityAgentOrchestrator for user: {self.user_id}")
|
||||
|
||||
def _initialize_components(self):
|
||||
"""Initialize all agent system components"""
|
||||
try:
|
||||
# Initialize shared LLM
|
||||
if TXTAI_AVAILABLE:
|
||||
self.llm = LLM(self.config.shared_llm)
|
||||
else:
|
||||
self.llm = None
|
||||
|
||||
# Initialize market signal detector
|
||||
if self.config.enable_market_signals:
|
||||
self.market_detector = MarketSignalDetector(self.user_id)
|
||||
|
||||
# Initialize performance monitoring
|
||||
if self.config.enable_performance_monitoring:
|
||||
self.performance_monitor = AgentPerformanceMonitor(self.user_id)
|
||||
|
||||
# Initialize safety framework
|
||||
if self.config.enable_safety:
|
||||
self.safety_framework = get_safety_framework(self.user_id)
|
||||
|
||||
# Create specialized agents
|
||||
self._create_specialized_agents()
|
||||
|
||||
# Create master orchestrator agent
|
||||
self._create_orchestrator_agent()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error initializing components for user {self.user_id}: {e}")
|
||||
raise e
|
||||
|
||||
def _create_specialized_agents(self):
|
||||
"""Create specialized marketing agents"""
|
||||
try:
|
||||
enabled_by_key = {}
|
||||
db = None
|
||||
try:
|
||||
from services.database import get_session_for_user
|
||||
from models.agent_activity_models import AgentProfile
|
||||
|
||||
db = get_session_for_user(self.user_id)
|
||||
if db:
|
||||
profiles = db.query(AgentProfile).filter(AgentProfile.user_id == self.user_id).all()
|
||||
enabled_by_key = {p.agent_key: bool(p.enabled) for p in profiles if p and p.agent_key and p.enabled is not None}
|
||||
except Exception:
|
||||
enabled_by_key = {}
|
||||
finally:
|
||||
try:
|
||||
if db:
|
||||
db.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Content Strategy Agent
|
||||
if enabled_by_key.get("content_strategist", True):
|
||||
self.content_agent = ContentStrategyAgent(self.user_id, self.config.shared_llm, llm=self.llm)
|
||||
self.agents['content'] = self.content_agent
|
||||
|
||||
# Competitor Response Agent
|
||||
if enabled_by_key.get("competitor_analyst", True):
|
||||
self.competitor_agent = CompetitorResponseAgent(self.user_id, self.config.shared_llm, llm=self.llm)
|
||||
self.agents['competitor'] = self.competitor_agent
|
||||
|
||||
# SEO Optimization Agent
|
||||
if enabled_by_key.get("seo_specialist", True):
|
||||
self.seo_agent = SEOOptimizationAgent(self.user_id, self.config.shared_llm, llm=self.llm)
|
||||
self.agents['seo'] = self.seo_agent
|
||||
|
||||
# Social Amplification Agent
|
||||
if enabled_by_key.get("social_media_manager", True):
|
||||
self.social_agent = SocialAmplificationAgent(self.user_id, self.config.shared_llm, llm=self.llm)
|
||||
self.agents['social'] = self.social_agent
|
||||
|
||||
# Trend Surfer Agent
|
||||
if enabled_by_key.get("trend_surfer", True):
|
||||
# TrendSurferAgent needs TxtaiIntelligenceService, which we might need to get from SIF or initialize
|
||||
# For now, we assume SIF integration is handled elsewhere or we pass a mock/stub if needed
|
||||
# But wait, TrendSurferAgent constructor is (intelligence_service, user_id)
|
||||
# We need to get the intelligence service here.
|
||||
# Since AgentOrchestrator doesn't hold TxtaiIntelligenceService directly (SIFIntegrationService does),
|
||||
# this is tricky.
|
||||
# However, SIFIntegrationService initializes AgentOrchestrator.
|
||||
# Let's import TxtaiIntelligenceService and initialize it here for the agent
|
||||
from services.intelligence.txtai_service import TxtaiIntelligenceService
|
||||
intel_service = TxtaiIntelligenceService(self.user_id)
|
||||
self.trend_surfer_agent = TrendSurferAgent(intel_service, self.user_id)
|
||||
self.agents['trend'] = self.trend_surfer_agent
|
||||
|
||||
logger.info(f"Created {len(self.agents)} specialized agents for user {self.user_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating specialized agents for user {self.user_id}: {e}")
|
||||
raise e
|
||||
|
||||
# Specialized agent creation methods have been moved to specialized_agents.py
|
||||
|
||||
|
||||
def _create_orchestrator_agent(self):
|
||||
"""Create master orchestrator agent using txtai native framework"""
|
||||
try:
|
||||
self.orchestrator_agent = StrategyOrchestratorAgent(
|
||||
user_id=self.user_id,
|
||||
market_detector=self.market_detector,
|
||||
performance_monitor=self.performance_monitor,
|
||||
llm=self.llm
|
||||
)
|
||||
|
||||
# Set sub-agents
|
||||
self.orchestrator_agent.set_sub_agents(self.agents)
|
||||
|
||||
logger.info(f"Created StrategyOrchestratorAgent for user {self.user_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating orchestrator agent: {e}")
|
||||
# Fallback to simple agent if class instantiation fails
|
||||
self.orchestrator_agent = Agent(llm=self.llm)
|
||||
|
||||
async def execute_marketing_strategy(self, market_context: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Execute coordinated marketing strategy using agent team"""
|
||||
try:
|
||||
logger.info(f"Executing marketing strategy for user {self.user_id}")
|
||||
|
||||
# Prepare comprehensive context
|
||||
context = await self._prepare_orchestrator_context(market_context)
|
||||
|
||||
# Execute orchestrator with full team
|
||||
# The StrategyOrchestratorAgent will autonomously delegate tasks to sub-agents
|
||||
instruction = (
|
||||
"Analyze current market conditions and coordinate our marketing team to respond effectively.\n\n"
|
||||
"Please:\n"
|
||||
"1. Analyze the market situation.\n"
|
||||
"2. DELEGATE tasks to specific agents using the 'task_delegator' tool.\n"
|
||||
"3. Synthesize their results into a unified strategy.\n"
|
||||
"4. Provide specific action recommendations.\n\n"
|
||||
"Return a comprehensive strategy with specific actions, priorities, and expected outcomes."
|
||||
)
|
||||
orchestrator_prompt = self.orchestrator_agent.build_task_prompt(instruction=instruction, task_context=context)
|
||||
result = await self.orchestrator_agent.run(orchestrator_prompt)
|
||||
|
||||
# Record performance metrics for the orchestration itself
|
||||
if self.config.enable_performance_monitoring:
|
||||
# We assume the agent's internal tracking handles per-action metrics
|
||||
pass
|
||||
|
||||
logger.info(f"Marketing strategy execution completed for user {self.user_id}")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"strategy": result,
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
# In a real system, we might parse the result to extract structured data
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Agent team execution failed for user {self.user_id}: {e}")
|
||||
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
async def process_market_signals(self) -> List[MarketSignal]:
|
||||
"""Process market signals and generate agent responses"""
|
||||
try:
|
||||
if not self.market_detector:
|
||||
return []
|
||||
|
||||
# Detect market signals
|
||||
signals = await self.market_detector.detect_market_signals()
|
||||
|
||||
logger.info(f"Processed {len(signals)} market signals for user {self.user_id}")
|
||||
|
||||
return signals
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing market signals for user {self.user_id}: {e}")
|
||||
return []
|
||||
|
||||
async def get_agent_status(self) -> Dict[str, Any]:
|
||||
"""Get status of all agents"""
|
||||
try:
|
||||
agent_statuses = {}
|
||||
|
||||
for agent_type, agent in self.agents.items():
|
||||
if hasattr(agent, 'get_current_status'):
|
||||
status = await agent.get_current_status()
|
||||
agent_statuses[agent_type] = status
|
||||
|
||||
# Get performance metrics if available
|
||||
performance_summary = {}
|
||||
if self.performance_monitor:
|
||||
all_performance = self.performance_monitor.get_all_agents_performance()
|
||||
performance_summary = {perf['agent_id']: perf for perf in all_performance}
|
||||
|
||||
return {
|
||||
"user_id": self.user_id,
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"agent_statuses": agent_statuses,
|
||||
"performance_summary": performance_summary,
|
||||
"market_signals_active": self.config.enable_market_signals,
|
||||
"safety_enabled": self.config.enable_safety,
|
||||
"performance_monitoring_enabled": self.config.enable_performance_monitoring
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting agent status for user {self.user_id}: {e}")
|
||||
return {
|
||||
"error": str(e),
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
# Tool implementations for txtai agents have been moved to StrategyOrchestratorAgent class
|
||||
|
||||
|
||||
# Specialized agent tools have been moved to specialized_agents.py
|
||||
|
||||
|
||||
# Helper methods
|
||||
|
||||
async def _prepare_orchestrator_context(self, market_context: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Prepare comprehensive context for orchestrator"""
|
||||
context = {
|
||||
"user_id": self.user_id,
|
||||
"market_conditions": market_context,
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"available_agents": list(self.agents.keys()),
|
||||
"agent_capabilities": self._get_agent_capabilities(),
|
||||
"system_status": await self.get_agent_status()
|
||||
}
|
||||
|
||||
return context
|
||||
|
||||
def _get_agent_capabilities(self) -> Dict[str, List[str]]:
|
||||
"""Get capabilities of each agent type"""
|
||||
return {
|
||||
"content": ["Content analysis", "Gap detection", "Optimization", "Performance tracking"],
|
||||
"competitor": ["Competitor monitoring", "Threat analysis", "Response generation", "Strategy execution"],
|
||||
"seo": ["SEO auditing", "Issue prioritization", "Auto-fixing", "Strategy generation"],
|
||||
"social": ["Social monitoring", "Content adaptation", "Engagement optimization", "Distribution management"],
|
||||
"trend": ["Trend detection", "Opportunity analysis", "Content angle generation"]
|
||||
}
|
||||
|
||||
# Service class for agent orchestration
|
||||
class AgentOrchestrationService:
|
||||
"""Service class for managing agent orchestration"""
|
||||
|
||||
def __init__(self):
|
||||
self.orchestrators: Dict[str, ALwrityAgentOrchestrator] = {}
|
||||
self.execution_history: List[Dict[str, Any]] = []
|
||||
|
||||
logger.info("Initialized AgentOrchestrationService")
|
||||
|
||||
async def get_or_create_orchestrator(self, user_id: str) -> ALwrityAgentOrchestrator:
|
||||
"""Get or create an orchestrator for a user"""
|
||||
if user_id not in self.orchestrators:
|
||||
config = AgentTeamConfiguration(user_id=user_id)
|
||||
self.orchestrators[user_id] = ALwrityAgentOrchestrator(config)
|
||||
logger.info(f"Created new orchestrator for user: {user_id}")
|
||||
|
||||
return self.orchestrators[user_id]
|
||||
|
||||
async def execute_marketing_strategy(self, user_id: str, market_context: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Execute marketing strategy for a user"""
|
||||
try:
|
||||
orchestrator = await self.get_or_create_orchestrator(user_id)
|
||||
result = await orchestrator.execute_marketing_strategy(market_context)
|
||||
|
||||
# Record in history
|
||||
execution_record = {
|
||||
"user_id": user_id,
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"market_context": market_context,
|
||||
"result": result,
|
||||
"success": result.get("success", False)
|
||||
}
|
||||
self.execution_history.append(execution_record)
|
||||
|
||||
# Keep only recent history (last 1000)
|
||||
if len(self.execution_history) > 1000:
|
||||
self.execution_history = self.execution_history[-1000:]
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error executing marketing strategy for user {user_id}: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
async def get_agent_status(self, user_id: str) -> Dict[str, Any]:
|
||||
"""Get agent status for a user"""
|
||||
try:
|
||||
orchestrator = await self.get_or_create_orchestrator(user_id)
|
||||
return await orchestrator.get_agent_status()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting agent status for user {user_id}: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
async def process_market_signals(self, user_id: str) -> List[MarketSignal]:
|
||||
"""Process market signals for a user"""
|
||||
try:
|
||||
orchestrator = await self.get_or_create_orchestrator(user_id)
|
||||
return await orchestrator.process_market_signals()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing market signals for user {user_id}: {e}")
|
||||
return []
|
||||
|
||||
def get_execution_history(self, user_id: str = None, limit: int = 100) -> List[Dict[str, Any]]:
|
||||
"""Get execution history"""
|
||||
if user_id:
|
||||
return [record for record in self.execution_history if record["user_id"] == user_id][-limit:]
|
||||
else:
|
||||
return self.execution_history[-limit:]
|
||||
|
||||
def get_global_performance_stats(self) -> Dict[str, Any]:
|
||||
"""Get global performance statistics"""
|
||||
if not self.execution_history:
|
||||
return {}
|
||||
|
||||
total_executions = len(self.execution_history)
|
||||
successful_executions = len([r for r in self.execution_history if r.get("success", False)])
|
||||
|
||||
unique_users = len(set(r["user_id"] for r in self.execution_history))
|
||||
|
||||
return {
|
||||
"total_executions": total_executions,
|
||||
"successful_executions": successful_executions,
|
||||
"success_rate": successful_executions / total_executions if total_executions > 0 else 0.0,
|
||||
"unique_users": unique_users,
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
# Global service instance
|
||||
orchestration_service = AgentOrchestrationService()
|
||||
|
||||
# Convenience functions for external use
|
||||
async def execute_marketing_strategy(user_id: str, market_context: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Execute marketing strategy for a user"""
|
||||
return await orchestration_service.execute_marketing_strategy(user_id, market_context)
|
||||
|
||||
async def get_agent_system_status(user_id: str) -> Dict[str, Any]:
|
||||
"""Get agent system status for a user"""
|
||||
return await orchestration_service.get_agent_status(user_id)
|
||||
|
||||
async def process_market_signals_for_user(user_id: str) -> List[MarketSignal]:
|
||||
"""Process market signals for a user"""
|
||||
return await orchestration_service.process_market_signals(user_id)
|
||||
1004
backend/services/intelligence/agents/core_agent_framework.py
Normal file
1004
backend/services/intelligence/agents/core_agent_framework.py
Normal file
File diff suppressed because it is too large
Load Diff
250
backend/services/intelligence/agents/market_signal_detector.py
Normal file
250
backend/services/intelligence/agents/market_signal_detector.py
Normal file
@@ -0,0 +1,250 @@
|
||||
"""
|
||||
Market Signal Detection System for ALwrity Autonomous Agents
|
||||
Built on txtai's semantic intelligence and existing monitoring infrastructure
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List, Any, Optional, Set
|
||||
from dataclasses import dataclass, asdict
|
||||
from enum import Enum
|
||||
|
||||
# Integration with existing ALwrity services
|
||||
from services.intelligence.monitoring.semantic_dashboard import RealTimeSemanticMonitor
|
||||
from services.intelligence.semantic_cache import SemanticCacheManager
|
||||
from services.seo_analyzer import ComprehensiveSEOAnalyzer
|
||||
from utils.logger_utils import get_service_logger
|
||||
|
||||
logger = get_service_logger(__name__)
|
||||
|
||||
class SignalType(Enum):
|
||||
"""Types of market signals that agents can detect"""
|
||||
COMPETITOR_CHANGE = "competitor"
|
||||
SERP_FLUCTUATION = "serp"
|
||||
SOCIAL_TREND = "social"
|
||||
INDUSTRY_NEWS = "industry"
|
||||
PERFORMANCE_CHANGE = "performance"
|
||||
CONTENT_GAP = "content_gap"
|
||||
SEO_OPPORTUNITY = "seo_opportunity"
|
||||
|
||||
class UrgencyLevel(Enum):
|
||||
"""Urgency levels for market signals"""
|
||||
LOW = "low"
|
||||
MEDIUM = "medium"
|
||||
HIGH = "high"
|
||||
CRITICAL = "critical"
|
||||
|
||||
@dataclass
|
||||
class MarketSignal:
|
||||
"""Represents a detected market signal"""
|
||||
signal_id: str
|
||||
signal_type: SignalType
|
||||
source: str
|
||||
description: str
|
||||
impact_score: float # 0.0 to 1.0
|
||||
urgency_level: UrgencyLevel
|
||||
confidence_score: float # 0.0 to 1.0
|
||||
related_topics: List[str]
|
||||
suggested_actions: List[str]
|
||||
metadata: Dict[str, Any]
|
||||
detected_at: str = None
|
||||
expires_at: str = None
|
||||
|
||||
def __post_init__(self):
|
||||
if self.detected_at is None:
|
||||
self.detected_at = datetime.utcnow().isoformat()
|
||||
if self.expires_at is None:
|
||||
# Default expiration based on urgency
|
||||
if self.urgency_level == UrgencyLevel.CRITICAL:
|
||||
expires_hours = 1
|
||||
elif self.urgency_level == UrgencyLevel.HIGH:
|
||||
expires_hours = 6
|
||||
elif self.urgency_level == UrgencyLevel.MEDIUM:
|
||||
expires_hours = 24
|
||||
else:
|
||||
expires_hours = 72
|
||||
|
||||
expires = datetime.utcnow().timestamp() + (expires_hours * 60 * 60)
|
||||
self.expires_at = datetime.fromtimestamp(expires).isoformat()
|
||||
|
||||
@dataclass
|
||||
class SignalContext:
|
||||
"""Context for signal detection"""
|
||||
user_id: str
|
||||
competitor_data: Dict[str, Any]
|
||||
semantic_health: Dict[str, Any]
|
||||
seo_performance: Dict[str, Any]
|
||||
content_analysis: Dict[str, Any]
|
||||
historical_data: Dict[str, Any]
|
||||
timestamp: str = None
|
||||
|
||||
def __post_init__(self):
|
||||
if self.timestamp is None:
|
||||
self.timestamp = datetime.utcnow().isoformat()
|
||||
|
||||
class MarketSignalDetector:
|
||||
"""Main market signal detection system"""
|
||||
|
||||
def __init__(self, user_id: str):
|
||||
self.user_id = user_id
|
||||
self.semantic_monitor = RealTimeSemanticMonitor(user_id)
|
||||
self.cache_manager = SemanticCacheManager()
|
||||
self.seo_analyzer = ComprehensiveSEOAnalyzer()
|
||||
|
||||
# Signal detection thresholds
|
||||
self.thresholds = {
|
||||
"competitor_change_threshold": 0.3, # 30% change in competitor metrics
|
||||
"serp_fluctuation_threshold": 0.2, # 20% change in SERP positions
|
||||
"social_trend_threshold": 0.15, # 15% change in social metrics
|
||||
"performance_change_threshold": 0.25, # 25% change in performance metrics
|
||||
"content_gap_threshold": 0.4, # 40% semantic gap
|
||||
"seo_opportunity_threshold": 0.3 # 30% SEO improvement opportunity
|
||||
}
|
||||
|
||||
# Historical data for trend analysis
|
||||
self.signal_history: List[MarketSignal] = []
|
||||
self.baseline_metrics: Dict[str, float] = {}
|
||||
|
||||
logger.info(f"Initialized MarketSignalDetector for user: {user_id}")
|
||||
|
||||
async def detect_market_signals(self) -> List[MarketSignal]:
|
||||
"""Detect all current market signals"""
|
||||
try:
|
||||
logger.info(f"Starting market signal detection for user: {self.user_id}")
|
||||
|
||||
# Get current context
|
||||
context = await self._get_signal_context()
|
||||
|
||||
# Check cache first
|
||||
cache_key = f"market_signals_{self.user_id}"
|
||||
cached_signals = self.cache_manager.get(cache_key)
|
||||
|
||||
if cached_signals and self._is_cache_valid(cached_signals):
|
||||
logger.info(f"Using cached market signals for user: {self.user_id}")
|
||||
return cached_signals
|
||||
|
||||
# Detect signals from multiple sources
|
||||
signals = []
|
||||
|
||||
# Competitor signals
|
||||
competitor_signals = await self._detect_competitor_signals(context)
|
||||
signals.extend(competitor_signals)
|
||||
|
||||
# SERP signals
|
||||
serp_signals = await self._detect_serp_signals(context)
|
||||
signals.extend(serp_signals)
|
||||
|
||||
# Social signals
|
||||
social_signals = await self._detect_social_signals(context)
|
||||
signals.extend(social_signals)
|
||||
|
||||
# Industry signals
|
||||
industry_signals = await self._detect_industry_signals(context)
|
||||
signals.extend(industry_signals)
|
||||
|
||||
# Performance signals
|
||||
performance_signals = await self._detect_performance_signals(context)
|
||||
signals.extend(performance_signals)
|
||||
|
||||
# Content gap signals
|
||||
content_signals = await self._detect_content_gap_signals(context)
|
||||
signals.extend(content_signals)
|
||||
|
||||
# SEO opportunity signals
|
||||
seo_signals = await self._detect_seo_opportunity_signals(context)
|
||||
signals.extend(seo_signals)
|
||||
|
||||
# Filter and prioritize signals
|
||||
filtered_signals = self._filter_signals(signals)
|
||||
prioritized_signals = self._prioritize_signals(filtered_signals)
|
||||
|
||||
# Update history
|
||||
self.signal_history.extend(prioritized_signals)
|
||||
self._trim_signal_history()
|
||||
|
||||
# Cache results
|
||||
self.cache_manager.set(cache_key, prioritized_signals, ttl=300) # 5 minute cache
|
||||
|
||||
logger.info(f"Detected {len(prioritized_signals)} market signals for user: {self.user_id}")
|
||||
|
||||
return prioritized_signals
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error detecting market signals: {str(e)}")
|
||||
return []
|
||||
|
||||
async def _get_signal_context(self) -> SignalContext:
|
||||
"""Fetch current context for signal detection"""
|
||||
# Placeholder implementation
|
||||
return SignalContext(
|
||||
user_id=self.user_id,
|
||||
competitor_data={},
|
||||
semantic_health={},
|
||||
seo_performance={},
|
||||
content_analysis={},
|
||||
historical_data={}
|
||||
)
|
||||
|
||||
def _is_cache_valid(self, signals: List[MarketSignal]) -> bool:
|
||||
"""Check if cached signals are still valid"""
|
||||
if not signals:
|
||||
return False
|
||||
# Basic check for now
|
||||
return True
|
||||
|
||||
async def _detect_competitor_signals(self, context: SignalContext) -> List[MarketSignal]:
|
||||
"""Detect signals from competitor activities"""
|
||||
return []
|
||||
|
||||
async def _detect_serp_signals(self, context: SignalContext) -> List[MarketSignal]:
|
||||
"""Detect signals from SERP changes"""
|
||||
return []
|
||||
|
||||
async def _detect_social_signals(self, context: SignalContext) -> List[MarketSignal]:
|
||||
"""Detect signals from social trends"""
|
||||
return []
|
||||
|
||||
async def _detect_industry_signals(self, context: SignalContext) -> List[MarketSignal]:
|
||||
"""Detect signals from industry news"""
|
||||
return []
|
||||
|
||||
async def _detect_performance_signals(self, context: SignalContext) -> List[MarketSignal]:
|
||||
"""Detect signals from site performance"""
|
||||
return []
|
||||
|
||||
async def _detect_content_gap_signals(self, context: SignalContext) -> List[MarketSignal]:
|
||||
"""Detect signals from content gaps"""
|
||||
return []
|
||||
|
||||
async def _detect_seo_opportunity_signals(self, context: SignalContext) -> List[MarketSignal]:
|
||||
"""Detect signals from SEO opportunities"""
|
||||
return []
|
||||
|
||||
def _filter_signals(self, signals: List[MarketSignal]) -> List[MarketSignal]:
|
||||
"""Filter out low-quality or duplicate signals"""
|
||||
return signals
|
||||
|
||||
def _prioritize_signals(self, signals: List[MarketSignal]) -> List[MarketSignal]:
|
||||
"""Prioritize signals based on impact and urgency"""
|
||||
return sorted(signals, key=lambda x: (x.urgency_level.value, x.impact_score), reverse=True)
|
||||
|
||||
def _trim_signal_history(self):
|
||||
"""Keep signal history within limits"""
|
||||
if len(self.signal_history) > 1000:
|
||||
self.signal_history = self.signal_history[-1000:]
|
||||
|
||||
class MarketTrendAnalyzer:
|
||||
"""
|
||||
Analyzer for detecting market trends from aggregated signals.
|
||||
"""
|
||||
def __init__(self, user_id: str):
|
||||
self.user_id = user_id
|
||||
self.detector = MarketSignalDetector(user_id)
|
||||
|
||||
async def analyze_trends(self, context: Optional[Dict[str, Any]] = None) -> List[MarketSignal]:
|
||||
"""Analyze current market trends"""
|
||||
# Placeholder implementation
|
||||
logger.info(f"Analyzing market trends for user {self.user_id}")
|
||||
return []
|
||||
128
backend/services/intelligence/agents/performance_monitor.py
Normal file
128
backend/services/intelligence/agents/performance_monitor.py
Normal file
@@ -0,0 +1,128 @@
|
||||
"""
|
||||
Agent Performance Monitoring Framework for ALwrity Autonomous Marketing Agents
|
||||
Tracks agent performance, efficiency, and provides optimization recommendations
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List, Any, Optional, Tuple
|
||||
from dataclasses import dataclass, asdict
|
||||
from enum import Enum
|
||||
from collections import defaultdict, deque
|
||||
|
||||
from utils.logger_utils import get_service_logger
|
||||
from services.database import get_session_for_user
|
||||
|
||||
logger = get_service_logger(__name__)
|
||||
|
||||
class AgentStatus(Enum):
|
||||
IDLE = "idle"
|
||||
BUSY = "busy"
|
||||
ERROR = "error"
|
||||
OFFLINE = "offline"
|
||||
INITIALIZING = "initializing"
|
||||
|
||||
class PerformanceMetric(Enum):
|
||||
RESPONSE_TIME = "response_time"
|
||||
SUCCESS_RATE = "success_rate"
|
||||
TOKEN_USAGE = "token_usage"
|
||||
COST_PER_ACTION = "cost_per_action"
|
||||
RESOURCE_UTILIZATION = "resource_utilization"
|
||||
GOAL_COMPLETION_RATE = "goal_completion_rate"
|
||||
|
||||
@dataclass
|
||||
class AgentPerformanceMetrics:
|
||||
agent_id: str
|
||||
timestamp: datetime
|
||||
metrics: Dict[str, float]
|
||||
context: Dict[str, Any]
|
||||
|
||||
class PerformanceMonitor:
|
||||
"""
|
||||
Monitors and analyzes agent performance metrics
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.metrics_buffer = deque(maxlen=1000)
|
||||
self.performance_history = defaultdict(list)
|
||||
self.alert_thresholds = {
|
||||
PerformanceMetric.SUCCESS_RATE: 0.8, # Alert if success rate < 80%
|
||||
PerformanceMetric.RESPONSE_TIME: 30.0, # Alert if response time > 30s
|
||||
PerformanceMetric.GOAL_COMPLETION_RATE: 0.7 # Alert if completion < 70%
|
||||
}
|
||||
|
||||
async def record_metric(self,
|
||||
agent_id: str,
|
||||
metric_type: PerformanceMetric,
|
||||
value: float,
|
||||
context: Optional[Dict[str, Any]] = None):
|
||||
"""Record a performance metric for an agent"""
|
||||
metric_entry = AgentPerformanceMetrics(
|
||||
agent_id=agent_id,
|
||||
timestamp=datetime.utcnow(),
|
||||
metrics={metric_type.value: value},
|
||||
context=context or {}
|
||||
)
|
||||
|
||||
self.metrics_buffer.append(metric_entry)
|
||||
self.performance_history[agent_id].append(metric_entry)
|
||||
|
||||
# Check thresholds
|
||||
await self._check_thresholds(agent_id, metric_type, value)
|
||||
|
||||
# Persist if needed (batching implemented in production)
|
||||
# await self._persist_metric(metric_entry)
|
||||
|
||||
async def get_agent_performance(self, agent_id: str, time_window_minutes: int = 60) -> Dict[str, Any]:
|
||||
"""Get aggregated performance metrics for an agent"""
|
||||
cutoff_time = datetime.utcnow() - timedelta(minutes=time_window_minutes)
|
||||
relevant_metrics = [
|
||||
m for m in self.performance_history[agent_id]
|
||||
if m.timestamp > cutoff_time
|
||||
]
|
||||
|
||||
if not relevant_metrics:
|
||||
return {}
|
||||
|
||||
aggregated = defaultdict(list)
|
||||
for m in relevant_metrics:
|
||||
for k, v in m.metrics.items():
|
||||
aggregated[k].append(v)
|
||||
|
||||
result = {
|
||||
"agent_id": agent_id,
|
||||
"period_minutes": time_window_minutes,
|
||||
"sample_size": len(relevant_metrics),
|
||||
"metrics": {
|
||||
k: sum(v) / len(v) for k, v in aggregated.items()
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
async def _check_thresholds(self, agent_id: str, metric_type: PerformanceMetric, value: float):
|
||||
"""Check if metric violates thresholds"""
|
||||
threshold = self.alert_thresholds.get(metric_type)
|
||||
if not threshold:
|
||||
return
|
||||
|
||||
is_violation = False
|
||||
if metric_type in [PerformanceMetric.SUCCESS_RATE, PerformanceMetric.GOAL_COMPLETION_RATE]:
|
||||
if value < threshold:
|
||||
is_violation = True
|
||||
elif value > threshold:
|
||||
is_violation = True
|
||||
|
||||
if is_violation:
|
||||
logger.warning(
|
||||
f"Performance alert for agent {agent_id}: "
|
||||
f"{metric_type.value} = {value} (Threshold: {threshold})"
|
||||
)
|
||||
# Trigger alert notification (impl via notification service)
|
||||
|
||||
# Singleton instance
|
||||
performance_monitor = PerformanceMonitor()
|
||||
AgentPerformanceMonitor = PerformanceMonitor
|
||||
performance_service = performance_monitor
|
||||
899
backend/services/intelligence/agents/safety_framework.py
Normal file
899
backend/services/intelligence/agents/safety_framework.py
Normal file
@@ -0,0 +1,899 @@
|
||||
"""
|
||||
Agent Safety Framework for ALwrity Autonomous Marketing Agents
|
||||
Implements safety constraints, validation, and rollback mechanisms
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List, Any, Optional, Set
|
||||
from dataclasses import dataclass, asdict
|
||||
from enum import Enum
|
||||
|
||||
from utils.logger_utils import get_service_logger
|
||||
from services.database import get_session_for_user
|
||||
|
||||
logger = get_service_logger(__name__)
|
||||
|
||||
class RiskLevel(Enum):
|
||||
"""Risk levels for agent actions"""
|
||||
LOW = "low"
|
||||
MEDIUM = "medium"
|
||||
HIGH = "high"
|
||||
CRITICAL = "critical"
|
||||
|
||||
class ActionCategory(Enum):
|
||||
"""Categories of agent actions"""
|
||||
CONTENT_MODIFICATION = "content_modification"
|
||||
SEO_OPTIMIZATION = "seo_optimization"
|
||||
COMPETITOR_RESPONSE = "competitor_response"
|
||||
SOCIAL_AMPLIFICATION = "social_amplification"
|
||||
STRATEGY_CHANGE = "strategy_change"
|
||||
SYSTEM_CONFIGURATION = "system_configuration"
|
||||
|
||||
@dataclass
|
||||
class SafetyConstraint:
|
||||
"""Represents a safety constraint for agent actions"""
|
||||
constraint_id: str
|
||||
name: str
|
||||
description: str
|
||||
action_categories: List[ActionCategory]
|
||||
risk_threshold: float # Maximum allowed risk level (0.0 to 1.0)
|
||||
approval_required: bool
|
||||
auto_approval_threshold: float # Risk level below which auto-approval is allowed
|
||||
daily_limit: Optional[int] = None # Maximum actions per day
|
||||
hourly_limit: Optional[int] = None # Maximum actions per hour
|
||||
conditions: Dict[str, Any] = None # Additional conditions for validation
|
||||
created_at: str = None
|
||||
|
||||
def __post_init__(self):
|
||||
if self.created_at is None:
|
||||
self.created_at = datetime.utcnow().isoformat()
|
||||
if self.conditions is None:
|
||||
self.conditions = {}
|
||||
|
||||
@dataclass
|
||||
class ActionCheckpoint:
|
||||
"""Represents a checkpoint for rollback purposes"""
|
||||
checkpoint_id: str
|
||||
action_id: str
|
||||
agent_id: str
|
||||
user_id: str
|
||||
action_type: str
|
||||
action_data: Dict[str, Any]
|
||||
system_state: Dict[str, Any]
|
||||
created_at: str = None
|
||||
|
||||
def __post_init__(self):
|
||||
if self.created_at is None:
|
||||
self.created_at = datetime.utcnow().isoformat()
|
||||
|
||||
@dataclass
|
||||
class SafetyValidation:
|
||||
"""Result of safety validation"""
|
||||
is_valid: bool
|
||||
risk_level: RiskLevel
|
||||
violations: List[str]
|
||||
recommendations: List[str]
|
||||
requires_approval: bool
|
||||
confidence_score: float # 0.0 to 1.0
|
||||
validation_timestamp: str = None
|
||||
|
||||
def __post_init__(self):
|
||||
if self.validation_timestamp is None:
|
||||
self.validation_timestamp = datetime.utcnow().isoformat()
|
||||
|
||||
class SafetyConstraintManager:
|
||||
"""Manages safety constraints for agent actions"""
|
||||
|
||||
def __init__(self, user_id: str):
|
||||
self.user_id = user_id
|
||||
self.constraints: Dict[str, SafetyConstraint] = {}
|
||||
self.action_history: List[Dict[str, Any]] = []
|
||||
self.violation_history: List[Dict[str, Any]] = []
|
||||
|
||||
# Initialize default constraints
|
||||
self._initialize_default_constraints()
|
||||
|
||||
logger.info(f"Initialized SafetyConstraintManager for user: {user_id}")
|
||||
|
||||
def _initialize_default_constraints(self):
|
||||
"""Initialize default safety constraints"""
|
||||
default_constraints = [
|
||||
SafetyConstraint(
|
||||
constraint_id="content_modification_limit",
|
||||
name="Content Modification Daily Limit",
|
||||
description="Limit the number of content modifications per day",
|
||||
action_categories=[ActionCategory.CONTENT_MODIFICATION],
|
||||
risk_threshold=0.7,
|
||||
approval_required=False,
|
||||
auto_approval_threshold=0.3,
|
||||
daily_limit=50,
|
||||
hourly_limit=10
|
||||
),
|
||||
SafetyConstraint(
|
||||
constraint_id="high_risk_approval_required",
|
||||
name="High Risk Action Approval",
|
||||
description="Require approval for high-risk actions",
|
||||
action_categories=[ActionCategory.STRATEGY_CHANGE, ActionCategory.SYSTEM_CONFIGURATION],
|
||||
risk_threshold=0.8,
|
||||
approval_required=True,
|
||||
auto_approval_threshold=0.2
|
||||
),
|
||||
SafetyConstraint(
|
||||
constraint_id="competitor_response_cooldown",
|
||||
name="Competitor Response Cooldown",
|
||||
description="Prevent excessive competitor responses",
|
||||
action_categories=[ActionCategory.COMPETITOR_RESPONSE],
|
||||
risk_threshold=0.6,
|
||||
approval_required=False,
|
||||
auto_approval_threshold=0.4,
|
||||
daily_limit=20,
|
||||
hourly_limit=5
|
||||
),
|
||||
SafetyConstraint(
|
||||
constraint_id="seo_optimization_safety",
|
||||
name="SEO Optimization Safety",
|
||||
description="Ensure SEO optimizations don't harm rankings",
|
||||
action_categories=[ActionCategory.SEO_OPTIMIZATION],
|
||||
risk_threshold=0.5,
|
||||
approval_required=False,
|
||||
auto_approval_threshold=0.3,
|
||||
daily_limit=30,
|
||||
hourly_limit=8
|
||||
),
|
||||
SafetyConstraint(
|
||||
constraint_id="social_amplification_limits",
|
||||
name="Social Amplification Limits",
|
||||
description="Limit social media amplification to prevent spam",
|
||||
action_categories=[ActionCategory.SOCIAL_AMPLIFICATION],
|
||||
risk_threshold=0.6,
|
||||
approval_required=False,
|
||||
auto_approval_threshold=0.4,
|
||||
daily_limit=25,
|
||||
hourly_limit=6
|
||||
)
|
||||
]
|
||||
|
||||
for constraint in default_constraints:
|
||||
self.constraints[constraint.constraint_id] = constraint
|
||||
|
||||
async def validate_action(self, action_data: Dict[str, Any]) -> SafetyValidation:
|
||||
"""Validate an action against safety constraints"""
|
||||
try:
|
||||
logger.info(f"Validating action for user {self.user_id}: {action_data.get('action_type', 'unknown')}")
|
||||
|
||||
violations = []
|
||||
recommendations = []
|
||||
requires_approval = False
|
||||
confidence_score = 1.0
|
||||
|
||||
# Extract action details
|
||||
action_type = action_data.get('action_type', 'unknown')
|
||||
action_category = self._determine_action_category(action_type)
|
||||
risk_score = action_data.get('risk_score', 0.5)
|
||||
impact_score = action_data.get('impact_score', 0.5)
|
||||
|
||||
# Determine risk level
|
||||
risk_level = self._calculate_risk_level(risk_score, impact_score)
|
||||
|
||||
# Check against all relevant constraints
|
||||
for constraint in self.constraints.values():
|
||||
if action_category in constraint.action_categories:
|
||||
constraint_result = await self._check_constraint(constraint, action_data, risk_level)
|
||||
|
||||
if not constraint_result['is_valid']:
|
||||
violations.extend(constraint_result['violations'])
|
||||
confidence_score *= 0.9 # Reduce confidence for violations
|
||||
|
||||
if constraint_result['requires_approval']:
|
||||
requires_approval = True
|
||||
|
||||
recommendations.extend(constraint_result['recommendations'])
|
||||
|
||||
# Check rate limits
|
||||
rate_limit_result = await self._check_rate_limits(action_category, action_data)
|
||||
if not rate_limit_result['is_valid']:
|
||||
violations.extend(rate_limit_result['violations'])
|
||||
confidence_score *= 0.8
|
||||
|
||||
# Check for suspicious patterns
|
||||
pattern_result = await self._check_suspicious_patterns(action_data)
|
||||
if not pattern_result['is_valid']:
|
||||
violations.extend(pattern_result['violations'])
|
||||
confidence_score *= 0.7
|
||||
requires_approval = True # Suspicious patterns always require approval
|
||||
|
||||
# Final validation
|
||||
is_valid = len(violations) == 0 and not requires_approval
|
||||
|
||||
logger.info(f"Action validation completed for user {self.user_id}. Valid: {is_valid}, Risk: {risk_level.value}, Violations: {len(violations)}")
|
||||
|
||||
# Record in history
|
||||
await self._record_validation_history(action_data, is_valid, violations)
|
||||
|
||||
return SafetyValidation(
|
||||
is_valid=is_valid,
|
||||
risk_level=risk_level,
|
||||
violations=violations,
|
||||
recommendations=recommendations,
|
||||
requires_approval=requires_approval,
|
||||
confidence_score=max(0.0, min(1.0, confidence_score))
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error validating action for user {self.user_id}: {e}")
|
||||
|
||||
# Return safe default on error
|
||||
return SafetyValidation(
|
||||
is_valid=False,
|
||||
risk_level=RiskLevel.CRITICAL,
|
||||
violations=["Validation system error"],
|
||||
recommendations=["Manual review required"],
|
||||
requires_approval=True,
|
||||
confidence_score=0.0
|
||||
)
|
||||
|
||||
def _determine_action_category(self, action_type: str) -> ActionCategory:
|
||||
"""Determine the category of an action"""
|
||||
action_type_lower = action_type.lower()
|
||||
|
||||
if any(keyword in action_type_lower for keyword in ['content', 'blog', 'article', 'post']):
|
||||
return ActionCategory.CONTENT_MODIFICATION
|
||||
elif any(keyword in action_type_lower for keyword in ['seo', 'meta', 'keyword', 'optimization']):
|
||||
return ActionCategory.SEO_OPTIMIZATION
|
||||
elif any(keyword in action_type_lower for keyword in ['competitor', 'competitive', 'response']):
|
||||
return ActionCategory.COMPETITOR_RESPONSE
|
||||
elif any(keyword in action_type_lower for keyword in ['social', 'share', 'amplify', 'distribute']):
|
||||
return ActionCategory.SOCIAL_AMPLIFICATION
|
||||
elif any(keyword in action_type_lower for keyword in ['strategy', 'plan', 'approach']):
|
||||
return ActionCategory.STRATEGY_CHANGE
|
||||
elif any(keyword in action_type_lower for keyword in ['config', 'setting', 'system']):
|
||||
return ActionCategory.SYSTEM_CONFIGURATION
|
||||
else:
|
||||
return ActionCategory.CONTENT_MODIFICATION # Default category
|
||||
|
||||
def _calculate_risk_level(self, risk_score: float, impact_score: float) -> RiskLevel:
|
||||
"""Calculate overall risk level"""
|
||||
# Weighted combination of risk and impact
|
||||
combined_score = (risk_score * 0.6) + (impact_score * 0.4)
|
||||
|
||||
if combined_score >= 0.8:
|
||||
return RiskLevel.CRITICAL
|
||||
elif combined_score >= 0.6:
|
||||
return RiskLevel.HIGH
|
||||
elif combined_score >= 0.3:
|
||||
return RiskLevel.MEDIUM
|
||||
else:
|
||||
return RiskLevel.LOW
|
||||
|
||||
async def _check_constraint(self, constraint: SafetyConstraint, action_data: Dict[str, Any], risk_level: RiskLevel) -> Dict[str, Any]:
|
||||
"""Check an action against a specific constraint"""
|
||||
violations = []
|
||||
recommendations = []
|
||||
requires_approval = False
|
||||
|
||||
# Check risk threshold
|
||||
if risk_level.value in ['high', 'critical'] and constraint.risk_threshold < 0.8:
|
||||
violations.append(f"Risk level {risk_level.value} exceeds constraint threshold")
|
||||
requires_approval = True
|
||||
|
||||
# Check rate limits
|
||||
if constraint.daily_limit:
|
||||
daily_count = await self._get_daily_action_count(constraint.constraint_id)
|
||||
if daily_count >= constraint.daily_limit:
|
||||
violations.append(f"Daily limit exceeded: {daily_count}/{constraint.daily_limit}")
|
||||
|
||||
if constraint.hourly_limit:
|
||||
hourly_count = await self._get_hourly_action_count(constraint.constraint_id)
|
||||
if hourly_count >= constraint.hourly_limit:
|
||||
violations.append(f"Hourly limit exceeded: {hourly_count}/{constraint.hourly_limit}")
|
||||
|
||||
# Check approval requirement
|
||||
if constraint.approval_required:
|
||||
requires_approval = True
|
||||
recommendations.append("Action requires manual approval due to safety constraints")
|
||||
|
||||
# Check auto-approval threshold
|
||||
risk_score = action_data.get('risk_score', 0.5)
|
||||
if risk_score > constraint.auto_approval_threshold:
|
||||
requires_approval = True
|
||||
|
||||
# Custom condition checks
|
||||
if constraint.conditions:
|
||||
condition_result = await self._check_custom_conditions(constraint.conditions, action_data)
|
||||
if not condition_result['is_valid']:
|
||||
violations.extend(condition_result['violations'])
|
||||
|
||||
is_valid = len(violations) == 0 and not requires_approval
|
||||
|
||||
return {
|
||||
"is_valid": is_valid,
|
||||
"violations": violations,
|
||||
"recommendations": recommendations,
|
||||
"requires_approval": requires_approval
|
||||
}
|
||||
|
||||
async def _check_rate_limits(self, action_category: ActionCategory, action_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Check rate limits for actions"""
|
||||
violations = []
|
||||
|
||||
# Get current time window counts
|
||||
recent_actions = await self._get_recent_actions(hours=1)
|
||||
category_actions = [action for action in recent_actions if self._determine_action_category(action.get('action_type', '')) == action_category]
|
||||
|
||||
# Check hourly limits
|
||||
if len(category_actions) > 50: # Default hourly limit
|
||||
violations.append(f"Hourly action limit exceeded for {action_category.value}")
|
||||
|
||||
# Check daily limits
|
||||
daily_actions = await self._get_recent_actions(hours=24)
|
||||
daily_category_actions = [action for action in daily_actions if self._determine_action_category(action.get('action_type', '')) == action_category]
|
||||
|
||||
if len(daily_category_actions) > 200: # Default daily limit
|
||||
violations.append(f"Daily action limit exceeded for {action_category.value}")
|
||||
|
||||
return {
|
||||
"is_valid": len(violations) == 0,
|
||||
"violations": violations
|
||||
}
|
||||
|
||||
async def _check_suspicious_patterns(self, action_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Check for suspicious patterns in actions"""
|
||||
violations = []
|
||||
|
||||
# Get recent action patterns
|
||||
recent_actions = await self._get_recent_actions(hours=24)
|
||||
|
||||
# Check for rapid repetitive actions
|
||||
action_type = action_data.get('action_type', '')
|
||||
similar_actions = [action for action in recent_actions if action.get('action_type') == action_type]
|
||||
|
||||
if len(similar_actions) > 10: # More than 10 similar actions in 24 hours
|
||||
violations.append(f"Suspicious pattern: {len(similar_actions)} similar actions in 24 hours")
|
||||
|
||||
# Check for unusual timing patterns
|
||||
if len(recent_actions) > 100: # More than 100 actions in 1 hour
|
||||
violations.append("Suspicious pattern: Unusually high action frequency")
|
||||
|
||||
# Check for conflicting actions
|
||||
conflicting_actions = await self._detect_conflicting_actions(action_data, recent_actions)
|
||||
if conflicting_actions:
|
||||
violations.append(f"Conflicting actions detected: {len(conflicting_actions)}")
|
||||
|
||||
return {
|
||||
"is_valid": len(violations) == 0,
|
||||
"violations": violations
|
||||
}
|
||||
|
||||
async def _detect_conflicting_actions(self, current_action: Dict[str, Any], recent_actions: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
"""Detect actions that conflict with recent actions"""
|
||||
conflicts = []
|
||||
|
||||
# Simple conflict detection based on action types
|
||||
conflicting_pairs = [
|
||||
("optimize_content", "delete_content"),
|
||||
("increase_keywords", "decrease_keywords"),
|
||||
("enable_feature", "disable_feature")
|
||||
]
|
||||
|
||||
current_action_type = current_action.get('action_type', '')
|
||||
|
||||
for pair in conflicting_pairs:
|
||||
if current_action_type == pair[0]:
|
||||
# Check for recent opposite action
|
||||
for action in recent_actions:
|
||||
if action.get('action_type') == pair[1]:
|
||||
conflicts.append(action)
|
||||
break
|
||||
elif current_action_type == pair[1]:
|
||||
# Check for recent opposite action
|
||||
for action in recent_actions:
|
||||
if action.get('action_type') == pair[0]:
|
||||
conflicts.append(action)
|
||||
break
|
||||
|
||||
return conflicts
|
||||
|
||||
async def _check_custom_conditions(self, conditions: Dict[str, Any], action_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Check custom conditions for constraints"""
|
||||
violations = []
|
||||
|
||||
# Example custom conditions (can be extended)
|
||||
if conditions.get('max_content_length'):
|
||||
content_length = len(action_data.get('content', ''))
|
||||
if content_length > conditions['max_content_length']:
|
||||
violations.append(f"Content length {content_length} exceeds maximum {conditions['max_content_length']}")
|
||||
|
||||
if conditions.get('allowed_keywords'):
|
||||
content = action_data.get('content', '').lower()
|
||||
allowed_keywords = [kw.lower() for kw in conditions['allowed_keywords']]
|
||||
if not any(keyword in content for keyword in allowed_keywords):
|
||||
violations.append("Content does not contain required keywords")
|
||||
|
||||
return {
|
||||
"is_valid": len(violations) == 0,
|
||||
"violations": violations
|
||||
}
|
||||
|
||||
async def _get_recent_actions(self, hours: int = 24) -> List[Dict[str, Any]]:
|
||||
"""Get recent actions from history"""
|
||||
cutoff_time = datetime.utcnow() - timedelta(hours=hours)
|
||||
|
||||
return [
|
||||
action for action in self.action_history
|
||||
if datetime.fromisoformat(action.get('timestamp', datetime.utcnow().isoformat())) > cutoff_time
|
||||
]
|
||||
|
||||
async def _get_daily_action_count(self, constraint_id: str) -> int:
|
||||
"""Get daily action count for a specific constraint"""
|
||||
daily_actions = await self._get_recent_actions(hours=24)
|
||||
return len(daily_actions)
|
||||
|
||||
async def _get_hourly_action_count(self, constraint_id: str) -> int:
|
||||
"""Get hourly action count for a specific constraint"""
|
||||
hourly_actions = await self._get_recent_actions(hours=1)
|
||||
return len(hourly_actions)
|
||||
|
||||
async def _record_validation_history(self, action_data: Dict[str, Any], is_valid: bool, violations: List[str]):
|
||||
"""Record validation in history"""
|
||||
validation_record = {
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"action_type": action_data.get('action_type', 'unknown'),
|
||||
"is_valid": is_valid,
|
||||
"violations": violations,
|
||||
"action_data": action_data
|
||||
}
|
||||
|
||||
self.action_history.append(validation_record)
|
||||
|
||||
# Keep only recent history (last 1000 records)
|
||||
if len(self.action_history) > 1000:
|
||||
self.action_history = self.action_history[-1000:]
|
||||
|
||||
# Record violations separately
|
||||
if violations:
|
||||
violation_record = {
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"action_type": action_data.get('action_type', 'unknown'),
|
||||
"violations": violations,
|
||||
"severity": "high" if len(violations) > 2 else "medium"
|
||||
}
|
||||
self.violation_history.append(violation_record)
|
||||
|
||||
# Keep only recent violations (last 500 records)
|
||||
if len(self.violation_history) > 500:
|
||||
self.violation_history = self.violation_history[-500:]
|
||||
|
||||
def add_custom_constraint(self, constraint: SafetyConstraint):
|
||||
"""Add a custom safety constraint"""
|
||||
self.constraints[constraint.constraint_id] = constraint
|
||||
logger.info(f"Added custom constraint for user {self.user_id}: {constraint.constraint_id}")
|
||||
|
||||
def remove_constraint(self, constraint_id: str):
|
||||
"""Remove a safety constraint"""
|
||||
if constraint_id in self.constraints:
|
||||
del self.constraints[constraint_id]
|
||||
logger.info(f"Removed constraint for user {self.user_id}: {constraint_id}")
|
||||
|
||||
def get_constraints(self) -> Dict[str, SafetyConstraint]:
|
||||
"""Get all safety constraints"""
|
||||
return self.constraints.copy()
|
||||
|
||||
def get_validation_history(self, limit: int = 100) -> List[Dict[str, Any]]:
|
||||
"""Get recent validation history"""
|
||||
return self.action_history[-limit:] if self.action_history else []
|
||||
|
||||
def get_violation_history(self, limit: int = 50) -> List[Dict[str, Any]]:
|
||||
"""Get recent violation history"""
|
||||
return self.violation_history[-limit:] if self.violation_history else []
|
||||
|
||||
class RollbackManager:
|
||||
"""Manages rollback operations for agent actions"""
|
||||
|
||||
def __init__(self, user_id: str):
|
||||
self.user_id = user_id
|
||||
self.checkpoints: List[ActionCheckpoint] = []
|
||||
self.rollback_history: List[Dict[str, Any]] = []
|
||||
|
||||
logger.info(f"Initialized RollbackManager for user: {user_id}")
|
||||
|
||||
async def create_checkpoint(self, action_data: Dict[str, Any], system_state: Dict[str, Any]) -> str:
|
||||
"""Create a checkpoint before executing an action"""
|
||||
try:
|
||||
checkpoint_id = f"checkpoint_{self.user_id}_{datetime.utcnow().strftime('%Y%m%d%H%M%S')}"
|
||||
|
||||
checkpoint = ActionCheckpoint(
|
||||
checkpoint_id=checkpoint_id,
|
||||
action_id=action_data.get('action_id', 'unknown'),
|
||||
agent_id=action_data.get('agent_id', 'unknown'),
|
||||
user_id=self.user_id,
|
||||
action_type=action_data.get('action_type', 'unknown'),
|
||||
action_data=action_data,
|
||||
system_state=system_state
|
||||
)
|
||||
|
||||
self.checkpoints.append(checkpoint)
|
||||
|
||||
# Keep only recent checkpoints (last 100)
|
||||
if len(self.checkpoints) > 100:
|
||||
self.checkpoints = self.checkpoints[-100:]
|
||||
|
||||
logger.info(f"Created checkpoint for user {self.user_id}: {checkpoint_id}")
|
||||
return checkpoint_id
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating checkpoint for user {self.user_id}: {e}")
|
||||
raise e
|
||||
|
||||
async def rollback_to_checkpoint(self, checkpoint_id: str) -> Dict[str, Any]:
|
||||
"""Rollback to a specific checkpoint"""
|
||||
try:
|
||||
# Find checkpoint
|
||||
checkpoint = next((cp for cp in self.checkpoints if cp.checkpoint_id == checkpoint_id), None)
|
||||
|
||||
if not checkpoint:
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"Checkpoint not found: {checkpoint_id}"
|
||||
}
|
||||
|
||||
logger.info(f"Rolling back to checkpoint for user {self.user_id}: {checkpoint_id}")
|
||||
|
||||
# Execute rollback (implementation depends on action type)
|
||||
rollback_result = await self._execute_rollback(checkpoint)
|
||||
|
||||
# Record in history
|
||||
rollback_record = {
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"checkpoint_id": checkpoint_id,
|
||||
"action_type": checkpoint.action_type,
|
||||
"success": rollback_result["success"],
|
||||
"details": rollback_result
|
||||
}
|
||||
self.rollback_history.append(rollback_record)
|
||||
|
||||
# Keep only recent rollback history (last 50)
|
||||
if len(self.rollback_history) > 50:
|
||||
self.rollback_history = self.rollback_history[-50:]
|
||||
|
||||
return rollback_result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error rolling back to checkpoint {checkpoint_id} for user {self.user_id}: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
async def _execute_rollback(self, checkpoint: ActionCheckpoint) -> Dict[str, Any]:
|
||||
"""Execute the rollback operation based on action type"""
|
||||
try:
|
||||
action_type = checkpoint.action_type
|
||||
action_data = checkpoint.action_data
|
||||
system_state = checkpoint.system_state
|
||||
|
||||
# Implement rollback logic for different action types
|
||||
if action_type == "content_modification":
|
||||
return await self._rollback_content_modification(action_data, system_state)
|
||||
elif action_type == "seo_optimization":
|
||||
return await self._rollback_seo_optimization(action_data, system_state)
|
||||
elif action_type == "competitor_response":
|
||||
return await self._rollback_competitor_response(action_data, system_state)
|
||||
elif action_type == "social_amplification":
|
||||
return await self._rollback_social_amplification(action_data, system_state)
|
||||
else:
|
||||
# Generic rollback
|
||||
return await self._rollback_generic(action_data, system_state)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error executing rollback for action {action_type}: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
async def _rollback_content_modification(self, action_data: Dict[str, Any], system_state: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Rollback content modification"""
|
||||
try:
|
||||
# Implementation would depend on how content is stored and managed
|
||||
# For now, return a placeholder implementation
|
||||
|
||||
original_content = system_state.get('original_content', {})
|
||||
modified_content = action_data.get('content', {})
|
||||
|
||||
logger.info(f"Rolling back content modification: {action_data.get('content_id', 'unknown')}")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Content modification rolled back successfully",
|
||||
"details": {
|
||||
"content_id": action_data.get('content_id'),
|
||||
"rollback_type": "content_modification",
|
||||
"original_state_restored": bool(original_content)
|
||||
}
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"Failed to rollback content modification: {str(e)}"
|
||||
}
|
||||
|
||||
async def _rollback_seo_optimization(self, action_data: Dict[str, Any], system_state: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Rollback SEO optimization"""
|
||||
try:
|
||||
original_seo_state = system_state.get('seo_state', {})
|
||||
|
||||
logger.info(f"Rolling back SEO optimization: {action_data.get('optimization_type', 'unknown')}")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "SEO optimization rolled back successfully",
|
||||
"details": {
|
||||
"optimization_type": action_data.get('optimization_type'),
|
||||
"rollback_type": "seo_optimization",
|
||||
"original_state_restored": bool(original_seo_state)
|
||||
}
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"Failed to rollback SEO optimization: {str(e)}"
|
||||
}
|
||||
|
||||
async def _rollback_competitor_response(self, action_data: Dict[str, Any], system_state: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Rollback competitor response"""
|
||||
try:
|
||||
logger.info(f"Rolling back competitor response: {action_data.get('response_type', 'unknown')}")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Competitor response rolled back successfully",
|
||||
"details": {
|
||||
"response_type": action_data.get('response_type'),
|
||||
"rollback_type": "competitor_response",
|
||||
"original_state_restored": True
|
||||
}
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"Failed to rollback competitor response: {str(e)}"
|
||||
}
|
||||
|
||||
async def _rollback_social_amplification(self, action_data: Dict[str, Any], system_state: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Rollback social amplification"""
|
||||
try:
|
||||
logger.info(f"Rolling back social amplification: {action_data.get('platform', 'unknown')}")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Social amplification rolled back successfully",
|
||||
"details": {
|
||||
"platform": action_data.get('platform'),
|
||||
"rollback_type": "social_amplification",
|
||||
"original_state_restored": True
|
||||
}
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"Failed to rollback social amplification: {str(e)}"
|
||||
}
|
||||
|
||||
async def _rollback_generic(self, action_data: Dict[str, Any], system_state: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Generic rollback for unknown action types"""
|
||||
try:
|
||||
logger.info(f"Performing generic rollback for action: {action_data.get('action_type', 'unknown')}")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Generic rollback completed",
|
||||
"details": {
|
||||
"action_type": action_data.get('action_type'),
|
||||
"rollback_type": "generic",
|
||||
"system_state_available": bool(system_state)
|
||||
}
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"Failed to perform generic rollback: {str(e)}"
|
||||
}
|
||||
|
||||
async def rollback_latest_actions(self, count: int = 1) -> List[Dict[str, Any]]:
|
||||
"""Rollback the latest N actions"""
|
||||
results = []
|
||||
|
||||
# Get latest checkpoints
|
||||
latest_checkpoints = self.checkpoints[-count:] if self.checkpoints else []
|
||||
|
||||
for checkpoint in reversed(latest_checkpoints):
|
||||
result = await self.rollback_to_checkpoint(checkpoint.checkpoint_id)
|
||||
results.append(result)
|
||||
|
||||
return results
|
||||
|
||||
def get_checkpoints(self, limit: int = 50) -> List[Dict[str, Any]]:
|
||||
"""Get recent checkpoints"""
|
||||
checkpoints_data = []
|
||||
|
||||
for checkpoint in self.checkpoints[-limit:]:
|
||||
checkpoints_data.append({
|
||||
"checkpoint_id": checkpoint.checkpoint_id,
|
||||
"action_id": checkpoint.action_id,
|
||||
"action_type": checkpoint.action_type,
|
||||
"agent_id": checkpoint.agent_id,
|
||||
"created_at": checkpoint.created_at,
|
||||
"system_state_keys": list(checkpoint.system_state.keys())
|
||||
})
|
||||
|
||||
return checkpoints_data
|
||||
|
||||
def get_rollback_history(self, limit: int = 50) -> List[Dict[str, Any]]:
|
||||
"""Get rollback history"""
|
||||
return self.rollback_history[-limit:] if self.rollback_history else []
|
||||
|
||||
class UserApprovalSystem:
|
||||
"""Manages user approval for high-risk actions"""
|
||||
|
||||
def __init__(self, user_id: str):
|
||||
self.user_id = user_id
|
||||
self.pending_approvals: Dict[str, Dict[str, Any]] = {}
|
||||
self.approval_history: List[Dict[str, Any]] = []
|
||||
|
||||
logger.info(f"Initialized UserApprovalSystem for user: {user_id}")
|
||||
|
||||
async def request_approval(self, action_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Request user approval for an action"""
|
||||
try:
|
||||
approval_id = f"approval_{self.user_id}_{datetime.utcnow().strftime('%Y%m%d%H%M%S')}"
|
||||
|
||||
approval_request = {
|
||||
"approval_id": approval_id,
|
||||
"action_data": action_data,
|
||||
"requested_at": datetime.utcnow().isoformat(),
|
||||
"status": "pending",
|
||||
"expires_at": (datetime.utcnow() + timedelta(hours=24)).isoformat()
|
||||
}
|
||||
|
||||
self.pending_approvals[approval_id] = approval_request
|
||||
|
||||
logger.info(f"Created approval request for user {self.user_id}: {approval_id}")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"approval_id": approval_id,
|
||||
"status": "pending",
|
||||
"message": "Approval request created successfully"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating approval request for user {self.user_id}: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
async def approve_action(self, approval_id: str, user_decision: str, user_comments: str = "") -> Dict[str, Any]:
|
||||
"""Process user approval decision"""
|
||||
try:
|
||||
if approval_id not in self.pending_approvals:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Approval request not found"
|
||||
}
|
||||
|
||||
approval_request = self.pending_approvals[approval_id]
|
||||
|
||||
# Check if approval has expired
|
||||
expires_at = datetime.fromisoformat(approval_request["expires_at"])
|
||||
if datetime.utcnow() > expires_at:
|
||||
del self.pending_approvals[approval_id]
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Approval request has expired"
|
||||
}
|
||||
|
||||
# Process decision
|
||||
approval_request["status"] = user_decision
|
||||
approval_request["decision_at"] = datetime.utcnow().isoformat()
|
||||
approval_request["user_comments"] = user_comments
|
||||
|
||||
# Record in history
|
||||
self.approval_history.append(approval_request)
|
||||
|
||||
# Remove from pending
|
||||
del self.pending_approvals[approval_id]
|
||||
|
||||
# Keep only recent history (last 100)
|
||||
if len(self.approval_history) > 100:
|
||||
self.approval_history = self.approval_history[-100:]
|
||||
|
||||
logger.info(f"Processed approval decision for user {self.user_id}: {approval_id} - {user_decision}")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"approval_id": approval_id,
|
||||
"status": user_decision,
|
||||
"message": f"Action {user_decision} successfully"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing approval decision for user {self.user_id}: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
def get_pending_approvals(self) -> List[Dict[str, Any]]:
|
||||
"""Get all pending approval requests"""
|
||||
return list(self.pending_approvals.values())
|
||||
|
||||
def get_approval_history(self, limit: int = 50) -> List[Dict[str, Any]]:
|
||||
"""Get recent approval history"""
|
||||
return self.approval_history[-limit:] if self.approval_history else []
|
||||
|
||||
def get_approval_statistics(self) -> Dict[str, Any]:
|
||||
"""Get approval statistics"""
|
||||
if not self.approval_history:
|
||||
return {
|
||||
"total_approvals": 0,
|
||||
"approved_count": 0,
|
||||
"rejected_count": 0,
|
||||
"approval_rate": 0.0,
|
||||
"pending_count": len(self.pending_approvals)
|
||||
}
|
||||
|
||||
total = len(self.approval_history)
|
||||
approved = len([a for a in self.approval_history if a["status"] == "approved"])
|
||||
rejected = len([a for a in self.approval_history if a["status"] == "rejected"])
|
||||
|
||||
return {
|
||||
"total_approvals": total,
|
||||
"approved_count": approved,
|
||||
"rejected_count": rejected,
|
||||
"approval_rate": approved / total if total > 0 else 0.0,
|
||||
"pending_count": len(self.pending_approvals)
|
||||
}
|
||||
|
||||
# Global safety framework instance
|
||||
safety_framework_instances: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
def get_safety_framework(user_id: str) -> Dict[str, Any]:
|
||||
"""Get or create safety framework components for a user"""
|
||||
if user_id not in safety_framework_instances:
|
||||
safety_framework_instances[user_id] = {
|
||||
"constraint_manager": SafetyConstraintManager(user_id),
|
||||
"rollback_manager": RollbackManager(user_id),
|
||||
"approval_system": UserApprovalSystem(user_id)
|
||||
}
|
||||
|
||||
return safety_framework_instances[user_id]
|
||||
|
||||
# Convenience functions
|
||||
async def validate_agent_action(user_id: str, action_data: Dict[str, Any]) -> SafetyValidation:
|
||||
"""Validate an agent action for a user"""
|
||||
framework = get_safety_framework(user_id)
|
||||
return await framework["constraint_manager"].validate_action(action_data)
|
||||
|
||||
async def create_action_checkpoint(user_id: str, action_data: Dict[str, Any], system_state: Dict[str, Any]) -> str:
|
||||
"""Create a checkpoint for an action"""
|
||||
framework = get_safety_framework(user_id)
|
||||
return await framework["rollback_manager"].create_checkpoint(action_data, system_state)
|
||||
|
||||
async def rollback_to_checkpoint(user_id: str, checkpoint_id: str) -> Dict[str, Any]:
|
||||
"""Rollback to a specific checkpoint"""
|
||||
framework = get_safety_framework(user_id)
|
||||
return await framework["rollback_manager"].rollback_to_checkpoint(checkpoint_id)
|
||||
|
||||
async def request_user_approval(user_id: str, action_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Request user approval for an action"""
|
||||
framework = get_safety_framework(user_id)
|
||||
return await framework["approval_system"].request_approval(action_data)
|
||||
1689
backend/services/intelligence/agents/specialized_agents.py
Normal file
1689
backend/services/intelligence/agents/specialized_agents.py
Normal file
File diff suppressed because it is too large
Load Diff
223
backend/services/intelligence/agents/team_catalog.py
Normal file
223
backend/services/intelligence/agents/team_catalog.py
Normal file
@@ -0,0 +1,223 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
|
||||
AgentCatalogEntry = Dict[str, Any]
|
||||
|
||||
|
||||
AGENT_TEAM_CATALOG: List[AgentCatalogEntry] = [
|
||||
{
|
||||
"agent_key": "strategy_orchestrator",
|
||||
"agent_type": "StrategyOrchestrator",
|
||||
"role": "Team Lead",
|
||||
"responsibilities": [
|
||||
"Coordinate all marketing agents and delegate work",
|
||||
"Synthesize a unified daily strategy across channels",
|
||||
"Prioritize actions based on impact and urgency",
|
||||
"Maintain safety constraints and request approval when needed",
|
||||
],
|
||||
"tools": [
|
||||
"market_signal_detector",
|
||||
"google_trends_fetcher",
|
||||
"agent_coordinator",
|
||||
"performance_analyzer",
|
||||
"strategy_synthesizer",
|
||||
"task_delegator",
|
||||
],
|
||||
"defaults": {
|
||||
"display_name_template": "{website_name} Marketing Team Lead",
|
||||
"enabled": True,
|
||||
"schedule": {"mode": "on_demand"},
|
||||
"system_prompt_template": (
|
||||
"You are the Marketing Strategy Orchestrator for {website_name}.\n\n"
|
||||
"Mission: coordinate the AI marketing team to help {website_name} win in digital marketing.\n\n"
|
||||
"Non-negotiables:\n"
|
||||
"- Delegate tasks to specialists using the available team tools.\n"
|
||||
"- Keep outputs practical for non-technical users.\n"
|
||||
"- Maintain safety constraints and request approval for high-risk actions.\n\n"
|
||||
"Context you may receive:\n"
|
||||
"- website_url, brand_voice, target_audience, competitors, content pillars\n\n"
|
||||
"Output style:\n"
|
||||
"- Provide a concise plan with priorities, expected outcomes, and next steps."
|
||||
),
|
||||
"task_prompt_template": (
|
||||
"Task: Create a unified marketing plan for today.\n"
|
||||
"Use the provided context and delegate specialized work when needed.\n\n"
|
||||
"Return JSON with:\n"
|
||||
"{\n"
|
||||
" \"summary\": string,\n"
|
||||
" \"priorities\": [string],\n"
|
||||
" \"delegations\": [{\"agent\": string, \"task\": string}],\n"
|
||||
" \"next_actions\": [{\"title\": string, \"why\": string, \"expected_outcome\": string, \"risk_level\": \"low\"|\"medium\"|\"high\"}]\n"
|
||||
"}\n"
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
"agent_key": "content_strategist",
|
||||
"agent_type": "content_strategist",
|
||||
"role": "Content Strategist",
|
||||
"responsibilities": [
|
||||
"Analyze content performance and engagement signals",
|
||||
"Identify content gaps using semantic and sitemap analysis",
|
||||
"Optimize content for clarity, SEO, and conversions",
|
||||
"Track performance over time and recommend next actions",
|
||||
],
|
||||
"tools": [
|
||||
"content_analyzer",
|
||||
"semantic_gap_detector",
|
||||
"content_optimizer",
|
||||
"performance_tracker",
|
||||
"sitemap_analyzer",
|
||||
],
|
||||
"defaults": {
|
||||
"display_name_template": "{website_name} Content Strategist",
|
||||
"enabled": True,
|
||||
"schedule": {"mode": "weekly", "days": ["mon"], "time": "09:00"},
|
||||
"system_prompt_template": (
|
||||
"You are the Content Strategy Agent for {website_name}.\n\n"
|
||||
"Mission: help {website_name} publish content that matches the brand voice and grows traffic.\n\n"
|
||||
"Operating principles:\n"
|
||||
"- Be specific, actionable, and non-technical.\n"
|
||||
"- Prefer high-impact, low-effort recommendations first.\n"
|
||||
"- Maintain brand consistency.\n\n"
|
||||
"When you respond, include:\n"
|
||||
"- What to do, why it matters, and what success looks like."
|
||||
),
|
||||
"task_prompt_template": (
|
||||
"Task: Propose the next 5 content actions for {website_name}.\n"
|
||||
"Inputs may include: website analysis, competitors, content pillars, recent results.\n\n"
|
||||
"Return JSON with:\n"
|
||||
"{\n"
|
||||
" \"actions\": [{\"title\": string, \"why\": string, \"outline\": [string], \"cta\": string, \"risk_level\": \"low\"|\"medium\"|\"high\"}],\n"
|
||||
" \"notes\": [string]\n"
|
||||
"}\n"
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
"agent_key": "competitor_analyst",
|
||||
"agent_type": "competitor_analyst",
|
||||
"role": "Competitor Analyst",
|
||||
"responsibilities": [
|
||||
"Monitor competitor strategy and positioning using SIF",
|
||||
"Assess threats and opportunities from competitor moves",
|
||||
"Generate counter-strategy recommendations",
|
||||
"Execute safe response actions (with approvals when needed)",
|
||||
],
|
||||
"tools": [
|
||||
"competitor_monitor",
|
||||
"threat_analyzer",
|
||||
"response_generator",
|
||||
"strategy_executor",
|
||||
],
|
||||
"defaults": {
|
||||
"display_name_template": "{website_name} Competitor Analyst",
|
||||
"enabled": True,
|
||||
"schedule": {"mode": "weekly", "days": ["wed"], "time": "10:00"},
|
||||
"system_prompt_template": (
|
||||
"You are the Competitor Response Agent for {website_name}.\n\n"
|
||||
"Mission: monitor competitor moves and translate them into clear actions for {website_name}.\n\n"
|
||||
"Rules:\n"
|
||||
"- Use semantic insights to avoid guesswork.\n"
|
||||
"- Avoid panic. Prioritize only meaningful threats.\n"
|
||||
"- Keep outputs concise and actionable."
|
||||
),
|
||||
"task_prompt_template": (
|
||||
"Task: Summarize competitor moves and recommend responses.\n\n"
|
||||
"Return JSON with:\n"
|
||||
"{\n"
|
||||
" \"threat_level\": \"low\"|\"medium\"|\"high\",\n"
|
||||
" \"signals\": [string],\n"
|
||||
" \"responses\": [{\"title\": string, \"why\": string, \"expected_outcome\": string, \"risk_level\": \"low\"|\"medium\"|\"high\"}]\n"
|
||||
"}\n"
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
"agent_key": "seo_specialist",
|
||||
"agent_type": "seo_specialist",
|
||||
"role": "SEO Specialist",
|
||||
"responsibilities": [
|
||||
"Audit technical SEO and prioritize fixes by impact",
|
||||
"Generate safe SEO fixes and improvements",
|
||||
"Adjust keyword strategy based on data and trends",
|
||||
"Validate changes against safety and quality constraints",
|
||||
],
|
||||
"tools": [
|
||||
"seo_auditor",
|
||||
"issue_prioritizer",
|
||||
"auto_fix_executor",
|
||||
"strategy_generator",
|
||||
"query_seo_knowledge_base",
|
||||
],
|
||||
"defaults": {
|
||||
"display_name_template": "{website_name} SEO Specialist",
|
||||
"enabled": True,
|
||||
"schedule": {"mode": "weekly", "days": ["fri"], "time": "11:00"},
|
||||
"system_prompt_template": (
|
||||
"You are the SEO Optimization Agent for {website_name}.\n\n"
|
||||
"Mission: continuously improve technical SEO and on-page basics while preserving user experience.\n\n"
|
||||
"Rules:\n"
|
||||
"- Prioritize high-impact, low-risk fixes.\n"
|
||||
"- Explain recommendations in simple language.\n"
|
||||
"- If an action is risky, require approval."
|
||||
),
|
||||
"task_prompt_template": (
|
||||
"Task: Produce a weekly SEO fix list for {website_name}.\n\n"
|
||||
"Return JSON with:\n"
|
||||
"{\n"
|
||||
" \"fixes\": [{\"title\": string, \"why\": string, \"steps\": [string], \"risk_level\": \"low\"|\"medium\"|\"high\"}],\n"
|
||||
" \"metrics_to_watch\": [string]\n"
|
||||
"}\n"
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
"agent_key": "social_media_manager",
|
||||
"agent_type": "social_media_manager",
|
||||
"role": "Social Media Manager",
|
||||
"responsibilities": [
|
||||
"Monitor social trends and identify opportunities",
|
||||
"Adapt content for platform-specific distribution",
|
||||
"Optimize engagement signals (timing, hooks, hashtags)",
|
||||
"Coordinate distribution safely (with approvals when needed)",
|
||||
],
|
||||
"tools": [
|
||||
"social_monitor",
|
||||
"content_adapter",
|
||||
"engagement_optimizer",
|
||||
"distribution_manager",
|
||||
],
|
||||
"defaults": {
|
||||
"display_name_template": "{website_name} Social Media Manager",
|
||||
"enabled": True,
|
||||
"schedule": {"mode": "weekly", "days": ["tue"], "time": "09:30"},
|
||||
"system_prompt_template": (
|
||||
"You are the Social Media Manager for {website_name}.\n\n"
|
||||
"Mission: help {website_name} distribute content effectively without spam.\n\n"
|
||||
"Rules:\n"
|
||||
"- Adapt to platform norms.\n"
|
||||
"- Optimize for engagement ethically.\n"
|
||||
"- Keep messages aligned with brand voice."
|
||||
),
|
||||
"task_prompt_template": (
|
||||
"Task: Suggest a weekly distribution plan for {website_name}.\n\n"
|
||||
"Return JSON with:\n"
|
||||
"{\n"
|
||||
" \"posts\": [{\"platform\": string, \"post\": string, \"best_time\": string, \"hashtags\": [string]}],\n"
|
||||
" \"notes\": [string]\n"
|
||||
"}\n"
|
||||
),
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def get_agent_catalog_entry(agent_key: str) -> Optional[AgentCatalogEntry]:
|
||||
agent_key_value = (agent_key or "").strip()
|
||||
for entry in AGENT_TEAM_CATALOG:
|
||||
if entry.get("agent_key") == agent_key_value:
|
||||
return entry
|
||||
return None
|
||||
165
backend/services/intelligence/agents/trend_surfer_agent.py
Normal file
165
backend/services/intelligence/agents/trend_surfer_agent.py
Normal file
@@ -0,0 +1,165 @@
|
||||
"""
|
||||
Trend Surfer Agent
|
||||
Agent for identifying and capitalizing on emerging market trends.
|
||||
"""
|
||||
|
||||
import traceback
|
||||
from typing import List, Dict, Any, Optional
|
||||
from loguru import logger
|
||||
|
||||
from services.intelligence.agents.specialized_agents import SIFBaseAgent
|
||||
from services.intelligence.agents.market_signal_detector import MarketSignalDetector, MarketSignal, UrgencyLevel, SignalType
|
||||
from services.intelligence.txtai_service import TxtaiIntelligenceService
|
||||
from services.research.trends.google_trends_service import GoogleTrendsService
|
||||
|
||||
class TrendSurferAgent(SIFBaseAgent):
|
||||
"""
|
||||
Agent for identifying and capitalizing on emerging market trends.
|
||||
"Surfs" the trends detected by MarketSignalDetector to propose timely content.
|
||||
"""
|
||||
|
||||
def __init__(self, intelligence_service: TxtaiIntelligenceService, user_id: str):
|
||||
super().__init__(intelligence_service)
|
||||
self.user_id = user_id
|
||||
self.signal_detector = MarketSignalDetector(user_id)
|
||||
self.trends_service = GoogleTrendsService()
|
||||
|
||||
async def surf_trends(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Identify high-potential trends and suggest content angles.
|
||||
Integrates real-time Google Trends data with MarketSignalDetector signals.
|
||||
"""
|
||||
self._log_agent_operation("Surfing market trends")
|
||||
|
||||
try:
|
||||
# 1. Get real-time trending searches from Google Trends
|
||||
realtime_trends = await self.trends_service.get_trending_searches(user_id=self.user_id)
|
||||
logger.info(f"[{self.__class__.__name__}] Found {len(realtime_trends)} real-time trends")
|
||||
|
||||
# 2. Detect internal market signals (competitors, SERP, etc.)
|
||||
signals = await self.signal_detector.detect_market_signals()
|
||||
|
||||
# 3. Analyze real-time trends and convert to signals if actionable
|
||||
trend_signals = await self._analyze_realtime_trends(realtime_trends)
|
||||
signals.extend(trend_signals)
|
||||
|
||||
if not signals:
|
||||
logger.info(f"[{self.__class__.__name__}] No active market signals found")
|
||||
return []
|
||||
|
||||
# Filter for actionable trends (High/Critical urgency or High impact)
|
||||
actionable_trends = [
|
||||
s for s in signals
|
||||
if s.urgency_level.value in ['high', 'critical'] or s.impact_score > 0.7
|
||||
]
|
||||
|
||||
logger.info(f"[{self.__class__.__name__}] Found {len(actionable_trends)} actionable trends")
|
||||
|
||||
opportunities = []
|
||||
for trend in actionable_trends:
|
||||
opp = await self._analyze_opportunity(trend)
|
||||
if opp:
|
||||
opportunities.append(opp)
|
||||
|
||||
return opportunities
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[{self.__class__.__name__}] Trend surfing failed: {e}")
|
||||
logger.error(f"[{self.__class__.__name__}] Full traceback: {traceback.format_exc()}")
|
||||
return []
|
||||
|
||||
async def _analyze_realtime_trends(self, trends: List[str]) -> List[MarketSignal]:
|
||||
"""
|
||||
Analyze raw trend keywords and convert actionable ones to MarketSignals.
|
||||
Uses pytrends (via GoogleTrendsService) to validate interest.
|
||||
"""
|
||||
signals = []
|
||||
# Limit to top 5 for detailed analysis to avoid rate limits
|
||||
top_trends = trends[:5]
|
||||
|
||||
for trend_kw in top_trends:
|
||||
try:
|
||||
# Get detailed data for the keyword
|
||||
trend_data = await self.trends_service.analyze_trends(
|
||||
keywords=[trend_kw],
|
||||
timeframe="now 7-d", # Last 7 days to see immediate trajectory
|
||||
geo="US" # Default to US for now, could be user-configured
|
||||
)
|
||||
|
||||
# Check if rising
|
||||
interest_over_time = trend_data.get("interest_over_time", [])
|
||||
if not interest_over_time:
|
||||
continue
|
||||
|
||||
# Simple logic: is the last point higher than the average?
|
||||
values = [float(point.get(trend_kw, 0)) for point in interest_over_time if trend_kw in point]
|
||||
if not values:
|
||||
continue
|
||||
|
||||
avg_interest = sum(values) / len(values)
|
||||
last_interest = values[-1]
|
||||
|
||||
# Calculate impact/urgency
|
||||
impact_score = min(last_interest / 100.0, 1.0) # Normalized
|
||||
urgency = UrgencyLevel.MEDIUM
|
||||
if last_interest > 80:
|
||||
urgency = UrgencyLevel.CRITICAL
|
||||
elif last_interest > 50:
|
||||
urgency = UrgencyLevel.HIGH
|
||||
|
||||
# Create Signal
|
||||
signal = MarketSignal(
|
||||
signal_id=f"trend_{trend_kw.replace(' ', '_')}_{int(values[-1])}",
|
||||
signal_type=SignalType.SOCIAL_TREND, # Using SOCIAL_TREND as proxy for general search trend
|
||||
source="google_trends",
|
||||
description=f"Surging interest in '{trend_kw}'",
|
||||
impact_score=impact_score,
|
||||
urgency_level=urgency,
|
||||
confidence_score=0.9,
|
||||
related_topics=[t.get("topic_title", "") for t in trend_data.get("related_topics", {}).get("top", [])[:3]],
|
||||
suggested_actions=["Create timely content", "Update social media"],
|
||||
metadata=trend_data
|
||||
)
|
||||
signals.append(signal)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"[{self.__class__.__name__}] Failed to analyze trend '{trend_kw}': {e}")
|
||||
continue
|
||||
|
||||
return signals
|
||||
|
||||
async def _analyze_opportunity(self, trend: MarketSignal) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Analyze a specific trend signal to generate a content opportunity.
|
||||
"""
|
||||
try:
|
||||
# Use semantic search to find if we already have content covering this
|
||||
query = f"{trend.description} {' '.join(trend.related_topics)}"
|
||||
existing_content = await self.intelligence.search(query, limit=3)
|
||||
|
||||
coverage_score = 0.0
|
||||
if existing_content:
|
||||
# If top result has high score, we might already cover it
|
||||
coverage_score = existing_content[0].get('score', 0.0)
|
||||
|
||||
# If already well-covered, might skip or suggest update
|
||||
if coverage_score > 0.8:
|
||||
recommendation = "Update existing content"
|
||||
else:
|
||||
recommendation = "Create new content"
|
||||
|
||||
return {
|
||||
"trend_id": trend.signal_id,
|
||||
"topic": trend.description,
|
||||
"source": trend.source,
|
||||
"urgency": trend.urgency_level.value,
|
||||
"impact_score": trend.impact_score,
|
||||
"current_coverage": coverage_score,
|
||||
"recommendation": recommendation,
|
||||
"suggested_angle": f"Leverage {trend.source} trend on {trend.related_topics[0] if trend.related_topics else 'topic'}",
|
||||
"detected_at": trend.detected_at
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"[{self.__class__.__name__}] Failed to analyze opportunity for signal {trend.signal_id}: {e}")
|
||||
return None
|
||||
Reference in New Issue
Block a user