ragaai-catalyst 2.2.4b5__py3-none-any.whl → 2.2.5b2__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 +0 -2
  2. ragaai_catalyst/dataset.py +59 -1
  3. ragaai_catalyst/tracers/agentic_tracing/tracers/main_tracer.py +5 -285
  4. ragaai_catalyst/tracers/agentic_tracing/utils/__init__.py +0 -2
  5. ragaai_catalyst/tracers/agentic_tracing/utils/create_dataset_schema.py +1 -1
  6. ragaai_catalyst/tracers/exporters/__init__.py +1 -2
  7. ragaai_catalyst/tracers/exporters/file_span_exporter.py +0 -1
  8. ragaai_catalyst/tracers/exporters/ragaai_trace_exporter.py +23 -1
  9. ragaai_catalyst/tracers/tracer.py +6 -186
  10. {ragaai_catalyst-2.2.4b5.dist-info → ragaai_catalyst-2.2.5b2.dist-info}/METADATA +1 -1
  11. {ragaai_catalyst-2.2.4b5.dist-info → ragaai_catalyst-2.2.5b2.dist-info}/RECORD +14 -45
  12. ragaai_catalyst/experiment.py +0 -486
  13. ragaai_catalyst/tracers/agentic_tracing/tests/FinancialAnalysisSystem.ipynb +0 -536
  14. ragaai_catalyst/tracers/agentic_tracing/tests/GameActivityEventPlanner.ipynb +0 -134
  15. ragaai_catalyst/tracers/agentic_tracing/tests/TravelPlanner.ipynb +0 -563
  16. ragaai_catalyst/tracers/agentic_tracing/tests/__init__.py +0 -0
  17. ragaai_catalyst/tracers/agentic_tracing/tests/ai_travel_agent.py +0 -197
  18. ragaai_catalyst/tracers/agentic_tracing/tests/unique_decorator_test.py +0 -172
  19. ragaai_catalyst/tracers/agentic_tracing/tracers/agent_tracer.py +0 -687
  20. ragaai_catalyst/tracers/agentic_tracing/tracers/base.py +0 -1319
  21. ragaai_catalyst/tracers/agentic_tracing/tracers/custom_tracer.py +0 -347
  22. ragaai_catalyst/tracers/agentic_tracing/tracers/langgraph_tracer.py +0 -0
  23. ragaai_catalyst/tracers/agentic_tracing/tracers/llm_tracer.py +0 -1182
  24. ragaai_catalyst/tracers/agentic_tracing/tracers/network_tracer.py +0 -288
  25. ragaai_catalyst/tracers/agentic_tracing/tracers/tool_tracer.py +0 -557
  26. ragaai_catalyst/tracers/agentic_tracing/tracers/user_interaction_tracer.py +0 -129
  27. ragaai_catalyst/tracers/agentic_tracing/upload/upload_local_metric.py +0 -74
  28. ragaai_catalyst/tracers/agentic_tracing/utils/api_utils.py +0 -21
  29. ragaai_catalyst/tracers/agentic_tracing/utils/generic.py +0 -32
  30. ragaai_catalyst/tracers/agentic_tracing/utils/get_user_trace_metrics.py +0 -28
  31. ragaai_catalyst/tracers/agentic_tracing/utils/span_attributes.py +0 -133
  32. ragaai_catalyst/tracers/agentic_tracing/utils/supported_llm_provider.toml +0 -34
  33. ragaai_catalyst/tracers/exporters/raga_exporter.py +0 -467
  34. ragaai_catalyst/tracers/langchain_callback.py +0 -821
  35. ragaai_catalyst/tracers/llamaindex_callback.py +0 -361
  36. ragaai_catalyst/tracers/llamaindex_instrumentation.py +0 -424
  37. ragaai_catalyst/tracers/upload_traces.py +0 -170
  38. ragaai_catalyst/tracers/utils/convert_langchain_callbacks_output.py +0 -62
  39. ragaai_catalyst/tracers/utils/convert_llama_instru_callback.py +0 -69
  40. ragaai_catalyst/tracers/utils/extraction_logic_llama_index.py +0 -74
  41. ragaai_catalyst/tracers/utils/langchain_tracer_extraction_logic.py +0 -82
  42. ragaai_catalyst/tracers/utils/rag_trace_json_converter.py +0 -403
  43. {ragaai_catalyst-2.2.4b5.dist-info → ragaai_catalyst-2.2.5b2.dist-info}/WHEEL +0 -0
  44. {ragaai_catalyst-2.2.4b5.dist-info → ragaai_catalyst-2.2.5b2.dist-info}/licenses/LICENSE +0 -0
  45. {ragaai_catalyst-2.2.4b5.dist-info → ragaai_catalyst-2.2.5b2.dist-info}/top_level.txt +0 -0
