""" FEEDBACK LEARNING SYSTEM Learns from user feedback to improve future responses """ import json import os from typing import Dict, List, Optional, Any from datetime import datetime from collections import defaultdict import logging logger = logging.getLogger(__name__) class FeedbackLearner: """Learns from user feedback to improve responses""" def __init__(self): self.feedback_data = defaultdict(list) self.response_quality = {} self.handler_feedback = defaultdict(lambda: {'positive': 0, 'negative': 0, 'total': 0}) self.learned_improvements = {} self.load_feedback_history() def record_feedback(self, session_id: str, message_id: str, handler: str, response: str, feedback_type: str, rating: Optional[int] = None, user_comment: str = '', corrections: Optional[str] = None) -> Dict: """ Record user feedback on a response Args: session_id: Session ID message_id: ID of the message/response handler: Handler that generated response (code, knowledge, conversation, etc.) response: The response text feedback_type: 'positive', 'negative', 'neutral' rating: Rating from 1-5 (optional) user_comment: User's comment (optional) corrections: Corrected version if user provided one (optional) """ feedback_record = { 'timestamp': datetime.now().isoformat(), 'session_id': session_id, 'message_id': message_id, 'handler': handler, 'response': response[:500], # Store first 500 chars 'feedback_type': feedback_type, 'rating': rating, 'comment': user_comment, 'corrections': corrections } # Store feedback self.feedback_data[handler].append(feedback_record) # Update statistics self.handler_feedback[handler]['total'] += 1 if feedback_type == 'positive': self.handler_feedback[handler]['positive'] += 1 elif feedback_type == 'negative': self.handler_feedback[handler]['negative'] += 1 # Learn from feedback self._learn_from_feedback(handler, feedback_record) logger.info(f"Feedback recorded: {handler} - {feedback_type} - Rating: {rating}") return feedback_record def _learn_from_feedback(self, handler: str, feedback: Dict): """Extract learning from feedback""" if handler not in self.learned_improvements: self.learned_improvements[handler] = { 'improvements': [], 'patterns': [], 'avoid_patterns': [] } # Positive feedback if feedback['feedback_type'] == 'positive': # Store what made a good response self.learned_improvements[handler]['patterns'].append({ 'response_snippet': feedback['response'][:100], 'rating': feedback['rating'], 'timestamp': feedback['timestamp'] }) # Negative feedback elif feedback['feedback_type'] == 'negative': # Store what to avoid self.learned_improvements[handler]['avoid_patterns'].append({ 'bad_response': feedback['response'][:100], 'correction': feedback['corrections'], 'comment': feedback['comment'], 'rating': feedback['rating'], 'timestamp': feedback['timestamp'] }) # Learn from corrections if feedback['corrections']: self.learned_improvements[handler]['improvements'].append({ 'wrong': feedback['response'][:100], 'correct': feedback['corrections'], 'handler': handler, 'timestamp': feedback['timestamp'] }) def get_handler_quality_score(self, handler: str) -> Dict: """Get quality score for a handler based on feedback""" feedback = self.handler_feedback[handler] if feedback['total'] == 0: return { 'handler': handler, 'quality_score': 0.5, 'total_feedback': 0, 'positive_percentage': 0.0, 'rating': 'Unknown' } positive_pct = feedback['positive'] / feedback['total'] * 100 quality_score = (feedback['positive'] - feedback['negative']) / feedback['total'] quality_score = (quality_score + 1) / 2 # Normalize to 0-1 rating_map = { (0.8, 1.0): 'Excellent', (0.6, 0.8): 'Good', (0.4, 0.6): 'Fair', (0.2, 0.4): 'Poor', (0.0, 0.2): 'Very Poor' } rating = 'Unknown' for (min_val, max_val), rating_name in rating_map.items(): if min_val <= quality_score < max_val: rating = rating_name break return { 'handler': handler, 'quality_score': round(quality_score, 3), 'total_feedback': feedback['total'], 'positive': feedback['positive'], 'negative': feedback['negative'], 'positive_percentage': round(positive_pct, 1), 'rating': rating } def get_improvement_suggestions(self, handler: str) -> Dict: """Get specific improvement suggestions for a handler""" if handler not in self.learned_improvements: return {'handler': handler, 'suggestions': []} improvements = self.learned_improvements[handler] suggestions = [] # Suggest based on avoid patterns if improvements['avoid_patterns']: most_common_issue = max( improvements['avoid_patterns'], key=lambda x: len(x.get('comment', '')) ) suggestions.append({ 'type': 'avoid', 'issue': most_common_issue['bad_response'], 'fix': most_common_issue['correction'], 'frequency': len(improvements['avoid_patterns']) }) # Suggest based on corrections if improvements['improvements']: suggestions.append({ 'type': 'common_mistake', 'count': len(improvements['improvements']), 'examples': improvements['improvements'][:3] }) return { 'handler': handler, 'suggestions': suggestions, 'total_improvements_learned': len(improvements['improvements']), 'confidence': len(improvements['patterns']) / max(1, len(improvements['patterns']) + len(improvements['avoid_patterns'])) } def apply_learned_improvements(self, handler: str, response: str) -> Dict: """Apply learned improvements to a response""" if handler not in self.learned_improvements: return {'original': response, 'improved': response, 'applied': []} improved = response applied = [] improvements = self.learned_improvements[handler]['improvements'] # Try to apply learned corrections for improvement in improvements: if improvement['wrong'] in response: improved = improved.replace( improvement['wrong'], improvement['correct'] ) applied.append({ 'from': improvement['wrong'], 'to': improvement['correct'] }) return { 'original': response, 'improved': improved, 'applied': applied, 'modified': len(applied) > 0 } def get_feedback_summary(self) -> Dict: """Get overall feedback summary""" summary = { 'total_feedback_records': sum(len(v) for v in self.feedback_data.values()), 'handlers_evaluated': len(self.handler_feedback), 'handler_scores': {}, 'overall_quality': 0.0, 'most_improved_handler': None, 'most_problematic_handler': None } # Calculate scores for each handler quality_scores = [] for handler in self.handler_feedback.keys(): score = self.get_handler_quality_score(handler) summary['handler_scores'][handler] = score quality_scores.append((handler, score['quality_score'])) if quality_scores: summary['overall_quality'] = sum(s[1] for s in quality_scores) / len(quality_scores) summary['most_improved_handler'] = max(quality_scores, key=lambda x: x[1])[0] summary['most_problematic_handler'] = min(quality_scores, key=lambda x: x[1])[0] return summary def save_feedback_history(self): """Save feedback history to file""" try: os.makedirs('noahski_data', exist_ok=True) feedback_file = 'noahski_data/user_feedback.json' # Convert defaultdict to regular dict for JSON serialization feedback_dict = { 'feedback': {k: v for k, v in self.feedback_data.items()}, 'handler_stats': {k: dict(v) for k, v in self.handler_feedback.items()}, 'learned_improvements': self.learned_improvements, 'timestamp': datetime.now().isoformat() } with open(feedback_file, 'w', encoding='utf-8') as f: json.dump(feedback_dict, f, indent=2, ensure_ascii=False) logger.info(f"Feedback history saved: {feedback_file}") except Exception as e: logger.error(f"Error saving feedback history: {e}") def load_feedback_history(self): """Load feedback history from file""" try: feedback_file = 'noahski_data/user_feedback.json' if os.path.exists(feedback_file): with open(feedback_file, 'r', encoding='utf-8') as f: data = json.load(f) self.feedback_data = defaultdict(list, data.get('feedback', {})) # Reconstruct handler_feedback for handler, stats in data.get('handler_stats', {}).items(): self.handler_feedback[handler] = stats self.learned_improvements = data.get('learned_improvements', {}) logger.info(f"Feedback history loaded: {feedback_file}") except Exception as e: logger.error(f"Error loading feedback history: {e}") # Global instance _feedback_learner = None def get_feedback_learner() -> FeedbackLearner: """Get or create global feedback learner""" global _feedback_learner if _feedback_learner is None: _feedback_learner = FeedbackLearner() return _feedback_learner