Skip to content

Commit 54dcb26

Browse files
shawn-yang-googlecopybara-github
authored andcommitted
feat: add support for agent_config_source (ADK) in AgentEngines
PiperOrigin-RevId: 875355220
1 parent e5f71de commit 54dcb26

File tree

4 files changed

+372
-59
lines changed

4 files changed

+372
-59
lines changed

tests/unit/vertexai/genai/test_agent_engines.py

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,6 +1113,63 @@ def test_create_agent_engine_config_with_developer_connect_source(self):
11131113
== _TEST_AGENT_ENGINE_IDENTITY_TYPE_SERVICE_ACCOUNT
11141114
)
11151115

1116+
def test_create_agent_engine_config_with_agent_config_source_and_requirements_file(
1117+
self,
1118+
):
1119+
with tempfile.TemporaryDirectory() as tmpdir:
1120+
requirements_file_path = os.path.join(tmpdir, "requirements.txt")
1121+
with open(requirements_file_path, "w") as f:
1122+
f.write("requests==2.0.0")
1123+
1124+
config = self.client.agent_engines._create_config(
1125+
mode="create",
1126+
display_name=_TEST_AGENT_ENGINE_DISPLAY_NAME,
1127+
description=_TEST_AGENT_ENGINE_DESCRIPTION,
1128+
class_methods=_TEST_AGENT_ENGINE_CLASS_METHODS,
1129+
agent_framework=_TEST_AGENT_FRAMEWORK,
1130+
identity_type=_TEST_AGENT_ENGINE_IDENTITY_TYPE_SERVICE_ACCOUNT,
1131+
python_version=_TEST_PYTHON_VERSION_OVERRIDE,
1132+
agent_config_source={"adk_config": {"json_config": {}}},
1133+
requirements_file=requirements_file_path,
1134+
)
1135+
1136+
assert config["spec"]["source_code_spec"] == {
1137+
"agent_config_source": {"adk_config": {"json_config": {}}},
1138+
"python_spec": {
1139+
"version": _TEST_PYTHON_VERSION_OVERRIDE,
1140+
"requirements_file": requirements_file_path,
1141+
},
1142+
}
1143+
1144+
def test_create_agent_engine_config_with_agent_config_source_and_entrypoint_module_warns(
1145+
self, caplog
1146+
):
1147+
caplog.set_level(logging.WARNING, logger="vertexai_genai.agentengines")
1148+
1149+
config = self.client.agent_engines._create_config(
1150+
mode="create",
1151+
display_name=_TEST_AGENT_ENGINE_DISPLAY_NAME,
1152+
description=_TEST_AGENT_ENGINE_DESCRIPTION,
1153+
class_methods=_TEST_AGENT_ENGINE_CLASS_METHODS,
1154+
agent_framework=_TEST_AGENT_FRAMEWORK,
1155+
identity_type=_TEST_AGENT_ENGINE_IDENTITY_TYPE_SERVICE_ACCOUNT,
1156+
python_version=_TEST_PYTHON_VERSION_OVERRIDE,
1157+
agent_config_source={"adk_config": {"json_config": {}}},
1158+
entrypoint_module="some_module",
1159+
)
1160+
1161+
assert (
1162+
"`entrypoint_module` and `entrypoint_object` are ignored when"
1163+
in caplog.text
1164+
)
1165+
assert config["spec"]["source_code_spec"] == {
1166+
"agent_config_source": {"adk_config": {"json_config": {}}},
1167+
"python_spec": {
1168+
"version": _TEST_PYTHON_VERSION_OVERRIDE,
1169+
},
1170+
}
1171+
# entrypoint_module is NOT in python_spec
1172+
11161173
@mock.patch.object(
11171174
_agent_engines_utils,
11181175
"_create_base64_encoded_tarball",
@@ -1146,6 +1203,106 @@ def test_create_agent_engine_config_with_source_packages_and_image_spec_raises(
11461203
)
11471204
assert "`image_spec` cannot be specified alongside" in str(excinfo.value)
11481205

1206+
@mock.patch.object(
1207+
_agent_engines_utils,
1208+
"_create_base64_encoded_tarball",
1209+
return_value="test_tarball",
1210+
)
1211+
def test_create_agent_engine_config_with_agent_config_source_and_image_spec_raises(
1212+
self, mock_create_base64_encoded_tarball
1213+
):
1214+
with tempfile.TemporaryDirectory() as tmpdir:
1215+
test_file_path = os.path.join(tmpdir, "test_file.txt")
1216+
with open(test_file_path, "w") as f:
1217+
f.write("test content")
1218+
requirements_file_path = os.path.join(tmpdir, "requirements.txt")
1219+
with open(requirements_file_path, "w") as f:
1220+
f.write("requests==2.0.0")
1221+
1222+
with pytest.raises(ValueError) as excinfo:
1223+
self.client.agent_engines._create_config(
1224+
mode="create",
1225+
display_name=_TEST_AGENT_ENGINE_DISPLAY_NAME,
1226+
description=_TEST_AGENT_ENGINE_DESCRIPTION,
1227+
class_methods=_TEST_AGENT_ENGINE_CLASS_METHODS,
1228+
agent_framework=_TEST_AGENT_FRAMEWORK,
1229+
identity_type=_TEST_AGENT_ENGINE_IDENTITY_TYPE_SERVICE_ACCOUNT,
1230+
python_version=_TEST_PYTHON_VERSION_OVERRIDE,
1231+
image_spec={},
1232+
agent_config_source={"adk_config": {"json_config": {}}},
1233+
)
1234+
assert "`image_spec` cannot be specified alongside" in str(excinfo.value)
1235+
1236+
def test_create_agent_engine_config_with_agent_config_source(self):
1237+
config = self.client.agent_engines._create_config(
1238+
mode="create",
1239+
display_name=_TEST_AGENT_ENGINE_DISPLAY_NAME,
1240+
description=_TEST_AGENT_ENGINE_DESCRIPTION,
1241+
class_methods=_TEST_AGENT_ENGINE_CLASS_METHODS,
1242+
agent_framework=_TEST_AGENT_FRAMEWORK,
1243+
identity_type=_TEST_AGENT_ENGINE_IDENTITY_TYPE_SERVICE_ACCOUNT,
1244+
python_version=_TEST_PYTHON_VERSION_OVERRIDE,
1245+
agent_config_source={"adk_config": {"json_config": {}}},
1246+
)
1247+
assert config["display_name"] == _TEST_AGENT_ENGINE_DISPLAY_NAME
1248+
assert config["description"] == _TEST_AGENT_ENGINE_DESCRIPTION
1249+
assert config["spec"]["agent_framework"] == _TEST_AGENT_FRAMEWORK
1250+
assert config["spec"]["source_code_spec"] == {
1251+
"agent_config_source": {"adk_config": {"json_config": {}}},
1252+
"python_spec": {"version": _TEST_PYTHON_VERSION_OVERRIDE},
1253+
}
1254+
assert config["spec"]["class_methods"] == _TEST_AGENT_ENGINE_CLASS_METHODS
1255+
assert (
1256+
config["spec"]["identity_type"]
1257+
== _TEST_AGENT_ENGINE_IDENTITY_TYPE_SERVICE_ACCOUNT
1258+
)
1259+
1260+
@mock.patch.object(
1261+
_agent_engines_utils,
1262+
"_create_base64_encoded_tarball",
1263+
return_value="test_tarball",
1264+
)
1265+
def test_create_agent_engine_config_with_source_packages_and_agent_config_source(
1266+
self, mock_create_base64_encoded_tarball
1267+
):
1268+
with tempfile.TemporaryDirectory() as tmpdir:
1269+
test_file_path = os.path.join(tmpdir, "test_file.txt")
1270+
with open(test_file_path, "w") as f:
1271+
f.write("test content")
1272+
requirements_file_path = os.path.join(tmpdir, "requirements.txt")
1273+
with open(requirements_file_path, "w") as f:
1274+
f.write("requests==2.0.0")
1275+
1276+
config = self.client.agent_engines._create_config(
1277+
mode="create",
1278+
display_name=_TEST_AGENT_ENGINE_DISPLAY_NAME,
1279+
description=_TEST_AGENT_ENGINE_DESCRIPTION,
1280+
source_packages=[test_file_path],
1281+
class_methods=_TEST_AGENT_ENGINE_CLASS_METHODS,
1282+
agent_framework=_TEST_AGENT_FRAMEWORK,
1283+
identity_type=_TEST_AGENT_ENGINE_IDENTITY_TYPE_SERVICE_ACCOUNT,
1284+
python_version=_TEST_PYTHON_VERSION_OVERRIDE,
1285+
agent_config_source={"adk_config": {"json_config": {}}},
1286+
)
1287+
assert config["display_name"] == _TEST_AGENT_ENGINE_DISPLAY_NAME
1288+
assert config["description"] == _TEST_AGENT_ENGINE_DESCRIPTION
1289+
assert config["spec"]["agent_framework"] == _TEST_AGENT_FRAMEWORK
1290+
assert config["spec"]["source_code_spec"] == {
1291+
"agent_config_source": {
1292+
"adk_config": {"json_config": {}},
1293+
"inline_source": {"source_archive": "test_tarball"},
1294+
},
1295+
"python_spec": {"version": _TEST_PYTHON_VERSION_OVERRIDE},
1296+
}
1297+
assert config["spec"]["class_methods"] == _TEST_AGENT_ENGINE_CLASS_METHODS
1298+
mock_create_base64_encoded_tarball.assert_called_once_with(
1299+
source_packages=[test_file_path]
1300+
)
1301+
assert (
1302+
config["spec"]["identity_type"]
1303+
== _TEST_AGENT_ENGINE_IDENTITY_TYPE_SERVICE_ACCOUNT
1304+
)
1305+
11491306
@mock.patch.object(
11501307
_agent_engines_utils,
11511308
"_create_base64_encoded_tarball",
@@ -1916,6 +2073,7 @@ def test_create_agent_engine_with_env_vars_dict(
19162073
python_version=None,
19172074
build_options=None,
19182075
image_spec=None,
2076+
agent_config_source=None,
19192077
)
19202078
request_mock.assert_called_with(
19212079
"post",
@@ -2018,6 +2176,7 @@ def test_create_agent_engine_with_custom_service_account(
20182176
python_version=None,
20192177
build_options=None,
20202178
image_spec=None,
2179+
agent_config_source=None,
20212180
)
20222181
request_mock.assert_called_with(
20232182
"post",
@@ -2119,6 +2278,7 @@ def test_create_agent_engine_with_experimental_mode(
21192278
python_version=None,
21202279
build_options=None,
21212280
image_spec=None,
2281+
agent_config_source=None,
21222282
)
21232283
request_mock.assert_called_with(
21242284
"post",
@@ -2289,6 +2449,7 @@ def test_create_agent_engine_with_class_methods(
22892449
python_version=None,
22902450
build_options=None,
22912451
image_spec=None,
2452+
agent_config_source=None,
22922453
)
22932454
request_mock.assert_called_with(
22942455
"post",
@@ -2385,6 +2546,7 @@ def test_create_agent_engine_with_agent_framework(
23852546
python_version=None,
23862547
build_options=None,
23872548
image_spec=None,
2549+
agent_config_source=None,
23882550
)
23892551
request_mock.assert_called_with(
23902552
"post",

vertexai/_genai/agent_engines.py

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -786,7 +786,12 @@ def _list_pager(
786786
def _is_lightweight_creation(
787787
self, agent: Any, config: types.AgentEngineConfig
788788
) -> bool:
789-
if agent or config.source_packages or config.developer_connect_source:
789+
if (
790+
agent
791+
or config.source_packages
792+
or config.developer_connect_source
793+
or config.agent_config_source
794+
):
790795
return False
791796
return True
792797

@@ -937,6 +942,9 @@ def create(
937942
developer_connect_source = json.loads(
938943
developer_connect_source.model_dump_json()
939944
)
945+
agent_config_source = config.agent_config_source
946+
if agent_config_source is not None:
947+
agent_config_source = json.loads(agent_config_source.model_dump_json())
940948
if agent and agent_engine:
941949
raise ValueError("Please specify only one of `agent` or `agent_engine`.")
942950
elif agent_engine:
@@ -975,6 +983,7 @@ def create(
975983
python_version=config.python_version,
976984
build_options=config.build_options,
977985
image_spec=config.image_spec,
986+
agent_config_source=agent_config_source,
978987
)
979988
operation = self._create(config=api_config)
980989
reasoning_engine_id = _agent_engines_utils._get_reasoning_engine_id(
@@ -1034,10 +1043,13 @@ def _set_source_code_spec(
10341043
image_spec: Optional[
10351044
types.ReasoningEngineSpecSourceCodeSpecImageSpecDict
10361045
] = None,
1046+
agent_config_source: Optional[
1047+
types.ReasoningEngineSpecSourceCodeSpecAgentConfigSourceDict
1048+
] = None,
10371049
) -> None:
10381050
"""Sets source_code_spec for agent engine inside the `spec`."""
10391051
source_code_spec = types.ReasoningEngineSpecSourceCodeSpecDict()
1040-
if source_packages:
1052+
if source_packages and not agent_config_source:
10411053
source_packages = _agent_engines_utils._validate_packages_or_raise(
10421054
packages=source_packages,
10431055
build_options=build_options,
@@ -1053,13 +1065,15 @@ def _set_source_code_spec(
10531065
source_code_spec["developer_connect_source"] = {
10541066
"config": developer_connect_source
10551067
}
1056-
else:
1068+
elif not agent_config_source:
10571069
raise ValueError(
1058-
"Please specify one of `source_packages` or `developer_connect_source`."
1070+
"Please specify one of `source_packages`, `developer_connect_source`, "
1071+
"or `agent_config_source`."
10591072
)
10601073
if class_methods is None:
10611074
raise ValueError(
1062-
"`class_methods` must be specified if `source_packages` or `developer_connect_source` is specified."
1075+
"`class_methods` must be specified if `source_packages`, "
1076+
"`developer_connect_source`, or `agent_config_source` is specified."
10631077
)
10641078
update_masks.append("spec.class_methods")
10651079
class_methods_spec_list = (
@@ -1078,6 +1092,11 @@ def _set_source_code_spec(
10781092
"`entrypoint_object`, or `requirements_file`, as they are "
10791093
"mutually exclusive."
10801094
)
1095+
if agent_config_source:
1096+
raise ValueError(
1097+
"`image_spec` cannot be specified alongside `agent_config_source`, "
1098+
"as they are mutually exclusive."
1099+
)
10811100
update_masks.append("spec.source_code_spec.image_spec")
10821101
source_code_spec["image_spec"] = image_spec
10831102
spec["source_code_spec"] = source_code_spec
@@ -1087,6 +1106,38 @@ def _set_source_code_spec(
10871106
python_spec: types.ReasoningEngineSpecSourceCodeSpecPythonSpecDict = {
10881107
"version": sys_version,
10891108
}
1109+
if agent_config_source is not None:
1110+
if entrypoint_module or entrypoint_object:
1111+
logger.warning(
1112+
"`entrypoint_module` and `entrypoint_object` are ignored when "
1113+
"`agent_config_source` is specified, as they are pre-defined."
1114+
)
1115+
if source_packages:
1116+
source_packages = _agent_engines_utils._validate_packages_or_raise(
1117+
packages=source_packages,
1118+
build_options=build_options,
1119+
)
1120+
update_masks.append(
1121+
"spec.source_code_spec.agent_config_source.inline_source.source_archive"
1122+
)
1123+
agent_config_source["inline_source"] = { # type: ignore[typeddict-item]
1124+
"source_archive": _agent_engines_utils._create_base64_encoded_tarball(
1125+
source_packages=source_packages
1126+
)
1127+
}
1128+
update_masks.append("spec.source_code_spec.agent_config_source")
1129+
source_code_spec["agent_config_source"] = agent_config_source
1130+
1131+
if requirements_file is not None:
1132+
update_masks.append(
1133+
"spec.source_code_spec.python_spec.requirements_file"
1134+
)
1135+
python_spec["requirements_file"] = requirements_file
1136+
source_code_spec["python_spec"] = python_spec
1137+
1138+
spec["source_code_spec"] = source_code_spec
1139+
return
1140+
10901141
if not entrypoint_module:
10911142
raise ValueError(
10921143
"`entrypoint_module` must be specified if `source_packages` or `developer_connect_source` is specified."
@@ -1235,6 +1286,9 @@ def _create_config(
12351286
image_spec: Optional[
12361287
types.ReasoningEngineSpecSourceCodeSpecImageSpecDict
12371288
] = None,
1289+
agent_config_source: Optional[
1290+
types.ReasoningEngineSpecSourceCodeSpecAgentConfigSourceDict
1291+
] = None,
12381292
) -> types.UpdateAgentEngineConfigDict:
12391293
import sys
12401294

@@ -1307,7 +1361,12 @@ def _create_config(
13071361
sys_version=sys_version,
13081362
build_options=build_options,
13091363
)
1310-
elif source_packages or developer_connect_source:
1364+
elif (
1365+
source_packages
1366+
or developer_connect_source
1367+
or image_spec
1368+
or agent_config_source
1369+
):
13111370
agent_engine_spec = {}
13121371
self._set_source_code_spec(
13131372
spec=agent_engine_spec,
@@ -1321,6 +1380,7 @@ def _create_config(
13211380
sys_version=sys_version,
13221381
build_options=build_options,
13231382
image_spec=image_spec,
1383+
agent_config_source=agent_config_source,
13241384
)
13251385

13261386
is_deployment_spec_updated = (
@@ -1336,7 +1396,8 @@ def _create_config(
13361396
"To update `env_vars`, `psc_interface_config`, `min_instances`, "
13371397
"`max_instances`, `resource_limits`, or `container_concurrency`, "
13381398
"you must also provide the `agent` variable or the source code "
1339-
"options (`source_packages` or `developer_connect_source`)."
1399+
"options (`source_packages`, `developer_connect_source` or "
1400+
"`agent_config_source`)."
13401401
)
13411402

13421403
if agent_engine_spec is not None:
@@ -1598,6 +1659,9 @@ def update(
15981659
developer_connect_source = json.loads(
15991660
developer_connect_source.model_dump_json()
16001661
)
1662+
agent_config_source = config.agent_config_source
1663+
if agent_config_source is not None:
1664+
agent_config_source = json.loads(agent_config_source.model_dump_json())
16011665
if agent and agent_engine:
16021666
raise ValueError("Please specify only one of `agent` or `agent_engine`.")
16031667
elif agent_engine:
@@ -1633,6 +1697,7 @@ def update(
16331697
agent_framework=config.agent_framework,
16341698
python_version=config.python_version,
16351699
build_options=config.build_options,
1700+
agent_config_source=agent_config_source,
16361701
)
16371702
operation = self._update(name=name, config=api_config)
16381703
reasoning_engine_id = _agent_engines_utils._get_reasoning_engine_id(

0 commit comments

Comments
 (0)