Skip to content

Commit 0659e1b

Browse files
vertex-sdk-botcopybara-github
authored andcommitted
chore: Enable default-on telemetry for ADK agents.
PiperOrigin-RevId: 826161271
1 parent 27ef56b commit 0659e1b

File tree

6 files changed

+405
-2
lines changed

6 files changed

+405
-2
lines changed

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

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)