Skip to content

Commit df66fd2

Browse files
yeesiancopybara-github
authored andcommitted
chore: GenAI SDK client - Address some pytype errors, and adding some additional validation checks
PiperOrigin-RevId: 785529704
1 parent 4b88698 commit df66fd2

4 files changed

Lines changed: 72 additions & 48 deletions

File tree

vertexai/_genai/_agent_engines_utils.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,22 @@
1414
#
1515
"""Utility functions for agent engines."""
1616

17-
from typing import Any, Callable, Coroutine, Iterator, AsyncIterator
17+
from typing import Any, Callable, Coroutine, Iterator, AsyncIterator, Protocol, Union
1818
from . import types
1919

2020

21+
AgentEngineOperationUnion = Union[
22+
types.AgentEngineOperation,
23+
types.AgentEngineMemoryOperation,
24+
types.AgentEngineGenerateMemoriesOperation,
25+
]
26+
27+
28+
class GetOperationFunction(Protocol):
29+
def __call__(self, *, operation_name: str, **kwargs) -> AgentEngineOperationUnion:
30+
...
31+
32+
2133
def _wrap_query_operation(*, method_name: str) -> Callable[..., Any]:
2234
"""Wraps an Agent Engine method, creating a callable for `query` API.
2335

vertexai/_genai/agent_engines.py

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,11 @@
1818
import json
1919
import logging
2020
import time
21-
from typing import Any, Callable, Iterator, Optional, Sequence, Union
21+
from typing import Any, Iterator, Optional, Sequence, Tuple, Union
2222
from urllib.parse import urlencode
2323

2424
from google.genai import _api_module
2525
from google.genai import _common
26-
from google.genai import types as genai_types
2726
from google.genai._common import get_value_by_path as getv
2827
from google.genai._common import set_value_by_path as setv
2928
from google.genai.pagers import Pager
@@ -2045,10 +2044,13 @@ def create(
20452044
api_async_client=AsyncAgentEngines(api_client_=self._api_client),
20462045
api_resource=operation.response,
20472046
)
2048-
logger.info("Agent Engine created. To use it in another session:")
2049-
logger.info(
2050-
f"agent_engine=client.agent_engines.get('{agent.api_resource.name}')"
2051-
)
2047+
if agent.api_resource:
2048+
logger.info("Agent Engine created. To use it in another session:")
2049+
logger.info(
2050+
f"agent_engine=client.agent_engines.get('{agent.api_resource.name}')"
2051+
)
2052+
else:
2053+
logger.warning("The operation returned an empty response.")
20522054
if agent_engine is not None:
20532055
# If the user did not provide an agent_engine (e.g. lightweight
20542056
# provisioning), it will not have any API methods registered.
@@ -2067,13 +2069,13 @@ def _create_config(
20672069
gcs_dir_name: Optional[str] = None,
20682070
extra_packages: Optional[Sequence[str]] = None,
20692071
env_vars: Optional[dict[str, Union[str, Any]]] = None,
2070-
context_spec: Optional[dict[str, Any]] = None,
2071-
):
2072+
context_spec: Optional[types.ReasoningEngineContextSpecDict] = None,
2073+
) -> types.UpdateAgentEngineConfigDict:
20722074
import sys
20732075
from vertexai.agent_engines import _agent_engines
20742076
from vertexai.agent_engines import _utils
20752077

2076-
config = {}
2078+
config: types.UpdateAgentEngineConfigDict = {}
20772079
update_masks = []
20782080
if mode not in ["create", "update"]:
20792081
raise ValueError(f"Unsupported mode: {mode}")
@@ -2091,28 +2093,36 @@ def _create_config(
20912093
if context_spec is not None:
20922094
config["context_spec"] = context_spec
20932095
if agent_engine is not None:
2096+
project = self._api_client.project
2097+
if project is None:
2098+
raise ValueError("project must be set using `vertexai.Client`.")
2099+
location = self._api_client.location
2100+
if location is None:
2101+
raise ValueError("location must be set using `vertexai.Client`.")
20942102
sys_version = f"{sys.version_info.major}.{sys.version_info.minor}"
20952103
gcs_dir_name = gcs_dir_name or _agent_engines._DEFAULT_GCS_DIR_NAME
20962104
agent_engine = _agent_engines._validate_agent_engine_or_raise(
20972105
agent_engine=agent_engine, logger=logger
20982106
)
2099-
_agent_engines._validate_staging_bucket_or_raise(staging_bucket)
2107+
staging_bucket = _agent_engines._validate_staging_bucket_or_raise(
2108+
staging_bucket=staging_bucket
2109+
)
21002110
requirements = _agent_engines._validate_requirements_or_raise(
21012111
agent_engine=agent_engine,
21022112
requirements=requirements,
21032113
logger=logger,
21042114
)
21052115
extra_packages = _agent_engines._validate_extra_packages_or_raise(
2106-
extra_packages
2116+
extra_packages=extra_packages,
21072117
)
21082118
# Prepares the Agent Engine for creation/update in Vertex AI. This
21092119
# involves packaging and uploading the artifacts for agent_engine,
21102120
# requirements and extra_packages to `staging_bucket/gcs_dir_name`.
21112121
_agent_engines._prepare(
21122122
agent_engine=agent_engine,
21132123
requirements=requirements,
2114-
project=self._api_client.project,
2115-
location=self._api_client.location,
2124+
project=project,
2125+
location=location,
21162126
staging_bucket=staging_bucket,
21172127
gcs_dir_name=gcs_dir_name,
21182128
extra_packages=extra_packages,
@@ -2142,7 +2152,9 @@ def _create_config(
21422152
gcs_dir_name,
21432153
_agent_engines._REQUIREMENTS_FILE,
21442154
)
2145-
agent_engine_spec = {"package_spec": package_spec}
2155+
agent_engine_spec: types.ReasoningEngineSpecDict = {
2156+
"package_spec": package_spec,
2157+
}
21462158
if env_vars is not None:
21472159
(
21482160
deployment_spec,
@@ -2172,7 +2184,7 @@ def _generate_deployment_spec_or_raise(
21722184
self,
21732185
*,
21742186
env_vars: Optional[dict[str, Union[str, Any]]] = None,
2175-
):
2187+
) -> Tuple[dict[str, Any], Sequence[str]]:
21762188
deployment_spec: dict[str, Any] = {}
21772189
update_masks = []
21782190
if env_vars:
@@ -2217,7 +2229,7 @@ def _await_operation(
22172229
*,
22182230
operation_name: str,
22192231
poll_interval_seconds: int = 10,
2220-
get_operation_fn: Optional[Callable[[str], Any]] = None,
2232+
get_operation_fn: Optional[_agent_engines_utils.GetOperationFunction] = None,
22212233
) -> Any:
22222234
"""Waits for the operation for creating an agent engine to complete.
22232235
@@ -2375,10 +2387,11 @@ def update(
23752387
api_async_client=AsyncAgentEngines(api_client_=self._api_client),
23762388
api_resource=operation.response,
23772389
)
2378-
logger.info("Agent Engine updated. To use it in another session:")
2379-
logger.info(
2380-
f"agent_engine=client.agent_engines.get('{agent.api_resource.name}')"
2381-
)
2390+
if agent.api_resource:
2391+
logger.info("Agent Engine updated. To use it in another session:")
2392+
logger.info(
2393+
f"agent_engine=client.agent_engines.get('{agent.api_resource.name}')"
2394+
)
23822395
return self._register_api_methods(agent=agent)
23832396

