Skip to content

Commit 39bb9df

Browse files
vertex-sdk-botcopybara-github
authored andcommitted
chore: Enable default-on telemetry for ADK agents.
PiperOrigin-RevId: 825861717
1 parent 35ac4c5 commit 39bb9df

6 files changed

Lines changed: 405 additions & 2 deletions

File tree

tests/unit/vertex_adk/test_agent_engine_templates_adk.py

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,25 @@
1616
import importlib
1717
import json
1818
import os
19+
import cloudpickle
20+
import sys
1921
from unittest import mock
2022
from typing import Optional
2123

2224
from google import auth
25+
from google.auth import credentials as auth_credentials
26+
from google.cloud import storage
2327
import vertexai
28+
from google.cloud import aiplatform
29+
from google.cloud.aiplatform_v1 import types as aip_types
30+
from google.cloud.aiplatform_v1.services import reasoning_engine_service
31+
from google.cloud.aiplatform import base
2432
from google.cloud.aiplatform import initializer
2533
from vertexai.agent_engines import _utils
2634
from vertexai import agent_engines
35+
from vertexai.agent_engines.templates import adk as adk_template
36+
from vertexai.agent_engines import _agent_engines
37+
from google.api_core import operation as ga_operation
2738
from google.genai import types
2839
import pytest
2940
import uuid
@@ -75,6 +86,52 @@ def __init__(self, name: str, model: str):
7586
"streaming_mode": "sse",
7687
"max_llm_calls": 500,
7788
}
89+
_TEST_STAGING_BUCKET = "gs://test-bucket"
90+
_TEST_CREDENTIALS = mock.Mock(spec=auth_credentials.AnonymousCredentials())
91+
_TEST_PARENT = f"projects/{_TEST_PROJECT}/locations/{_TEST_LOCATION}"
92+
_TEST_RESOURCE_ID = "1028944691210842416"
93+
_TEST_AGENT_ENGINE_RESOURCE_NAME = (
94+
f"{_TEST_PARENT}/reasoningEngines/{_TEST_RESOURCE_ID}"
95+
)
96+
_TEST_AGENT_ENGINE_DISPLAY_NAME = "Agent Engine Display Name"
97+
_TEST_GCS_DIR_NAME = _agent_engines._DEFAULT_GCS_DIR_NAME
98+
_TEST_BLOB_FILENAME = _agent_engines._BLOB_FILENAME
99+
_TEST_REQUIREMENTS_FILE = _agent_engines._REQUIREMENTS_FILE
100+
_TEST_EXTRA_PACKAGES_FILE = _agent_engines._EXTRA_PACKAGES_FILE
101+
_TEST_AGENT_ENGINE_GCS_URI = "{}/{}/{}".format(
102+
_TEST_STAGING_BUCKET,
103+
_TEST_GCS_DIR_NAME,
104+
_TEST_BLOB_FILENAME,
105+
)
106+
_TEST_AGENT_ENGINE_DEPENDENCY_FILES_GCS_URI = "{}/{}/{}".format(
107+
_TEST_STAGING_BUCKET,
108+
_TEST_GCS_DIR_NAME,
109+
_TEST_EXTRA_PACKAGES_FILE,
110+
)
111+
_TEST_AGENT_ENGINE_REQUIREMENTS_GCS_URI = "{}/{}/{}".format(
112+
_TEST_STAGING_BUCKET,
113+
_TEST_GCS_DIR_NAME,
114+
_TEST_REQUIREMENTS_FILE,
115+
)
116+
_TEST_AGENT_ENGINE_PACKAGE_SPEC = aip_types.ReasoningEngineSpec.PackageSpec(
117+
python_version=f"{sys.version_info.major}.{sys.version_info.minor}",
118+
pickle_object_gcs_uri=_TEST_AGENT_ENGINE_GCS_URI,
119+
dependency_files_gcs_uri=_TEST_AGENT_ENGINE_DEPENDENCY_FILES_GCS_URI,
120+
requirements_gcs_uri=_TEST_AGENT_ENGINE_REQUIREMENTS_GCS_URI,
121+
)
122+
_ADK_AGENT_FRAMEWORK = adk_template.AdkApp.agent_framework
123+
_TEST_AGENT_ENGINE_OBJ = aip_types.ReasoningEngine(
124+
name=_TEST_AGENT_ENGINE_RESOURCE_NAME,
125+
display_name=_TEST_AGENT_ENGINE_DISPLAY_NAME,
126+
spec=aip_types.ReasoningEngineSpec(
127+
package_spec=_TEST_AGENT_ENGINE_PACKAGE_SPEC,
128+
agent_framework=_ADK_AGENT_FRAMEWORK,
129+
),
130+
)
131+
132+
GOOGLE_CLOUD_AGENT_ENGINE_ENABLE_TELEMETRY = (
133+
"GOOGLE_CLOUD_AGENT_ENGINE_ENABLE_TELEMETRY"
134+
)
78135

