|
77 | 77 | " google-cloud-alloydb-connector[asyncpg]==1.4.0 \\\n", |
78 | 78 | " sqlalchemy==2.0.36 \\\n", |
79 | 79 | " 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", |
81 | 82 | " asyncio==3.4.3 \\\n", |
82 | 83 | " greenlet==3.1.1 \\\n", |
83 | 84 | " --quiet" |
|
792 | 793 | }, |
793 | 794 | "outputs": [], |
794 | 795 | "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", |
798 | 797 | "\n", |
| 798 | + "from google import genai\n", |
| 799 | + "from google.genai import types\n", |
799 | 800 | "\n", |
800 | 801 | "async def embed_text(\n", |
801 | 802 | " batch_data: List[dict[str, Any]],\n", |
802 | | - " model: TextEmbeddingModel,\n", |
803 | 803 | " cols_to_embed: List[str],\n", |
| 804 | + " client: genai.Client,\n", |
| 805 | + " model_name: str = \"text-embedding-004\",\n", |
804 | 806 | " task_type: str = \"SEMANTIC_SIMILARITY\",\n", |
805 | 807 | " retries: int = 100,\n", |
806 | 808 | " delay: int = 30,\n", |
807 | 809 | ") -> 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", |
809 | 811 | "\n", |
810 | 812 | " 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", |
820 | 820 | "\n", |
821 | 821 | " 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", |
834 | 823 | " Raises:\n", |
835 | | - " Exception: Raises the encountered exception if all retries fail.\n", |
| 824 | + " Exception: Raises the encountered exception if all retries fail.\n", |
836 | 825 | " \"\"\"\n", |
837 | 826 | " logger = logging.getLogger(\"embed_objects\")\n", |
838 | 827 | " global total_char_count\n", |
839 | 828 | "\n", |
840 | | - " # Place all of the embeddings into a single list\n", |
| 829 | + " # Extract non-empty text strings to embed\n", |
841 | 830 | " inputs = []\n", |
842 | 831 | " for row in batch_data:\n", |
843 | 832 | " for col in cols_to_embed:\n", |
844 | 833 | " if col in row and row[col]:\n", |
845 | | - " inputs.append(TextEmbeddingInput(row[col], task_type))\n", |
| 834 | + " inputs.append(str(row[col]))\n", |
846 | 835 | "\n", |
847 | | - " # Retry loop\n", |
848 | 836 | " for attempt in range(retries):\n", |
849 | 837 | " 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", |
852 | 849 | "\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", |
855 | 857 | "\n", |
856 | | - " # group the results together by id\n", |
857 | | - " embedding_iter = iter(embeddings)\n", |
858 | 858 | " results = []\n", |
859 | 859 | " for row in batch_data:\n", |
860 | | - " r = {\"id\": row[\"id\"]}\n", |
| 860 | + " r = {\"id\": row.get(\"id\")}\n", |
861 | 861 | " for col in cols_to_embed:\n", |
862 | 862 | " 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", |
864 | 865 | " else:\n", |
865 | 866 | " r[f\"{col}_embedding\"] = None\n", |
866 | 867 | " results.append(r)\n", |
| 868 | + "\n", |
867 | 869 | " return results\n", |
868 | 870 | "\n", |
869 | 871 | " 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", |
873 | 877 | " 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", |
875 | 883 | " return []\n", |
876 | 884 | "\n", |
877 | 885 | "\n", |
878 | 886 | "async def embed_objects_concurrently(\n", |
879 | 887 | " cols_to_embed: List[str],\n", |
880 | 888 | " 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", |
883 | 892 | " max_concurrency: int = 5,\n", |
884 | 893 | ") -> AsyncIterator[List[dict[str, Union[str, List[float]]]]]:\n", |
885 | 894 | " \"\"\"Embeds text data concurrently from an asynchronous batch data generator.\n", |
886 | 895 | "\n", |
887 | 896 | " 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", |
895 | 904 | " Yields:\n", |
896 | | - " A list of records containing ids and embeddings.\n", |
| 905 | + " A list of records containing IDs and mapped column embeddings.\n", |
897 | 906 | " \"\"\"\n", |
898 | 907 | " logger = logging.getLogger(\"embed_objects\")\n", |
899 | 908 | "\n", |
900 | | - " # Keep track of pending tasks\n", |
901 | 909 | " pending: set[asyncio.Task] = set()\n", |
902 | 910 | " has_next = True\n", |
| 911 | + "\n", |
903 | 912 | " while pending or has_next:\n", |
904 | 913 | " while len(pending) < max_concurrency and has_next:\n", |
905 | 914 | " try:\n", |
906 | 915 | " 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", |
908 | 923 | " pending.add(asyncio.ensure_future(coro))\n", |
909 | 924 | " except StopAsyncIteration:\n", |
910 | 925 | " has_next = False\n", |
|
915 | 930 | " )\n", |
916 | 931 | " for task in done:\n", |
917 | 932 | " 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", |
919 | 936 | " yield result" |
920 | 937 | ] |
921 | 938 | }, |
|
1045 | 1062 | }, |
1046 | 1063 | "outputs": [], |
1047 | 1064 | "source": [ |
1048 | | - "import vertexai\n", |
1049 | 1065 | "import time\n", |
1050 | | - "from vertexai.language_models import TextEmbeddingModel\n", |
| 1066 | + "\n", |
| 1067 | + "from google import genai\n", |
1051 | 1068 | "\n", |
1052 | 1069 | "### Define variables ###\n", |
1053 | 1070 | "\n", |
|
1080 | 1097 | "):\n", |
1081 | 1098 | " \"\"\"Orchestrates the end-to-end workflow for generating and storing embeddings.\n", |
1082 | 1099 | "\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", |
1091 | 1106 | "\n", |
1092 | 1107 | " 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", |
1098 | 1111 | " \"\"\"\n", |
1099 | | - " # Set up connections to the database\n", |
| 1112 | + " # Set up database connection pool\n", |
1100 | 1113 | " pool = await init_connection_pool(connector, database_name, pool_size=pool_size)\n", |
1101 | 1114 | "\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", |
1105 | 1117 | "\n", |
1106 | 1118 | " start_time = time.monotonic()\n", |
1107 | 1119 | "\n", |
1108 | | - " # Fetch source data from the database\n", |
| 1120 | + " # Fetch source data from database\n", |
1109 | 1121 | " source_data = get_source_data(pool, cols_to_embed)\n", |
1110 | 1122 | "\n", |
1111 | | - " # Divide the source data into batches for efficient processing\n", |
| 1123 | + " # Divide source data into asynchronous batches\n", |
1112 | 1124 | " batch_data = batch_source_data(source_data, cols_to_embed)\n", |
1113 | 1125 | "\n", |
1114 | | - " # Generate embeddings for the batched data concurrently\n", |
| 1126 | + " # Generate embeddings concurrently using the GenAI SDK\n", |
1115 | 1127 | " 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", |
1117 | 1134 | " )\n", |
1118 | 1135 | "\n", |
1119 | 1136 | " # Update the database with the generated embeddings concurrently\n", |
|
1124 | 1141 | " end_time = time.monotonic()\n", |
1125 | 1142 | " elapsed_time = end_time - start_time\n", |
1126 | 1143 | "\n", |
1127 | | - " # Release database connections and close the connector\n", |
| 1144 | + " # Release database resources\n", |
1128 | 1145 | " await pool.dispose()\n", |
1129 | 1146 | " await connector.close()\n", |
1130 | 1147 | "\n", |
|
0 commit comments