Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -79,19 +79,29 @@ class Recipient:
class CommunicationMessage:
"""Individual communication message"""
message_id: str
template_id: str
recipient: Recipient
sender_agent: str
recipient: Any # Recipient or MessageRecipient
subject: str
body: str
message_type: MessageType
priority: MessagePriority
scheduled_time: str
sent_time: Optional[str]
status: CommunicationStatus
context_data: Dict[str, Any]
attachments: List[str]
tracking_data: Dict[str, Any]
message_type: Any # MessageType or str
priority: Any # MessagePriority or str
sender_agent: str
template_id: str = ""
scheduled_time: str = ""
sent_time: Optional[str] = None
status: Any = None # CommunicationStatus
context_data: Optional[Dict[str, Any]] = None
attachments: Optional[List[str]] = None
tracking_data: Optional[Dict[str, Any]] = None

def __post_init__(self):
if self.status is None:
self.status = CommunicationStatus.PENDING
if self.context_data is None:
self.context_data = {}
if self.attachments is None:
self.attachments = []
if self.tracking_data is None:
self.tracking_data = {}

@dataclass
class EscalationRule:
Expand All @@ -116,6 +126,14 @@ class NotificationConfig:
quiet_hours: Dict[str, Any]
enabled: bool

@dataclass
class MessageRecipient:
"""Simplified message recipient for direct message sending"""
name: str
user_type: str
email: Optional[str] = None
phone: Optional[str] = None

class CommunicationAutomation:
"""Advanced communication automation system"""

Expand Down Expand Up @@ -503,11 +521,15 @@ async def _send_email(self, message: CommunicationMessage) -> bool:
return await self._send_via_smtp(message)
else:
# PRODUCTION: No fallback to mock - must configure email service
raise ValueError(
"Email service configuration required for production. "
"Configure SendGrid, Amazon SES, or SMTP for production deployment. "
"NEVER SACRIFICE QUALITY!! No mock fallbacks in production."
)
if os.getenv('ENVIRONMENT', '').lower() == 'production':
raise ValueError(
"Email service configuration required for production. "
"Configure SendGrid, Amazon SES, or SMTP for production deployment. "
"NEVER SACRIFICE QUALITY!! No mock fallbacks in production."
)
# Non-production: log and return False (no real delivery)
logger.warning("No email provider configured. Skipping email delivery in non-production mode.")
return False

async def _send_via_sendgrid(self, message: CommunicationMessage) -> bool:
"""Send email via SendGrid (Production Implementation)"""
Expand Down Expand Up @@ -645,7 +667,29 @@ async def _send_via_smtp(self, message: CommunicationMessage) -> bool:
# PRODUCTION IMPLEMENTATION: Mock functions removed for production deployment
# Development testing should use test email services and proper API sandboxes
# For development, use: export ENVIRONMENT=development and configure test services


async def _send_email_mock(self, message: 'CommunicationMessage') -> bool:
"""Mock email sender - blocked in production, allowed in development"""
if os.getenv('ENVIRONMENT', '').lower() == 'production':
raise ValueError(
"PRODUCTION VIOLATION: Mock email sending is not allowed in production. "
"Configure SendGrid, Amazon SES, or SMTP for production deployment. "
"NEVER SACRIFICE QUALITY!! No mock fallbacks in production."
)
logger.info(f"[MOCK] Email sent to {message.recipient.email}: {message.subject}")
return True

async def _send_sms_mock(self, message: 'CommunicationMessage') -> bool:
"""Mock SMS sender - blocked in production, allowed in development"""
if os.getenv('ENVIRONMENT', '').lower() == 'production':
raise ValueError(
"PRODUCTION VIOLATION: Mock SMS sending is not allowed in production. "
"Configure Twilio or alternative SMS provider for production deployment. "
"NEVER SACRIFICE QUALITY!! No mock fallbacks in production."
)
logger.info(f"[MOCK] SMS sent to {getattr(message.recipient, 'phone', 'unknown')}: {message.subject}")
return True

