ragaai-catalyst 2.2.4b4__py3-none-any.whl → 2.2.4.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.
@@ -0,0 +1,156 @@
1
+ import logging
2
+ import os
3
+ import threading
4
+
5
+ from requests.adapters import HTTPAdapter
6
+ from urllib3.util.retry import Retry
7
+ from urllib3.exceptions import PoolError, MaxRetryError, NewConnectionError
8
+ from requests.exceptions import ConnectionError, Timeout
9
+ from http.client import RemoteDisconnected
10
+ from ragaai_catalyst import RagaAICatalyst
11
+ import requests
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class SessionManager:
17
+ """Shared session manager with connection pooling for HTTP requests"""
18
+ _instance = None
19
+ _session = None
20
+ _lock = threading.Lock()
21
+
22
+ def __new__(cls):
23
+ if cls._instance is None:
24
+ with cls._lock: # Thread-safe singleton
25
+ if cls._instance is None: # Double-check locking
26
+ logger.info("Creating new SessionManager singleton instance")
27
+ cls._instance = super(SessionManager, cls).__new__(cls)
28
+ cls._instance._initialize_session()
29
+ else:
30
+ logger.debug("SessionManager instance already exists, returning existing instance")
31
+ else:
32
+ logger.debug("SessionManager instance exists, returning existing instance")
33
+ return cls._instance
34
+
35
+ def _initialize_session(self):
36
+ """Initialize session with connection pooling and retry strategy"""
37
+ logger.info("Initializing HTTP session with connection pooling and retry strategy")
38
+ self._session = requests.Session()
39
+
40
+ retry_strategy = Retry(
41
+ total=3, # number of retries
42
+ connect=3, # number of retries for connection-related errors
43
+ read=3, # number of retries for read-related errors
44
+ backoff_factor=0.5, # wait 0.5, 1, 2... seconds between retries
45
+ status_forcelist=[500, 502, 503, 504] # HTTP status codes to retry on
46
+ )
47
+
48
+ adapter = HTTPAdapter(
49
+ max_retries=retry_strategy,
50
+ pool_connections=5, # number of connection pools to cache (per host)
51
+ pool_maxsize=50, # maximum number of connections in each pool
52
+ pool_block=True # Block/wait when pool is full rather than raising error
53
+ )
54
+
55
+ self._session.mount("http://", adapter)
56
+ self._session.mount("https://", adapter)
57
+
58
+ # Set session-level configuration to handle connection issues
59
+ self._session.headers.update({
60
+ 'Connection': 'keep-alive',
61
+ 'User-Agent': 'RagaAI-Catalyst/1.0'
62
+ })
63
+
64
+ logger.info("HTTP session initialized successfully with adapters mounted for http:// and https://")
65
+
66
+ # Warm up connection pool using RagaAICatalyst.BASE_URL
67
+ if os.getenv("RAGAAI_CATALYST_BASE_URL") is not None:
68
+ base_url = os.getenv("RAGAAI_CATALYST_BASE_URL")
69
+ logger.info(f"Warming up connection pool using RagaAICatalyst.BASE_URL: {base_url}")
70
+ self.warm_up_connections(base_url)
71
+ else:
72
+ logger.warning(f"RAGAAI_CATALYST_BASE_URL not available, skipping connection warmup")
73
+
74
+ @property
75
+ def session(self):
76
+ if self._session is None:
77
+ logger.warning("Session accessed but not initialized, reinitializing...")
78
+ self._initialize_session()
79
+ return self._session
80
+
81
+ def warm_up_connections(self, base_url, num_connections=3):
82
+ """
83
+ Warm up the connection pool by making lightweight requests to healthcheck endpoint.
84
+ This can help prevent RemoteDisconnected errors on initial requests.
85
+ """
86
+ if not self._session:
87
+ return
88
+
89
+ # Construct healthcheck URL
90
+ healthcheck_url = f"{base_url.rstrip('/')}/healthcheck"
91
+ logger.info(f"Warming up connection pool with {num_connections} connections to {healthcheck_url}")
92
+
93
+ for i in range(num_connections):
94
+ try:
95
+ # Make a lightweight HEAD request to the healthcheck endpoint to warm up the connection
96
+ response = self._session.head(healthcheck_url, timeout=10)
97
+ logger.info(f"Warmup connection {i+1}: Status {response.status_code}")
98
+ except Exception as e:
99
+ logger.warning(f"Warmup connection {i+1} failed (this may be normal): {e}")
100
+ # Ignore other failures during warmup as they're expected
101
+ continue
102
+
103
+ logger.info("Connection pool warmup completed")
104
+
105
+ def close(self):
106
+ """Close the session"""
107
+ if self._session:
108
+ logger.info("Closing HTTP session")
109
+ self._session.close()
110
+ self._session = None
111
+ logger.info("HTTP session closed successfully")
112
+ else:
113
+ logger.debug("Close called but session was already None")
114
+
115
+ def handle_request_exceptions(self, e, operation_name):
116
+ """Handle common request exceptions with appropriate logging"""
117
+ logger.error(f"Exception occurred during {operation_name}")
118
+ if isinstance(e, (PoolError, MaxRetryError)):
119
+ logger.error(f"Connection pool exhausted during {operation_name}: {e}")
120
+ elif isinstance(e, NewConnectionError):
121
+ logger.error(f"Failed to establish new connection during {operation_name}: {e}")
122
+ elif isinstance(e, RemoteDisconnected):
123
+ logger.error(f"Remote connection closed unexpectedly during {operation_name}: {e}")
124
+ elif isinstance(e, ConnectionError):
125
+ logger.error(f"Connection error during {operation_name}: {e}")
126
+ elif isinstance(e, Timeout):
127
+ logger.error(f"Request timeout during {operation_name}: {e}")
128
+ else:
129
+ logger.error(f"Unexpected error during {operation_name}: {e}")
130
+
131
+ def make_request_with_retry(self, method, url, **kwargs):
132
+ """
133
+ Make HTTP request with additional retry logic for RemoteDisconnected errors
134
+ that may not be caught by urllib3's retry mechanism.
135
+ """
136
+ max_retries = 3
137
+ for attempt in range(max_retries):
138
+ try:
139
+ response = self._session.request(method, url, **kwargs)
140
+ return response
141
+ except (RemoteDisconnected, ConnectionError) as e:
142
+ logger.warning(f"Connection error on attempt {attempt + 1}/{max_retries}: {e}")
143
+ if attempt == max_retries - 1:
144
+ # Re-raise the exception on the last attempt
145
+ raise
146
+ # Wait before retrying (exponential backoff)
147
+ import time
148
+ wait_time = 2 ** attempt
149
+ logger.info(f"Retrying in {wait_time} seconds...")
150
+ time.sleep(wait_time)
151
+
152
+
153
+ # Global session manager instance
154
+ logger.info("Creating global SessionManager instance")
155
+ session_manager = SessionManager()
156
+ logger.info(f"Global SessionManager instance created with ID: {id(session_manager)}")
@@ -22,6 +22,7 @@ from typing import Dict, Any, Optional
22
22
  import threading
