google-cloud-agentplatform 1.165.1.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agentplatform/__init__.py +72 -0
- agentplatform/_genai/__init__.py +43 -0
- agentplatform/_genai/_agent_engines_utils.py +2341 -0
- agentplatform/_genai/_bigquery_utils.py +49 -0
- agentplatform/_genai/_datasets_utils.py +344 -0
- agentplatform/_genai/_evals_builtin_tools.py +209 -0
- agentplatform/_genai/_evals_common.py +4268 -0
- agentplatform/_genai/_evals_constant.py +122 -0
- agentplatform/_genai/_evals_data_converters.py +926 -0
- agentplatform/_genai/_evals_metric_handlers.py +1783 -0
- agentplatform/_genai/_evals_metric_loaders.py +401 -0
- agentplatform/_genai/_evals_utils.py +1043 -0
- agentplatform/_genai/_evals_visualization.py +2070 -0
- agentplatform/_genai/_gcs_utils.py +262 -0
- agentplatform/_genai/_logging_utils.py +47 -0
- agentplatform/_genai/_memory_bank_utils.py +206 -0
- agentplatform/_genai/_observability_data_converter.py +186 -0
- agentplatform/_genai/_operations_utils.py +94 -0
- agentplatform/_genai/_prompt_management_utils.py +147 -0
- agentplatform/_genai/_prompt_optimizer_utils.py +215 -0
- agentplatform/_genai/_skills_utils.py +69 -0
- agentplatform/_genai/_transformers.py +628 -0
- agentplatform/_genai/a2a_task_events.py +509 -0
- agentplatform/_genai/a2a_tasks.py +861 -0
- agentplatform/_genai/agent_engines.py +3931 -0
- agentplatform/_genai/client.py +519 -0
- agentplatform/_genai/datasets.py +3045 -0
- agentplatform/_genai/endpoints.py +1149 -0
- agentplatform/_genai/evals.py +6883 -0
- agentplatform/_genai/example_stores.py +1445 -0
- agentplatform/_genai/feedback_contexts.py +700 -0
- agentplatform/_genai/feedback_entries.py +1644 -0
- agentplatform/_genai/live.py +64 -0
- agentplatform/_genai/live_agent_engines.py +179 -0
- agentplatform/_genai/memories.py +2962 -0
- agentplatform/_genai/memory_banks.py +1927 -0
- agentplatform/_genai/memory_revisions.py +465 -0
- agentplatform/_genai/model_garden.py +2638 -0
- agentplatform/_genai/prompt_optimizer.py +995 -0
- agentplatform/_genai/prompts.py +4515 -0
- agentplatform/_genai/rag.py +4961 -0
- agentplatform/_genai/runtime_revisions.py +1257 -0
- agentplatform/_genai/runtimes.py +78 -0
- agentplatform/_genai/sandbox_snapshots.py +1015 -0
- agentplatform/_genai/sandbox_templates.py +1088 -0
- agentplatform/_genai/sandboxes.py +1604 -0
- agentplatform/_genai/session_events.py +543 -0
- agentplatform/_genai/sessions.py +1449 -0
- agentplatform/_genai/skill_revisions.py +377 -0
- agentplatform/_genai/skills.py +1708 -0
- agentplatform/_genai/types/__init__.py +4695 -0
- agentplatform/_genai/types/agent_engines.py +16 -0
- agentplatform/_genai/types/common.py +32784 -0
- agentplatform/_genai/types/evals.py +1031 -0
- agentplatform/_genai/types/prompt_optimizer.py +107 -0
- agentplatform/_genai/types/prompts.py +107 -0
- agentplatform/version.py +17 -0
- google_cloud_agentplatform-1.165.1.dev0.dist-info/METADATA +79 -0
- google_cloud_agentplatform-1.165.1.dev0.dist-info/RECORD +62 -0
- google_cloud_agentplatform-1.165.1.dev0.dist-info/WHEEL +5 -0
- google_cloud_agentplatform-1.165.1.dev0.dist-info/licenses/LICENSE +202 -0
- google_cloud_agentplatform-1.165.1.dev0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Copyright 2025 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
|
+
|
|
16
|
+
import logging
|
|
17
|
+
|
|
18
|
+
from google.cloud import bigquery
|
|
19
|
+
from google.genai._api_client import BaseApiClient
|
|
20
|
+
import pandas as pd
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class BigQueryUtils:
|
|
27
|
+
"""Handles BigQuery operations."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, api_client: BaseApiClient):
|
|
30
|
+
self.api_client = api_client
|
|
31
|
+
self.bigquery_client = bigquery.Client(
|
|
32
|
+
project=self.api_client.project,
|
|
33
|
+
credentials=self.api_client._credentials,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def load_bigquery_to_dataframe(self, table_uri: str) -> "pd.DataFrame":
|
|
37
|
+
"""Loads data from a BigQuery table into a DataFrame."""
|
|
38
|
+
table = self.bigquery_client.get_table(table_uri)
|
|
39
|
+
return self.bigquery_client.list_rows(table).to_dataframe()
|
|
40
|
+
|
|
41
|
+
def upload_dataframe_to_bigquery(
|
|
42
|
+
self, df: "pd.DataFrame", bq_table_uri: str
|
|
43
|
+
) -> None:
|
|
44
|
+
"""Uploads a Pandas DataFrame to a BigQuery table."""
|
|
45
|
+
job = self.bigquery_client.load_table_from_dataframe(df, bq_table_uri)
|
|
46
|
+
job.result()
|
|
47
|
+
logger.info(
|
|
48
|
+
f"DataFrame successfully uploaded to BigQuery table: {bq_table_uri}"
|
|
49
|
+
)
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
# Copyright 2025 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
|
+
"""Utility functions for multimodal dataset."""
|
|
16
|
+
|
|
17
|
+
import asyncio
|
|
18
|
+
import datetime
|
|
19
|
+
from typing import Any, Type, TypeVar
|
|
20
|
+
import uuid
|
|
21
|
+
|
|
22
|
+
import google.auth.credentials
|
|
23
|
+
from agentplatform._genai.types import common
|
|
24
|
+
from google.genai import _common
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
METADATA_SCHEMA_URI = (
|
|
28
|
+
"gs://google-cloud-aiplatform/schema/dataset/metadata/multimodal_1.0.0.yaml"
|
|
29
|
+
)
|
|
30
|
+
_BQ_MULTIREGIONS = {"us", "eu"}
|
|
31
|
+
_DEFAULT_BQ_DATASET_PREFIX = "vertex_datasets"
|
|
32
|
+
_DEFAULT_BQ_TABLE_PREFIX = "multimodal_dataset"
|
|
33
|
+
|
|
34
|
+
T = TypeVar("T", bound=_common.BaseModel)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def create_from_response(
|
|
38
|
+
model_type: Type[T],
|
|
39
|
+
response: dict[str, Any],
|
|
40
|
+
config: Any | None = None,
|
|
41
|
+
) -> T:
|
|
42
|
+
"""Creates a model from a response."""
|
|
43
|
+
kwargs = (
|
|
44
|
+
{
|
|
45
|
+
"config": {
|
|
46
|
+
"response_schema": getattr(config, "response_schema", None),
|
|
47
|
+
"response_json_schema": getattr(config, "response_json_schema", None),
|
|
48
|
+
"include_all_fields": getattr(config, "include_all_fields", None),
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if config
|
|
52
|
+
else {}
|
|
53
|
+
)
|
|
54
|
+
return model_type._from_response(response=response, kwargs=kwargs)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def validate_multimodal_dataset_bigquery_uri(
|
|
58
|
+
multimodal_dataset: common.MultimodalDataset,
|
|
59
|
+
) -> None:
|
|
60
|
+
"""Validates that a multimodal dataset has a bigquery uri or raises ValueError."""
|
|
61
|
+
if (
|
|
62
|
+
not hasattr(multimodal_dataset, "metadata")
|
|
63
|
+
or multimodal_dataset.metadata is None
|
|
64
|
+
):
|
|
65
|
+
raise ValueError("Multimodal dataset metadata is required.")
|
|
66
|
+
if (
|
|
67
|
+
not hasattr(multimodal_dataset.metadata, "input_config")
|
|
68
|
+
or multimodal_dataset.metadata.input_config is None
|
|
69
|
+
):
|
|
70
|
+
raise ValueError("Multimodal dataset input config is required.")
|
|
71
|
+
if (
|
|
72
|
+
not hasattr(multimodal_dataset.metadata.input_config, "bigquery_source")
|
|
73
|
+
or multimodal_dataset.metadata.input_config.bigquery_source is None
|
|
74
|
+
):
|
|
75
|
+
raise ValueError("Multimodal dataset input config bigquery source is required.")
|
|
76
|
+
if (
|
|
77
|
+
not hasattr(multimodal_dataset.metadata.input_config.bigquery_source, "uri")
|
|
78
|
+
or multimodal_dataset.metadata.input_config.bigquery_source.uri is None
|
|
79
|
+
):
|
|
80
|
+
raise ValueError(
|
|
81
|
+
"Multimodal dataset input config bigquery source uri is required."
|
|
82
|
+
)
|
|
83
|
+
if not str(multimodal_dataset.metadata.input_config.bigquery_source.uri).startswith(
|
|
84
|
+
"bq://"
|
|
85
|
+
):
|
|
86
|
+
raise ValueError(
|
|
87
|
+
"Multimodal dataset bigquery source uri must start with 'bq://'."
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _try_import_bigframes() -> Any:
|
|
92
|
+
"""Tries to import `bigframes`."""
|
|
93
|
+
try:
|
|
94
|
+
import bigframes
|
|
95
|
+
import bigframes.pandas
|
|
96
|
+
import bigframes.bigquery
|
|
97
|
+
|
|
98
|
+
return bigframes
|
|
99
|
+
except ImportError as exc:
|
|
100
|
+
raise ImportError(
|
|
101
|
+
"`bigframes` is not installed. Please call 'pip install bigframes'."
|
|
102
|
+
) from exc
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _try_import_bigquery() -> Any:
|
|
106
|
+
"""Tries to import `bigquery`."""
|
|
107
|
+
try:
|
|
108
|
+
from google.cloud import bigquery
|
|
109
|
+
|
|
110
|
+
return bigquery
|
|
111
|
+
except ImportError as exc:
|
|
112
|
+
raise ImportError(
|
|
113
|
+
"`bigquery` is not installed. Please call 'pip install"
|
|
114
|
+
" google-cloud-bigquery'."
|
|
115
|
+
) from exc
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _bq_dataset_location_allowed(
|
|
119
|
+
vertex_location: str, bq_dataset_location: str
|
|
120
|
+
) -> bool:
|
|
121
|
+
if bq_dataset_location == vertex_location:
|
|
122
|
+
return True
|
|
123
|
+
if bq_dataset_location in _BQ_MULTIREGIONS:
|
|
124
|
+
return vertex_location.startswith(bq_dataset_location)
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _normalize_and_validate_table_id(
|
|
129
|
+
*,
|
|
130
|
+
table_id: str,
|
|
131
|
+
project: str,
|
|
132
|
+
location: str,
|
|
133
|
+
credentials: google.auth.credentials.Credentials,
|
|
134
|
+
) -> str:
|
|
135
|
+
bigquery = _try_import_bigquery()
|
|
136
|
+
|
|
137
|
+
table_ref = bigquery.TableReference.from_string(table_id, default_project=project)
|
|
138
|
+
if table_ref.project != project:
|
|
139
|
+
raise ValueError(
|
|
140
|
+
"The BigQuery table "
|
|
141
|
+
f"`{table_ref.project}.{table_ref.dataset_id}.{table_ref.table_id}`"
|
|
142
|
+
" must be in the same project as the multimodal dataset."
|
|
143
|
+
f" The multimodal dataset is in `{project}`, but the BigQuery table"
|
|
144
|
+
f" is in `{table_ref.project}`."
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
dataset_ref = bigquery.DatasetReference(
|
|
148
|
+
project=table_ref.project, dataset_id=table_ref.dataset_id
|
|
149
|
+
)
|
|
150
|
+
client = bigquery.Client(project=project, credentials=credentials)
|
|
151
|
+
bq_dataset = client.get_dataset(dataset_ref=dataset_ref)
|
|
152
|
+
if not _bq_dataset_location_allowed(location, bq_dataset.location):
|
|
153
|
+
raise ValueError(
|
|
154
|
+
"The BigQuery dataset"
|
|
155
|
+
f" `{dataset_ref.project}.{dataset_ref.dataset_id}` must be in the"
|
|
156
|
+
" same location as the multimodal dataset. The multimodal dataset"
|
|
157
|
+
f" is in `{location}`, but the BigQuery dataset is in"
|
|
158
|
+
f" `{bq_dataset.location}`."
|
|
159
|
+
)
|
|
160
|
+
return f"{table_ref.project}.{table_ref.dataset_id}.{table_ref.table_id}"
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
async def _normalize_and_validate_table_id_async(
|
|
164
|
+
*,
|
|
165
|
+
table_id: str,
|
|
166
|
+
project: str,
|
|
167
|
+
location: str,
|
|
168
|
+
credentials: google.auth.credentials.Credentials,
|
|
169
|
+
) -> str:
|
|
170
|
+
bigquery = _try_import_bigquery()
|
|
171
|
+
|
|
172
|
+
table_ref = bigquery.TableReference.from_string(table_id, default_project=project)
|
|
173
|
+
if table_ref.project != project:
|
|
174
|
+
raise ValueError(
|
|
175
|
+
"The BigQuery table "
|
|
176
|
+
f"`{table_ref.project}.{table_ref.dataset_id}.{table_ref.table_id}`"
|
|
177
|
+
" must be in the same project as the multimodal dataset."
|
|
178
|
+
f" The multimodal dataset is in `{project}`, but the BigQuery table"
|
|
179
|
+
f" is in `{table_ref.project}`."
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
dataset_ref = bigquery.DatasetReference(
|
|
183
|
+
project=table_ref.project, dataset_id=table_ref.dataset_id
|
|
184
|
+
)
|
|
185
|
+
client = bigquery.Client(project=project, credentials=credentials)
|
|
186
|
+
bq_dataset = await asyncio.to_thread(client.get_dataset, dataset_ref=dataset_ref)
|
|
187
|
+
if not _bq_dataset_location_allowed(location, bq_dataset.location):
|
|
188
|
+
raise ValueError(
|
|
189
|
+
"The BigQuery dataset"
|
|
190
|
+
f" `{dataset_ref.project}.{dataset_ref.dataset_id}` must be in the"
|
|
191
|
+
" same location as the multimodal dataset. The multimodal dataset"
|
|
192
|
+
f" is in `{location}`, but the BigQuery dataset is in"
|
|
193
|
+
f" `{bq_dataset.location}`."
|
|
194
|
+
)
|
|
195
|
+
return f"{table_ref.project}.{table_ref.dataset_id}.{table_ref.table_id}"
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _create_default_bigquery_dataset_if_not_exists(
|
|
199
|
+
*,
|
|
200
|
+
project: str,
|
|
201
|
+
location: str,
|
|
202
|
+
credentials: google.auth.credentials.Credentials,
|
|
203
|
+
) -> str:
|
|
204
|
+
bigquery = _try_import_bigquery()
|
|
205
|
+
|
|
206
|
+
bigquery_client = bigquery.Client(project=project, credentials=credentials)
|
|
207
|
+
location_str = location.lower().replace("-", "_")
|
|
208
|
+
dataset_id = bigquery.DatasetReference(
|
|
209
|
+
project, f"{_DEFAULT_BQ_DATASET_PREFIX}_{location_str}"
|
|
210
|
+
)
|
|
211
|
+
dataset = bigquery.Dataset(dataset_ref=dataset_id)
|
|
212
|
+
dataset.location = location
|
|
213
|
+
bigquery_client.create_dataset(dataset, exists_ok=True)
|
|
214
|
+
return f"{dataset_id.project}.{dataset_id.dataset_id}"
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
async def _create_default_bigquery_dataset_if_not_exists_async(
|
|
218
|
+
*,
|
|
219
|
+
project: str,
|
|
220
|
+
location: str,
|
|
221
|
+
credentials: google.auth.credentials.Credentials,
|
|
222
|
+
) -> str:
|
|
223
|
+
bigquery = _try_import_bigquery()
|
|
224
|
+
|
|
225
|
+
bigquery_client = bigquery.Client(project=project, credentials=credentials)
|
|
226
|
+
location_str = location.lower().replace("-", "_")
|
|
227
|
+
dataset_id = bigquery.DatasetReference(
|
|
228
|
+
project, f"{_DEFAULT_BQ_DATASET_PREFIX}_{location_str}"
|
|
229
|
+
)
|
|
230
|
+
dataset = bigquery.Dataset(dataset_ref=dataset_id)
|
|
231
|
+
dataset.location = location
|
|
232
|
+
await asyncio.to_thread(bigquery_client.create_dataset, dataset, exists_ok=True)
|
|
233
|
+
return f"{dataset_id.project}.{dataset_id.dataset_id}"
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _generate_target_table_id(dataset_id: str) -> str:
|
|
237
|
+
return f"{dataset_id}.{_DEFAULT_BQ_TABLE_PREFIX}_{str(uuid.uuid4())}"
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def generate_multimodal_dataset_display_name() -> str:
|
|
241
|
+
"""Generates a display name with a timestamp."""
|
|
242
|
+
return f"MultimodalDataset {datetime.datetime.now().isoformat(sep=' ')}"
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def get_batch_job_unique_name() -> str:
|
|
246
|
+
"""Generates a unique name suffix for a batch job destination."""
|
|
247
|
+
timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
|
|
248
|
+
unique_id = uuid.uuid4().hex[0:5]
|
|
249
|
+
return f"{timestamp}_{unique_id}"
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def save_dataframe_to_bigquery(
|
|
253
|
+
dataframe: "bigframes.pandas.DataFrame", # type: ignore # noqa: F821
|
|
254
|
+
target_table_id: str,
|
|
255
|
+
bq_client: "bigquery.Client", # type: ignore # noqa: F821
|
|
256
|
+
) -> None:
|
|
257
|
+
# `to_gbq` does not support cross-region use cases. We use `copy_table` as a workaround.
|
|
258
|
+
temp_table_id = dataframe.to_gbq()
|
|
259
|
+
copy_job = bq_client.copy_table(
|
|
260
|
+
sources=temp_table_id,
|
|
261
|
+
destination=target_table_id,
|
|
262
|
+
)
|
|
263
|
+
copy_job.result()
|
|
264
|
+
bq_client.delete_table(temp_table_id)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
async def save_dataframe_to_bigquery_async(
|
|
268
|
+
dataframe: "bigframes.pandas.DataFrame", # type: ignore # noqa: F821
|
|
269
|
+
target_table_id: str,
|
|
270
|
+
bq_client: "bigquery.Client", # type: ignore # noqa: F821
|
|
271
|
+
) -> None:
|
|
272
|
+
# `to_gbq` does not support cross-region use cases. We use `copy_table` as a workaround.
|
|
273
|
+
temp_table_id = await asyncio.to_thread(dataframe.to_gbq)
|
|
274
|
+
copy_job = await asyncio.to_thread(
|
|
275
|
+
bq_client.copy_table,
|
|
276
|
+
sources=temp_table_id,
|
|
277
|
+
destination=target_table_id,
|
|
278
|
+
)
|
|
279
|
+
await asyncio.to_thread(copy_job.result)
|
|
280
|
+
await asyncio.to_thread(bq_client.delete_table, temp_table_id)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def load_dataframe_from_bigquery(
|
|
284
|
+
*,
|
|
285
|
+
bigquery_uri: str,
|
|
286
|
+
project: str,
|
|
287
|
+
location: str,
|
|
288
|
+
credentials: google.auth.credentials.Credentials,
|
|
289
|
+
) -> "bigframes.pandas.DataFrame": # type: ignore # noqa: F821
|
|
290
|
+
"""Loads a BigQuery table into a BigFrames DataFrame.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
bigquery_uri: The URI of the BigQuery table, with or without the `bq://`
|
|
294
|
+
prefix.
|
|
295
|
+
project: The project to use for the BigFrames session.
|
|
296
|
+
location: The location to use for the BigFrames session.
|
|
297
|
+
credentials: The credentials to use for the BigFrames session.
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
A BigFrames DataFrame backed by the BigQuery table.
|
|
301
|
+
"""
|
|
302
|
+
bigframes = _try_import_bigframes()
|
|
303
|
+
session_options = bigframes.BigQueryOptions(
|
|
304
|
+
credentials=credentials,
|
|
305
|
+
project=project,
|
|
306
|
+
location=location,
|
|
307
|
+
)
|
|
308
|
+
with bigframes.connect(session_options) as session:
|
|
309
|
+
return session.read_gbq(bigquery_uri.removeprefix("bq://"))
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
async def load_dataframe_from_bigquery_async(
|
|
313
|
+
*,
|
|
314
|
+
bigquery_uri: str,
|
|
315
|
+
project: str,
|
|
316
|
+
location: str,
|
|
317
|
+
credentials: google.auth.credentials.Credentials,
|
|
318
|
+
) -> "bigframes.pandas.DataFrame": # type: ignore # noqa: F821
|
|
319
|
+
"""Loads a BigQuery table into a BigFrames DataFrame.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
bigquery_uri: The URI of the BigQuery table, with or without the `bq://`
|
|
323
|
+
prefix.
|
|
324
|
+
project: The project to use for the BigFrames session.
|
|
325
|
+
location: The location to use for the BigFrames session.
|
|
326
|
+
credentials: The credentials to use for the BigFrames session.
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
A BigFrames DataFrame backed by the BigQuery table.
|
|
330
|
+
"""
|
|
331
|
+
return await asyncio.to_thread(
|
|
332
|
+
load_dataframe_from_bigquery,
|
|
333
|
+
bigquery_uri=bigquery_uri,
|
|
334
|
+
project=project,
|
|
335
|
+
location=location,
|
|
336
|
+
credentials=credentials,
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def resolve_dataset_name(resource_name_or_id: str, project: str, location: str) -> str:
|
|
341
|
+
"""Resolves a dataset name or ID to a full resource name."""
|
|
342
|
+
if "/" not in resource_name_or_id:
|
|
343
|
+
return f"projects/{project}/locations/{location}/datasets/{resource_name_or_id}"
|
|
344
|
+
return resource_name_or_id
|
|
@@ -0,0 +1,209 @@
|
|
|
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
|
+
"""Built-in tool catalog for Gemini Agent evaluation display.
|
|
16
|
+
|
|
17
|
+
The Gemini Agents API (``GET agents/{id}``) returns each tool as a bare type
|
|
18
|
+
discriminator (e.g. ``{"type": "code_execution"}``) with no parameter schema
|
|
19
|
+
or description. The authoritative, full-fidelity expansion lives server-side
|
|
20
|
+
in ``cloud/ai/platform/evaluation/utils/interaction_converter.py``.
|
|
21
|
+
|
|
22
|
+
This module is a **display-only duplicate** of that server catalog, kept here
|
|
23
|
+
so ``show()`` can render tools with full names and descriptions without a
|
|
24
|
+
server round-trip. Parameter schemas are intentionally omitted to avoid
|
|
25
|
+
publishing internal tool contract details.
|
|
26
|
+
|
|
27
|
+
**If the server catalog changes, this SDK-side copy must be updated to match.**
|
|
28
|
+
|
|
29
|
+
Sandbox orchestration tools (``provision_sandbox``, ``load_sandbox``) are
|
|
30
|
+
intentionally excluded from the tool catalog. They are infrastructure
|
|
31
|
+
initialization, not user-facing agent capabilities.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from typing import Any, Optional
|
|
35
|
+
|
|
36
|
+
from google.genai import types as genai_types
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Maps a built-in Gemini Agent tool type to the concrete FunctionDeclarations
|
|
40
|
+
# the agent actually exposes for that type.
|
|
41
|
+
#
|
|
42
|
+
# Source of truth: interaction_converter.py, _BUILTIN_TOOL_FUNCTION_DECLARATIONS
|
|
43
|
+
BUILTIN_TOOL_DECLARATIONS: dict[str, list[genai_types.FunctionDeclaration]] = {
|
|
44
|
+
"code_execution": [
|
|
45
|
+
genai_types.FunctionDeclaration(
|
|
46
|
+
name="run_command",
|
|
47
|
+
description="Runs a shell command on the sandbox VM.",
|
|
48
|
+
),
|
|
49
|
+
],
|
|
50
|
+
"filesystem": [
|
|
51
|
+
genai_types.FunctionDeclaration(
|
|
52
|
+
name="view_file",
|
|
53
|
+
description="Reads the content of a workspace file.",
|
|
54
|
+
),
|
|
55
|
+
genai_types.FunctionDeclaration(
|
|
56
|
+
name="create_file",
|
|
57
|
+
description="Writes content to a new or existing file.",
|
|
58
|
+
),
|
|
59
|
+
genai_types.FunctionDeclaration(
|
|
60
|
+
name="edit_file",
|
|
61
|
+
description="Replaces a specific block of text in a file.",
|
|
62
|
+
),
|
|
63
|
+
genai_types.FunctionDeclaration(
|
|
64
|
+
name="list_dir",
|
|
65
|
+
description="Lists the files in a directory.",
|
|
66
|
+
),
|
|
67
|
+
genai_types.FunctionDeclaration(
|
|
68
|
+
name="delete_file",
|
|
69
|
+
description="Removes a file from the workspace.",
|
|
70
|
+
),
|
|
71
|
+
genai_types.FunctionDeclaration(
|
|
72
|
+
name="move_file",
|
|
73
|
+
description="Renames or moves a file.",
|
|
74
|
+
),
|
|
75
|
+
],
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# Sandbox-environment orchestration tools.
|
|
80
|
+
# Source of truth: interaction_converter.py, _SANDBOX_TOOL_NAMES
|
|
81
|
+
SANDBOX_TOOL_NAMES: frozenset[str] = frozenset(
|
|
82
|
+
{
|
|
83
|
+
"provision_sandbox",
|
|
84
|
+
"load_sandbox",
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def is_sandbox_only_turn(
|
|
90
|
+
events: list[Any],
|
|
91
|
+
) -> bool:
|
|
92
|
+
"""Returns True if a turn contains only sandbox initialization events.
|
|
93
|
+
|
|
94
|
+
Sandbox provisioning events (``provision_sandbox``, ``load_sandbox``)
|
|
95
|
+
are infrastructure setup steps that happen before the user's first
|
|
96
|
+
real prompt.
|
|
97
|
+
|
|
98
|
+
A turn is sandbox-only when every event is either a
|
|
99
|
+
``function_call`` or ``function_response`` referencing a sandbox
|
|
100
|
+
tool name. Events with plain text content (model output, user
|
|
101
|
+
input) disqualify the turn.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
events: The list of AgentEvents in the turn.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
True if the turn is sandbox-only and should be merged into the
|
|
108
|
+
next real turn for display.
|
|
109
|
+
"""
|
|
110
|
+
if not events:
|
|
111
|
+
return True
|
|
112
|
+
|
|
113
|
+
for event in events:
|
|
114
|
+
content = getattr(event, "content", None)
|
|
115
|
+
if not content:
|
|
116
|
+
continue
|
|
117
|
+
parts = getattr(content, "parts", None)
|
|
118
|
+
if not parts:
|
|
119
|
+
continue
|
|
120
|
+
for part in parts:
|
|
121
|
+
if getattr(part, "function_call", None):
|
|
122
|
+
if part.function_call.name not in SANDBOX_TOOL_NAMES:
|
|
123
|
+
return False
|
|
124
|
+
elif getattr(part, "function_response", None):
|
|
125
|
+
if part.function_response.name not in SANDBOX_TOOL_NAMES:
|
|
126
|
+
return False
|
|
127
|
+
else:
|
|
128
|
+
# Any other part type (text, inline_data, executable_code,
|
|
129
|
+
# code_execution_result, etc.) means this is a real
|
|
130
|
+
# conversational event, not sandbox infrastructure.
|
|
131
|
+
return False
|
|
132
|
+
return True
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def agent_tools_to_config_tools(
|
|
136
|
+
agent_tools: Optional[list[Any]],
|
|
137
|
+
) -> Optional[list[genai_types.Tool]]:
|
|
138
|
+
"""Maps Gemini Agents API tools to ``genai_types.Tool`` for display.
|
|
139
|
+
|
|
140
|
+
Expands built-in agent tool types into their concrete function declarations
|
|
141
|
+
using ``BUILTIN_TOOL_DECLARATIONS`` (a display-only duplicate of the
|
|
142
|
+
server-side catalog in ``interaction_converter.py``).
|
|
143
|
+
|
|
144
|
+
Mapping rules:
|
|
145
|
+
* ``code_execution`` is expanded to ``run_command``.
|
|
146
|
+
* ``filesystem`` is expanded to ``view_file``, ``create_file``,
|
|
147
|
+
``edit_file``, ``list_dir``, ``delete_file``, ``move_file``.
|
|
148
|
+
* ``google_search`` and ``url_context`` are mapped to their typed
|
|
149
|
+
``genai_types.Tool`` variant.
|
|
150
|
+
* ``mcp_server`` is represented as a named declaration with a
|
|
151
|
+
human-readable label.
|
|
152
|
+
* Tools carrying explicit ``function_declarations`` are passed through.
|
|
153
|
+
|
|
154
|
+
Sandbox orchestration tools (``provision_sandbox``, ``load_sandbox``)
|
|
155
|
+
are intentionally excluded. They are infrastructure initialization,
|
|
156
|
+
not user-facing capabilities.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
agent_tools: The ``tools`` list from a fetched Gemini agent dict.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
A list of ``genai_types.Tool``, or ``None`` if there are no mappable
|
|
163
|
+
tools.
|
|
164
|
+
"""
|
|
165
|
+
if not agent_tools:
|
|
166
|
+
return None
|
|
167
|
+
tools: list[genai_types.Tool] = []
|
|
168
|
+
for tool in agent_tools or []:
|
|
169
|
+
if not isinstance(tool, dict):
|
|
170
|
+
continue
|
|
171
|
+
tool_type = tool.get("type")
|
|
172
|
+
remainder = {k: v for k, v in tool.items() if k != "type"}
|
|
173
|
+
|
|
174
|
+
# Check the built-in catalog first (code_execution, filesystem).
|
|
175
|
+
catalog_decls = BUILTIN_TOOL_DECLARATIONS.get(tool_type or "")
|
|
176
|
+
if catalog_decls:
|
|
177
|
+
tools.append(genai_types.Tool(function_declarations=list(catalog_decls)))
|
|
178
|
+
elif tool_type == "google_search":
|
|
179
|
+
tools.append(genai_types.Tool(google_search=genai_types.GoogleSearch()))
|
|
180
|
+
elif tool_type == "url_context":
|
|
181
|
+
tools.append(genai_types.Tool(url_context=genai_types.UrlContext()))
|
|
182
|
+
elif "function_declarations" in remainder:
|
|
183
|
+
# Real function tool with explicit declarations.
|
|
184
|
+
tools.append(genai_types.Tool.model_validate(remainder))
|
|
185
|
+
elif tool_type == "mcp_server":
|
|
186
|
+
label = remainder.get("name") or remainder.get("url")
|
|
187
|
+
description = f"MCP server: {label}" if label else "MCP server."
|
|
188
|
+
tools.append(
|
|
189
|
+
genai_types.Tool(
|
|
190
|
+
function_declarations=[
|
|
191
|
+
genai_types.FunctionDeclaration(
|
|
192
|
+
name="mcp_server", description=description
|
|
193
|
+
)
|
|
194
|
+
]
|
|
195
|
+
)
|
|
196
|
+
)
|
|
197
|
+
elif tool_type:
|
|
198
|
+
# Unknown built-in: show by name so it isn't silently dropped.
|
|
199
|
+
tools.append(
|
|
200
|
+
genai_types.Tool(
|
|
201
|
+
function_declarations=[
|
|
202
|
+
genai_types.FunctionDeclaration(name=tool_type)
|
|
203
|
+
]
|
|
204
|
+
)
|
|
205
|
+
)
|
|
206
|
+
elif remainder:
|
|
207
|
+
tools.append(genai_types.Tool.model_validate(remainder))
|
|
208
|
+
|
|
209
|
+
return tools or None
|