ragaai-catalyst 2.1b4__py3-none-any.whl → 2.1.1__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 (32) hide show
  1. ragaai_catalyst/tracers/agentic_tracing/README.md +97 -0
  2. ragaai_catalyst/tracers/agentic_tracing/__init__.py +4 -2
  3. ragaai_catalyst/tracers/agentic_tracing/data/__init__.py +0 -0
  4. ragaai_catalyst/tracers/agentic_tracing/{data_structure.py → data/data_structure.py} +14 -1
  5. ragaai_catalyst/tracers/agentic_tracing/tests/__init__.py +0 -0
  6. ragaai_catalyst/tracers/agentic_tracing/tracers/__init__.py +0 -0
  7. ragaai_catalyst/tracers/agentic_tracing/{agent_tracer.py → tracers/agent_tracer.py} +15 -10
  8. ragaai_catalyst/tracers/agentic_tracing/{base.py → tracers/base.py} +5 -6
  9. ragaai_catalyst/tracers/agentic_tracing/{llm_tracer.py → tracers/llm_tracer.py} +40 -280
  10. ragaai_catalyst/tracers/agentic_tracing/{agentic_tracing.py → tracers/main_tracer.py} +41 -8
  11. ragaai_catalyst/tracers/agentic_tracing/{tool_tracer.py → tracers/tool_tracer.py} +3 -3
  12. ragaai_catalyst/tracers/agentic_tracing/upload/__init__.py +0 -0
  13. ragaai_catalyst/tracers/agentic_tracing/{upload_agentic_traces.py → upload/upload_agentic_traces.py} +3 -0
  14. ragaai_catalyst/tracers/agentic_tracing/utils/llm_utils.py +229 -43
  15. ragaai_catalyst/tracers/tracer.py +11 -12
  16. {ragaai_catalyst-2.1b4.dist-info → ragaai_catalyst-2.1.1.dist-info}/METADATA +5 -5
  17. ragaai_catalyst-2.1.1.dist-info/RECORD +60 -0
  18. {ragaai_catalyst-2.1b4.dist-info → ragaai_catalyst-2.1.1.dist-info}/WHEEL +1 -1
  19. ragaai_catalyst/tracers/agentic_tracing/utils/data_classes.py +0 -61
  20. ragaai_catalyst-2.1b4.dist-info/RECORD +0 -56
  21. /ragaai_catalyst/tracers/agentic_tracing/{examples → tests}/FinancialAnalysisSystem.ipynb +0 -0
  22. /ragaai_catalyst/tracers/agentic_tracing/{examples → tests}/GameActivityEventPlanner.ipynb +0 -0
  23. /ragaai_catalyst/tracers/agentic_tracing/{examples → tests}/TravelPlanner.ipynb +0 -0
  24. /ragaai_catalyst/tracers/agentic_tracing/{sample.py → tests/ai_travel_agent.py} +0 -0
  25. /ragaai_catalyst/tracers/agentic_tracing/{unique_decorator_test.py → tests/unique_decorator_test.py} +0 -0
  26. /ragaai_catalyst/tracers/agentic_tracing/{network_tracer.py → tracers/network_tracer.py} +0 -0
  27. /ragaai_catalyst/tracers/agentic_tracing/{user_interaction_tracer.py → tracers/user_interaction_tracer.py} +0 -0
  28. /ragaai_catalyst/tracers/agentic_tracing/{upload_code.py → upload/upload_code.py} +0 -0
  29. /ragaai_catalyst/tracers/agentic_tracing/{file_name_tracker.py → utils/file_name_tracker.py} +0 -0
  30. /ragaai_catalyst/tracers/agentic_tracing/{unique_decorator.py → utils/unique_decorator.py} +0 -0
  31. /ragaai_catalyst/tracers/agentic_tracing/{zip_list_of_unique_files.py → utils/zip_list_of_unique_files.py} +0 -0
  32. {ragaai_catalyst-2.1b4.dist-info → ragaai_catalyst-2.1.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,97 @@
1
+ # Agentic Tracing
2
+
3
+ This module provides tracing functionality for agentic AI systems, helping track and analyze various aspects of AI agent behavior including LLM interactions, tool usage, and network activities.
4
+
5
+ ## Directory Structure
6
+
7
+ ```
8
+ agentic_tracing/
9
+ ├── tracers/ # Core tracing implementations
10
+ │ ├── main_tracer.py # Main tracing functionality
11
+ │ ├── agent_tracer.py # Agent behavior tracing
12
+ │ ├── base.py # Base tracing classes
13
+ │ ├── llm_tracer.py # Language model interaction tracing
14
+ │ ├── network_tracer.py # Network activity tracing
15
+ │ ├── tool_tracer.py # Tool usage tracing
16
+ │ ├── user_interaction_tracer.py # User interaction tracing
17
+ │ └── __init__.py # Tracer module initialization
18
+ ├── data/ # Data structures and classes
19
+ │ ├── data_classes.py # Data class definitions
20
+ │ └── __init__.py # Data module initialization
21
+ ├── utils/ # Utility functions and helpers
22
+ │ ├── api_utils.py # API-related utilities
23
+ │ ├── file_name_tracker.py # Tracks file names and paths
24
+ │ ├── generic.py # Generic utility functions
25
+ │ ├── llm_utils.py # LLM-specific utilities
26
+ │ ├── model_costs.json # Model cost configurations
27
+ │ ├── trace_utils.py # General tracing utilities
28
+ │ ├── unique_decorator.py # Unique ID generation
29
+ │ ├── zip_list_of_unique_files.py # File handling utilities
30
+ │ └── __init__.py # Utils module initialization
31
+ ├── tests/ # Test suites and examples
32
+ │ ├── ai_travel_agent.py # Travel agent test implementation
33
+ │ ├── unique_decorator_test.py # Tests for unique decorator
34
+ │ ├── TravelPlanner.ipynb # Travel planner example notebook
35
+ │ ├── FinancialAnalysisSystem.ipynb # Financial analysis example
36
+ │ ├── GameActivityEventPlanner.ipynb # Game event planner example
37
+ │ └── __init__.py # Tests module initialization
38
+ ├── upload/ # Upload functionality
39
+ │ ├── upload_code.py # Code upload utilities
40
+ │ └── __init__.py # Upload module initialization
41
+ └── __init__.py # Package initialization
42
+ ```
43
+
44
+ ## Components
45
+
46
+ ### Tracers
47
+ Different types of tracers for various aspects of agent behavior:
48
+ - Main Tracer: Core tracing functionality for managing and coordinating different trace types
49
+ - Agent Tracer: Tracks agent behavior, decisions, and state changes
50
+ - Base Tracer: Provides base classes and common functionality for all tracers
51
+ - LLM Tracer: Monitors language model interactions, including:
52
+ - Token usage tracking
53
+ - Cost calculation
54
+ - Input/output monitoring
55
+ - Model parameter tracking
56
+ - Network Tracer: Tracks network activities and API calls
57
+ - Tool Tracer: Monitors tool usage and execution
58
+ - User Interaction Tracer: Tracks user interactions and feedback
59
+
60
+ ### Data
61
+ Core data structures and classes:
62
+ - Data Classes: Defines structured data types for:
63
+ - LLM calls
64
+ - Network requests
65
+ - Tool executions
66
+ - Trace components
67
+ - Agent states
68
+ - User interactions
69
+
70
+ ### Utils
71
+ Helper functions and utilities:
72
+ - API Utils: Handles API-related operations and configurations
73
+ - LLM Utils: Utilities for handling LLM-specific operations:
74
+ - Model name extraction
75
+ - Token usage calculation
76
+ - Cost computation
77
+ - Parameter sanitization
78
+ - Generic Utils: Common utility functions used across modules
79
+ - Trace Utils: General tracing utilities
80
+ - File Name Tracker: Manages file paths and names
81
+ - Unique Decorator: Generates unique identifiers for trace components
82
+ - Model Costs: Configuration for different model pricing
83
+ - Zip List of Unique Files: Handles file compression and unique file management
84
+
85
+ ### Tests
86
+ Test suites and example implementations:
87
+ - AI Travel Agent: Test implementation of a travel planning agent
88
+ - Unique Decorator Tests: Unit tests for unique ID generation
89
+ - Example Notebooks:
90
+ - Travel Planner: Example of travel planning implementation
91
+ - Financial Analysis: Example of financial system analysis
92
+ - Game Event Planner: Example of game activity planning
93
+
94
+ ### Upload
95
+ Components for uploading and managing trace data:
96
+ - Code Upload: Handles uploading of traced code and execution data
97
+ - Supports various data formats and trace types
@@ -1,3 +1,5 @@
1
- from .agentic_tracing import AgenticTracing
1
+ from .tracers.main_tracer import AgenticTracing
2
+ from .utils.file_name_tracker import TrackName
3
+ from .utils.unique_decorator import generate_unique_hash_simple, mydecorator
2
4
 
3
- __all__ = ['AgenticTracing']
5
+ __all__ = ['AgenticTracing', 'TrackName', 'generate_unique_hash_simple', 'mydecorator']
@@ -1,4 +1,4 @@
1
- from dataclasses import dataclass
1
+ from dataclasses import dataclass, field
2
2
  from typing import List, Dict, Optional, Any, Union
3
3
  from datetime import datetime
4
4
  import uuid
@@ -159,6 +159,19 @@ class ToolInfo:
159
159
  version: str
160
160
  memory_used: int
161
161
 
162
+ @dataclass
163
+ class LLMCall:
164
+ name: str
165
+ model_name: str
166
+ input_prompt: str
167
+ output_response: str
168
+ tool_call: Dict
169
+ token_usage: Dict[str, int]
170
+ cost: Dict[str, float]
171
+ start_time: float = field(default=0)
172
+ end_time: float = field(default=0)
173
+ duration: float = field(default=0)
174
+
162
175
  class Component:
163
176
  def __init__(self, id: str, hash_id: str, type: str, name: str, start_time: str, end_time: str, parent_id: int, info: Dict[str, Any], data: Dict[str, Any], network_calls: Optional[List[NetworkCall]] = None, interactions: Optional[List[Union[Interaction, Dict]]] = None, error: Optional[Dict[str, Any]] = None):
164
177
  self.id = id
@@ -3,12 +3,10 @@ import uuid
3
3
  from datetime import datetime
4
4
  import psutil
5
5
  from typing import Optional, Any, Dict, List
6
- from .unique_decorator import mydecorator
7
- from .unique_decorator import generate_unique_hash_simple
8
-
6
+ from ..utils.unique_decorator import mydecorator, generate_unique_hash_simple
9
7
  import contextvars
10
8
  import asyncio
11
- from .file_name_tracker import TrackName
9
+ from ..utils.file_name_tracker import TrackName
12
10
 
13
11
 
14
12
  class AgentTracerMixin:
@@ -20,16 +18,23 @@ class AgentTracerMixin:
20
18
  self.agent_children = contextvars.ContextVar("agent_children", default=[])
21
19
  self.component_network_calls = contextvars.ContextVar("component_network_calls", default={})
22
20
  self.component_user_interaction = contextvars.ContextVar("component_user_interaction", default={})
21
+ self.version = contextvars.ContextVar("version", default="1.0.0")
22
+ self.agent_type = contextvars.ContextVar("agent_type", default="generic")
23
+ self.capabilities = contextvars.ContextVar("capabilities", default=[])
24
+ self.start_time = contextvars.ContextVar("start_time", default=None)
25
+ self.input_data = contextvars.ContextVar("input_data", default=None)
23
26
  self.gt = None
24
27
 
25
28
 
26
- def trace_agent(self, name: str, agent_type: str = "generic", version: str = "1.0.0", capabilities: List[str] = None):
29
+ def trace_agent(self, name: str, agent_type: str = None, version: str = None, capabilities: List[str] = None):
27
30
  def decorator(target):
28
31
  # Check if target is a class
29
32
  is_class = isinstance(target, type)
30
33
  tracer = self # Store reference to tracer instance
31
34
  top_level_hash_id = generate_unique_hash_simple(target) # Generate hash based on the decorated target code
32
-
35
+ self.version.set(version)
36
+ self.agent_type.set(agent_type)
37
+ self.capabilities.set(capabilities)
33
38
 
34
39
  if is_class:
35
40
  # Store original __init__
@@ -160,6 +165,8 @@ class AgentTracerMixin:
160
165
  return func(*args, **kwargs)
161
166
 
162
167
  start_time = datetime.now()
168
+ self.start_time = start_time
169
+ self.input_data = self._sanitize_input(args, kwargs)
163
170
  start_memory = psutil.Process().memory_info().rss
164
171
  component_id = str(uuid.uuid4())
165
172
 
@@ -206,12 +213,11 @@ class AgentTracerMixin:
206
213
  start_time=start_time,
207
214
  end_time=end_time,
208
215
  memory_used=memory_used,
209
- input_data=self._sanitize_input(args, kwargs),
216
+ input_data=self.input_data,
210
217
  output_data=self._sanitize_output(result),
211
218
  children=children,
212
219
  parent_id=parent_agent_id
213
220
  )
