66from .metrics import StagehandMetrics
77from .utils import convert_dict_keys_to_camel_case
88
9- __all__ = ["_create_session" , "_execute" , "_get_replay_metrics" ]
9+ __all__ = ["_create_session" , "_execute" , "_get_replay_metrics" , "_get_replay_metrics_sync" ]
1010
1111
1212async def _create_session (self ):
@@ -210,11 +210,59 @@ async def _execute(self, method: str, payload: dict[str, Any]) -> Any:
210210 raise
211211
212212
213- async def _get_replay_metrics ( self ) :
213+ def _parse_replay_metrics_data ( data : dict ) -> StagehandMetrics :
214214 """
215- Fetch replay metrics from the API and parse them into StagehandMetrics.
215+ Parse raw API response data into StagehandMetrics.
216+ Shared by both async and sync fetch paths.
216217 """
218+ if not data .get ("success" ):
219+ raise RuntimeError (
220+ f"Failed to fetch metrics: { data .get ('error' , 'Unknown error' )} "
221+ )
222+
223+ api_data = data .get ("data" , {})
224+ metrics = StagehandMetrics ()
225+
226+ pages = api_data .get ("pages" , [])
227+ for page in pages :
228+ actions = page .get ("actions" , [])
229+ for action in actions :
230+ method = action .get ("method" , "" ).lower ()
231+ token_usage = action .get ("tokenUsage" , {})
232+
233+ if token_usage :
234+ input_tokens = token_usage .get ("inputTokens" , 0 )
235+ output_tokens = token_usage .get ("outputTokens" , 0 )
236+ time_ms = token_usage .get ("timeMs" , 0 )
237+
238+ if method == "act" :
239+ metrics .act_prompt_tokens += input_tokens
240+ metrics .act_completion_tokens += output_tokens
241+ metrics .act_inference_time_ms += time_ms
242+ elif method == "extract" :
243+ metrics .extract_prompt_tokens += input_tokens
244+ metrics .extract_completion_tokens += output_tokens
245+ metrics .extract_inference_time_ms += time_ms
246+ elif method == "observe" :
247+ metrics .observe_prompt_tokens += input_tokens
248+ metrics .observe_completion_tokens += output_tokens
249+ metrics .observe_inference_time_ms += time_ms
250+ elif method == "agent" :
251+ metrics .agent_prompt_tokens += input_tokens
252+ metrics .agent_completion_tokens += output_tokens
253+ metrics .agent_inference_time_ms += time_ms
254+
255+ metrics .total_prompt_tokens += input_tokens
256+ metrics .total_completion_tokens += output_tokens
257+ metrics .total_inference_time_ms += time_ms
258+
259+ return metrics
260+
217261
262+ async def _get_replay_metrics (self ):
263+ """
264+ Fetch replay metrics from the API (async version).
265+ """
218266 if not self .session_id :
219267 raise ValueError ("session_id is required to fetch metrics." )
220268
@@ -241,55 +289,46 @@ async def _get_replay_metrics(self):
241289 f"Failed to fetch metrics with status { response .status_code } : { error_text } "
242290 )
243291
244- data = response .json ()
292+ return _parse_replay_metrics_data (response .json ())
293+
294+ except Exception as e :
295+ self .logger .error (f"[EXCEPTION] Error fetching replay metrics: { str (e )} " )
296+ raise
297+
298+
299+ def _get_replay_metrics_sync (self ):
300+ """
301+ Fetch replay metrics from the API (sync version).
302+ Uses a synchronous httpx request so it can be called from sync contexts
303+ even when an async event loop is already running.
304+ """
305+ import httpx
306+
307+ if not self .session_id :
308+ raise ValueError ("session_id is required to fetch metrics." )
309+
310+ headers = {
311+ "x-bb-api-key" : self .browserbase_api_key ,
312+ "x-bb-project-id" : self .browserbase_project_id ,
313+ "Content-Type" : "application/json" ,
314+ }
245315
246- if not data .get ("success" ):
316+ try :
317+ response = httpx .get (
318+ f"{ self .api_url } /sessions/{ self .session_id } /replay" ,
319+ headers = headers ,
320+ timeout = self .timeout_settings ,
321+ )
322+
323+ if response .status_code != 200 :
324+ self .logger .error (
325+ f"[HTTP ERROR] Failed to fetch metrics. Status { response .status_code } : { response .text } "
326+ )
247327 raise RuntimeError (
248- f"Failed to fetch metrics: { data . get ( 'error' , 'Unknown error' ) } "
328+ f"Failed to fetch metrics with status { response . status_code } : { response . text } "
249329 )
250330
251- # Parse the API data into StagehandMetrics format
252- api_data = data .get ("data" , {})
253- metrics = StagehandMetrics ()
254-
255- # Parse pages and their actions
256- pages = api_data .get ("pages" , [])
257- for page in pages :
258- actions = page .get ("actions" , [])
259- for action in actions :
260- # Get method name and token usage
261- method = action .get ("method" , "" ).lower ()
262- token_usage = action .get ("tokenUsage" , {})
263-
264- if token_usage :
265- input_tokens = token_usage .get ("inputTokens" , 0 )
266- output_tokens = token_usage .get ("outputTokens" , 0 )
267- time_ms = token_usage .get ("timeMs" , 0 )
268-
269- # Map method to metrics fields
270- if method == "act" :
271- metrics .act_prompt_tokens += input_tokens
272- metrics .act_completion_tokens += output_tokens
273- metrics .act_inference_time_ms += time_ms
274- elif method == "extract" :
275- metrics .extract_prompt_tokens += input_tokens
276- metrics .extract_completion_tokens += output_tokens
277- metrics .extract_inference_time_ms += time_ms
278- elif method == "observe" :
279- metrics .observe_prompt_tokens += input_tokens
280- metrics .observe_completion_tokens += output_tokens
281- metrics .observe_inference_time_ms += time_ms
282- elif method == "agent" :
283- metrics .agent_prompt_tokens += input_tokens
284- metrics .agent_completion_tokens += output_tokens
285- metrics .agent_inference_time_ms += time_ms
286-
287- # Always update totals for any method with token usage
288- metrics .total_prompt_tokens += input_tokens
289- metrics .total_completion_tokens += output_tokens
290- metrics .total_inference_time_ms += time_ms
291-
292- return metrics
331+ return _parse_replay_metrics_data (response .json ())
293332
294333 except Exception as e :
295334 self .logger .error (f"[EXCEPTION] Error fetching replay metrics: { str (e )} " )
0 commit comments