From 9751b3363a5e000f929c3ab38484e6e1633427d6 Mon Sep 17 00:00:00 2001 From: wsa-2002 Date: Mon, 10 Nov 2025 15:13:17 +0800 Subject: [PATCH] fix: prompt token may be None in streaming mode --- src/google/adk/telemetry/tracing.py | 9 ++--- tests/unittests/telemetry/test_spans.py | 44 +++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/google/adk/telemetry/tracing.py b/src/google/adk/telemetry/tracing.py index f03cdc8010..ec0d968b49 100644 --- a/src/google/adk/telemetry/tracing.py +++ b/src/google/adk/telemetry/tracing.py @@ -293,10 +293,11 @@ def trace_call_llm( span.set_attribute('gcp.vertex.agent.llm_response', {}) if llm_response.usage_metadata is not None: - span.set_attribute( - 'gen_ai.usage.input_tokens', - llm_response.usage_metadata.prompt_token_count, - ) + if llm_response.usage_metadata.prompt_token_count is not None: + span.set_attribute( + 'gen_ai.usage.input_tokens', + llm_response.usage_metadata.prompt_token_count, + ) if llm_response.usage_metadata.candidates_token_count is not None: span.set_attribute( 'gen_ai.usage.output_tokens', diff --git a/tests/unittests/telemetry/test_spans.py b/tests/unittests/telemetry/test_spans.py index 38a8358f59..6a1bbe3889 100644 --- a/tests/unittests/telemetry/test_spans.py +++ b/tests/unittests/telemetry/test_spans.py @@ -155,6 +155,50 @@ async def test_trace_call_llm(monkeypatch, mock_span_fixture): ) +@pytest.mark.asyncio +async def test_trace_call_llm_with_no_usage_metadata( + monkeypatch, mock_span_fixture +): + """Test trace_call_llm handles usage metadata with None token counts.""" + 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?')], + ), + ], + config=types.GenerateContentConfig( + top_p=0.95, + max_output_tokens=1024, + ), + ) + llm_response = LlmResponse( + turn_complete=True, + finish_reason=types.FinishReason.STOP, + usage_metadata=types.GenerateContentResponseUsageMetadata(), + ) + trace_call_llm(invocation_context, 'test_event_id', llm_request, llm_response) + + expected_calls = [ + mock.call('gen_ai.system', 'gcp.vertex.agent'), + mock.call('gen_ai.request.top_p', 0.95), + mock.call('gen_ai.request.max_tokens', 1024), + mock.call('gcp.vertex.agent.llm_response', mock.ANY), + mock.call('gen_ai.response.finish_reasons', ['stop']), + ] + assert mock_span_fixture.set_attribute.call_count == 10 + mock_span_fixture.set_attribute.assert_has_calls( + expected_calls, any_order=True + ) + + @pytest.mark.asyncio async def test_trace_call_llm_with_binary_content( monkeypatch, mock_span_fixture