@@ -323,3 +323,69 @@ def test_stop_embedding_without_tokens(self) -> None:
323323
324324 # Token metrics should NOT be recorded when input_tokens is not set
325325 self .assertNotIn ("gen_ai.client.token.usage" , metrics )
326+
327+
328+ class TelemetryHandlerToolMetricsTest (TestBase ):
329+ def _harvest_metrics (self ) -> Dict [str , List [Any ]]:
330+ metrics = self .get_sorted_metrics ()
331+ metrics_by_name : Dict [str , List [Any ]] = {}
332+ for metric in metrics or []:
333+ points = metric .data .data_points or []
334+ metrics_by_name .setdefault (metric .name , []).extend (points )
335+ return metrics_by_name
336+
337+ def test_stop_tool_records_duration (self ) -> None :
338+ handler = TelemetryHandler (
339+ tracer_provider = self .tracer_provider ,
340+ meter_provider = self .meter_provider ,
341+ )
342+ with patch ("timeit.default_timer" , return_value = 1000.0 ):
343+ invocation = handler .start_tool ("get_weather" )
344+ invocation .metric_attributes = {"custom.key" : "custom_value" }
345+
346+ with patch ("timeit.default_timer" , return_value = 1002.5 ):
347+ invocation .stop ()
348+
349+ metrics = self ._harvest_metrics ()
350+ self .assertIn ("gen_ai.client.operation.duration" , metrics )
351+ duration_points = metrics ["gen_ai.client.operation.duration" ]
352+ self .assertEqual (len (duration_points ), 1 )
353+ duration_point = duration_points [0 ]
354+
355+ self .assertEqual (
356+ duration_point .attributes [GenAI .GEN_AI_OPERATION_NAME ],
357+ "execute_tool" ,
358+ )
359+ self .assertEqual (
360+ duration_point .attributes ["custom.key" ], "custom_value"
361+ )
362+ self .assertAlmostEqual (duration_point .sum , 2.5 , places = 3 )
363+ self .assertNotIn ("gen_ai.client.token.usage" , metrics )
364+
365+ def test_fail_tool_records_duration_with_error (self ) -> None :
366+ handler = TelemetryHandler (
367+ tracer_provider = self .tracer_provider ,
368+ meter_provider = self .meter_provider ,
369+ )
370+ with patch ("timeit.default_timer" , return_value = 500.0 ):
371+ invocation = handler .start_tool ("failing_tool" )
372+
373+ error = Error (message = "Tool execution failed" , type = RuntimeError )
374+ with patch ("timeit.default_timer" , return_value = 501.5 ):
375+ invocation .fail (error )
376+
377+ metrics = self ._harvest_metrics ()
378+ self .assertIn ("gen_ai.client.operation.duration" , metrics )
379+ duration_points = metrics ["gen_ai.client.operation.duration" ]
380+ self .assertEqual (len (duration_points ), 1 )
381+ duration_point = duration_points [0 ]
382+
383+ self .assertEqual (
384+ duration_point .attributes ["error.type" ], "RuntimeError"
385+ )
386+ self .assertEqual (
387+ duration_point .attributes [GenAI .GEN_AI_OPERATION_NAME ],
388+ "execute_tool" ,
389+ )
390+ self .assertAlmostEqual (duration_point .sum , 1.5 , places = 3 )
391+ self .assertNotIn ("gen_ai.client.token.usage" , metrics )
0 commit comments