Skip to content

Commit 527b6dc

Browse files
vertex-sdk-botcopybara-github
authored andcommitted
feat(agentplatform): add agent_card to AgentRuntimeConfig for runtimes.create and update
PiperOrigin-RevId: 998390852
1 parent 16024c8 commit 527b6dc

3 files changed

Lines changed: 106 additions & 4 deletions

File tree

‎agentplatform/_genai/runtimes.py‎

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1911,6 +1911,7 @@ def create(
19111911
container_spec=config.container_spec,
19121912
keep_alive_probe=keep_alive_probe,
19131913
build_config=config.build_config,
1914+
agent_card=config.agent_card,
19141915
)
19151916
operation = self._create(config=api_config)
19161917
logger.info(f"Create Agent Runtime backing LRO: {operation.name}")
@@ -2230,6 +2231,7 @@ def _create_config(
22302231
keep_alive_probe: Optional[dict[str, Any]] = None,
22312232
traffic_config: Optional[types.ReasoningEngineTrafficConfigDict] = None,
22322233
build_config: Optional[types.ReasoningEngineSpecBuildSpecDict] = None,
2234+
agent_card: Optional[dict[str, Any]] = None,
22332235
) -> types.UpdateRuntimeConfigDict:
22342236
import sys
22352237

@@ -2404,14 +2406,15 @@ def _create_config(
24042406
agent=agent,
24052407
)
24062408

2407-
if hasattr(agent, "agent_card"):
2408-
agent_card = getattr(agent, "agent_card")
2409-
if agent_card:
2409+
# An agent_card given in the config takes precedence; it is added below.
2410+
if agent_card is None and hasattr(agent, "agent_card"):
2411+
agent_object_card = getattr(agent, "agent_card")
2412+
if agent_object_card:
24102413
try:
24112414
from google.protobuf import json_format
24122415

