Skip to content

Commit 0775da5

Browse files
committed
fix: tolerate context-likes without user_content or session in record_agent_invocation
Use getattr for ctx.user_content and ctx.session.events when calling _record_agent_metrics so that test doubles, partial migrations, and external embedders that pass minimal InvocationContext-like objects don't crash with AttributeError on the metrics call. Change-Id: I7d54252dd222f3796e6ccdc13d4844a177b65e7d
1 parent e16629b commit 0775da5

2 files changed

Lines changed: 37 additions & 2 deletions

File tree

src/google/adk/telemetry/_instrumentation.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ async def record_agent_invocation(
114114
_record_agent_metrics(
115115
agent.name,
116116
elapsed_ms,
117-
ctx.user_content,
118-
ctx.session.events,
117+
getattr(ctx, "user_content", None),
118+
getattr(getattr(ctx, "session", None), "events", []),
119119
caught_error,
120120
)
121121

tests/unittests/telemetry/test_instrumentation.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
from google.adk.telemetry import _instrumentation
2121
from opentelemetry import trace
22+
import pytest
2223

2324

2425
def test_get_elapsed_ms_span_none():
@@ -80,3 +81,37 @@ def test_get_elapsed_ms_span_non_int_end():
8081
with mock.patch("time.monotonic", return_value=12.0):
8182
elapsed = _instrumentation._get_elapsed_ms(mock_span, start_time)
8283
assert elapsed == 2000.0
84+
85+
86+
@pytest.mark.asyncio
87+
async def test_record_agent_invocation_tolerates_minimal_context():
88+
"""Tolerates context-likes that lack user_content or session.
89+
90+
Test doubles, partial migrations, and external embedders can pass an
91+
InvocationContext-like object without `user_content` or with a `session`
92+
that has no `events` attribute. The telemetry path must not raise
93+
AttributeError on the metrics call in those cases.
94+
"""
95+
agent = mock.MagicMock()
96+
agent.name = "test_agent"
97+
# Bare object without `user_content` and without `session`.
98+
bare_ctx = object()
99+
100+
with (
101+
mock.patch.object(
102+
_instrumentation, "_record_agent_metrics"
103+
) as mock_record,
104+
mock.patch.object(_instrumentation, "tracing") as mock_tracing,
105+
):
106+
mock_tracing.tracer.start_as_current_span.return_value.__enter__.return_value = mock.MagicMock(
107+
spec=trace.Span
108+
)
109+
async with _instrumentation.record_agent_invocation(bare_ctx, agent):
110+
pass
111+
112+
mock_record.assert_called_once()
113+
call_args = mock_record.call_args
114+
# positional: (agent_name, elapsed_ms, user_content, events, caught_error)
115+
assert call_args.args[0] == "test_agent"
116+
assert call_args.args[2] is None # user_content default
117+
assert call_args.args[3] == [] # events default

0 commit comments

Comments
 (0)