praisonaiagents 0.0.145__py3-none-any.whl → 0.0.146__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -838,14 +838,55 @@ class OpenAIClient:
838
838
  )
839
839
  else:
840
840
  # Process as regular non-streaming response
841
- final_response = self.create_completion(
842
- messages=messages,
843
- model=model,
844
- temperature=temperature,
845
- tools=formatted_tools,
846
- stream=False,
847
- **kwargs
848
- )
841
+ if display_fn and console:
842
+ # When verbose (display_fn provided), use streaming for better UX
843
+ try:
844
+ with Live(display_fn("", start_time), console=console, refresh_per_second=4, transient=True) as live:
845
+ # Use streaming when display_fn is provided for progressive display
846
+ response_stream = self.create_completion(
847
+ messages=messages,
848
+ model=model,
849
+ temperature=temperature,
850
+ tools=formatted_tools,
851
+ stream=True, # Always stream when verbose/display_fn
852
+ **kwargs
853
+ )
854
+
855
+ full_response_text = ""
856
+ chunks = []
857
+
858
+ # Process streaming response
859
+ for chunk in response_stream:
860
+ chunks.append(chunk)
861
+ if chunk.choices[0].delta.content:
862
+ full_response_text += chunk.choices[0].delta.content
863
+ live.update(display_fn(full_response_text, start_time))
864
+
865
+ # Process final response from chunks
866
+ final_response = process_stream_chunks(chunks)
867
+
868
+ # Clear the last generating display with a blank line
869
+ console.print()
870
+ except Exception as e:
871
+ self.logger.error(f"Error in Live display for non-streaming: {e}")
872
+ # Fallback to regular completion without display
873
+ final_response = self.create_completion(
874
+ messages=messages,
875
+ model=model,
876
+ temperature=temperature,
877
+ tools=formatted_tools,
878
+ stream=False,
879
+ **kwargs
880
+ )
881
+ else:
882
+ final_response = self.create_completion(
883
+ messages=messages,
884
+ model=model,
885
+ temperature=temperature,
886
+ tools=formatted_tools,
887
+ stream=False,
888
+ **kwargs
889
+ )
849
890
 
850
891
  if not final_response:
851
892
  return None
@@ -969,14 +1010,55 @@ class OpenAIClient:
969
1010
  )
970
1011
  else:
971
1012
  # Process as regular non-streaming response
972
- final_response = await self.acreate_completion(
973
- messages=messages,
974
- model=model,
975
- temperature=temperature,
976
- tools=formatted_tools,
977
- stream=False,
978
- **kwargs
979
- )
1013
+ if display_fn and console:
1014
+ # When verbose (display_fn provided), use streaming for better UX
1015
+ try:
1016
+ with Live(display_fn("", start_time), console=console, refresh_per_second=4, transient=True) as live:
1017
+ # Use streaming when display_fn is provided for progressive display
1018
+ response_stream = await self.acreate_completion(
1019
+ messages=messages,
1020
+ model=model,
1021
+ temperature=temperature,
1022
+ tools=formatted_tools,
1023
+ stream=True, # Always stream when verbose/display_fn
1024
+ **kwargs
1025
+ )
1026
+
1027
+ full_response_text = ""
1028
+ chunks = []
1029
+
1030
+ # Process streaming response
1031
+ async for chunk in response_stream:
1032
+ chunks.append(chunk)
1033
+ if chunk.choices[0].delta.content:
1034
+ full_response_text += chunk.choices[0].delta.content
1035
+ live.update(display_fn(full_response_text, start_time))
1036
+
1037
+ # Process final response from chunks
1038
+ final_response = process_stream_chunks(chunks)
1039
+
1040
+ # Clear the last generating display with a blank line
1041
+ console.print()
1042
+ except Exception as e:
1043
+ self.logger.error(f"Error in Live display for async non-streaming: {e}")
1044
+ # Fallback to regular completion without display
1045
+ final_response = await self.acreate_completion(
1046
+ messages=messages,
1047
+ model=model,
1048
+ temperature=temperature,
1049
+ tools=formatted_tools,
1050
+ stream=False,
1051
+ **kwargs
1052
+ )
1053
+ else:
1054
+ final_response = await self.acreate_completion(
1055
+ messages=messages,
1056
+ model=model,
1057
+ temperature=temperature,
1058
+ tools=formatted_tools,
1059
+ stream=False,
1060
+ **kwargs
1061
+ )
980
1062
 