23
23
  import uuid
24
24
 
25
+
25
26
  # Set up logging
26
27
  log_dir = os.path.join(tempfile.gettempdir(), "ragaai_logs")
27
28
  os.makedirs(log_dir, exist_ok=True)
@@ -49,11 +50,13 @@ try:
49
50
  from ragaai_catalyst.tracers.agentic_tracing.upload.upload_code import upload_code
50
51
  # from ragaai_catalyst.tracers.agentic_tracing.upload.upload_trace_metric import upload_trace_metric
51
52
  from ragaai_catalyst.tracers.agentic_tracing.utils.create_dataset_schema import create_dataset_schema_with_trace
53
+ from ragaai_catalyst.tracers.agentic_tracing.upload.session_manager import session_manager
52
54
  from ragaai_catalyst import RagaAICatalyst
53
55
  IMPORTS_AVAILABLE = True
54
56
  except ImportError:
55
57
  logger.warning("RagaAI Catalyst imports not available - running in test mode")
56
58
  IMPORTS_AVAILABLE = False
59
+ session_manager = None
57
60
 
58
61
  # Define task queue directory
59
62
  QUEUE_DIR = os.path.join(tempfile.gettempdir(), "ragaai_tasks")
@@ -72,6 +75,10 @@ _executor_lock = threading.Lock()
72
75
  _futures: Dict[str, Any] = {}
73
76
  _futures_lock = threading.Lock()
74
77
 
78
+ # Dataset creation cache to avoid redundant API calls
79
+ _dataset_cache: Dict[str, Dict[str, Any]] = {}
80
+ _dataset_cache_lock = threading.Lock()
81
+ DATASET_CACHE_DURATION = 600 # 10 minutes in seconds
75
82
 
76
83
  _cleanup_lock = threading.Lock()
77
84
  _last_cleanup = 0
@@ -88,7 +95,7 @@ def get_executor(max_workers=None):
88
95
  if _executor is None:
89
96
  # Calculate optimal worker count
90
97
  if max_workers is None:
91
- max_workers = min(32, (os.cpu_count() or 1) * 4)
98
+ max_workers = min(8, (os.cpu_count() or 1) * 4)
92
99
 
93
100
  logger.info(f"Creating ThreadPoolExecutor with {max_workers} workers")
