ragaai-catalyst 2.1b0__py3-none-any.whl → 2.1b2__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.
Files changed (45) hide show
  1. ragaai_catalyst/__init__.py +1 -0
  2. ragaai_catalyst/dataset.py +1 -4
  3. ragaai_catalyst/evaluation.py +4 -5
  4. ragaai_catalyst/guard_executor.py +97 -0
  5. ragaai_catalyst/guardrails_manager.py +41 -15
  6. ragaai_catalyst/internal_api_completion.py +1 -1
  7. ragaai_catalyst/prompt_manager.py +7 -2
  8. ragaai_catalyst/ragaai_catalyst.py +1 -1
  9. ragaai_catalyst/synthetic_data_generation.py +7 -0
  10. ragaai_catalyst/tracers/__init__.py +1 -1
  11. ragaai_catalyst/tracers/agentic_tracing/__init__.py +3 -0
  12. ragaai_catalyst/tracers/agentic_tracing/agent_tracer.py +422 -0
  13. ragaai_catalyst/tracers/agentic_tracing/agentic_tracing.py +198 -0
  14. ragaai_catalyst/tracers/agentic_tracing/base.py +376 -0
  15. ragaai_catalyst/tracers/agentic_tracing/data_structure.py +248 -0
  16. ragaai_catalyst/tracers/agentic_tracing/examples/FinancialAnalysisSystem.ipynb +536 -0
  17. ragaai_catalyst/tracers/agentic_tracing/examples/GameActivityEventPlanner.ipynb +134 -0
  18. ragaai_catalyst/tracers/agentic_tracing/examples/TravelPlanner.ipynb +563 -0
  19. ragaai_catalyst/tracers/agentic_tracing/file_name_tracker.py +46 -0
  20. ragaai_catalyst/tracers/agentic_tracing/llm_tracer.py +808 -0
  21. ragaai_catalyst/tracers/agentic_tracing/network_tracer.py +286 -0
  22. ragaai_catalyst/tracers/agentic_tracing/sample.py +197 -0
  23. ragaai_catalyst/tracers/agentic_tracing/tool_tracer.py +247 -0
  24. ragaai_catalyst/tracers/agentic_tracing/unique_decorator.py +165 -0
  25. ragaai_catalyst/tracers/agentic_tracing/unique_decorator_test.py +172 -0
  26. ragaai_catalyst/tracers/agentic_tracing/upload_agentic_traces.py +187 -0
  27. ragaai_catalyst/tracers/agentic_tracing/upload_code.py +115 -0
  28. ragaai_catalyst/tracers/agentic_tracing/user_interaction_tracer.py +43 -0
  29. ragaai_catalyst/tracers/agentic_tracing/utils/__init__.py +3 -0
  30. ragaai_catalyst/tracers/agentic_tracing/utils/api_utils.py +18 -0
  31. ragaai_catalyst/tracers/agentic_tracing/utils/data_classes.py +61 -0
  32. ragaai_catalyst/tracers/agentic_tracing/utils/generic.py +32 -0
  33. ragaai_catalyst/tracers/agentic_tracing/utils/llm_utils.py +177 -0
  34. ragaai_catalyst/tracers/agentic_tracing/utils/model_costs.json +7823 -0
  35. ragaai_catalyst/tracers/agentic_tracing/utils/trace_utils.py +74 -0
  36. ragaai_catalyst/tracers/agentic_tracing/zip_list_of_unique_files.py +184 -0
  37. ragaai_catalyst/tracers/exporters/raga_exporter.py +1 -7
  38. ragaai_catalyst/tracers/tracer.py +30 -4
  39. ragaai_catalyst/tracers/upload_traces.py +127 -0
  40. ragaai_catalyst-2.1b2.dist-info/METADATA +43 -0
  41. ragaai_catalyst-2.1b2.dist-info/RECORD +56 -0
  42. {ragaai_catalyst-2.1b0.dist-info → ragaai_catalyst-2.1b2.dist-info}/WHEEL +1 -1
  43. ragaai_catalyst-2.1b0.dist-info/METADATA +0 -295
  44. ragaai_catalyst-2.1b0.dist-info/RECORD +0 -28
  45. {ragaai_catalyst-2.1b0.dist-info → ragaai_catalyst-2.1b2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,422 @@
1
+ import functools
2
+ import uuid
3
+ from datetime import datetime
4
+ import psutil
5
+ from typing import Optional, Any, Dict, List
6
+ from .unique_decorator import mydecorator
7
+ from .unique_decorator import generate_unique_hash_simple
8
+
9
+ import contextvars
10
+ import asyncio
11
+ from .file_name_tracker import TrackName
12
+
13
+
14
+ class AgentTracerMixin:
15
+ def __init__(self, *args, **kwargs):
16
+ super().__init__(*args, **kwargs)
17
+ self.file_tracker = TrackName()
18
+ self.current_agent_id = contextvars.ContextVar("agent_id", default=None)
19
+ self.current_agent_name = contextvars.ContextVar("agent_name", default=None)
20
+ self.agent_children = contextvars.ContextVar("agent_children", default=[])
21
+ self.component_network_calls = contextvars.ContextVar("component_network_calls", default={})
22
+ self.component_user_interaction = contextvars.ContextVar("component_user_interaction", default={})
23
+ self.gt = None
24
+
25
+
26
+ def trace_agent(self, name: str, agent_type: str = "generic", version: str = "1.0.0", capabilities: List[str] = None):
27
+ def decorator(target):
28
+ # Check if target is a class
29
+ is_class = isinstance(target, type)
30
+ tracer = self # Store reference to tracer instance
31
+ top_level_hash_id = generate_unique_hash_simple(target) # Generate hash based on the decorated target code
32
+
33
+
34
+ if is_class:
35
+ # Store original __init__
36
+ original_init = target.__init__
37
+
38
+ def wrapped_init(self, *args, **kwargs):
39
+ self.gt = kwargs.get('gt', None) if kwargs else None
40
+ # Set agent context before initializing
41
+ component_id = str(uuid.uuid4())
42
+ hash_id = top_level_hash_id
43
+
44
+ # Store the component ID in the instance
45
+ self._agent_component_id = component_id
46
+
47
+ # Get parent agent ID if exists
48
+ parent_agent_id = tracer.current_agent_id.get()
49
+
50
+ # Create agent component
51
+ agent_component = tracer.create_agent_component(
52
+ component_id=component_id,
53
+ hash_id=hash_id,
54
+ name=name,
55
+ agent_type=agent_type,
56
+ version=version,
57
+ capabilities=capabilities or [],
58
+ start_time=datetime.now(),
59
+ end_time=datetime.now(),
60
+ memory_used=0,
61
+ input_data=tracer._sanitize_input(args, kwargs),
62
+ output_data=None,
63
+ children=[],
64
+ parent_id=parent_agent_id
65
+ )
66
+
67
+ # Store component for later updates
68
+ if not hasattr(tracer, '_agent_components'):
69
+ tracer._agent_components = {}
70
+ tracer._agent_components[component_id] = agent_component
71
+
72
+ # If this is a nested agent, add it to parent's children
73
+ if parent_agent_id:
74
+ parent_children = tracer.agent_children.get()
75
+ parent_children.append(agent_component)
76
+ tracer.agent_children.set(parent_children)
77
+ else:
78
+ # Only add to root components if no parent
79
+ tracer.add_component(agent_component)
80
+
81
+ # Call original __init__ with this agent as current
82
+ token = tracer.current_agent_id.set(component_id)
83
+ try:
84
+ original_init(self, *args, **kwargs)
85
+ finally:
86
+ tracer.current_agent_id.reset(token)
87
+
88
+ # Wrap all public methods to track execution
89
+ for attr_name in dir(target):
90
+ if not attr_name.startswith('_'):
91
+ attr_value = getattr(target, attr_name)
92
+ if callable(attr_value):
93
+ def wrap_method(method):
94
+ @self.file_tracker.trace_decorator
95
+ @functools.wraps(method)
96
+ def wrapped_method(self, *args, **kwargs):
97
+ self.gt = kwargs.get('gt', None) if kwargs else None
98
+ # Set this agent as current during method execution
99
+ token = tracer.current_agent_id.set(self._agent_component_id)
100
+
101
+ # Store parent's children before setting new empty list
102
+ parent_children = tracer.agent_children.get()
103
+ children_token = tracer.agent_children.set([])
104
+
105
+ try:
106
+ start_time = datetime.now()
107
+ result = method(self, *args, **kwargs)
108
+ end_time = datetime.now()
109
+
110
+ # Update agent component with method result
111
+ if hasattr(tracer, '_agent_components'):
112
+ component = tracer._agent_components.get(self._agent_component_id)
113
+ if component:
114
+ component['data']['output'] = tracer._sanitize_output(result)
115
+ component['data']['input'] = tracer._sanitize_input(args, kwargs)
116
+ component['start_time'] = start_time.isoformat()
117
+ component['end_time'] = end_time.isoformat()
118
+
119
+ # Get children accumulated during method execution
120
+ children = tracer.agent_children.get()
121
+ if children:
122
+ if 'children' not in component['data']:
123
+ component['data']['children'] = []
124
+ component['data']['children'].extend(children)
125
+
126
+ # Add this component as a child to parent's children list
127
+ parent_children.append(component)
128
+ tracer.agent_children.set(parent_children)
129
+ return result
130
+ finally:
131
+ tracer.current_agent_id.reset(token)
132
+ tracer.agent_children.reset(children_token)
133
+ return wrapped_method
134
+
135
+ setattr(target, attr_name, wrap_method(attr_value))
136
+
137
+ # Replace __init__ with wrapped version
138
+ target.__init__ = wrapped_init
139
+ return target
140
+ else:
141
+ # For function decorators, use existing sync/async tracing
142
+ is_async = asyncio.iscoroutinefunction(target)
143
+ if is_async:
144
+ async def wrapper(*args, **kwargs):
145
+ return await self._trace_agent_execution(target, name, agent_type, version, capabilities, top_level_hash_id, *args, **kwargs)
146
+ return wrapper
147
+ else:
148
+ def wrapper(*args, **kwargs):
149
+ return self._trace_sync_agent_execution(target, name, agent_type, version, capabilities, *args, **kwargs)
150
+ return wrapper
151
+
152
+ return decorator
153
+
154
+ def _trace_sync_agent_execution(self, func, name, agent_type, version, capabilities, *args, **kwargs):
155
+ # Generate a unique hash_id for this execution context
156
+ hash_id = str(uuid.uuid4())
157
+
158
+ """Synchronous version of agent tracing"""
159
+ if not self.is_active:
160
+ return func(*args, **kwargs)
161
+
162
+ start_time = datetime.now()
163
+ start_memory = psutil.Process().memory_info().rss
164
+ component_id = str(uuid.uuid4())
165
+
166
+ # Extract ground truth if present
167
+ ground_truth = kwargs.pop('gt', None) if kwargs else None
168
+
169
+ # Get parent agent ID if exists
170
+ parent_agent_id = self.current_agent_id.get()
171
+
172
+ # Set the current agent context
173
+ agent_token = self.current_agent_id.set(component_id)
174
+ agent_name_token = self.current_agent_name.set(name)
175
+
176
+ # Initialize empty children list for this agent
177
+ parent_children = self.agent_children.get()
178
+ children_token = self.agent_children.set([])
179
+
180
+ # Start tracking network calls for this component
181
+ self.start_component(component_id)
182
+
183
+ try:
184
+ # Execute the agent
185
+ result = func(*args, **kwargs)
186
+
187
+ # Calculate resource usage
188
+ end_time = datetime.now()
189
+ end_memory = psutil.Process().memory_info().rss
190
+ memory_used = max(0, end_memory - start_memory)
191
+
192
+ # Get children components collected during execution
193
+ children = self.agent_children.get()
194
+
195
+ # End tracking network calls for this component
196
+ self.end_component(component_id)
197
+
198
+ # Create agent component with children and parent if exists
199
+ agent_component = self.create_agent_component(
200
+ component_id=component_id,
201
+ hash_id=hash_id,
202
+ name=name,
203
+ agent_type=agent_type,
204
+ version=version,
205
+ capabilities=capabilities or [],
206
+ start_time=start_time,
207
+ end_time=end_time,
208
+ memory_used=memory_used,
209
+ input_data=self._sanitize_input(args, kwargs),
210
+ output_data=self._sanitize_output(result),
211
+ children=children,
212
+ parent_id=parent_agent_id
213
+ )
214
+
215
+ # Add ground truth to component data if present
216
+ if ground_truth is not None:
217
+ agent_component["data"]["gt"] = ground_truth
218
+
219
+ # Add this component as a child to parent's children list
220
+ parent_children.append(agent_component)
221
+ self.agent_children.set(parent_children)
222
+
223
+ # Only add to root components if no parent
224
+ if not parent_agent_id:
225
+ self.add_component(agent_component)
226
+
227
+ return result
228
+ finally:
229
+ self.current_agent_id.reset(agent_token)
230
+ self.current_agent_name.reset(agent_name_token)
231
+ self.agent_children.reset(children_token)
232
+
233
+ async def _trace_agent_execution(self, func, name, agent_type, version, capabilities, hash_id, *args, **kwargs):
234
+ """Asynchronous version of agent tracing"""
235
+ if not self.is_active:
236
+ return await func(*args, **kwargs)
237
+
238
+ start_time = datetime.now()
239
+ start_memory = psutil.Process().memory_info().rss
240
+ component_id = str(uuid.uuid4())
241
+
242
+ # Extract ground truth if present
243
+ ground_truth = kwargs.pop('gt', None) if kwargs else None
244
+
245
+ # Get parent agent ID if exists
246
+ parent_agent_id = self.current_agent_id.get()
247
+
248
+ # Set the current agent context
249
+ agent_token = self.current_agent_id.set(component_id)
250
+ agent_name_token = self.current_agent_name.set(name)
251
+
252
+ # Initialize empty children list for this agent
253
+ parent_children = self.agent_children.get()
254
+ children_token = self.agent_children.set([])
255
+
256
+ try:
257
+ # Execute the agent
258
+ result = await func(*args, **kwargs)
259
+
260
+ # Calculate resource usage
261
+ end_time = datetime.now()
262
+ end_memory = psutil.Process().memory_info().rss
263
+ memory_used = max(0, end_memory - start_memory)
264
+
265
+ # Get children components collected during execution
266
+ children = self.agent_children.get()
267
+
268
+ # Create agent component with children and parent if exists
269
+ agent_component = self.create_agent_component(
270
+ component_id=component_id,
271
+ hash_id=hash_id,
272
+ name=name,
273
+ agent_type=agent_type,
274
+ version=version,
275
+ capabilities=capabilities or [],
276
+ start_time=start_time,
277
+ end_time=end_time,
278
+ memory_used=memory_used,
279
+ input_data=self._sanitize_input(args, kwargs),
280
+ output_data=self._sanitize_output(result),
281
+ children=children,
282
+ parent_id=parent_agent_id
283
+ )
284
+
285
+ # Add ground truth to component data if present
286
+ if ground_truth is not None:
287
+ agent_component["data"]["gt"] = ground_truth
288
+
289
+ # Add this component as a child to parent's children list
290
+ parent_children.append(agent_component)
291
+ self.agent_children.set(parent_children)
292
+
293
+ # Only add to root components if no parent
294
+ if not parent_agent_id:
295
+ self.add_component(agent_component)
296
+
297
+ return result
298
+ except Exception as e:
299
+ error_component = {
300
+ "code": 500,
301
+ "type": type(e).__name__,
302
+ "message": str(e),
303
+ "details": {}
304
+ }
305
+
306
+ # Get children even in case of error
307
+ children = self.agent_children.get()
308
+
309
+ # Set parent_id for all children
310
+ for child in children:
311
+ child["parent_id"] = component_id
312
+
313
+ # End tracking network calls for this component
314
+ self.end_component(component_id)
315
+
316
+ agent_component = self.create_agent_component(
317
+ component_id=component_id,
318
+ hash_id=hash_id,
319
+ name=name,
320
+ agent_type=agent_type,
321
+ version=version,
322
+ capabilities=capabilities or [],
323
+ start_time=start_time,
324
+ end_time=datetime.now(),
325
+ memory_used=0,
326
+ input_data=self._sanitize_input(args, kwargs),
327
+ output_data=None,
328
+ error=error_component,
329
+ children=children,
330
+ parent_id=parent_agent_id # Add parent ID if exists
331
+ )
332
+
333
+ # If this is a nested agent, add it to parent's children
334
+ if parent_agent_id:
335
+ parent_component = self._agent_components.get(parent_agent_id)
336
+ if parent_component:
337
+ if 'children' not in parent_component['data']:
338
+ parent_component['data']['children'] = []
339
+ parent_component['data']['children'].append(agent_component)
340
+ else:
341
+ # Only add to root components if no parent
342
+ self.add_component(agent_component)
343
+ raise
344
+ finally:
345
+ # Reset context variables
346
+ self.current_agent_id.reset(agent_token)
347
+ self.current_agent_name.reset(agent_name_token)
348
+ self.agent_children.reset(children_token)
349
+
350
+ def create_agent_component(self, **kwargs):
351
+ """Create an agent component according to the data structure"""
352
+ start_time = kwargs["start_time"]
353
+ component = {
354
+ "id": kwargs["component_id"],
355
+ "hash_id": kwargs["hash_id"],
356
+ "source_hash_id": None,
357
+ "type": "agent",
358
+ "name": kwargs["name"],
359
+ "start_time": start_time.isoformat(),
360
+ "end_time": kwargs["end_time"].isoformat(),
361
+ "error": kwargs.get("error"),
362
+ "parent_id": kwargs.get("parent_id"),
363
+ "info": {
364
+ "agent_type": kwargs["agent_type"],
365
+ "version": kwargs["version"],
366
+ "capabilities": kwargs["capabilities"],
367
+ "memory_used": kwargs["memory_used"]
368
+ },
369
+ "data": {
370
+ "input": kwargs["input_data"],
371
+ "output": kwargs["output_data"],
372
+ "children": kwargs.get("children", [])
373
+ },
374
+ "network_calls": self.component_network_calls.get(kwargs["component_id"], []),
375
+ "interactions": self.component_user_interaction.get(kwargs["component_id"], [])
376
+ }
377
+
378
+ if self.gt:
379
+ component["data"]["gt"] = self.gt
380
+
381
+ return component
382
+
383
+ def start_component(self, component_id):
384
+ """Start tracking network calls for a component"""
385
+ component_network_calls = self.component_network_calls.get()
386
+ if component_id not in component_network_calls:
387
+ component_network_calls[component_id] = []
388
+ self.component_network_calls.set(component_network_calls)
389
+
390
+ def end_component(self, component_id):
391
+ """End tracking network calls for a component"""
392
+ component_network_calls = self.component_network_calls.get()
393
+ if component_id in component_network_calls:
394
+ component_network_calls[component_id] = []
395
+ self.component_network_calls.set(component_network_calls)
396
+
397
+ def _sanitize_input(self, args: tuple, kwargs: dict) -> str:
398
+ """Convert input arguments to text format.
399
+
400
+ Args:
401
+ args: Input arguments tuple
402
+ kwargs: Input keyword arguments dict
403
+
404
+ Returns:
405
+ str: Text representation of the input arguments
406
+ """
407
+ def _sanitize_value(value):
408
+ if isinstance(value, dict):
409
+ return str({k: _sanitize_value(v) for k, v in value.items()})
410
+ elif isinstance(value, (list, tuple)):
411
+ return str([_sanitize_value(item) for item in value])
412
+ return str(value)
413
+
414
+ sanitized_args = [_sanitize_value(arg) for arg in args]
415
+ sanitized_kwargs = {k: _sanitize_value(v) for k, v in kwargs.items()}
416
+ return str({"args": sanitized_args, "kwargs": sanitized_kwargs})
417
+
418
+ def _sanitize_output(self, output: Any) -> Any:
419
+ """Sanitize and format output data"""
420
+ if isinstance(output, (int, float, bool, str, list, dict)):
421
+ return output
422
+ return str(output)
@@ -0,0 +1,198 @@
1
+ import contextvars
2
+ from typing import Optional, Dict
3
+ import json
4
+ from datetime import datetime
5
+ import uuid
6
+ import os
7
+ import builtins
8
+ from pathlib import Path
9
+
10
+ from .base import BaseTracer
11
+ from .llm_tracer import LLMTracerMixin
12
+ from .tool_tracer import ToolTracerMixin
13
+ from .agent_tracer import AgentTracerMixin
14
+ from .network_tracer import NetworkTracer
15
+ from .user_interaction_tracer import UserInteractionTracer
16
+
17
+ from .data_structure import (
18
+ Trace, Metadata, SystemInfo, OSInfo, EnvironmentInfo,
19
+ Resources, CPUResource, MemoryResource, DiskResource, NetworkResource,
20
+ ResourceInfo, MemoryInfo, DiskInfo, NetworkInfo,
21
+ Component, LLMComponent, AgentComponent, ToolComponent,
22
+ NetworkCall, Interaction, Error
23
+ )
24
+
25
+
26
+ from ...ragaai_catalyst import RagaAICatalyst
27
+ from ..upload_traces import UploadTraces
28
+
29
+ class AgenticTracing(BaseTracer, LLMTracerMixin, ToolTracerMixin, AgentTracerMixin):
30
+ def __init__(self, user_detail, auto_instrument_llm: bool = True):
31
+ # Initialize all parent classes
32
+ self.user_interaction_tracer = UserInteractionTracer()
33
+ LLMTracerMixin.__init__(self)
34
+ ToolTracerMixin.__init__(self)
35
+ AgentTracerMixin.__init__(self)
36
+
37
+ self.project_name = user_detail["project_name"]
38
+ self.project_id = user_detail["project_id"]
39
+ self.dataset_name = user_detail["dataset_name"]
40
+ self.trace_user_detail = user_detail["trace_user_detail"]
41
+ self.base_url = f"{RagaAICatalyst.BASE_URL}"
42
+ self.timeout = 10
43
+
44
+ BaseTracer.__init__(self, user_detail)
45
+
46
+ self.auto_instrument_llm = auto_instrument_llm
47
+ self.tools: Dict[str, Tool] = {}
48
+ self.call_depth = contextvars.ContextVar("call_depth", default=0)
49
+ self.current_component_id = contextvars.ContextVar("current_component_id", default=None)
50
+ self.network_tracer = NetworkTracer()
51
+ self.is_active = False
52
+ self.current_agent_id = contextvars.ContextVar("current_agent_id", default=None)
53
+ self.agent_children = contextvars.ContextVar("agent_children", default=[])
54
+ self.component_network_calls = {} # Store network calls per component
55
+ self.component_user_interaction = {}
56
+
57
+ # Create output directory if it doesn't exist
58
+ self.output_dir = Path("./traces") # Using default traces directory
59
+ self.output_dir.mkdir(exist_ok=True)
60
+
61
+ def start_component(self, component_id: str):
62
+ """Start tracking network calls for a component"""
63
+ self.component_network_calls[component_id] = []
64
+ self.network_tracer.network_calls = [] # Reset network calls
65
+ self.component_user_interaction[component_id] = []
66
+ self.current_component_id.set(component_id)
67
+ self.user_interaction_tracer.component_id.set(component_id)
68
+
69
+ def end_component(self, component_id: str):
70
+ """End tracking network calls for a component"""
71
+ self.component_network_calls[component_id] = self.network_tracer.network_calls.copy()
72
+ self.network_tracer.network_calls = [] # Reset for next component
73
+ self.component_user_interaction[component_id] = self.user_interaction_tracer.interactions.copy()
74
+ self.user_interaction_tracer.interactions = []
75
+
76
+ def start(self):
77
+ """Start tracing"""
78
+ # Setup user interaction tracing
79
+ self.user_interaction_tracer.project_id.set(self.project_id)
80
+ self.user_interaction_tracer.trace_id.set(self.trace_id)
81
+ self.user_interaction_tracer.tracer = self
82
+ self.user_interaction_tracer.component_id.set(self.current_component_id.get())
83
+ builtins.print = self.user_interaction_tracer.traced_print
84
+ builtins.input = self.user_interaction_tracer.traced_input
85
+
86
+ # Start base tracer (includes system info and resource monitoring)
87
+ super().start()
88
+ self.is_active = True
89
+
90
+ # Activate network tracing
91
+ self.network_tracer.activate_patches()
92
+
93
+ # Instrument calls from mixins
94
+ if self.auto_instrument_llm:
95
+ self.instrument_llm_calls()
96
+
97
+ def stop(self):
98
+ """Stop tracing and save results"""
99
+ if self.is_active:
100
+ # Restore original print and input functions
101
+ builtins.print = self.user_interaction_tracer.original_print
102
+ builtins.input = self.user_interaction_tracer.original_input
103
+
104
+ # Calculate final metrics before stopping
105
+ self._calculate_final_metrics()
106
+
107
+ # Deactivate network tracing
108
+ self.network_tracer.deactivate_patches()
109
+
110
+ # Stop base tracer (includes saving to file)
111
+ super().stop()
112
+
113
+ # Cleanup
114
+ self.unpatch_llm_calls()
115
+ self.is_active = False
116
+
117
+
118
+ def _calculate_final_metrics(self):
119
+ """Calculate total cost and tokens from all components"""
120
+ total_cost = 0.0
121
+ total_tokens = 0
122
+
123
+ def process_component(component):
124
+ nonlocal total_cost, total_tokens
125
+ # Convert component to dict if it's an object
126
+ comp_dict = component.__dict__ if hasattr(component, '__dict__') else component
127
+
128
+ if comp_dict.get('type') == "llm":
129
+ info = comp_dict.get('info', {})
130
+ if isinstance(info, dict):
131
+ # Extract cost
132
+ cost_info = info.get('cost', {})
133
+ if isinstance(cost_info, dict):
134
+ total_cost += cost_info.get('total_cost', 0)
135
+
136
+ # Extract tokens
137
+ token_info = info.get('tokens', {})
138
+ if isinstance(token_info, dict):
139
+ total_tokens += token_info.get('total_tokens', 0)
140
+ else:
141
+ token_info = info.get('token_usage', {})
142
+ if isinstance(token_info, dict):
143
+ total_tokens += token_info.get('total_tokens', 0)
144
+
145
+ # Process children if they exist
146
+ data = comp_dict.get('data', {})
147
+ if isinstance(data, dict):
148
+ children = data.get('children', [])
149
+ if children:
150
+ for child in children:
151
+ process_component(child)
152
+
153
+ # Process all root components
154
+ for component in self.components:
155
+ process_component(component)
156
+
157
+ # Update metadata in trace
158
+ if hasattr(self, 'trace'):
159
+ if isinstance(self.trace.metadata, dict):
160
+ self.trace.metadata['total_cost'] = total_cost
161
+ self.trace.metadata['total_tokens'] = total_tokens
162
+ else:
163
+ self.trace.metadata.total_cost = total_cost
164
+ self.trace.metadata.total_tokens = total_tokens
165
+
166
+ def add_component(self, component_data: dict):
167
+ """Add a component to the trace data"""
168
+ # Convert dict to appropriate Component type
169
+ filtered_data = {k: v for k, v in component_data.items() if k in ["id", "hash_id", "type", "name", "start_time", "end_time", "parent_id", "info", "data", "network_calls", "interactions"]}
170
+
171
+ if component_data["type"] == "llm":
172
+ component = LLMComponent(**filtered_data)
173
+ elif component_data["type"] == "agent":
174
+ component = AgentComponent(**filtered_data)
175
+ elif component_data["type"] == "tool":
176
+ component = ToolComponent(**filtered_data)
177
+ else:
178
+ component = Component(**component_data)
179
+
180
+ # Check if there's an active agent context
181
+ current_agent_id = self.current_agent_id.get()
182
+ if current_agent_id and component_data["type"] in ["llm", "tool"]:
183
+ # Add this component as a child of the current agent
184
+ current_children = self.agent_children.get()
185
+ current_children.append(component_data)
186
+ self.agent_children.set(current_children)
187
+ else:
188
+ # Add component to the main trace
189
+ super().add_component(component)
190
+
191
+ def __enter__(self):
192
+ """Context manager entry"""
193
+ self.start()
194
+ return self
195
+
196
+ def __exit__(self, exc_type, exc_value, traceback):
197
+ """Context manager exit"""
198
+ self.stop()