Skip to content

Commit b7fcd15

Browse files
yinghsienwucopybara-github
authored andcommitted
fix: Resolve vertexai.types against agentplatform.types
PiperOrigin-RevId: 984095463
1 parent 36a47bd commit b7fcd15

5 files changed

Lines changed: 228 additions & 10 deletions

File tree

agentplatform/__init__.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,17 @@ def __getattr__(name): # type: ignore[no-untyped-def]
5454
global _genai_types
5555
if _genai_types is None:
5656
_genai_types = importlib.import_module("._genai.types", __name__)
57-
if "vertexai.types" not in sys.modules:
58-
sys.modules["vertexai.types"] = _genai_types
57+
# `types` is an alias for `._genai.types` rather than a real submodule,
58+
# so register it to keep
59+
# `from agentplatform.types import TypeName`
60+
# working without a prior attribute access. Spell it the google3 way:
61+
# Copybara rewrites that prefix in both directions, and the external
62+
# spelling fails its reversibility check. This key was `vertexai.types`,
63+
# which both misnamed this package's own alias and shadowed the real
64+
# `vertexai.types` module.
65+
types_module_name = f"{__name__}.types"
66+
if types_module_name not in sys.modules:
67+
sys.modules[types_module_name] = _genai_types
5968
return _genai_types
6069

6170
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")

tests/unit/vertexai/test_autorater_yaml.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,14 @@ def setup_method(self):
160160
vertexai.init(
161161
project=_TEST_PROJECT,
162162
location=_TEST_LOCATION,
163+
# Set explicitly rather than inherited. `init` leaves an already
164+
# configured credential in place, and test_extensions installs a
165+
# `Mock(spec=AnonymousCredentials)` globally that it never resets;
166+
# `_upload_string_to_gcs` then hands `global_config.credentials`
167+
# to `storage.Client`, which rejects the Mock's `universe_domain`.
168+
# Whether that leak reaches this module depends on how
169+
# `--dist=loadscope` happens to assign modules to xdist workers.
170+
credentials=auth_credentials.AnonymousCredentials(),
163171
)
164172

165173
def teardown_method(self):
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# -*- coding: utf-8 -*-
2+
3+
# Copyright 2026 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+
"""Unit tests for `vertexai.types` resolving against `agentplatform.types`."""
18+
19+
import sys
20+
21+
import agentplatform
22+
import vertexai
23+
from agentplatform._genai import types as agentplatform_types
24+
from vertexai._genai import types as legacy_types
25+
import pytest
26+
27+
# Both modules resolve these out of `_evals_metric_loaders` on first access, so
28+
# they are checked on their own rather than in the bulk comparisons below.
29+
_LAZY_NAMES = frozenset({"PrebuiltMetric", "RubricMetric"})
30+
31+
_SHARED_NAMES = sorted(
32+
(set(agentplatform_types.__all__) & set(legacy_types.__all__)) - _LAZY_NAMES
33+
)
34+
_LEGACY_ONLY_NAMES = sorted(
35+
set(legacy_types.__all__) - set(agentplatform_types.__all__)
36+
)
37+
_AGENT_PLATFORM_ONLY_NAMES = sorted(
38+
set(agentplatform_types.__all__) - set(legacy_types.__all__)
39+
)
40+
41+
42+
def test_vertexai_types_is_the_alias_module():
43+
assert sys.modules[vertexai.types.__name__] is vertexai.types
44+
assert vertexai.types is not legacy_types
45+
assert vertexai.types is not agentplatform_types
46+
47+
48+
def test_shared_names_are_the_agentplatform_objects():
49+
"""The point of the alias: one class per message rather than two."""
50+
assert _SHARED_NAMES, "expected the two modules to share generated types"
51+
mismatched = [
52+
name
53+
for name in _SHARED_NAMES
54+
if getattr(vertexai.types, name) is not getattr(agentplatform_types, name)
55+
]
56+
assert not mismatched
57+
58+
59+
def test_memory_profile_survives_an_isinstance_check():
60+
"""Regression: an agentplatform object checked against the vertexai name."""
61+
profile = agentplatform_types.MemoryProfile(schema_id="user-profile", profile={})
62+
assert isinstance(profile, vertexai.types.MemoryProfile)
63+
64+
65+
def test_shared_types_still_nest_inside_a_legacy_only_model():
66+
"""The two halves have to interoperate, not just coexist.
67+
68+
`GenerateAgentEngineMemoriesConfig` stays a `vertexai` class because
69+
agentplatform renamed it, but its `metadata` values are now agentplatform
70+
objects. Pydantic accepts them because both models derive from the genai
71+
`BaseModel`, which sets `from_attributes=True`.
72+
"""
73+
config = vertexai.types.GenerateAgentEngineMemoriesConfig(
74+
metadata={"record": vertexai.types.MemoryMetadataValue(string_value="123")}
75+
)
76+
assert type(config) is legacy_types.GenerateAgentEngineMemoriesConfig
77+
assert config.metadata["record"].string_value == "123"
78+
79+
80+
def test_legacy_only_names_still_resolve():
81+
"""The `AgentEngine*` surface agentplatform renamed is not taken away."""
82+
assert "AgentEngine" in _LEGACY_ONLY_NAMES
83+
mismatched = [
84+
name
85+
for name in _LEGACY_ONLY_NAMES
86+
if getattr(vertexai.types, name) is not getattr(legacy_types, name)
87+
]
88+
assert not mismatched
89+
90+
91+
def test_agentplatform_only_names_are_reachable():
92+
assert "Runtime" in _AGENT_PLATFORM_ONLY_NAMES
93+
mismatched = [
94+
name
95+
for name in _AGENT_PLATFORM_ONLY_NAMES
96+
if getattr(vertexai.types, name) is not getattr(agentplatform_types, name)
97+
]
98+
assert not mismatched
99+
100+
101+
@pytest.mark.parametrize("name", sorted(_LAZY_NAMES))
102+
def test_lazily_loaded_metric_names_resolve_to_agentplatform(name):
103+
assert getattr(vertexai.types, name) is getattr(agentplatform_types, name)
104+
105+
106+
def test_all_is_the_union_of_both_modules():
107+
expected = set(agentplatform_types.__all__) | set(legacy_types.__all__)
108+
assert set(vertexai.types.__all__) == expected
109+
assert set(dir(vertexai.types)) == expected
110+
111+
112+
def test_unknown_name_still_raises_attribute_error():
113+
with pytest.raises(AttributeError):
114+
_ = vertexai.types.NoSuchTypeName
115+
116+
117+
def test_agentplatform_registers_its_own_types_alias():
118+
"""`agentplatform.types` must no longer claim the `vertexai.types` key."""
119+
assert agentplatform.types is agentplatform_types
120+
assert sys.modules[f"{agentplatform.__name__}.types"] is agentplatform_types
121+
assert sys.modules[f"{vertexai.__name__}.types"] is not agentplatform_types