23842397
def _stream_query(
@@ -2403,7 +2416,7 @@ def _stream_query(
24032416
path = f"{path}?{urlencode(query_params)}"
24042417
# TODO: remove the hack that pops config.
24052418
request_dict.pop("config", None)
2406-
http_options: Optional[genai_types.HttpOptions] = None
2419+
http_options = None
24072420
if (
24082421
parameter_model.config is not None
24092422
and parameter_model.config.http_options is not None

vertexai/agent_engines/_agent_engines.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import abc
1717
import inspect
1818
import io
19+
import logging
1920
import os
2021
import sys
2122
import tarfile
@@ -66,7 +67,7 @@
6667
_DEFAULT_STREAM_METHOD_NAME = "stream_query"
6768
_DEFAULT_ASYNC_STREAM_METHOD_NAME = "async_stream_query"
6869
_DEFAULT_METHOD_RETURN_TYPE = "dict[str, Any]"
69-
_DEFAULT_ASYNC_METHOD_RETURN_TYPE = "Coroutine[Any]"
70+
_DEFAULT_ASYNC_METHOD_RETURN_TYPE = "Coroutine[Any, Any, Any]"
7071
_DEFAULT_STREAM_METHOD_RETURN_TYPE = "Iterable[Any]"
7172
_DEFAULT_ASYNC_STREAM_METHOD_RETURN_TYPE = "AsyncIterable[Any]"
7273
_DEFAULT_METHOD_DOCSTRING_TEMPLATE = """
@@ -116,7 +117,7 @@ class Queryable(Protocol):
116117
"""Protocol for Agent Engines that can be queried."""
117118

118119
@abc.abstractmethod
119-
def query(self, **kwargs):
120+
def query(self, **kwargs) -> Any:
120121
"""Runs the Agent Engine to serve the user query."""
121122

122123

@@ -125,7 +126,7 @@ class AsyncQueryable(Protocol):
125126
"""Protocol for Agent Engines that can be queried asynchronously."""
126127

127128
@abc.abstractmethod
128-
def async_query(self, **kwargs):
129+
def async_query(self, **kwargs) -> Coroutine[Any, Any, Any]:
129130
"""Runs the Agent Engine to serve the user query asynchronously."""
130131

131132

@@ -143,7 +144,7 @@ class StreamQueryable(Protocol):
143144
"""Protocol for Agent Engines that can stream responses."""
144145

145146
@abc.abstractmethod
146-
def stream_query(self, **kwargs):
147+
def stream_query(self, **kwargs) -> Iterable[Any]:
147148
"""Stream responses to serve the user query."""
148149

149150

@@ -152,7 +153,7 @@ class Cloneable(Protocol):
152153
"""Protocol for Agent Engines that can be cloned."""
153154

154155
@abc.abstractmethod
155-
def clone(self):
156+
def clone(self) -> Any:
156157
"""Return a clone of the object."""
157158

158159

@@ -161,7 +162,7 @@ class OperationRegistrable(Protocol):
161162
"""Protocol for agents that have registered operations."""
162163

163164
@abc.abstractmethod
164-
def register_operations(self, **kwargs):
165+
def register_operations(self, **kwargs) -> Dict[str, Sequence[str]]:
165166
"""Register the user provided operations (modes and methods)."""
166167

167168

@@ -238,7 +239,7 @@ def clone(self):
238239
sys_paths=self._tmpl_attrs.get("sys_paths"),
239240
)
240241

241-
def register_operations(self) -> Dict[str, Sequence[str]]:
242+
def register_operations(self, **kwargs) -> Dict[str, Sequence[str]]:
242243
return self._tmpl_attrs.get("register_operations")
243244

244245
def set_up(self) -> None:
@@ -441,7 +442,7 @@ def create(
441442
staging_bucket = initializer.global_config.staging_bucket
442443
if agent_engine is not None:
443444
agent_engine = _validate_agent_engine_or_raise(agent_engine)
444-
_validate_staging_bucket_or_raise(staging_bucket)
445+
staging_bucket = _validate_staging_bucket_or_raise(staging_bucket)
445446
if agent_engine is None:
446447
if requirements is not None:
447448
raise ValueError("requirements must be None if agent_engine is None.")
@@ -634,7 +635,7 @@ def update(
634635
nonexistent file.
635636
"""
636637
staging_bucket = initializer.global_config.staging_bucket
637-
_validate_staging_bucket_or_raise(staging_bucket)
638+
staging_bucket = _validate_staging_bucket_or_raise(staging_bucket)
638639
historical_operation_schemas = self.operation_schemas()
639640
gcs_dir_name = gcs_dir_name or _DEFAULT_GCS_DIR_NAME
640641

@@ -780,12 +781,13 @@ def _validate_sys_version_or_raise(sys_version: str) -> None:
780781
)
781782

782783

783-
def _validate_staging_bucket_or_raise(staging_bucket: str) -> str:
784+
def _validate_staging_bucket_or_raise(staging_bucket: Optional[str]) -> str:
784785
"""Tries to validate the staging bucket."""
785786
if not staging_bucket:
786787
raise ValueError("Please provide a `staging_bucket` in `vertexai.init(...)`")
787788
if not staging_bucket.startswith("gs://"):
788789
raise ValueError(f"{staging_bucket=} must start with `gs://`")
790+
return staging_bucket
789791

790792

791793
def _validate_agent_engine_or_raise(
@@ -906,7 +908,7 @@ def _validate_requirements_or_raise(
906908
*,
907909
agent_engine: _AgentEngineInterface,
908910
requirements: Optional[Sequence[str]] = None,
909-
logger: base.Logger = _LOGGER,
911+
logger: logging.getLoggerClass() = _LOGGER,
910912
) -> Sequence[str]:
911913
"""Tries to validate the requirements."""
912914
if requirements is None:
@@ -929,7 +931,7 @@ def _validate_requirements_or_raise(
929931

930932

931933
def _validate_extra_packages_or_raise(
932-
extra_packages: Sequence[str],
934+
extra_packages: Optional[Sequence[str]],
933935
build_options: Optional[Dict[str, Sequence[str]]] = None,
934936
) -> Sequence[str]:
935937
"""Tries to validates the extra packages."""
@@ -1165,6 +1167,7 @@ def _get_agent_framework(
11651167
if (
11661168
hasattr(agent_engine, _AGENT_FRAMEWORK_ATTR)
11671169
and getattr(agent_engine, _AGENT_FRAMEWORK_ATTR) is not None
1170+
and isinstance(getattr(agent_engine, _AGENT_FRAMEWORK_ATTR), str)
11681171
):
11691172
return getattr(agent_engine, _AGENT_FRAMEWORK_ATTR)
11701173
return _DEFAULT_AGENT_FRAMEWORK

vertexai/reasoning_engines/_utils.py

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def to_dict(message: proto.Message) -> JsonDict:
103103
return result
104104

105105

106-
def dataclass_to_dict(obj: dataclasses.dataclass) -> JsonDict:
106+
def dataclass_to_dict(obj: dataclasses.dataclass) -> Any:
107107
"""Converts a dataclass to a JSON dictionary.
108108
109109
Args:
@@ -116,7 +116,7 @@ def dataclass_to_dict(obj: dataclasses.dataclass) -> JsonDict:
116116
return json.loads(json.dumps(dataclasses.asdict(obj)))
117117

118118

119-
def _llama_index_response_to_dict(obj: LlamaIndexResponse) -> Dict[str, Any]:
119+
def _llama_index_response_to_dict(obj: LlamaIndexResponse) -> Any:
120120
response = {}
121121
if hasattr(obj, "response"):
122122
response["response"] = obj.response
@@ -128,15 +128,11 @@ def _llama_index_response_to_dict(obj: LlamaIndexResponse) -> Dict[str, Any]:
128128
return json.loads(json.dumps(response))
129129

130130

131-
def _llama_index_chat_response_to_dict(
132-
obj: LlamaIndexChatResponse,
133-
) -> Dict[str, Any]:
131+
def _llama_index_chat_response_to_dict(obj: LlamaIndexChatResponse) -> Any:
134132
return json.loads(obj.message.model_dump_json())
135133

136134

137-
def _llama_index_base_model_to_dict(
138-
obj: LlamaIndexBaseModel,
139-
) -> Dict[str, Any]:
135+
def _llama_index_base_model_to_dict(obj: LlamaIndexBaseModel) -> Any:
140136
return json.loads(obj.model_dump_json())
141137

142138

@@ -330,7 +326,7 @@ def _import_cloud_storage_or_raise() -> types.ModuleType:
330326
except ImportError as e:
331327
raise ImportError(
332328
"Cloud Storage is not installed. Please call "
333-
"'pip install google-cloud-aiplatform[reasoningengine]'."
329+
"'pip install google-cloud-aiplatform[agent_engines]'."
334330
) from e
335331
return storage
336332

@@ -342,7 +338,7 @@ def _import_cloudpickle_or_raise() -> types.ModuleType:
342338
except ImportError as e:
343339
raise ImportError(
344340
"cloudpickle is not installed. Please call "
345-
"'pip install google-cloud-aiplatform[reasoningengine]'."
341+
"'pip install google-cloud-aiplatform[agent_engines]'."
346342
) from e
347343
return cloudpickle
348344

@@ -358,7 +354,7 @@ def _import_pydantic_or_raise() -> types.ModuleType:
358354
except ImportError as e:
359355
raise ImportError(
360356
"pydantic is not installed. Please call "
361-
"'pip install google-cloud-aiplatform[reasoningengine]'."
357+
"'pip install google-cloud-aiplatform[agent_engines]'."
362358
) from e
363359
return pydantic
364360

@@ -372,7 +368,7 @@ def _import_opentelemetry_or_warn() -> Optional[types.ModuleType]:
372368
except ImportError:
373369
_LOGGER.warning(
374370
"opentelemetry-sdk is not installed. Please call "
375-
"'pip install google-cloud-aiplatform[reasoningengine]'."
371+
"'pip install google-cloud-aiplatform[agent_engines]'."
376372
)
377373
return None
378374

@@ -386,7 +382,7 @@ def _import_opentelemetry_sdk_trace_or_warn() -> Optional[types.ModuleType]:
386382
except ImportError:
387383
_LOGGER.warning(
388384
"opentelemetry-sdk is not installed. Please call "
389-
"'pip install google-cloud-aiplatform[reasoningengine]'."
385+
"'pip install google-cloud-aiplatform[agent_engines]'."
390386
)
391387
return None
392388

@@ -400,7 +396,7 @@ def _import_cloud_trace_v2_or_warn() -> Optional[types.ModuleType]:
400396
except ImportError:
401397
_LOGGER.warning(
402398
"google-cloud-trace is not installed. Please call "
403-
"'pip install google-cloud-aiplatform[reasoningengine]'."
399+
"'pip install google-cloud-aiplatform[agent_engines]'."
404400
)
405401
return None
406402

0 commit comments

Comments
 (0)