diff --git a/src/google/adk/telemetry/tracing.py b/src/google/adk/telemetry/tracing.py index d98d12a43c..172513c490 100644 --- a/src/google/adk/telemetry/tracing.py +++ b/src/google/adk/telemetry/tracing.py @@ -47,7 +47,7 @@ GEN_AI_TOOL_DESCRIPTION = 'gen_ai.tool.description' GEN_AI_TOOL_NAME = 'gen_ai.tool.name' GEN_AI_TOOL_TYPE = 'gen_ai.tool.type' - +EMPTY_JSON_STRING = '{}' # Needed to avoid circular imports if TYPE_CHECKING: from ..agents.base_agent import BaseAgent @@ -140,8 +140,8 @@ def trace_tool_call( # Setting empty llm request and response (as UI expect these) while not # applicable for tool_response. - span.set_attribute('gcp.vertex.agent.llm_request', '{}') - span.set_attribute('gcp.vertex.agent.llm_response', '{}') + span.set_attribute('gcp.vertex.agent.llm_request', EMPTY_JSON_STRING) + span.set_attribute('gcp.vertex.agent.llm_response', EMPTY_JSON_STRING) if _should_add_request_response_to_spans(): span.set_attribute( @@ -149,7 +149,7 @@ def trace_tool_call( _safe_json_serialize(args), ) else: - span.set_attribute('gcp.vertex.agent.tool_call_args', '{}') + span.set_attribute('gcp.vertex.agent.tool_call_args', EMPTY_JSON_STRING) # Tracing tool response tool_call_id = '' @@ -179,7 +179,7 @@ def trace_tool_call( _safe_json_serialize(tool_response), ) else: - span.set_attribute('gcp.vertex.agent.tool_response', '{}') + span.set_attribute('gcp.vertex.agent.tool_response', EMPTY_JSON_STRING) def trace_merged_tool_calls( @@ -219,13 +219,13 @@ def trace_merged_tool_calls( function_response_event_json, ) else: - span.set_attribute('gcp.vertex.agent.tool_response', '{}') + span.set_attribute('gcp.vertex.agent.tool_response', EMPTY_JSON_STRING) # Setting empty llm request and response (as UI expect these) while not # applicable for tool_response. - span.set_attribute('gcp.vertex.agent.llm_request', '{}') + span.set_attribute('gcp.vertex.agent.llm_request', EMPTY_JSON_STRING) span.set_attribute( 'gcp.vertex.agent.llm_response', - '{}', + EMPTY_JSON_STRING, ) @@ -265,7 +265,7 @@ def trace_call_llm( _safe_json_serialize(_build_llm_request_for_trace(llm_request)), ) else: - span.set_attribute('gcp.vertex.agent.llm_request', '{}') + span.set_attribute('gcp.vertex.agent.llm_request', EMPTY_JSON_STRING) # Consider removing once GenAI SDK provides a way to record this info. if llm_request.config: if llm_request.config.top_p: @@ -290,7 +290,7 @@ def trace_call_llm( llm_response_json, ) else: - span.set_attribute('gcp.vertex.agent.llm_response', '{}') + span.set_attribute('gcp.vertex.agent.llm_response', EMPTY_JSON_STRING) if llm_response.usage_metadata is not None: span.set_attribute( @@ -346,7 +346,7 @@ def trace_send_data( ]), ) else: - span.set_attribute('gcp.vertex.agent.data', '{}') + span.set_attribute('gcp.vertex.agent.data', EMPTY_JSON_STRING) def _build_llm_request_for_trace(llm_request: LlmRequest) -> dict[str, Any]: diff --git a/tests/unittests/telemetry/test_spans.py b/tests/unittests/telemetry/test_spans.py index dd785daf7e..6c5fee56f9 100644 --- a/tests/unittests/telemetry/test_spans.py +++ b/tests/unittests/telemetry/test_spans.py @@ -83,6 +83,20 @@ async def _create_invocation_context( return invocation_context +def _assert_span_attribute_set_to_empty_json(mock_span, attribute_name: str): + """Helper to assert span attribute is set to empty JSON string '{}'.""" + calls = [ + call + for call in mock_span.set_attribute.call_args_list + if call.args[0] == attribute_name + ] + assert len(calls) == 1, f"Expected '{attribute_name}' to be set exactly once" + assert calls[0].args[1] == '{}', ( + f"Expected JSON string '{{}}' for {attribute_name} when content capture" + f' is disabled, got {calls[0].args[1]!r}' + ) + + @pytest.mark.asyncio async def test_trace_agent_invocation(mock_span_fixture): """Test trace_agent_invocation sets span attributes correctly.""" @@ -474,20 +488,12 @@ async def test_call_llm_disabling_request_response_content( # Act trace_call_llm(invocation_context, 'test_event_id', llm_request, llm_response) - # Assert - assert ( - 'gcp.vertex.agent.llm_request', - '{}', - ) in ( - call_obj.args - for call_obj in mock_span_fixture.set_attribute.call_args_list + # Assert - Check attributes are set to JSON string '{}' not dict {} + _assert_span_attribute_set_to_empty_json( + mock_span_fixture, 'gcp.vertex.agent.llm_request' ) - assert ( - 'gcp.vertex.agent.llm_response', - '{}', - ) in ( - call_obj.args - for call_obj in mock_span_fixture.set_attribute.call_args_list + _assert_span_attribute_set_to_empty_json( + mock_span_fixture, 'gcp.vertex.agent.llm_response' ) @@ -533,20 +539,12 @@ def test_trace_tool_call_disabling_request_response_content( function_response_event=mock_event_fixture, ) - # Assert - assert ( - 'gcp.vertex.agent.tool_call_args', - '{}', - ) in ( - call_obj.args - for call_obj in mock_span_fixture.set_attribute.call_args_list + # Assert - Check attributes are set to JSON string '{}' not dict {} + _assert_span_attribute_set_to_empty_json( + mock_span_fixture, 'gcp.vertex.agent.tool_call_args' ) - assert ( - 'gcp.vertex.agent.tool_response', - '{}', - ) in ( - call_obj.args - for call_obj in mock_span_fixture.set_attribute.call_args_list + _assert_span_attribute_set_to_empty_json( + mock_span_fixture, 'gcp.vertex.agent.tool_response' ) @@ -574,41 +572,7 @@ def test_trace_merged_tool_disabling_request_response_content( function_response_event=mock_event_fixture, ) - # Assert - assert ( - 'gcp.vertex.agent.tool_response', - '{}', - ) in ( - call_obj.args - for call_obj in mock_span_fixture.set_attribute.call_args_list - ) - - -@pytest.mark.asyncio -async def test_trace_send_data_disabling_request_response_content( - monkeypatch, mock_span_fixture -): - """Test trace_send_data sets placeholders when capture is disabled.""" - monkeypatch.setenv(ADK_CAPTURE_MESSAGE_CONTENT_IN_SPANS, 'false') - monkeypatch.setattr( - 'opentelemetry.trace.get_current_span', lambda: mock_span_fixture - ) - - agent = LlmAgent(name='test_agent') - invocation_context = await _create_invocation_context(agent) - - trace_send_data( - invocation_context=invocation_context, - event_id='test_event_id', - data=[ - types.Content( - role='user', - parts=[types.Part(text='hi')], - ) - ], - ) - - assert ('gcp.vertex.agent.data', '{}') in ( - call_obj.args - for call_obj in mock_span_fixture.set_attribute.call_args_list + # Assert - Check attribute is set to JSON string '{}' not dict {} + _assert_span_attribute_set_to_empty_json( + mock_span_fixture, 'gcp.vertex.agent.tool_response' )