diff --git a/.kokoro/release/agentplatform.sh b/.kokoro/release/agentplatform.sh index 6a06cfee63..b41842c8fb 100755 --- a/.kokoro/release/agentplatform.sh +++ b/.kokoro/release/agentplatform.sh @@ -67,7 +67,14 @@ function build_package() { # Pinned: these run with credentials that can write to the staging repository, # so the release must not pick up whatever happens to be latest on PyPI that # day. Bump deliberately. - python3 -m pip install \ + # + # --ignore-installed because the image carries distro Python packages that pip + # otherwise treats as already satisfying twine. That leaves the old /usr/lib + # requests-toolbelt in place beside a urllib3 2.x under /usr/local, and twine + # then fails on import reaching for urllib3.contrib.appengine, which 2.x + # removed. Installing the whole dependency set under /usr/local, which precedes + # /usr/lib on sys.path, keeps the two sets apart. + python3 -m pip install --ignore-installed \ "build==1.6.0" \ "twine==7.0.0" \ "keyring==25.7.0" \ diff --git a/.release-please-manifest.json b/.release-please-manifest.json index d5eff4fbed..6a4aaea37c 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "2.1.3" + ".": "2.2.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index e3c74a87b6..a9c02c51af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## [2.2.0](https://github.com/googleapis/python-aiplatform/compare/v2.1.3...v2.2.0) (2026-09-22) + + +### Features + +* Update discovery doc ([6ad6fe8](https://github.com/googleapis/python-aiplatform/commit/6ad6fe8829c437b37012f2d0ff4f8a1ad2f95bac)) + + +### Bug Fixes + +* Allow unknown union variants when validating gaos responses in evals ([a9c4e6e](https://github.com/googleapis/python-aiplatform/commit/a9c4e6ee29111a57ae5fdb637da923b928280a2d)) +* Resolve vertexai.types against agentplatform.types ([5af2f8e](https://github.com/googleapis/python-aiplatform/commit/5af2f8e70784252a1ec50d59ce8805a01170acfe)) +* Resolve vertexai.types against agentplatform.types ([36a47bd](https://github.com/googleapis/python-aiplatform/commit/36a47bd6f43194333a6291af0b06b46ed8960d35)) +* Resolve vertexai.types against agentplatform.types ([75ecb3b](https://github.com/googleapis/python-aiplatform/commit/75ecb3b5c6cb610c8fec5c72c698996c0919e962)) + ## [2.1.3](https://github.com/googleapis/python-aiplatform/compare/v2.1.0...v2.1.3) (2026-09-15) diff --git a/agentplatform/__init__.py b/agentplatform/__init__.py index feb8436449..fc5923a4b0 100644 --- a/agentplatform/__init__.py +++ b/agentplatform/__init__.py @@ -54,8 +54,17 @@ def __getattr__(name): # type: ignore[no-untyped-def] global _genai_types if _genai_types is None: _genai_types = importlib.import_module("._genai.types", __name__) - if "vertexai.types" not in sys.modules: - sys.modules["vertexai.types"] = _genai_types + # `types` is an alias for `._genai.types` rather than a real submodule, + # so register it to keep + # `from agentplatform.types import TypeName` + # working without a prior attribute access. Spell it the google3 way: + # Copybara rewrites that prefix in both directions, and the external + # spelling fails its reversibility check. This key was `vertexai.types`, + # which both misnamed this package's own alias and shadowed the real + # `vertexai.types` module. + types_module_name = f"{__name__}.types" + if types_module_name not in sys.modules: + sys.modules[types_module_name] = _genai_types return _genai_types raise AttributeError(f"module '{__name__}' has no attribute '{name}'") diff --git a/agentplatform/_genai/_evals_common.py b/agentplatform/_genai/_evals_common.py index 7cb197d8a6..db85f3819d 100644 --- a/agentplatform/_genai/_evals_common.py +++ b/agentplatform/_genai/_evals_common.py @@ -37,6 +37,11 @@ from google.genai._gaos.types.interactions import functionresultstep from google.genai._gaos.types.interactions import modeloutputstep from google.genai._gaos.types.interactions import userinputstep + +try: + from google.genai._gaos.utils.serializers import ALLOW_UNKNOWN_UNION_VARIANTS +except (ImportError, AttributeError): + ALLOW_UNKNOWN_UNION_VARIANTS = "speakeasy_allow_unknown_union_variants" from google.genai.models import Models import pandas as pd from tqdm import tqdm @@ -775,7 +780,16 @@ def _interaction_dict_to_agent_data( Returns: An AgentData object with one or more ConversationTurns. """ - typed_interaction = interaction_types.Interaction.model_validate(interaction) + # A server response, so unknown step types must degrade rather than raise. + # `Step` is an open discriminated union: parse_open_union only falls back to + # the Unknown variant when the validation context carries + # ALLOW_UNKNOWN_UNION_VARIANTS, and raises without it so that a + # user-constructed request payload surfaces its mistakes locally. This + # payload comes off the Interactions API, so it is on the tolerant side of + # that line. _interaction_steps_to_events already drops steps it cannot map. + typed_interaction = interaction_types.Interaction.model_validate( + interaction, context={ALLOW_UNKNOWN_UNION_VARIANTS: True} + ) all_events = _interaction_steps_to_events(typed_interaction.steps or []) # Group events into turns. Each UserInputStep starts a new turn. @@ -1689,8 +1703,12 @@ def _resolve_interactions_to_eval_cases( break interaction_dict = json.loads(response.body) try: + # Also a server response -- see _interaction_dict_to_agent_data. + # Without the context a single step type this client does not + # know about raised, and the except below turned that into a + # `break`, silently truncating the interaction history. typed_interaction = interaction_types.Interaction.model_validate( - interaction_dict + interaction_dict, context={ALLOW_UNKNOWN_UNION_VARIANTS: True} ) except Exception as e: logger.warning("Failed to validate interaction model: %s", e) diff --git a/agentplatform/_genai/evals.py b/agentplatform/_genai/evals.py index 32d045588a..ab6de8d7ca 100644 --- a/agentplatform/_genai/evals.py +++ b/agentplatform/_genai/evals.py @@ -215,6 +215,13 @@ def _CustomCodeExecutionSpec_from_vertex( getv(from_object, ["evaluation_function"]), ) + if getv(from_object, ["codeExecutionRegion"]) is not None: + setv( + to_object, + ["code_execution_region"], + getv(from_object, ["codeExecutionRegion"]), + ) + return to_object @@ -237,6 +244,13 @@ def _CustomCodeExecutionSpec_to_vertex( getv(from_object, ["remote_custom_function"]), ) + if getv(from_object, ["code_execution_region"]) is not None: + setv( + to_object, + ["codeExecutionRegion"], + getv(from_object, ["code_execution_region"]), + ) + return to_object diff --git a/agentplatform/_genai/types/common.py b/agentplatform/_genai/types/common.py index a5c33b5b9c..32351b8742 100644 --- a/agentplatform/_genai/types/common.py +++ b/agentplatform/_genai/types/common.py @@ -328,6 +328,8 @@ class SandboxState(_common.CaseInSensitiveEnum): """Sandbox runtime is pausing.""" STATE_RESUMING = "STATE_RESUMING" """Sandbox runtime is resuming.""" + STATE_STOPPING = "STATE_STOPPING" + """Sandbox runtime is stopping.""" class Protocol(_common.CaseInSensitiveEnum): @@ -1705,6 +1707,10 @@ def evaluate(instance: dict[str, Any]) -> float: Instance is the evaluation instance, any fields populated in the instance are available to the function as instance[field_name].""", ) + code_execution_region: Optional[str] = Field( + default=None, + description="""Optional. The region to use for code execution. If set, the Code Execution Sandbox will be invoked in the specified region regardless of the request's originating region. Must be a region where the Code Execution Sandbox is available. Supported regions: northamerica-northeast1, southamerica-east1, us-central1, us-east1, us-east4, us-west1, us-west4, europe-central2, europe-north1, europe-southwest1, europe-west1, europe-west2, europe-west3, europe-west4, europe-west6, europe-west8, europe-west9, me-west1, asia-east1, asia-east2, asia-northeast1, asia-northeast3, asia-south1, asia-south2, asia-southeast1, australia-southeast2. If unset, the request's originating region is used; requests from regions where the sandbox is unavailable will fail with UNIMPLEMENTED.""", + ) class CustomCodeExecutionSpecDict(TypedDict, total=False): @@ -1724,6 +1730,9 @@ def evaluate(instance: dict[str, Any]) -> float: Instance is the evaluation instance, any fields populated in the instance are available to the function as instance[field_name].""" + code_execution_region: Optional[str] + """Optional. The region to use for code execution. If set, the Code Execution Sandbox will be invoked in the specified region regardless of the request's originating region. Must be a region where the Code Execution Sandbox is available. Supported regions: northamerica-northeast1, southamerica-east1, us-central1, us-east1, us-east4, us-west1, us-west4, europe-central2, europe-north1, europe-southwest1, europe-west1, europe-west2, europe-west3, europe-west4, europe-west6, europe-west8, europe-west9, me-west1, asia-east1, asia-east2, asia-northeast1, asia-northeast3, asia-south1, asia-south2, asia-southeast1, australia-southeast2. If unset, the request's originating region is used; requests from regions where the sandbox is unavailable will fail with UNIMPLEMENTED.""" + CustomCodeExecutionSpecOrDict = Union[ CustomCodeExecutionSpec, CustomCodeExecutionSpecDict @@ -6665,7 +6674,7 @@ class ReservationAffinity(_common.BaseModel): ) values: Optional[list[str]] = Field( default=None, - description="""Optional. Corresponds to the label values of a reservation resource. This must be the full resource name of the reservation or reservation block.""", + description="""Optional. Corresponds to the label values of a reservation resource. This must be the resource name of the reservation, reservation block, or reservation sub- block.""", ) @@ -6679,7 +6688,7 @@ class ReservationAffinityDict(TypedDict, total=False): """Required. Specifies the reservation affinity type.""" values: Optional[list[str]] - """Optional. Corresponds to the label values of a reservation resource. This must be the full resource name of the reservation or reservation block.""" + """Optional. Corresponds to the label values of a reservation resource. This must be the resource name of the reservation, reservation block, or reservation sub- block.""" ReservationAffinityOrDict = Union[ReservationAffinity, ReservationAffinityDict] @@ -11538,6 +11547,9 @@ class Memory(_common.BaseModel): default=None, description="""Optional. Represents the structured content of the memory.""", ) + context: Optional[str] = Field( + default=None, description="""Optional. Represents the context of the memory.""" + ) class MemoryDict(TypedDict, total=False): @@ -11594,6 +11606,9 @@ class MemoryDict(TypedDict, total=False): structured_content: Optional[MemoryStructuredContentDict] """Optional. Represents the structured content of the memory.""" + context: Optional[str] + """Optional. Represents the context of the memory.""" + MemoryOrDict = Union[Memory, MemoryDict] @@ -13281,6 +13296,10 @@ class MemoryRevision(_common.BaseModel): default=None, description="""Output only. Represents the structured value of the memory at the time of revision creation.""", ) + context: Optional[str] = Field( + default=None, + description="""Output only. Represents the context of the Memory Revision. The context may include context from both the historical revisions and the extracted content.""", + ) class MemoryRevisionDict(TypedDict, total=False): @@ -13307,6 +13326,9 @@ class MemoryRevisionDict(TypedDict, total=False): structured_data: Optional[dict[str, Any]] """Output only. Represents the structured value of the memory at the time of revision creation.""" + context: Optional[str] + """Output only. Represents the context of the Memory Revision. The context may include context from both the historical revisions and the extracted content.""" + MemoryRevisionOrDict = Union[MemoryRevision, MemoryRevisionDict] @@ -16564,13 +16586,13 @@ class SandboxEnvironmentSpecComputerUseEnvironmentDict(TypedDict, total=False): class SandboxEnvironmentSpecShellEnvironment(_common.BaseModel): - """The shell environment with customized settings.""" + """The shell environment.""" pass class SandboxEnvironmentSpecShellEnvironmentDict(TypedDict, total=False): - """The shell environment with customized settings.""" + """The shell environment.""" pass @@ -16590,7 +16612,12 @@ class SandboxEnvironmentSpec(_common.BaseModel): Field(default=None, description="""Optional. The computer use environment.""") ) shell_environment: Optional[SandboxEnvironmentSpecShellEnvironment] = Field( - default=None, description="""Optional. The shell environment.""" + default=None, + description="""Optional. The shell environment for executing shell commands and scripts.""", + ) + use_gke_td: Optional[bool] = Field( + default=None, + description="""Optional. Immutable. Whether to provision the SandboxEnvironment via the GKE TD pool. Immutable.""", ) @@ -16606,7 +16633,10 @@ class SandboxEnvironmentSpecDict(TypedDict, total=False): """Optional. The computer use environment.""" shell_environment: Optional[SandboxEnvironmentSpecShellEnvironmentDict] - """Optional. The shell environment.""" + """Optional. The shell environment for executing shell commands and scripts.""" + + use_gke_td: Optional[bool] + """Optional. Immutable. Whether to provision the SandboxEnvironment via the GKE TD pool. Immutable.""" SandboxEnvironmentSpecOrDict = Union[SandboxEnvironmentSpec, SandboxEnvironmentSpecDict] @@ -17551,11 +17581,11 @@ class SandboxEnvironmentTemplateEgressControlConfigDnsPeeringConfig(_common.Base ) target_network: Optional[str] = Field( default=None, - description="""Required. The VPC network name in the target_project where the DNS zone specified by 'domain' is visible.""", + description="""Required. The VPC network name in the target_project where the DNS zone specified by `domain` is visible.""", ) target_project: Optional[str] = Field( default=None, - description="""Required. The project ID hosting the Cloud DNS managed zone that contains the 'domain'. The Vertex AI Service Agent requires the dns.peer role on this project.""", + description="""Required. The project ID hosting the Cloud DNS managed zone that contains the `domain`. The Vertex AI Service Agent requires the dns.peer role on this project.""", ) @@ -17568,10 +17598,10 @@ class SandboxEnvironmentTemplateEgressControlConfigDnsPeeringConfigDict( """Required. The DNS name suffix of the zone being peered to, e.g., "my-internal-domain.corp.". Must end with a dot.""" target_network: Optional[str] - """Required. The VPC network name in the target_project where the DNS zone specified by 'domain' is visible.""" + """Required. The VPC network name in the target_project where the DNS zone specified by `domain` is visible.""" target_project: Optional[str] - """Required. The project ID hosting the Cloud DNS managed zone that contains the 'domain'. The Vertex AI Service Agent requires the dns.peer role on this project.""" + """Required. The project ID hosting the Cloud DNS managed zone that contains the `domain`. The Vertex AI Service Agent requires the dns.peer role on this project.""" SandboxEnvironmentTemplateEgressControlConfigDnsPeeringConfigOrDict = Union[ @@ -17586,10 +17616,6 @@ class SandboxEnvironmentTemplateEgressControlConfig(_common.BaseModel): internet_access: Optional[bool] = Field( default=None, description="""Optional. Whether to allow internet access.""" ) - customer_vpc_network: Optional[str] = Field( - default=None, - description="""Optional. The customer VPC network that sandbox egress is routed into.""", - ) dns_peering_configs: Optional[ list[SandboxEnvironmentTemplateEgressControlConfigDnsPeeringConfig] ] = Field( @@ -17598,7 +17624,7 @@ class SandboxEnvironmentTemplateEgressControlConfig(_common.BaseModel): ) network_attachment: Optional[str] = Field( default=None, - description="""Optional. The name of the customer VPC NetworkAttachment used to draw a PSC interface IP into the customer VPC for sandbox egress.""", + description="""Optional. The name of the customer VPC `NetworkAttachment` used to draw a PSC interface IP into the customer VPC for sandbox egress.""", ) @@ -17608,16 +17634,13 @@ class SandboxEnvironmentTemplateEgressControlConfigDict(TypedDict, total=False): internet_access: Optional[bool] """Optional. Whether to allow internet access.""" - customer_vpc_network: Optional[str] - """Optional. The customer VPC network that sandbox egress is routed into.""" - dns_peering_configs: Optional[ list[SandboxEnvironmentTemplateEgressControlConfigDnsPeeringConfigDict] ] """Optional. DNS peering configurations that allow sandbox egress to resolve customer-internal domains via the customer VPC.""" network_attachment: Optional[str] - """Optional. The name of the customer VPC NetworkAttachment used to draw a PSC interface IP into the customer VPC for sandbox egress.""" + """Optional. The name of the customer VPC `NetworkAttachment` used to draw a PSC interface IP into the customer VPC for sandbox egress.""" SandboxEnvironmentTemplateEgressControlConfigOrDict = Union[ @@ -17873,6 +17896,10 @@ class SandboxEnvironmentTemplate(_common.BaseModel): default=None, description="""Optional. The configuration for private ingress (PSC-E) of this template. When set, the sandbox router is exposed privately via a PSC service attachment so VPC-SC customers can connect from their VPC over a private endpoint instead of the public internet. The resulting service attachment is surfaced on `SandboxEnvironment.connection_info.service_attachment`. Only the PSC-E (service-attachment/ingress) portion of `PrivateServiceConnectConfig` applies here: `enable_private_service_connect` and `project_allowlist` (the consumer projects allowed to connect). The nested `psc_interface_config` (PSC-I / egress) is not used for sandbox ingress; sandbox egress is configured via `egress_control_config` instead.""", ) + use_gke_td: Optional[bool] = Field( + default=None, + description="""Optional. Immutable. Whether to provision the SandboxEnvironmentTemplate via the GKE TD pool.""", + ) class SandboxEnvironmentTemplateDict(TypedDict, total=False): @@ -17909,6 +17936,9 @@ class SandboxEnvironmentTemplateDict(TypedDict, total=False): ingress_control_config: Optional[PrivateServiceConnectConfigDict] """Optional. The configuration for private ingress (PSC-E) of this template. When set, the sandbox router is exposed privately via a PSC service attachment so VPC-SC customers can connect from their VPC over a private endpoint instead of the public internet. The resulting service attachment is surfaced on `SandboxEnvironment.connection_info.service_attachment`. Only the PSC-E (service-attachment/ingress) portion of `PrivateServiceConnectConfig` applies here: `enable_private_service_connect` and `project_allowlist` (the consumer projects allowed to connect). The nested `psc_interface_config` (PSC-I / egress) is not used for sandbox ingress; sandbox egress is configured via `egress_control_config` instead.""" + use_gke_td: Optional[bool] + """Optional. Immutable. Whether to provision the SandboxEnvironmentTemplate via the GKE TD pool.""" + SandboxEnvironmentTemplateOrDict = Union[ SandboxEnvironmentTemplate, SandboxEnvironmentTemplateDict @@ -18340,6 +18370,10 @@ class SandboxEnvironmentSnapshot(_common.BaseModel): default=None, description="""Output only. The timestamp when this SandboxEnvironment was most recently updated.""", ) + use_gke_td: Optional[bool] = Field( + default=None, + description="""Output only. Whether the source SandboxEnvironment uses the GKE TD pool.""", + ) class SandboxEnvironmentSnapshotDict(TypedDict, total=False): @@ -18379,6 +18413,9 @@ class SandboxEnvironmentSnapshotDict(TypedDict, total=False): update_time: Optional[datetime.datetime] """Output only. The timestamp when this SandboxEnvironment was most recently updated.""" + use_gke_td: Optional[bool] + """Output only. Whether the source SandboxEnvironment uses the GKE TD pool.""" + SandboxEnvironmentSnapshotOrDict = Union[ SandboxEnvironmentSnapshot, SandboxEnvironmentSnapshotDict @@ -20965,6 +21002,10 @@ class SchemaPromptSpecAppBuilderData(_common.BaseModel): description="""Linked resources attached to the application by the user.""", ) ) + deployed_regions: Optional[list[str]] = Field( + default=None, + description="""Optional. The Cloud Run regions in which the application is currently deployed. Used to rediscover and redeploy the app in the regions it already runs in, which may differ from the prompt's location.""", + ) class SchemaPromptSpecAppBuilderDataDict(TypedDict, total=False): @@ -20979,6 +21020,9 @@ class SchemaPromptSpecAppBuilderDataDict(TypedDict, total=False): linked_resources: Optional[list[SchemaPromptSpecAppBuilderDataLinkedResourceDict]] """Linked resources attached to the application by the user.""" + deployed_regions: Optional[list[str]] + """Optional. The Cloud Run regions in which the application is currently deployed. Used to rediscover and redeploy the app in the regions it already runs in, which may differ from the prompt's location.""" + SchemaPromptSpecAppBuilderDataOrDict = Union[ SchemaPromptSpecAppBuilderData, SchemaPromptSpecAppBuilderDataDict @@ -28666,11 +28710,11 @@ class AudioTranscriptionWordInfoDict(TypedDict, total=False): class AudioTranscription(_common.BaseModel): - """The transcription of an audio part. For multi-speaker audio, each speaker segment is a separate Part with its own AudioTranscription carrying the speaker_label.""" + """The transcription of an audio part. For multi-speaker audio, each speaker segment is a separate `Part` with its own `AudioTranscription` carrying the `speaker_label`.""" speaker_label: Optional[str] = Field( default=None, - description="""Optional. A label identifying the speaker of this audio segment (e.g. "spk_1", "spk_2"). Present when diarization is set.""", + description="""Optional. A label identifying the speaker of this audio segment (e.g. `spk_1`, `spk_2`). Present when `diarization` is set.""", ) text: Optional[str] = Field( default=None, @@ -28678,21 +28722,21 @@ class AudioTranscription(_common.BaseModel): ) words: Optional[list[AudioTranscriptionWordInfo]] = Field( default=None, - description="""Optional. Detailed word-level transcriptions and timing details. Present when word_timestamp is set.""", + description="""Optional. Detailed word-level transcriptions and timing details. Present when `word_timestamp` is set.""", ) class AudioTranscriptionDict(TypedDict, total=False): - """The transcription of an audio part. For multi-speaker audio, each speaker segment is a separate Part with its own AudioTranscription carrying the speaker_label.""" + """The transcription of an audio part. For multi-speaker audio, each speaker segment is a separate `Part` with its own `AudioTranscription` carrying the `speaker_label`.""" speaker_label: Optional[str] - """Optional. A label identifying the speaker of this audio segment (e.g. "spk_1", "spk_2"). Present when diarization is set.""" + """Optional. A label identifying the speaker of this audio segment (e.g. `spk_1`, `spk_2`). Present when `diarization` is set.""" text: Optional[str] """Required. The transcription text of this audio segment.""" words: Optional[list[AudioTranscriptionWordInfoDict]] - """Optional. Detailed word-level transcriptions and timing details. Present when word_timestamp is set.""" + """Optional. Detailed word-level transcriptions and timing details. Present when `word_timestamp` is set.""" AudioTranscriptionOrDict = Union[AudioTranscription, AudioTranscriptionDict] diff --git a/agentplatform/version.py b/agentplatform/version.py index c8e34a22af..7dd9ca3966 100644 --- a/agentplatform/version.py +++ b/agentplatform/version.py @@ -14,4 +14,4 @@ # """Version file for agentplatform.""" -__version__ = "2.1.3" +__version__ = "2.2.0" diff --git a/google/cloud/aiplatform/gapic_version.py b/google/cloud/aiplatform/gapic_version.py index 8311290ce3..a0f4fbaafc 100644 --- a/google/cloud/aiplatform/gapic_version.py +++ b/google/cloud/aiplatform/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "2.1.3" # {x-release-please-version} +__version__ = "2.2.0" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1/schema/predict/instance/gapic_version.py b/google/cloud/aiplatform/v1/schema/predict/instance/gapic_version.py index 8311290ce3..a0f4fbaafc 100644 --- a/google/cloud/aiplatform/v1/schema/predict/instance/gapic_version.py +++ b/google/cloud/aiplatform/v1/schema/predict/instance/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "2.1.3" # {x-release-please-version} +__version__ = "2.2.0" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1/schema/predict/instance_v1/gapic_version.py b/google/cloud/aiplatform/v1/schema/predict/instance_v1/gapic_version.py index 8311290ce3..a0f4fbaafc 100644 --- a/google/cloud/aiplatform/v1/schema/predict/instance_v1/gapic_version.py +++ b/google/cloud/aiplatform/v1/schema/predict/instance_v1/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "2.1.3" # {x-release-please-version} +__version__ = "2.2.0" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1/schema/predict/params/gapic_version.py b/google/cloud/aiplatform/v1/schema/predict/params/gapic_version.py index 8311290ce3..a0f4fbaafc 100644 --- a/google/cloud/aiplatform/v1/schema/predict/params/gapic_version.py +++ b/google/cloud/aiplatform/v1/schema/predict/params/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "2.1.3" # {x-release-please-version} +__version__ = "2.2.0" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1/schema/predict/params_v1/gapic_version.py b/google/cloud/aiplatform/v1/schema/predict/params_v1/gapic_version.py index 8311290ce3..a0f4fbaafc 100644 --- a/google/cloud/aiplatform/v1/schema/predict/params_v1/gapic_version.py +++ b/google/cloud/aiplatform/v1/schema/predict/params_v1/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "2.1.3" # {x-release-please-version} +__version__ = "2.2.0" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1/schema/predict/prediction/gapic_version.py b/google/cloud/aiplatform/v1/schema/predict/prediction/gapic_version.py index 8311290ce3..a0f4fbaafc 100644 --- a/google/cloud/aiplatform/v1/schema/predict/prediction/gapic_version.py +++ b/google/cloud/aiplatform/v1/schema/predict/prediction/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "2.1.3" # {x-release-please-version} +__version__ = "2.2.0" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1/schema/predict/prediction_v1/gapic_version.py b/google/cloud/aiplatform/v1/schema/predict/prediction_v1/gapic_version.py index 8311290ce3..a0f4fbaafc 100644 --- a/google/cloud/aiplatform/v1/schema/predict/prediction_v1/gapic_version.py +++ b/google/cloud/aiplatform/v1/schema/predict/prediction_v1/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "2.1.3" # {x-release-please-version} +__version__ = "2.2.0" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1/schema/trainingjob/definition/gapic_version.py b/google/cloud/aiplatform/v1/schema/trainingjob/definition/gapic_version.py index 8311290ce3..a0f4fbaafc 100644 --- a/google/cloud/aiplatform/v1/schema/trainingjob/definition/gapic_version.py +++ b/google/cloud/aiplatform/v1/schema/trainingjob/definition/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "2.1.3" # {x-release-please-version} +__version__ = "2.2.0" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1/schema/trainingjob/definition_v1/gapic_version.py b/google/cloud/aiplatform/v1/schema/trainingjob/definition_v1/gapic_version.py index 8311290ce3..a0f4fbaafc 100644 --- a/google/cloud/aiplatform/v1/schema/trainingjob/definition_v1/gapic_version.py +++ b/google/cloud/aiplatform/v1/schema/trainingjob/definition_v1/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "2.1.3" # {x-release-please-version} +__version__ = "2.2.0" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1beta1/schema/predict/instance/gapic_version.py b/google/cloud/aiplatform/v1beta1/schema/predict/instance/gapic_version.py index 8311290ce3..a0f4fbaafc 100644 --- a/google/cloud/aiplatform/v1beta1/schema/predict/instance/gapic_version.py +++ b/google/cloud/aiplatform/v1beta1/schema/predict/instance/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "2.1.3" # {x-release-please-version} +__version__ = "2.2.0" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1beta1/schema/predict/instance_v1beta1/gapic_version.py b/google/cloud/aiplatform/v1beta1/schema/predict/instance_v1beta1/gapic_version.py index 8311290ce3..a0f4fbaafc 100644 --- a/google/cloud/aiplatform/v1beta1/schema/predict/instance_v1beta1/gapic_version.py +++ b/google/cloud/aiplatform/v1beta1/schema/predict/instance_v1beta1/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "2.1.3" # {x-release-please-version} +__version__ = "2.2.0" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1beta1/schema/predict/params/gapic_version.py b/google/cloud/aiplatform/v1beta1/schema/predict/params/gapic_version.py index 8311290ce3..a0f4fbaafc 100644 --- a/google/cloud/aiplatform/v1beta1/schema/predict/params/gapic_version.py +++ b/google/cloud/aiplatform/v1beta1/schema/predict/params/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "2.1.3" # {x-release-please-version} +__version__ = "2.2.0" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1beta1/schema/predict/params_v1beta1/gapic_version.py b/google/cloud/aiplatform/v1beta1/schema/predict/params_v1beta1/gapic_version.py index 8311290ce3..a0f4fbaafc 100644 --- a/google/cloud/aiplatform/v1beta1/schema/predict/params_v1beta1/gapic_version.py +++ b/google/cloud/aiplatform/v1beta1/schema/predict/params_v1beta1/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "2.1.3" # {x-release-please-version} +__version__ = "2.2.0" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1beta1/schema/predict/prediction/gapic_version.py b/google/cloud/aiplatform/v1beta1/schema/predict/prediction/gapic_version.py index 8311290ce3..a0f4fbaafc 100644 --- a/google/cloud/aiplatform/v1beta1/schema/predict/prediction/gapic_version.py +++ b/google/cloud/aiplatform/v1beta1/schema/predict/prediction/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "2.1.3" # {x-release-please-version} +__version__ = "2.2.0" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1beta1/schema/predict/prediction_v1beta1/gapic_version.py b/google/cloud/aiplatform/v1beta1/schema/predict/prediction_v1beta1/gapic_version.py index 8311290ce3..a0f4fbaafc 100644 --- a/google/cloud/aiplatform/v1beta1/schema/predict/prediction_v1beta1/gapic_version.py +++ b/google/cloud/aiplatform/v1beta1/schema/predict/prediction_v1beta1/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "2.1.3" # {x-release-please-version} +__version__ = "2.2.0" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1beta1/schema/trainingjob/definition/gapic_version.py b/google/cloud/aiplatform/v1beta1/schema/trainingjob/definition/gapic_version.py index 8311290ce3..a0f4fbaafc 100644 --- a/google/cloud/aiplatform/v1beta1/schema/trainingjob/definition/gapic_version.py +++ b/google/cloud/aiplatform/v1beta1/schema/trainingjob/definition/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "2.1.3" # {x-release-please-version} +__version__ = "2.2.0" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1beta1/schema/trainingjob/definition_v1beta1/gapic_version.py b/google/cloud/aiplatform/v1beta1/schema/trainingjob/definition_v1beta1/gapic_version.py index 8311290ce3..a0f4fbaafc 100644 --- a/google/cloud/aiplatform/v1beta1/schema/trainingjob/definition_v1beta1/gapic_version.py +++ b/google/cloud/aiplatform/v1beta1/schema/trainingjob/definition_v1beta1/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "2.1.3" # {x-release-please-version} +__version__ = "2.2.0" # {x-release-please-version} diff --git a/google/cloud/aiplatform/version.py b/google/cloud/aiplatform/version.py index 0b8efed442..150d58c94d 100644 --- a/google/cloud/aiplatform/version.py +++ b/google/cloud/aiplatform/version.py @@ -15,4 +15,4 @@ # limitations under the License. # -__version__ = "2.1.3" +__version__ = "2.2.0" diff --git a/google/cloud/aiplatform_v1/gapic_version.py b/google/cloud/aiplatform_v1/gapic_version.py index 8311290ce3..a0f4fbaafc 100644 --- a/google/cloud/aiplatform_v1/gapic_version.py +++ b/google/cloud/aiplatform_v1/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "2.1.3" # {x-release-please-version} +__version__ = "2.2.0" # {x-release-please-version} diff --git a/google/cloud/aiplatform_v1beta1/gapic_version.py b/google/cloud/aiplatform_v1beta1/gapic_version.py index 8311290ce3..a0f4fbaafc 100644 --- a/google/cloud/aiplatform_v1beta1/gapic_version.py +++ b/google/cloud/aiplatform_v1beta1/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "2.1.3" # {x-release-please-version} +__version__ = "2.2.0" # {x-release-please-version} diff --git a/pypi/_vertex_ai_placeholder/version.py b/pypi/_vertex_ai_placeholder/version.py index 236d8f60f9..17a40260e0 100644 --- a/pypi/_vertex_ai_placeholder/version.py +++ b/pypi/_vertex_ai_placeholder/version.py @@ -15,4 +15,4 @@ # limitations under the License. # -__version__ = "2.1.3" +__version__ = "2.2.0" diff --git a/samples/generated_samples/snippet_metadata_google.cloud.aiplatform.v1.json b/samples/generated_samples/snippet_metadata_google.cloud.aiplatform.v1.json index 817a9190fa..72bd680368 100644 --- a/samples/generated_samples/snippet_metadata_google.cloud.aiplatform.v1.json +++ b/samples/generated_samples/snippet_metadata_google.cloud.aiplatform.v1.json @@ -8,7 +8,7 @@ ], "language": "PYTHON", "name": "google-cloud-aiplatform", - "version": "2.1.3" + "version": "2.2.0" }, "snippets": [ { diff --git a/samples/generated_samples/snippet_metadata_google.cloud.aiplatform.v1beta1.json b/samples/generated_samples/snippet_metadata_google.cloud.aiplatform.v1beta1.json index d5331d4633..c26e8327c9 100644 --- a/samples/generated_samples/snippet_metadata_google.cloud.aiplatform.v1beta1.json +++ b/samples/generated_samples/snippet_metadata_google.cloud.aiplatform.v1beta1.json @@ -8,7 +8,7 @@ ], "language": "PYTHON", "name": "google-cloud-aiplatform", - "version": "2.1.3" + "version": "2.2.0" }, "snippets": [ { diff --git a/tests/unit/vertexai/test_autorater_yaml.py b/tests/unit/vertexai/test_autorater_yaml.py index 669f8c0851..fc767b8a46 100644 --- a/tests/unit/vertexai/test_autorater_yaml.py +++ b/tests/unit/vertexai/test_autorater_yaml.py @@ -160,6 +160,14 @@ def setup_method(self): vertexai.init( project=_TEST_PROJECT, location=_TEST_LOCATION, + # Set explicitly rather than inherited. `init` leaves an already + # configured credential in place, and test_extensions installs a + # `Mock(spec=AnonymousCredentials)` globally that it never resets; + # `_upload_string_to_gcs` then hands `global_config.credentials` + # to `storage.Client`, which rejects the Mock's `universe_domain`. + # Whether that leak reaches this module depends on how + # `--dist=loadscope` happens to assign modules to xdist workers. + credentials=auth_credentials.AnonymousCredentials(), ) def teardown_method(self): diff --git a/tests/unit/vertexai/test_types_alias.py b/tests/unit/vertexai/test_types_alias.py new file mode 100644 index 0000000000..e13e0f3555 --- /dev/null +++ b/tests/unit/vertexai/test_types_alias.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- + +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Unit tests for `vertexai.types` resolving against `agentplatform.types`.""" + +import sys + +import agentplatform +import vertexai +from agentplatform._genai import types as agentplatform_types +from vertexai._genai import types as legacy_types +import pytest + +# Both modules resolve these out of `_evals_metric_loaders` on first access, so +# they are checked on their own rather than in the bulk comparisons below. +_LAZY_NAMES = frozenset({"PrebuiltMetric", "RubricMetric"}) + +_SHARED_NAMES = sorted( + (set(agentplatform_types.__all__) & set(legacy_types.__all__)) - _LAZY_NAMES +) +_LEGACY_ONLY_NAMES = sorted( + set(legacy_types.__all__) - set(agentplatform_types.__all__) +) +_AGENT_PLATFORM_ONLY_NAMES = sorted( + set(agentplatform_types.__all__) - set(legacy_types.__all__) +) + + +def test_vertexai_types_is_the_alias_module(): + assert sys.modules[vertexai.types.__name__] is vertexai.types + assert vertexai.types is not legacy_types + assert vertexai.types is not agentplatform_types + + +def test_shared_names_are_the_agentplatform_objects(): + """The point of the alias: one class per message rather than two.""" + assert _SHARED_NAMES, "expected the two modules to share generated types" + mismatched = [ + name + for name in _SHARED_NAMES + if getattr(vertexai.types, name) is not getattr(agentplatform_types, name) + ] + assert not mismatched + + +def test_memory_profile_survives_an_isinstance_check(): + """Regression: an agentplatform object checked against the vertexai name.""" + profile = agentplatform_types.MemoryProfile(schema_id="user-profile", profile={}) + assert isinstance(profile, vertexai.types.MemoryProfile) + + +def test_shared_types_still_nest_inside_a_legacy_only_model(): + """The two halves have to interoperate, not just coexist. + + `GenerateAgentEngineMemoriesConfig` stays a `vertexai` class because + agentplatform renamed it, but its `metadata` values are now agentplatform + objects. Pydantic accepts them because both models derive from the genai + `BaseModel`, which sets `from_attributes=True`. + """ + config = vertexai.types.GenerateAgentEngineMemoriesConfig( + metadata={"record": vertexai.types.MemoryMetadataValue(string_value="123")} + ) + assert type(config) is legacy_types.GenerateAgentEngineMemoriesConfig + assert config.metadata["record"].string_value == "123" + + +def test_legacy_only_names_still_resolve(): + """The `AgentEngine*` surface agentplatform renamed is not taken away.""" + assert "AgentEngine" in _LEGACY_ONLY_NAMES + mismatched = [ + name + for name in _LEGACY_ONLY_NAMES + if getattr(vertexai.types, name) is not getattr(legacy_types, name) + ] + assert not mismatched + + +def test_agentplatform_only_names_are_reachable(): + assert "Runtime" in _AGENT_PLATFORM_ONLY_NAMES + mismatched = [ + name + for name in _AGENT_PLATFORM_ONLY_NAMES + if getattr(vertexai.types, name) is not getattr(agentplatform_types, name) + ] + assert not mismatched + + +@pytest.mark.parametrize("name", sorted(_LAZY_NAMES)) +def test_lazily_loaded_metric_names_resolve_to_agentplatform(name): + assert getattr(vertexai.types, name) is getattr(agentplatform_types, name) + + +def test_all_is_the_union_of_both_modules(): + expected = set(agentplatform_types.__all__) | set(legacy_types.__all__) + assert set(vertexai.types.__all__) == expected + assert set(dir(vertexai.types)) == expected + + +def test_unknown_name_still_raises_attribute_error(): + with pytest.raises(AttributeError): + _ = vertexai.types.NoSuchTypeName + + +def test_agentplatform_registers_its_own_types_alias(): + """`agentplatform.types` must no longer claim the `vertexai.types` key.""" + assert agentplatform.types is agentplatform_types + assert sys.modules[f"{agentplatform.__name__}.types"] is agentplatform_types + assert sys.modules[f"{vertexai.__name__}.types"] is not agentplatform_types diff --git a/vertexai/__init__.py b/vertexai/__init__.py index 4a7c313a84..3405d2e4c5 100644 --- a/vertexai/__init__.py +++ b/vertexai/__init__.py @@ -15,7 +15,6 @@ """The vertexai module.""" import importlib -import sys from google.cloud.aiplatform import version as aiplatform_version @@ -24,7 +23,6 @@ from google.cloud.aiplatform import init _genai_client = None -_genai_types = None def __getattr__(name): # type: ignore[no-untyped-def] @@ -45,12 +43,10 @@ def __getattr__(name): # type: ignore[no-untyped-def] return getattr(_genai_client, name) if name == "types": - global _genai_types - if _genai_types is None: - _genai_types = importlib.import_module("._genai.types", __name__) - if "vertexai.types" not in sys.modules: - sys.modules["vertexai.types"] = _genai_types - return _genai_types + # `types` is a real submodule that resolves against + # `agentplatform.types`, so importing it also binds it as an attribute + # here and this runs only once. + return importlib.import_module(".types", __name__) raise AttributeError(f"module '{__name__}' has no attribute '{name}'") diff --git a/vertexai/types.py b/vertexai/types.py new file mode 100644 index 0000000000..b49c1b3bb1 --- /dev/null +++ b/vertexai/types.py @@ -0,0 +1,84 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""`vertexai.types` resolved against `agentplatform.types`. + +Agent Platform re-generated the Gen AI types under `agentplatform` rather than +moving them, so `vertexai.types.Memory` and `agentplatform.types.Memory` were +two distinct classes carrying identical definitions. Code that hands an +`agentplatform` object to a caller written against the `vertexai` name fails +`isinstance`, even though both describe the same message. + +This module removes the split. Every name `agentplatform.types` defines +resolves here to the `agentplatform` class, so the two spellings are one +object. The names that only ever existed under `vertexai` -- the `AgentEngine*` +surface Agent Platform renamed to `Runtime*` and `MemoryBank*` -- keep +resolving to their `vertexai._genai.types` classes, so nothing is taken away +from callers that have not migrated. + +The unification is a runtime one. Names resolve through `__getattr__`, so a +type checker still reads `vertexai.types.X` as `Any`, exactly as it did while +this name was a lazy alias for `vertexai._genai.types`. Re-exporting both +modules under `typing.TYPE_CHECKING` would make them resolve statically too, +but it also makes the checker read the generated `Optional` fields for the +first time, which turns long-standing unguarded accesses in downstream callers +into new type errors. Static resolution is being rolled out separately, once +those callers are fixed. + +The dependency runs one way: `vertexai` reaches into `agentplatform`, never the +reverse. `google-cloud-agentplatform` ships `agentplatform` without `vertexai` +and without the generated clients, and nothing here changes that. + +Resolution stays lazy at each step. `vertexai.__getattr__` defers importing +this module until `vertexai.types` is touched; `vertexai._genai.types` is +imported only if a name is not found in `agentplatform`; and neither module's +`PrebuiltMetric`/`RubricMetric` is resolved until asked for, so the evaluation +dependencies are still not pulled in by a bare import. +""" + +from __future__ import annotations + +import importlib as _importlib +import types as _module_types +import typing as _typing + +from agentplatform._genai import types as _agentplatform_types + +_legacy_types: _module_types.ModuleType | None = None + + +def _get_legacy_types() -> _module_types.ModuleType: + """Imports `vertexai._genai.types` on first use.""" + global _legacy_types + if _legacy_types is None: + _legacy_types = _importlib.import_module("._genai.types", __package__) + return _legacy_types + + +def __getattr__(name: str) -> _typing.Any: + # See https://peps.python.org/pep-0562/ + if name == "__all__": + return __dir__() + try: + return getattr(_agentplatform_types, name) + except AttributeError: + pass + try: + return getattr(_get_legacy_types(), name) + except AttributeError: + raise AttributeError(f"module '{__name__}' has no attribute '{name}'") from None + + +def __dir__() -> list[str]: + return sorted(set(_agentplatform_types.__all__) | set(_get_legacy_types().__all__))