@@ -1,1319 +0,0 @@
1
- import json
2
- import os
3
- from datetime import datetime
4
- from pathlib import Path
5
- from typing import List, Any, Dict, Optional
6
- import uuid
7
- import sys
8
- import tempfile
9
- import threading
10
- import time
11
-
12
- from ragaai_catalyst.tracers.agentic_tracing.upload.upload_local_metric import calculate_metric
13
- from ragaai_catalyst import RagaAICatalyst
14
- from ragaai_catalyst.tracers.agentic_tracing.data.data_structure import (
15
- Trace,
16
- Metadata,
17
- SystemInfo,
18
- Resources,
19
- Component,
20
- )
21
- from ragaai_catalyst.tracers.agentic_tracing.utils.file_name_tracker import TrackName
22
- from ragaai_catalyst.tracers.agentic_tracing.utils.zip_list_of_unique_files import zip_list_of_unique_files
23
- from ragaai_catalyst.tracers.agentic_tracing.utils.span_attributes import SpanAttributes
24
- from ragaai_catalyst.tracers.agentic_tracing.utils.system_monitor import SystemMonitor
25
- from ragaai_catalyst.tracers.agentic_tracing.upload.trace_uploader import submit_upload_task, get_task_status, ensure_uploader_running
26
-
27
- import logging
28
-
29
- logger = logging.getLogger(__name__)
30
- logging_level = (
31
- logger.setLevel(logging.DEBUG) if os.getenv("DEBUG") == "1" else logging.INFO
32
- )
33
-
34
-
35
- class TracerJSONEncoder(json.JSONEncoder):
36
- def default(self, obj):
37
- if isinstance(obj, datetime):
38
- return obj.isoformat()
39
- if isinstance(obj, bytes):
40
- try:
41
- return obj.decode("utf-8")
42
- except UnicodeDecodeError:
43
- return str(obj) # Fallback to string representation
44
- if hasattr(obj, "to_dict"): # Handle objects with to_dict method
45
- return obj.to_dict()
46
- if hasattr(obj, "__dict__"):
47
- # Filter out None values and handle nested serialization
48
- return {
49
- k: v
50
- for k, v in obj.__dict__.items()
51
- if v is not None and not k.startswith("_")
52
- }
53
- try:
54
- # Try to convert to a basic type
55
- return str(obj)
56
- except:
57
- return None # Last resort: return None instead of failing
58
-
59
-
60
- class BaseTracer:
61
- def __init__(self, user_details):
62
- self.user_details = user_details
63
- self.project_name = self.user_details["project_name"]
64
- self.dataset_name = self.user_details["dataset_name"]
65
- self.project_id = self.user_details["project_id"]
66
- self.trace_name = self.user_details["trace_name"]
67
- self.base_url = self.user_details.get("base_url", RagaAICatalyst.BASE_URL) # Get base_url from user_details or fallback to default
68
- self.visited_metrics = []
69
- self.trace_metrics = []
70
-
71
- # Initialize trace data
72
- self.trace_id = None
73
- self.start_time = None
74
- self.components: List[Component] = []
75
- self.file_tracker = TrackName()
76
- self.span_attributes_dict = {}
77
-
78
- self.interval_time = self.user_details['interval_time']
79
- self.memory_usage_list = []
80
- self.cpu_usage_list = []
81
- self.disk_usage_list = []
82
- self.network_usage_list = []
83
- self.tracking_thread = None
84
- self.tracking = False
85
- self.system_monitor = None
86
- self.gt = None
87
-
88
- # For post processing of tracing file before uploading
89
- self.post_processor = None
90
-
91
- # For upload tracking
92
- self.upload_task_id = None
93
-
94
- # For backward compatibility
95
- self._upload_tasks = []
96
- self._is_uploading = False
97
- self._upload_completed_callback = None
98
- self.timeout = self.user_details.get("timeout", 120)
99
-
100
- ensure_uploader_running()
101
-
102
- def _get_system_info(self) -> SystemInfo:
103
- return self.system_monitor.get_system_info()
104
-
105
- def _get_resources(self) -> Resources:
106
- return self.system_monitor.get_resources()
107
-
108
- def _track_memory_usage(self):
109
- self.memory_usage_list = []
110
- while self.tracking:
111
- usage = self.system_monitor.track_memory_usage()
112
- self.memory_usage_list.append(usage)
113
- try:
114
- time.sleep(self.interval_time)
115
- except Exception as e:
116
- logger.warning(f"Sleep interrupted in memory tracking: {str(e)}")
117
-
118
- def _track_cpu_usage(self):
119
- self.cpu_usage_list = []
120
- while self.tracking:
121
- usage = self.system_monitor.track_cpu_usage(self.interval_time)
122
- self.cpu_usage_list.append(usage)
123
- try:
124
- time.sleep(self.interval_time)
125
- except Exception as e:
126
- logger.warning(f"Sleep interrupted in CPU tracking: {str(e)}")
127
-
128
- def _track_disk_usage(self):
129
- self.disk_usage_list = []
130
- while self.tracking:
131
- usage = self.system_monitor.track_disk_usage()
132
- self.disk_usage_list.append(usage)
133
- try:
134
- time.sleep(self.interval_time)
135
- except Exception as e:
136
- logger.warning(f"Sleep interrupted in disk tracking: {str(e)}")
137
-
138
- def _track_network_usage(self):
139
- self.network_usage_list = []
140
- while self.tracking:
141
- usage = self.system_monitor.track_network_usage()
142
- self.network_usage_list.append(usage)
143
- try:
144
- time.sleep(self.interval_time)
145
- except Exception as e:
146
- logger.warning(f"Sleep interrupted in network tracking: {str(e)}")
147
-
148
- def register_post_processor(self, post_processor_func):
149
- """
150
- Register a post-processing function that will be called after trace generation.
151
-
152
- Args:
153
- post_processor_func (callable): A function that takes a trace JSON file path as input
154
- and returns a processed trace JSON file path.
155
- The function signature should be:
156
- def post_processor_func(original_trace_json_path: os.PathLike) -> os.PathLike
157
- """
158
- if not callable(post_processor_func):
159
- raise TypeError("post_processor_func must be a callable")
160
- self.post_processor = post_processor_func
161
- logger.debug("Post-processor function registered successfully in BaseTracer")
162
-
163
- def start(self):
164
- """Initialize a new trace"""
165
- self.tracking = True
166
- self.trace_id = str(uuid.uuid4())
167
- self.file_tracker.trace_main_file()
168
- self.system_monitor = SystemMonitor(self.trace_id)
169
- threading.Thread(target=self._track_memory_usage).start()
170
- threading.Thread(target=self._track_cpu_usage).start()
171
- threading.Thread(target=self._track_disk_usage).start()
172
- threading.Thread(target=self._track_network_usage).start()
173
-
174
- # Reset metrics
175
- self.visited_metrics = []
176
- self.trace_metrics = []
177
-
178
- metadata = Metadata(
179
- cost={},
180
- tokens={},
181
- system_info=self._get_system_info(),
182
- resources=self._get_resources(),
183
- )
184
-
185
- # Get the start time
186
- self.start_time = datetime.now().astimezone().isoformat()
187
-
188
- self.data_key = [
189
- {
190
- "start_time": datetime.now().astimezone().isoformat(),
191
- "end_time": "",
192
- "spans": self.components,
193
- }
194
- ]
195
-
196
- self.trace = Trace(
197
- id=self.trace_id,
198
- trace_name=self.trace_name,
199
- project_name=self.project_name,
200
- start_time=datetime.now().astimezone().isoformat(),
201
- end_time="", # Will be set when trace is stopped
202
- metadata=metadata,
203
- data=self.data_key,
204
- replays={"source": None},
205
- metrics=[] # Initialize empty metrics list
206
- )
207
-
208
- def on_upload_completed(self, callback_fn):
209
- """
210
- Register a callback function to be called when all uploads are completed.
211
- For backward compatibility - simulates the old callback mechanism.
212
-
213
- Args:
214
- callback_fn: A function that takes a single argument (the tracer instance)
215
- """
216
- self._upload_completed_callback = callback_fn
217
-
218
- # Check for status periodically and call callback when complete
219
- def check_status_and_callback():
220
- if self.upload_task_id:
221
- status = self.get_upload_status()
222
- if status.get("status") in ["completed", "failed"]:
223
- self._is_uploading = False
224
- # Execute callback
225
- try:
226
- if self._upload_completed_callback:
227
- self._upload_completed_callback(self)
228
- except Exception as e:
229
- logger.error(f"Error in upload completion callback: {e}")
230
- return
231
-
232
- # Schedule next check
233
- threading.Timer(5.0, check_status_and_callback).start()
234
-
235
- # Start status checking if we already have a task
236
- if self.upload_task_id:
237
- threading.Timer(5.0, check_status_and_callback).start()
238
-
239
- return self
240
-
241
- def wait_for_uploads(self, timeout=None):
242
- """
243
- Wait for all async uploads to complete.
244
- This provides backward compatibility with the old API.
245
-
246
- Args:
247
- timeout: Maximum time to wait in seconds (None means wait indefinitely)
248
-
249
- Returns:
250
- True if all uploads completed successfully, False otherwise
251
- """
252
- if not self.upload_task_id:
253
- return True
254
-
255
- start_time = time.time()
256
- while True:
257
- # Check if timeout expired
258
- if timeout is not None and time.time() - start_time > timeout:
259
- logger.warning(f"Upload wait timed out after {timeout} seconds")
260
- return False
261
-
262
- # Get current status
263
- status = self.get_upload_status()
264
- if status.get("status") == "completed":
265
- return True
266
- elif status.get("status") == "failed":
267
- logger.error(f"Upload failed: {status.get('error')}")
268
- return False
269
- elif status.get("status") == "unknown":
270
- logger.warning("Upload task not found, assuming completed")
271
- return True
272
-
273
- # Sleep before checking again
274
- time.sleep(1.0)
275
-
276
- def stop(self):
277
- """Stop the trace and save to JSON file, then submit to background uploader"""
278
- if hasattr(self, "trace"):
279
- # Set end times
280
- self.trace.data[0]["end_time"] = datetime.now().astimezone().isoformat()
281
- self.trace.end_time = datetime.now().astimezone().isoformat()
282
-
283
- # Stop tracking metrics
284
- self.tracking = False
285
-
286
- # Process and aggregate metrics
287
- self._process_resource_metrics()
288
-
289
- # Process trace spans
290
- self.trace = self._change_span_ids_to_int(self.trace)
291
- self.trace = self._change_agent_input_output(self.trace)
292
- # self.trace = self._extract_cost_tokens(self.trace)
293
-
294
- # Create traces directory and prepare file paths
295
- self.traces_dir = tempfile.gettempdir()
296
- filename = self.trace.id + ".json"
297
- filepath = f"{self.traces_dir}/{filename}"
298
-
299
- # Process source files
300
- list_of_unique_files = self.file_tracker.get_unique_files()
301
- hash_id, zip_path = zip_list_of_unique_files(
302
- list_of_unique_files, output_dir=self.traces_dir
303
- )
304
- self.trace.metadata.system_info.source_code = hash_id
305
-
306
- # Prepare trace data for saving
307
- trace_data = self.trace.to_dict()
308
- trace_data["metrics"] = self.trace_metrics
309
- cleaned_trace_data = self._clean_trace(trace_data)
310
- cleaned_trace_data = self._extract_cost_tokens(cleaned_trace_data)
311
-
312
- # Add interactions
313
- interactions = self.format_interactions()
314
- cleaned_trace_data["workflow"] = interactions["workflow"]
315
-
316
- # Save trace data to file
317
- with open(filepath, "w") as f:
318
- json.dump(cleaned_trace_data, f, cls=TracerJSONEncoder, indent=2)
319
-
320
- logger.info("Traces saved successfully.")
321
- logger.debug(f"Trace saved to {filepath}")
322
-
323
- # Apply post-processor if registered
324
- if self.post_processor is not None:
325
- try:
326
- filepath = self.post_processor(filepath)
327
- logger.debug(f"Post-processor applied successfully in BaseTracer, new path: {filepath}")
328
- except Exception as e:
329
- logger.error(f"Error in post-processing in BaseTracer: {e}")
330
-
331
- # Make sure uploader process is available
332
- ensure_uploader_running()
333
-
334
- logger.debug("Base URL used for uploading: {}".format(self.base_url))
335
- # Submit to background process for uploading using futures
336
- self.upload_task_id = submit_upload_task(
337
- filepath=filepath,
338
- hash_id=hash_id,
339
- zip_path=zip_path,
340
- project_name=self.project_name,
341
- project_id=self.project_id,
342
- dataset_name=self.dataset_name,
343
- user_details=self.user_details,
344
- base_url=self.base_url,
345
- timeout=self.timeout
346
- )
347
-
348
- # For backward compatibility
349
- self._is_uploading = True
350
-
351
- # Start checking for completion if a callback is registered
352
- if self._upload_completed_callback:
353
- # Start a thread to check status and call callback when complete
354
- def check_status_and_callback():
355
- status = self.get_upload_status()
356
- if status.get("status") in ["completed", "failed"]:
357
- self._is_uploading = False
358
- # Execute callback
359
- try:
360
- self._upload_completed_callback(self)
361
- except Exception as e:
362
- logger.error(f"Error in upload completion callback: {e}")
363
- return
364
-
365
- # Check again after a delay
366
- threading.Timer(5.0, check_status_and_callback).start()
367
-
368
- # Start checking
369
- threading.Timer(5.0, check_status_and_callback).start()
370
-
371
- logger.info(f"Submitted upload task with ID: {self.upload_task_id}")
372
-
373
- # Cleanup local resources
374
- self.components = []
375
- self.file_tracker.reset()
376
-
377
- def get_upload_status(self):
378
- """
379
- Get the status of the upload task.
380
-
381
- Returns:
382
- dict: Status information
383
- """
384
- if not self.upload_task_id:
385
- return {"status": "not_started", "message": "No upload has been initiated"}
386
-
387
- return get_task_status(self.upload_task_id)
388
-
389
- def _process_resource_metrics(self):
390
- """Process and aggregate all resource metrics"""
391
- # Process memory metrics
392
- self.trace.metadata.resources.memory.values = self.memory_usage_list
393
-
394
- # Process CPU metrics
395
- self.trace.metadata.resources.cpu.values = self.cpu_usage_list
396
-
397
- # Process network and disk metrics
398
- network_uploads, network_downloads = 0, 0
399
- disk_read, disk_write = 0, 0
400
-
401
- # Handle cases where lists might have different lengths
402
- min_len = min(len(self.network_usage_list), len(self.disk_usage_list)) if self.network_usage_list and self.disk_usage_list else 0
403
- for i in range(min_len):
404
- network_usage = self.network_usage_list[i]
405
- disk_usage = self.disk_usage_list[i]
406
-
407
- # Safely get network usage values with defaults of 0
408
- network_uploads += network_usage.get('uploads', 0) or 0
409
- network_downloads += network_usage.get('downloads', 0) or 0
410
-
411
- # Safely get disk usage values with defaults of 0
412
- disk_read += disk_usage.get('disk_read', 0) or 0
413
- disk_write += disk_usage.get('disk_write', 0) or 0
414
-
415
- # Set aggregate values
416
- disk_list_len = len(self.disk_usage_list)
417
- self.trace.metadata.resources.disk.read = [disk_read / disk_list_len if disk_list_len > 0 else 0]
418
- self.trace.metadata.resources.disk.write = [disk_write / disk_list_len if disk_list_len > 0 else 0]
419
-
420
- network_list_len = len(self.network_usage_list)
421
- self.trace.metadata.resources.network.uploads = [
422
- network_uploads / network_list_len if network_list_len > 0 else 0]
423
- self.trace.metadata.resources.network.downloads = [
424
- network_downloads / network_list_len if network_list_len > 0 else 0]
425
-
426
- # Set interval times
427
- self.trace.metadata.resources.cpu.interval = float(self.interval_time)
428
- self.trace.metadata.resources.memory.interval = float(self.interval_time)
429
- self.trace.metadata.resources.disk.interval = float(self.interval_time)
430
- self.trace.metadata.resources.network.interval = float(self.interval_time)
431
-
432
- def add_component(self, component: Component):
433
- """Add a component to the trace"""
434
- self.components.append(component)
435
-
436
- def __enter__(self):
437
- self.start()
438
- return self
439
-
440
- def __exit__(self, exc_type, exc_value, traceback):
441
- self.stop()
442
-
443
- def _process_children(self, children_list, parent_id, current_id):
444
- """Helper function to process children recursively."""
445
- for child in children_list:
446
- child["id"] = current_id
447
- child["parent_id"] = parent_id
448
- current_id += 1
449
- # Recursively process nested children if they exist
450
- if "children" in child["data"]:
451
- current_id = self._process_children(child["data"]["children"], child["id"], current_id)
452
- return current_id
453
-
454
- def _change_span_ids_to_int(self, trace):
455
- id, parent_id = 1, 0
456
- for span in trace.data[0]["spans"]:
457
- span.id = id
458
- span.parent_id = parent_id
459
- id += 1
460
- if span.type == "agent" and "children" in span.data:
461
- id = self._process_children(span.data["children"], span.id, id)
462
- return trace
463
-
464
- def _change_agent_input_output(self, trace):
465
- for span in trace.data[0]["spans"]:
466
- if span.type == "agent":
467
- childrens = span.data["children"]
468
- span.data["input"] = None
469
- span.data["output"] = None
470
- if childrens:
471
- # Find first non-null input going forward
472
- for child in childrens:
473
- if "data" not in child:
474
- continue
475
- input_data = child["data"].get("input")
476
-
477
- if input_data:
478
- span.data["input"] = (
479
- input_data["args"]
480
- if hasattr(input_data, "args")
481
- else input_data
482
- )
483
- break
484
-
485
- # Find first non-null output going backward
486
- for child in reversed(childrens):
487
- if "data" not in child:
488
- continue
489
- output_data = child["data"].get("output")
490
-
491
- if output_data and output_data != "" and output_data != "None":
492
- span.data["output"] = output_data
493
- break
494
- return trace
495
-
496
- def _extract_cost_tokens(self, trace):
497
- cost = {}
498
- tokens = {}
499
-
500
- def process_span_info(info):
501
- if not isinstance(info, dict):
502
- return
503
- cost_info = info.get("cost", {})
504
- for key, value in cost_info.items():
505
- if key not in cost:
506
- cost[key] = 0
507
- cost[key] += value
508
- token_info = info.get("tokens", {})
509
- for key, value in token_info.items():
510
- if key not in tokens:
511
- tokens[key] = 0
512
- tokens[key] += value
513
-
514
- def process_spans(spans):
515
- for span in spans:
516
- # Get span type, handling both span objects and dictionaries
517
- span_type = span.type if hasattr(span, 'type') else span.get('type')
518
- span_info = span.info if hasattr(span, 'info') else span.get('info', {})
519
- span_data = span.data if hasattr(span, 'data') else span.get('data', {})
520
-
521
- # Process direct LLM spans
522
- if span_type == "llm":
523
- process_span_info(span_info)
524
- # Process agent spans recursively
525
- elif span_type == "agent":
526
- # Process LLM children in the current agent span
527
- children = span_data.get("children", [])
528
- for child in children:
529
- child_type = child.get("type")
530
- if child_type == "llm":
531
- process_span_info(child.get("info", {}))
532
- # Recursively process nested agent spans
533
- elif child_type == "agent":
534
- process_spans([child])
535
-
536
- process_spans(trace["data"][0]["spans"])
537
- trace["metadata"].cost = cost
538
- trace["metadata"].tokens = tokens
539
- trace["metadata"].total_cost = cost.get("total_cost", 0)
540
- trace["metadata"].total_tokens = tokens.get("total_tokens", 0)
541
- return trace
542
-
543
- def _clean_trace(self, trace):
544
- # Convert span to dict if it has to_dict method
545
- def _to_dict_if_needed(obj):
546
- if hasattr(obj, "to_dict"):
547
- return obj.to_dict()
548
- return obj
549
-
550
- def deduplicate_spans(spans):
551
- seen_llm_spans = {} # Dictionary to track unique LLM spans
552
- unique_spans = []
553
-
554
- for span in spans:
555
- # Convert span to dictionary if needed
556
- span_dict = _to_dict_if_needed(span)
557
-
558
- # Skip spans without hash_id
559
- if "hash_id" not in span_dict:
560
- continue
561
-
562
- if span_dict.get("type") == "llm":
563
- # Create a unique key based on hash_id, input, and output
564
- span_key = (
565
- span_dict.get("hash_id"),
566
- str(span_dict.get("data", {}).get("input")),
567
- str(span_dict.get("data", {}).get("output")),
568
- )
569
-
570
- # Check if we've seen this span before
571
- if span_key not in seen_llm_spans:
572
- seen_llm_spans[span_key] = True
573
- unique_spans.append(span)
574
- else:
575
- # If we have interactions in the current span, replace the existing one
576
- current_interactions = span_dict.get("interactions", [])
577
- if current_interactions:
578
- # Find and replace the existing span with this one that has interactions
579
- for i, existing_span in enumerate(unique_spans):
580
- existing_dict = (
581
- existing_span
582
- if isinstance(existing_span, dict)
583
- else existing_span.__dict__
584
- )
585
- if (
586
- existing_dict.get("hash_id")
587
- == span_dict.get("hash_id")
588
- and str(existing_dict.get("data", {}).get("input"))
589
- == str(span_dict.get("data", {}).get("input"))
590
- and str(existing_dict.get("data", {}).get("output"))
591
- == str(span_dict.get("data", {}).get("output"))
592
- ):
593
- unique_spans[i] = span
594
- break
595
-
596
- else:
597
- # For non-LLM spans, process their children if they exist
598
- if "data" in span_dict and "children" in span_dict["data"]:
599
- children = span_dict["data"]["children"]
600
- # Filter and deduplicate children
601
- filtered_children = deduplicate_spans(children)
602
- if isinstance(span, dict):
603
- span["data"]["children"] = filtered_children
604
- else:
605
- span.data["children"] = filtered_children
606
- unique_spans.append(span)
607
-
608
- # Process spans to update model information for LLM spans with same name
609
- llm_spans_by_name = {}
610
- for i, span in enumerate(unique_spans):
611
- span_dict = span if isinstance(span, dict) else span.__dict__
612
-
613
- if span_dict.get('type') == 'llm':
614
- span_name = span_dict.get('name')
615
- if span_name:
616
- if span_name not in llm_spans_by_name:
617
- llm_spans_by_name[span_name] = []
618
- llm_spans_by_name[span_name].append((i, span_dict))
619
-
620
- # Update model information for spans with same name
621
- for spans_with_same_name in llm_spans_by_name.values():
622
- if len(spans_with_same_name) > 1:
623
- # Check if any span has non-default model
624
- has_custom_model = any(
625
- span[1].get('info', {}).get('model') != 'default'
626
- for span in spans_with_same_name
627
- )
628
-
629
- # If we have a custom model, update all default models to 'custom'
630
- if has_custom_model:
631
- for idx, span_dict in spans_with_same_name:
632
- if span_dict.get('info', {}).get('model') == 'default':
633
- if isinstance(unique_spans[idx], dict):
634
- if 'info' not in unique_spans[idx]:
635
- unique_spans[idx]['info'] = {}
636
- # unique_spans[idx]['info']['model'] = 'custom'
637
- unique_spans[idx]['type'] = 'custom'
638
- else:
639
- if not hasattr(unique_spans[idx], 'info'):
640
- unique_spans[idx].info = {}
641
- # unique_spans[idx].info['model'] = 'custom'
642
- unique_spans[idx].type = 'custom'
643
-
644
- return unique_spans
645
-
646
- # Remove any spans without hash ids
647
- for data in trace.get("data", []):
648
- if "spans" in data:
649
- # First filter out spans without hash_ids, then deduplicate
650
- data["spans"] = deduplicate_spans(data["spans"])
651
-
652
- return trace
653
-
654
- def add_tags(self, tags: List[str]):
655
- raise NotImplementedError
656
-
657
- def _process_child_interactions(self, child, interaction_id, interactions):
658
- """
659
- Helper method to process child interactions recursively.
660
-
661
- Args:
662
- child (dict): The child span to process
663
- interaction_id (int): Current interaction ID
664
- interactions (list): List of interactions to append to
665
-
666
- Returns:
667
- int: Next interaction ID to use
668
- """
669
- child_type = child.get("type")
670
-
671
- if child_type == "tool":
672
- # Tool call start
673
- interactions.append(
674
- {
675
- "id": str(interaction_id),
676
- "span_id": child.get("id"),
677
- "interaction_type": "tool_call_start",
678
- "name": child.get("name"),
679
- "content": {
680
- "parameters": [
681
- child.get("data", {}).get("input", {}).get("args"),
682
- child.get("data", {}).get("input", {}).get("kwargs"),
683
- ]
684
- },
685
- "timestamp": child.get("start_time"),
686
- "error": child.get("error"),
687
- }
688
- )
689
- interaction_id += 1
690
-
691
- # Tool call end
692
- interactions.append(
693
- {
694
- "id": str(interaction_id),
695
- "span_id": child.get("id"),
696
- "interaction_type": "tool_call_end",
697
- "name": child.get("name"),
698
- "content": {
699
- "returns": child.get("data", {}).get("output"),
700
- },
701
- "timestamp": child.get("end_time"),
702
- "error": child.get("error"),
703
- }
704
- )
705
- interaction_id += 1
706
-
707
- elif child_type == "llm":
708
- interactions.append(
709
- {
710
- "id": str(interaction_id),
711
- "span_id": child.get("id"),
712
- "interaction_type": "llm_call_start",
713
- "name": child.get("name"),
714
- "content": {
715
- "prompt": child.get("data", {}).get("input"),
716
- },
717
- "timestamp": child.get("start_time"),
718
- "error": child.get("error"),
719
- }
720
- )
721
- interaction_id += 1
722
-
723
- interactions.append(
724
- {
725
- "id": str(interaction_id),
726
- "span_id": child.get("id"),
727
- "interaction_type": "llm_call_end",
728
- "name": child.get("name"),
729
- "content": {"response": child.get("data", {}).get("output")},
730
- "timestamp": child.get("end_time"),
731
- "error": child.get("error"),
732
- }
733
- )
734
- interaction_id += 1
735
-
736
- elif child_type == "agent":
737
- interactions.append(
738
- {
739
- "id": str(interaction_id),
740
- "span_id": child.get("id"),
741
- "interaction_type": "agent_call_start",
742
- "name": child.get("name"),
743
- "content": None,
744
- "timestamp": child.get("start_time"),
745
- "error": child.get("error"),
746
- }
747
- )
748
- interaction_id += 1
749
-
750
- # Process nested children recursively
751
- if "children" in child.get("data", {}):
752
- for nested_child in child["data"]["children"]:
753
- interaction_id = self._process_child_interactions(
754
- nested_child, interaction_id, interactions
755
- )
756
-
757
- interactions.append(
758
- {
759
- "id": str(interaction_id),
760
- "span_id": child.get("id"),
761
- "interaction_type": "agent_call_end",
762
- "name": child.get("name"),
763
- "content": child.get("data", {}).get("output"),
764
- "timestamp": child.get("end_time"),
765
- "error": child.get("error"),
766
- }
767
- )
768
- interaction_id += 1
769
-
770
- else:
771
- interactions.append(
772
- {
773
- "id": str(interaction_id),
774
- "span_id": child.get("id"),
775
- "interaction_type": f"{child_type}_call_start",
776
- "name": child.get("name"),
777
- "content": child.get("data", {}),
778
- "timestamp": child.get("start_time"),
779
- "error": child.get("error"),
780
- }
781
- )
782
- interaction_id += 1
783
-
784
- interactions.append(
785
- {
786
- "id": str(interaction_id),
787
- "span_id": child.get("id"),
788
- "interaction_type": f"{child_type}_call_end",
789
- "name": child.get("name"),
790
- "content": child.get("data", {}),
791
- "timestamp": child.get("end_time"),
792
- "error": child.get("error"),
793
- }
794
- )
795
- interaction_id += 1
796
-
797
- # Process additional interactions and network calls
798
- if "interactions" in child:
799
- for interaction in child["interactions"]:
800
- interaction["id"] = str(interaction_id)
801
- interaction["span_id"] = child.get("id")
802
- interaction["error"] = None
803
- interactions.append(interaction)
804
- interaction_id += 1
805
-
806
- if "network_calls" in child:
807
- for child_network_call in child["network_calls"]:
808
- network_call = {}
809
- network_call["id"] = str(interaction_id)
810
- network_call["span_id"] = child.get("id")
811
- network_call["interaction_type"] = "network_call"
812
- network_call["name"] = None
813
- network_call["content"] = {
814
- "request": {
815
- "url": child_network_call.get("url"),
816
- "method": child_network_call.get("method"),
817
- "headers": child_network_call.get("headers"),
818
- },
819
- "response": {
820
- "status_code": child_network_call.get("status_code"),
821
- "headers": child_network_call.get("response_headers"),
822
- "body": child_network_call.get("response_body"),
823
- },
824
- }
825
- network_call["timestamp"] = child_network_call.get("start_time")
826
- network_call["error"] = child_network_call.get("error")
827
- interactions.append(network_call)
828
- interaction_id += 1
829
-
830
- return interaction_id
831
-
832
- def format_interactions(self) -> dict:
833
- """
834
- Format interactions from trace data into a standardized format.
835
- Returns a dictionary containing formatted interactions based on trace data.
836
-
837
- The function processes spans from self.trace and formats them into interactions
838
- of various types including: agent_start, agent_end, input, output, tool_call_start,
839
- tool_call_end, llm_call, file_read, file_write, network_call.
840
-
841
- Returns:
842
- dict: A dictionary with "workflow" key containing a list of interactions
843
- sorted by timestamp.
844
- """
845
- interactions = []
846
- interaction_id = 1
847
-
848
- if not hasattr(self, "trace") or not self.trace.data:
849
- return {"workflow": []}
850
-
851
- for span in self.trace.data[0]["spans"]:
852
- # Process agent spans
853
- if span.type == "agent":
854
- # Add agent_start interaction
855
- interactions.append(
856
- {
857
- "id": str(interaction_id),
858
- "span_id": span.id,
859
- "interaction_type": "agent_call_start",
860
- "name": span.name,
861
- "content": None,
862
- "timestamp": span.start_time,
863
- "error": span.error,
864
- }
865
- )
866
- interaction_id += 1
867
-
868
- # Process children of agent recursively
869
- if "children" in span.data:
870
- for child in span.data["children"]:
871
- interaction_id = self._process_child_interactions(
872
- child, interaction_id, interactions
873
- )
874
-
875
- # Add agent_end interaction
876
- interactions.append(
877
- {
878
- "id": str(interaction_id),
879
- "span_id": span.id,
880
- "interaction_type": "agent_call_end",
881
- "name": span.name,
882
- "content": span.data.get("output"),
883
- "timestamp": span.end_time,
884
- "error": span.error,
885
- }
886
- )
887
- interaction_id += 1
888
-
889
- elif span.type == "tool":
890
- interactions.append(
891
- {
892
- "id": str(interaction_id),
893
- "span_id": span.id,
894
- "interaction_type": "tool_call_start",
895
- "name": span.name,
896
- "content": {
897
- "prompt": span.data.get("input"),
898
- "response": span.data.get("output"),
899
- },
900
- "timestamp": span.start_time,
901
- "error": span.error,
902
- }
903
- )
904
- interaction_id += 1
905
-
906
- interactions.append(
907
- {
908
- "id": str(interaction_id),
909
- "span_id": span.id,
910
- "interaction_type": "tool_call_end",
911
- "name": span.name,
912
- "content": {
913
- "prompt": span.data.get("input"),
914
- "response": span.data.get("output"),
915
- },
916
- "timestamp": span.end_time,
917
- "error": span.error,
918
- }
919
- )
920
- interaction_id += 1
921
-
922
- elif span.type == "llm":
923
- interactions.append(
924
- {
925
- "id": str(interaction_id),
926
- "span_id": span.id,
927
- "interaction_type": "llm_call_start",
928
- "name": span.name,
929
- "content": {
930
- "prompt": span.data.get("input"),
931
- },
932
- "timestamp": span.start_time,
933
- "error": span.error,
934
- }
935
- )
936
- interaction_id += 1
937
-
938
- interactions.append(
939
- {
940
- "id": str(interaction_id),
941
- "span_id": span.id,
942
- "interaction_type": "llm_call_end",
943
- "name": span.name,
944
- "content": {"response": span.data.get("output")},
945
- "timestamp": span.end_time,
946
- "error": span.error,
947
- }
948
- )
949
- interaction_id += 1
950
-
951
- else:
952
- interactions.append(
953
- {
954
- "id": str(interaction_id),
955
- "span_id": span.id,
956
- "interaction_type": f"{span.type}_call_start",
957
- "name": span.name,
958
- "content": span.data,
959
- "timestamp": span.start_time,
960
- "error": span.error,
961
- }
962
- )
963
- interaction_id += 1
964
-
965
- interactions.append(
966
- {
967
- "id": str(interaction_id),
968
- "span_id": span.id,
969
- "interaction_type": f"{span.type}_call_end",
970
- "name": span.name,
971
- "content": span.data,
972
- "timestamp": span.end_time,
973
- "error": span.error,
974
- }
975
- )
976
- interaction_id += 1
977
-
978
- # Process interactions from span.data if they exist
979
- if span.interactions:
980
- for span_interaction in span.interactions:
981
- interaction = {}
982
- interaction["id"] = str(interaction_id)
983
- interaction["span_id"] = span.id
984
- interaction["interaction_type"] = span_interaction.type
985
- interaction["content"] = span_interaction.content
986
- interaction["timestamp"] = span_interaction.timestamp
987
- interaction["error"] = span.error
988
- interactions.append(interaction)
989
- interaction_id += 1
990
-
991
- if span.network_calls:
992
- for span_network_call in span.network_calls:
993
- network_call = {}
994
- network_call["id"] = str(interaction_id)
995
- network_call["span_id"] = span.id
996
- network_call["interaction_type"] = "network_call"
997
- network_call["name"] = None
998
- network_call["content"] = {
999
- "request": {
1000
- "url": span_network_call.get("url"),
1001
- "method": span_network_call.get("method"),
1002
- "headers": span_network_call.get("headers"),
1003
- },
1004
- "response": {
1005
- "status_code": span_network_call.get("status_code"),
1006
- "headers": span_network_call.get("response_headers"),
1007
- "body": span_network_call.get("response_body"),
1008
- },
1009
- }
1010
- network_call["timestamp"] = span_network_call.get("timestamp")
1011
- network_call["error"] = span_network_call.get("error")
1012
- interactions.append(network_call)
1013
- interaction_id += 1
1014
-
1015
- # Sort interactions by timestamp
1016
- sorted_interactions = sorted(
1017
- interactions, key=lambda x: x["timestamp"] if x["timestamp"] else ""
1018
- )
1019
-
1020
- # Reassign IDs to maintain sequential order after sorting
1021
- for idx, interaction in enumerate(sorted_interactions, 1):
1022
- interaction["id"] = str(idx)
1023
-
1024
- return {"workflow": sorted_interactions}
1025
-
1026
- # TODO: Add support for execute metrics. Maintain list of all metrics to be added for this span
1027
-
1028
- def execute_metrics(self,
1029
- name: str,
1030
- model: str,
1031
- provider: str,
1032
- prompt: str,
1033
- context: str,
1034
- response: str
1035
- ):
1036
- if not hasattr(self, 'trace'):
1037
- logger.warning("Cannot add metrics before trace is initialized. Call start() first.")
1038
- return
1039
-
1040
- # Convert individual parameters to metric dict if needed
1041
- if isinstance(name, str):
1042
- metrics = [{
1043
- "name": name
1044
- }]
1045
- else:
1046
- # Handle dict or list input
1047
- metrics = name if isinstance(name, list) else [name] if isinstance(name, dict) else []
1048
-
1049
- try:
1050
- for metric in metrics:
1051
- if not isinstance(metric, dict):
1052
- raise ValueError(f"Expected dict, got {type(metric)}")
1053
-
1054
- if "name" not in metric :
1055
- raise ValueError("Metric must contain 'name'") #score was written not required here
1056
-
1057
- # Handle duplicate metric names on executing metric
1058
- metric_name = metric["name"]
1059
- if metric_name in self.visited_metrics:
1060
- count = sum(1 for m in self.visited_metrics if m.startswith(metric_name))
1061
- metric_name = f"{metric_name}_{count + 1}"
1062
- self.visited_metrics.append(metric_name)
1063
-
1064
- result = calculate_metric(project_id=self.project_id,
1065
- metric_name=metric_name,
1066
- model=model,
1067
- org_domain="raga",
1068
- provider=provider,
1069
- user_id="1", # self.user_details['id'],
1070
- prompt=prompt,
1071
- context=context,
1072
- response=response
1073
- )
1074
-
1075
- result = result['data']
1076
- formatted_metric = {
1077
- "name": metric_name,
1078
- "score": result.get("score"),
1079
- "reason": result.get("reason", ""),
1080
- "source": "user",
1081
- "cost": result.get("cost"),
1082
- "latency": result.get("latency"),
1083
- "mappings": [],
1084
- "config": result.get("metric_config", {})
1085
- }
1086
-
1087
- logger.debug(f"Executed metric: {formatted_metric}")
1088
-
1089
- except ValueError as e:
1090
- logger.error(f"Validation Error: {e}")
1091
- except Exception as e:
1092
- logger.error(f"Error adding metric: {e}")
1093
-
1094
- def add_metrics(
1095
- self,
1096
- name: str | List[Dict[str, Any]] | Dict[str, Any] = None,
1097
- score: float | int = None,
1098
- reasoning: str = "",
1099
- cost: float = None,
1100
- latency: float = None,
1101
- metadata: Dict[str, Any] = None,
1102
- config: Dict[str, Any] = None,
1103
- ):
1104
- """Add metrics at the trace level.
1105
-
1106
- Can be called in two ways:
1107
- 1. With individual parameters:
1108
- tracer.add_metrics(name="metric_name", score=0.9, reasoning="Good performance")
1109
-
1110
- 2. With a dictionary or list of dictionaries:
1111
- tracer.add_metrics({"name": "metric_name", "score": 0.9})
1112
- tracer.add_metrics([{"name": "metric1", "score": 0.9}, {"name": "metric2", "score": 0.8}])
1113
-
1114
- Args:
1115
- name: Either the metric name (str) or a metric dictionary/list of dictionaries
1116
- score: Score value (float or int) when using individual parameters
1117
- reasoning: Optional explanation for the score
1118
- cost: Optional cost associated with the metric
1119
- latency: Optional latency measurement
1120
- metadata: Optional additional metadata as key-value pairs
1121
- config: Optional configuration parameters
1122
- """
1123
- if not hasattr(self, 'trace'):
1124
- logger.warning("Cannot add metrics before trace is initialized. Call start() first.")
1125
- return
1126
-
1127
- # Convert individual parameters to metric dict if needed
1128
- if isinstance(name, str):
1129
- metrics = [{
1130
- "name": name,
1131
- "score": score,
1132
- "reasoning": reasoning,
1133
- "cost": cost,
1134
- "latency": latency,
1135
- "metadata": metadata or {},
1136
- "config": config or {}
1137
- }]
1138
- else:
1139
- # Handle dict or list input
1140
- metrics = name if isinstance(name, list) else [name] if isinstance(name, dict) else []
1141
-
1142
- try:
1143
- for metric in metrics:
1144
- if not isinstance(metric, dict):
1145
- raise ValueError(f"Expected dict, got {type(metric)}")
1146
-
1147
- if "name" not in metric or "score" not in metric:
1148
- raise ValueError("Metric must contain 'name' and 'score' fields")
1149
-
1150
- # Handle duplicate metric names
1151
- metric_name = metric["name"]
1152
- if metric_name in self.visited_metrics:
1153
- count = sum(1 for m in self.visited_metrics if m.startswith(metric_name))
1154
- metric_name = f"{metric_name}_{count + 1}"
1155
- self.visited_metrics.append(metric_name)
1156
-
1157
- formatted_metric = {
1158
- "name": metric_name,
1159
- "score": metric["score"],
1160
- "reason": metric.get("reasoning", ""),
1161
- "source": "user",
1162
- "cost": metric.get("cost"),
1163
- "latency": metric.get("latency"),
1164
- "metadata": metric.get("metadata", {}),
1165
- "mappings": [],
1166
- "config": metric.get("config", {})
1167
- }
1168
-
1169
- self.trace_metrics.append(formatted_metric)
1170
- logger.debug(f"Added trace-level metric: {formatted_metric}")
1171
-
1172
- except ValueError as e:
1173
- logger.error(f"Validation Error: {e}")
1174
- except Exception as e:
1175
- logger.error(f"Error adding metric: {e}")
1176
-
1177
- def span(self, span_name):
1178
- if span_name not in self.span_attributes_dict:
1179
- self.span_attributes_dict[span_name] = SpanAttributes(span_name, self.project_id)
1180
- return self.span_attributes_dict[span_name]
1181
-
1182
- @staticmethod
1183
- def get_formatted_metric(span_attributes_dict, project_id, name):
1184
- if name in span_attributes_dict:
1185
- local_metrics = span_attributes_dict[name].local_metrics or []
1186
- local_metrics_results = []
1187
- for metric in local_metrics:
1188
- try:
1189
- logger.info("calculating the metric, please wait....")
1190
-
1191
- mapping = metric.get("mapping", {})
1192
- result = calculate_metric(project_id=project_id,
1193
- metric_name=metric.get("name"),
1194
- model=metric.get("model"),
1195
- provider=metric.get("provider"),
1196
- **mapping
1197
- )
1198
-
1199
- result = result['data']['data'][0]
1200
- config = result['metric_config']
1201
- metric_config = {
1202
- "job_id": config.get("job_id"),
1203
- "metric_name": config.get("displayName"),
1204
- "model": config.get("model"),
1205
- "org_domain": config.get("orgDomain"),
1206
- "provider": config.get("provider"),
1207
- "reason": config.get("reason"),
1208
- "request_id": config.get("request_id"),
1209
- "user_id": config.get("user_id"),
1210
- "threshold": {
1211
- "is_editable": config.get("threshold").get("isEditable"),
1212
- "lte": config.get("threshold").get("lte")
1213
- }
1214
- }
1215
- formatted_metric = {
1216
- "name": metric.get("displayName"),
1217
- "displayName": metric.get("displayName"),
1218
- "score": result.get("score"),
1219
- "reason": result.get("reason", ""),
1220
- "source": "user",
1221
- "cost": result.get("cost"),
1222
- "latency": result.get("latency"),
1223
- "mappings": [],
1224
- "config": metric_config
1225
- }
1226
- local_metrics_results.append(formatted_metric)
1227
- except ValueError as e:
1228
- logger.error(f"Validation Error: {e}")
1229
- except Exception as e:
1230
- logger.error(f"Error executing metric: {e}")
1231
-
1232
- return local_metrics_results
1233
-
1234
-
1235
- def upload_directly(self):
1236
- """Upload trace directly without using the background process"""
1237
- # Check if we have necessary details
1238
- if not hasattr(self, 'trace') or not self.trace_id:
1239
- print("No trace to upload")
1240
- return False
1241
-
1242
- # Get the filepath from the last trace
1243
- trace_dir = tempfile.gettempdir()
1244
- trace_file = os.path.join(trace_dir, f"{self.trace_id}.json")
1245
-
1246
- # If filepath wasn't saved from previous stop() call, try to find it
1247
- if not os.path.exists(trace_file):
1248
- print(f"Looking for trace file for {self.trace_id}")
1249
- # Try to find the trace file by pattern
1250
- for file in os.listdir(trace_dir):
1251
- if file.endswith(".json") and self.trace_id in file:
1252
- trace_file = os.path.join(trace_dir, file)
1253
- print(f"Found trace file: {trace_file}")
1254
- break
1255
-
1256
- if not os.path.exists(trace_file):
1257
- print(f"Trace file not found for ID {self.trace_id}")
1258
- return False
1259
-
1260
- print(f"Starting direct upload of {trace_file}")
1261
-
1262
- try:
1263
- # 1. Create the dataset schema
1264
- print("Creating dataset schema...")
1265
- from ragaai_catalyst.tracers.agentic_tracing.utils.create_dataset_schema import create_dataset_schema_with_trace
1266
- response = create_dataset_schema_with_trace(
1267
- dataset_name=self.dataset_name,
1268
- project_name=self.project_name
1269
- )
1270
- print(f"Schema created: {response}")
1271
-
1272
- # 2. Get code hash and zip path if available
1273
- code_hash = None
1274
- zip_path = None
1275
- try:
1276
- with open(trace_file, 'r') as f:
1277
- data = json.load(f)
1278
- code_hash = data.get("metadata", {}).get("system_info", {}).get("source_code")
1279
- if code_hash:
1280
- zip_path = os.path.join(trace_dir, f"{code_hash}.zip")
1281
- print(f"Found code hash: {code_hash}")
1282
- print(f"Zip path: {zip_path}")
1283
- except Exception as e:
1284
- print(f"Error getting code hash: {e}")
1285
-
1286
- # 3. Upload agentic traces
1287
- print("Uploading agentic traces...")
1288
- from ragaai_catalyst.tracers.agentic_tracing.upload.upload_agentic_traces import UploadAgenticTraces
1289
- from ragaai_catalyst import RagaAICatalyst
1290
- upload_traces = UploadAgenticTraces(
1291
- json_file_path=trace_file,
1292
- project_name=self.project_name,
1293
- project_id=self.project_id,
1294
- dataset_name=self.dataset_name,
1295
- user_detail=self.user_details,
1296
- base_url=RagaAICatalyst.BASE_URL,
1297
- )
1298
- upload_traces.upload_agentic_traces()
1299
- print("Agentic traces uploaded successfully")
1300
-
1301
- # 4. Upload code hash if available
1302
- if code_hash and zip_path and os.path.exists(zip_path):
1303
- print(f"Uploading code hash: {code_hash}")
1304
- from ragaai_catalyst.tracers.agentic_tracing.upload.upload_code import upload_code
1305
- response = upload_code(
1306
- hash_id=code_hash,
1307
- zip_path=zip_path,
1308
- project_name=self.project_name,
1309
- dataset_name=self.dataset_name,
1310
- )
1311
- print(f"Code uploaded: {response}")
1312
-
1313
- print("Upload completed successfully - check UI now")
1314
- return True
1315
- except Exception as e:
1316
- print(f"Error during direct upload: {e}")
1317
- import traceback
1318
- traceback.print_exc()
1319
- return False