Skip to content

Commit 6e81e97

Browse files
cleop-googlecopybara-github
authored andcommitted
feat: GenAI SDK client(multimodal) - Implement create_from_gemini_request_jsonl in Datasets and AsyncDatasets classes.
PiperOrigin-RevId: 908222654
1 parent 2faf725 commit 6e81e97

2 files changed

Lines changed: 300 additions & 0 deletions

File tree

agentplatform/_genai/datasets.py

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,6 +1112,109 @@ def create_from_bigframes(
11121112
multimodal_dataset=multimodal_dataset, config=config
11131113
)
11141114

1115+
def create_from_gemini_request_jsonl(
1116+
self,
1117+
*,
1118+
gcs_uri: str,
1119+
multimodal_dataset: Optional[types.MultimodalDatasetOrDict] = None,
1120+
target_table_id: Optional[str] = None,
1121+
config: Optional[types.CreateMultimodalDatasetConfigOrDict] = None,
1122+
) -> types.MultimodalDataset:
1123+
"""Creates a multimodal dataset from a JSONL file stored on GCS.
1124+
1125+
The JSONL file should contain instances of Gemini
1126+
`GenerateContentRequest` on each line. The data will be stored in a
1127+
BigQuery table with a single column called "requests". The
1128+
request_column_name in the dataset metadata will be set to "requests".
1129+
1130+
Args:
1131+
gcs_uri (str):
1132+
The Google Cloud Storage URI of the JSONL file to import.
1133+
For example, 'gs://my-bucket/path/to/data.jsonl'
1134+
multimodal_dataset:
1135+
Optional. A representation of a multimodal dataset.
1136+
target_table_id (str):
1137+
Optional. The BigQuery table id where the dataframe will be
1138+
uploaded. The table id can be in the format of "dataset.table"
1139+
or "project.dataset.table". Note that the BigQuery
1140+
dataset must already exist and be in the same location as the
1141+
multimodal dataset. If not provided, a generated table id will
1142+
be created in the `vertex_datasets` dataset (e.g.
1143+
`project.vertex_datasets_us_central1.multimodal_dataset_4cbf7ffd`).
1144+
config:
1145+
Optional. A configuration for creating the multimodal dataset. If not
1146+
provided, the default configuration will be used.
1147+
1148+
Returns:
1149+
The created multimodal dataset.
1150+
"""
1151+
bigquery = _datasets_utils._try_import_bigquery()
1152+
1153+
if isinstance(multimodal_dataset, dict):
1154+
multimodal_dataset = types.MultimodalDataset(**multimodal_dataset)
1155+
elif not multimodal_dataset:
1156+
multimodal_dataset = types.MultimodalDataset()
1157+
1158+
if not gcs_uri.startswith("gs://"):
1159+
raise ValueError(
1160+
"Invalid GCS URI format. Expected: gs://bucket-name/object-path"
1161+
)
1162+
1163+
project = self._api_client.project
1164+
location = self._api_client.location
1165+
credentials = self._api_client._credentials
1166+
1167+
if target_table_id:
1168+
target_table_id = _datasets_utils._normalize_and_validate_table_id(
1169+
table_id=target_table_id,
1170+
project=project,
1171+
location=location,
1172+
credentials=credentials,
1173+
)
1174+
else:
1175+
dataset_id = _datasets_utils._create_default_bigquery_dataset_if_not_exists(
1176+
project=project, location=location, credentials=credentials
1177+
)
1178+
target_table_id = _datasets_utils._generate_target_table_id(dataset_id)
1179+
1180+
request_column_name = "requests"
1181+
1182+
# Setup LoadJobConfig to load the JSONL file as a CSV directly from GCS.
1183+
# We use an unused character (unit separator \x1f) as the field delimiter
1184+
# and an empty string as the quote character. This forces BigQuery to
1185+
# treat each line (a valid JSON string) as a single CSV row.
1186+
job_config = bigquery.LoadJobConfig(
1187+
source_format=bigquery.SourceFormat.CSV,
1188+
field_delimiter="\x1f",
1189+
quote_character="",
1190+
schema=[bigquery.SchemaField(request_column_name, "JSON")],
1191+
write_disposition=bigquery.WriteDisposition.WRITE_TRUNCATE,
1192+
)
1193+
1194+
client = bigquery.Client(project=project, credentials=credentials)
1195+
load_job = client.load_table_from_uri(
1196+
gcs_uri,
1197+
target_table_id,
1198+
job_config=job_config,
1199+
)
1200+
load_job.result()
1201+
1202+
multimodal_dataset = multimodal_dataset.model_copy(deep=True)
1203+
metadata = multimodal_dataset.metadata or types.SchemaTablesDatasetMetadata()
1204+
1205+
read_config = (
1206+
metadata.gemini_request_read_config or types.GeminiRequestReadConfig()
1207+
)
1208+
read_config.assembled_request_column_name = request_column_name
1209+
metadata.gemini_request_read_config = read_config
1210+
1211+
multimodal_dataset.metadata = metadata
1212+
multimodal_dataset.set_bigquery_uri(f"bq://{target_table_id}")
1213+
1214+
return self.create_from_bigquery(
1215+
multimodal_dataset=multimodal_dataset, config=config
1216+
)
1217+
11151218
def update_multimodal_dataset(
11161219
self,
11171220
*,
@@ -2400,6 +2503,111 @@ async def create_from_bigframes(
24002503
multimodal_dataset=multimodal_dataset, config=config
24012504
)
24022505

2506+
async def create_from_gemini_request_jsonl(
2507+
self,
2508+
*,
2509+
gcs_uri: str,
2510+
multimodal_dataset: Optional[types.MultimodalDatasetOrDict] = None,
2511+
target_table_id: Optional[str] = None,
2512+
config: Optional[types.CreateMultimodalDatasetConfigOrDict] = None,
2513+
) -> types.MultimodalDataset:
2514+
"""Creates a multimodal dataset from a JSONL file stored on GCS.
2515+
2516+
The JSONL file should contain instances of Gemini
2517+
`GenerateContentRequest` on each line. The data will be stored in a
2518+
BigQuery table with a single column called "requests". The
2519+
request_column_name in the dataset metadata will be set to "requests".
2520+
2521+
Args:
2522+
gcs_uri (str):
2523+
The Google Cloud Storage URI of the JSONL file to import.
2524+
For example, 'gs://my-bucket/path/to/data.jsonl'
2525+
multimodal_dataset:
2526+
Optional. A representation of a multimodal dataset.
2527+
target_table_id (str):
2528+
Optional. The BigQuery table id where the dataframe will be
2529+
uploaded. The table id can be in the format of "dataset.table"
2530+
or "project.dataset.table". Note that the BigQuery
2531+
dataset must already exist and be in the same location as the
2532+
multimodal dataset. If not provided, a generated table id will
2533+
be created in the `vertex_datasets` dataset (e.g.
2534+
`project.vertex_datasets_us_central1.multimodal_dataset_4cbf7ffd`).
2535+
config:
2536+
Optional. A configuration for creating the multimodal dataset. If not
2537+
provided, the default configuration will be used.
2538+
2539+
Returns:
2540+
The created multimodal dataset.
2541+
"""
2542+
bigquery = _datasets_utils._try_import_bigquery()
2543+
2544+
if isinstance(multimodal_dataset, dict):
2545+
multimodal_dataset = types.MultimodalDataset(**multimodal_dataset)
2546+
elif not multimodal_dataset:
2547+
multimodal_dataset = types.MultimodalDataset()
2548+
2549+
if not gcs_uri.startswith("gs://"):
2550+
raise ValueError(
2551+
"Invalid GCS URI format. Expected: gs://bucket-name/object-path"
2552+
)
2553+
2554+
project = self._api_client.project
2555+
location = self._api_client.location
2556+
credentials = self._api_client._credentials
2557+
2558+
if target_table_id:
2559+
target_table_id = (
2560+
await _datasets_utils._normalize_and_validate_table_id_async(
2561+
table_id=target_table_id,
2562+
project=project,
2563+
location=location,
2564+
credentials=credentials,
2565+
)
2566+
)
2567+
else:
2568+
dataset_id = await _datasets_utils._create_default_bigquery_dataset_if_not_exists_async(
2569+
project=project, location=location, credentials=credentials
2570+
)
2571+
target_table_id = _datasets_utils._generate_target_table_id(dataset_id)
2572+
2573+
request_column_name = "requests"
2574+
2575+
# Setup LoadJobConfig to load the JSONL file as a CSV directly from GCS.
2576+
# We use an unused character (unit separator \x1f) as the field delimiter
2577+
# and an empty string as the quote character. This forces BigQuery to
2578+
# treat each line (a valid JSON string) as a single CSV row.
2579+
job_config = bigquery.LoadJobConfig(
2580+
source_format=bigquery.SourceFormat.CSV,
2581+
field_delimiter="\x1f",
2582+
quote_character="",
2583+
schema=[bigquery.SchemaField(request_column_name, "JSON")],
2584+
write_disposition=bigquery.WriteDisposition.WRITE_TRUNCATE,
2585+
)
2586+
2587+
client = bigquery.Client(project=project, credentials=credentials)
2588+
load_job = client.load_table_from_uri(
2589+
gcs_uri,
2590+
target_table_id,
2591+
job_config=job_config,
2592+
)
2593+
await asyncio.to_thread(load_job.result)
2594+
2595+
multimodal_dataset = multimodal_dataset.model_copy(deep=True)
2596+
metadata = multimodal_dataset.metadata or types.SchemaTablesDatasetMetadata()
2597+
2598+
read_config = (
2599+
metadata.gemini_request_read_config or types.GeminiRequestReadConfig()
2600+
)
2601+
read_config.assembled_request_column_name = request_column_name
2602+
metadata.gemini_request_read_config = read_config
2603+
2604+
multimodal_dataset.metadata = metadata
2605+
multimodal_dataset.set_bigquery_uri(f"bq://{target_table_id}")
2606+
2607+
return await self.create_from_bigquery(
2608+
multimodal_dataset=multimodal_dataset, config=config
2609+
)
2610+
24032611
async def update_multimodal_dataset(
24042612
self,
24052613
*,

tests/unit/agentplatform/genai/replays/test_create_multimodal_datasets.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,50 @@ def test_create_dataset_from_bigframes_preserves_other_metadata(client, is_repla
295295
)
296296

297297

298+
@pytest.mark.usefixtures("mock_bigquery_client", "mock_import_bigframes")
299+
def test_create_from_gemini_request_jsonl(client, is_replay_mode):
300+
if is_replay_mode:
301+
gcs_uri = "gs://test-bucket/test-blob.jsonl"
302+
else:
303+
gcs_uri = (
304+
"gs://cloud-samples-data/ai-platform/generative_ai/gemini-2_0/text/sft_train_data.jsonl"
305+
)
306+
307+
dataset = client.datasets.create_from_gemini_request_jsonl(
308+
gcs_uri=gcs_uri,
309+
target_table_id=BIGQUERY_TABLE_NAME,
310+
multimodal_dataset={
311+
"display_name": "test-from-gemini-jsonl",
312+
},
313+
)
314+
assert dataset.display_name == "test-from-gemini-jsonl"
315+
assert (
316+
dataset.metadata.gemini_request_read_config.assembled_request_column_name
317+
== "requests"
318+
)
319+
if not is_replay_mode:
320+
bigquery_client = bigquery.Client(
321+
project=client._api_client.project,
322+
location=client._api_client.location,
323+
credentials=client._api_client._credentials,
324+
)
325+
rows = bigquery_client.list_rows(
326+
dataset.metadata.input_config.bigquery_source.uri[5:]
327+
)
328+
df = rows.to_dataframe()
329+
assert len(df) > 0
330+
assert "requests" in df.columns
331+
assert "contents" in str(df["requests"].iloc[0])
332+
333+
334+
def test_create_from_gemini_request_jsonl_raises_invalid_gcs_uri(client):
335+
with pytest.raises(
336+
ValueError,
337+
match="Invalid GCS URI format. Expected: gs://bucket-name/object-path",
338+
):
339+
client.datasets.create_from_gemini_request_jsonl(gcs_uri="invalid_uri")
340+
341+
298342
pytestmark = pytest_helper.setup(
299343
file=__file__,
300344
globals_for_file=globals(),
@@ -549,3 +593,51 @@ async def test_create_dataset_from_bigframes_preserves_other_metadata_async(
549593
assert dataset.metadata.input_config.bigquery_source.uri == (
550594
f"bq://{BIGQUERY_TABLE_NAME}"
551595
)
596+
597+
598+
@pytest.mark.asyncio
599+
@pytest.mark.usefixtures("mock_bigquery_client", "mock_import_bigframes")
600+
async def test_create_from_gemini_request_jsonl_async(client, is_replay_mode):
601+
if is_replay_mode:
602+
gcs_uri = "gs://test-bucket/test-blob-async.jsonl"
603+
else:
604+
gcs_uri = (
605+
"gs://cloud-samples-data/ai-platform/generative_ai/gemini-2_0/text/sft_train_data.jsonl"
606+
)
607+
608+
dataset = await client.aio.datasets.create_from_gemini_request_jsonl(
609+
gcs_uri=gcs_uri,
610+
target_table_id=BIGQUERY_TABLE_NAME,
611+
multimodal_dataset={
612+
"display_name": "test-from-gemini-jsonl-async",
613+
},
614+
)
615+
assert dataset.display_name == "test-from-gemini-jsonl-async"
616+
assert (
617+
dataset.metadata.gemini_request_read_config.assembled_request_column_name
618+
== "requests"
619+
)
620+
if not is_replay_mode:
621+
bigquery_client = bigquery.Client(
622+
project=client._api_client.project,
623+
location=client._api_client.location,
624+
credentials=client._api_client._credentials,
625+
)
626+
rows = bigquery_client.list_rows(
627+
dataset.metadata.input_config.bigquery_source.uri[5:]
628+
)
629+
df = rows.to_dataframe()
630+
assert len(df) > 0
631+
assert "requests" in df.columns
632+
assert "contents" in str(df["requests"].iloc[0])
633+
634+
635+
@pytest.mark.asyncio
636+
async def test_create_from_gemini_request_jsonl_raises_invalid_gcs_uri_async(client):
637+
with pytest.raises(
638+
ValueError,
639+
match="Invalid GCS URI format. Expected: gs://bucket-name/object-path",
640+
):
641+
await client.aio.datasets.create_from_gemini_request_jsonl(
642+
gcs_uri="invalid_uri"
643+
)

0 commit comments

Comments
 (0)