981
1063
  if not final_response:
982
1064
  return None
@@ -138,6 +138,8 @@ class Memory:
138
138
  logging.getLogger('httpx').setLevel(logging.WARNING)
139
139
  logging.getLogger('httpcore').setLevel(logging.WARNING)
140
140
  logging.getLogger('chromadb.segment.impl.vector.local_persistent_hnsw').setLevel(logging.ERROR)
141
+ logging.getLogger('utils').setLevel(logging.WARNING)
142
+ logging.getLogger('litellm.utils').setLevel(logging.WARNING)
141
143
 
142
144
  self.provider = self.cfg.get("provider", "rag")
143
145
  self.use_mem0 = (self.provider.lower() == "mem0") and MEM0_AVAILABLE
@@ -1546,3 +1548,46 @@ class Memory:
1546
1548
  logger.info(f"After quality filter: {len(filtered)} results")
1547
1549
 
1548
1550
  return filtered
1551
+
1552
+ def get_all_memories(self) -> List[Dict[str, Any]]:
1553
+ """Get all memories from both short-term and long-term storage"""
1554
+ all_memories = []
1555
+
1556
+ try:
1557
+ # Get short-term memories
1558
+ conn = sqlite3.connect(self.short_db)
1559
+ c = conn.cursor()
1560
+ rows = c.execute("SELECT id, content, meta, created_at FROM short_mem").fetchall()
1561
+ conn.close()
1562
+
1563
+ for row in rows:
1564
+ meta = json.loads(row[2] or "{}")
1565
+ all_memories.append({
1566
+ "id": row[0],
1567
+ "text": row[1],
1568
+ "metadata": meta,
1569
+ "created_at": row[3],
1570
+ "type": "short_term"
1571
+ })
1572
+
1573
+ # Get long-term memories
1574
+ conn = sqlite3.connect(self.long_db)
1575
+ c = conn.cursor()
1576
+ rows = c.execute("SELECT id, content, meta, created_at FROM long_mem").fetchall()
1577
+ conn.close()
1578
+
1579
+ for row in rows:
1580
+ meta = json.loads(row[2] or "{}")
1581
+ all_memories.append({
1582
+ "id": row[0],
1583
+ "text": row[1],
1584
+ "metadata": meta,
1585
+ "created_at": row[3],
1586
+ "type": "long_term"
1587
+ })
1588
+
1589
+ return all_memories
1590
+
1591
+ except Exception as e:
1592
+ self._log_verbose(f"Error getting all memories: {e}", logging.ERROR)
1593
+ return []
@@ -1,13 +1,24 @@
1
1
  """
2
- PraisonAI Agents Minimal Telemetry Module
2
+ PraisonAI Agents Telemetry & Performance Monitoring Module
3
+
4
+ This module provides:
5
+ 1. Anonymous usage tracking with privacy-first design
6
+ 2. User-friendly performance monitoring and analysis tools
3
7
 
4
- This module provides anonymous usage tracking with privacy-first design.
5
8
  Telemetry is opt-out and can be disabled via environment variables:
6
9
  - PRAISONAI_TELEMETRY_DISABLED=true
7
10
  - PRAISONAI_DISABLE_TELEMETRY=true
8
11
  - DO_NOT_TRACK=true
9
12
 
10
13
  No personal data, prompts, or responses are collected.
14
+
15
+ Performance Monitoring Features:
16
+ - Function performance tracking with detailed statistics
17
+ - API call monitoring and analysis
18
+ - Function execution flow visualization
19
+ - Performance bottleneck identification
20
+ - Real-time performance reporting
21
+ - CLI interface for easy access
11
22
  """
