daita-agents 0.1.0__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.
Potentially problematic release.
This version of daita-agents might be problematic. Click here for more details.
- daita/__init__.py +208 -0
- daita/agents/__init__.py +33 -0
- daita/agents/base.py +722 -0
- daita/agents/substrate.py +895 -0
- daita/cli/__init__.py +145 -0
- daita/cli/__main__.py +7 -0
- daita/cli/ascii_art.py +44 -0
- daita/cli/core/__init__.py +0 -0
- daita/cli/core/create.py +254 -0
- daita/cli/core/deploy.py +473 -0
- daita/cli/core/deployments.py +309 -0
- daita/cli/core/import_detector.py +219 -0
- daita/cli/core/init.py +382 -0
- daita/cli/core/logs.py +239 -0
- daita/cli/core/managed_deploy.py +709 -0
- daita/cli/core/run.py +648 -0
- daita/cli/core/status.py +421 -0
- daita/cli/core/test.py +239 -0
- daita/cli/core/webhooks.py +172 -0
- daita/cli/main.py +588 -0
- daita/cli/utils.py +541 -0
- daita/config/__init__.py +62 -0
- daita/config/base.py +159 -0
- daita/config/settings.py +184 -0
- daita/core/__init__.py +262 -0
- daita/core/decision_tracing.py +701 -0
- daita/core/exceptions.py +480 -0
- daita/core/focus.py +251 -0
- daita/core/interfaces.py +76 -0
- daita/core/plugin_tracing.py +550 -0
- daita/core/relay.py +695 -0
- daita/core/reliability.py +381 -0
- daita/core/scaling.py +444 -0
- daita/core/tools.py +402 -0
- daita/core/tracing.py +770 -0
- daita/core/workflow.py +1084 -0
- daita/display/__init__.py +1 -0
- daita/display/console.py +160 -0
- daita/execution/__init__.py +58 -0
- daita/execution/client.py +856 -0
- daita/execution/exceptions.py +92 -0
- daita/execution/models.py +317 -0
- daita/llm/__init__.py +60 -0
- daita/llm/anthropic.py +166 -0
- daita/llm/base.py +373 -0
- daita/llm/factory.py +101 -0
- daita/llm/gemini.py +152 -0
- daita/llm/grok.py +114 -0
- daita/llm/mock.py +135 -0
- daita/llm/openai.py +109 -0
- daita/plugins/__init__.py +141 -0
- daita/plugins/base.py +37 -0
- daita/plugins/base_db.py +167 -0
- daita/plugins/elasticsearch.py +844 -0
- daita/plugins/mcp.py +481 -0
- daita/plugins/mongodb.py +510 -0
- daita/plugins/mysql.py +351 -0
- daita/plugins/postgresql.py +331 -0
- daita/plugins/redis_messaging.py +500 -0
- daita/plugins/rest.py +529 -0
- daita/plugins/s3.py +761 -0
- daita/plugins/slack.py +729 -0
- daita/utils/__init__.py +18 -0
- daita_agents-0.1.0.dist-info/METADATA +350 -0
- daita_agents-0.1.0.dist-info/RECORD +69 -0
- daita_agents-0.1.0.dist-info/WHEEL +5 -0
- daita_agents-0.1.0.dist-info/entry_points.txt +2 -0
- daita_agents-0.1.0.dist-info/licenses/LICENSE +56 -0
- daita_agents-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Plugin Tracing Integration for Daita Agents - Fixed Complete Version
|
|
3
|
+
|
|
4
|
+
Simplified automatic tracing for all plugin operations:
|
|
5
|
+
- Database queries (PostgreSQL, MySQL, MongoDB)
|
|
6
|
+
- API calls (REST)
|
|
7
|
+
- Cloud storage operations (S3)
|
|
8
|
+
- File operations
|
|
9
|
+
- Custom plugin executions
|
|
10
|
+
|
|
11
|
+
FIXED ISSUES:
|
|
12
|
+
- Completed all missing function implementations
|
|
13
|
+
- Fixed async/sync method detection
|
|
14
|
+
- Improved error handling and logging
|
|
15
|
+
- Added proper metadata extraction
|
|
16
|
+
- Fixed circular import issues
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import asyncio
|
|
20
|
+
import logging
|
|
21
|
+
import time
|
|
22
|
+
import functools
|
|
23
|
+
from typing import Dict, Any, Optional, Callable, List
|
|
24
|
+
from contextlib import asynccontextmanager
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
class TracedPlugin:
|
|
29
|
+
"""
|
|
30
|
+
Complete traced plugin wrapper that automatically captures all operations.
|
|
31
|
+
|
|
32
|
+
Transparently adds tracing to any plugin without changing the interface.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, original_plugin: Any, plugin_name: str):
|
|
36
|
+
"""
|
|
37
|
+
Initialize traced plugin wrapper.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
original_plugin: The original plugin instance
|
|
41
|
+
plugin_name: Name of plugin (postgresql, rest, etc.)
|
|
42
|
+
"""
|
|
43
|
+
self._original = original_plugin
|
|
44
|
+
self._plugin_name = plugin_name
|
|
45
|
+
|
|
46
|
+
# Import here to avoid circular imports
|
|
47
|
+
from .tracing import get_trace_manager
|
|
48
|
+
self._trace_manager = get_trace_manager()
|
|
49
|
+
|
|
50
|
+
# Copy over all attributes from original plugin
|
|
51
|
+
for attr_name in dir(original_plugin):
|
|
52
|
+
if not attr_name.startswith('_') and not hasattr(self, attr_name):
|
|
53
|
+
attr = getattr(original_plugin, attr_name)
|
|
54
|
+
if callable(attr):
|
|
55
|
+
# Wrap callable methods with tracing
|
|
56
|
+
setattr(self, attr_name, self._wrap_method(attr, attr_name))
|
|
57
|
+
else:
|
|
58
|
+
# Copy non-callable attributes directly
|
|
59
|
+
setattr(self, attr_name, attr)
|
|
60
|
+
|
|
61
|
+
logger.debug(f"Created traced {plugin_name} plugin")
|
|
62
|
+
|
|
63
|
+
def _wrap_method(self, method: Callable, method_name: str) -> Callable:
|
|
64
|
+
"""Wrap a plugin method with automatic tracing."""
|
|
65
|
+
|
|
66
|
+
if asyncio.iscoroutinefunction(method):
|
|
67
|
+
@functools.wraps(method)
|
|
68
|
+
async def async_traced_method(*args, **kwargs):
|
|
69
|
+
return await self._trace_async_method(method, method_name, args, kwargs)
|
|
70
|
+
return async_traced_method
|
|
71
|
+
else:
|
|
72
|
+
@functools.wraps(method)
|
|
73
|
+
def sync_traced_method(*args, **kwargs):
|
|
74
|
+
return self._trace_sync_method(method, method_name, args, kwargs)
|
|
75
|
+
return sync_traced_method
|
|
76
|
+
|
|
77
|
+
async def _trace_async_method(self, method: Callable, method_name: str, args: tuple, kwargs: dict):
|
|
78
|
+
"""Trace an async plugin method."""
|
|
79
|
+
from .tracing import TraceType, TraceStatus
|
|
80
|
+
|
|
81
|
+
async with self._trace_manager.span(
|
|
82
|
+
operation_name=f"{self._plugin_name}_{method_name}",
|
|
83
|
+
trace_type=TraceType.TOOL_EXECUTION,
|
|
84
|
+
tool_name=self._plugin_name,
|
|
85
|
+
tool_operation=method_name,
|
|
86
|
+
input_data=self._prepare_input_data(args, kwargs)
|
|
87
|
+
) as span_id:
|
|
88
|
+
|
|
89
|
+
start_time = time.time()
|
|
90
|
+
try:
|
|
91
|
+
result = await method(*args, **kwargs)
|
|
92
|
+
|
|
93
|
+
# Extract metadata from result
|
|
94
|
+
metadata = self._extract_result_metadata(result)
|
|
95
|
+
|
|
96
|
+
# Update span with success metadata
|
|
97
|
+
self._trace_manager.end_span(
|
|
98
|
+
span_id=span_id,
|
|
99
|
+
status=TraceStatus.SUCCESS,
|
|
100
|
+
output_data=result,
|
|
101
|
+
duration_ms=(time.time() - start_time) * 1000,
|
|
102
|
+
**metadata
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
return result
|
|
106
|
+
|
|
107
|
+
except Exception as e:
|
|
108
|
+
# Update span with error metadata
|
|
109
|
+
self._trace_manager.end_span(
|
|
110
|
+
span_id=span_id,
|
|
111
|
+
status=TraceStatus.ERROR,
|
|
112
|
+
error_message=str(e),
|
|
113
|
+
duration_ms=(time.time() - start_time) * 1000
|
|
114
|
+
)
|
|
115
|
+
raise
|
|
116
|
+
|
|
117
|
+
def _trace_sync_method(self, method: Callable, method_name: str, args: tuple, kwargs: dict):
|
|
118
|
+
"""Trace a sync plugin method."""
|
|
119
|
+
from .tracing import TraceType, TraceStatus
|
|
120
|
+
|
|
121
|
+
# For sync methods, we need to create a simple span without async context
|
|
122
|
+
span_id = self._trace_manager.start_span(
|
|
123
|
+
operation_name=f"{self._plugin_name}_{method_name}",
|
|
124
|
+
trace_type=TraceType.TOOL_EXECUTION,
|
|
125
|
+
tool_name=self._plugin_name,
|
|
126
|
+
tool_operation=method_name,
|
|
127
|
+
input_data=self._prepare_input_data(args, kwargs)
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
start_time = time.time()
|
|
131
|
+
try:
|
|
132
|
+
result = method(*args, **kwargs)
|
|
133
|
+
|
|
134
|
+
# Extract metadata from result
|
|
135
|
+
metadata = self._extract_result_metadata(result)
|
|
136
|
+
|
|
137
|
+
# Update span with success metadata
|
|
138
|
+
self._trace_manager.end_span(
|
|
139
|
+
span_id=span_id,
|
|
140
|
+
status=TraceStatus.SUCCESS,
|
|
141
|
+
output_data=result,
|
|
142
|
+
duration_ms=(time.time() - start_time) * 1000,
|
|
143
|
+
**metadata
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
return result
|
|
147
|
+
|
|
148
|
+
except Exception as e:
|
|
149
|
+
# Update span with error metadata
|
|
150
|
+
self._trace_manager.end_span(
|
|
151
|
+
span_id=span_id,
|
|
152
|
+
status=TraceStatus.ERROR,
|
|
153
|
+
error_message=str(e),
|
|
154
|
+
duration_ms=(time.time() - start_time) * 1000
|
|
155
|
+
)
|
|
156
|
+
raise
|
|
157
|
+
|
|
158
|
+
def _prepare_input_data(self, args: tuple, kwargs: dict) -> Dict[str, Any]:
|
|
159
|
+
"""Prepare input data for tracing (sanitized)."""
|
|
160
|
+
try:
|
|
161
|
+
# Sanitize sensitive data
|
|
162
|
+
safe_kwargs = {}
|
|
163
|
+
for key, value in kwargs.items():
|
|
164
|
+
if key.lower() in ['password', 'token', 'secret', 'key', 'auth']:
|
|
165
|
+
safe_kwargs[key] = '[REDACTED]'
|
|
166
|
+
elif isinstance(value, str) and len(value) > 200:
|
|
167
|
+
safe_kwargs[key] = value[:200] + '...'
|
|
168
|
+
else:
|
|
169
|
+
safe_kwargs[key] = value
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
"args_count": len(args),
|
|
173
|
+
"kwargs": safe_kwargs,
|
|
174
|
+
"method_type": "async" if asyncio.iscoroutinefunction(args[0] if args else None) else "sync"
|
|
175
|
+
}
|
|
176
|
+
except Exception as e:
|
|
177
|
+
return {"input_error": str(e)}
|
|
178
|
+
|
|
179
|
+
def _extract_result_metadata(self, result: Any) -> Dict[str, Any]:
|
|
180
|
+
"""Extract metadata from operation result."""
|
|
181
|
+
metadata = {
|
|
182
|
+
"result_type": type(result).__name__,
|
|
183
|
+
"success": True
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
# Count results for database/collections
|
|
188
|
+
if hasattr(result, '__len__') and not isinstance(result, str):
|
|
189
|
+
metadata["result_count"] = len(result)
|
|
190
|
+
elif isinstance(result, (list, tuple)):
|
|
191
|
+
metadata["result_count"] = len(result)
|
|
192
|
+
|
|
193
|
+
# API response codes
|
|
194
|
+
if hasattr(result, 'status_code'):
|
|
195
|
+
metadata["status_code"] = result.status_code
|
|
196
|
+
metadata["success"] = 200 <= result.status_code < 300
|
|
197
|
+
|
|
198
|
+
# Content size for string responses
|
|
199
|
+
if isinstance(result, str):
|
|
200
|
+
metadata["content_size"] = len(result)
|
|
201
|
+
elif hasattr(result, 'text'):
|
|
202
|
+
metadata["content_size"] = len(result.text)
|
|
203
|
+
elif hasattr(result, 'content'):
|
|
204
|
+
metadata["content_size"] = len(str(result.content))
|
|
205
|
+
|
|
206
|
+
# Database-specific metadata
|
|
207
|
+
if hasattr(result, 'rowcount'):
|
|
208
|
+
metadata["rows_affected"] = result.rowcount
|
|
209
|
+
elif hasattr(result, 'inserted_id'):
|
|
210
|
+
metadata["inserted_id"] = str(result.inserted_id)
|
|
211
|
+
|
|
212
|
+
except Exception as e:
|
|
213
|
+
metadata["metadata_error"] = str(e)
|
|
214
|
+
|
|
215
|
+
return metadata
|
|
216
|
+
|
|
217
|
+
# Delegate all other attributes to the original plugin
|
|
218
|
+
def __getattr__(self, name):
|
|
219
|
+
return getattr(self._original, name)
|
|
220
|
+
|
|
221
|
+
def __setattr__(self, name, value):
|
|
222
|
+
if name.startswith('_') or name in ['_original', '_plugin_name', '_trace_manager']:
|
|
223
|
+
super().__setattr__(name, value)
|
|
224
|
+
else:
|
|
225
|
+
setattr(self._original, name, value)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# Simple plugin wrapper function
|
|
229
|
+
|
|
230
|
+
def trace_plugin(plugin: Any, plugin_name: str = None) -> Any:
|
|
231
|
+
"""
|
|
232
|
+
Automatically wrap any plugin with tracing capabilities.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
plugin: Original plugin instance
|
|
236
|
+
plugin_name: Plugin name (auto-detected if not provided)
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
Traced plugin with automatic operation tracking
|
|
240
|
+
"""
|
|
241
|
+
if plugin is None:
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
# Auto-detect plugin name if not provided
|
|
245
|
+
if not plugin_name:
|
|
246
|
+
plugin_name = plugin.__class__.__name__.lower().replace('plugin', '')
|
|
247
|
+
if not plugin_name:
|
|
248
|
+
plugin_name = 'unknown_plugin'
|
|
249
|
+
|
|
250
|
+
return TracedPlugin(plugin, plugin_name)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
# Traced plugin factory functions
|
|
254
|
+
|
|
255
|
+
def traced_postgresql(**kwargs):
|
|
256
|
+
"""Create PostgreSQL plugin with automatic tracing."""
|
|
257
|
+
try:
|
|
258
|
+
from ..plugins.postgresql import postgresql
|
|
259
|
+
plugin = postgresql(**kwargs)
|
|
260
|
+
return trace_plugin(plugin, "postgresql")
|
|
261
|
+
except ImportError as e:
|
|
262
|
+
logger.error(f"PostgreSQL plugin not available: {e}")
|
|
263
|
+
return None
|
|
264
|
+
except Exception as e:
|
|
265
|
+
logger.error(f"Failed to create PostgreSQL plugin: {e}")
|
|
266
|
+
return None
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def traced_mysql(**kwargs):
|
|
270
|
+
"""Create MySQL plugin with automatic tracing."""
|
|
271
|
+
try:
|
|
272
|
+
from ..plugins.mysql import mysql
|
|
273
|
+
plugin = mysql(**kwargs)
|
|
274
|
+
return trace_plugin(plugin, "mysql")
|
|
275
|
+
except ImportError as e:
|
|
276
|
+
logger.error(f"MySQL plugin not available: {e}")
|
|
277
|
+
return None
|
|
278
|
+
except Exception as e:
|
|
279
|
+
logger.error(f"Failed to create MySQL plugin: {e}")
|
|
280
|
+
return None
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def traced_mongodb(**kwargs):
|
|
284
|
+
"""Create MongoDB plugin with automatic tracing."""
|
|
285
|
+
try:
|
|
286
|
+
from ..plugins.mongodb import mongodb
|
|
287
|
+
plugin = mongodb(**kwargs)
|
|
288
|
+
return trace_plugin(plugin, "mongodb")
|
|
289
|
+
except ImportError as e:
|
|
290
|
+
logger.error(f"MongoDB plugin not available: {e}")
|
|
291
|
+
return None
|
|
292
|
+
except Exception as e:
|
|
293
|
+
logger.error(f"Failed to create MongoDB plugin: {e}")
|
|
294
|
+
return None
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def traced_rest(**kwargs):
|
|
298
|
+
"""Create REST API plugin with automatic tracing."""
|
|
299
|
+
try:
|
|
300
|
+
from ..plugins.rest import rest
|
|
301
|
+
plugin = rest(**kwargs)
|
|
302
|
+
return trace_plugin(plugin, "rest")
|
|
303
|
+
except ImportError as e:
|
|
304
|
+
logger.error(f"REST plugin not available: {e}")
|
|
305
|
+
return None
|
|
306
|
+
except Exception as e:
|
|
307
|
+
logger.error(f"Failed to create REST plugin: {e}")
|
|
308
|
+
return None
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def traced_s3(**kwargs):
|
|
312
|
+
"""Create S3 plugin with automatic tracing."""
|
|
313
|
+
try:
|
|
314
|
+
from ..plugins.s3 import s3
|
|
315
|
+
plugin = s3(**kwargs)
|
|
316
|
+
return trace_plugin(plugin, "s3")
|
|
317
|
+
except ImportError as e:
|
|
318
|
+
logger.error(f"S3 plugin not available: {e}")
|
|
319
|
+
return None
|
|
320
|
+
except Exception as e:
|
|
321
|
+
logger.error(f"Failed to create S3 plugin: {e}")
|
|
322
|
+
return None
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def traced_slack(**kwargs):
|
|
326
|
+
"""Create Slack plugin with automatic tracing."""
|
|
327
|
+
try:
|
|
328
|
+
from ..plugins.slack import slack
|
|
329
|
+
plugin = slack(**kwargs)
|
|
330
|
+
return trace_plugin(plugin, "slack")
|
|
331
|
+
except ImportError as e:
|
|
332
|
+
logger.error(f"Slack plugin not available: {e}")
|
|
333
|
+
return None
|
|
334
|
+
except Exception as e:
|
|
335
|
+
logger.error(f"Failed to create Slack plugin: {e}")
|
|
336
|
+
return None
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def traced_elasticsearch(**kwargs):
|
|
340
|
+
"""Create Elasticsearch plugin with automatic tracing."""
|
|
341
|
+
try:
|
|
342
|
+
from ..plugins.elasticsearch import elasticsearch
|
|
343
|
+
plugin = elasticsearch(**kwargs)
|
|
344
|
+
return trace_plugin(plugin, "elasticsearch")
|
|
345
|
+
except ImportError as e:
|
|
346
|
+
logger.error(f"Elasticsearch plugin not available: {e}")
|
|
347
|
+
return None
|
|
348
|
+
except Exception as e:
|
|
349
|
+
logger.error(f"Failed to create Elasticsearch plugin: {e}")
|
|
350
|
+
return None
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
# Context managers for batch operations
|
|
354
|
+
|
|
355
|
+
@asynccontextmanager
|
|
356
|
+
async def traced_transaction(db_plugin, operation_name: str = "transaction"):
|
|
357
|
+
"""
|
|
358
|
+
Context manager for tracing database transactions.
|
|
359
|
+
|
|
360
|
+
Usage:
|
|
361
|
+
async with traced_transaction(db, "user_registration"):
|
|
362
|
+
await db.query("INSERT INTO users ...")
|
|
363
|
+
await db.query("INSERT INTO profiles ...")
|
|
364
|
+
"""
|
|
365
|
+
from .tracing import get_trace_manager, TraceType
|
|
366
|
+
trace_manager = get_trace_manager()
|
|
367
|
+
|
|
368
|
+
async with trace_manager.span(
|
|
369
|
+
operation_name=f"transaction_{operation_name}",
|
|
370
|
+
trace_type=TraceType.TOOL_EXECUTION,
|
|
371
|
+
tool_name=getattr(db_plugin, '_plugin_name', 'database'),
|
|
372
|
+
tool_operation=operation_name,
|
|
373
|
+
transaction_type="database"
|
|
374
|
+
):
|
|
375
|
+
try:
|
|
376
|
+
yield db_plugin
|
|
377
|
+
except Exception as e:
|
|
378
|
+
logger.error(f"Transaction {operation_name} failed: {e}")
|
|
379
|
+
raise
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
@asynccontextmanager
|
|
383
|
+
async def traced_api_batch(api_plugin, batch_name: str = "api_batch"):
|
|
384
|
+
"""
|
|
385
|
+
Context manager for tracing API batch operations.
|
|
386
|
+
|
|
387
|
+
Usage:
|
|
388
|
+
async with traced_api_batch(api, "user_sync"):
|
|
389
|
+
user_data = await api.get("/users/123")
|
|
390
|
+
await api.post("/sync", json=user_data)
|
|
391
|
+
"""
|
|
392
|
+
from .tracing import get_trace_manager, TraceType
|
|
393
|
+
trace_manager = get_trace_manager()
|
|
394
|
+
|
|
395
|
+
async with trace_manager.span(
|
|
396
|
+
operation_name=f"api_batch_{batch_name}",
|
|
397
|
+
trace_type=TraceType.TOOL_EXECUTION,
|
|
398
|
+
tool_name=getattr(api_plugin, '_plugin_name', 'api'),
|
|
399
|
+
tool_operation=batch_name,
|
|
400
|
+
batch_type="api"
|
|
401
|
+
):
|
|
402
|
+
try:
|
|
403
|
+
yield api_plugin
|
|
404
|
+
except Exception as e:
|
|
405
|
+
logger.error(f"API batch {batch_name} failed: {e}")
|
|
406
|
+
raise
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
# Utility functions
|
|
410
|
+
|
|
411
|
+
def get_plugin_traces(plugin_name: Optional[str] = None, limit: int = 20) -> List[Dict[str, Any]]:
|
|
412
|
+
"""Get recent plugin execution traces."""
|
|
413
|
+
try:
|
|
414
|
+
from .tracing import get_trace_manager
|
|
415
|
+
trace_manager = get_trace_manager()
|
|
416
|
+
operations = trace_manager.get_recent_operations(limit=limit * 2)
|
|
417
|
+
|
|
418
|
+
# Filter for tool executions
|
|
419
|
+
tool_ops = [
|
|
420
|
+
op for op in operations
|
|
421
|
+
if op.get('type') == 'tool_execution'
|
|
422
|
+
]
|
|
423
|
+
|
|
424
|
+
# Filter by plugin name if specified
|
|
425
|
+
if plugin_name:
|
|
426
|
+
tool_ops = [
|
|
427
|
+
op for op in tool_ops
|
|
428
|
+
if op.get('metadata', {}).get('tool_name') == plugin_name
|
|
429
|
+
]
|
|
430
|
+
|
|
431
|
+
return tool_ops[:limit]
|
|
432
|
+
except Exception as e:
|
|
433
|
+
logger.error(f"Error getting plugin traces: {e}")
|
|
434
|
+
return []
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def get_plugin_stats(plugin_name: Optional[str] = None) -> Dict[str, Any]:
|
|
438
|
+
"""Get comprehensive plugin usage statistics."""
|
|
439
|
+
try:
|
|
440
|
+
traces = get_plugin_traces(plugin_name, limit=50)
|
|
441
|
+
|
|
442
|
+
if not traces:
|
|
443
|
+
return {"total_operations": 0, "success_rate": 0}
|
|
444
|
+
|
|
445
|
+
total_ops = len(traces)
|
|
446
|
+
successful_ops = len([t for t in traces if t.get('status') == 'success'])
|
|
447
|
+
|
|
448
|
+
# Calculate average latency
|
|
449
|
+
latencies = [t.get('duration_ms', 0) for t in traces if t.get('duration_ms')]
|
|
450
|
+
avg_latency = sum(latencies) / len(latencies) if latencies else 0
|
|
451
|
+
|
|
452
|
+
# Operation distribution
|
|
453
|
+
operations = {}
|
|
454
|
+
for trace in traces:
|
|
455
|
+
op_name = trace.get('metadata', {}).get('tool_operation', 'unknown')
|
|
456
|
+
operations[op_name] = operations.get(op_name, 0) + 1
|
|
457
|
+
|
|
458
|
+
# Error distribution
|
|
459
|
+
errors = {}
|
|
460
|
+
for trace in traces:
|
|
461
|
+
if trace.get('status') == 'error':
|
|
462
|
+
error_msg = trace.get('error', 'Unknown error')
|
|
463
|
+
# Categorize errors
|
|
464
|
+
if 'timeout' in error_msg.lower():
|
|
465
|
+
error_type = 'timeout'
|
|
466
|
+
elif 'connection' in error_msg.lower():
|
|
467
|
+
error_type = 'connection'
|
|
468
|
+
elif 'permission' in error_msg.lower() or 'auth' in error_msg.lower():
|
|
469
|
+
error_type = 'permission'
|
|
470
|
+
else:
|
|
471
|
+
error_type = 'other'
|
|
472
|
+
errors[error_type] = errors.get(error_type, 0) + 1
|
|
473
|
+
|
|
474
|
+
return {
|
|
475
|
+
"plugin_name": plugin_name,
|
|
476
|
+
"total_operations": total_ops,
|
|
477
|
+
"successful_operations": successful_ops,
|
|
478
|
+
"failed_operations": total_ops - successful_ops,
|
|
479
|
+
"success_rate": successful_ops / total_ops if total_ops > 0 else 0,
|
|
480
|
+
"average_latency_ms": avg_latency,
|
|
481
|
+
"operation_distribution": operations,
|
|
482
|
+
"error_distribution": errors,
|
|
483
|
+
"total_latency_ms": sum(latencies)
|
|
484
|
+
}
|
|
485
|
+
except Exception as e:
|
|
486
|
+
logger.error(f"Error getting plugin stats: {e}")
|
|
487
|
+
return {"total_operations": 0, "success_rate": 0}
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
# Advanced tracing helpers
|
|
491
|
+
|
|
492
|
+
def create_custom_traced_plugin(plugin_instance: Any, plugin_name: str, custom_metadata: Dict[str, Any] = None) -> TracedPlugin:
|
|
493
|
+
"""
|
|
494
|
+
Create a custom traced plugin with additional metadata.
|
|
495
|
+
|
|
496
|
+
Args:
|
|
497
|
+
plugin_instance: The plugin to wrap
|
|
498
|
+
plugin_name: Name for tracing
|
|
499
|
+
custom_metadata: Additional metadata to include in traces
|
|
500
|
+
|
|
501
|
+
Returns:
|
|
502
|
+
TracedPlugin instance with custom metadata
|
|
503
|
+
"""
|
|
504
|
+
traced_plugin = TracedPlugin(plugin_instance, plugin_name)
|
|
505
|
+
|
|
506
|
+
if custom_metadata:
|
|
507
|
+
# Add custom metadata to the traced plugin
|
|
508
|
+
traced_plugin._custom_metadata = custom_metadata
|
|
509
|
+
|
|
510
|
+
# Override the metadata extraction to include custom data
|
|
511
|
+
original_extract = traced_plugin._extract_result_metadata
|
|
512
|
+
|
|
513
|
+
def enhanced_extract(result):
|
|
514
|
+
metadata = original_extract(result)
|
|
515
|
+
metadata.update(custom_metadata)
|
|
516
|
+
return metadata
|
|
517
|
+
|
|
518
|
+
traced_plugin._extract_result_metadata = enhanced_extract
|
|
519
|
+
|
|
520
|
+
return traced_plugin
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
# Export everything
|
|
524
|
+
__all__ = [
|
|
525
|
+
# Main tracing functions
|
|
526
|
+
"trace_plugin",
|
|
527
|
+
|
|
528
|
+
# Traced plugin factories
|
|
529
|
+
"traced_postgresql",
|
|
530
|
+
"traced_mysql",
|
|
531
|
+
"traced_mongodb",
|
|
532
|
+
"traced_rest",
|
|
533
|
+
"traced_s3",
|
|
534
|
+
"traced_slack",
|
|
535
|
+
"traced_elasticsearch",
|
|
536
|
+
|
|
537
|
+
# Context managers
|
|
538
|
+
"traced_transaction",
|
|
539
|
+
"traced_api_batch",
|
|
540
|
+
|
|
541
|
+
# Utility functions
|
|
542
|
+
"get_plugin_traces",
|
|
543
|
+
"get_plugin_stats",
|
|
544
|
+
|
|
545
|
+
# Advanced functions
|
|
546
|
+
"create_custom_traced_plugin",
|
|
547
|
+
|
|
548
|
+
# Plugin class (for advanced usage)
|
|
549
|
+
"TracedPlugin"
|
|
550
|
+
]
|