79136

80137
@pytest.fixture(scope="module")
@@ -726,3 +783,174 @@ async def test_async_stream_query_invalid_message_type(self):
726783
):
727784
async for _ in app.async_stream_query(user_id=_TEST_USER_ID, message=123):
728785
pass
786+
787+
788+
@pytest.fixture(scope="module")
789+
def create_agent_engine_mock():
790+
with mock.patch.object(
791+
reasoning_engine_service.ReasoningEngineServiceClient,
792+
"create_reasoning_engine",
793+
) as create_agent_engine_mock:
794+
create_agent_engine_lro_mock = mock.Mock(ga_operation.Operation)
795+
create_agent_engine_lro_mock.result.return_value = _TEST_AGENT_ENGINE_OBJ
796+
create_agent_engine_mock.return_value = create_agent_engine_lro_mock
797+
yield create_agent_engine_mock
798+
799+
800+
@pytest.fixture(scope="module")
801+
def get_agent_engine_mock():
802+
with mock.patch.object(
803+
reasoning_engine_service.ReasoningEngineServiceClient,
804+
"get_reasoning_engine",
805+
) as get_agent_engine_mock:
806+
api_client_mock = mock.Mock()
807+
api_client_mock.get_reasoning_engine.return_value = _TEST_AGENT_ENGINE_OBJ
808+
get_agent_engine_mock.return_value = api_client_mock
809+
yield get_agent_engine_mock
810+
811+
812+
@pytest.fixture(scope="module")
813+
def cloud_storage_create_bucket_mock():
814+
with mock.patch.object(storage, "Client") as cloud_storage_mock:
815+
bucket_mock = mock.Mock(spec=storage.Bucket)
816+
bucket_mock.blob.return_value.open.return_value = "blob_file"
817+
bucket_mock.blob.return_value.upload_from_filename.return_value = None
818+
bucket_mock.blob.return_value.upload_from_string.return_value = None
819+
820+
cloud_storage_mock.get_bucket = mock.Mock(
821+
side_effect=ValueError("bucket not found")
822+
)
823+
cloud_storage_mock.bucket.return_value = bucket_mock
824+
cloud_storage_mock.create_bucket.return_value = bucket_mock
825+
826+
yield cloud_storage_mock
827+
828+
829+
@pytest.fixture(scope="module")
830+
def cloudpickle_dump_mock():
831+
with mock.patch.object(cloudpickle, "dump") as cloudpickle_dump_mock:
832+
yield cloudpickle_dump_mock
833+
834+
835+
@pytest.fixture(scope="module")
836+
def cloudpickle_load_mock():
837+
with mock.patch.object(cloudpickle, "load") as cloudpickle_load_mock:
838+
yield cloudpickle_load_mock
839+
840+
841+
@pytest.fixture(scope="function")
842+
def get_gca_resource_mock():
843+
with mock.patch.object(
844+
base.VertexAiResourceNoun,
845+
"_get_gca_resource",
846+
) as get_gca_resource_mock:
847+
get_gca_resource_mock.return_value = _TEST_AGENT_ENGINE_OBJ
848+
yield get_gca_resource_mock
849+
850+
851+
# Function scope is required for the pytest parameterized tests.
852+
@pytest.fixture(scope="function")
853+
def update_agent_engine_mock():
854+
with mock.patch.object(
855+
reasoning_engine_service.ReasoningEngineServiceClient,
856+
"update_reasoning_engine",
857+
) as update_agent_engine_mock:
858+
yield update_agent_engine_mock
859+
860+
861+
@pytest.mark.usefixtures("google_auth_mock")
862+
class TestAgentEngines:
863+
def setup_method(self):
864+
importlib.reload(initializer)
865+
importlib.reload(aiplatform)
866+
aiplatform.init(
867+
project=_TEST_PROJECT,
868+
location=_TEST_LOCATION,
869+
credentials=_TEST_CREDENTIALS,
870+
staging_bucket=_TEST_STAGING_BUCKET,
871+
)
872+
873+
def teardown_method(self):
874+
initializer.global_pool.shutdown(wait=True)
875+
876+
@pytest.mark.parametrize(
877+
"env_vars,expected_env_vars",
878+
[
879+
({}, {GOOGLE_CLOUD_AGENT_ENGINE_ENABLE_TELEMETRY: "true"}),
880+
(None, {GOOGLE_CLOUD_AGENT_ENGINE_ENABLE_TELEMETRY: "true"}),
881+
(
882+
{"some_env": "some_val"},
883+
{
884+
"some_env": "some_val",
885+
GOOGLE_CLOUD_AGENT_ENGINE_ENABLE_TELEMETRY: "true",
886+
},
887+
),
888+
(
889+
{GOOGLE_CLOUD_AGENT_ENGINE_ENABLE_TELEMETRY: "false"},
890+
{GOOGLE_CLOUD_AGENT_ENGINE_ENABLE_TELEMETRY: "false"},
891+
),
892+
],
893+
)
894+
def test_create_default_telemetry_enablement(
895+
self,
896+
create_agent_engine_mock: mock.Mock,
897+
cloud_storage_create_bucket_mock: mock.Mock,
898+
cloudpickle_dump_mock: mock.Mock,
899+
cloudpickle_load_mock: mock.Mock,
900+
get_gca_resource_mock: mock.Mock,
901+
env_vars: dict[str, str],
902+
expected_env_vars: dict[str, str],
903+
):
904+
agent_engines.create(
905+
agent_engine=agent_engines.AdkApp(agent=_TEST_AGENT),
906+
env_vars=env_vars,
907+
)
908+
create_agent_engine_mock.assert_called_once()
909+
deployment_spec = create_agent_engine_mock.call_args.kwargs[
910+
"reasoning_engine"
911+
].spec.deployment_spec
912+
assert _utils.to_dict(deployment_spec)["env"] == [
913+
{"name": key, "value": value} for key, value in expected_env_vars.items()
914+
]
915+
916+
@pytest.mark.parametrize(
917+
"env_vars,expected_env_vars",
918+
[
919+
({}, {GOOGLE_CLOUD_AGENT_ENGINE_ENABLE_TELEMETRY: "true"}),
920+
(None, {GOOGLE_CLOUD_AGENT_ENGINE_ENABLE_TELEMETRY: "true"}),
921+
(
922+
{"some_env": "some_val"},
923+
{
924+
"some_env": "some_val",
925+
GOOGLE_CLOUD_AGENT_ENGINE_ENABLE_TELEMETRY: "true",
926+
},
927+
),
928+
(
929+
{GOOGLE_CLOUD_AGENT_ENGINE_ENABLE_TELEMETRY: "false"},
930+
{GOOGLE_CLOUD_AGENT_ENGINE_ENABLE_TELEMETRY: "false"},
931+
),
932+
],
933+
)
934+
def test_update_default_telemetry_enablement(
935+
self,
936+
update_agent_engine_mock: mock.Mock,
937+
cloud_storage_create_bucket_mock: mock.Mock,
938+
cloudpickle_dump_mock: mock.Mock,
939+
cloudpickle_load_mock: mock.Mock,
940+
get_gca_resource_mock: mock.Mock,
941+
get_agent_engine_mock: mock.Mock,
942+
env_vars: dict[str, str],
943+
expected_env_vars: dict[str, str],
944+
):
945+
agent_engines.update(
946+
resource_name=_TEST_AGENT_ENGINE_RESOURCE_NAME,
947+
description="foobar", # avoid "At least one of ... must be specified" errors.
948+
env_vars=env_vars,
949+
)
950+
update_agent_engine_mock.assert_called_once()
951+
deployment_spec = update_agent_engine_mock.call_args.kwargs[
952+
"request"
953+
].reasoning_engine.spec.deployment_spec
954+
assert _utils.to_dict(deployment_spec)["env"] == [
955+
{"name": key, "value": value} for key, value in expected_env_vars.items()
956+
]