94
101
  _executor = concurrent.futures.ThreadPoolExecutor(
@@ -110,9 +117,57 @@ def generate_unique_task_id():
110
117
  unique_id = str(uuid.uuid4())[:8] # Short UUID
111
118
  return f"task_{int(time.time())}_{os.getpid()}_{counter}_{unique_id}"
112
119
 
120
+ def _generate_dataset_cache_key(dataset_name: str, project_name: str, base_url: str) -> str:
121
+ """Generate a unique cache key for dataset creation"""
122
+ return f"{dataset_name}#{project_name}#{base_url}"
123
+
124
+ def _is_dataset_cached(cache_key: str) -> bool:
125
+ """Check if dataset creation is cached and still valid"""
126
+ with _dataset_cache_lock:
127
+ if cache_key not in _dataset_cache:
128
+ return False
129
+
130
+ cache_entry = _dataset_cache[cache_key]
131
+ cache_time = cache_entry.get('timestamp', 0)
132
+ current_time = time.time()
133
+
134
+ # Check if cache is still valid (within 10 minutes)
135
+ if current_time - cache_time <= DATASET_CACHE_DURATION:
136
+ logger.info(f"Dataset creation cache hit for key: {cache_key}")
137
+ return True
138
+ else:
139
+ # Cache expired, remove it
140
+ logger.info(f"Dataset creation cache expired for key: {cache_key}")
141
+ del _dataset_cache[cache_key]
142
+ return False
143
+
144
+ def _cache_dataset_creation(cache_key: str, response: Any) -> None:
145
+ """Cache successful dataset creation"""
146
+ with _dataset_cache_lock:
147
+ _dataset_cache[cache_key] = {
148
+ 'timestamp': time.time(),
149
+ 'response': response
150
+ }
151
+
152
+ def _cleanup_expired_cache_entries() -> None:
153
+ """Remove expired cache entries"""
154
+ current_time = time.time()
155
+ with _dataset_cache_lock:
156
+ expired_keys = []
157
+ for cache_key, cache_entry in _dataset_cache.items():
158
+ cache_time = cache_entry.get('timestamp', 0)
159
+ if current_time - cache_time > DATASET_CACHE_DURATION:
160
+ expired_keys.append(cache_key)
161
+
162
+ for key in expired_keys:
163
+ del _dataset_cache[key]
164
+
165
+ if expired_keys:
166
+ logger.info(f"Cleaned up {len(expired_keys)} expired dataset cache entries")
167
+
113
168
  def process_upload(task_id: str, filepath: str, hash_id: str, zip_path: str,
114
169
  project_name: str, project_id: str, dataset_name: str,
115
- user_details: Dict[str, Any], base_url: str, timeout=120, fail_on_trace_error=True) -> Dict[str, Any]:
170
+ user_details: Dict[str, Any], base_url: str, tracer_type, timeout=120, fail_on_trace_error=True) -> Dict[str, Any]:
116
171
  """
117
172
  Process a single upload task
118
173
 
@@ -165,20 +220,36 @@ def process_upload(task_id: str, filepath: str, hash_id: str, zip_path: str,
165
220
  save_task_status(result)
166
221
  return result
167
222
 
168
- # Step 1: Create dataset schema
223
+ # Step 1: Create dataset schema (with caching)
169
224
  logger.info(f"Creating dataset schema for {dataset_name} with base_url: {base_url} and timeout: {timeout}")
170
- try:
171
- response = create_dataset_schema_with_trace(
172
- dataset_name=dataset_name,
173
- project_name=project_name,
174
- base_url=base_url,
175
- user_details=user_details,
176
- timeout=timeout
177
- )
178
- logger.info(f"Dataset schema created: {response}")
179
- except Exception as e:
180
- logger.error(f"Error creating dataset schema: {e}")
181
- # Continue with other steps
225
+
226
+ # Generate cache key and check if dataset creation is already cached
227
+ cache_key = _generate_dataset_cache_key(dataset_name, project_name, base_url)
228
+
229
+ if _is_dataset_cached(cache_key):
230
+ logger.info(f"Dataset schema creation skipped (cached) for {dataset_name}")
231
+ else:
232
+ try:
233
+ # Clean up expired cache entries periodically
234
+ # _cleanup_expired_cache_entries()
235
+
236
+ response = create_dataset_schema_with_trace(
237
+ dataset_name=dataset_name,
238
+ project_name=project_name,
239
+ base_url=base_url,
240
+ user_details=user_details,
241
+ timeout=timeout
242
+ )
243
+ logger.info(f"Dataset schema created: {response}")
244
+
245
+ # Cache the response only if status code is 200
246
+ if response and hasattr(response, 'status_code') and response.status_code in [200, 201]:
247
+ _cache_dataset_creation(cache_key, response)
248
+ logger.info(f"Response cached successfully for dataset: {dataset_name} and key: {cache_key}")
249
+
250
+ except Exception as e:
251
+ logger.error(f"Error creating dataset schema: {e}")
252
+ # Continue with other steps
182
253
 
183
254
  # Step 2: Upload trace metrics
184
255
  # if filepath and os.path.exists(filepath):
@@ -238,28 +309,34 @@ def process_upload(task_id: str, filepath: str, hash_id: str, zip_path: str,
238
309
  logger.error(error_msg)
239
310
 
240
311
  # Step 4: Upload code hash
241
- if hash_id and zip_path and os.path.exists(zip_path):
242
- logger.info(f"Uploading code hash {hash_id} with base_url: {base_url} and timeout: {timeout}")
243
- try:
244
- response = upload_code(
245
- hash_id=hash_id,
246
- zip_path=zip_path,
247
- project_name=project_name,
248
- dataset_name=dataset_name,
249
- base_url=base_url,
250
- timeout=timeout
251
- )
252
- logger.info(f"Code hash uploaded: {response}")
253
- except Exception as e:
254
- logger.error(f"Error uploading code hash: {e}")
255
- else:
256
- logger.warning(f"Code zip {zip_path} not found, skipping code upload")
257
-
312
+ if tracer_type.startswith("agentic/"):
313
+ logger.info(f"Tracer type '{tracer_type}' matches agentic pattern, proceeding with code upload")
314
+ if hash_id and zip_path and os.path.exists(zip_path):
315
+ logger.info(f"Uploading code hash {hash_id} with base_url: {base_url} and timeout: {timeout}")
316
+ try:
317
+ response = upload_code(
318
+ hash_id=hash_id,
319
+ zip_path=zip_path,
320
+ project_name=project_name,
321
+ dataset_name=dataset_name,
322
+ base_url=base_url,
323
+ timeout=timeout
324
+ )
325
+ if response is None:
326
+ error_msg = "Code hash not uploaded"
327
+ logger.error(error_msg)
328
+ else:
329
+ logger.info(f"Code hash uploaded successfully: {response}")
330
+ except Exception as e:
331
+ logger.error(f"Error uploading code hash: {e}")
332
+ else:
333
+ logger.warning(f"Code zip {zip_path} not found, skipping code upload")
334
+
258
335
  # Mark task as completed
259
336
  result["status"] = STATUS_COMPLETED
260
337
  result["end_time"] = datetime.now().isoformat()
261
338
  logger.info(f"Task {task_id} completed successfully")
262
-
339
+
263
340
  except Exception as e:
264
341
  logger.error(f"Error processing task {task_id}: {e}")
265
342
  result["status"] = STATUS_FAILED
@@ -302,7 +379,8 @@ def save_task_status(task_status: Dict[str, Any]):
302
379
  with open(status_path, "w") as f:
303
380
  json.dump(task_status, f, indent=2)
304
381
 
305
- def submit_upload_task(filepath, hash_id, zip_path, project_name, project_id, dataset_name, user_details, base_url, timeout=120):
382
+ def submit_upload_task(filepath, hash_id, zip_path, project_name, project_id, dataset_name, user_details, base_url,
383
+ tracer_type, timeout=120):
306
384
  """
307
385
  Submit a new upload task using futures.
308
386
 
@@ -349,6 +427,7 @@ def submit_upload_task(filepath, hash_id, zip_path, project_name, project_id, da
349
427
  dataset_name=dataset_name,
350
428
  user_details=user_details,
351
429
  base_url=base_url,
430
+ tracer_type = tracer_type,
352
431
  timeout=timeout,
353
432
  fail_on_trace_error=True
354
433
  )
@@ -379,6 +458,7 @@ def submit_upload_task(filepath, hash_id, zip_path, project_name, project_id, da
379
458
  dataset_name=dataset_name,
380
459
  user_details=user_details,
381
460
  base_url=base_url,
461
+ tracer_type=tracer_type,
382
462
  timeout=timeout,
383
463
  fail_on_trace_error=True
384
464
  )
