262 lines
12 KiB
Python
262 lines
12 KiB
Python
# Copyright (c) 2025 Patrick Motsch
|
|
# All rights reserved.
|
|
# adaptiveLearningEngine.py
|
|
# Enhanced learning engine that tracks validation patterns and adapts prompts
|
|
|
|
import logging
|
|
from typing import Dict, Any, List, Optional
|
|
from datetime import datetime, timezone
|
|
from collections import defaultdict
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class AdaptiveLearningEngine:
|
|
"""Enhanced learning engine that tracks validation patterns and adapts prompts"""
|
|
|
|
def __init__(self):
|
|
self.validationHistory = [] # Store validation results with context
|
|
self.failurePatterns = defaultdict(list) # Track failure patterns by action type
|
|
self.successPatterns = defaultdict(list) # Track success patterns
|
|
self.actionAttempts = defaultdict(int) # Track attempt counts per action
|
|
self.learningInsights = {} # Store learned insights per workflow
|
|
|
|
def recordValidationResult(self, validationResult: Dict[str, Any], actionContext: Dict[str, Any],
|
|
workflowId: str, attemptNumber: int):
|
|
"""Record validation result and learn from it"""
|
|
try:
|
|
actionName = actionContext.get('actionName', 'unknown')
|
|
|
|
# Store validation history
|
|
validationEntry = {
|
|
'workflowId': workflowId,
|
|
'actionName': actionName,
|
|
'attemptNumber': attemptNumber,
|
|
'validationResult': validationResult,
|
|
'actionContext': actionContext,
|
|
'timestamp': datetime.now(timezone.utc).isoformat(),
|
|
'success': validationResult.get('overallSuccess', False),
|
|
'qualityScore': validationResult.get('qualityScore', 0.0)
|
|
}
|
|
|
|
self.validationHistory.append(validationEntry)
|
|
|
|
# Track patterns
|
|
if validationResult.get('overallSuccess', False):
|
|
self.successPatterns[actionName].append(validationEntry)
|
|
else:
|
|
self.failurePatterns[actionName].append(validationEntry)
|
|
|
|
# Update attempt count
|
|
self.actionAttempts[f"{workflowId}:{actionName}"] += 1
|
|
|
|
# Generate learning insights
|
|
self._generateLearningInsights(workflowId, actionName)
|
|
|
|
logger.info(f"Recorded validation for {actionName} (attempt {attemptNumber}): "
|
|
f"Success={validationResult.get('overallSuccess', False)}, "
|
|
f"Quality={validationResult.get('qualityScore', 0.0)}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error recording validation result: {str(e)}")
|
|
|
|
def getAdaptiveContextForActionSelection(self, workflowId: str, userPrompt: str) -> Dict[str, Any]:
|
|
"""Generate adaptive context for action selection prompt"""
|
|
try:
|
|
# Get recent validation history for this workflow
|
|
recentValidations = [
|
|
v for v in self.validationHistory
|
|
if v['workflowId'] == workflowId
|
|
][-5:] # Last 5 validations
|
|
|
|
# Analyze failure patterns
|
|
failureAnalysis = self._analyzeFailurePatterns(recentValidations)
|
|
|
|
# Generate specific guidance for next action
|
|
adaptiveGuidance = self._generateActionGuidance(userPrompt, recentValidations, failureAnalysis)
|
|
|
|
return {
|
|
'recentValidations': recentValidations,
|
|
'failureAnalysis': failureAnalysis,
|
|
'adaptiveGuidance': adaptiveGuidance,
|
|
'learningInsights': self.learningInsights.get(workflowId, {}),
|
|
'escalationLevel': self._getEscalationLevel(workflowId)
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error generating adaptive context: {str(e)}")
|
|
return {}
|
|
|
|
def getAdaptiveContextForParameters(self, workflowId: str, actionName: str,
|
|
parametersContext: str) -> Dict[str, Any]:
|
|
"""Generate adaptive context for parameter selection prompt"""
|
|
try:
|
|
# Get validation history for this specific action name
|
|
actionValidations = [
|
|
v for v in self.validationHistory
|
|
if v['workflowId'] == workflowId and v['actionName'] == actionName
|
|
][-3:] # Last 3 attempts for this action
|
|
|
|
# Analyze what went wrong in previous attempts
|
|
failureAnalysis = self._analyzeParameterFailures(actionValidations)
|
|
|
|
# Generate specific parameter guidance
|
|
parameterGuidance = self._generateParameterGuidance(actionName, parametersContext, failureAnalysis)
|
|
|
|
return {
|
|
'actionValidations': actionValidations,
|
|
'failureAnalysis': failureAnalysis,
|
|
'parameterGuidance': parameterGuidance,
|
|
'attemptNumber': len(actionValidations) + 1,
|
|
'escalationLevel': self._getEscalationLevel(workflowId)
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error generating parameter context: {str(e)}")
|
|
return {}
|
|
|
|
def _analyzeFailurePatterns(self, validations: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
"""Analyze failure patterns from validation history"""
|
|
if not validations:
|
|
return {}
|
|
|
|
failedValidations = [v for v in validations if not v['success']]
|
|
|
|
if not failedValidations:
|
|
return {'hasFailures': False}
|
|
|
|
# Extract common failure themes
|
|
commonIssues = []
|
|
for validation in failedValidations:
|
|
issues = validation['validationResult'].get('validationDetails', [{}])
|
|
for issue in issues:
|
|
commonIssues.extend(issue.get('issues', []))
|
|
|
|
# Count most common issues
|
|
issueCounts = defaultdict(int)
|
|
for issue in commonIssues:
|
|
issueCounts[issue] += 1
|
|
|
|
return {
|
|
'hasFailures': True,
|
|
'failureCount': len(failedValidations),
|
|
'commonIssues': dict(sorted(issueCounts.items(), key=lambda x: x[1], reverse=True)),
|
|
'lastFailure': failedValidations[-1] if failedValidations else None
|
|
}
|
|
|
|
def _analyzeParameterFailures(self, validations: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
"""Analyze parameter-specific failure patterns"""
|
|
if not validations:
|
|
return {'hasFailures': False}
|
|
|
|
failedValidations = [v for v in validations if not v['success']]
|
|
|
|
if not failedValidations:
|
|
return {'hasFailures': False}
|
|
|
|
# Extract common failure themes
|
|
commonIssues = []
|
|
for validation in failedValidations:
|
|
issues = validation['validationResult'].get('validationDetails', [{}])
|
|
for issue in issues:
|
|
commonIssues.extend(issue.get('issues', []))
|
|
|
|
# Count most common issues
|
|
issueCounts = defaultdict(int)
|
|
for issue in commonIssues:
|
|
issueCounts[issue] += 1
|
|
|
|
return {
|
|
'hasFailures': True,
|
|
'failureCount': len(failedValidations),
|
|
'commonIssues': dict(sorted(issueCounts.items(), key=lambda x: x[1], reverse=True)),
|
|
'lastFailure': failedValidations[-1] if failedValidations else None,
|
|
'attemptNumber': len(validations)
|
|
}
|
|
|
|
def _generateActionGuidance(self, userPrompt: str, validations: List[Dict[str, Any]],
|
|
failureAnalysis: Dict[str, Any]) -> str:
|
|
"""Generate specific guidance for next action based on learning"""
|
|
if not failureAnalysis.get('hasFailures', False):
|
|
return "No previous failures detected. Proceed with standard approach."
|
|
|
|
guidance_parts = []
|
|
|
|
# Add failure awareness
|
|
failureCount = failureAnalysis.get('failureCount', 0)
|
|
if failureCount >= 3:
|
|
guidance_parts.append("CRITICAL: Multiple previous attempts have failed. Consider alternative approaches.")
|
|
elif failureCount >= 1:
|
|
guidance_parts.append("WARNING: Previous attempts have failed. Learn from validation feedback.")
|
|
|
|
# Add specific issue guidance
|
|
commonIssues = failureAnalysis.get('commonIssues', {})
|
|
if commonIssues:
|
|
guidance_parts.append("COMMON FAILURE PATTERNS:")
|
|
for issue, count in list(commonIssues.items())[:3]: # Top 3 issues
|
|
guidance_parts.append(f"- {issue} (occurred {count} times)")
|
|
|
|
# Add specific action guidance based on user prompt
|
|
if "email" in userPrompt.lower() and "outlook" in userPrompt.lower():
|
|
if any("account" in str(issue).lower() for issue in commonIssues.keys()):
|
|
guidance_parts.append("SPECIFIC GUIDANCE: Ensure email is sent from the correct account (valueon).")
|
|
if any("attachment" in str(issue).lower() for issue in commonIssues.keys()):
|
|
guidance_parts.append("SPECIFIC GUIDANCE: Verify PDF attachment is properly included.")
|
|
if any("summary" in str(issue).lower() for issue in commonIssues.keys()):
|
|
guidance_parts.append("SPECIFIC GUIDANCE: Include German summary in email body.")
|
|
|
|
return "\n".join(guidance_parts) if guidance_parts else "No specific guidance available."
|
|
|
|
def _generateParameterGuidance(self, actionName: str, parametersContext: str,
|
|
failureAnalysis: Dict[str, Any]) -> str:
|
|
"""Generate generic parameter guidance based on previous failures (no app-specific logic)."""
|
|
if not failureAnalysis.get('hasFailures', False):
|
|
return "No previous parameter failures. Use standard parameter values."
|
|
|
|
guidanceParts = []
|
|
|
|
# Attempt awareness
|
|
attemptNumber = failureAnalysis.get('attemptNumber', 1)
|
|
if attemptNumber and attemptNumber >= 3:
|
|
guidanceParts.append(f"Attempt #{attemptNumber}: Adjust parameters based on validation feedback.")
|
|
|
|
# Generic issues summary
|
|
commonIssues = failureAnalysis.get('commonIssues', {}) or {}
|
|
if commonIssues:
|
|
guidanceParts.append("Address the following parameter issues:")
|
|
for issueKey, issueDesc in commonIssues.items():
|
|
guidanceParts.append(f"- {issueKey}: {issueDesc}")
|
|
|
|
# Keep guidance format stable
|
|
return "\n".join(guidanceParts) if guidanceParts else "Use standard parameter values."
|
|
|
|
def _getEscalationLevel(self, workflowId: str) -> str:
|
|
"""Determine escalation level based on failure patterns"""
|
|
workflowValidations = [v for v in self.validationHistory if v['workflowId'] == workflowId]
|
|
failedAttempts = len([v for v in workflowValidations if not v['success']])
|
|
|
|
if failedAttempts >= 5:
|
|
return "critical"
|
|
elif failedAttempts >= 3:
|
|
return "high"
|
|
elif failedAttempts >= 1:
|
|
return "medium"
|
|
else:
|
|
return "low"
|
|
|
|
def _generateLearningInsights(self, workflowId: str, actionName: str):
|
|
"""Generate learning insights for a workflow"""
|
|
if workflowId not in self.learningInsights:
|
|
self.learningInsights[workflowId] = {}
|
|
|
|
# Analyze patterns for this workflow
|
|
workflowValidations = [v for v in self.validationHistory if v['workflowId'] == workflowId]
|
|
|
|
insights = {
|
|
'totalAttempts': len(workflowValidations),
|
|
'successfulAttempts': len([v for v in workflowValidations if v['success']]),
|
|
'failedAttempts': len([v for v in workflowValidations if not v['success']]),
|
|
'lastActionName': actionName,
|
|
'escalationLevel': self._getEscalationLevel(workflowId)
|
|
}
|
|
|
|
self.learningInsights[workflowId] = insights
|