Skip to content

Commit f0dc877

Browse files
vertex-sdk-botcopybara-github
authored andcommitted
feat: add resource attributes and GenAI instrumentation to default instrumentator in ADK VAE template
PiperOrigin-RevId: 819772253
1 parent af8c898 commit f0dc877

4 files changed

Lines changed: 293 additions & 76 deletions

File tree

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@
140140
adk_extra_require = [
141141
# 1.0.0 contains breaking changes, so we need to pin to 1.0.0.
142142
"google-adk >= 1.0.0, < 2.0.0",
143+
"opentelemetry-instrumentation-google-genai>=0.3b0, <1.0.0",
143144
]
144145

145146
reasoning_engine_extra_require = [
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# -*- coding: utf-8 -*-
2+
3+
# Copyright 2025 Google LLC
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
"""System tests for reasoning engines."""
18+
import pytest
19+
import time
20+
import sys
21+
import datetime
22+
from google import auth
23+
import google.cloud.aiplatform
24+
import vertexai
25+
from google.cloud import trace_v1
26+
from google.adk.sessions import in_memory_session_service
27+
from google.protobuf.timestamp_pb2 import Timestamp
28+
from tests.system.aiplatform import e2e_base
29+
from vertexai import agent_engines
30+
31+
_BLOB_FILENAME = agent_engines._agent_engines._BLOB_FILENAME
32+
_CANNED_AGENT_RESPONSE = "Hello agent"
33+
34+
35+
# Class definition needs to be in a function, to not pull in entire testing module as a dependency, when pickling this class.
36+
def adk_agent_no_dependencies():
37+
from google.adk.agents import base_agent
38+
39+
class AdkAgentNoDependencies(base_agent.BaseAgent):
40+
async def _run_async_impl(self, ctx):
41+
from google.adk.events import event
42+
from google.genai import types
43+
44+
yield event.Event(
45+
invocation_id=ctx.invocation_id,
46+
author="agent",
47+
content=types.Content(
48+
role="agent", parts=[types.Part(text=_CANNED_AGENT_RESPONSE)]
49+
),
50+
)
51+
52+
return AdkAgentNoDependencies(name="test_agent")
53+
54+
55+
@pytest.mark.usefixtures(
56+
"prepare_staging_bucket", "delete_staging_bucket", "tear_down_resources"
57+
)
58+
class TestAgentEngines(e2e_base.TestEndToEnd):
59+
"""System tests for reasoning engines."""
60+
61+
_temp_prefix = "test-reasoning-engine"
62+
63+
@pytest.mark.asyncio
64+
async def test_adk_template(self, shared_state):
65+
# Avoid import errors template when pickling the template.
66+
sys.modules["google.cloud.aiplatform.aiplatform"] = google.cloud.aiplatform
67+
super().setup_method()
68+
credentials, _ = auth.default(
69+
scopes=["https://www.googleapis.com/auth/cloud-platform"]
70+
)
71+
vertexai.init(
72+
project=e2e_base._PROJECT,
73+
location=e2e_base._LOCATION,
74+
staging_bucket=f"gs://{shared_state['staging_bucket_name']}",
75+
credentials=credentials,
76+
)
77+
78+
app = agent_engines.AdkApp(
79+
agent=adk_agent_no_dependencies(),
80+
enable_tracing=True,
81+
session_service_builder=in_memory_session_service.InMemorySessionService,
82+
)
83+
agent = agent_engines.AgentEngine.create(
84+
agent_engine=app,
85+
requirements=["google-cloud-aiplatform[agent_engines,adk]"],
86+
display_name="test-display-name",
87+
description="test-description",
88+
gcs_dir_name="test-gcs-dir-name",
89+
)
90+
shared_state.setdefault("resources", [])
91+
shared_state["resources"].append(agent) # Deletion at teardown.
92+
93+
resp = await agent.async_stream_query(
94+
message="Hello", user_id="test-user"
95+
).__anext__()
96+
assert resp["content"]["parts"][0]["text"] == _CANNED_AGENT_RESPONSE
97+
98+
traces = []
99+
trace_query_attempts = 10
100+
trace_query_end_time = datetime.datetime.now(
101+
datetime.timezone.utc
102+
) + datetime.timedelta(minutes=1)
103+
trace_query_start_time = trace_query_end_time - datetime.timedelta(minutes=2)
104+
trace_client = trace_v1.TraceServiceClient()
105+
for _ in range(trace_query_attempts):
106+
traces = trace_client.list_traces(
107+
request=trace_v1.ListTracesRequest(
108+
project_id=e2e_base._PROJECT,
109+
start_time=Timestamp().FromDatetime(dt=trace_query_start_time),
110+
end_time=Timestamp().FromDatetime(dt=trace_query_end_time),
111+
)
112+
)
113+
traces = list(traces)
114+
if len(traces) > 0:
115+
break
116+
time.sleep(5)
117+
118+
assert len(traces) > 0

tests/unit/vertex_adk/test_agent_engine_templates_adk.py

Lines changed: 62 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
import base64
1616
import importlib
1717
import json
18+
import os
1819
from unittest import mock
20+
import dataclasses
1921

2022
from google import auth
2123
import vertexai
@@ -94,16 +96,35 @@ def vertexai_init_mock():
9496

9597
@pytest.fixture
9698
def cloud_trace_exporter_mock():
97-
with mock.patch.object(
98-
_utils,
99-
"_import_cloud_trace_exporter_or_warn",
100-
) as cloud_trace_exporter_mock:
101-
yield cloud_trace_exporter_mock
99+
import sys
100+
import opentelemetry
101+
102+
mock_cloud_trace_exporter = mock.Mock()
103+
104+
opentelemetry.exporter = type(sys)("exporter")
105+
opentelemetry.exporter.cloud_trace = type(sys)("cloud_trace")
106+
opentelemetry.exporter.cloud_trace.CloudTraceSpanExporter = (
107+
mock_cloud_trace_exporter
108+
)
109+
110+
sys.modules["opentelemetry.exporter"] = opentelemetry.exporter
111+
sys.modules["opentelemetry.exporter.cloud_trace"] = (
112+
opentelemetry.exporter.cloud_trace
113+
)
114+
115+
yield mock_cloud_trace_exporter
116+
117+
del sys.modules["opentelemetry.exporter.cloud_trace"]
118+
del sys.modules["opentelemetry.exporter"]
102119

103120

104121
@pytest.fixture
105-
def tracer_provider_mock():
106-
with mock.patch("opentelemetry.sdk.trace.TracerProvider") as tracer_provider_mock:
122+
def trace_provider_mock():
123+
import opentelemetry.sdk.trace
124+
125+
with mock.patch.object(
126+
opentelemetry.sdk.trace, "TracerProvider"
127+
) as tracer_provider_mock:
107128
yield tracer_provider_mock
108129

109130

@@ -115,15 +136,6 @@ def simple_span_processor_mock():
115136
yield simple_span_processor_mock
116137

117138

118-
@pytest.fixture
119-
def mock_adk_version():
120-
with mock.patch(
121-
"google.cloud.aiplatform.vertexai.agent_engines.templates.adk.get_adk_version",
122-
return_value="1.5.0",
123-
):
124-
yield
125-
126-
127139
class _MockRunner:
128140
def run(self, *args, **kwargs):
129141
from google.adk.events import event
@@ -385,6 +397,40 @@ async def test_async_search_memory(self):
385397
)
386398
assert len(response.memories) >= 1
387399