24132416
runtime_spec["agent_card"] = json_format.MessageToDict(
2414-
agent_card
2417+
agent_object_card
24152418
)
24162419
except Exception as e:
24172420
raise ValueError(
@@ -2452,6 +2455,12 @@ def _create_config(
24522455
if build_spec:
24532456
runtime_spec["build_spec"] = build_spec
24542457

2458+
if agent_card is not None:
2459+
if runtime_spec is None:
2460+
runtime_spec = {}
2461+
runtime_spec["agent_card"] = agent_card
2462+
update_masks.append("spec.agent_card")
2463+
24552464
if runtime_spec is not None:
24562465
config["spec"] = runtime_spec
24572466

@@ -2724,6 +2733,7 @@ def update(
27242733
keep_alive_probe=keep_alive_probe,
27252734
traffic_config=traffic_config,
27262735
build_config=config.build_config,
2736+
agent_card=config.agent_card,
27272737
)
27282738
operation = self._update(name=name, config=api_config)
27292739
logger.info(f"Update Agent Runtime backing LRO: {operation.name}")

‎agentplatform/_genai/types/common.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32104,6 +32104,16 @@ class AgentRuntimeConfig(_common.BaseModel):
3210432104
default=None,
3210532105
description="""The build config for the Agent Runtime. Allows bringing your own Cloud Build private worker pool (BYOBP) and, optionally, a build-time service account for the container build. Supported keys: `worker_pool` (the resource name of the Cloud Build WorkerPool to use for the build) and `service_account` (the service account that Cloud Build uses to run the build; only applicable when `worker_pool` is specified).""",
3210632106
)
32107+
agent_card: Optional[dict[str, Any]] = Field(
32108+
default=None,
32109+
description="""The A2A agent card for the Agent Runtime, as a dictionary with
32110+
the A2A JSON field names (camelCase, such as `preferredTransport`).
32111+
Use it when there is no `agent` object to read the card from, for
32112+
example with `container_spec` or `source_packages`. If the `agent` also
32113+
has an `agent_card`, this value is used instead. On `update()`, it
32114+
replaces the deployed card; pass `{}` to remove it. The service sets
32115+
the card's `url` to the runtime's A2A endpoint.""",
32116+
)
3210732117

3210832118

3210932119
class AgentRuntimeConfigDict(TypedDict, total=False):
@@ -32294,6 +32304,15 @@ class AgentRuntimeConfigDict(TypedDict, total=False):
3229432304
build_config: Optional[ReasoningEngineSpecBuildSpecDict]
3229532305
"""The build config for the Agent Runtime. Allows bringing your own Cloud Build private worker pool (BYOBP) and, optionally, a build-time service account for the container build. Supported keys: `worker_pool` (the resource name of the Cloud Build WorkerPool to use for the build) and `service_account` (the service account that Cloud Build uses to run the build; only applicable when `worker_pool` is specified)."""
3229632306

32307+
agent_card: Optional[dict[str, Any]]
32308+
"""The A2A agent card for the Agent Runtime, as a dictionary with
32309+
the A2A JSON field names (camelCase, such as `preferredTransport`).
32310+
Use it when there is no `agent` object to read the card from, for
32311+
example with `container_spec` or `source_packages`. If the `agent` also
32312+
has an `agent_card`, this value is used instead. On `update()`, it
32313+
replaces the deployed card; pass `{}` to remove it. The service sets
32314+
the card's `url` to the runtime's A2A endpoint."""
32315+
3229732316

3229832317
AgentRuntimeConfigOrDict = Union[AgentRuntimeConfig, AgentRuntimeConfigDict]
3229932318

‎tests/unit/agentplatform/genai/test_agent_engines.py‎

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,7 @@ def register_operations(self) -> Dict[str, List[str]]:
578578
},
579579
"max_seconds": 60,
580580
}
581+
_TEST_AGENT_CARD = {"name": "test-agent", "defaultInputModes": ["text"]}
581582
_TEST_AGENT_ENGINE_SPEC = _genai_types.ReasoningEngineSpecDict(
582583
agent_framework=_TEST_AGENT_ENGINE_FRAMEWORK,
583584
class_methods=[_TEST_AGENT_ENGINE_CLASS_METHOD_1],
@@ -1434,6 +1435,18 @@ def test_create_runtime_config_with_container_spec(self):
14341435
)
14351436
assert "keep_alive_probe" not in config["spec"].get("deployment_spec", {})
14361437

