From 3e986d83f105f71a2c0a06a69cb8bedf24cf9c23 Mon Sep 17 00:00:00 2001 From: truetype Date: Fri, 20 Mar 2026 21:10:58 +0100 Subject: [PATCH] Phase 18: Advanced analytics and enhanced documentation --- README.md | 99 ++++++++++- analytics.py | 494 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 585 insertions(+), 8 deletions(-) create mode 100644 analytics.py diff --git a/README.md b/README.md index 3480aae..9a63b29 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Un agente configurabile basato su DeepAgents che supporta piΓΉ provider LLM. - **Web UI responsive**: Interfaccia web per gestione configurazioni ottimizzata per mobile e desktop - **Dashboard monitoraggio**: Monitoraggio in tempo reale del sistema con supporto multilingue - **Marketplace plugin**: Scopri e installa plugin dal marketplace +- **Analytics avanzate**: Analisi dettagliate dell'uso e raccomandazioni - **Comandi integrati**: Comandi speciali con prefisso "/" per gestione rapida - **Monitoraggio**: Sistema di logging e monitoraggio delle performance - **Dashboard performance**: Monitoraggio dettagliato delle performance @@ -225,6 +226,7 @@ Quando interagisci con l'agente, puoi usare comandi speciali preceduti da "/" pe /clear - Pulisce lo schermo /plugins - Elenca i plugin disponibili /marketplace - Apre il marketplace dei plugin +/analytics - Mostra il report analytics /language - Cambia la lingua dell'interfaccia /performance - Mostra il report delle performance /monitoring - Apre la dashboard di monitoraggio @@ -250,15 +252,25 @@ Ollama URL: http://192.168.0.50:11434 πŸ‘€ You: Elenca i file nella directory corrente πŸ€– MaxClaw: [Esegue ls e mostra i file] -πŸ‘€ You: /performance -πŸ“Š Performance Report (Last 24 hours) -================================================== +πŸ‘€ You: /analytics +πŸ“Š Advanced Analytics Report (Last 30 days) +============================================================ -πŸ“ˆ Request Statistics: - Total Requests: 150 - Total Errors: 2 - Error Rate: 1.33% - Avg Response Time: 0.245s +πŸ’¬ Conversation Statistics: + Total Conversations: 45 + Total Message Characters: 12,500 + Total Response Characters: 25,800 + Average Message Length: 278 chars + Average Response Length: 573 chars + +πŸ€– Provider Usage: + ollama: 30 (66.7%) + openai: 15 (33.3%) + Most Used: ollama + +πŸ’‘ Insights and Recommendations: + πŸ’‘ You're heavily using ollama (67% of conversations). Consider diversifying with other providers for different use cases. + βœ… Great response times! Average is 0.8s. πŸ‘€ You: /exit πŸ‘‹ Goodbye! @@ -324,6 +336,76 @@ curl -X POST http://localhost:8000/configurations \ }' ``` +## Analytics Avanzate + +MaxClaw Agent include un sistema avanzato di analytics per monitorare l'uso e ottenere raccomandazioni. + +### Report Analytics + +Il report analytics include: + +1. **Statistiche Conversazioni**: + - Numero totale di conversazioni + - Caratteri totali dei messaggi e risposte + - Lunghezza media di messaggi e risposte + +2. **Utilizzo Provider**: + - Quale provider viene usato di piΓΉ + - Distribuzione percentuale dell'uso + - Modello piΓΉ utilizzato + +3. **Metriche di Performance**: + - Tempo di risposta medio + - Tempo di risposta minimo e massimo + - Tempo totale di risposta + +4. **Utilizzo Strumenti**: + - Quali strumenti vengono usati di piΓΉ + - Numero di utilizzi per strumento + +5. **Utilizzo Comandi**: + - Quali comandi vengono usati di piΓΉ + - Numero di utilizzi per comando + +6. **Pattern di Errori**: + - Errori ricorrenti + - Pattern di errore piΓΉ comuni + +7. **Statistiche Sessioni**: + - Numero totale di sessioni + - Durata media delle sessioni + - Messaggi medi per sessione + +### Raccomandazioni + +Il sistema genera automaticamente raccomandazioni basate sui pattern di utilizzo: + +- **Diversificazione provider**: Se usi troppo un singolo provider +- **Ottimizzazione performance**: Se i tempi di risposta sono lenti +- **Utilizzo strumenti**: Se usi frequentemente certi strumenti +- **Gestione errori**: Se ci sono errori ricorrenti +- **Pattern di utilizzo**: Se l'uso Γ¨ troppo basso o troppo alto + +### Visualizzare le Analytics + +```bash +# Usa il comando /analytics +/analytics + +# Oppure esegui lo script direttamente +python analytics.py +``` + +### Configurazione + +I dati di analytics vengono salvati in `~/.maxclaw/analytics/` e includono: +- Conversazioni (truncati per privacy) +- Utilizzo strumenti +- Utilizzo provider +- Utilizzo comandi +- Pattern di errori +- Sessioni utente + ## Marketplace dei Plugin MaxClaw Agent include un marketplace per scoprire e installare plugin. @@ -944,6 +1026,7 @@ MaxClaw Agent include un sistema di monitoraggio e logging: - **Dashboard web**: Monitoraggio in tempo reale via interfaccia web con supporto multilingue - **Interfacce responsive**: Ottimizzate per dispositivi mobili e desktop - **Marketplace plugin**: Scopri e installa plugin dal marketplace +- **Analytics avanzate**: Analisi dettagliate dell'uso e raccomandazioni ## Sicurezza diff --git a/analytics.py b/analytics.py new file mode 100644 index 0000000..c30998e --- /dev/null +++ b/analytics.py @@ -0,0 +1,494 @@ +""" +Advanced analytics for MaxClaw Agent +Provides usage analytics, insights, and reporting +""" +import os +import sys +import json +import time +from datetime import datetime, timedelta +from typing import Dict, List, Any, Optional +from pathlib import Path +from collections import defaultdict, Counter + +# Add current directory to path +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + + +class AnalyticsEngine: + """Advanced analytics engine for MaxClaw Agent""" + + def __init__(self, data_dir: Optional[str] = None): + """ + Initialize analytics engine + + Args: + data_dir: Directory for storing analytics data + """ + if data_dir is None: + self.data_dir = Path.home() / ".maxclaw" / "analytics" + else: + self.data_dir = Path(data_dir) + + # Create data directory + self.data_dir.mkdir(parents=True, exist_ok=True) + + # Analytics data + self.usage_data = { + "conversations": [], + "tool_usage": defaultdict(int), + "provider_usage": defaultdict(int), + "command_usage": defaultdict(int), + "error_patterns": defaultdict(int), + "response_times": [], + "user_sessions": [] + } + + # Load existing data + self._load_data() + + def _load_data(self): + """Load analytics data from file""" + data_file = self.data_dir / "analytics_data.json" + + if data_file.exists(): + try: + with open(data_file, 'r') as f: + data = json.load(f) + # Convert defaultdicts back + self.usage_data["tool_usage"] = defaultdict(int, data.get("tool_usage", {})) + self.usage_data["provider_usage"] = defaultdict(int, data.get("provider_usage", {})) + self.usage_data["command_usage"] = defaultdict(int, data.get("command_usage", {})) + self.usage_data["error_patterns"] = defaultdict(int, data.get("error_patterns", {})) + self.usage_data["conversations"] = data.get("conversations", []) + self.usage_data["response_times"] = data.get("response_times", []) + self.usage_data["user_sessions"] = data.get("user_sessions", []) + except Exception as e: + print(f"Warning: Could not load analytics data: {e}") + + def _save_data(self): + """Save analytics data to file""" + data_file = self.data_dir / "analytics_data.json" + + try: + # Convert defaultdicts to regular dicts for JSON serialization + data_to_save = { + "conversations": self.usage_data["conversations"], + "tool_usage": dict(self.usage_data["tool_usage"]), + "provider_usage": dict(self.usage_data["provider_usage"]), + "command_usage": dict(self.usage_data["command_usage"]), + "error_patterns": dict(self.usage_data["error_patterns"]), + "response_times": self.usage_data["response_times"], + "user_sessions": self.usage_data["user_sessions"] + } + + with open(data_file, 'w') as f: + json.dump(data_to_save, f, indent=2) + except Exception as e: + print(f"Warning: Could not save analytics data: {e}") + + def record_conversation(self, user_message: str, agent_response: str, + provider: str, model: str, response_time: float): + """Record a conversation for analytics""" + conversation = { + "timestamp": datetime.now().isoformat(), + "user_message": user_message[:100], # Truncate for privacy + "agent_response": agent_response[:100], # Truncate for privacy + "provider": provider, + "model": model, + "response_time": response_time, + "message_length": len(user_message), + "response_length": len(agent_response) + } + + self.usage_data["conversations"].append(conversation) + self.usage_data["response_times"].append(response_time) + + # Keep only last 1000 conversations + if len(self.usage_data["conversations"]) > 1000: + self.usage_data["conversations"] = self.usage_data["conversations"][-1000:] + + # Keep only last 5000 response times + if len(self.usage_data["response_times"]) > 5000: + self.usage_data["response_times"] = self.usage_data["response_times"][-5000:] + + self._save_data() + + def record_tool_usage(self, tool_name: str, success: bool, duration: float): + """Record tool usage""" + self.usage_data["tool_usage"][tool_name] += 1 + self._save_data() + + def record_provider_usage(self, provider: str, model: str): + """Record provider usage""" + key = f"{provider}:{model}" + self.usage_data["provider_usage"][key] += 1 + self._save_data() + + def record_command_usage(self, command: str): + """Record command usage""" + self.usage_data["command_usage"][command] += 1 + self._save_data() + + def record_error(self, error_type: str, error_message: str): + """Record error patterns""" + # Simplify error message for pattern recognition + simplified = error_message[:50].lower() + self.usage_data["error_patterns"][simplified] += 1 + self._save_data() + + def record_user_session(self, session_id: str, duration: float, + messages_count: int, tools_used: List[str]): + """Record user session""" + session = { + "session_id": session_id, + "timestamp": datetime.now().isoformat(), + "duration": duration, + "messages_count": messages_count, + "tools_used": tools_used, + "tools_count": len(tools_used) + } + + self.usage_data["user_sessions"].append(session) + + # Keep only last 100 sessions + if len(self.usage_data["user_sessions"]) > 100: + self.usage_data["user_sessions"] = self.usage_data["user_sessions"][-100:] + + self._save_data() + + def get_usage_summary(self, days: int = 30) -> Dict[str, Any]: + """ + Get usage summary for the last N days + + Args: + days: Number of days to include + + Returns: + Dictionary with usage summary + """ + cutoff_time = datetime.now() - timedelta(days=days) + + # Filter recent conversations + recent_conversations = [ + c for c in self.usage_data["conversations"] + if datetime.fromisoformat(c["timestamp"]) > cutoff_time + ] + + # Calculate statistics + total_conversations = len(recent_conversations) + total_messages = sum(c.get("message_length", 0) for c in recent_conversations) + total_responses = sum(c.get("response_length", 0) for c in recent_conversations) + + # Provider usage + provider_counts = Counter() + for conv in recent_conversations: + provider = conv.get("provider", "unknown") + provider_counts[provider] += 1 + + # Model usage + model_counts = Counter() + for conv in recent_conversations: + model = conv.get("model", "unknown") + model_counts[model] += 1 + + # Response time statistics + recent_response_times = [ + c.get("response_time", 0) for c in recent_conversations + if c.get("response_time") + ] + + if recent_response_times: + avg_response_time = sum(recent_response_times) / len(recent_response_times) + min_response_time = min(recent_response_times) + max_response_time = max(recent_response_times) + else: + avg_response_time = min_response_time = max_response_time = 0 + + # Tool usage + tool_usage = dict(self.usage_data["tool_usage"]) + + # Command usage + command_usage = dict(self.usage_data["command_usage"]) + + # Error patterns + error_patterns = dict(self.usage_data["error_patterns"]) + + # Session statistics + recent_sessions = [ + s for s in self.usage_data["user_sessions"] + if datetime.fromisoformat(s["timestamp"]) > cutoff_time + ] + + total_sessions = len(recent_sessions) + total_session_duration = sum(s.get("duration", 0) for s in recent_sessions) + avg_session_duration = total_session_duration / total_sessions if total_sessions > 0 else 0 + + return { + "period_days": days, + "conversations": { + "total": total_conversations, + "total_messages_chars": total_messages, + "total_responses_chars": total_responses, + "avg_message_length": total_messages / total_conversations if total_conversations > 0 else 0, + "avg_response_length": total_responses / total_conversations if total_conversations > 0 else 0 + }, + "providers": { + "usage": dict(provider_counts), + "most_used": provider_counts.most_common(1)[0][0] if provider_counts else "None" + }, + "models": { + "usage": dict(model_counts), + "most_used": model_counts.most_common(1)[0][0] if model_counts else "None" + }, + "performance": { + "avg_response_time": avg_response_time, + "min_response_time": min_response_time, + "max_response_time": max_response_time, + "total_response_time": sum(recent_response_times) + }, + "tools": { + "usage": tool_usage, + "most_used": max(tool_usage.items(), key=lambda x: x[1])[0] if tool_usage else "None" + }, + "commands": { + "usage": command_usage, + "most_used": max(command_usage.items(), key=lambda x: x[1])[0] if command_usage else "None" + }, + "errors": { + "patterns": error_patterns, + "most_common": max(error_patterns.items(), key=lambda x: x[1])[0] if error_patterns else "None" + }, + "sessions": { + "total": total_sessions, + "total_duration": total_session_duration, + "avg_duration": avg_session_duration, + "avg_messages_per_session": sum(s.get("messages_count", 0) for s in recent_sessions) / total_sessions if total_sessions > 0 else 0 + }, + "generated_at": datetime.now().isoformat() + } + + def get_detailed_report(self, days: int = 30) -> str: + """ + Get detailed analytics report + + Args: + days: Number of days to include + + Returns: + Formatted report string + """ + summary = self.get_usage_summary(days) + + report = f"πŸ“Š Advanced Analytics Report (Last {days} days)\n" + report += "=" * 60 + "\n\n" + + # Conversation statistics + report += "πŸ’¬ Conversation Statistics:\n" + report += f" Total Conversations: {summary['conversations']['total']}\n" + report += f" Total Message Characters: {summary['conversations']['total_messages_chars']:,}\n" + report += f" Total Response Characters: {summary['conversations']['total_responses_chars']:,}\n" + report += f" Average Message Length: {summary['conversations']['avg_message_length']:.0f} chars\n" + report += f" Average Response Length: {summary['conversations']['avg_response_length']:.0f} chars\n\n" + + # Provider usage + report += "πŸ€– Provider Usage:\n" + for provider, count in summary['providers']['usage'].items(): + percentage = (count / summary['conversations']['total'] * 100) if summary['conversations']['total'] > 0 else 0 + report += f" {provider}: {count} ({percentage:.1f}%)\n" + report += f" Most Used: {summary['providers']['most_used']}\n\n" + + # Model usage + report += "🧠 Model Usage:\n" + for model, count in summary['models']['usage'].items(): + percentage = (count / summary['conversations']['total'] * 100) if summary['conversations']['total'] > 0 else 0 + report += f" {model}: {count} ({percentage:.1f}%)\n" + report += f" Most Used: {summary['models']['most_used']}\n\n" + + # Performance metrics + report += "⚑ Performance Metrics:\n" + report += f" Average Response Time: {summary['performance']['avg_response_time']:.3f}s\n" + report += f" Minimum Response Time: {summary['performance']['min_response_time']:.3f}s\n" + report += f" Maximum Response Time: {summary['performance']['max_response_time']:.3f}s\n" + report += f" Total Response Time: {summary['performance']['total_response_time']:.1f}s\n\n" + + # Tool usage + report += "πŸ”§ Tool Usage:\n" + if summary['tools']['usage']: + for tool, count in sorted(summary['tools']['usage'].items(), key=lambda x: x[1], reverse=True)[:10]: + report += f" {tool}: {count}\n" + report += f" Most Used: {summary['tools']['most_used']}\n" + else: + report += " No tool usage recorded\n" + report += "\n" + + # Command usage + report += "⌨️ Command Usage:\n" + if summary['commands']['usage']: + for command, count in sorted(summary['commands']['usage'].items(), key=lambda x: x[1], reverse=True)[:10]: + report += f" {command}: {count}\n" + report += f" Most Used: {summary['commands']['most_used']}\n" + else: + report += " No command usage recorded\n" + report += "\n" + + # Error patterns + report += "❌ Error Patterns:\n" + if summary['errors']['patterns']: + for pattern, count in sorted(summary['errors']['patterns'].items(), key=lambda x: x[1], reverse=True)[:5]: + report += f" {pattern}: {count}\n" + report += f" Most Common: {summary['errors']['most_common']}\n" + else: + report += " No errors recorded\n" + report += "\n" + + # Session statistics + report += "πŸ‘₯ Session Statistics:\n" + report += f" Total Sessions: {summary['sessions']['total']}\n" + report += f" Total Session Duration: {summary['sessions']['total_duration']:.1f}s\n" + report += f" Average Session Duration: {summary['sessions']['avg_duration']:.1f}s\n" + report += f" Average Messages per Session: {summary['sessions']['avg_messages_per_session']:.1f}\n" + + return report + + def get_insights(self, days: int = 30) -> List[str]: + """ + Get insights and recommendations based on usage patterns + + Args: + days: Number of days to analyze + + Returns: + List of insights and recommendations + """ + summary = self.get_usage_summary(days) + insights = [] + + # Provider insights + if summary['providers']['usage']: + most_used_provider = summary['providers']['most_used'] + provider_count = summary['providers']['usage'].get(most_used_provider, 0) + total_conversations = summary['conversations']['total'] + + if total_conversations > 0: + provider_percentage = (provider_count / total_conversations) * 100 + if provider_percentage > 80: + insights.append(f"πŸ’‘ You're heavily using {most_used_provider} ({provider_percentage:.0f}% of conversations). Consider diversifying with other providers for different use cases.") + elif provider_percentage < 20: + insights.append(f"πŸ’‘ You're barely using {most_used_provider} ({provider_percentage:.0f}% of conversations). Consider if this provider is meeting your needs.") + + # Performance insights + avg_response_time = summary['performance']['avg_response_time'] + if avg_response_time > 5.0: + insights.append(f"⚠️ Average response time is high ({avg_response_time:.1f}s). Consider using faster models or optimizing your queries.") + elif avg_response_time < 1.0: + insights.append(f"βœ… Great response times! Average is {avg_response_time:.1f}s.") + + # Tool usage insights + if summary['tools']['usage']: + most_used_tool = summary['tools']['most_used'] + tool_count = summary['tools']['usage'].get(most_used_tool, 0) + insights.append(f"πŸ”§ You frequently use the '{most_used_tool}' tool ({tool_count} times). Consider creating shortcuts or aliases for it.") + + # Error insights + if summary['errors']['patterns']: + most_common_error = summary['errors']['most_common'] + error_count = summary['errors']['patterns'].get(most_common_error, 0) + if error_count > 5: + insights.append(f"❌ You're encountering a recurring error: '{most_common_error}' ({error_count} times). Consider investigating the root cause.") + + # Session insights + avg_session_duration = summary['sessions']['avg_duration'] + if avg_session_duration > 3600: # More than 1 hour + insights.append(f"⏱️ Your average session is long ({avg_session_duration/60:.0f} minutes). Consider breaking work into shorter sessions for better focus.") + + # Usage pattern insights + total_conversations = summary['conversations']['total'] + if total_conversations > 100: + insights.append(f"πŸ“ˆ You're a power user with {total_conversations} conversations in {days} days!") + elif total_conversations < 10: + insights.append(f"πŸ’‘ You've only had {total_conversations} conversations in {days} days. Consider exploring more features.") + + # Default insight if none generated + if not insights: + insights.append("πŸ“Š Your usage patterns look normal. Keep using MaxClaw Agent!") + + return insights + + def clear_old_data(self, days: int = 90): + """ + Clear data older than specified days + + Args: + days: Number of days to keep data + """ + cutoff_time = datetime.now() - timedelta(days=days) + + # Filter out old conversations + self.usage_data["conversations"] = [ + c for c in self.usage_data["conversations"] + if datetime.fromisoformat(c["timestamp"]) > cutoff_time + ] + + # Filter out old sessions + self.usage_data["user_sessions"] = [ + s for s in self.usage_data["user_sessions"] + if datetime.fromisoformat(s["timestamp"]) > cutoff_time + ] + + # Save cleaned data + self._save_data() + + print(f"βœ… Cleared analytics data older than {days} days") + + +# Global analytics engine instance +analytics_engine = AnalyticsEngine() + + +def get_usage_summary(days: int = 30) -> Dict[str, Any]: + """ + Get usage summary + + Args: + days: Number of days to include + + Returns: + Usage summary dictionary + """ + return analytics_engine.get_usage_summary(days) + + +def get_analytics_report(days: int = 30) -> str: + """ + Get formatted analytics report + + Args: + days: Number of days to include + + Returns: + Formatted report string + """ + return analytics_engine.get_detailed_report(days) + + +def get_analytics_insights(days: int = 30) -> List[str]: + """ + Get analytics insights and recommendations + + Args: + days: Number of days to analyze + + Returns: + List of insights and recommendations + """ + return analytics_engine.get_insights(days) + + +if __name__ == "__main__": + # Print analytics report when run directly + print(get_analytics_report()) + print("\n" + "=" * 60) + print("πŸ’‘ Insights and Recommendations:") + for insight in get_analytics_insights(): + print(f" {insight}") \ No newline at end of file