214
-
215
221
  # Add ground truth to component data if present
216
222
  if ground_truth is not None:
217
223
  agent_component["data"]["gt"] = ground_truth
@@ -253,13 +259,12 @@ class AgentTracerMixin:
253
259
  start_time=start_time,
254
260
  end_time=datetime.now(),
255
261
  memory_used=0,
256
- input_data=self._sanitize_input(args, kwargs),
262
+ input_data=self.input_data,
257
263
  output_data=None,
258
264
  error=error_component,
259
265
  children=children,
260
266
  parent_id=parent_agent_id # Add parent ID if exists
261
267
  )
262
-
263
268
  # If this is a nested agent, add it to parent's children
264
269
  if parent_agent_id:
265
270
  parent_component = self._agent_components.get(parent_agent_id)
@@ -10,18 +10,17 @@ import uuid
10
10
  import sys
11
11
  import tempfile
12
12
 
13
- from .data_structure import (
13
+ from ..data.data_structure import (
14
14
  Trace, Metadata, SystemInfo, OSInfo, EnvironmentInfo,
15
15
  Resources, CPUResource, MemoryResource, DiskResource, NetworkResource,
16
16
  ResourceInfo, MemoryInfo, DiskInfo, NetworkInfo,
17
17
  Component,
18
18
  )
19
19
 
20
- from .upload_agentic_traces import UploadAgenticTraces
21
- from .upload_code import upload_code
22
-
23
- from .file_name_tracker import TrackName
24
- from .zip_list_of_unique_files import zip_list_of_unique_files
20
+ from ..upload.upload_agentic_traces import UploadAgenticTraces
21
+ from ..upload.upload_code import upload_code
22
+ from ..utils.file_name_tracker import TrackName
23
+ from ..utils.zip_list_of_unique_files import zip_list_of_unique_files
25
24
 
26
25
  class TracerJSONEncoder(json.JSONEncoder):
27
26
  def default(self, obj):
@@ -3,15 +3,27 @@ import asyncio
3
3
  import psutil
4
4
  import wrapt
5
5
  import functools
6
+ import json
7
+ import os
8
+ import time
6
9
  from datetime import datetime
7
10
  import uuid
8
11
  import contextvars
9
12
  import traceback
10
13
 
11
- from .unique_decorator import generate_unique_hash_simple
12
- from .utils.trace_utils import load_model_costs
13
- from .utils.llm_utils import extract_llm_output
14
- from .file_name_tracker import TrackName
14
+ from ..utils.llm_utils import (
15
+ extract_model_name,
16
+ extract_parameters,
17
+ extract_token_usage,
18
+ extract_input_data,
19
+ calculate_llm_cost,
20
+ sanitize_api_keys,
21
+ sanitize_input,
22
+ extract_llm_output,
23
+ )
24
+ from ..utils.trace_utils import load_model_costs
25
+ from ..utils.unique_decorator import generate_unique_hash_simple
26
+ from ..utils.file_name_tracker import TrackName
15
27
 
16
28
 
17
29
  class LLMTracerMixin:
@@ -237,148 +249,6 @@ class LLMTracerMixin:
237
249
  setattr(obj, method_name, wrapped_method)
238
250
  self.patches.append((obj, method_name, original_method))
239
251
 
240
- def _extract_model_name(self, args, kwargs, result):
241
- """Extract model name from kwargs or result"""
242
- # First try direct model parameter
243
- model = kwargs.get("model", "")
244
-
245
- if not model:
246
- # Try to get from instance
247
- instance = kwargs.get("self", None)
248
- if instance:
249
- # Try model_name first (Google format)
250
- if hasattr(instance, "model_name"):
251
- model = instance.model_name
252
- # Try model attribute
253
- elif hasattr(instance, "model"):
254
- model = instance.model
255
-
256
- # TODO: This way isn't scalable. The necessity for normalising model names needs to be fixed. We shouldn't have to do this
257
- # Normalize Google model names
258
- if model and isinstance(model, str):
259
- model = model.lower()
260
- if "gemini-1.5-flash" in model:
261
- return "gemini-1.5-flash"
262
- if "gemini-1.5-pro" in model:
263
- return "gemini-1.5-pro"
264
- if "gemini-pro" in model:
265
- return "gemini-pro"
266
-
267
- if 'to_dict' in dir(result):
268
- result = result.to_dict()
269
- if 'model_version' in result:
270
- model = result['model_version']
271
-
272
- return model or "default"
273
-
274
- def _extract_parameters(self, kwargs):
275
- """Extract all non-null parameters from kwargs"""
276
- parameters = {k: v for k, v in kwargs.items() if v is not None}
277
-
278
- # Remove contents key in parameters (Google LLM Response)
279
- if 'contents' in parameters:
280
- del parameters['contents']
281
-
282
- # Remove messages key in parameters (OpenAI message)
283
- if 'messages' in parameters:
284
- del parameters['messages']
285
-
286
- if 'generation_config' in parameters:
287
- generation_config = parameters['generation_config']
288
- # If generation_config is already a dict, use it directly
289
- if isinstance(generation_config, dict):
290
- config_dict = generation_config
291
- else:
292
- # Convert GenerationConfig to dictionary if it has a to_dict method, otherwise try to get its __dict__
293
- config_dict = getattr(generation_config, 'to_dict', lambda: generation_config.__dict__)()
294
- parameters.update(config_dict)
295
- del parameters['generation_config']
296
-
297
- return parameters
298
-
299
- def _extract_token_usage(self, result):
300
- """Extract token usage from result"""
301
- # Handle coroutines
302
- if asyncio.iscoroutine(result):
303
- # Get the current event loop
304
- loop = asyncio.get_event_loop()
305
- # Run the coroutine in the current event loop
306
- result = loop.run_until_complete(result)
307
-
308
-
309
- # Handle standard OpenAI/Anthropic format
310
- if hasattr(result, "usage"):
311
- usage = result.usage
312
- return {
313
- "prompt_tokens": getattr(usage, "prompt_tokens", 0),
314
- "completion_tokens": getattr(usage, "completion_tokens", 0),
315
- "total_tokens": getattr(usage, "total_tokens", 0)
316
- }
317
-
318
- # Handle Google GenerativeAI format with usage_metadata
319
- if hasattr(result, "usage_metadata"):
320
- metadata = result.usage_metadata
321
- return {
322
- "prompt_tokens": getattr(metadata, "prompt_token_count", 0),
323
- "completion_tokens": getattr(metadata, "candidates_token_count", 0),
324
- "total_tokens": getattr(metadata, "total_token_count", 0)
325
- }
326
-
327
- # Handle Vertex AI format
328
- if hasattr(result, "text"):
329
- # For LangChain ChatVertexAI
330
- total_tokens = getattr(result, "token_count", 0)
331
- if not total_tokens and hasattr(result, "_raw_response"):
332
- # Try to get from raw response
333
- total_tokens = getattr(result._raw_response, "token_count", 0)
334
- return {
335
- # TODO: This implementation is incorrect. Vertex AI does provide this breakdown
336
- "prompt_tokens": 0, # Vertex AI doesn't provide this breakdown
337
- "completion_tokens": total_tokens,
338
- "total_tokens": total_tokens
339
- }
340
-
341
- return { # TODO: Passing 0 in case of not recorded is not correct. This needs to be fixes. Discuss before making changes to this
342
- "prompt_tokens": 0,
343
- "completion_tokens": 0,
344
- "total_tokens": 0
345
- }
346
-
347
- def _extract_input_data(self, args, kwargs, result):
348
- return {
349
- 'args': args,
350
- 'kwargs': kwargs
351
- }
352
-
353
- def _calculate_cost(self, token_usage, model_name):
354
- # TODO: Passing default cost is a faulty logic & implementation and should be fixed
355
- """Calculate cost based on token usage and model"""
356
- if not isinstance(token_usage, dict):
357
- token_usage = {
358
- "prompt_tokens": 0,
359
- "completion_tokens": 0,
360
- "total_tokens": token_usage if isinstance(token_usage, (int, float)) else 0
361
- }
362
-
363
- # TODO: This is a temporary fix. This needs to be fixed
364
-
365
- # Get model costs, defaulting to Vertex AI PaLM2 costs if unknown
366
- model_cost = self.model_costs.get(model_name, {
367
- "input_cost_per_token": 0.0,
368
- "output_cost_per_token": 0.0
369
- })
370
-
371
- input_cost = (token_usage.get("prompt_tokens", 0)) * model_cost.get("input_cost_per_token", 0.0)
372
- output_cost = (token_usage.get("completion_tokens", 0)) * model_cost.get("output_cost_per_token", 0.0)
373
- total_cost = input_cost + output_cost
374
-
375
- # TODO: Return the value as it is, no need to round
376
- return {
377
- "input_cost": round(input_cost, 10),
378
- "output_cost": round(output_cost, 10),
379
- "total_cost": round(total_cost, 10)
380
- }
381
-
382
252
  def create_llm_component(self, component_id, hash_id, name, llm_type, version, memory_used, start_time, end_time, input_data, output_data, cost={}, usage={}, error=None, parameters={}):
383
253
  # Update total metrics
384
254
  self.total_tokens += usage.get("total_tokens", 0)
@@ -449,10 +319,11 @@ class LLMTracerMixin:
449
319
  memory_used = max(0, end_memory - start_memory)
450
320
 
451
321
  # Extract token usage and calculate cost
452
- token_usage = self._extract_token_usage(result)
453
- model_name = self._extract_model_name(args, kwargs, result)
454
- cost = self._calculate_cost(token_usage, model_name)
455
- parameters = self._extract_parameters(kwargs)
322
+ model_name = extract_model_name(args, kwargs, result)
323
+ token_usage = extract_token_usage(result)
324
+ cost = calculate_llm_cost(token_usage, model_name, self.model_costs)
325
+ parameters = extract_parameters(kwargs)
326
+ input_data = extract_input_data(args, kwargs, result)
456
327
 
457
328
  # End tracking network calls for this component
458
329
  self.end_component(component_id)
@@ -461,9 +332,6 @@ class LLMTracerMixin:
461
332
  if name is None:
462
333
  name = original_func.__name__
463
334
 
464
- # Create input data with ground truth
465
- input_data = self._extract_input_data(args, kwargs, result)
466
-
467
335
  # Create LLM component
468
336
  llm_component = self.create_llm_component(
469
337
  component_id=component_id,
@@ -501,12 +369,6 @@ class LLMTracerMixin:
501
369
  name = self.current_llm_call_name.get()
502
370
  if name is None:
503
371
  name = original_func.__name__
504
-
505
- end_memory = psutil.Process().memory_info().rss
506
- memory_used = max(0, end_memory - start_memory)
507
-
508
- # Get parent agent ID if exists
509
- parent_agent_id = self.current_agent_id.get() if hasattr(self, 'current_agent_id') else None
510
372
 
511
373
  llm_component = self.create_llm_component(
512
374
  component_id=component_id,
@@ -514,24 +376,15 @@ class LLMTracerMixin:
514
376
  name=name,
515
377
  llm_type="unknown",
516
378
  version="1.0.0",
517
- memory_used=memory_used,
379
+ memory_used=0,
518
380
  start_time=start_time,
519
381
  end_time=end_time,
520
- input_data=self._extract_input_data(args, kwargs, None),
382
+ input_data=extract_input_data(args, kwargs, None),
521
383
  output_data=None,
522
- error=error_component,
523
- parent_id=parent_agent_id
384
+ error=error_component
524
385
  )
525
386
 
526
- # If this is part of an agent trace, add it to agent's children
527
- if parent_agent_id and hasattr(self, 'agent_children'):
528
- parent_children = self.agent_children.get()
529
- parent_children.append(llm_component)
530
- self.agent_children.set(parent_children)
531
- else:
532
- # Only add to root components if no parent
533
- self.add_component(llm_component)
534
-
387
+ self.add_component(llm_component)
535
388
  raise
536
389
 
537
390
  def trace_llm_call_sync(self, original_func, *args, **kwargs):
@@ -563,10 +416,11 @@ class LLMTracerMixin:
563
416
  memory_used = max(0, end_memory - start_memory)
564
417
 
565
418
  # Extract token usage and calculate cost
566
- token_usage = self._extract_token_usage(result)
567
- model_name = self._extract_model_name(args, kwargs, result)
568
- cost = self._calculate_cost(token_usage, model_name)
569
- parameters = self._extract_parameters(kwargs)
419
+ model_name = extract_model_name(args, kwargs, result)
420
+ token_usage = extract_token_usage(result)
421
+ cost = calculate_llm_cost(token_usage, model_name, self.model_costs)
422
+ parameters = extract_parameters(kwargs)
423
+ input_data = extract_input_data(args, kwargs, result)
570
424
 
571
425
  # End tracking network calls for this component
572
426
  self.end_component(component_id)
@@ -574,9 +428,6 @@ class LLMTracerMixin:
574
428
  name = self.current_llm_call_name.get()
575
429
  if name is None:
576
430
  name = original_func.__name__
577
-
578
- # Create input data with ground truth
579
- input_data = self._extract_input_data(args, kwargs, result)
580
431
 
581
432
  # Create LLM component
582
433
  llm_component = self.create_llm_component(
@@ -594,7 +445,7 @@ class LLMTracerMixin:
594
445
  usage=token_usage,
595
446
  parameters=parameters
596
447
  )
597
-
448
+ self.llm_data = llm_component
598
449
  self.add_component(llm_component)
599
450
  return result
600
451
 
@@ -618,9 +469,6 @@ class LLMTracerMixin:
618
469
  end_memory = psutil.Process().memory_info().rss
619
470
  memory_used = max(0, end_memory - start_memory)
620
471
 
621
- # Get parent agent ID if exists
622
- parent_agent_id = self.current_agent_id.get() if hasattr(self, 'current_agent_id') else None
623
-
624
472
  llm_component = self.create_llm_component(
625
473
  component_id=component_id,
626
474
  hash_id=hash_id,
@@ -630,24 +478,16 @@ class LLMTracerMixin:
630
478
  memory_used=memory_used,
631
479
  start_time=start_time,
632
480
  end_time=end_time,
633
- input_data=self._extract_input_data(args, kwargs, None),
481
+ input_data=extract_input_data(args, kwargs, None),
634
482
  output_data=None,
635
- error=error_component,
636
- parent_id=parent_agent_id
483
+ error=error_component
637
484
  )
638
-
639
- # If this is part of an agent trace, add it to agent's children
640
- if parent_agent_id and hasattr(self, 'agent_children'):
641
- parent_children = self.agent_children.get()
642
- parent_children.append(llm_component)
643
- self.agent_children.set(parent_children)
644
- else:
645
- # Only add to root components if no parent
646
- self.add_component(llm_component)
647
-
485
+ self.llm_data = llm_component
486
+ self.add_component(llm_component, is_error=True)
648
487
  raise
649
488
 
650
489
  def trace_llm(self, name: str = None):
490
+ self.current_llm_call_name.set(name)
651
491
  def decorator(func):
652
492
  @self.file_tracker.trace_decorator
653
493
  @functools.wraps(func)
@@ -661,7 +501,6 @@ class LLMTracerMixin:
661
501
  parent_agent_id = self.current_agent_id.get()
662
502
  self.start_component(component_id)
663
503
 
664
- start_time = datetime.now()
665
504
  error_info = None
666
505
  result = None
667
506
 
@@ -681,7 +520,8 @@ class LLMTracerMixin:
681
520
  finally:
682
521
 
683
522
  llm_component = self.llm_data
684
- llm_component['name'] = name
523
+ if (name is not None) or (name != ""):
524
+ llm_component['name'] = name
685
525
 
686
526
  if self.gt:
687
527
  llm_component["data"]["gt"] = self.gt
@@ -731,6 +571,8 @@ class LLMTracerMixin:
731
571
  finally:
732
572
 
733
573
  llm_component = self.llm_data
574
+ if (name is not None) or (name != ""):
575
+ llm_component['name'] = name
734
576
 
735
577
  if error_info:
736
578
  llm_component["error"] = error_info["error"]
@@ -755,85 +597,3 @@ class LLMTracerMixin:
755
597
  except Exception as e:
756
598
  print(f"Error unpatching {method_name}: {str(e)}")
757
599
  self.patches = []
758
-
759
- def _sanitize_api_keys(self, data):
760
- """Remove sensitive information from data"""
761
- if isinstance(data, dict):
762
- return {k: self._sanitize_api_keys(v) for k, v in data.items()
763
- if not any(sensitive in k.lower() for sensitive in ['key', 'token', 'secret', 'password'])}
764
- elif isinstance(data, list):
765
- return [self._sanitize_api_keys(item) for item in data]
766
- elif isinstance(data, tuple):
767
- return tuple(self._sanitize_api_keys(item) for item in data)
768
- return data
769
-
770
- def _sanitize_input(self, args, kwargs):
771
- """Convert input arguments to text format.
772
-
773
- Args:
774
- args: Input arguments that may contain nested dictionaries
775
-
776
- Returns:
777
- str: Text representation of the input arguments
778
- """
779
- if isinstance(args, dict):
780
- return str({k: self._sanitize_input(v, {}) for k, v in args.items()})
781
- elif isinstance(args, (list, tuple)):
782
- return str([self._sanitize_input(item, {}) for item in args])
783
- return str(args)
784
-
785
- def extract_llm_output(result):
786
- """Extract output from LLM response"""
787
- class OutputResponse:
788
- def __init__(self, output_response):
789
- self.output_response = output_response
790
-
791
- # Handle coroutines
792
- if asyncio.iscoroutine(result):
793
- # For sync context, run the coroutine
794
- if not asyncio.get_event_loop().is_running():
795
- result = asyncio.run(result)
796
- else:
797
- # We're in an async context, but this function is called synchronously
798
- # Return a placeholder and let the caller handle the coroutine
799
- return OutputResponse("Coroutine result pending")
800
-
801
- # Handle Google GenerativeAI format
802
- if hasattr(result, "result"):
803
- candidates = getattr(result.result, "candidates", [])
804
- output = []
805
- for candidate in candidates:
806
- content = getattr(candidate, "content", None)
807
- if content and hasattr(content, "parts"):
808
- for part in content.parts:
809
- if hasattr(part, "text"):
810
- output.append({
811
- "content": part.text,
812
- "role": getattr(content, "role", "assistant"),
813
- "finish_reason": getattr(candidate, "finish_reason", None)
814
- })
815
- return OutputResponse(output)
816
-
817
- # Handle Vertex AI format
818
- if hasattr(result, "text"):
819
- return OutputResponse([{
820
- "content": result.text,
821
- "role": "assistant"
822
- }])
823
-
824
- # Handle OpenAI format
825
- if hasattr(result, "choices"):
826
- return OutputResponse([{
827
- "content": choice.message.content,
828
- "role": choice.message.role
829
- } for choice in result.choices])
830
-
831
- # Handle Anthropic format
832
- if hasattr(result, "completion"):
833
- return OutputResponse([{
834
- "content": result.completion,
835
- "role": "assistant"
836
- }])
837
-
838
- # Default case
839
- return OutputResponse(str(result))