vertexai/__init__.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
"""The vertexai module."""
1616

1717
import importlib
18-
import sys
1918

2019
from google.cloud.aiplatform import version as aiplatform_version
2120

@@ -24,7 +23,6 @@
2423
from google.cloud.aiplatform import init
2524

2625
_genai_client = None
27-
_genai_types = None
2826

2927

3028
def __getattr__(name): # type: ignore[no-untyped-def]
@@ -45,12 +43,10 @@ def __getattr__(name): # type: ignore[no-untyped-def]
4543
return getattr(_genai_client, name)
4644

4745
if name == "types":
48-
global _genai_types
49-
if _genai_types is None:
50-
_genai_types = importlib.import_module("._genai.types", __name__)
51-
if "vertexai.types" not in sys.modules:
52-
sys.modules["vertexai.types"] = _genai_types
53-
return _genai_types
46+
# `types` is a real submodule that resolves against
47+
# `agentplatform.types`, so importing it also binds it as an attribute
48+
# here and this runs only once.
49+
return importlib.import_module(".types", __name__)
5450

5551
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
5652

vertexai/types.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
"""`vertexai.types` resolved against `agentplatform.types`.
16+
17+
Agent Platform re-generated the Gen AI types under `agentplatform` rather than
18+
moving them, so `vertexai.types.Memory` and `agentplatform.types.Memory` were
19+
two distinct classes carrying identical definitions. Code that hands an
20+
`agentplatform` object to a caller written against the `vertexai` name fails
21+
`isinstance`, even though both describe the same message.
22+
23+
This module removes the split. Every name `agentplatform.types` defines
24+
resolves here to the `agentplatform` class, so the two spellings are one
25+
object. The names that only ever existed under `vertexai` -- the `AgentEngine*`
26+
surface Agent Platform renamed to `Runtime*` and `MemoryBank*` -- keep
27+
resolving to their `vertexai._genai.types` classes, so nothing is taken away
28+
from callers that have not migrated.
29+
30+
The unification is a runtime one. Names resolve through `__getattr__`, so a
31+
type checker still reads `vertexai.types.X` as `Any`, exactly as it did while
32+
this name was a lazy alias for `vertexai._genai.types`. Re-exporting both
33+
modules under `typing.TYPE_CHECKING` would make them resolve statically too,
34+
but it also makes the checker read the generated `Optional` fields for the
35+
first time, which turns long-standing unguarded accesses in downstream callers
36+
into new type errors. Static resolution is being rolled out separately, once
37+
those callers are fixed.
38+
39+
The dependency runs one way: `vertexai` reaches into `agentplatform`, never the
40+
reverse. `google-cloud-agentplatform` ships `agentplatform` without `vertexai`
41+
and without the generated clients, and nothing here changes that.
42+
43+
Resolution stays lazy at each step. `vertexai.__getattr__` defers importing
44+
this module until `vertexai.types` is touched; `vertexai._genai.types` is
45+
imported only if a name is not found in `agentplatform`; and neither module's
46+
`PrebuiltMetric`/`RubricMetric` is resolved until asked for, so the evaluation
47+
dependencies are still not pulled in by a bare import.
48+
"""
49+
50+
from __future__ import annotations
51+
52+
import importlib as _importlib
53+
import types as _module_types
54+
import typing as _typing
55+
56+
from agentplatform._genai import types as _agentplatform_types
57+
58+
_legacy_types: _module_types.ModuleType | None = None
59+
60+
61+
def _get_legacy_types() -> _module_types.ModuleType:
62+
"""Imports `vertexai._genai.types` on first use."""
63+
global _legacy_types
64+
if _legacy_types is None:
65+
_legacy_types = _importlib.import_module("._genai.types", __package__)
66+
return _legacy_types
67+
68+
69+
def __getattr__(name: str) -> _typing.Any:
70+
# See https://peps.python.org/pep-0562/
71+
if name == "__all__":
72+
return __dir__()
73+
try:
74+
return getattr(_agentplatform_types, name)
75+
except AttributeError:
76+
pass
77+
try:
78+
return getattr(_get_legacy_types(), name)
79+
except AttributeError:
80+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'") from None
81+
82+
83+
def __dir__() -> list[str]:
84+
return sorted(set(_agentplatform_types.__all__) | set(_get_legacy_types().__all__))

0 commit comments

Comments
 (0)