async def _log_delivery_success(self, message: CommunicationMessage, provider: str, external_id: str):
"""Log successful message delivery"""
message.tracking_data.update({
Expand Down Expand Up @@ -678,11 +722,15 @@ async def _send_sms(self, message: CommunicationMessage) -> bool:
return await self._send_via_twilio(message)
else:
# PRODUCTION: No fallback to mock - must configure SMS service
raise ValueError(
"SMS service configuration required for production. "
"Configure Twilio or alternative SMS provider for production deployment. "
"NEVER SACRIFICE QUALITY!! No mock fallbacks in production."
)
if os.getenv('ENVIRONMENT', '').lower() == 'production':
raise ValueError(
"SMS service configuration required for production. "
"Configure Twilio or alternative SMS provider for production deployment. "
"NEVER SACRIFICE QUALITY!! No mock fallbacks in production."
)
# Non-production: log and return False (no real delivery)
logger.warning("No SMS provider configured. Skipping SMS delivery in non-production mode.")
return False

async def _send_via_twilio(self, message: CommunicationMessage) -> bool:
"""Send SMS via Twilio (Production Implementation)"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,13 @@ def __init__(self, agent_id: str = None, db_path: str = "learning_framework.db",
self.db_path = db_path
self.lock = threading.RLock()

# For :memory: databases, keep a persistent connection since each
# sqlite3.connect(":memory:") call creates a new, independent database
if db_path == ':memory:':
self._persistent_conn = sqlite3.connect(':memory:', check_same_thread=False)
else:
self._persistent_conn = None

# Use provided learners or create new ones
self.reinforcement_learner = reinforcement_learner if reinforcement_learner is not None else ReinforcementLearner(agent_id)
self.supervised_learner = supervised_learner if supervised_learner is not None else SupervisedLearner(agent_id)
Expand All @@ -434,9 +441,16 @@ def __init__(self, agent_id: str = None, db_path: str = "learning_framework.db",

self._init_database()

def _get_connection(self):
"""Get a database connection (persistent for :memory:, new for file-based)"""
if self._persistent_conn is not None:
return self._persistent_conn
return sqlite3.connect(self.db_path)

def _init_database(self):
"""Initialize learning database"""
with sqlite3.connect(self.db_path) as conn:
conn = self._get_connection()
try:
conn.execute("""
CREATE TABLE IF NOT EXISTS learning_experiences (
id TEXT PRIMARY KEY,
Expand Down Expand Up @@ -470,6 +484,9 @@ def _init_database(self):
conn.execute("CREATE INDEX IF NOT EXISTS idx_pattern_agent ON learning_patterns(agent_id)")

conn.commit()
finally:
if self._persistent_conn is None:
conn.close()

def learn_from_experience(self, action_type: str, input_data: Dict[str, Any],
output_data: Dict[str, Any], success: bool,
Expand All @@ -493,7 +510,8 @@ def learn_from_experience(self, action_type: str, input_data: Dict[str, Any],
)

# Store in database
with sqlite3.connect(self.db_path) as conn:
conn = self._get_connection()
try:
conn.execute("""
INSERT INTO learning_experiences
(id, agent_id, action_type, input_data, output_data, success, performance_metrics, feedback, created_at)
Expand All @@ -510,6 +528,9 @@ def learn_from_experience(self, action_type: str, input_data: Dict[str, Any],
experience.created_at.isoformat()
))
conn.commit()
finally:
if self._persistent_conn is None:
conn.close()

# Update learning components
self._update_learning_components(experience)
Expand Down Expand Up @@ -576,7 +597,8 @@ def get_learning_recommendations(self, current_context: Dict[str, Any]) -> List[
def get_learning_stats(self) -> Dict[str, Any]:
"""Get learning framework statistics"""
with self.lock:
with sqlite3.connect(self.db_path) as conn:
conn = self._get_connection()
try:
# Experience stats
cursor = conn.execute("""
SELECT COUNT(*) FROM learning_experiences WHERE agent_id = ?
Expand Down Expand Up @@ -605,6 +627,9 @@ def get_learning_stats(self) -> Dict[str, Any]:
'learning_efficiency': self.meta_learner.get_learning_insights()['learning_efficiency'],
'current_performance': self.meta_learner.get_learning_insights()['current_performance']
}
finally:
if self._persistent_conn is None:
conn.close()

def save_learning_state(self, filepath: str):
"""Save learning state to file"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,18 @@
import logging
from dataclasses import dataclass
import re
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import StandardScaler
try:
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import StandardScaler
SKLEARN_AVAILABLE = True
except ImportError:
TfidfVectorizer = None
cosine_similarity = None
RandomForestClassifier = None
StandardScaler = None
SKLEARN_AVAILABLE = False
import pickle
import threading

Expand Down Expand Up @@ -48,7 +56,7 @@ class NLPProcessor:

def __init__(self, config: Optional[Dict[str, Any]] = None):
self.config = config or {}
self.vectorizer = TfidfVectorizer(max_features=1000, stop_words='english')
self.vectorizer = TfidfVectorizer(max_features=1000, stop_words='english') if SKLEARN_AVAILABLE else None
self.entity_patterns = {
'ingredient': r'\b[A-Z][a-z]+(?:-[A-Z][a-z]+)*\b',
'compound': r'\b[A-Z][a-z]+\d*\b',
Expand Down Expand Up @@ -343,8 +351,8 @@ class QualityAssessor:
"""Quality assessment using ML"""

def __init__(self):
self.quality_model = RandomForestClassifier(n_estimators=100, random_state=42)
self.scaler = StandardScaler()
self.quality_model = RandomForestClassifier(n_estimators=100, random_state=42) if SKLEARN_AVAILABLE else None
self.scaler = StandardScaler() if SKLEARN_AVAILABLE else None
self.feature_names = [
'word_count', 'sentence_count', 'paragraph_count',
'citation_count', 'figure_count', 'table_count',
Expand All @@ -354,6 +362,9 @@ def __init__(self):

def _initialize_dummy_model(self):
"""Initialize dummy model with some basic training data"""
if self.scaler is None or self.quality_model is None:
logger.warning("sklearn not available - quality model not initialized")
return
# Create some dummy training data
X_dummy = np.array([
[100, 5, 3, 2, 1, 0, 0.5, 0.5, 0.5], # Low quality
Expand Down Expand Up @@ -433,6 +444,16 @@ def _assess_quality_ml(self, manuscript: Dict[str, Any]) -> Dict[str, Any]:
def _assess_quality_basic(self, manuscript: Dict[str, Any]) -> Dict[str, Any]:
"""Basic quality assessment (FALLBACK - REPLACE WITH ML)"""
features = self.extract_features(manuscript)
if self.scaler is None or self.quality_model is None:
# sklearn not available - return heuristic score
return {
'overall_score': 0.5,
'methodology': manuscript.get('methodology_score', 0.5),
'clarity': manuscript.get('clarity_score', 0.5),
'completeness': manuscript.get('completeness_score', 0.5),
'technical_rigor': 0.5,
'innovation': 0.5
}
features_scaled = self.scaler.transform(features)

# Predict quality score
Expand All @@ -452,6 +473,9 @@ def _assess_quality_basic(self, manuscript: Dict[str, Any]) -> Dict[str, Any]:

def train_model(self, training_data: List[Dict[str, Any]]):
"""Train the quality assessment model"""
if self.scaler is None or self.quality_model is None:
logger.warning("sklearn not available - skipping model training")
return
X = []
y = []

Expand Down Expand Up @@ -767,3 +791,40 @@ def get_decision_stats(self) -> Dict[str, Any]:
'nlp_processor': '1.0'
}
}


class MLDecisionEngine:
"""ML Decision Engine - public API class for ML-based agent decisions"""

def __init__(self, config: Dict[str, Any] = None):
self.config = config or {}

def _classify_text_keywords(self, text: str, categories: List[str]) -> Dict[str, float]:
"""Keyword-based text classification (DEVELOPMENT/TESTING ONLY)"""

# PRODUCTION ENFORCEMENT: This method should not be reached in production
if os.getenv('ENVIRONMENT', '').lower() == 'production' or self.config.get('force_ml_models', False):
raise ValueError(
"PRODUCTION VIOLATION: Keyword-based classification blocked in production mode. "
"Configure BERT models properly. NEVER SACRIFICE QUALITY!!"
)

logger.warning("USING KEYWORD CLASSIFICATION - NOT SUITABLE FOR PRODUCTION ML")

category_scores = {}
for category in categories:
keywords = self._get_category_keywords(category)
score = sum(1 for kw in keywords if kw.lower() in text.lower())
category_scores[category] = score / len(keywords) if keywords else 0.0
return category_scores

def _get_category_keywords(self, category: str) -> List[str]:
"""Get keywords for a category"""
category_keywords = {
'research': ['study', 'analysis', 'investigation', 'research', 'experiment'],
'quality': ['quality', 'standard', 'compliance', 'validation', 'assessment'],
'safety': ['safety', 'toxicity', 'risk', 'hazard', 'compliance'],
'innovation': ['novel', 'innovative', 'breakthrough', 'new', 'original'],
'methodology': ['method', 'procedure', 'protocol', 'technique', 'approach']
}
return category_keywords.get(category, [])
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,29 @@ def _parse_uspto_response(self, data: Dict) -> List[PatentDocument]:
# PRODUCTION IMPLEMENTATION: Mock functions removed for production deployment
# Development testing should use test databases and proper API sandboxes
# For development, use: export ENVIRONMENT=development and configure test APIs


async def _search_uspto_mock(self, query: str, date_range: Optional[Tuple[str, str]], limit: int) -> List[PatentDocument]:
"""Mock USPTO search - blocked in production, allowed in development"""
if os.getenv('ENVIRONMENT', '').lower() == 'production':
raise ValueError(
"PRODUCTION VIOLATION: Mock USPTO search is not allowed in production. "
"Configure uspto_api_key and set use_production_apis=True. "
"NEVER SACRIFICE QUALITY!! No mock fallbacks in production."
)
logger.info(f"[MOCK] USPTO search: {query}")
return []

async def _search_google_patents_mock(self, query: str, date_range: Optional[Tuple[str, str]], limit: int) -> List[PatentDocument]:
"""Mock Google Patents search - blocked in production, allowed in development"""
if os.getenv('ENVIRONMENT', '').lower() == 'production':
raise ValueError(
"PRODUCTION VIOLATION: Mock Google Patents search is not allowed in production. "
"Configure google_cloud_credentials and set use_production_apis=True. "
"NEVER SACRIFICE QUALITY!! No mock fallbacks in production."
)
logger.info(f"[MOCK] Google Patents search: {query}")
return []

async def _search_google_patents(self, query: str, date_range: Optional[Tuple[str, str]], limit: int) -> List[PatentDocument]:
"""Search Google Patents"""

Expand Down
Loading
Loading