12
23
 
13
24
  import os
@@ -20,15 +31,64 @@ if TYPE_CHECKING:
20
31
  # Import the classes for real (not just type checking)
21
32
  from .telemetry import MinimalTelemetry, TelemetryCollector
22
33
 
34
+ # Import performance monitoring tools
35
+ try:
36
+ from .performance_monitor import (
37
+ PerformanceMonitor, performance_monitor,
38
+ monitor_function, track_api_call, get_performance_report,
39
+ get_function_stats, get_api_stats, get_slowest_functions,
40
+ get_slowest_apis, clear_performance_data
41
+ )
42
+ from .performance_utils import (
43
+ FunctionFlowAnalyzer, PerformanceAnalyzer,
44
+ flow_analyzer, performance_analyzer,
45
+ analyze_function_flow, visualize_execution_flow,
46
+ analyze_performance_trends, generate_comprehensive_report
47
+ )
48
+ from .performance_cli import PerformanceCLI
49
+ PERFORMANCE_MONITORING_AVAILABLE = True
50
+ except ImportError:
51
+ PERFORMANCE_MONITORING_AVAILABLE = False
52
+
23
53
  __all__ = [
54
+ # Core telemetry
24
55
  'get_telemetry',
25
56
  'enable_telemetry',
26
- 'disable_telemetry',
57
+ 'disable_telemetry',
27
58
  'force_shutdown_telemetry',
28
59
  'MinimalTelemetry',
29
60
  'TelemetryCollector', # For backward compatibility
30
61
  ]
31
62
 
63
+ # Add performance monitoring to __all__ if available
64
+ if PERFORMANCE_MONITORING_AVAILABLE:
65
+ __all__.extend([
66
+ # Performance monitoring classes
67
+ 'PerformanceMonitor',
68
+ 'FunctionFlowAnalyzer',
69
+ 'PerformanceAnalyzer',
70
+ 'PerformanceCLI',
71
+ # Global instances
72
+ 'performance_monitor',
73
+ 'flow_analyzer',
74
+ 'performance_analyzer',
75
+ # Convenience functions
76
+ 'monitor_function',
77
+ 'track_api_call',
78
+ 'get_performance_report',
79
+ 'get_function_stats',
80
+ 'get_api_stats',
81
+ 'get_slowest_functions',
82
+ 'get_slowest_apis',
83
+ 'clear_performance_data',
84
+ 'analyze_function_flow',
85
+ 'visualize_execution_flow',
86
+ 'analyze_performance_trends',
87
+ 'generate_comprehensive_report',
88
+ # Availability flag
89
+ 'PERFORMANCE_MONITORING_AVAILABLE'
90
+ ])
91
+
32
92
 
33
93
  def get_telemetry() -> 'MinimalTelemetry':
34
94
  """Get the global telemetry instance."""
