Skip to content

Commit db30773

Browse files
authored
refactor(alloydb): migrate batch processing notebook to genai SDK (#14537)
* refactor(alloydb): migrate batch processing notebook to genai SDK Updates snippets in the `embeddings_batch_processing.ipynb` notebook to use the newer `google-genai` SDK. * Addressed a comment.
1 parent 841379c commit db30773

1 file changed

Lines changed: 97 additions & 80 deletions

File tree

alloydb/notebooks/embeddings_batch_processing.ipynb

Lines changed: 97 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@
7777
" google-cloud-alloydb-connector[asyncpg]==1.4.0 \\\n",
7878
" sqlalchemy==2.0.36 \\\n",
7979
" pandas==2.2.3 \\\n",
80-
" vertexai==1.70.0 \\\n",
80+
" google-cloud-aiplatform==1.165.1 \\\n",
81+
" google-genai==2.19.0 \\\n",
8182
" asyncio==3.4.3 \\\n",
8283
" greenlet==3.1.1 \\\n",
8384
" --quiet"
@@ -792,119 +793,133 @@
792793
},
793794
"outputs": [],
794795
"source": [
795-
"from google.api_core.exceptions import ResourceExhausted\n",
796-
"from typing import Union\n",
797-
"from vertexai.language_models import TextEmbeddingInput, TextEmbeddingModel\n",
796+
"from typing import Any, AsyncIterator, List, Optional, Union\n",
798797
"\n",
798+
"from google import genai\n",
799+
"from google.genai import types\n",
799800
"\n",
800801
"async def embed_text(\n",
801802
" batch_data: List[dict[str, Any]],\n",
802-
" model: TextEmbeddingModel,\n",
803803
" cols_to_embed: List[str],\n",
804+
" client: genai.Client,\n",
805+
" model_name: str = \"text-embedding-004\",\n",
804806
" task_type: str = \"SEMANTIC_SIMILARITY\",\n",
805807
" retries: int = 100,\n",
806808
" delay: int = 30,\n",
807809
") -> List[dict[str, Union[List[float], str]]]:\n",
808-
" \"\"\"Embeds text data from a batch of records using a Vertex AI embedding model.\n",
810+
" \"\"\"Embeds text data from a batch of records using the google-genai SDK.\n",
809811
"\n",
810812
" Args:\n",
811-
" batch_data: A data batch containing records with text data to embed.\n",
812-
" model: The Vertex AI `TextEmbeddingModel` to use for generating embeddings.\n",
813-
" cols_to_embed: A list of column names containing the data to be embedded.\n",
814-
" task_type: The task type for the embedding model. Defaults to\n",
815-
" \"SEMANTIC_SIMILARITY\".\n",
816-
" Supported task types: https://cloud.google.com/vertex-ai/generative-ai/docs/embeddings/task-types\n",
817-
" retries: The maximum number of times to retry embedding generation in case\n",
818-
" of errors. Defaults to 100.\n",
819-
" delay: The delay in seconds between retries. Defaults to 30.\n",
813+
" batch_data: A data batch containing records with text data to embed.\n",
814+
" cols_to_embed: A list of column names containing the data to be embedded.\n",
815+
" model_name: The embedding model ID.\n",
816+
" task_type: The task type for the embedding model.\n",
817+
" retries: The maximum number of retry attempts in case of errors.\n",
818+
" delay: The delay in seconds between retries.\n",
819+
" client: An optional genai.Client instance for connection reuse.\n",
820820
"\n",
821821
" Returns:\n",
822-
" A list of records containing ids and embeddings.\n",
823-
" Example:\n",
824-
" [\n",
825-
" {\n",
826-
" 'id': 'id1',\n",
827-
" 'col1_embedding': [1.0, 1.1, ...],\n",
828-
" 'col2_embedding': [2.0, 2.1, ...],\n",
829-
" ...\n",
830-
" },\n",
831-
" ...\n",
832-
" ]\n",
833-
" where col1 and col2 are columns containing data to be embedded.\n",
822+
" A list of records containing IDs and mapped column embeddings.\n",
834823
" Raises:\n",
835-
" Exception: Raises the encountered exception if all retries fail.\n",
824+
" Exception: Raises the encountered exception if all retries fail.\n",
836825
" \"\"\"\n",
837826
" logger = logging.getLogger(\"embed_objects\")\n",
838827
" global total_char_count\n",
839828
"\n",
840-
" # Place all of the embeddings into a single list\n",
829+
" # Extract non-empty text strings to embed\n",
841830
" inputs = []\n",
842831
" for row in batch_data:\n",
843832
" for col in cols_to_embed:\n",
844833
" if col in row and row[col]:\n",
845-
" inputs.append(TextEmbeddingInput(row[col], task_type))\n",
834+
" inputs.append(str(row[col]))\n",
846835
"\n",
847-
" # Retry loop\n",
848836
" for attempt in range(retries):\n",
849837
" try:\n",
850-
" # Get embeddings for the text data\n",
851-
" embeddings = await model.get_embeddings_async(inputs)\n",
838+
" # Asynchronous API call using client_instance.aio\n",
839+
" response = await client.aio.models.embed_content(\n",
840+
" model=model_name,\n",
841+
" contents=inputs,\n",
842+
" config=types.EmbedContentConfig(\n",
843+
" task_type=task_type,\n",
844+
" ),\n",
845+
" )\n",
846+
"\n",
847+
" # Track character metrics\n",
848+
" total_char_count += sum(len(text) for text in inputs)\n",
852849
"\n",
853-
" # Increase total char count\n",
854-
" total_char_count += sum([len(input.text) for input in inputs])\n",
850+
" # Map response embeddings back to dataset record structure\n",
851+
" embeddings_list = (\n",
852+
" response.embeddings\n",
853+
" if response.embeddings\n",
854+
" else [response.embedding]\n",
855+
" )\n",
856+
" embedding_iter = iter(embeddings_list)\n",
855857
"\n",
856-
" # group the results together by id\n",
857-
" embedding_iter = iter(embeddings)\n",
858858
" results = []\n",
859859
" for row in batch_data:\n",
860-
" r = {\"id\": row[\"id\"]}\n",
860+
" r = {\"id\": row.get(\"id\")}\n",
861861
" for col in cols_to_embed:\n",
862862
" if col in row and row[col]:\n",
863-
" r[f\"{col}_embedding\"] = str(next(embedding_iter).values)\n",
863+
" embedding_obj = next(embedding_iter)\n",
864+
" r[f\"{col}_embedding\"] = str(embedding_obj.values)\n",
864865
" else:\n",
865866
" r[f\"{col}_embedding\"] = None\n",
866867
" results.append(r)\n",
868+
"\n",
867869
" return results\n",
868870
"\n",
869871
" except Exception as e:\n",
870-
" if attempt < retries - 1: # Retry only if attempts are left\n",
871-
" logger.warning(f\"Error: {e}. Retrying in {delay} seconds...\")\n",
872-
" await asyncio.sleep(delay) # Wait before retrying\n",
872+
" if attempt < retries - 1:\n",
873+
" logger.warning(\n",
874+
" f\"Error: {e}. Retrying in {delay} seconds (attempt {attempt + 1}/{retries})...\"\n",
875+
" )\n",
876+
" await asyncio.sleep(delay)\n",
873877
" else:\n",
874-
" logger.error(f\"Failed to get embeddings for data: {batch_data} after {retries} attempts.\")\n",
878+
" logger.error(\n",
879+
" f\"Failed to get embeddings after {retries} attempts: {e}\"\n",
880+
" )\n",
881+
" raise e\n",
882+
"\n",
875883
" return []\n",
876884
"\n",
877885
"\n",
878886
"async def embed_objects_concurrently(\n",
879887
" cols_to_embed: List[str],\n",
880888
" batch_data: AsyncIterator[List[dict[str, Any]]],\n",
881-
" model: TextEmbeddingModel,\n",
882-
" task_type: str,\n",
889+
" client: genai.Client,\n",
890+
" model_name: str = \"text-embedding-004\",\n",
891+
" task_type: str = \"SEMANTIC_SIMILARITY\",\n",
883892
" max_concurrency: int = 5,\n",
884893
") -> AsyncIterator[List[dict[str, Union[str, List[float]]]]]:\n",
885894
" \"\"\"Embeds text data concurrently from an asynchronous batch data generator.\n",
886895
"\n",
887896
" Args:\n",
888-
" cols_to_embed: A list of column names containing the data to be embedded.\n",
889-
" batch_data: A data batch containing records with text data to embed.\n",
890-
" model: The Vertex AI `TextEmbeddingModel` to use for generating embeddings.\n",
891-
" task_type: The task type for the embedding model.\n",
892-
" Supported task types: https://cloud.google.com/vertex-ai/generative-ai/docs/embeddings/task-types\n",
893-
" max_concurrency: The maximum number of embedding tasks to run concurrently.\n",
894-
" Defaults to 5.\n",
897+
" cols_to_embed: A list of column names containing the data to be embedded.\n",
898+
" batch_data: An async generator yielding data batches with records.\n",
899+
" model_name: The embedding model ID.\n",
900+
" task_type: The task type for the embedding model.\n",
901+
" max_concurrency: The maximum number of concurrent tasks to execute.\n",
902+
" client: An optional genai.Client instance for connection reuse.\n",
903+
"\n",
895904
" Yields:\n",
896-
" A list of records containing ids and embeddings.\n",
905+
" A list of records containing IDs and mapped column embeddings.\n",
897906
" \"\"\"\n",
898907
" logger = logging.getLogger(\"embed_objects\")\n",
899908
"\n",
900-
" # Keep track of pending tasks\n",
901909
" pending: set[asyncio.Task] = set()\n",
902910
" has_next = True\n",
911+
"\n",
903912
" while pending or has_next:\n",
904913
" while len(pending) < max_concurrency and has_next:\n",
905914
" try:\n",
906915
" data = await batch_data.__anext__()\n",
907-
" coro = embed_text(data, model, cols_to_embed, task_type)\n",
916+
" coro = embed_text(\n",
917+
" batch_data=data,\n",
918+
" cols_to_embed=cols_to_embed,\n",
919+
" model_name=model_name,\n",
920+
" task_type=task_type,\n",
921+
" client=client,\n",
922+
" )\n",
908923
" pending.add(asyncio.ensure_future(coro))\n",
909924
" except StopAsyncIteration:\n",
910925
" has_next = False\n",
@@ -915,7 +930,9 @@
915930
" )\n",
916931
" for task in done:\n",
917932
" result = task.result()\n",
918-
" logger.info(f\"Embedding task completed: Processed {len(result)} rows.\")\n",
933+
" logger.info(\n",
934+
" f\"Embedding task completed: Processed {len(result)} rows.\"\n",
935+
" )\n",
919936
" yield result"
920937
]
921938
},
@@ -1045,9 +1062,9 @@
10451062
},
10461063
"outputs": [],
10471064
"source": [
1048-
"import vertexai\n",
10491065
"import time\n",
1050-
"from vertexai.language_models import TextEmbeddingModel\n",
1066+
"\n",
1067+
"from google import genai\n",
10511068
"\n",
10521069
"### Define variables ###\n",
10531070
"\n",
@@ -1080,40 +1097,40 @@
10801097
"):\n",
10811098
" \"\"\"Orchestrates the end-to-end workflow for generating and storing embeddings.\n",
10821099
"\n",
1083-
" The workflow includes the following major steps:\n",
1084-
"\n",
1085-
" 1. Data Retrieval: Fetches data from the database that requires embedding.\n",
1086-
" 2. Batching: Divides the data into batches for optimized processing.\n",
1087-
" 3. Embedding Generation: Generates embeddings concurrently for the batched\n",
1088-
" data using the Vertex AI model.\n",
1089-
" 4. Database Update: Updates the database concurrently with the generated\n",
1090-
" embeddings.\n",
1100+
" Workflow Steps:\n",
1101+
" 1. Connection Initialization: Initializes DB pool.\n",
1102+
" 2. Client Initialization: Instantiates shared GenAI client for workflow lifetime.\n",
1103+
" 3. Data Retrieval & Batching: Fetches and chunks source data into async streams.\n",
1104+
" 4. Embedding Generation: Uses google-genai client asynchronously and concurrently.\n",
1105+
" 5. Database Update: Stores output embeddings concurrently in the database.\n",
10911106
"\n",
10921107
" Args:\n",
1093-
" pool_size: The size of the database connection pool. Defaults to 10.\n",
1094-
" embed_data_concurrency: The maximum number of concurrent tasks for generating embeddings.\n",
1095-
" Defaults to 20.\n",
1096-
" batch_update_concurrency: The maximum number of concurrent tasks for updating the database.\n",
1097-
" Defaults to 10.\n",
1108+
" pool_size: The size of the database connection pool.\n",
1109+
" embed_data_concurrency: Max concurrent tasks for generating embeddings.\n",
1110+
" batch_update_concurrency: Max concurrent tasks for database updates.\n",
10981111
" \"\"\"\n",
1099-
" # Set up connections to the database\n",
1112+
" # Set up database connection pool\n",
11001113
" pool = await init_connection_pool(connector, database_name, pool_size=pool_size)\n",
11011114
"\n",
1102-
" # Initialise VertexAI and the model to be used to generate embeddings\n",
1103-
" vertexai.init(project=project_id, location=region)\n",
1104-
" model = TextEmbeddingModel.from_pretrained(model_name)\n",
1115+
" # Initialize single GenAI client instance for the top-level workflow lifetime\n",
1116+
" client = genai.Client(vertexai=True, project=project_id, location=region)\n",
11051117
"\n",
11061118
" start_time = time.monotonic()\n",
11071119
"\n",
1108-
" # Fetch source data from the database\n",
1120+
" # Fetch source data from database\n",
11091121
" source_data = get_source_data(pool, cols_to_embed)\n",
11101122
"\n",
1111-
" # Divide the source data into batches for efficient processing\n",
1123+
" # Divide source data into asynchronous batches\n",
11121124
" batch_data = batch_source_data(source_data, cols_to_embed)\n",
11131125
"\n",
1114-
" # Generate embeddings for the batched data concurrently\n",
1126+
" # Generate embeddings concurrently using the GenAI SDK\n",
11151127
" embeddings_data = embed_objects_concurrently(\n",
1116-
" cols_to_embed, batch_data, model, task, max_concurrency=embed_data_concurrency\n",
1128+
" cols_to_embed=cols_to_embed,\n",
1129+
" batch_data=batch_data,\n",
1130+
" model_name=model_name,\n",
1131+
" task_type=task,\n",
1132+
" max_concurrency=embed_data_concurrency,\n",
1133+
" client=client,\n",
11171134
" )\n",
11181135
"\n",
11191136
" # Update the database with the generated embeddings concurrently\n",
@@ -1124,7 +1141,7 @@
11241141
" end_time = time.monotonic()\n",
11251142
" elapsed_time = end_time - start_time\n",
11261143
"\n",
1127-
" # Release database connections and close the connector\n",
1144+
" # Release database resources\n",
11281145
" await pool.dispose()\n",
11291146
" await connector.close()\n",
11301147
"\n",

0 commit comments

Comments
 (0)