praisonaiagents 0.0.152__py3-none-any.whl → 0.0.153__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 +57 -7
- praisonaiagents/agent/agent.py +4 -0
- praisonaiagents/agents/agents.py +116 -1
- praisonaiagents/llm/llm.py +70 -0
- praisonaiagents/main.py +7 -0
- praisonaiagents/mcp/mcp_http_stream.py +42 -2
- praisonaiagents/telemetry/__init__.py +31 -3
- praisonaiagents/telemetry/integration.py +385 -84
- praisonaiagents/telemetry/performance_monitor.py +162 -1
- praisonaiagents/telemetry/performance_utils.py +35 -7
- praisonaiagents/telemetry/telemetry.py +145 -42
- praisonaiagents/telemetry/token_collector.py +170 -0
- praisonaiagents/telemetry/token_telemetry.py +89 -0
- {praisonaiagents-0.0.152.dist-info → praisonaiagents-0.0.153.dist-info}/METADATA +1 -1
- {praisonaiagents-0.0.152.dist-info → praisonaiagents-0.0.153.dist-info}/RECORD +17 -15
- {praisonaiagents-0.0.152.dist-info → praisonaiagents-0.0.153.dist-info}/WHEEL +0 -0
- {praisonaiagents-0.0.152.dist-info → praisonaiagents-0.0.153.dist-info}/top_level.txt +0 -0
praisonaiagents/__init__.py
CHANGED
@@ -60,6 +60,9 @@ try:
|
|
60
60
|
get_telemetry,
|
61
61
|
enable_telemetry,
|
62
62
|
disable_telemetry,
|
63
|
+
enable_performance_mode,
|
64
|
+
disable_performance_mode,
|
65
|
+
cleanup_telemetry_resources,
|
63
66
|
MinimalTelemetry,
|
64
67
|
TelemetryCollector
|
65
68
|
)
|
@@ -80,22 +83,66 @@ except ImportError:
|
|
80
83
|
def disable_telemetry():
|
81
84
|
pass
|
82
85
|
|
86
|
+
def enable_performance_mode():
|
87
|
+
pass
|
88
|
+
|
89
|
+
def disable_performance_mode():
|
90
|
+
pass
|
91
|
+
|
92
|
+
def cleanup_telemetry_resources():
|
93
|
+
pass
|
94
|
+
|
83
95
|
MinimalTelemetry = None
|
84
96
|
TelemetryCollector = None
|
85
97
|
|
86
98
|
# Add Agents as an alias for PraisonAIAgents
|
87
99
|
Agents = PraisonAIAgents
|
88
100
|
|
89
|
-
#
|
101
|
+
# Enable PostHog telemetry by default with actual event posting
|
102
|
+
# PostHog events are posted by default unless explicitly disabled
|
103
|
+
# Users can:
|
104
|
+
# - Disable completely: PRAISONAI_DISABLE_TELEMETRY=true (or DO_NOT_TRACK=true)
|
105
|
+
# - Enable performance mode: PRAISONAI_PERFORMANCE_MODE=true (minimal overhead, limited events)
|
106
|
+
# - Enable full telemetry: PRAISONAI_FULL_TELEMETRY=true (detailed tracking)
|
107
|
+
# - Legacy opt-in mode: PRAISONAI_AUTO_INSTRUMENT=true
|
90
108
|
if _telemetry_available:
|
91
109
|
try:
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
110
|
+
import os
|
111
|
+
|
112
|
+
# Check for explicit disable (respects DO_NOT_TRACK and other disable flags)
|
113
|
+
telemetry_disabled = any([
|
114
|
+
os.environ.get('PRAISONAI_TELEMETRY_DISABLED', '').lower() in ('true', '1', 'yes'),
|
115
|
+
os.environ.get('PRAISONAI_DISABLE_TELEMETRY', '').lower() in ('true', '1', 'yes'),
|
116
|
+
os.environ.get('DO_NOT_TRACK', '').lower() in ('true', '1', 'yes'),
|
117
|
+
])
|
118
|
+
|
119
|
+
# Check for performance mode (minimal overhead with limited events)
|
120
|
+
performance_mode = os.environ.get('PRAISONAI_PERFORMANCE_MODE', '').lower() in ('true', '1', 'yes')
|
121
|
+
|
122
|
+
# Check for full telemetry mode (more detailed tracking)
|
123
|
+
full_telemetry = os.environ.get('PRAISONAI_FULL_TELEMETRY', '').lower() in ('true', '1', 'yes')
|
124
|
+
|
125
|
+
# Legacy explicit auto-instrument option
|
126
|
+
explicit_auto_instrument = os.environ.get('PRAISONAI_AUTO_INSTRUMENT', '').lower() in ('true', '1', 'yes')
|
127
|
+
|
128
|
+
# Enable PostHog by default unless explicitly disabled
|
129
|
+
if not telemetry_disabled:
|
130
|
+
_telemetry = get_telemetry()
|
131
|
+
if _telemetry and _telemetry.enabled:
|
132
|
+
from .telemetry.integration import auto_instrument_all
|
133
|
+
|
134
|
+
# Default: PostHog telemetry is enabled and events are posted
|
135
|
+
# Performance mode can be explicitly enabled for minimal overhead
|
136
|
+
use_performance_mode = performance_mode and not (full_telemetry or explicit_auto_instrument)
|
137
|
+
auto_instrument_all(_telemetry, performance_mode=use_performance_mode)
|
138
|
+
|
139
|
+
# Track package import for basic usage analytics
|
140
|
+
try:
|
141
|
+
_telemetry.track_feature_usage("package_import")
|
142
|
+
except Exception:
|
143
|
+
pass
|
97
144
|
except Exception:
|
98
|
-
# Silently fail if there are any issues
|
145
|
+
# Silently fail if there are any issues - never break user applications
|
99
146
|
pass
|
100
147
|
|
101
148
|
__all__ = [
|
@@ -135,6 +182,9 @@ __all__ = [
|
|
135
182
|
'get_telemetry',
|
136
183
|
'enable_telemetry',
|
137
184
|
'disable_telemetry',
|
185
|
+
'enable_performance_mode',
|
186
|
+
'disable_performance_mode',
|
187
|
+
'cleanup_telemetry_resources',
|
138
188
|
'MinimalTelemetry',
|
139
189
|
'TelemetryCollector'
|
140
190
|
]
|
praisonaiagents/agent/agent.py
CHANGED
@@ -207,6 +207,7 @@ class Agent:
|
|
207
207
|
use_system_prompt: Optional[bool] = True,
|
208
208
|
markdown: bool = True,
|
209
209
|
stream: bool = False,
|
210
|
+
metrics: bool = False,
|
210
211
|
self_reflect: bool = False,
|
211
212
|
max_reflect: int = 3,
|
212
213
|
min_reflect: int = 1,
|
@@ -283,6 +284,8 @@ class Agent:
|
|
283
284
|
readability and structure. Defaults to True.
|
284
285
|
stream (bool, optional): Enable streaming responses from the language model for real-time
|
285
286
|
output when using Agent.start() method. Defaults to False for backward compatibility.
|
287
|
+
metrics (bool, optional): Enable automatic token usage tracking and display summary
|
288
|
+
when tasks complete. Simplifies token monitoring for cost optimization. Defaults to False.
|
286
289
|
self_reflect (bool, optional): Enable self-reflection capabilities where the agent
|
287
290
|
evaluates and improves its own responses. Defaults to False.
|
288
291
|
max_reflect (int, optional): Maximum number of self-reflection iterations to prevent
|
@@ -465,6 +468,7 @@ class Agent:
|
|
465
468
|
self.chat_history = []
|
466
469
|
self.markdown = markdown
|
467
470
|
self.stream = stream
|
471
|
+
self.metrics = metrics
|
468
472
|
self.max_reflect = max_reflect
|
469
473
|
self.min_reflect = min_reflect
|
470
474
|
self.reflect_prompt = reflect_prompt
|
praisonaiagents/agents/agents.py
CHANGED
@@ -15,6 +15,12 @@ import asyncio
|
|
15
15
|
import uuid
|
16
16
|
from enum import Enum
|
17
17
|
|
18
|
+
# Import token tracking
|
19
|
+
try:
|
20
|
+
from ..telemetry.token_collector import _token_collector
|
21
|
+
except ImportError:
|
22
|
+
_token_collector = None
|
23
|
+
|
18
24
|
# Task status constants
|
19
25
|
class TaskStatus(Enum):
|
20
26
|
"""Enumeration for task status values to ensure consistency"""
|
@@ -305,6 +311,11 @@ class PraisonAIAgents:
|
|
305
311
|
task.status = "in progress"
|
306
312
|
|
307
313
|
executor_agent = task.agent
|
314
|
+
|
315
|
+
# Set current agent for token tracking
|
316
|
+
llm = getattr(executor_agent, 'llm', None) or getattr(executor_agent, 'llm_instance', None)
|
317
|
+
if llm and hasattr(llm, 'set_current_agent'):
|
318
|
+
llm.set_current_agent(executor_agent.name)
|
308
319
|
|
309
320
|
# Ensure tools are available from both task and agent
|
310
321
|
tools = task.tools or []
|
@@ -401,6 +412,12 @@ Context:
|
|
401
412
|
agent=executor_agent.name,
|
402
413
|
output_format="RAW"
|
403
414
|
)
|
415
|
+
|
416
|
+
# Add token metrics if available
|
417
|
+
if llm and hasattr(llm, 'last_token_metrics'):
|
418
|
+
token_metrics = llm.last_token_metrics
|
419
|
+
if token_metrics:
|
420
|
+
task_output.token_metrics = token_metrics
|
404
421
|
|
405
422
|
if task.output_json:
|
406
423
|
cleaned = self.clean_json_output(agent_output)
|
@@ -633,6 +650,11 @@ Context:
|
|
633
650
|
task.status = "in progress"
|
634
651
|
|
635
652
|
executor_agent = task.agent
|
653
|
+
|
654
|
+
# Set current agent for token tracking
|
655
|
+
llm = getattr(executor_agent, 'llm', None) or getattr(executor_agent, 'llm_instance', None)
|
656
|
+
if llm and hasattr(llm, 'set_current_agent'):
|
657
|
+
llm.set_current_agent(executor_agent.name)
|
636
658
|
|
637
659
|
task_prompt = f"""
|
638
660
|
You need to do the following task: {task.description}.
|
@@ -749,6 +771,12 @@ Context:
|
|
749
771
|
agent=executor_agent.name,
|
750
772
|
output_format="RAW"
|
751
773
|
)
|
774
|
+
|
775
|
+
# Add token metrics if available
|
776
|
+
if llm and hasattr(llm, 'last_token_metrics'):
|
777
|
+
token_metrics = llm.last_token_metrics
|
778
|
+
if token_metrics:
|
779
|
+
task_output.token_metrics = token_metrics
|
752
780
|
|
753
781
|
if task.output_json:
|
754
782
|
cleaned = self.clean_json_output(agent_output)
|
@@ -905,6 +933,18 @@ Context:
|
|
905
933
|
# Run tasks as before
|
906
934
|
self.run_all_tasks()
|
907
935
|
|
936
|
+
# Auto-display token metrics if any agent has metrics=True
|
937
|
+
metrics_enabled = any(getattr(agent, 'metrics', False) for agent in self.agents)
|
938
|
+
if metrics_enabled:
|
939
|
+
try:
|
940
|
+
self.display_token_usage()
|
941
|
+
except (ImportError, AttributeError) as e:
|
942
|
+
# Token tracking not available or not properly configured
|
943
|
+
logging.debug(f"Could not auto-display token usage: {e}")
|
944
|
+
except Exception as e:
|
945
|
+
# Log unexpected errors for debugging
|
946
|
+
logging.debug(f"Unexpected error in token metrics display: {e}")
|
947
|
+
|
908
948
|
# Get results
|
909
949
|
results = {
|
910
950
|
"task_status": self.get_all_tasks_status(),
|
@@ -924,6 +964,10 @@ Context:
|
|
924
964
|
# Return full results dict if return_dict is True or if no final result was found
|
925
965
|
return results
|
926
966
|
|
967
|
+
def run(self, content=None, return_dict=False, **kwargs):
|
968
|
+
"""Alias for start() method to provide consistent API with Agent class"""
|
969
|
+
return self.start(content=content, return_dict=return_dict, **kwargs)
|
970
|
+
|
927
971
|
def set_state(self, key: str, value: Any) -> None:
|
928
972
|
"""Set a state value"""
|
929
973
|
self._state[key] = value
|
@@ -1038,6 +1082,77 @@ Context:
|
|
1038
1082
|
return True
|
1039
1083
|
|
1040
1084
|
return False
|
1085
|
+
|
1086
|
+
def get_token_usage_summary(self) -> Dict[str, Any]:
|
1087
|
+
"""Get a summary of token usage across all agents and tasks."""
|
1088
|
+
if not _token_collector:
|
1089
|
+
return {"error": "Token tracking not available"}
|
1090
|
+
|
1091
|
+
return _token_collector.get_session_summary()
|
1092
|
+
|
1093
|
+
def get_detailed_token_report(self) -> Dict[str, Any]:
|
1094
|
+
"""Get a detailed token usage report."""
|
1095
|
+
if not _token_collector:
|
1096
|
+
return {"error": "Token tracking not available"}
|
1097
|
+
|
1098
|
+
summary = _token_collector.get_session_summary()
|
1099
|
+
recent = _token_collector.get_recent_interactions(limit=20)
|
1100
|
+
|
1101
|
+
# Calculate cost estimates (example rates)
|
1102
|
+
cost_per_1k_input = 0.0005 # $0.0005 per 1K input tokens
|
1103
|
+
cost_per_1k_output = 0.0015 # $0.0015 per 1K output tokens
|
1104
|
+
|
1105
|
+
total_metrics = summary.get("total_metrics", {})
|
1106
|
+
input_cost = (total_metrics.get("input_tokens", 0) / 1000) * cost_per_1k_input
|
1107
|
+
output_cost = (total_metrics.get("output_tokens", 0) / 1000) * cost_per_1k_output
|
1108
|
+
total_cost = input_cost + output_cost
|
1109
|
+
|
1110
|
+
return {
|
1111
|
+
"summary": summary,
|
1112
|
+
"recent_interactions": recent,
|
1113
|
+
"cost_estimate": {
|
1114
|
+
"input_cost": f"${input_cost:.4f}",
|
1115
|
+
"output_cost": f"${output_cost:.4f}",
|
1116
|
+
"total_cost": f"${total_cost:.4f}",
|
1117
|
+
"note": "Cost estimates based on example rates"
|
1118
|
+
}
|
1119
|
+
}
|
1120
|
+
|
1121
|
+
def display_token_usage(self):
|
1122
|
+
"""Display token usage in a formatted table."""
|
1123
|
+
if not _token_collector:
|
1124
|
+
print("Token tracking not available")
|
1125
|
+
return
|
1126
|
+
|
1127
|
+
summary = _token_collector.get_session_summary()
|
1128
|
+
|
1129
|
+
print("\n" + "="*50)
|
1130
|
+
print("TOKEN USAGE SUMMARY")
|
1131
|
+
print("="*50)
|
1132
|
+
|
1133
|
+
total_metrics = summary.get("total_metrics", {})
|
1134
|
+
print(f"\nTotal Interactions: {summary.get('total_interactions', 0)}")
|
1135
|
+
print(f"Total Tokens: {total_metrics.get('total_tokens', 0):,}")
|
1136
|
+
print(f" - Input Tokens: {total_metrics.get('input_tokens', 0):,}")
|
1137
|
+
print(f" - Output Tokens: {total_metrics.get('output_tokens', 0):,}")
|
1138
|
+
print(f" - Cached Tokens: {total_metrics.get('cached_tokens', 0):,}")
|
1139
|
+
print(f" - Reasoning Tokens: {total_metrics.get('reasoning_tokens', 0):,}")
|
1140
|
+
|
1141
|
+
# By model
|
1142
|
+
by_model = summary.get("by_model", {})
|
1143
|
+
if by_model:
|
1144
|
+
print("\nUsage by Model:")
|
1145
|
+
for model, metrics in by_model.items():
|
1146
|
+
print(f" {model}: {metrics.get('total_tokens', 0):,} tokens")
|
1147
|
+
|
1148
|
+
# By agent
|
1149
|
+
by_agent = summary.get("by_agent", {})
|
1150
|
+
if by_agent:
|
1151
|
+
print("\nUsage by Agent:")
|
1152
|
+
for agent, metrics in by_agent.items():
|
1153
|
+
print(f" {agent}: {metrics.get('total_tokens', 0):,} tokens")
|
1154
|
+
|
1155
|
+
print("="*50 + "\n")
|
1041
1156
|
|
1042
1157
|
def launch(self, path: str = '/agents', port: int = 8000, host: str = '0.0.0.0', debug: bool = False, protocol: str = "http"):
|
1043
1158
|
"""
|
@@ -1416,4 +1531,4 @@ Context:
|
|
1416
1531
|
return None
|
1417
1532
|
else:
|
1418
1533
|
display_error(f"Invalid protocol: {protocol}. Choose 'http' or 'mcp'.")
|
1419
|
-
return None
|
1534
|
+
return None
|
praisonaiagents/llm/llm.py
CHANGED
@@ -20,6 +20,13 @@ from ..main import (
|
|
20
20
|
from rich.console import Console
|
21
21
|
from rich.live import Live
|
22
22
|
|
23
|
+
# Import token tracking
|
24
|
+
try:
|
25
|
+
from ..telemetry.token_collector import TokenMetrics, _token_collector
|
26
|
+
except ImportError:
|
27
|
+
TokenMetrics = None
|
28
|
+
_token_collector = None
|
29
|
+
|
23
30
|
# Logging is already configured in _logging.py via __init__.py
|
24
31
|
|
25
32
|
# TODO: Include in-build tool calling in LLM class
|
@@ -254,6 +261,11 @@ class LLM:
|
|
254
261
|
self.min_reflect = extra_settings.get('min_reflect', 1)
|
255
262
|
self.reasoning_steps = extra_settings.get('reasoning_steps', False)
|
256
263
|
|
264
|
+
# Token tracking
|
265
|
+
self.last_token_metrics: Optional[TokenMetrics] = None
|
266
|
+
self.session_token_metrics: Optional[TokenMetrics] = None
|
267
|
+
self.current_agent_name: Optional[str] = None
|
268
|
+
|
257
269
|
# Enable error dropping for cleaner output
|
258
270
|
litellm.drop_params = True
|
259
271
|
# Enable parameter modification for providers like Anthropic
|
@@ -941,6 +953,9 @@ class LLM:
|
|
941
953
|
response_text = resp["choices"][0]["message"]["content"]
|
942
954
|
final_response = resp
|
943
955
|
|
956
|
+
# Track token usage
|
957
|
+
self._track_token_usage(final_response, model)
|
958
|
+
|
944
959
|
# Execute callbacks and display based on verbose setting
|
945
960
|
generation_time_val = time.time() - current_time
|
946
961
|
response_content = f"Reasoning:\n{reasoning_content}\n\nAnswer:\n{response_text}" if reasoning_content else response_text
|
@@ -1118,6 +1133,9 @@ class LLM:
|
|
1118
1133
|
# Handle None content from Gemini
|
1119
1134
|
response_content = final_response["choices"][0]["message"].get("content")
|
1120
1135
|
response_text = response_content if response_content is not None else ""
|
1136
|
+
|
1137
|
+
# Track token usage
|
1138
|
+
self._track_token_usage(final_response, self.model)
|
1121
1139
|
|
1122
1140
|
# Execute callbacks and display based on verbose setting
|
1123
1141
|
if verbose and not interaction_displayed:
|
@@ -1264,6 +1282,9 @@ class LLM:
|
|
1264
1282
|
# Handle None content from Gemini
|
1265
1283
|
response_content = final_response["choices"][0]["message"].get("content")
|
1266
1284
|
response_text = response_content if response_content is not None else ""
|
1285
|
+
|
1286
|
+
# Track token usage
|
1287
|
+
self._track_token_usage(final_response, self.model)
|
1267
1288
|
|
1268
1289
|
# Execute callbacks and display based on verbose setting
|
1269
1290
|
if verbose and not interaction_displayed:
|
@@ -2861,6 +2882,55 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
|
2861
2882
|
|
2862
2883
|
litellm.callbacks = events
|
2863
2884
|
|
2885
|
+
def _track_token_usage(self, response: Dict[str, Any], model: str) -> Optional[TokenMetrics]:
|
2886
|
+
"""Extract and track token usage from LLM response."""
|
2887
|
+
if not TokenMetrics or not _token_collector:
|
2888
|
+
return None
|
2889
|
+
|
2890
|
+
try:
|
2891
|
+
usage = response.get("usage", {})
|
2892
|
+
if not usage:
|
2893
|
+
return None
|
2894
|
+
|
2895
|
+
# Extract token counts
|
2896
|
+
metrics = TokenMetrics(
|
2897
|
+
input_tokens=usage.get("prompt_tokens", 0),
|
2898
|
+
output_tokens=usage.get("completion_tokens", 0),
|
2899
|
+
cached_tokens=usage.get("cached_tokens", 0),
|
2900
|
+
reasoning_tokens=usage.get("reasoning_tokens", 0),
|
2901
|
+
audio_input_tokens=usage.get("audio_input_tokens", 0),
|
2902
|
+
audio_output_tokens=usage.get("audio_output_tokens", 0)
|
2903
|
+
)
|
2904
|
+
|
2905
|
+
# Store metrics
|
2906
|
+
self.last_token_metrics = metrics
|
2907
|
+
|
2908
|
+
# Update session metrics
|
2909
|
+
if not self.session_token_metrics:
|
2910
|
+
self.session_token_metrics = TokenMetrics()
|
2911
|
+
self.session_token_metrics = self.session_token_metrics + metrics
|
2912
|
+
|
2913
|
+
# Track in global collector
|
2914
|
+
_token_collector.track_tokens(
|
2915
|
+
model=model,
|
2916
|
+
agent=self.current_agent_name,
|
2917
|
+
metrics=metrics,
|
2918
|
+
metadata={
|
2919
|
+
"provider": self.provider,
|
2920
|
+
"stream": False
|
2921
|
+
}
|
2922
|
+
)
|
2923
|
+
|
2924
|
+
return metrics
|
2925
|
+
|
2926
|
+
except Exception as e:
|
2927
|
+
if self.verbose:
|
2928
|
+
logging.warning(f"Failed to track token usage: {e}")
|
2929
|
+
return None
|
2930
|
+
|
2931
|
+
def set_current_agent(self, agent_name: Optional[str]):
|
2932
|
+
"""Set the current agent name for token tracking."""
|
2933
|
+
self.current_agent_name = agent_name
|
2864
2934
|
|
2865
2935
|
def _build_completion_params(self, **override_params) -> Dict[str, Any]:
|
2866
2936
|
"""Build parameters for litellm completion calls with all necessary config"""
|
praisonaiagents/main.py
CHANGED
@@ -12,6 +12,12 @@ from rich.markdown import Markdown
|
|
12
12
|
from rich.live import Live
|
13
13
|
import asyncio
|
14
14
|
|
15
|
+
# Import token metrics if available
|
16
|
+
try:
|
17
|
+
from .telemetry.token_collector import TokenMetrics
|
18
|
+
except ImportError:
|
19
|
+
TokenMetrics = None
|
20
|
+
|
15
21
|
# Logging is already configured in _logging.py via __init__.py
|
16
22
|
|
17
23
|
# Global list to store error logs
|
@@ -415,6 +421,7 @@ class TaskOutput(BaseModel):
|
|
415
421
|
json_dict: Optional[Dict[str, Any]] = None
|
416
422
|
agent: str
|
417
423
|
output_format: Literal["RAW", "JSON", "Pydantic"] = "RAW"
|
424
|
+
token_metrics: Optional['TokenMetrics'] = None # Add token metrics field
|
418
425
|
|
419
426
|
def json(self) -> Optional[str]:
|
420
427
|
if self.output_format == "JSON" and self.json_dict:
|
@@ -192,14 +192,20 @@ class HTTPStreamTransport:
|
|
192
192
|
self._message_queue = asyncio.Queue()
|
193
193
|
self._pending_requests = {}
|
194
194
|
self._closing = False
|
195
|
+
self._closed = False
|
195
196
|
|
196
197
|
async def __aenter__(self):
|
197
198
|
self._session = aiohttp.ClientSession()
|
198
199
|
return self
|
199
200
|
|
200
201
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
202
|
+
# Prevent double closing
|
203
|
+
if self._closed:
|
204
|
+
return
|
205
|
+
|
201
206
|
# Set closing flag to stop listener gracefully
|
202
207
|
self._closing = True
|
208
|
+
self._closed = True
|
203
209
|
|
204
210
|
if self._sse_task:
|
205
211
|
self._sse_task.cancel()
|
@@ -210,6 +216,13 @@ class HTTPStreamTransport:
|
|
210
216
|
if self._session:
|
211
217
|
await self._session.close()
|
212
218
|
|
219
|
+
def __del__(self):
|
220
|
+
"""Lightweight cleanup during garbage collection."""
|
221
|
+
# Note: We cannot safely run async cleanup in __del__
|
222
|
+
# The best practice is to use async context managers or explicit close() calls
|
223
|
+
pass
|
224
|
+
|
225
|
+
|
213
226
|
async def send_request(self, request: Dict[str, Any]) -> Union[Dict[str, Any], None]:
|
214
227
|
"""Send a request to the HTTP Stream endpoint."""
|
215
228
|
if not self._session:
|
@@ -460,7 +473,34 @@ class HTTPStreamMCPClient:
|
|
460
473
|
|
461
474
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
462
475
|
"""Async context manager exit."""
|
476
|
+
await self.aclose()
|
477
|
+
|
478
|
+
async def aclose(self):
|
479
|
+
"""Async cleanup method to close all resources."""
|
463
480
|
if self.transport:
|
464
|
-
await self.transport.__aexit__(
|
481
|
+
await self.transport.__aexit__(None, None, None)
|
465
482
|
if hasattr(self, '_session_context') and self._session_context:
|
466
|
-
await self._session_context.__aexit__(
|
483
|
+
await self._session_context.__aexit__(None, None, None)
|
484
|
+
|
485
|
+
def close(self):
|
486
|
+
"""Synchronous cleanup method to close all resources."""
|
487
|
+
if hasattr(self, 'transport') and self.transport and not getattr(self.transport, '_closed', False):
|
488
|
+
try:
|
489
|
+
# Use the global event loop for non-blocking cleanup
|
490
|
+
loop = get_event_loop()
|
491
|
+
if not loop.is_closed():
|
492
|
+
# Schedule cleanup without blocking
|
493
|
+
asyncio.run_coroutine_threadsafe(self.aclose(), loop)
|
494
|
+
except Exception:
|
495
|
+
# Silently ignore cleanup errors to avoid impacting performance
|
496
|
+
pass
|
497
|
+
|
498
|
+
def __del__(self):
|
499
|
+
"""Cleanup when object is garbage collected."""
|
500
|
+
try:
|
501
|
+
# Simple, lightweight cleanup
|
502
|
+
if hasattr(self, 'transport') and self.transport:
|
503
|
+
self.close()
|
504
|
+
except Exception:
|
505
|
+
# Never raise exceptions in __del__
|
506
|
+
pass
|
@@ -5,19 +5,24 @@ This module provides:
|
|
5
5
|
1. Anonymous usage tracking with privacy-first design
|
6
6
|
2. User-friendly performance monitoring and analysis tools
|
7
7
|
|
8
|
-
Telemetry
|
8
|
+
Telemetry can be disabled via environment variables:
|
9
9
|
- PRAISONAI_TELEMETRY_DISABLED=true
|
10
10
|
- PRAISONAI_DISABLE_TELEMETRY=true
|
11
11
|
- DO_NOT_TRACK=true
|
12
12
|
|
13
|
+
Performance monitoring can be optimized via environment variables:
|
14
|
+
- PRAISONAI_PERFORMANCE_DISABLED=true (disables performance monitoring overhead)
|
15
|
+
- PRAISONAI_FLOW_ANALYSIS_ENABLED=true (enables expensive flow analysis - opt-in only)
|
16
|
+
|
13
17
|
No personal data, prompts, or responses are collected.
|
14
18
|
|
15
19
|
Performance Monitoring Features:
|
16
20
|
- Function performance tracking with detailed statistics
|
17
21
|
- API call monitoring and analysis
|
18
|
-
- Function execution flow visualization
|
22
|
+
- Function execution flow visualization (opt-in)
|
19
23
|
- Performance bottleneck identification
|
20
24
|
- Real-time performance reporting
|
25
|
+
- External APM metrics export (DataDog, New Relic compatible)
|
21
26
|
- CLI interface for easy access
|
22
27
|
"""
|
23
28
|
|
@@ -37,7 +42,7 @@ try:
|
|
37
42
|
PerformanceMonitor, performance_monitor,
|
38
43
|
monitor_function, track_api_call, get_performance_report,
|
39
44
|
get_function_stats, get_api_stats, get_slowest_functions,
|
40
|
-
get_slowest_apis, clear_performance_data
|
45
|
+
get_slowest_apis, clear_performance_data, export_external_apm_metrics
|
41
46
|
)
|
42
47
|
from .performance_utils import (
|
43
48
|
FunctionFlowAnalyzer, PerformanceAnalyzer,
|
@@ -58,6 +63,10 @@ __all__ = [
|
|
58
63
|
'force_shutdown_telemetry',
|
59
64
|
'MinimalTelemetry',
|
60
65
|
'TelemetryCollector', # For backward compatibility
|
66
|
+
# Performance optimizations
|
67
|
+
'enable_performance_mode',
|
68
|
+
'disable_performance_mode',
|
69
|
+
'cleanup_telemetry_resources',
|
61
70
|
]
|
62
71
|
|
63
72
|
# Add performance monitoring to __all__ if available
|
@@ -81,6 +90,7 @@ if PERFORMANCE_MONITORING_AVAILABLE:
|
|
81
90
|
'get_slowest_functions',
|
82
91
|
'get_slowest_apis',
|
83
92
|
'clear_performance_data',
|
93
|
+
'export_external_apm_metrics',
|
84
94
|
'analyze_function_flow',
|
85
95
|
'visualize_execution_flow',
|
86
96
|
'analyze_performance_trends',
|
@@ -114,6 +124,24 @@ def force_shutdown_telemetry():
|
|
114
124
|
_force_shutdown_telemetry()
|
115
125
|
|
116
126
|
|
127
|
+
def enable_performance_mode():
|
128
|
+
"""Enable performance mode for minimal telemetry overhead."""
|
129
|
+
from .integration import enable_performance_mode as _enable_performance_mode
|
130
|
+
_enable_performance_mode()
|
131
|
+
|
132
|
+
|
133
|
+
def disable_performance_mode():
|
134
|
+
"""Disable performance mode to resume full telemetry tracking."""
|
135
|
+
from .integration import disable_performance_mode as _disable_performance_mode
|
136
|
+
_disable_performance_mode()
|
137
|
+
|
138
|
+
|
139
|
+
def cleanup_telemetry_resources():
|
140
|
+
"""Clean up telemetry resources including thread pools and queues."""
|
141
|
+
from .integration import cleanup_telemetry_resources as _cleanup_telemetry_resources
|
142
|
+
_cleanup_telemetry_resources()
|
143
|
+
|
144
|
+
|
117
145
|
# Auto-instrumentation and cleanup setup
|
118
146
|
_initialized = False
|
119
147
|
_atexit_registered = False
|