@@ -42,13 +42,27 @@ def instrument_agent(agent: 'Agent', telemetry: Optional['MinimalTelemetry'] = N
42
42
  if original_chat:
43
43
  @wraps(original_chat)
44
44
  def instrumented_chat(*args, **kwargs):
45
+ import threading
46
+
45
47
  try:
46
48
  result = original_chat(*args, **kwargs)
47
- telemetry.track_agent_execution(agent.name, success=True)
49
+ # Track success asynchronously to prevent blocking
50
+ def track_async():
51
+ try:
52
+ telemetry.track_agent_execution(agent.name, success=True)
53
+ except:
54
+ pass # Ignore telemetry errors
55
+ threading.Thread(target=track_async, daemon=True).start()
48
56
  return result
49
57
  except Exception as e:
50
- telemetry.track_agent_execution(agent.name, success=False)
51
- telemetry.track_error(type(e).__name__)
58
+ # Track error asynchronously
59
+ def track_error_async():
60
+ try:
61
+ telemetry.track_agent_execution(agent.name, success=False)
62
+ telemetry.track_error(type(e).__name__)
63
+ except:
64
+ pass # Ignore telemetry errors
65
+ threading.Thread(target=track_error_async, daemon=True).start()
52
66
  raise
53
67
 
54
68
  agent.chat = instrumented_chat
@@ -57,13 +71,53 @@ def instrument_agent(agent: 'Agent', telemetry: Optional['MinimalTelemetry'] = N
57
71
  if original_start:
58
72
  @wraps(original_start)
59
73
  def instrumented_start(*args, **kwargs):
74
+ import types
75
+ import threading
76
+
60
77
  try:
61
78
  result = original_start(*args, **kwargs)
62
- telemetry.track_agent_execution(agent.name, success=True)
63
- return result
79
+
80
+ # Check if result is a generator (streaming mode)
81
+ if isinstance(result, types.GeneratorType):
82
+ # For streaming, defer telemetry tracking to avoid blocking
83
+ def streaming_wrapper():
84
+ try:
85
+ for chunk in result:
86
+ yield chunk
87
+ # Track success only after streaming completes
88
+ # Use a separate thread to make it truly non-blocking
89
+ def track_async():
90
+ try:
91
+ telemetry.track_agent_execution(agent.name, success=True)
92
+ except:
93
+ pass # Ignore telemetry errors
94
+ threading.Thread(target=track_async, daemon=True).start()
95
+ except Exception as e:
96
+ # Track error immediately
97
+ threading.Thread(target=lambda: telemetry.track_agent_execution(agent.name, success=False), daemon=True).start()
98
+ threading.Thread(target=lambda: telemetry.track_error(type(e).__name__), daemon=True).start()
99
+ raise
100
+
101
+ return streaming_wrapper()
102
+ else:
103
+ # For non-streaming, track immediately but asynchronously
104
+ def track_async():
105
+ try:
106
+ telemetry.track_agent_execution(agent.name, success=True)
107
+ except:
108
+ pass # Ignore telemetry errors
109
+ threading.Thread(target=track_async, daemon=True).start()
110
+ return result
111
+
64
112
  except Exception as e:
65
- telemetry.track_agent_execution(agent.name, success=False)
66
- telemetry.track_error(type(e).__name__)
113
+ # Track error immediately but asynchronously
114
+ def track_error_async():
115
+ try:
116
+ telemetry.track_agent_execution(agent.name, success=False)
117
+ telemetry.track_error(type(e).__name__)
118
+ except:
119
+ pass # Ignore telemetry errors
120
+ threading.Thread(target=track_error_async, daemon=True).start()
67
121
  raise
68
122
 
69
123
  agent.start = instrumented_start
@@ -72,13 +126,27 @@ def instrument_agent(agent: 'Agent', telemetry: Optional['MinimalTelemetry'] = N
72
126
  if original_run:
73
127
  @wraps(original_run)
74
128
  def instrumented_run(*args, **kwargs):
129
+ import threading
130
+
75
131
  try:
76
132
  result = original_run(*args, **kwargs)
77
- telemetry.track_agent_execution(agent.name, success=True)
133
+ # Track success asynchronously to prevent blocking
134
+ def track_async():
135
+ try:
136
+ telemetry.track_agent_execution(agent.name, success=True)
137
+ except:
138
+ pass # Ignore telemetry errors
139
+ threading.Thread(target=track_async, daemon=True).start()
78
140
  return result
79
141
  except Exception as e:
80
- telemetry.track_agent_execution(agent.name, success=False)
81
- telemetry.track_error(type(e).__name__)
142
+ # Track error asynchronously
143
+ def track_error_async():
144
+ try:
145
+ telemetry.track_agent_execution(agent.name, success=False)
146
+ telemetry.track_error(type(e).__name__)
147
+ except:
148
+ pass # Ignore telemetry errors
149
+ threading.Thread(target=track_error_async, daemon=True).start()
82
150
  raise
83
151
 
84
152
  agent.run = instrumented_run