From 78e69628e596b077c57f16d4c20f53b552c2e869 Mon Sep 17 00:00:00 2001 From: DineshThumma9 Date: Thu, 8 Jan 2026 21:02:09 +0530 Subject: [PATCH 1/4] fix:Use JSON string instead of dic for span attributes when content capture is disabled --- src/google/adk/telemetry/tracing.py | 12 +-- tests/unittests/telemetry/test_spans.py | 107 +++++++++++++----------- 2 files changed, 66 insertions(+), 53 deletions(-) diff --git a/src/google/adk/telemetry/tracing.py b/src/google/adk/telemetry/tracing.py index 386ae3b453..d98d12a43c 100644 --- a/src/google/adk/telemetry/tracing.py +++ b/src/google/adk/telemetry/tracing.py @@ -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', '{}') # 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', '{}') def trace_merged_tool_calls( @@ -219,7 +219,7 @@ 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', '{}') # Setting empty llm request and response (as UI expect these) while not # applicable for tool_response. span.set_attribute('gcp.vertex.agent.llm_request', '{}') @@ -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', '{}') # 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', '{}') 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', '{}') 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 c87730a5e7..56ea5ed4fe 100644 --- a/tests/unittests/telemetry/test_spans.py +++ b/tests/unittests/telemetry/test_spans.py @@ -473,24 +473,31 @@ async def test_call_llm_disabling_request_response_content( # Act trace_call_llm(invocation_context, 'test_event_id', llm_request, llm_response) - # Assert - assert not any( - arg_name == 'gcp.vertex.agent.llm_request' and arg_value != {} - for arg_name, arg_value in ( - call_obj.args - for call_obj in mock_span_fixture.set_attribute.call_args_list - ) - ), "Attribute 'gcp.vertex.agent.llm_request' was incorrectly set on the span." - - assert not any( - arg_name == 'gcp.vertex.agent.llm_response' and arg_value != {} - for arg_name, arg_value in ( - call_obj.args - for call_obj in mock_span_fixture.set_attribute.call_args_list - ) - ), ( - "Attribute 'gcp.vertex.agent.llm_response' was incorrectly set on the" - ' span.' + # Assert - Check attributes are set to JSON string '{}' not dict {} + llm_request_calls = [ + call + for call in mock_span_fixture.set_attribute.call_args_list + if call.args[0] == 'gcp.vertex.agent.llm_request' + ] + assert ( + len(llm_request_calls) == 1 + ), "Expected 'gcp.vertex.agent.llm_request' to be set exactly once" + assert llm_request_calls[0].args[1] == '{}', ( + "Expected JSON string '{}' for llm_request when content capture is" + f' disabled, got {llm_request_calls[0].args[1]!r}' + ) + + llm_response_calls = [ + call + for call in mock_span_fixture.set_attribute.call_args_list + if call.args[0] == 'gcp.vertex.agent.llm_response' + ] + assert ( + len(llm_response_calls) == 1 + ), "Expected 'gcp.vertex.agent.llm_response' to be set exactly once" + assert llm_response_calls[0].args[1] == '{}', ( + "Expected JSON string '{}' for llm_response when content capture is" + f' disabled, got {llm_response_calls[0].args[1]!r}' ) @@ -536,27 +543,31 @@ def test_trace_tool_call_disabling_request_response_content( function_response_event=mock_event_fixture, ) - # Assert - assert not any( - arg_name == 'gcp.vertex.agent.tool_call_args' and arg_value != {} - for arg_name, arg_value in ( - call_obj.args - for call_obj in mock_span_fixture.set_attribute.call_args_list - ) - ), ( - "Attribute 'gcp.vertex.agent.tool_call_args' was incorrectly set on the" - ' span.' + # Assert - Check attributes are set to JSON string '{}' not dict {} + tool_args_calls = [ + call + for call in mock_span_fixture.set_attribute.call_args_list + if call.args[0] == 'gcp.vertex.agent.tool_call_args' + ] + assert ( + len(tool_args_calls) == 1 + ), "Expected 'gcp.vertex.agent.tool_call_args' to be set exactly once" + assert tool_args_calls[0].args[1] == '{}', ( + "Expected JSON string '{}' for tool_call_args when content capture is" + f' disabled, got {tool_args_calls[0].args[1]!r}' ) - assert not any( - arg_name == 'gcp.vertex.agent.tool_response' and arg_value != {} - for arg_name, arg_value in ( - call_obj.args - for call_obj in mock_span_fixture.set_attribute.call_args_list - ) - ), ( - "Attribute 'gcp.vertex.agent.tool_response' was incorrectly set on the" - ' span.' + tool_response_calls = [ + call + for call in mock_span_fixture.set_attribute.call_args_list + if call.args[0] == 'gcp.vertex.agent.tool_response' + ] + assert ( + len(tool_response_calls) == 1 + ), "Expected 'gcp.vertex.agent.tool_response' to be set exactly once" + assert tool_response_calls[0].args[1] == '{}', ( + "Expected JSON string '{}' for tool_response when content capture is" + f' disabled, got {tool_response_calls[0].args[1]!r}' ) @@ -584,14 +595,16 @@ def test_trace_merged_tool_disabling_request_response_content( function_response_event=mock_event_fixture, ) - # Assert - assert not any( - arg_name == 'gcp.vertex.agent.tool_response' and arg_value != {} - for arg_name, arg_value in ( - call_obj.args - for call_obj in mock_span_fixture.set_attribute.call_args_list - ) - ), ( - "Attribute 'gcp.vertex.agent.tool_response' was incorrectly set on the" - ' span.' + # Assert - Check attribute is set to JSON string '{}' not dict {} + tool_response_calls = [ + call + for call in mock_span_fixture.set_attribute.call_args_list + if call.args[0] == 'gcp.vertex.agent.tool_response' + ] + assert ( + len(tool_response_calls) == 1 + ), "Expected 'gcp.vertex.agent.tool_response' to be set exactly once" + assert tool_response_calls[0].args[1] == '{}', ( + "Expected JSON string '{}' for tool_response when content capture is" + f' disabled, got {tool_response_calls[0].args[1]!r}' ) From c743d2d42bafea362f6937336b46d07d80435d17 Mon Sep 17 00:00:00 2001 From: DineshThumma9 Date: Thu, 8 Jan 2026 22:17:05 +0530 Subject: [PATCH 2/4] Trigger CLA Check From 19f3b63234c37e9847f311113e4877112d5ca911 Mon Sep 17 00:00:00 2001 From: DineshThumma9 Date: Thu, 8 Jan 2026 22:35:24 +0530 Subject: [PATCH 3/4] refactored:Added helper function and improved maintainability --- src/google/adk/telemetry/tracing.py | 22 +++---- tests/unittests/telemetry/test_spans.py | 83 ++++++++----------------- 2 files changed, 37 insertions(+), 68 deletions(-) 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 56ea5ed4fe..dc7e268de3 100644 --- a/tests/unittests/telemetry/test_spans.py +++ b/tests/unittests/telemetry/test_spans.py @@ -82,6 +82,22 @@ 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 is" + f' 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,30 +490,11 @@ async def test_call_llm_disabling_request_response_content( trace_call_llm(invocation_context, 'test_event_id', llm_request, llm_response) # Assert - Check attributes are set to JSON string '{}' not dict {} - llm_request_calls = [ - call - for call in mock_span_fixture.set_attribute.call_args_list - if call.args[0] == 'gcp.vertex.agent.llm_request' - ] - assert ( - len(llm_request_calls) == 1 - ), "Expected 'gcp.vertex.agent.llm_request' to be set exactly once" - assert llm_request_calls[0].args[1] == '{}', ( - "Expected JSON string '{}' for llm_request when content capture is" - f' disabled, got {llm_request_calls[0].args[1]!r}' + _assert_span_attribute_set_to_empty_json( + mock_span_fixture, 'gcp.vertex.agent.llm_request' ) - - llm_response_calls = [ - call - for call in mock_span_fixture.set_attribute.call_args_list - if call.args[0] == 'gcp.vertex.agent.llm_response' - ] - assert ( - len(llm_response_calls) == 1 - ), "Expected 'gcp.vertex.agent.llm_response' to be set exactly once" - assert llm_response_calls[0].args[1] == '{}', ( - "Expected JSON string '{}' for llm_response when content capture is" - f' disabled, got {llm_response_calls[0].args[1]!r}' + _assert_span_attribute_set_to_empty_json( + mock_span_fixture, 'gcp.vertex.agent.llm_response' ) @@ -544,30 +541,11 @@ def test_trace_tool_call_disabling_request_response_content( ) # Assert - Check attributes are set to JSON string '{}' not dict {} - tool_args_calls = [ - call - for call in mock_span_fixture.set_attribute.call_args_list - if call.args[0] == 'gcp.vertex.agent.tool_call_args' - ] - assert ( - len(tool_args_calls) == 1 - ), "Expected 'gcp.vertex.agent.tool_call_args' to be set exactly once" - assert tool_args_calls[0].args[1] == '{}', ( - "Expected JSON string '{}' for tool_call_args when content capture is" - f' disabled, got {tool_args_calls[0].args[1]!r}' + _assert_span_attribute_set_to_empty_json( + mock_span_fixture, 'gcp.vertex.agent.tool_call_args' ) - - tool_response_calls = [ - call - for call in mock_span_fixture.set_attribute.call_args_list - if call.args[0] == 'gcp.vertex.agent.tool_response' - ] - assert ( - len(tool_response_calls) == 1 - ), "Expected 'gcp.vertex.agent.tool_response' to be set exactly once" - assert tool_response_calls[0].args[1] == '{}', ( - "Expected JSON string '{}' for tool_response when content capture is" - f' disabled, got {tool_response_calls[0].args[1]!r}' + _assert_span_attribute_set_to_empty_json( + mock_span_fixture, 'gcp.vertex.agent.tool_response' ) @@ -596,15 +574,6 @@ def test_trace_merged_tool_disabling_request_response_content( ) # Assert - Check attribute is set to JSON string '{}' not dict {} - tool_response_calls = [ - call - for call in mock_span_fixture.set_attribute.call_args_list - if call.args[0] == 'gcp.vertex.agent.tool_response' - ] - assert ( - len(tool_response_calls) == 1 - ), "Expected 'gcp.vertex.agent.tool_response' to be set exactly once" - assert tool_response_calls[0].args[1] == '{}', ( - "Expected JSON string '{}' for tool_response when content capture is" - f' disabled, got {tool_response_calls[0].args[1]!r}' + _assert_span_attribute_set_to_empty_json( + mock_span_fixture, 'gcp.vertex.agent.tool_response' ) From 709675743920f0c33c6b3a9a80d5e04ccc5b93dd Mon Sep 17 00:00:00 2001 From: DineshThumma9 Date: Fri, 9 Jan 2026 10:32:13 +0530 Subject: [PATCH 4/4] fixed formatting --- tests/unittests/telemetry/test_spans.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/unittests/telemetry/test_spans.py b/tests/unittests/telemetry/test_spans.py index dc7e268de3..3f7cfccb2a 100644 --- a/tests/unittests/telemetry/test_spans.py +++ b/tests/unittests/telemetry/test_spans.py @@ -89,12 +89,10 @@ def _assert_span_attribute_set_to_empty_json(mock_span, attribute_name: str): 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 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 is" - f' disabled, got {calls[0].args[1]!r}' + f"Expected JSON string '{{}}' for {attribute_name} when content capture" + f' is disabled, got {calls[0].args[1]!r}' )