tests/unit/vertex_langchain/test_agent_engines.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3260,7 +3260,7 @@ def test_create_agent_engine_with_invalid_type_env_var(
32603260
"TEST_ENV_VAR": 0.01, # should be a string or dict or SecretRef
32613261
},
32623262
)
3263-
with pytest.raises(TypeError, match="env_vars must be a list or a dict"):
3263+
with pytest.raises(TypeError, match="env_vars must be a list, tuple or a dict"):
32643264
agent_engines.create(
32653265
self.test_agent,
32663266
display_name=_TEST_AGENT_ENGINE_DISPLAY_NAME,

tests/unit/vertexai/genai/test_agent_engines.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from google.cloud import aiplatform
3232
import vertexai
3333
from google.cloud.aiplatform import initializer
34+
from vertexai.agent_engines.templates import adk
3435
from vertexai._genai import _agent_engines_utils
3536
from vertexai._genai import agent_engines
3637
from vertexai._genai import types as _genai_types
@@ -40,6 +41,9 @@
4041

4142

4243
_TEST_AGENT_FRAMEWORK = "test-agent-framework"
44+
GOOGLE_CLOUD_AGENT_ENGINE_ENABLE_TELEMETRY = (
45+
"GOOGLE_CLOUD_AGENT_ENGINE_ENABLE_TELEMETRY"
46+
)
4347

4448

4549
class CapitalizeEngine:
@@ -848,6 +852,49 @@ def test_create_agent_engine_config_lightweight(self, mock_prepare):
848852
"description": _TEST_AGENT_ENGINE_DESCRIPTION,
849853
}
850854

