3333import io .opentelemetry .api .GlobalOpenTelemetry ;
3434import io .opentelemetry .api .common .AttributeKey ;
3535import io .opentelemetry .api .trace .Span ;
36+ import io .opentelemetry .api .trace .StatusCode ;
3637import io .opentelemetry .api .trace .Tracer ;
3738import io .opentelemetry .context .Context ;
3839import io .opentelemetry .context .Scope ;
6162import java .util .function .BiConsumer ;
6263import java .util .function .Consumer ;
6364import java .util .function .Supplier ;
65+ import org .jspecify .annotations .Nullable ;
6466import org .reactivestreams .Publisher ;
6567import org .reactivestreams .Subscriber ;
6668import org .reactivestreams .Subscription ;
@@ -77,6 +79,11 @@ public class Tracing {
7779
7880 private static final Logger log = LoggerFactory .getLogger (Tracing .class );
7981
82+ private static final String INVOKE_AGENT_OPERATION = "invoke_agent" ;
83+ private static final String EXECUTE_TOOL_OPERATION = "execute_tool" ;
84+ private static final String SEND_DATA_OPERATION = "send_data" ;
85+ private static final String CALL_LLM_OPERATION = "call_llm" ;
86+
8087 private static final AttributeKey <List <String >> GEN_AI_RESPONSE_FINISH_REASONS =
8188 AttributeKey .stringArrayKey ("gen_ai.response.finish_reasons" );
8289
@@ -134,15 +141,6 @@ public class Tracing {
134141
135142 private Tracing () {}
136143
137- private static void traceWithSpan (String methodName , Consumer <Span > traceAction ) {
138- Span span = Span .current ();
139- if (!span .getSpanContext ().isValid ()) {
140- log .trace ("{}: No valid span in current context." , methodName );
141- return ;
142- }
143- traceAction .accept (span );
144- }
145-
146144 private static void setInvocationAttributes (
147145 Span span , InvocationContext invocationContext , String eventId ) {
148146 span .setAttribute (ADK_INVOCATION_ID , invocationContext .invocationId ());
@@ -159,12 +157,6 @@ private static void setInvocationAttributes(
159157 }
160158 }
161159
162- private static void setToolExecutionAttributes (Span span ) {
163- span .setAttribute (GEN_AI_OPERATION_NAME , "execute_tool" );
164- span .setAttribute (ADK_LLM_REQUEST , "{}" );
165- span .setAttribute (ADK_LLM_RESPONSE , "{}" );
166- }
167-
168160 private static void setJsonAttribute (Span span , AttributeKey <String > key , Object value ) {
169161 if (!CAPTURE_MESSAGE_CONTENT_IN_SPANS ) {
170162 span .setAttribute (key , "{}" );
@@ -198,7 +190,7 @@ public static void setTracerForTesting(Tracer tracer) {
198190 */
199191 public static void traceAgentInvocation (
200192 Span span , String agentName , String agentDescription , InvocationContext invocationContext ) {
201- span .setAttribute (GEN_AI_OPERATION_NAME , "invoke_agent" );
193+ span .setAttribute (GEN_AI_OPERATION_NAME , INVOKE_AGENT_OPERATION );
202194 span .setAttribute (GEN_AI_AGENT_DESCRIPTION , agentDescription );
203195 span .setAttribute (GEN_AI_AGENT_NAME , agentName );
204196 if (invocationContext .session () != null && invocationContext .session ().id () != null ) {
@@ -207,58 +199,62 @@ public static void traceAgentInvocation(
207199 }
208200
209201 /**
210- * Traces tool call arguments.
211- *
212- * @param args The arguments to the tool call.
213- */
214- public static void traceToolCall (
215- String toolName , String toolDescription , String toolType , Map <String , Object > args ) {
216- traceWithSpan (
217- "traceToolCall" ,
218- span -> {
219- setToolExecutionAttributes (span );
220- span .setAttribute (GEN_AI_TOOL_NAME , toolName );
221- span .setAttribute (GEN_AI_TOOL_DESCRIPTION , toolDescription );
222- span .setAttribute (GEN_AI_TOOL_TYPE , toolType );
223-
224- setJsonAttribute (span , ADK_TOOL_CALL_ARGS , args );
225- });
226- }
227-
228- /**
229- * Traces tool response event.
202+ * Traces a tool execution, including its arguments, response, and any potential error.
230203 *
231- * @param eventId The ID of the event.
232- * @param functionResponseEvent The function response event.
204+ * @param span The span representing the tool execution.
205+ * @param toolName The name of the tool.
206+ * @param toolDescription The tool's description.
207+ * @param toolType The tool's type (e.g., "FunctionTool").
208+ * @param args The arguments passed to the tool.
209+ * @param functionResponseEvent The event containing the tool's response, if successful.
210+ * @param error The exception thrown during execution, if any.
233211 */
234- public static void traceToolResponse (String eventId , Event functionResponseEvent ) {
235- traceWithSpan (
236- "traceToolResponse" ,
237- span -> {
238- setToolExecutionAttributes (span );
239- span .setAttribute (ADK_EVENT_ID , eventId );
240-
241- FunctionResponse functionResponse =
242- functionResponseEvent .functionResponses ().stream ().findFirst ().orElse (null );
243-
244- String toolCallId = "<not specified>" ;
245- Object toolResponse = "<not specified>" ;
246- if (functionResponse != null ) {
247- toolCallId = functionResponse .id ().orElse (toolCallId );
248- if (functionResponse .response ().isPresent ()) {
249- toolResponse = functionResponse .response ().get ();
250- }
251- }
252-
253- span .setAttribute (GEN_AI_TOOL_CALL_ID , toolCallId );
212+ public static void traceToolExecution (
213+ Span span ,
214+ String toolName ,
215+ String toolDescription ,
216+ String toolType ,
217+ Map <String , Object > args ,
218+ @ Nullable Event functionResponseEvent ,
219+ @ Nullable Exception error ) {
220+ span .setAttribute (GEN_AI_OPERATION_NAME , EXECUTE_TOOL_OPERATION );
221+ span .setAttribute (GEN_AI_TOOL_NAME , toolName );
222+ span .setAttribute (GEN_AI_TOOL_DESCRIPTION , toolDescription );
223+ span .setAttribute (GEN_AI_TOOL_TYPE , toolType );
224+
225+ setJsonAttribute (span , ADK_TOOL_CALL_ARGS , args );
226+
227+ if (functionResponseEvent != null ) {
228+ span .setAttribute (ADK_EVENT_ID , functionResponseEvent .id ());
229+ FunctionResponse functionResponse =
230+ functionResponseEvent .functionResponses ().stream ().findFirst ().orElse (null );
231+
232+ String toolCallId = "<not specified>" ;
233+ Object toolResponse = "<not specified>" ;
234+ if (functionResponse != null ) {
235+ toolCallId = functionResponse .id ().orElse (toolCallId );
236+ if (functionResponse .response ().isPresent ()) {
237+ toolResponse = functionResponse .response ().get ();
238+ }
239+ }
240+ span .setAttribute (GEN_AI_TOOL_CALL_ID , toolCallId );
241+ Object finalToolResponse =
242+ (toolResponse instanceof Map ) ? toolResponse : ImmutableMap .of ("result" , toolResponse );
243+ setJsonAttribute (span , ADK_TOOL_RESPONSE , finalToolResponse );
244+ } else {
245+ // Set placeholder if no response event is available (e.g., due to an error)
246+ span .setAttribute (GEN_AI_TOOL_CALL_ID , "<not specified>" );
247+ setJsonAttribute (span , ADK_TOOL_RESPONSE , "{}" );
248+ }
254249
255- Object finalToolResponse =
256- (toolResponse instanceof Map )
257- ? toolResponse
258- : ImmutableMap .of ("result" , toolResponse );
250+ // Also set empty LLM attributes for UI compatibility, like in traceToolResponse
251+ span .setAttribute (ADK_LLM_REQUEST , "{}" );
252+ span .setAttribute (ADK_LLM_RESPONSE , "{}" );
259253
260- setJsonAttribute (span , ADK_TOOL_RESPONSE , finalToolResponse );
261- });
254+ if (error != null ) {
255+ span .setStatus (StatusCode .ERROR , error .getMessage ());
256+ span .recordException (error );
257+ }
262258 }
263259
264260 /**
@@ -303,15 +299,22 @@ public static void traceCallLlm(
303299 InvocationContext invocationContext ,
304300 String eventId ,
305301 LlmRequest llmRequest ,
306- LlmResponse llmResponse ) {
302+ LlmResponse llmResponse ,
303+ @ Nullable Exception error ) {
307304 span .setAttribute (GEN_AI_SYSTEM , "gcp.vertex.agent" );
305+ span .setAttribute (GEN_AI_OPERATION_NAME , CALL_LLM_OPERATION );
308306 llmRequest .model ().ifPresent (modelName -> span .setAttribute (GEN_AI_REQUEST_MODEL , modelName ));
309307
310308 setInvocationAttributes (span , invocationContext , eventId );
311309
312310 setJsonAttribute (span , ADK_LLM_REQUEST , buildLlmRequestForTrace (llmRequest ));
313311 setJsonAttribute (span , ADK_LLM_RESPONSE , llmResponse );
314312
313+ if (error != null ) {
314+ span .setStatus (StatusCode .ERROR , error .getMessage ());
315+ span .recordException (error );
316+ }
317+
315318 llmRequest
316319 .config ()
317320 .ifPresent (
@@ -352,18 +355,45 @@ public static void traceCallLlm(
352355 * @param data A list of content objects being sent.
353356 */
354357 public static void traceSendData (
355- InvocationContext invocationContext , String eventId , List <Content > data ) {
356- traceWithSpan (
357- "traceSendData" ,
358- span -> {
359- setInvocationAttributes (span , invocationContext , eventId );
360-
361- ImmutableList <Content > safeData =
362- Optional .ofNullable (data ).orElse (ImmutableList .of ()).stream ()
363- .filter (Objects ::nonNull )
364- .collect (toImmutableList ());
365- setJsonAttribute (span , ADK_DATA , safeData );
366- });
358+ Span span , InvocationContext invocationContext , String eventId , List <Content > data ) {
359+ if (!span .getSpanContext ().isValid ()) {
360+ log .trace ("traceSendData: No valid span in current context." );
361+ return ;
362+ }
363+ setInvocationAttributes (span , invocationContext , eventId );
364+ span .setAttribute (GEN_AI_OPERATION_NAME , SEND_DATA_OPERATION );
365+
366+ ImmutableList <Content > safeData =
367+ Optional .ofNullable (data ).orElse (ImmutableList .of ()).stream ()
368+ .filter (Objects ::nonNull )
369+ .collect (toImmutableList ());
370+ setJsonAttribute (span , ADK_DATA , safeData );
371+ }
372+
373+ /**
374+ * Traces merged tool call events.
375+ *
376+ * <p>Calling this function is not needed for telemetry purposes. This is provided for preventing
377+ * /debug/trace requests (typically sent by web UI).
378+ *
379+ * @param responseEventId The ID of the response event.
380+ * @param functionResponseEvent The merged response event.
381+ */
382+ public static void traceMergedToolCalls (
383+ Span span , String responseEventId , Event functionResponseEvent ) {
384+ if (!span .getSpanContext ().isValid ()) {
385+ log .trace ("traceMergedToolCalls: No valid span in current context." );
386+ return ;
387+ }
388+ span .setAttribute (GEN_AI_OPERATION_NAME , EXECUTE_TOOL_OPERATION );
389+ span .setAttribute (GEN_AI_TOOL_NAME , "(merged tools)" );
390+ span .setAttribute (GEN_AI_TOOL_DESCRIPTION , "(merged tools)" );
391+ span .setAttribute (GEN_AI_TOOL_CALL_ID , responseEventId );
392+ span .setAttribute (ADK_TOOL_CALL_ARGS , "N/A" );
393+ span .setAttribute (ADK_EVENT_ID , responseEventId );
394+ setJsonAttribute (span , ADK_TOOL_RESPONSE , functionResponseEvent );
395+ span .setAttribute (ADK_LLM_REQUEST , "{}" );
396+ span .setAttribute (ADK_LLM_RESPONSE , "{}" );
367397 }
368398
369399 /**
0 commit comments