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.
- praisonaiagents/__init__.py +68 -7
- praisonaiagents/agent/agent.py +358 -48
- praisonaiagents/llm/__init__.py +40 -14
- praisonaiagents/llm/llm.py +485 -59
- praisonaiagents/llm/openai_client.py +98 -16
- praisonaiagents/memory/memory.py +45 -0
- praisonaiagents/telemetry/__init__.py +63 -3
- praisonaiagents/telemetry/integration.py +78 -10
- praisonaiagents/telemetry/performance_cli.py +397 -0
- praisonaiagents/telemetry/performance_monitor.py +573 -0
- praisonaiagents/telemetry/performance_utils.py +571 -0
- praisonaiagents/telemetry/telemetry.py +35 -11
- {praisonaiagents-0.0.145.dist-info → praisonaiagents-0.0.146.dist-info}/METADATA +9 -3
- {praisonaiagents-0.0.145.dist-info → praisonaiagents-0.0.146.dist-info}/RECORD +16 -13
- {praisonaiagents-0.0.145.dist-info → praisonaiagents-0.0.146.dist-info}/WHEEL +0 -0
- {praisonaiagents-0.0.145.dist-info → praisonaiagents-0.0.146.dist-info}/top_level.txt +0 -0
@@ -838,14 +838,55 @@ class OpenAIClient:
|
|
838
838
|
)
|
839
839
|
else:
|
840
840
|
# Process as regular non-streaming response
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
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
|
-
|
973
|
-
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
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
|
praisonaiagents/memory/memory.py
CHANGED
@@ -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
|
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
|
-
|
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
|
-
|
51
|
-
|
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
|
-
|
63
|
-
|
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
|
-
|
66
|
-
|
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
|
-
|
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
|
-
|
81
|
-
|
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
|