1438+
def test_create_runtime_config_with_container_spec_and_agent_card(self):
1439+
container_spec = {"image_uri": "gcr.io/test-project/test-image"}
1440+
config = self.client.runtimes._create_config(
1441+
mode="create",
1442+
container_spec=container_spec,
1443+
class_methods=_TEST_AGENT_ENGINE_CLASS_METHODS,
1444+
agent_card=_TEST_AGENT_CARD,
1445+
)
1446+
assert config["spec"]["container_spec"] == container_spec
1447+
assert config["spec"]["agent_card"] == _TEST_AGENT_CARD
1448+
assert "update_mask" not in config
1449+
14371450
def test_create_runtime_config_with_container_spec_and_keep_alive_probe(
14381451
self,
14391452
):
@@ -1644,6 +1657,32 @@ def test_update_runtime_config_without_agent_card_omits_mask(
16441657
assert "agent_card" not in config["spec"]
16451658
assert "spec.agent_card" not in config["update_mask"].split(",")
16461659

1660+
@pytest.mark.parametrize("agent_card", [_TEST_AGENT_CARD, {}])
1661+
def test_update_runtime_config_with_only_agent_card(self, agent_card):
1662+
config = self.client.runtimes._create_config(
1663+
mode="update",
1664+
agent_card=agent_card,
1665+
)
1666+
assert config["spec"] == {"agent_card": agent_card}
1667+
assert config["update_mask"] == "spec.agent_card"
1668+
1669+
@mock.patch.object(_runtimes_utils, "_prepare")
1670+
def test_update_runtime_config_agent_card_overrides_agent(self, mock_prepare):
1671+
from google.protobuf import struct_pb2
1672+
1673+
agent_object_card = struct_pb2.Struct()
1674+
agent_object_card["version"] = "1.0.0"
1675+
config_card = {"name": "test-agent", "version": "2.0.0"}
1676+
config = self.client.runtimes._create_config(
1677+
mode="update",
1678+
agent=CapitalizeEngineWithAgentCard(agent_card=agent_object_card),
1679+
staging_bucket=_TEST_STAGING_BUCKET,
1680+
requirements=_TEST_AGENT_ENGINE_REQUIREMENTS,
1681+
agent_card=config_card,
1682+
)
1683+
assert config["spec"]["agent_card"] == config_card
1684+
assert config["update_mask"].split(",").count("spec.agent_card") == 1
1685+
16471686
@mock.patch.object(_runtimes_utils, "_prepare")
16481687
def test_update_runtime_clear_service_account(self, mock_prepare):
16491688
config = self.client.runtimes._create_config(
@@ -2394,6 +2433,7 @@ def test_create_runtime_with_env_vars_dict(
23942433
container_spec=None,
23952434
keep_alive_probe=None,
23962435
build_config=None,
2436+
agent_card=None,
23972437
)
23982438
request_mock.assert_called_with(
23992439
"post",
@@ -2501,6 +2541,7 @@ def test_create_runtime_with_custom_service_account(
25012541
container_spec=None,
25022542
keep_alive_probe=None,
25032543
build_config=None,
2544+
agent_card=None,
25042545
)
25052546
request_mock.assert_called_with(
25062547
"post",
@@ -2607,6 +2648,7 @@ def test_create_runtime_with_experimental_mode(
26072648
container_spec=None,
26082649
keep_alive_probe=None,
26092650
build_config=None,
2651+
agent_card=None,
26102652
)
26112653
request_mock.assert_called_with(
26122654
"post",
@@ -2782,6 +2824,7 @@ def test_create_runtime_with_class_methods(
27822824
container_spec=None,
27832825
keep_alive_probe=None,
27842826
build_config=None,
2827+
agent_card=None,
27852828
)
27862829
request_mock.assert_called_with(
27872830
"post",
@@ -2883,6 +2926,7 @@ def test_create_runtime_with_agent_framework(
28832926
container_spec=None,
28842927
keep_alive_probe=None,
28852928
build_config=None,
2929+
agent_card=None,
28862930
)
28872931
request_mock.assert_called_with(
28882932
"post",
@@ -3246,6 +3290,35 @@ def test_update_runtime_description(self, mock_await_operation):
32463290
None,
32473291
)
32483292

3293+
@mock.patch.object(_runtimes_utils, "_await_operation")
3294+
def test_update_runtime_agent_card(self, mock_await_operation):
3295+
mock_await_operation.return_value = _genai_types.RuntimeOperation(
3296+
response=_genai_types.ReasoningEngine(
3297+
name=_TEST_AGENT_ENGINE_RESOURCE_NAME,
3298+
spec=_TEST_AGENT_ENGINE_SPEC,
3299+
)
3300+
)
3301+
with mock.patch.object(
3302+
self.client.runtimes._api_client, "request"
3303+
) as request_mock:
3304+
request_mock.return_value = genai_types.HttpResponse(body="")
3305+
self.client.runtimes.update(
3306+
name=_TEST_AGENT_ENGINE_RESOURCE_NAME,
3307+
config=_genai_types.AgentRuntimeConfig(
3308+
agent_card=_TEST_AGENT_CARD,
3309+
),
3310+
)
3311+
request_mock.assert_called_with(
3312+
"patch",
3313+
f"{_TEST_AGENT_ENGINE_RESOURCE_NAME}?updateMask=spec.agent_card",
3314+
{
3315+
"_url": {"name": _TEST_AGENT_ENGINE_RESOURCE_NAME},
3316+
"spec": {"agent_card": _TEST_AGENT_CARD},
3317+
"_query": {"updateMask": "spec.agent_card"},
3318+
},
3319+
None,
3320+
)
3321+
32493322
def test_delete_runtime(self):
32503323
with mock.patch.object(
32513324
self.client.runtimes._api_client, "request"

0 commit comments

Comments
 (0)