400+
@mock.patch.dict(os.environ, {"GOOGLE_CLOUD_AGENT_ENGINE_ID": "test_agent_id"})
401+
def test_tracing_setup(
402+
self, trace_provider_mock: mock.Mock, cloud_trace_exporter_mock: mock.Mock
403+
):
404+
app = agent_engines.AdkApp(agent=_TEST_AGENT, enable_tracing=True)
405+
app.set_up()
406+
407+
expected_attributes = {
408+
"telemetry.sdk.language": "python",
409+
"telemetry.sdk.name": "opentelemetry",
410+
"telemetry.sdk.version": "1.36.0",
411+
"gcp.project_id": "test-project",
412+
"service.name": "test_agent_id",
413+
"cloud.resource_id": "//aiplatform.googleapis.com/projects/test-project/locations/us-central1/reasoningEngines/test_agent_id",
414+
}
415+
416+
@dataclasses.dataclass
417+
class RegexMatchingAll:
418+
keys: set[str]
419+
420+
def __eq__(self, regex: object) -> bool:
421+
return isinstance(regex, str) and set(regex.split("|")) == self.keys
422+
423+
cloud_trace_exporter_mock.assert_called_once_with(
424+
project_id=_TEST_PROJECT,
425+
client=mock.ANY,
426+
resource_regex=RegexMatchingAll(keys=set(expected_attributes.keys())),
427+
)
428+
429+
assert (
430+
trace_provider_mock.call_args.kwargs["resource"].attributes
431+
== expected_attributes
432+
)
433+
388434
@pytest.mark.usefixtures("caplog")
389435
def test_enable_tracing(
390436
self,

0 commit comments

Comments
 (0)