praisonaiagents 0.0.102__py3-none-any.whl → 0.0.103__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 +47 -1
- praisonaiagents/llm/__init__.py +11 -0
- praisonaiagents/llm/llm.py +6 -0
- praisonaiagents/memory/memory.py +4 -0
- praisonaiagents/telemetry/__init__.py +102 -0
- praisonaiagents/telemetry/integration.py +242 -0
- praisonaiagents/telemetry/telemetry.py +350 -0
- {praisonaiagents-0.0.102.dist-info → praisonaiagents-0.0.103.dist-info}/METADATA +7 -3
- {praisonaiagents-0.0.102.dist-info → praisonaiagents-0.0.103.dist-info}/RECORD +11 -8
- {praisonaiagents-0.0.102.dist-info → praisonaiagents-0.0.103.dist-info}/WHEEL +0 -0
- {praisonaiagents-0.0.102.dist-info → praisonaiagents-0.0.103.dist-info}/top_level.txt +0 -0
praisonaiagents/__init__.py
CHANGED
@@ -30,9 +30,50 @@ from .main import (
|
|
30
30
|
async_display_callbacks,
|
31
31
|
)
|
32
32
|
|
33
|
+
# Telemetry support (lazy loaded)
|
34
|
+
try:
|
35
|
+
from .telemetry import (
|
36
|
+
get_telemetry,
|
37
|
+
enable_telemetry,
|
38
|
+
disable_telemetry,
|
39
|
+
MinimalTelemetry,
|
40
|
+
TelemetryCollector
|
41
|
+
)
|
42
|
+
_telemetry_available = True
|
43
|
+
except ImportError:
|
44
|
+
# Telemetry not available - provide stub functions
|
45
|
+
_telemetry_available = False
|
46
|
+
def get_telemetry():
|
47
|
+
return None
|
48
|
+
|
49
|
+
def enable_telemetry(*args, **kwargs):
|
50
|
+
import logging
|
51
|
+
logging.warning(
|
52
|
+
"Telemetry not available. Install with: pip install praisonaiagents[telemetry]"
|
53
|
+
)
|
54
|
+
return None
|
55
|
+
|
56
|
+
def disable_telemetry():
|
57
|
+
pass
|
58
|
+
|
59
|
+
MinimalTelemetry = None
|
60
|
+
TelemetryCollector = None
|
61
|
+
|
33
62
|
# Add Agents as an alias for PraisonAIAgents
|
34
63
|
Agents = PraisonAIAgents
|
35
64
|
|
65
|
+
# Apply telemetry auto-instrumentation after all imports
|
66
|
+
if _telemetry_available:
|
67
|
+
try:
|
68
|
+
# Only instrument if telemetry is enabled
|
69
|
+
_telemetry = get_telemetry()
|
70
|
+
if _telemetry and _telemetry.enabled:
|
71
|
+
from .telemetry.integration import auto_instrument_all
|
72
|
+
auto_instrument_all(_telemetry)
|
73
|
+
except Exception:
|
74
|
+
# Silently fail if there are any issues
|
75
|
+
pass
|
76
|
+
|
36
77
|
__all__ = [
|
37
78
|
'Agent',
|
38
79
|
'ImageAgent',
|
@@ -60,5 +101,10 @@ __all__ = [
|
|
60
101
|
'Chunking',
|
61
102
|
'MCP',
|
62
103
|
'GuardrailResult',
|
63
|
-
'LLMGuardrail'
|
104
|
+
'LLMGuardrail',
|
105
|
+
'get_telemetry',
|
106
|
+
'enable_telemetry',
|
107
|
+
'disable_telemetry',
|
108
|
+
'MinimalTelemetry',
|
109
|
+
'TelemetryCollector'
|
64
110
|
]
|
praisonaiagents/llm/__init__.py
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
import logging
|
2
2
|
import warnings
|
3
|
+
import os
|
4
|
+
|
5
|
+
# Disable litellm telemetry before any imports
|
6
|
+
os.environ["LITELLM_TELEMETRY"] = "False"
|
3
7
|
|
4
8
|
# Suppress all relevant logs at module level
|
5
9
|
logging.getLogger("litellm").setLevel(logging.ERROR)
|
@@ -17,4 +21,11 @@ logging.basicConfig(level=logging.WARNING)
|
|
17
21
|
# Import after suppressing warnings
|
18
22
|
from .llm import LLM, LLMContextLengthExceededException
|
19
23
|
|
24
|
+
# Ensure telemetry is disabled after import as well
|
25
|
+
try:
|
26
|
+
import litellm
|
27
|
+
litellm.telemetry = False
|
28
|
+
except ImportError:
|
29
|
+
pass
|
30
|
+
|
20
31
|
__all__ = ["LLM", "LLMContextLengthExceededException"]
|
praisonaiagents/llm/llm.py
CHANGED
@@ -17,6 +17,9 @@ from ..main import (
|
|
17
17
|
from rich.console import Console
|
18
18
|
from rich.live import Live
|
19
19
|
|
20
|
+
# Disable litellm telemetry before any imports
|
21
|
+
os.environ["LITELLM_TELEMETRY"] = "False"
|
22
|
+
|
20
23
|
# TODO: Include in-build tool calling in LLM class
|
21
24
|
# TODO: Restructure so that duplicate calls are not made (Sync with agent.py)
|
22
25
|
class LLMContextLengthExceededException(Exception):
|
@@ -108,6 +111,9 @@ class LLM:
|
|
108
111
|
):
|
109
112
|
try:
|
110
113
|
import litellm
|
114
|
+
# Disable telemetry
|
115
|
+
litellm.telemetry = False
|
116
|
+
|
111
117
|
# Set litellm options globally
|
112
118
|
litellm.set_verbose = False
|
113
119
|
litellm.success_callback = []
|
praisonaiagents/memory/memory.py
CHANGED
@@ -6,6 +6,9 @@ import shutil
|
|
6
6
|
from typing import Any, Dict, List, Optional, Union, Literal
|
7
7
|
import logging
|
8
8
|
|
9
|
+
# Disable litellm telemetry before any imports
|
10
|
+
os.environ["LITELLM_TELEMETRY"] = "False"
|
11
|
+
|
9
12
|
# Set up logger
|
10
13
|
logger = logging.getLogger(__name__)
|
11
14
|
|
@@ -31,6 +34,7 @@ except ImportError:
|
|
31
34
|
|
32
35
|
try:
|
33
36
|
import litellm
|
37
|
+
litellm.telemetry = False # Disable telemetry
|
34
38
|
LITELLM_AVAILABLE = True
|
35
39
|
except ImportError:
|
36
40
|
LITELLM_AVAILABLE = False
|
@@ -0,0 +1,102 @@
|
|
1
|
+
"""
|
2
|
+
PraisonAI Agents Minimal Telemetry Module
|
3
|
+
|
4
|
+
This module provides anonymous usage tracking with privacy-first design.
|
5
|
+
Telemetry is opt-out and can be disabled via environment variables:
|
6
|
+
- PRAISONAI_TELEMETRY_DISABLED=true
|
7
|
+
- PRAISONAI_DISABLE_TELEMETRY=true
|
8
|
+
- DO_NOT_TRACK=true
|
9
|
+
|
10
|
+
No personal data, prompts, or responses are collected.
|
11
|
+
"""
|
12
|
+
|
13
|
+
import os
|
14
|
+
import atexit
|
15
|
+
from typing import Optional, TYPE_CHECKING
|
16
|
+
|
17
|
+
if TYPE_CHECKING:
|
18
|
+
from .telemetry import MinimalTelemetry, TelemetryCollector
|
19
|
+
|
20
|
+
# Import the classes for real (not just type checking)
|
21
|
+
from .telemetry import MinimalTelemetry, TelemetryCollector
|
22
|
+
|
23
|
+
__all__ = [
|
24
|
+
'get_telemetry',
|
25
|
+
'enable_telemetry',
|
26
|
+
'disable_telemetry',
|
27
|
+
'MinimalTelemetry',
|
28
|
+
'TelemetryCollector', # For backward compatibility
|
29
|
+
]
|
30
|
+
|
31
|
+
|
32
|
+
def get_telemetry() -> 'MinimalTelemetry':
|
33
|
+
"""Get the global telemetry instance."""
|
34
|
+
from .telemetry import get_telemetry as _get_telemetry
|
35
|
+
return _get_telemetry()
|
36
|
+
|
37
|
+
|
38
|
+
def enable_telemetry():
|
39
|
+
"""Enable telemetry (if not disabled by environment)."""
|
40
|
+
from .telemetry import enable_telemetry as _enable_telemetry
|
41
|
+
_enable_telemetry()
|
42
|
+
|
43
|
+
|
44
|
+
def disable_telemetry():
|
45
|
+
"""Disable telemetry."""
|
46
|
+
from .telemetry import disable_telemetry as _disable_telemetry
|
47
|
+
_disable_telemetry()
|
48
|
+
|
49
|
+
|
50
|
+
# Auto-instrumentation and cleanup setup
|
51
|
+
_initialized = False
|
52
|
+
_atexit_registered = False
|
53
|
+
|
54
|
+
def _ensure_atexit():
|
55
|
+
"""Ensure atexit handler is registered."""
|
56
|
+
global _atexit_registered
|
57
|
+
if _atexit_registered:
|
58
|
+
return
|
59
|
+
|
60
|
+
# Check if telemetry should be disabled
|
61
|
+
telemetry_disabled = any([
|
62
|
+
os.environ.get('PRAISONAI_TELEMETRY_DISABLED', '').lower() in ('true', '1', 'yes'),
|
63
|
+
os.environ.get('PRAISONAI_DISABLE_TELEMETRY', '').lower() in ('true', '1', 'yes'),
|
64
|
+
os.environ.get('DO_NOT_TRACK', '').lower() in ('true', '1', 'yes'),
|
65
|
+
])
|
66
|
+
|
67
|
+
if not telemetry_disabled:
|
68
|
+
# Register atexit handler to flush telemetry on exit
|
69
|
+
atexit.register(lambda: get_telemetry().flush())
|
70
|
+
_atexit_registered = True
|
71
|
+
|
72
|
+
def _initialize_telemetry():
|
73
|
+
"""Initialize telemetry with auto-instrumentation and cleanup."""
|
74
|
+
global _initialized
|
75
|
+
if _initialized:
|
76
|
+
return
|
77
|
+
|
78
|
+
# Ensure atexit is registered
|
79
|
+
_ensure_atexit()
|
80
|
+
|
81
|
+
# Check if telemetry should be disabled
|
82
|
+
telemetry_disabled = any([
|
83
|
+
os.environ.get('PRAISONAI_TELEMETRY_DISABLED', '').lower() in ('true', '1', 'yes'),
|
84
|
+
os.environ.get('PRAISONAI_DISABLE_TELEMETRY', '').lower() in ('true', '1', 'yes'),
|
85
|
+
os.environ.get('DO_NOT_TRACK', '').lower() in ('true', '1', 'yes'),
|
86
|
+
])
|
87
|
+
|
88
|
+
if not telemetry_disabled:
|
89
|
+
try:
|
90
|
+
# Defer the actual instrumentation to avoid circular imports
|
91
|
+
# This will be called when get_telemetry() is first accessed
|
92
|
+
_initialized = True
|
93
|
+
except Exception:
|
94
|
+
# Silently fail if there are any issues
|
95
|
+
pass
|
96
|
+
|
97
|
+
|
98
|
+
# No need for lazy auto-instrumentation here since main __init__.py handles it
|
99
|
+
|
100
|
+
|
101
|
+
# Initialize atexit handler early
|
102
|
+
_ensure_atexit()
|
@@ -0,0 +1,242 @@
|
|
1
|
+
"""
|
2
|
+
Simplified integration module for adding telemetry to core PraisonAI components.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from typing import Any, Optional, TYPE_CHECKING
|
6
|
+
from functools import wraps
|
7
|
+
import time
|
8
|
+
|
9
|
+
if TYPE_CHECKING:
|
10
|
+
from .telemetry import MinimalTelemetry
|
11
|
+
from ..agent.agent import Agent
|
12
|
+
from ..task.task import Task
|
13
|
+
from ..agents.agents import PraisonAIAgents
|
14
|
+
|
15
|
+
|
16
|
+
def instrument_agent(agent: 'Agent', telemetry: Optional['MinimalTelemetry'] = None):
|
17
|
+
"""
|
18
|
+
Instrument an Agent instance with minimal telemetry.
|
19
|
+
|
20
|
+
Args:
|
21
|
+
agent: The Agent instance to instrument
|
22
|
+
telemetry: Optional telemetry instance (uses global if not provided)
|
23
|
+
"""
|
24
|
+
if not telemetry:
|
25
|
+
from .telemetry import get_telemetry
|
26
|
+
telemetry = get_telemetry()
|
27
|
+
|
28
|
+
if not telemetry.enabled:
|
29
|
+
return agent
|
30
|
+
|
31
|
+
# Check if agent is already instrumented to avoid double-counting
|
32
|
+
if hasattr(agent, '_telemetry_instrumented'):
|
33
|
+
return agent
|
34
|
+
|
35
|
+
# Store original methods
|
36
|
+
original_chat = agent.chat if hasattr(agent, 'chat') else None
|
37
|
+
original_start = agent.start if hasattr(agent, 'start') else None
|
38
|
+
original_run = agent.run if hasattr(agent, 'run') else None
|
39
|
+
original_execute_tool = agent.execute_tool if hasattr(agent, 'execute_tool') else None
|
40
|
+
|
41
|
+
# Wrap chat method if it exists (this is the main method called by workflow)
|
42
|
+
if original_chat:
|
43
|
+
@wraps(original_chat)
|
44
|
+
def instrumented_chat(*args, **kwargs):
|
45
|
+
try:
|
46
|
+
result = original_chat(*args, **kwargs)
|
47
|
+
telemetry.track_agent_execution(agent.name, success=True)
|
48
|
+
return result
|
49
|
+
except Exception as e:
|
50
|
+
telemetry.track_agent_execution(agent.name, success=False)
|
51
|
+
telemetry.track_error(type(e).__name__)
|
52
|
+
raise
|
53
|
+
|
54
|
+
agent.chat = instrumented_chat
|
55
|
+
|
56
|
+
# Wrap start method if it exists
|
57
|
+
if original_start:
|
58
|
+
@wraps(original_start)
|
59
|
+
def instrumented_start(*args, **kwargs):
|
60
|
+
try:
|
61
|
+
result = original_start(*args, **kwargs)
|
62
|
+
telemetry.track_agent_execution(agent.name, success=True)
|
63
|
+
return result
|
64
|
+
except Exception as e:
|
65
|
+
telemetry.track_agent_execution(agent.name, success=False)
|
66
|
+
telemetry.track_error(type(e).__name__)
|
67
|
+
raise
|
68
|
+
|
69
|
+
agent.start = instrumented_start
|
70
|
+
|
71
|
+
# Wrap run method if it exists
|
72
|
+
if original_run:
|
73
|
+
@wraps(original_run)
|
74
|
+
def instrumented_run(*args, **kwargs):
|
75
|
+
try:
|
76
|
+
result = original_run(*args, **kwargs)
|
77
|
+
telemetry.track_agent_execution(agent.name, success=True)
|
78
|
+
return result
|
79
|
+
except Exception as e:
|
80
|
+
telemetry.track_agent_execution(agent.name, success=False)
|
81
|
+
telemetry.track_error(type(e).__name__)
|
82
|
+
raise
|
83
|
+
|
84
|
+
agent.run = instrumented_run
|
85
|
+
|
86
|
+
# Wrap execute_tool method
|
87
|
+
if original_execute_tool:
|
88
|
+
@wraps(original_execute_tool)
|
89
|
+
def instrumented_execute_tool(tool_name: str, *args, **kwargs):
|
90
|
+
try:
|
91
|
+
result = original_execute_tool(tool_name, *args, **kwargs)
|
92
|
+
telemetry.track_tool_usage(tool_name, success=True)
|
93
|
+
return result
|
94
|
+
except Exception as e:
|
95
|
+
telemetry.track_tool_usage(tool_name, success=False)
|
96
|
+
telemetry.track_error(type(e).__name__)
|
97
|
+
raise
|
98
|
+
|
99
|
+
agent.execute_tool = instrumented_execute_tool
|
100
|
+
|
101
|
+
# Mark agent as instrumented to avoid double instrumentation
|
102
|
+
agent._telemetry_instrumented = True
|
103
|
+
|
104
|
+
return agent
|
105
|
+
|
106
|
+
|
107
|
+
def instrument_workflow(workflow: 'PraisonAIAgents', telemetry: Optional['MinimalTelemetry'] = None):
|
108
|
+
"""
|
109
|
+
Instrument a PraisonAIAgents workflow with minimal telemetry.
|
110
|
+
|
111
|
+
Args:
|
112
|
+
workflow: The PraisonAIAgents instance to instrument
|
113
|
+
telemetry: Optional telemetry instance (uses global if not provided)
|
114
|
+
"""
|
115
|
+
if not telemetry:
|
116
|
+
from .telemetry import get_telemetry
|
117
|
+
telemetry = get_telemetry()
|
118
|
+
|
119
|
+
if not telemetry.enabled:
|
120
|
+
return workflow
|
121
|
+
|
122
|
+
# Check if workflow is already instrumented to avoid double-counting
|
123
|
+
if hasattr(workflow, '_telemetry_instrumented'):
|
124
|
+
return workflow
|
125
|
+
|
126
|
+
# Track feature usage
|
127
|
+
telemetry.track_feature_usage(f"workflow_{workflow.process}" if hasattr(workflow, 'process') else "workflow")
|
128
|
+
|
129
|
+
# Instrument all agents in the workflow
|
130
|
+
if hasattr(workflow, 'agents') and workflow.agents:
|
131
|
+
for agent in workflow.agents:
|
132
|
+
instrument_agent(agent, telemetry)
|
133
|
+
|
134
|
+
# Wrap the execute_task method to track task completions
|
135
|
+
if hasattr(workflow, 'execute_task'):
|
136
|
+
original_execute_task = workflow.execute_task
|
137
|
+
|
138
|
+
@wraps(original_execute_task)
|
139
|
+
def instrumented_execute_task(task_id, *args, **kwargs):
|
140
|
+
task = None
|
141
|
+
try:
|
142
|
+
# Get task info
|
143
|
+
if hasattr(workflow, 'tasks') and task_id < len(workflow.tasks):
|
144
|
+
task = workflow.tasks[task_id]
|
145
|
+
|
146
|
+
result = original_execute_task(task_id, *args, **kwargs)
|
147
|
+
|
148
|
+
# Track task completion
|
149
|
+
task_name = task.name if task and hasattr(task, 'name') else f"task_{task_id}"
|
150
|
+
telemetry.track_task_completion(task_name, success=True)
|
151
|
+
|
152
|
+
return result
|
153
|
+
except Exception as e:
|
154
|
+
telemetry.track_error(type(e).__name__)
|
155
|
+
if task:
|
156
|
+
task_name = task.name if hasattr(task, 'name') else f"task_{task_id}"
|
157
|
+
telemetry.track_task_completion(task_name, success=False)
|
158
|
+
raise
|
159
|
+
|
160
|
+
workflow.execute_task = instrumented_execute_task
|
161
|
+
|
162
|
+
# Wrap the start method
|
163
|
+
original_start = workflow.start
|
164
|
+
|
165
|
+
@wraps(original_start)
|
166
|
+
def instrumented_start(*args, **kwargs):
|
167
|
+
try:
|
168
|
+
result = original_start(*args, **kwargs)
|
169
|
+
# Don't double-track here since agent.chat already tracks execution
|
170
|
+
return result
|
171
|
+
except Exception as e:
|
172
|
+
telemetry.track_error(type(e).__name__)
|
173
|
+
raise
|
174
|
+
|
175
|
+
workflow.start = instrumented_start
|
176
|
+
|
177
|
+
# Also wrap astart if it exists (async version)
|
178
|
+
if hasattr(workflow, 'astart'):
|
179
|
+
original_astart = workflow.astart
|
180
|
+
|
181
|
+
@wraps(original_astart)
|
182
|
+
async def instrumented_astart(*args, **kwargs):
|
183
|
+
try:
|
184
|
+
result = await original_astart(*args, **kwargs)
|
185
|
+
# Don't double-track here since agent.chat already tracks execution
|
186
|
+
return result
|
187
|
+
except Exception as e:
|
188
|
+
telemetry.track_error(type(e).__name__)
|
189
|
+
raise
|
190
|
+
|
191
|
+
workflow.astart = instrumented_astart
|
192
|
+
|
193
|
+
# Mark workflow as instrumented to avoid double instrumentation
|
194
|
+
workflow._telemetry_instrumented = True
|
195
|
+
|
196
|
+
return workflow
|
197
|
+
|
198
|
+
|
199
|
+
# Auto-instrumentation helper
|
200
|
+
def auto_instrument_all(telemetry: Optional['MinimalTelemetry'] = None):
|
201
|
+
"""
|
202
|
+
Automatically instrument all new instances of Agent and PraisonAIAgents.
|
203
|
+
This should be called after enabling telemetry.
|
204
|
+
|
205
|
+
Args:
|
206
|
+
telemetry: Optional telemetry instance (uses global if not provided)
|
207
|
+
"""
|
208
|
+
if not telemetry:
|
209
|
+
from .telemetry import get_telemetry
|
210
|
+
telemetry = get_telemetry()
|
211
|
+
|
212
|
+
if not telemetry.enabled:
|
213
|
+
return
|
214
|
+
|
215
|
+
try:
|
216
|
+
# Import the classes
|
217
|
+
from ..agent.agent import Agent
|
218
|
+
from ..agents.agents import PraisonAIAgents
|
219
|
+
|
220
|
+
# Store original __init__ methods
|
221
|
+
original_agent_init = Agent.__init__
|
222
|
+
original_workflow_init = PraisonAIAgents.__init__
|
223
|
+
|
224
|
+
# Wrap Agent.__init__
|
225
|
+
@wraps(original_agent_init)
|
226
|
+
def agent_init_wrapper(self, *args, **kwargs):
|
227
|
+
original_agent_init(self, *args, **kwargs)
|
228
|
+
instrument_agent(self, telemetry)
|
229
|
+
|
230
|
+
# Wrap PraisonAIAgents.__init__
|
231
|
+
@wraps(original_workflow_init)
|
232
|
+
def workflow_init_wrapper(self, *args, **kwargs):
|
233
|
+
original_workflow_init(self, *args, **kwargs)
|
234
|
+
instrument_workflow(self, telemetry)
|
235
|
+
|
236
|
+
# Apply wrapped constructors
|
237
|
+
Agent.__init__ = agent_init_wrapper
|
238
|
+
PraisonAIAgents.__init__ = workflow_init_wrapper
|
239
|
+
|
240
|
+
except ImportError:
|
241
|
+
# Classes not available, skip auto-instrumentation
|
242
|
+
pass
|
@@ -0,0 +1,350 @@
|
|
1
|
+
"""
|
2
|
+
Minimal telemetry implementation for PraisonAI Agents.
|
3
|
+
|
4
|
+
This module provides anonymous usage tracking with privacy-first design.
|
5
|
+
All telemetry is opt-out via environment variables.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import os
|
9
|
+
import time
|
10
|
+
import platform
|
11
|
+
import hashlib
|
12
|
+
from typing import Dict, Any, Optional
|
13
|
+
from datetime import datetime
|
14
|
+
import logging
|
15
|
+
|
16
|
+
# Try to import PostHog
|
17
|
+
try:
|
18
|
+
from posthog import Posthog
|
19
|
+
POSTHOG_AVAILABLE = True
|
20
|
+
except ImportError:
|
21
|
+
POSTHOG_AVAILABLE = False
|
22
|
+
|
23
|
+
# Check for opt-out environment variables
|
24
|
+
_TELEMETRY_DISABLED = any([
|
25
|
+
os.environ.get('PRAISONAI_TELEMETRY_DISABLED', '').lower() in ('true', '1', 'yes'),
|
26
|
+
os.environ.get('PRAISONAI_DISABLE_TELEMETRY', '').lower() in ('true', '1', 'yes'),
|
27
|
+
os.environ.get('DO_NOT_TRACK', '').lower() in ('true', '1', 'yes'),
|
28
|
+
])
|
29
|
+
|
30
|
+
|
31
|
+
class MinimalTelemetry:
|
32
|
+
"""
|
33
|
+
Minimal telemetry collector for anonymous usage tracking.
|
34
|
+
|
35
|
+
Privacy guarantees:
|
36
|
+
- No personal data is collected
|
37
|
+
- No prompts, responses, or user content is tracked
|
38
|
+
- Only anonymous metrics about feature usage
|
39
|
+
- Respects DO_NOT_TRACK standard
|
40
|
+
- Can be disabled via environment variables
|
41
|
+
"""
|
42
|
+
|
43
|
+
def __init__(self, enabled: bool = None):
|
44
|
+
"""
|
45
|
+
Initialize the minimal telemetry collector.
|
46
|
+
|
47
|
+
Args:
|
48
|
+
enabled: Override the environment-based enable/disable setting
|
49
|
+
"""
|
50
|
+
# Respect explicit enabled parameter, otherwise check environment
|
51
|
+
if enabled is not None:
|
52
|
+
self.enabled = enabled
|
53
|
+
else:
|
54
|
+
self.enabled = not _TELEMETRY_DISABLED
|
55
|
+
|
56
|
+
self.logger = logging.getLogger(__name__)
|
57
|
+
|
58
|
+
if not self.enabled:
|
59
|
+
self.logger.debug("Telemetry is disabled")
|
60
|
+
return
|
61
|
+
|
62
|
+
# Generate anonymous session ID (not user ID)
|
63
|
+
session_data = f"{datetime.now().isoformat()}-{os.getpid()}-{time.time()}"
|
64
|
+
self.session_id = hashlib.sha256(session_data.encode()).hexdigest()[:16]
|
65
|
+
|
66
|
+
# Basic metrics storage
|
67
|
+
self._metrics = {
|
68
|
+
"agent_executions": 0,
|
69
|
+
"task_completions": 0,
|
70
|
+
"tool_calls": 0,
|
71
|
+
"errors": 0,
|
72
|
+
}
|
73
|
+
|
74
|
+
# Collect basic environment info (anonymous)
|
75
|
+
self._environment = {
|
76
|
+
"python_version": platform.python_version(),
|
77
|
+
"os_type": platform.system(),
|
78
|
+
"framework_version": self._get_framework_version(),
|
79
|
+
}
|
80
|
+
|
81
|
+
self.logger.debug(f"Telemetry enabled with session {self.session_id}")
|
82
|
+
|
83
|
+
# Initialize PostHog if available
|
84
|
+
if POSTHOG_AVAILABLE:
|
85
|
+
try:
|
86
|
+
self._posthog = Posthog(
|
87
|
+
project_api_key='phc_skZpl3eFLQJ4iYjsERNMbCO6jfeSJi2vyZlPahKgxZ7',
|
88
|
+
host='https://eu.i.posthog.com',
|
89
|
+
disable_geoip=True
|
90
|
+
)
|
91
|
+
except:
|
92
|
+
self._posthog = None
|
93
|
+
else:
|
94
|
+
self._posthog = None
|
95
|
+
|
96
|
+
def _get_framework_version(self) -> str:
|
97
|
+
"""Get the PraisonAI Agents version."""
|
98
|
+
try:
|
99
|
+
from .. import __version__
|
100
|
+
return __version__
|
101
|
+
except ImportError:
|
102
|
+
return "unknown"
|
103
|
+
|
104
|
+
def track_agent_execution(self, agent_name: str = None, success: bool = True):
|
105
|
+
"""
|
106
|
+
Track an agent execution event.
|
107
|
+
|
108
|
+
Args:
|
109
|
+
agent_name: Name of the agent (not logged, just for counting)
|
110
|
+
success: Whether the execution was successful
|
111
|
+
"""
|
112
|
+
if not self.enabled:
|
113
|
+
return
|
114
|
+
|
115
|
+
self._metrics["agent_executions"] += 1
|
116
|
+
|
117
|
+
# In a real implementation, this would send to a backend
|
118
|
+
# For now, just log at debug level
|
119
|
+
self.logger.debug(f"Agent execution tracked: success={success}")
|
120
|
+
|
121
|
+
def track_task_completion(self, task_name: str = None, success: bool = True):
|
122
|
+
"""
|
123
|
+
Track a task completion event.
|
124
|
+
|
125
|
+
Args:
|
126
|
+
task_name: Name of the task (not logged, just for counting)
|
127
|
+
success: Whether the task completed successfully
|
128
|
+
"""
|
129
|
+
if not self.enabled:
|
130
|
+
return
|
131
|
+
|
132
|
+
self._metrics["task_completions"] += 1
|
133
|
+
|
134
|
+
self.logger.debug(f"Task completion tracked: success={success}")
|
135
|
+
|
136
|
+
def track_tool_usage(self, tool_name: str, success: bool = True):
|
137
|
+
"""
|
138
|
+
Track tool usage event.
|
139
|
+
|
140
|
+
Args:
|
141
|
+
tool_name: Name of the tool being used
|
142
|
+
success: Whether the tool call was successful
|
143
|
+
"""
|
144
|
+
if not self.enabled:
|
145
|
+
return
|
146
|
+
|
147
|
+
self._metrics["tool_calls"] += 1
|
148
|
+
|
149
|
+
# Only track tool name, not arguments or results
|
150
|
+
self.logger.debug(f"Tool usage tracked: {tool_name}, success={success}")
|
151
|
+
|
152
|
+
def track_error(self, error_type: str = None):
|
153
|
+
"""
|
154
|
+
Track an error event.
|
155
|
+
|
156
|
+
Args:
|
157
|
+
error_type: Type of error (not the full message)
|
158
|
+
"""
|
159
|
+
if not self.enabled:
|
160
|
+
return
|
161
|
+
|
162
|
+
self._metrics["errors"] += 1
|
163
|
+
|
164
|
+
# Only track error type, not full error messages
|
165
|
+
self.logger.debug(f"Error tracked: type={error_type or 'unknown'}")
|
166
|
+
|
167
|
+
def track_feature_usage(self, feature_name: str):
|
168
|
+
"""
|
169
|
+
Track usage of a specific feature.
|
170
|
+
|
171
|
+
Args:
|
172
|
+
feature_name: Name of the feature being used
|
173
|
+
"""
|
174
|
+
if not self.enabled:
|
175
|
+
return
|
176
|
+
|
177
|
+
# Track which features are being used
|
178
|
+
self.logger.debug(f"Feature usage tracked: {feature_name}")
|
179
|
+
|
180
|
+
def get_metrics(self) -> Dict[str, Any]:
|
181
|
+
"""
|
182
|
+
Get current metrics summary.
|
183
|
+
|
184
|
+
Returns:
|
185
|
+
Dictionary of current metrics
|
186
|
+
"""
|
187
|
+
if not self.enabled:
|
188
|
+
return {"enabled": False}
|
189
|
+
|
190
|
+
return {
|
191
|
+
"enabled": True,
|
192
|
+
"session_id": self.session_id,
|
193
|
+
"metrics": self._metrics.copy(),
|
194
|
+
"environment": self._environment.copy(),
|
195
|
+
}
|
196
|
+
|
197
|
+
def flush(self):
|
198
|
+
"""
|
199
|
+
Flush any pending telemetry data.
|
200
|
+
|
201
|
+
In a real implementation, this would send data to a backend.
|
202
|
+
"""
|
203
|
+
if not self.enabled:
|
204
|
+
return
|
205
|
+
|
206
|
+
metrics = self.get_metrics()
|
207
|
+
self.logger.debug(f"Telemetry flush: {metrics}")
|
208
|
+
|
209
|
+
# Send to PostHog if available
|
210
|
+
if hasattr(self, '_posthog') and self._posthog:
|
211
|
+
|
212
|
+
try:
|
213
|
+
self._posthog.capture(
|
214
|
+
distinct_id='anonymous',
|
215
|
+
event='sdk_used',
|
216
|
+
properties={
|
217
|
+
'version': self._environment['framework_version'],
|
218
|
+
'os': platform.system(),
|
219
|
+
'$process_person_profile': False,
|
220
|
+
'$geoip_disable': True
|
221
|
+
}
|
222
|
+
)
|
223
|
+
except:
|
224
|
+
pass
|
225
|
+
|
226
|
+
# Reset counters
|
227
|
+
for key in self._metrics:
|
228
|
+
if isinstance(self._metrics[key], int):
|
229
|
+
self._metrics[key] = 0
|
230
|
+
|
231
|
+
|
232
|
+
# Global telemetry instance
|
233
|
+
_telemetry_instance = None
|
234
|
+
|
235
|
+
|
236
|
+
def get_telemetry() -> MinimalTelemetry:
|
237
|
+
"""
|
238
|
+
Get the global telemetry instance.
|
239
|
+
|
240
|
+
Returns:
|
241
|
+
The global MinimalTelemetry instance
|
242
|
+
"""
|
243
|
+
global _telemetry_instance
|
244
|
+
if _telemetry_instance is None:
|
245
|
+
_telemetry_instance = MinimalTelemetry()
|
246
|
+
return _telemetry_instance
|
247
|
+
|
248
|
+
|
249
|
+
def disable_telemetry():
|
250
|
+
"""Programmatically disable telemetry."""
|
251
|
+
global _telemetry_instance
|
252
|
+
if _telemetry_instance:
|
253
|
+
_telemetry_instance.enabled = False
|
254
|
+
else:
|
255
|
+
_telemetry_instance = MinimalTelemetry(enabled=False)
|
256
|
+
|
257
|
+
|
258
|
+
def enable_telemetry():
|
259
|
+
"""Programmatically enable telemetry (if not disabled by environment)."""
|
260
|
+
global _telemetry_instance
|
261
|
+
if not _TELEMETRY_DISABLED:
|
262
|
+
if _telemetry_instance:
|
263
|
+
_telemetry_instance.enabled = True
|
264
|
+
else:
|
265
|
+
_telemetry_instance = MinimalTelemetry(enabled=True)
|
266
|
+
|
267
|
+
|
268
|
+
# For backward compatibility with existing code
|
269
|
+
class TelemetryCollector:
|
270
|
+
"""Backward compatibility wrapper for the old TelemetryCollector interface."""
|
271
|
+
|
272
|
+
def __init__(self, backend: str = "minimal", service_name: str = "praisonai-agents", **kwargs):
|
273
|
+
self.telemetry = get_telemetry()
|
274
|
+
|
275
|
+
def start(self):
|
276
|
+
"""Start telemetry collection."""
|
277
|
+
# No-op for minimal implementation
|
278
|
+
pass
|
279
|
+
|
280
|
+
def stop(self):
|
281
|
+
"""Stop telemetry collection and flush data."""
|
282
|
+
self.telemetry.flush()
|
283
|
+
|
284
|
+
def trace_agent_execution(self, agent_name: str, **attributes):
|
285
|
+
"""Compatibility method for agent execution tracking."""
|
286
|
+
from contextlib import contextmanager
|
287
|
+
|
288
|
+
@contextmanager
|
289
|
+
def _trace():
|
290
|
+
try:
|
291
|
+
yield None
|
292
|
+
self.telemetry.track_agent_execution(agent_name, success=True)
|
293
|
+
except Exception:
|
294
|
+
self.telemetry.track_agent_execution(agent_name, success=False)
|
295
|
+
raise
|
296
|
+
|
297
|
+
return _trace()
|
298
|
+
|
299
|
+
def trace_task_execution(self, task_name: str, agent_name: str = None, **attributes):
|
300
|
+
"""Compatibility method for task execution tracking."""
|
301
|
+
from contextlib import contextmanager
|
302
|
+
|
303
|
+
@contextmanager
|
304
|
+
def _trace():
|
305
|
+
try:
|
306
|
+
yield None
|
307
|
+
self.telemetry.track_task_completion(task_name, success=True)
|
308
|
+
except Exception:
|
309
|
+
self.telemetry.track_task_completion(task_name, success=False)
|
310
|
+
raise
|
311
|
+
|
312
|
+
return _trace()
|
313
|
+
|
314
|
+
def trace_tool_call(self, tool_name: str, **attributes):
|
315
|
+
"""Compatibility method for tool call tracking."""
|
316
|
+
from contextlib import contextmanager
|
317
|
+
|
318
|
+
@contextmanager
|
319
|
+
def _trace():
|
320
|
+
try:
|
321
|
+
yield None
|
322
|
+
self.telemetry.track_tool_usage(tool_name, success=True)
|
323
|
+
except Exception:
|
324
|
+
self.telemetry.track_tool_usage(tool_name, success=False)
|
325
|
+
raise
|
326
|
+
|
327
|
+
return _trace()
|
328
|
+
|
329
|
+
def trace_llm_call(self, model: str = None, **attributes):
|
330
|
+
"""Compatibility method for LLM call tracking."""
|
331
|
+
from contextlib import contextmanager
|
332
|
+
|
333
|
+
@contextmanager
|
334
|
+
def _trace():
|
335
|
+
# We don't track LLM calls in minimal telemetry
|
336
|
+
yield None
|
337
|
+
|
338
|
+
return _trace()
|
339
|
+
|
340
|
+
def record_tokens(self, prompt_tokens: int, completion_tokens: int, model: str = None):
|
341
|
+
"""Compatibility method - we don't track token usage."""
|
342
|
+
pass
|
343
|
+
|
344
|
+
def record_cost(self, cost: float, model: str = None):
|
345
|
+
"""Compatibility method - we don't track costs."""
|
346
|
+
pass
|
347
|
+
|
348
|
+
def get_metrics(self) -> Dict[str, Any]:
|
349
|
+
"""Get current metrics."""
|
350
|
+
return self.telemetry.get_metrics()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: praisonaiagents
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.103
|
4
4
|
Summary: Praison AI agents for completing complex tasks with Self Reflection Agents
|
5
5
|
Author: Mervin Praison
|
6
6
|
Requires-Python: >=3.10
|
@@ -8,13 +8,14 @@ Requires-Dist: pydantic
|
|
8
8
|
Requires-Dist: rich
|
9
9
|
Requires-Dist: openai
|
10
10
|
Requires-Dist: mcp>=1.6.0
|
11
|
+
Requires-Dist: posthog>=4.0.0
|
11
12
|
Provides-Extra: mcp
|
12
13
|
Requires-Dist: mcp>=1.6.0; extra == "mcp"
|
13
14
|
Requires-Dist: fastapi>=0.115.0; extra == "mcp"
|
14
15
|
Requires-Dist: uvicorn>=0.34.0; extra == "mcp"
|
15
16
|
Provides-Extra: memory
|
16
17
|
Requires-Dist: chromadb>=1.0.0; extra == "memory"
|
17
|
-
Requires-Dist: litellm>=1.
|
18
|
+
Requires-Dist: litellm>=1.72.0; extra == "memory"
|
18
19
|
Provides-Extra: knowledge
|
19
20
|
Requires-Dist: mem0ai>=0.1.0; extra == "knowledge"
|
20
21
|
Requires-Dist: chromadb>=1.0.0; extra == "knowledge"
|
@@ -24,11 +25,13 @@ Provides-Extra: graph
|
|
24
25
|
Requires-Dist: mem0ai[graph]>=0.1.0; extra == "graph"
|
25
26
|
Requires-Dist: chromadb>=1.0.0; extra == "graph"
|
26
27
|
Provides-Extra: llm
|
27
|
-
Requires-Dist: litellm>=1.
|
28
|
+
Requires-Dist: litellm>=1.72.0; extra == "llm"
|
28
29
|
Requires-Dist: pydantic>=2.4.2; extra == "llm"
|
29
30
|
Provides-Extra: api
|
30
31
|
Requires-Dist: fastapi>=0.115.0; extra == "api"
|
31
32
|
Requires-Dist: uvicorn>=0.34.0; extra == "api"
|
33
|
+
Provides-Extra: telemetry
|
34
|
+
Requires-Dist: posthog>=4.0.0; extra == "telemetry"
|
32
35
|
Provides-Extra: all
|
33
36
|
Requires-Dist: praisonaiagents[memory]; extra == "all"
|
34
37
|
Requires-Dist: praisonaiagents[knowledge]; extra == "all"
|
@@ -36,3 +39,4 @@ Requires-Dist: praisonaiagents[graph]; extra == "all"
|
|
36
39
|
Requires-Dist: praisonaiagents[llm]; extra == "all"
|
37
40
|
Requires-Dist: praisonaiagents[mcp]; extra == "all"
|
38
41
|
Requires-Dist: praisonaiagents[api]; extra == "all"
|
42
|
+
Requires-Dist: praisonaiagents[telemetry]; extra == "all"
|
@@ -1,4 +1,4 @@
|
|
1
|
-
praisonaiagents/__init__.py,sha256=
|
1
|
+
praisonaiagents/__init__.py,sha256=10wExtoVkZ31OwxoKjj9gtWq2uvC05dYBommV2Eii4M,2769
|
2
2
|
praisonaiagents/approval.py,sha256=UJ4OhfihpFGR5CAaMphqpSvqdZCHi5w2MGw1MByZ1FQ,9813
|
3
3
|
praisonaiagents/main.py,sha256=_-XE7_Y7ChvtLQMivfNFrrnAhv4wSSDhH9WJMWlkS0w,16315
|
4
4
|
praisonaiagents/session.py,sha256=CI-ffCiOfmgB-1zFFik9daKCB5Sm41Q9ZOaq1-oSLW8,9250
|
@@ -14,17 +14,20 @@ praisonaiagents/guardrails/llm_guardrail.py,sha256=MTTqmYDdZX-18QN9T17T5P_6H2qnV
|
|
14
14
|
praisonaiagents/knowledge/__init__.py,sha256=xL1Eh-a3xsHyIcU4foOWF-JdWYIYBALJH9bge0Ujuto,246
|
15
15
|
praisonaiagents/knowledge/chunking.py,sha256=G6wyHa7_8V0_7VpnrrUXbEmUmptlT16ISJYaxmkSgmU,7678
|
16
16
|
praisonaiagents/knowledge/knowledge.py,sha256=OKPar-XGyAp1ndmbOOdCgqFnTCqpOThYVSIZRxZyP58,15683
|
17
|
-
praisonaiagents/llm/__init__.py,sha256=
|
18
|
-
praisonaiagents/llm/llm.py,sha256=
|
17
|
+
praisonaiagents/llm/__init__.py,sha256=bSywIHBHH0YUf4hSx-FmFXkRv2g1Rlhuk-gjoImE8j8,925
|
18
|
+
praisonaiagents/llm/llm.py,sha256=4AyXTgcolemKf4kHQOwQAIE4MJ0z-CuGs9F7UkHbyfE,98385
|
19
19
|
praisonaiagents/mcp/__init__.py,sha256=ibbqe3_7XB7VrIcUcetkZiUZS1fTVvyMy_AqCSFG8qc,240
|
20
20
|
praisonaiagents/mcp/mcp.py,sha256=_gfp8hrSVT9aPqEDDfU8MiCdg0-3dVQpEQUE6AbrJlo,17243
|
21
21
|
praisonaiagents/mcp/mcp_sse.py,sha256=DLh3F_aoVRM1X-7hgIOWOw4FQ1nGmn9YNbQTesykzn4,6792
|
22
22
|
praisonaiagents/memory/__init__.py,sha256=aEFdhgtTqDdMhc_JCWM-f4XI9cZIj7Wz5g_MUa-0amg,397
|
23
|
-
praisonaiagents/memory/memory.py,sha256=
|
23
|
+
praisonaiagents/memory/memory.py,sha256=eYXVvuXrvt4LaEJ-AAbAiwpFUCuS5LH5F7Z0cBW5_gQ,42186
|
24
24
|
praisonaiagents/process/__init__.py,sha256=lkYbL7Hn5a0ldvJtkdH23vfIIZLIcanK-65C0MwaorY,52
|
25
25
|
praisonaiagents/process/process.py,sha256=gxhMXG3s4CzaREyuwE5zxCMx2Wp_b_Wd53tDfkj8Qk8,66567
|
26
26
|
praisonaiagents/task/__init__.py,sha256=VL5hXVmyGjINb34AalxpBMl-YW9m5EDcRkMTKkSSl7c,80
|
27
27
|
praisonaiagents/task/task.py,sha256=imqJ8wzZzVyUSym2EyF2tC-vAsV1UdfI_P3YM5mqAiw,20786
|
28
|
+
praisonaiagents/telemetry/__init__.py,sha256=5iAOrj_N_cKMmh2ltWGYs3PfOYt_jcwUoElW8fTAIsc,3062
|
29
|
+
praisonaiagents/telemetry/integration.py,sha256=36vvYac8tW92YzQYbBeKWKM8JC9IiizlxhUy3AFqPlA,8667
|
30
|
+
praisonaiagents/telemetry/telemetry.py,sha256=T2Mv_iOXYbL-C3CZW5EEEs7N0dUk1S2xrD1FQjVaxmc,11064
|
28
31
|
praisonaiagents/tools/README.md,sha256=bIQGTSqQbC8l_UvTAnKbnh1TxrybSFGbCqxnhvDwkE4,4450
|
29
32
|
praisonaiagents/tools/__init__.py,sha256=Rrgi7_3-yLHpfBB81WUi0-wD_wb_BsukwHVdjDYAF-0,9316
|
30
33
|
praisonaiagents/tools/arxiv_tools.py,sha256=1stb31zTjLTon4jCnpZG5de9rKc9QWgC0leLegvPXWo,10528
|
@@ -48,7 +51,7 @@ praisonaiagents/tools/xml_tools.py,sha256=iYTMBEk5l3L3ryQ1fkUnNVYK-Nnua2Kx2S0dxN
|
|
48
51
|
praisonaiagents/tools/yaml_tools.py,sha256=uogAZrhXV9O7xvspAtcTfpKSQYL2nlOTvCQXN94-G9A,14215
|
49
52
|
praisonaiagents/tools/yfinance_tools.py,sha256=s2PBj_1v7oQnOobo2fDbQBACEHl61ftG4beG6Z979ZE,8529
|
50
53
|
praisonaiagents/tools/train/data/generatecot.py,sha256=H6bNh-E2hqL5MW6kX3hqZ05g9ETKN2-kudSjiuU_SD8,19403
|
51
|
-
praisonaiagents-0.0.
|
52
|
-
praisonaiagents-0.0.
|
53
|
-
praisonaiagents-0.0.
|
54
|
-
praisonaiagents-0.0.
|
54
|
+
praisonaiagents-0.0.103.dist-info/METADATA,sha256=QPwe9pWnpP519hGH179MF5Vt_Jt0uD-9F-O5D5IhK84,1669
|
55
|
+
praisonaiagents-0.0.103.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
56
|
+
praisonaiagents-0.0.103.dist-info/top_level.txt,sha256=_HsRddrJ23iDx5TTqVUVvXG2HeHBL5voshncAMDGjtA,16
|
57
|
+
praisonaiagents-0.0.103.dist-info/RECORD,,
|
File without changes
|
File without changes
|