From 5880109ab13456d5ec511e991de9271fbbec41d8 Mon Sep 17 00:00:00 2001 From: George Weale Date: Mon, 12 Jan 2026 11:14:45 -0800 Subject: [PATCH] fix: Set empty JSON string as placeholder for redacted content in traces When content capture is disabled, trace attributes for tool arguments, tool responses, LLM requests, LLM responses, and agent data are now set to the string '{}' instead of an empty dictionary Close #4094 Co-authored-by: George Weale PiperOrigin-RevId: 855304806 --- src/google/adk/telemetry/tracing.py | 12 +-- tests/unittests/telemetry/test_spans.py | 113 ++++++++++++++---------- 2 files changed, 71 insertions(+), 54 deletions(-) diff --git a/src/google/adk/telemetry/tracing.py b/src/google/adk/telemetry/tracing.py index 386ae3b4..d98d12a4 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 c87730a5..dd785daf 100644 --- a/tests/unittests/telemetry/test_spans.py +++ b/tests/unittests/telemetry/test_spans.py @@ -27,6 +27,7 @@ from google.adk.telemetry.tracing import ADK_CAPTURE_MESSAGE_CONTENT_IN_SPANS from google.adk.telemetry.tracing import trace_agent_invocation from google.adk.telemetry.tracing import trace_call_llm from google.adk.telemetry.tracing import trace_merged_tool_calls +from google.adk.telemetry.tracing import trace_send_data from google.adk.telemetry.tracing import trace_tool_call from google.adk.tools.base_tool import BaseTool from google.genai import types @@ -447,7 +448,7 @@ def test_trace_merged_tool_calls_sets_correct_attributes( async def test_call_llm_disabling_request_response_content( monkeypatch, mock_span_fixture ): - """Test trace_call_llm doesn't set request and response attributes if env is set to false""" + """Test trace_call_llm sets placeholders when capture is disabled.""" # Arrange monkeypatch.setenv(ADK_CAPTURE_MESSAGE_CONTENT_IN_SPANS, 'false') monkeypatch.setattr( @@ -474,23 +475,19 @@ async def test_call_llm_disabling_request_response_content( 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 ( + 'gcp.vertex.agent.llm_request', + '{}', + ) in ( + call_obj.args + for call_obj in mock_span_fixture.set_attribute.call_args_list + ) + assert ( + 'gcp.vertex.agent.llm_response', + '{}', + ) in ( + call_obj.args + for call_obj in mock_span_fixture.set_attribute.call_args_list ) @@ -500,7 +497,7 @@ def test_trace_tool_call_disabling_request_response_content( mock_tool_fixture, mock_event_fixture, ): - """Test trace_tool_call doesn't set request and response attributes if env is set to false""" + """Test trace_tool_call sets placeholders when capture is disabled.""" # Arrange monkeypatch.setenv(ADK_CAPTURE_MESSAGE_CONTENT_IN_SPANS, 'false') monkeypatch.setattr( @@ -537,26 +534,19 @@ def test_trace_tool_call_disabling_request_response_content( ) # 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 ( + 'gcp.vertex.agent.tool_call_args', + '{}', + ) in ( + call_obj.args + for call_obj in mock_span_fixture.set_attribute.call_args_list ) - - 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 ( + 'gcp.vertex.agent.tool_response', + '{}', + ) in ( + call_obj.args + for call_obj in mock_span_fixture.set_attribute.call_args_list ) @@ -565,7 +555,7 @@ def test_trace_merged_tool_disabling_request_response_content( mock_span_fixture, mock_event_fixture, ): - """Test trace_merged_tool doesn't set request and response attributes if env is set to false""" + """Test trace_merged_tool_calls sets placeholders when capture is disabled.""" # Arrange monkeypatch.setenv(ADK_CAPTURE_MESSAGE_CONTENT_IN_SPANS, 'false') monkeypatch.setattr( @@ -585,13 +575,40 @@ def test_trace_merged_tool_disabling_request_response_content( ) # 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 ( + '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 )