855+
@mock.patch.object(_agent_engines_utils, "_prepare")
856+
@pytest.mark.parametrize(
857+
"env_vars,expected_env_vars",
858+
[
859+
({}, {GOOGLE_CLOUD_AGENT_ENGINE_ENABLE_TELEMETRY: "true"}),
860+
(None, {GOOGLE_CLOUD_AGENT_ENGINE_ENABLE_TELEMETRY: "true"}),
861+
(
862+
{"some_env": "some_val"},
863+
{
864+
"some_env": "some_val",
865+
GOOGLE_CLOUD_AGENT_ENGINE_ENABLE_TELEMETRY: "true",
866+
},
867+
),
868+
(
869+
{GOOGLE_CLOUD_AGENT_ENGINE_ENABLE_TELEMETRY: "false"},
870+
{GOOGLE_CLOUD_AGENT_ENGINE_ENABLE_TELEMETRY: "false"},
871+
),
872+
],
873+
)
874+
def test_agent_engine_adk_telemetry_enablement(
875+
self,
876+
mock_prepare: mock.Mock,
877+
env_vars: dict[str, str],
878+
expected_env_vars: dict[str, str],
879+
):
880+
agent = mock.Mock(spec=adk.AdkApp)
881+
agent.clone = lambda: agent
882+
agent.register_operations = lambda: {}
883+
884+
config = self.client.agent_engines._create_config(
885+
mode="create",
886+
agent=agent,
887+
staging_bucket=_TEST_STAGING_BUCKET,
888+
display_name=_TEST_AGENT_ENGINE_DISPLAY_NAME,
889+
description=_TEST_AGENT_ENGINE_DESCRIPTION,
890+
env_vars=env_vars,
891+
)
892+
assert config["display_name"] == _TEST_AGENT_ENGINE_DISPLAY_NAME
893+
assert config["description"] == _TEST_AGENT_ENGINE_DESCRIPTION
894+
assert config["spec"]["deployment_spec"]["env"] == [
895+
{"name": key, "value": value} for key, value in expected_env_vars.items()
896+
]
897+
851898
@mock.patch.object(_agent_engines_utils, "_prepare")
852899
def test_create_agent_engine_config_full(self, mock_prepare):
853900
config = self.client.agent_engines._create_config(

vertexai/_genai/_agent_engines_utils.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1845,3 +1845,50 @@ def _validate_resource_limits_or_raise(resource_limits: dict[str, str]) -> None:
18451845
f"Memory size of {memory_str} requires at least {min_cpu} CPUs."
18461846
f" Got {cpu}"
18471847
)
1848+
1849+
1850+
def _is_adk_agent(agent_engine: _AgentEngineInterface) -> bool:
1851+
"""Checks if the agent engine is an ADK agent.
1852+
1853+
Args:
1854+
agent_engine: The agent engine to check.
1855+
1856+
Returns:
1857+
True if the agent engine is an ADK agent, False otherwise.
1858+
"""
1859+
1860+
from vertexai.agent_engines.templates import adk
1861+
1862+
return isinstance(agent_engine, adk.AdkApp)
1863+
1864+
1865+
def _add_telemetry_enablement_env(
1866+
env_vars: Optional[Dict[str, Union[str, Any]]]
1867+
) -> Optional[Dict[str, Union[str, Any]]]:
1868+
"""Adds telemetry enablement env var to the env vars.
1869+
1870+
This is in order to achieve default-on telemetry.
1871+
If the telemetry enablement env var is already set, we do not override it.
1872+
1873+
Args:
1874+
env_vars: The env vars to add the telemetry enablement env var to.
1875+
1876+
Returns:
1877+
The env vars with the telemetry enablement env var added.
1878+
"""
1879+
1880+
GOOGLE_CLOUD_AGENT_ENGINE_ENABLE_TELEMETRY = (
1881+
"GOOGLE_CLOUD_AGENT_ENGINE_ENABLE_TELEMETRY"
1882+
)
1883+
env_to_add = {GOOGLE_CLOUD_AGENT_ENGINE_ENABLE_TELEMETRY: "true"}
1884+
1885+
if env_vars is None:
1886+
return env_to_add
1887+
1888+
if not isinstance(env_vars, dict):
1889+
raise TypeError(f"env_vars must be a dict, but got {type(env_vars)}.")
1890+
1891+
if GOOGLE_CLOUD_AGENT_ENGINE_ENABLE_TELEMETRY in env_vars:
1892+
return env_vars
1893+
1894+
return env_vars | env_to_add

vertexai/_genai/agent_engines.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,6 +1032,8 @@ def _create_config(
10321032
raise ValueError("location must be set using `vertexai.Client`.")
10331033
gcs_dir_name = gcs_dir_name or _agent_engines_utils._DEFAULT_GCS_DIR_NAME
10341034
agent = _agent_engines_utils._validate_agent_or_raise(agent=agent)
1035+
if _agent_engines_utils._is_adk_agent(agent):
1036+
env_vars = _agent_engines_utils._add_telemetry_enablement_env(env_vars)
10351037
staging_bucket = _agent_engines_utils._validate_staging_bucket_or_raise(
10361038
staging_bucket=staging_bucket,
10371039
)

0 commit comments

Comments
 (0)