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.
- ragaai_catalyst/tracers/agentic_tracing/README.md +97 -0
- ragaai_catalyst/tracers/agentic_tracing/__init__.py +4 -2
- ragaai_catalyst/tracers/agentic_tracing/data/__init__.py +0 -0
- ragaai_catalyst/tracers/agentic_tracing/{data_structure.py → data/data_structure.py} +14 -1
- ragaai_catalyst/tracers/agentic_tracing/tests/__init__.py +0 -0
- ragaai_catalyst/tracers/agentic_tracing/tracers/__init__.py +0 -0
- ragaai_catalyst/tracers/agentic_tracing/{agent_tracer.py → tracers/agent_tracer.py} +15 -10
- ragaai_catalyst/tracers/agentic_tracing/{base.py → tracers/base.py} +5 -6
- ragaai_catalyst/tracers/agentic_tracing/{llm_tracer.py → tracers/llm_tracer.py} +40 -280
- ragaai_catalyst/tracers/agentic_tracing/{agentic_tracing.py → tracers/main_tracer.py} +41 -8
- ragaai_catalyst/tracers/agentic_tracing/{tool_tracer.py → tracers/tool_tracer.py} +3 -3
- ragaai_catalyst/tracers/agentic_tracing/upload/__init__.py +0 -0
- ragaai_catalyst/tracers/agentic_tracing/{upload_agentic_traces.py → upload/upload_agentic_traces.py} +3 -0
- ragaai_catalyst/tracers/agentic_tracing/utils/llm_utils.py +229 -43
- ragaai_catalyst/tracers/tracer.py +11 -12
- {ragaai_catalyst-2.1b4.dist-info → ragaai_catalyst-2.1.1.dist-info}/METADATA +5 -5
- ragaai_catalyst-2.1.1.dist-info/RECORD +60 -0
- {ragaai_catalyst-2.1b4.dist-info → ragaai_catalyst-2.1.1.dist-info}/WHEEL +1 -1
- ragaai_catalyst/tracers/agentic_tracing/utils/data_classes.py +0 -61
- ragaai_catalyst-2.1b4.dist-info/RECORD +0 -56
- /ragaai_catalyst/tracers/agentic_tracing/{examples → tests}/FinancialAnalysisSystem.ipynb +0 -0
- /ragaai_catalyst/tracers/agentic_tracing/{examples → tests}/GameActivityEventPlanner.ipynb +0 -0
- /ragaai_catalyst/tracers/agentic_tracing/{examples → tests}/TravelPlanner.ipynb +0 -0
- /ragaai_catalyst/tracers/agentic_tracing/{sample.py → tests/ai_travel_agent.py} +0 -0
- /ragaai_catalyst/tracers/agentic_tracing/{unique_decorator_test.py → tests/unique_decorator_test.py} +0 -0
- /ragaai_catalyst/tracers/agentic_tracing/{network_tracer.py → tracers/network_tracer.py} +0 -0
- /ragaai_catalyst/tracers/agentic_tracing/{user_interaction_tracer.py → tracers/user_interaction_tracer.py} +0 -0
- /ragaai_catalyst/tracers/agentic_tracing/{upload_code.py → upload/upload_code.py} +0 -0
- /ragaai_catalyst/tracers/agentic_tracing/{file_name_tracker.py → utils/file_name_tracker.py} +0 -0
- /ragaai_catalyst/tracers/agentic_tracing/{unique_decorator.py → utils/unique_decorator.py} +0 -0
- /ragaai_catalyst/tracers/agentic_tracing/{zip_list_of_unique_files.py → utils/zip_list_of_unique_files.py} +0 -0
- {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 .
|
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']
|
File without changes
|
@@ -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
|
File without changes
|
File without changes
|
@@ -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 =
|
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.
|
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.
|
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 .
|
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 .
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
453
|
-
|
454
|
-
cost =
|
455
|
-
parameters =
|
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=
|
379
|
+
memory_used=0,
|
518
380
|
start_time=start_time,
|
519
381
|
end_time=end_time,
|
520
|
-
input_data=
|
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
|
-
|
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
|
-
|
567
|
-
|
568
|
-
cost =
|
569
|
-
parameters =
|
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=
|
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
|
-
|
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
|
-
|
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))
|