@@ -550,6 +630,14 @@ def shutdown(timeout=120):
550
630
 
551
631
  _executor = None
552
632
 
633
+ # Close the session manager to clean up HTTP connections
634
+ if session_manager is not None:
635
+ try:
636
+ session_manager.close()
637
+ logger.info("Session manager closed successfully")
638
+ except Exception as e:
639
+ logger.error(f"Error closing session manager: {e}")
640
+
553
641
  # Register shutdown handler
554
642
  atexit.register(shutdown)
555
643
 
@@ -4,6 +4,10 @@ import os
4
4
  import re
5
5
  import time
6
6
  from urllib.parse import urlparse, urlunparse
7
+ from urllib3.exceptions import PoolError, MaxRetryError, NewConnectionError
8
+ from requests.exceptions import ConnectionError, Timeout, RequestException
9
+ from http.client import RemoteDisconnected
10
+ from .session_manager import session_manager
7
11
 
8
12
  import requests
9
13
 
@@ -48,7 +52,7 @@ class UploadAgenticTraces:
48
52
  start_time = time.time()
49
53
  endpoint = f"{self.base_url}/v1/llm/presigned-url"
50
54
  # Changed to POST from GET
51
- response = requests.request(
55
+ response = session_manager.make_request_with_retry(
52
56
  "POST", endpoint, headers=headers, data=payload, timeout=self.timeout
53
57
  )
54
58
  elapsed_ms = (time.time() - start_time) * 1000
@@ -62,7 +66,7 @@ class UploadAgenticTraces:
62
66
  return presignedurl
63
67
  else:
64
68
  # If POST fails, try GET
65
- response = requests.request(
69
+ response = session_manager.make_request_with_retry(
66
70
  "GET", endpoint, headers=headers, data=payload, timeout=self.timeout
67
71
  )
68
72
  elapsed_ms = (time.time() - start_time) * 1000
@@ -83,7 +87,7 @@ class UploadAgenticTraces:
83
87
  "Authorization": f"Bearer {token}",
84
88
  "X-Project-Name": self.project_name,
85
89
  }
86
- response = requests.request(
90
+ response = session_manager.make_request_with_retry(
87
91
  "POST",
88
92
  endpoint,
89
93
  headers=headers,
@@ -110,8 +114,10 @@ class UploadAgenticTraces:
110
114
  f"Error while getting presigned url: {response.json()['message']}"
111
115
  )
112
116
  return None
113
-
114
- except requests.exceptions.RequestException as e:
117
+ except (PoolError, MaxRetryError, NewConnectionError, ConnectionError, Timeout, RemoteDisconnected) as e:
118
+ session_manager.handle_request_exceptions(e, "getting presigned URL")
119
+ return None
120
+ except RequestException as e:
115
121
  logger.error(f"Error while getting presigned url: {e}")
116
122
  return None
117
123
 
@@ -138,16 +144,16 @@ class UploadAgenticTraces:
138
144
 
139
145
  if "blob.core.windows.net" in presignedUrl: # Azure
140
146
  headers["x-ms-blob-type"] = "BlockBlob"
141
- print("Uploading agentic traces...")
147
+ logger.info("Uploading agentic traces to presigned URL...")
142
148
  try:
143
149
  with open(filename) as f:
144
150
  payload = f.read().replace("\n", "").replace("\r", "").encode()
145
151
  except Exception as e:
146
- print(f"Error while reading file: {e}")
152
+ logger.error(f"Error while reading file: {e}")
147
153
  return False
148
154
  try:
149
155
  start_time = time.time()
150
- response = requests.request(
156
+ response = session_manager.make_request_with_retry(
151
157
  "PUT", presignedUrl, headers=headers, data=payload, timeout=self.timeout
152
158
  )
153
159
  elapsed_ms = (time.time() - start_time) * 1000
@@ -157,8 +163,11 @@ class UploadAgenticTraces:
157
163
  if response.status_code != 200 or response.status_code != 201:
158
164
  return response, response.status_code
159
165
  return True
160
- except requests.exceptions.RequestException as e:
161
- print(f"Error while uploading to presigned url: {e}")
166
+ except (PoolError, MaxRetryError, NewConnectionError, ConnectionError, Timeout, RemoteDisconnected) as e:
167
+ session_manager.handle_request_exceptions(e, "uploading trace to presigned URL")
168
+ return False
169
+ except RequestException as e:
170
+ logger.error(f"Error while uploading trace to presigned url: {e}")
162
171
  return False
163
172
 
164
173
  def insert_traces(self, presignedUrl):
@@ -177,16 +186,16 @@ class UploadAgenticTraces:
177
186
  try:
178
187
  start_time = time.time()
179
188
  endpoint = f"{self.base_url}/v1/llm/insert/trace"
180
- response = requests.request(
189
+ response = session_manager.make_request_with_retry(
181
190
  "POST", endpoint, headers=headers, data=payload, timeout=self.timeout
182
191
  )
183
192
  elapsed_ms = (time.time() - start_time) * 1000
184
193
  logger.debug(
185
194
  f"API Call: [POST] {endpoint} | Status: {response.status_code} | Time: {elapsed_ms:.2f}ms"
186
195
  )
187
- if response.status_code != 200:
188
- print(f"Error inserting traces: {response.json()['message']}")
189
- return False
196
+ if response.status_code in [200, 201]:
197
+ logger.info(f"Traces inserted successfully: {response.json()['message']}")
198
+ return True
190
199
  elif response.status_code == 401:
191
200
  logger.warning("Received 401 error. Attempting to refresh token.")
192
201
  token = RagaAICatalyst.get_token(force_refresh=True)
@@ -195,7 +204,7 @@ class UploadAgenticTraces:
195
204
  "Content-Type": "application/json",
196
205
  "X-Project-Name": self.project_name,
197
206
  }
198
- response = requests.request(
207
+ response = session_manager.make_request_with_retry(
199
208
  "POST",
200
209
  endpoint,
201
210
  headers=headers,
@@ -206,17 +215,21 @@ class UploadAgenticTraces:
206
215
  logger.debug(
207
216
  f"API Call: [POST] {endpoint} | Status: {response.status_code} | Time: {elapsed_ms:.2f}ms"
208
217
  )
209
- if response.status_code != 200:
210
- print(f"Error inserting traces: {response.json()['message']}")
211
- return False
218
+ if response.status_code in [200, 201]:
219
+ logger.info(f"Traces inserted successfully: {response.json()['message']}")
220
+ return True
212
221
  else:
213
- print("Error while inserting traces")
222
+ logger.error(f"Error while inserting traces after 401: {response.json()['message']}")
214
223
  return False
215
224
  else:
216
- return True
217
- except requests.exceptions.RequestException as e:
218
- print(f"Error while inserting traces: {e}")
219
- return None
225
+ logger.error(f"Error while inserting traces: {response.json()['message']}")
226
+ return False
227
+ except (PoolError, MaxRetryError, NewConnectionError, ConnectionError, Timeout, RemoteDisconnected) as e:
228
+ session_manager.handle_request_exceptions(e, "inserting traces")
229
+ return False
230
+ except RequestException as e:
231
+ logger.error(f"Error while inserting traces: {e}")
232
+ return False
220
233
 
221
234
  def _get_dataset_spans(self):
222
235
  try:
@@ -245,26 +258,26 @@ class UploadAgenticTraces:
245
258
  continue
246
259
  return dataset_spans
247
260
  except Exception as e:
248
- print(f"Error while reading dataset spans: {e}")
261
+ logger.error(f"Error while reading dataset spans: {e}")
249
262
  return None
250
263
 
251
264
  def upload_agentic_traces(self):
252
265
  try:
253
266
  presigned_url = self._get_presigned_url()
254
267
  if presigned_url is None:
255
- print("Warning: Failed to obtain presigned URL")
268
+ logger.warning("Warning: Failed to obtain presigned URL")
256
269
  return False
257
270
 
258
271
  # Upload the file using the presigned URL
259
272
  upload_result = self._put_presigned_url(presigned_url, self.json_file_path)
260
273
  if not upload_result:
261
- print("Error: Failed to upload file to presigned URL")
274
+ logger.error("Error: Failed to upload file to presigned URL")
262
275
  return False
263
276
  elif isinstance(upload_result, tuple):
264
277
  response, status_code = upload_result
265
278
  if status_code not in [200, 201]:
266
- print(
267
- f"Error: Upload failed with status code {status_code}: {response.text if hasattr(response, 'text') else 'Unknown error'}")
279
+ logger.error(
280
+ f"Error: Uploading agentic traces failed with status code {status_code}: {response.text if hasattr(response, 'text') else 'Unknown error'}")
268
281
  return False
269
282
  # Insert trace records
270
283
  insert_success = self.insert_traces(presigned_url)
@@ -272,13 +285,14 @@ class UploadAgenticTraces:
272
285
  print("Error: Failed to insert trace records")
273
286
  return False
274
287
 
275
- print("Successfully uploaded agentic traces")
288
+ logger.info("Successfully uploaded agentic traces")
276
289
  return True
277
290
  except FileNotFoundError:
278
- print(f"Error: Trace file not found at {self.json_file_path}")
291
+ logger.error(f"Error: Trace file not found at {self.json_file_path}")
279
292
  return False
280
293
  except ConnectionError as e:
281
- print(f"Error: Network connection failed while uploading traces: {e}")
294
+ logger.error(f"Error: Network connection failed while uploading traces: {e}")
282
295
  return False
283
296
  except Exception as e:
284
- print(f"Error while uploading agentic traces: {e}")
297
+ logger.error(f"Error while uploading agentic traces: {e}")
298
+ return False
@@ -1,15 +1,19 @@
1
- import json
2
1
  import logging
3
2
  import os
4
3
  import time
4
+ import re
5
+ import json
6
+ from urllib.parse import urlparse, urlunparse
7
+ from urllib3.exceptions import PoolError, MaxRetryError, NewConnectionError
8
+ from requests.exceptions import ConnectionError, Timeout, RequestException
9
+ from http.client import RemoteDisconnected
5
10
 
6
11
  import requests
7
12
 
8
13
  from ragaai_catalyst.ragaai_catalyst import RagaAICatalyst
14
+ from .session_manager import session_manager
9
15
 
10
16
  logger = logging.getLogger(__name__)
11
- import re
12
- from urllib.parse import urlparse, urlunparse
13
17
 
14
18
 
15
19
  def upload_code(
@@ -19,11 +23,26 @@ def upload_code(
19
23
  project_name, dataset_name, base_url, timeout=timeout
20
24
  )
21
25
 
26
+ # Handle None case during exceptions - do not proceed
27
+ if code_hashes_list is None:
28
+ logger.error("Failed to fetch existing code hashes, cannot proceed with upload")
29
+ return None
30
+
22
31
  if hash_id not in code_hashes_list:
23
32
  presigned_url = _fetch_presigned_url(
24
33
  project_name, dataset_name, base_url, timeout=timeout
25
34
  )
26
- _put_zip_presigned_url(project_name, presigned_url, zip_path, timeout=timeout)
35
+ # Handle None case for presigned URL
36
+ if presigned_url is None:
37
+ logger.error("Failed to fetch presigned URL, cannot proceed with upload")
38
+ return None
39
+
40
+ upload_result = _put_zip_presigned_url(project_name, presigned_url, zip_path, timeout=timeout)
41
+
42
+ # Handle upload failure
43
+ if upload_result is False or (isinstance(upload_result, tuple) and upload_result[1] not in [200, 201]):
44
+ logger.error("Failed to upload zip file")
45
+ return None
27
46
 
28
47
  response = _insert_code(
29
48
  dataset_name,
@@ -33,6 +52,10 @@ def upload_code(
33
52
  base_url,
34
53
  timeout=timeout,
35
54
  )
55
+ # Handle None response from insert_code
56
+ if response is None:
57
+ logger.error("Failed to insert code metadata")
58
+ return None
36
59
  return response
37
60
  else:
38
61
  return "Code already exists"
@@ -49,7 +72,7 @@ def _fetch_dataset_code_hashes(project_name, dataset_name, base_url=None, timeou
49
72
  url_base = base_url if base_url is not None else RagaAICatalyst.BASE_URL
50
73
  start_time = time.time()
51
74
  endpoint = f"{url_base}/v2/llm/dataset/code?datasetName={dataset_name}"
52
- response = requests.request(
75
+ response = session_manager.make_request_with_retry(
53
76
  "GET", endpoint, headers=headers, data=payload, timeout=timeout
54
77
  )
55
78
  elapsed_ms = (time.time() - start_time) * 1000
@@ -57,7 +80,7 @@ def _fetch_dataset_code_hashes(project_name, dataset_name, base_url=None, timeou
57
80
  f"API Call: [GET] {endpoint} | Status: {response.status_code} | Time: {elapsed_ms:.2f}ms"
58
81
  )
59
82
 
60
- if response.status_code == 200:
83
+ if response.status_code in [200, 201]:
61
84
  return response.json()["data"]["codeHashes"]
62
85
  elif response.status_code == 401:
63
86
  logger.warning("Received 401 error. Attempting to refresh token.")
@@ -66,22 +89,25 @@ def _fetch_dataset_code_hashes(project_name, dataset_name, base_url=None, timeou
66
89
  "Authorization": f"Bearer {os.getenv('RAGAAI_CATALYST_TOKEN')}",
67
90
  "X-Project-Name": project_name,
68
91
  }
69
- response = requests.request(
92
+ response = session_manager.make_request_with_retry(
70
93
  "GET", endpoint, headers=headers, data=payload, timeout=timeout
71
94
  )
72
95
  elapsed_ms = (time.time() - start_time) * 1000
73
- logger.debug(
74
- f"API Call: [GET] {endpoint} | Status: {response.status_code} | Time: {elapsed_ms:.2f}ms"
75
- )
76
- if response.status_code == 200:
96
+ logger.debug(f"API Call: [GET] {endpoint} | Status: {response.status_code} | Time: {elapsed_ms:.2f}ms")
97
+ if response.status_code in [200, 201]:
77
98
  return response.json()["data"]["codeHashes"]
78
99
  else:
79
- logger.error(
80
- f"Failed to fetch code hashes: {response.json()['message']}"
81
- )
82
- except requests.exceptions.RequestException as e:
100
+ logger.error(f"Failed to fetch code hashes: {response.json()['message']}")
101
+ return None
102
+ else:
103
+ logger.error(f"Error while fetching dataset code hashes: {response.json()['message']}")
104
+ return None
105
+ except (PoolError, MaxRetryError, NewConnectionError, ConnectionError, Timeout, RemoteDisconnected) as e:
106
+ session_manager.handle_request_exceptions(e, "fetching dataset code hashes")
107
+ return None
108
+ except RequestException as e:
83
109
  logger.error(f"Failed to list datasets: {e}")
84
- pass
110
+ return None
85
111
 
86
112
 
87
113
  def update_presigned_url(presigned_url, base_url):
@@ -156,7 +182,7 @@ def _fetch_presigned_url(project_name, dataset_name, base_url=None, timeout=120)
156
182
  logger.debug(
157
183
  f"API Call: [POST] {endpoint} | Status: {response.status_code} | Time: {elapsed_ms:.2f}ms"
158
184
  )
159
- if response.status_code == 200:
185
+ if response.status_code in [200, 201]:
160
186
  presigned_url = response.json()["data"]["presignedUrls"][0]
161
187
  presigned_url = update_presigned_url(presigned_url, url_base)
162
188
  return presigned_url
@@ -168,9 +194,10 @@ def _fetch_presigned_url(project_name, dataset_name, base_url=None, timeout=120)
168
194
  logger.error(
169
195
  f"Failed to fetch code hashes: {response.json()['message']}"
170
196
  )
197
+ return None
171
198
  except requests.exceptions.RequestException as e:
172
199
  logger.error(f"Failed to list datasets: {e}")
173
- pass
200
+ return None
174
201
 
175
202
 
176
203
  def _put_zip_presigned_url(project_name, presignedUrl, filename, timeout=120):
@@ -181,21 +208,28 @@ def _put_zip_presigned_url(project_name, presignedUrl, filename, timeout=120):
181
208
 
182
209
  if "blob.core.windows.net" in presignedUrl: # Azure
183
210
  headers["x-ms-blob-type"] = "BlockBlob"
184
- print("Uploading code...")
185
- with open(filename, "rb") as f:
186
- payload = f.read()
187
-
188
- start_time = time.time()
189
- response = requests.request(
190
- "PUT", presignedUrl, headers=headers, data=payload, timeout=timeout
191
- )
192
- elapsed_ms = (time.time() - start_time) * 1000
193
- logger.debug(
194
- f"API Call: [PUT] {presignedUrl} | Status: {response.status_code} | Time: {elapsed_ms:.2f}ms"
195
- )
196
- if response.status_code != 200 or response.status_code != 201:
197
- return response, response.status_code
211
+ logger.info("Uploading code to presigned URL...")
212
+ try:
213
+ with open(filename, "rb") as f:
214
+ payload = f.read()
198
215
 
216
+ start_time = time.time()
217
+ response = session_manager.make_request_with_retry(
218
+ "PUT", presignedUrl, headers=headers, data=payload, timeout=timeout
219
+ )
220
+ elapsed_ms = (time.time() - start_time) * 1000
221
+ logger.debug(
222
+ f"API Call: [PUT] {presignedUrl} | Status: {response.status_code} | Time: {elapsed_ms:.2f}ms"
223
+ )
224
+ if response.status_code not in [200, 201]:
225
+ return response, response.status_code
226
+ return True
227
+ except (PoolError, MaxRetryError, NewConnectionError, ConnectionError, Timeout, RemoteDisconnected) as e:
228
+ session_manager.handle_request_exceptions(e, "uploading zip to presigned URL")
229
+ return False
230
+ except RequestException as e:
231
+ logger.error(f"Failed to upload zip: {e}")
232
+ return False
199
233
 
200
234
  def _insert_code(
201
235
  dataset_name, hash_id, presigned_url, project_name, base_url=None, timeout=120
@@ -218,39 +252,43 @@ def _insert_code(
218
252
  url_base = base_url if base_url is not None else RagaAICatalyst.BASE_URL
219
253
  start_time = time.time()
220
254
  endpoint = f"{url_base}/v2/llm/dataset/code"
221
- response = requests.request(
255
+ response = session_manager.make_request_with_retry(
222
256
  "POST", endpoint, headers=headers, data=payload, timeout=timeout
223
257
  )
224
258
  elapsed_ms = (time.time() - start_time) * 1000
225
259
  logger.debug(
226
260
  f"API Call: [POST] {endpoint} | Status: {response.status_code} | Time: {elapsed_ms:.2f}ms"
227
261
  )
228
- if response.status_code == 200:
262
+ if response.status_code in [200, 201]:
229
263
  return response.json()["message"]
230
264
 
231
265
  elif response.status_code == 401:
232
- logger.warning("Received 401 error. Attempting to refresh token.")
266
+ logger.warning("Received 401 error during inserting code. Attempting to refresh token.")
233
267
  token = RagaAICatalyst.get_token(force_refresh=True)
234
268
  headers = {
235
269
  "X-Project-Name": project_name,
236
270
  "Content-Type": "application/json",
237
271
  "Authorization": f"Bearer {token}",
238
272
  }
239
- response = requests.request(
273
+ response = session_manager.make_request_with_retry(
240
274
  "POST", endpoint, headers=headers, data=payload, timeout=timeout
241
275
  )
242
276
  elapsed_ms = (time.time() - start_time) * 1000
243
277
  logger.debug(
244
278
  f"API Call: [POST] {endpoint} | Status: {response.status_code} | Time: {elapsed_ms:.2f}ms"
245
279
  )
246
- if response.status_code == 200:
280
+ if response.status_code in [200, 201]:
281
+ logger.info(f"Code inserted successfully after 401: {response.json()['message']}")
247
282
  return response.json()["message"]
248
283
  else:
249
- logger.error(f"Failed to insert code: {response.json()['message']}")
250
- pass
284
+ logger.error(f"Failed to insert code after 401: {response.json()['message']}")
285
+ return None
251
286
  else:
252
287
  logger.error(f"Failed to insert code: {response.json()['message']}")
253
- pass
254
- except requests.exceptions.RequestException as e:
288
+ return None
289
+ except (PoolError, MaxRetryError, NewConnectionError, ConnectionError, Timeout, RemoteDisconnected) as e:
290
+ session_manager.handle_request_exceptions(e, "inserting code")
291
+ return None
292
+ except RequestException as e:
255
293
  logger.error(f"Failed to insert code: {e}")
256
- pass
294
+ return None
@@ -203,6 +203,7 @@ class RAGATraceExporter(SpanExporter):
203
203
  dataset_name=self.dataset_name,
204
204
  user_details=self.user_details,
205
205
  base_url=self.base_url,
206
+ tracer_type=self.tracer_type,
206
207
  timeout=self.timeout
207
208
  )
208
209
 
@@ -156,14 +156,14 @@ def convert_json_format(
156
156
 
157
157
  # If prompt tokens or/and completion tokens are not present, will calculate it using tiktoken
158
158
  try:
159
- if prompt_tokens == 0:
159
+ if prompt_tokens == 0 and span["attributes"].get("openinference.span.kind") == "LLM" and span["status"].get("status_code") != "ERROR":
160
160
  prompt_value = span["attributes"].get("input.value")
161
161
  if prompt_value:
162
162
  prompt_tokens = count_tokens(prompt_value)
163
163
  logger.debug(
164
164
  f"Prompt tokens not present, calculated it: {prompt_tokens}"
165
165
  )
166
- if completion_tokens == 0:
166
+ if completion_tokens == 0 and span["attributes"].get("openinference.span.kind") == "LLM" and span["status"].get("status_code") != "ERROR" :
167
167
  completion_value = span["attributes"].get("output.value")
168
168
  if completion_value:
169
169
  completion_tokens = count_tokens(completion_value)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ragaai_catalyst
3
- Version: 2.2.4b4
3
+ Version: 2.2.4.1
4
4
  Summary: RAGA AI CATALYST
5
5
  Author-email: Kiran Scaria <kiran.scaria@raga.ai>, Kedar Gaikwad <kedar.gaikwad@raga.ai>, Dushyant Mahajan <dushyant.mahajan@raga.ai>, Siddhartha Kosti <siddhartha.kosti@raga.ai>, Ritika Goel <ritika.goel@raga.ai>, Vijay Chaurasia <vijay.chaurasia@raga.ai>, Tushar Kumar <tushar.kumar@raga.ai>, Rishabh Pandey <rishabh.pandey@raga.ai>, Jyotsana C G <jyotsana@raga.ai>
6
6
  Requires-Python: <=3.13.2,>=3.10
@@ -54,9 +54,10 @@ ragaai_catalyst/tracers/agentic_tracing/tracers/network_tracer.py,sha256=m8CxYkl
54
54
  ragaai_catalyst/tracers/agentic_tracing/tracers/tool_tracer.py,sha256=xxrliKPfdfbIZRZqMnUewsaTD8_Hv0dbuoBivNZGD4U,21674
55
55
  ragaai_catalyst/tracers/agentic_tracing/tracers/user_interaction_tracer.py,sha256=bhSUhNQCuJXKjgJAXhjKEYjnHMpYN90FSZdR84fNIKU,4614
56
56
  ragaai_catalyst/tracers/agentic_tracing/upload/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
- ragaai_catalyst/tracers/agentic_tracing/upload/trace_uploader.py,sha256=iMUMFR9XVipCBunpv8_No8bCoP3lqG47M5dg-ugibWo,21006
58
- ragaai_catalyst/tracers/agentic_tracing/upload/upload_agentic_traces.py,sha256=t3spo5w7TyfR0Zeqm1h5Z-bJ-BlZ3EPGTvRdK5lpFpE,11705
59
- ragaai_catalyst/tracers/agentic_tracing/upload/upload_code.py,sha256=2mxdi7k_SoDqQUFo1oQ__28CpmSIvVugYcbuRltUK9Q,9920
57
+ ragaai_catalyst/tracers/agentic_tracing/upload/session_manager.py,sha256=sOlxeIYIP8tycaTtZC9xkZosi6EDJUxvDw0_rc_NLI8,6823
58
+ ragaai_catalyst/tracers/agentic_tracing/upload/trace_uploader.py,sha256=Ujbu0KDl7oDr-cFtLwrQK_i7ghMuPV92mFnRfobJ1aI,24822
59
+ ragaai_catalyst/tracers/agentic_tracing/upload/upload_agentic_traces.py,sha256=0u4GWgqtaBz9cnr_KuqVIWDvhHWkgTAOTtiy0w8RPuk,13017
60
+ ragaai_catalyst/tracers/agentic_tracing/upload/upload_code.py,sha256=CbTx2vBAPIat5bdIClv9szOo4i33YL_1v04mkUjNG2c,12170
60
61
  ragaai_catalyst/tracers/agentic_tracing/upload/upload_local_metric.py,sha256=m1O8lKpxKwtHofXLW3fTHX5yfqDW5GxoveARlg5cTw4,2571
61
62
  ragaai_catalyst/tracers/agentic_tracing/utils/__init__.py,sha256=XdB3X_ufe4RVvGorxSqAiB9dYv4UD7Hvvuw3bsDUppY,60
62
63
  ragaai_catalyst/tracers/agentic_tracing/utils/api_utils.py,sha256=ZduFA7MmTnWfQ2FzSD0hxMAAfNNTgBs4CXcHZdXJv6k,749
@@ -76,7 +77,7 @@ ragaai_catalyst/tracers/exporters/__init__.py,sha256=wQbaqyeIjVZxYprHCKZ9BeiqxeX
76
77
  ragaai_catalyst/tracers/exporters/dynamic_trace_exporter.py,sha256=Rm-QaLv1qMAKpHKcFOcK_HWaKHwFBoUH45_4QYipE-g,6843
77
78
  ragaai_catalyst/tracers/exporters/file_span_exporter.py,sha256=NZsD3rShUiC3rO9y3Y2vqEtS3MO51FXZy0p3q9cdDNY,6403
78
79
  ragaai_catalyst/tracers/exporters/raga_exporter.py,sha256=l-RfysTIXYxtvYkVlJbRvg-AzJbT4Fdb-YiZh0mfuDs,17868
79
- ragaai_catalyst/tracers/exporters/ragaai_trace_exporter.py,sha256=VLvlWFRFPhE32WrF-_J_vCczduz13WAcOW8MKDgDYJc,8979
80
+ ragaai_catalyst/tracers/exporters/ragaai_trace_exporter.py,sha256=VxO96ldBpG5mCncrN5mXErIZMlxQ1ewhNoMLfCrzegM,9025
80
81
  ragaai_catalyst/tracers/instrumentators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
81
82
  ragaai_catalyst/tracers/utils/__init__.py,sha256=KeMaZtYaTojilpLv65qH08QmpYclfpacDA0U3wg6Ybw,64
82
83
  ragaai_catalyst/tracers/utils/convert_langchain_callbacks_output.py,sha256=SehrD7q8ytAiUYoWr406b4mWs3Lk0Rcy6Ekkihh22TI,1703
@@ -86,10 +87,10 @@ ragaai_catalyst/tracers/utils/langchain_tracer_extraction_logic.py,sha256=XS2_x2
86
87
  ragaai_catalyst/tracers/utils/model_prices_and_context_window_backup.json,sha256=WlZCZeOQ54aMVjYS8BAeka2uaFC3ftBTMZ8zzzA8TAI,495947
87
88
  ragaai_catalyst/tracers/utils/rag_extraction_logic_final.py,sha256=3ygkRT__lLDRflRttjzPu28tIA8cTCiGQVMQjqMItqQ,11309
88
89
  ragaai_catalyst/tracers/utils/rag_trace_json_converter.py,sha256=54IEZO-YRjUAahV5nw8KClXqTF1LhfDry_TsZ4KGow4,20467
89
- ragaai_catalyst/tracers/utils/trace_json_converter.py,sha256=U9GFVDCWRQvmBSMTDIZoMerJCnH8Gijw95r2oQbuFdQ,11560
90
+ ragaai_catalyst/tracers/utils/trace_json_converter.py,sha256=-HZVmijeUFLO7e9OAvi1RJdWVTxPRUHPd1MkKQlCD54,11785
90
91
  ragaai_catalyst/tracers/utils/utils.py,sha256=o-p9n2ZuophdrV0wrixu-BqRHCkovup_klc3mS8mU8g,2374
91
- ragaai_catalyst-2.2.4b4.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
92
- ragaai_catalyst-2.2.4b4.dist-info/METADATA,sha256=sOTQ0RA2Ao4UevDTayNNcYJhJ8OBza9sKJXpJ7lHg-0,17679
93
- ragaai_catalyst-2.2.4b4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
94
- ragaai_catalyst-2.2.4b4.dist-info/top_level.txt,sha256=HpgsdRgEJMk8nqrU6qdCYk3di7MJkDL0B19lkc7dLfM,16
95
- ragaai_catalyst-2.2.4b4.dist-info/RECORD,,
92
+ ragaai_catalyst-2.2.4.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
93
+ ragaai_catalyst-2.2.4.1.dist-info/METADATA,sha256=m3lK6boDHhoDHZhhrnIF1ZUsxzdoXQI8jgJxn2NxP_g,17679
94
+ ragaai_catalyst-2.2.4.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
95
+ ragaai_catalyst-2.2.4.1.dist-info/top_level.txt,sha256=HpgsdRgEJMk8nqrU6qdCYk3di7MJkDL0B19lkc7dLfM,16
96
+ ragaai_catalyst-2.2.4.1.dist-info/RECORD,,