feat(otel): env variable for disabling llm_request and llm_response in spans

The default without the variable set is to keep the content in spans to keep backward compatible behavior for existing users.

This allows to enable tracing without potential PII data from request and response. Google GenAI instrumentation lib requires an explicit opt-in to enable request and response content - see https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation-genai/opentelemetry-instrumentation-google-genai#enabling-message-content.

PiperOrigin-RevId: 820351154
This commit is contained in:
Kacper Jawoszek
2025-10-16 13:07:27 -07:00
committed by Copybara-Service
parent 9dc00360e3
commit e50f05a9fc
2 changed files with 208 additions and 29 deletions
+146
View File
@@ -13,6 +13,7 @@
# limitations under the License.
import json
import os
from typing import Any
from typing import Dict
from typing import Optional
@@ -23,6 +24,7 @@ from google.adk.agents.llm_agent import LlmAgent
from google.adk.models.llm_request import LlmRequest
from google.adk.models.llm_response import LlmResponse
from google.adk.sessions.in_memory_session_service import InMemorySessionService
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
@@ -371,3 +373,147 @@ def test_trace_merged_tool_calls_sets_correct_attributes(
expected_calls, any_order=True
)
mock_event_fixture.model_dumps_json.assert_called_once_with(exclude_none=True)
@pytest.mark.asyncio
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"""
# Arrange
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)
llm_request = LlmRequest(
model='gemini-pro',
contents=[
types.Content(
role='user',
parts=[types.Part(text='Hello, how are you?')],
),
],
)
llm_response = LlmResponse(
turn_complete=True,
finish_reason=types.FinishReason.STOP,
)
# Act
trace_call_llm(invocation_context, 'test_event_id', llm_request, llm_response)
# Assert
assert not any(
call_obj.args[0] == 'gcp.vertex.agent.llm_request'
and call_obj.args[1] != {}
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(
call_obj.args[0] == 'gcp.vertex.agent.llm_response'
and call_obj.args[1] != {}
for call_obj in mock_span_fixture.set_attribute.call_args_list
), (
"Attribute 'gcp.vertex.agent.llm_response' was incorrectly set on the"
' span.'
)
def test_trace_tool_call_disabling_request_response_content(
monkeypatch,
mock_span_fixture,
mock_tool_fixture,
mock_event_fixture,
):
"""Test trace_tool_call doesn't set request and response attributes if env is set to false"""
# Arrange
monkeypatch.setenv(ADK_CAPTURE_MESSAGE_CONTENT_IN_SPANS, 'false')
monkeypatch.setattr(
'opentelemetry.trace.get_current_span', lambda: mock_span_fixture
)
test_args: Dict[str, Any] = {'query': 'details', 'id_list': [1, 2, 3]}
test_tool_call_id: str = 'tool_call_id_002'
test_event_id: str = 'event_id_dict_002'
dict_function_response: Dict[str, Any] = {
'data': 'structured_data',
'count': 5,
}
mock_event_fixture.id = test_event_id
mock_event_fixture.content = types.Content(
role='user',
parts=[
types.Part(
function_response=types.FunctionResponse(
id=test_tool_call_id,
name='test_function_1',
response=dict_function_response,
)
),
],
)
# Act
trace_tool_call(
tool=mock_tool_fixture,
args=test_args,
function_response_event=mock_event_fixture,
)
# Assert
assert not any(
call_obj.args[0] == 'gcp.vertex.agent.tool_call_args'
and call_obj.args[1] != {}
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 not any(
call_obj.args[0] == 'gcp.vertex.agent.tool_response'
and call_obj.args[1] != {}
for call_obj in mock_span_fixture.set_attribute.call_args_list
), (
"Attribute 'gcp.vertex.agent.tool_response' was incorrectly set on the"
' span.'
)
def test_trace_merged_tool_disabling_request_response_content(
monkeypatch,
mock_span_fixture,
mock_event_fixture,
):
"""Test trace_merged_tool doesn't set request and response attributes if env is set to false"""
# Arrange
monkeypatch.setenv(ADK_CAPTURE_MESSAGE_CONTENT_IN_SPANS, 'false')
monkeypatch.setattr(
'opentelemetry.trace.get_current_span', lambda: mock_span_fixture
)
test_response_event_id = 'merged_evt_id_001'
custom_event_json_output = (
'{"custom_event_payload": true, "details": "merged_details"}'
)
mock_event_fixture.model_dumps_json.return_value = custom_event_json_output
# Act
trace_merged_tool_calls(
response_event_id=test_response_event_id,
function_response_event=mock_event_fixture,
)
# Assert
assert not any(
call_obj.args[0] == 'gcp.vertex.agent.tool_response'
and call_obj.args[1] != {}
for call_obj in mock_span_fixture.set_attribute.call_args_list
), (
"Attribute 'gcp.vertex.agent.tool_response' was incorrectly set on the"
' span.'
)