@@ -281,9 +281,8 @@ def get_evaluation_rows(
281281 from_timestamp : Optional [datetime ] = None ,
282282 to_timestamp : Optional [datetime ] = None ,
283283 include_tool_calls : bool = True ,
284- backend_sleep_between_gets : float = 0.1 ,
285- backend_max_retries : int = 3 ,
286- proxy_max_retries : int = 3 ,
284+ sleep_between_gets : float = 0.1 ,
285+ max_retries : int = 3 ,
287286 span_name : Optional [str ] = None ,
288287 converter : Optional [TraceDictConverter ] = None ,
289288 ) -> List [EvaluationRow ]:
@@ -305,9 +304,10 @@ def get_evaluation_rows(
305304 from_timestamp: Explicit start time (ISO format)
306305 to_timestamp: Explicit end time (ISO format)
307306 include_tool_calls: Whether to include tool calling traces
308- backend_sleep_between_gets: Sleep time between backend trace fetches (passed to proxy)
309- backend_max_retries: Maximum retries for backend operations (passed to proxy)
310- proxy_max_retries: Maximum retries when proxy returns 404 (client-side retries with exponential backoff)
307+ sleep_between_gets: Sleep time between polling attempts (default: 2.5s)
308+ max_retries: Max retry attempts used by proxy for both:
309+ 1. Rate limit errors (429) on individual API calls (fast exponential backoff)
310+ 2. Ensuring all traces are fetched
311311 span_name: If provided, extract messages from generations within this named span
312312 converter: Optional custom converter implementing TraceDictConverter protocol.
313313 If provided, this will be used instead of the default conversion logic.
@@ -317,10 +317,15 @@ def get_evaluation_rows(
317317
318318 Raises:
319319 ValueError: If tags list is empty
320+
321+ Note:
322+ The proxy handles all retry logic internally using a single max_retries parameter
323+ for both rate limiting and completeness checking. If traces are incomplete after
324+ all retries, the proxy will return 404 with details about missing insertion_ids.
320325 """
321- # Validate that tags are provided (security requirement)
326+ # Validate that tags are provided
322327 if not tags or len (tags ) == 0 :
323- raise ValueError ("At least one tag is required to fetch traces (security: prevents fetching all traces) " )
328+ raise ValueError ("At least one tag is required to fetch traces" )
324329
325330 eval_rows = []
326331
@@ -339,58 +344,40 @@ def get_evaluation_rows(
339344 "hours_back" : hours_back ,
340345 "from_timestamp" : from_timestamp .isoformat () if from_timestamp else None ,
341346 "to_timestamp" : to_timestamp .isoformat () if to_timestamp else None ,
342- "sleep_between_gets" : backend_sleep_between_gets ,
343- "max_retries" : backend_max_retries ,
347+ "sleep_between_gets" : sleep_between_gets ,
348+ "max_retries" : max_retries ,
344349 }
345350
346351 # Remove None values
347352 params = {k : v for k , v in params .items () if v is not None }
348353
349- # Make request to proxy with retry logic
354+ # Make request to proxy
350355 if self .project_id :
351356 url = f"{ self .base_url } /v1/project_id/{ self .project_id } /traces"
352357 else :
353358 url = f"{ self .base_url } /v1/traces"
354359
355- # Retry loop for handling backend indexing delays (proxy returns 404)
356360 result = None
357- for attempt in range (proxy_max_retries ):
358- try :
359- response = requests .get (url , params = params , timeout = self .timeout )
360- response .raise_for_status ()
361- result = response .json ()
362- break # Success, exit retry loop
363- except requests .exceptions .HTTPError as e :
364- error_msg = str (e )
365- should_retry = False
366-
367- # Try to extract detail message from response
368- if e .response is not None :
369- try :
370- error_detail = e .response .json ().get ("detail" , "" )
371- error_msg = error_detail or e .response .text
372-
373- # Retry on 404 if it's due to incomplete/missing traces (backend still indexing)
374- if e .response .status_code == 404 :
375- should_retry = True
376- except Exception :
377- error_msg = e .response .text
378-
379- if should_retry and attempt < proxy_max_retries - 1 :
380- sleep_time = 2 ** (attempt + 1 )
381- logger .warning (error_msg )
382- time .sleep (sleep_time )
383- else :
384- # Final retry or non-retryable error
385- logger .error ("Failed to fetch traces from proxy: %s" , error_msg )
386- return eval_rows
387- except requests .exceptions .RequestException as e :
388- # Non-HTTP errors (network issues, timeouts, etc.)
389- logger .error ("Failed to fetch traces from proxy: %s" , str (e ))
390- return eval_rows
391-
392- if result is None :
393- logger .error ("Failed to fetch traces after %d retries" , proxy_max_retries )
361+ try :
362+ response = requests .get (url , params = params , timeout = self .timeout )
363+ response .raise_for_status ()
364+ result = response .json ()
365+ except requests .exceptions .HTTPError as e :
366+ error_msg = str (e )
367+
368+ # Try to extract detail message from response
369+ if e .response is not None :
370+ try :
371+ error_detail = e .response .json ().get ("detail" , {})
372+ error_msg = error_detail or e .response .text
373+ except Exception : # In case e.response.json() fails
374+ error_msg = f"Proxy error: { e .response .text } "
375+
376+ logger .error ("Failed to fetch traces from proxy: %s" , error_msg )
377+ return eval_rows
378+ except requests .exceptions .RequestException as e :
379+ # Non-HTTP errors (network issues, timeouts, etc.)
380+ logger .error ("Failed to fetch traces from proxy: %s" , str (e ))
394381 return eval_rows
395382
396383 # Extract traces from response
0 commit comments