App de búsqueda de tiendas de juguetes con bases de datos de Cloud, entornos de ejecución sin servidores y integraciones de código abierto

1. Descripción general

Imagina entrar en una juguetería de forma virtual o presencial, donde encontrar el regalo perfecto es sencillo. Puedes describir lo que buscas, subir una foto de un juguete o incluso diseñar tu propia creación. La tienda entenderá tus necesidades al instante y te brindará una experiencia personalizada. No es una fantasía futurista, sino una realidad potenciada por la IA, la tecnología de la nube y una visión de comercio electrónico personalizado.

El desafío: Puede ser difícil encontrar el producto perfecto que coincida con tu imaginación. Los términos de búsqueda genéricos, las palabras clave y las búsquedas difusas suelen ser insuficientes, la navegación por páginas interminables puede ser tediosa y la desconexión entre lo que imaginas y lo que está disponible puede generar frustración.

La solución: La aplicación de demostración aborda este desafío de frente y aprovecha el poder de la IA para ofrecer una experiencia verdaderamente personalizada y fluida con la búsqueda contextual y la generación personalizada del producto que coincide con el contexto de búsqueda.

Qué compilarás

Como parte de este lab, harás lo siguiente:

  1. Crea una instancia de AlloyDB y carga el conjunto de datos de juguetes
  2. Habilita las extensiones de modelos de IA generativa y pgvector en AlloyDB
  3. Genera incorporaciones a partir de la descripción del producto y realiza una búsqueda de similitud de coseno en tiempo real para el texto de búsqueda del usuario.
  4. Invoca Gemini 2.0 Flash para describir la imagen que subió el usuario para la búsqueda contextual de juguetes
  5. Invoca Imagen 3 para crear un juguete personalizado según los intereses del usuario
  6. Invocar una herramienta de predicción de precios creada con la Caja de herramientas de IA generativa para bases de datos para obtener detalles de precios del juguete creado de forma personalizada
  7. Implementa la solución en Cloud Run Functions sin servidores

Requisitos

  • Un navegador, como Chrome o Firefox.
  • Un proyecto de Google Cloud con facturación habilitada.

2. Arquitectura

Flujo de datos: Analicemos con más detalle cómo se mueven los datos a través de nuestro sistema:

  1. Búsqueda contextual con RAG potenciada por IA (generación mejorada por recuperación)

Piensa en esto: en lugar de solo buscar "automóvil rojo", el sistema comprende lo siguiente:

“vehículo pequeño adecuado para un niño de 3 años”.

AlloyDB como base: Usamos AlloyDB, la base de datos compatible con PostgreSQL y completamente administrada de Google Cloud, para almacenar nuestros datos de juguetes, incluidas las descripciones, las URLs de las imágenes y otros atributos relevantes.

pgvector para la búsqueda semántica: pgvector, una extensión de PostgreSQL, nos permite almacenar incorporaciones de vectores de descripciones de juguetes y consultas de búsqueda de usuarios. Esto permite la búsqueda semántica, lo que significa que el sistema comprende el significado más allá de las palabras, no solo las palabras clave exactas.

Similitud coseno para la relevancia: Usamos la similitud coseno para medir la similitud semántica entre el vector de búsqueda del usuario y los vectores de descripción de los juguetes, y mostrar los resultados más relevantes.

Índice ScaNN para velocidad y precisión: Para garantizar resultados rápidos y precisos, especialmente a medida que crece nuestro inventario de juguetes, integramos el índice ScaNN (vecinos más cercanos escalables). Esto mejora significativamente la eficiencia y la recuperación de nuestra búsqueda de vectores.

  1. Búsqueda y comprensión basadas en imágenes con Gemini 2.0 Flash

En lugar de escribir el contexto como texto, supongamos que el usuario quiere subir una foto de un juguete conocido con el que quiere realizar la búsqueda. Los usuarios pueden subir una imagen de un juguete que les guste y obtener funciones relevantes con ella. Aprovechamos el modelo de Flash Gemini 2.0 de Google, invocado con LangChain4j, para analizar la imagen y extraer el contexto relevante, como el color, el material, el tipo y el grupo etario objetivo del juguete.

  1. Cómo crear el juguete de tus sueños personalizado con IA generativa: Imagen 3

La verdadera magia ocurre cuando los usuarios deciden crear su propio juguete. Con la Imagen 3, les permitimos describir el juguete de sus sueños con instrucciones de texto simples. Imagina poder decir: "Quiero un dragón de peluche con alas púrpuras y una cara amigable" y ver cómo ese dragón cobra vida en la pantalla. Luego, la Imagen 3 genera una imagen del juguete diseñado a medida, lo que le brinda al usuario una visualización clara de su creación.

  1. Predicción de precios potenciada por agentes y Gen AI Toolbox para bases de datos

Implementamos una función de predicción de precios que estima el costo de producir el juguete diseñado a medida. Esto se logra con un agente que incluye una herramienta sofisticada de cálculo de precios.

Gen AI Toolbox para bases de datos: Este agente se integra sin problemas con nuestra base de datos mediante la nueva herramienta de código abierto de Google, Gen AI Toolbox para bases de datos. Esto permite que el agente acceda a datos en tiempo real sobre los costos de los materiales, los procesos de fabricación y otros factores relevantes para proporcionar una estimación de precios precisa. Obtén más información aquí.

  1. Java Spring Boot, Gemini Code Assist y Cloud Run para un desarrollo optimizado y una implementación sin servidores

Toda la aplicación se compila con Java Spring Boot, un framework sólido y escalable. Aprovechamos Gemini Code Assist durante todo el proceso de desarrollo, en particular para el desarrollo de frontend, lo que aceleró significativamente el ciclo de desarrollo y mejoró la calidad del código. Usamos Cloud Run para implementar toda la aplicación y Cloud Run Functions para implementar la base de datos y las funciones de agentes como extremos independientes.

3. Antes de comenzar

Crea un proyecto

  1. En la página del selector de proyectos de la consola de Google Cloud, selecciona o crea un proyecto de Google Cloud.
  2. Asegúrate de que la facturación esté habilitada para tu proyecto de Cloud. Obtén información para verificar si la facturación está habilitada en un proyecto .
  3. Usarás Cloud Shell, un entorno de línea de comandos que se ejecuta en Google Cloud y que viene precargado con bq. Haz clic en Activar Cloud Shell en la parte superior de la consola de Google Cloud.

Imagen del botón Activar Cloud Shell

  1. Una vez que te conectes a Cloud Shell, verifica que ya te hayas autenticado y que el proyecto esté configurado con tu ID de proyecto con el siguiente comando:
gcloud auth list
  1. En Cloud Shell, ejecuta el siguiente comando para confirmar que el comando gcloud conoce tu proyecto.
gcloud config list project
  1. Si tu proyecto no está configurado, usa el siguiente comando para hacerlo:
gcloud config set project <YOUR_PROJECT_ID>
  1. Ejecuta los siguientes comandos uno por uno en la terminal de Cloud Shell para habilitar las APIs requeridas:

También hay un solo comando para ejecutar lo siguiente, pero si eres usuario de una cuenta de prueba, es posible que tengas problemas de cuota cuando intentes habilitarlos de forma masiva. Por eso, los comandos se escriben uno por línea.

gcloud services enable alloydb.googleapis.com
gcloud services enable compute.googleapis.com 
gcloud services enable cloudresourcemanager.googleapis.com 
gcloud services enable servicenetworking.googleapis.com 
gcloud services enable run.googleapis.com 
gcloud services enable cloudbuild.googleapis.com 
gcloud services enable cloudfunctions.googleapis.com 
gcloud services enable aiplatform.googleapis.com

La alternativa al comando gcloud es buscar cada producto en la consola o usar este vínculo.

Si falta alguna API, puedes habilitarla durante el transcurso de la implementación.

Consulta la documentación para ver los comandos y el uso de gcloud.

4. Configuración de la base de datos

En este lab, usaremos AlloyDB como la base de datos para almacenar los datos de la tienda de juguetes. Usa clústeres para contener todos los recursos, como bases de datos y registros. Cada clúster tiene una instancia principal que proporciona un punto de acceso a los datos. Las tablas contendrán los datos reales.

Crearé un clúster, una instancia y una tabla de AlloyDB en los que se cargará el conjunto de datos de comercio electrónico.

Crea un clúster y una instancia

  1. Navega a la página de AlloyDB en Cloud Console. Una forma sencilla de encontrar la mayoría de las páginas en la consola de Cloud es buscarlas con la barra de búsqueda de la consola.
  2. Selecciona CREATE CLUSTER en esa página:

f76ff480c8c889aa.png

  1. Verás una pantalla como la siguiente. Crea un clúster y una instancia con los siguientes valores (asegúrate de que los valores coincidan en caso de que estés clonando el código de la aplicación desde el repositorio):
  • cluster id: "vector-cluster"
  • contrasena: "alloydb"
  • Compatible con PostgreSQL 15
  • Región: "us-central1"
  • Redes: "default"

538dba58908162fb.png

  1. Cuando selecciones la red predeterminada, verás una pantalla como la que se muestra a continuación.

Selecciona CONFIGURAR CONEXIÓN.
7939bbb6802a91bf.png

  1. Allí, selecciona "Usar un rango de IP asignado automáticamente" y haz clic en Continuar. Después de revisar la información, selecciona CREAR CONEXIÓN. 768ff5210e79676f.png
  2. Una vez que se configure la red, podrás continuar con la creación del clúster. Haz clic en CREATE CLUSTER para completar la configuración del clúster como se muestra a continuación:

e06623e55195e16e.png

Asegúrate de cambiar el ID de la instancia a "

vector-instance"

.

Ten en cuenta que la creación del clúster tardará alrededor de 10 minutos. Una vez que se realice correctamente, deberías ver una pantalla que muestre la descripción general del clúster que acabas de crear.

5. Transferencia de datos

Ahora es el momento de agregar una tabla con los datos de la tienda. Navega a AlloyDB, selecciona el clúster principal y, luego, AlloyDB Studio:

847e35f1bf8a8bd8.png

Es posible que debas esperar a que se termine de crear la instancia. Una vez que lo hagas, accede a AlloyDB con las credenciales que creaste cuando creaste el clúster. Usa los siguientes datos para autenticarte en PostgreSQL:

  • Nombre de usuario : "postgres"
  • Base de datos : "postgres"
  • Contraseña : "alloydb"

Una vez que te hayas autenticado correctamente en AlloyDB Studio, los comandos SQL se ingresarán en el editor. Puedes agregar varias ventanas del editor con el signo más que se encuentra a la derecha de la última ventana.

91a86d9469d499c4.png

Ingresarás comandos para AlloyDB en las ventanas del editor, con las opciones Ejecutar, Formatear y Borrar según sea necesario.

Habilita las extensiones

Para compilar esta app, usaremos las extensiones pgvector y google_ml_integration. La extensión pgvector te permite almacenar y buscar embeddings de vectores. La extensión google_ml_integration proporciona funciones que usas para acceder a los extremos de predicción de Vertex AI y obtener predicciones en SQL. Para habilitar estas extensiones, ejecuta los siguientes DDL:

CREATE EXTENSION IF NOT EXISTS google_ml_integration CASCADE;
CREATE EXTENSION IF NOT EXISTS vector;

Si quieres verificar las extensiones que se habilitaron en tu base de datos, ejecuta este comando SQL:

select extname, extversion from pg_extension;

Crea una tabla

Crea una tabla con la siguiente sentencia DDL:

CREATE TABLE toys ( id VARCHAR(25), name VARCHAR(25), description VARCHAR(20000), quantity INT, price FLOAT, image_url VARCHAR(200), text_embeddings vector(768)) ;

Si el comando anterior se ejecuta correctamente, deberías poder ver la tabla en la base de datos.

Transfiere datos

Para este lab, tenemos datos de prueba de alrededor de 72 registros en este archivo SQL. Contiene los campos id, name, description, quantity, price, image_url. Los demás campos se completarán más adelante en el lab.

Copia las líneas o las instrucciones de inserción desde allí y, luego, pégalas en una pestaña del editor en blanco y selecciona EJECUTAR.

Para ver el contenido de la tabla, expande la sección Explorador hasta que puedas ver la tabla llamada apparels. Selecciona los tres puntos (⋮) para ver la opción de consultar la tabla. Se abrirá una sentencia SELECT en una nueva pestaña del editor.

cfaa52b717f9aaed.png

Otorgar permiso

Ejecuta la siguiente sentencia para otorgar derechos de ejecución en la función embedding al usuario postgres:

GRANT EXECUTE ON FUNCTION embedding TO postgres;

Otorga el rol de usuario de Vertex AI a la cuenta de servicio de AlloyDB

Ve a la terminal de Cloud Shell y escribe el siguiente comando:

PROJECT_ID=$(gcloud config get-value project)

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:service-$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")@gcp-sa-alloydb.iam.gserviceaccount.com" \
--role="roles/aiplatform.user"

6. Crea incorporaciones para el contexto

Para las computadoras, es mucho más fácil procesar números que procesar texto. Un sistema de incorporación convierte el texto en una serie de números de punto flotante que deberían representarlo, sin importar cómo esté redactado, qué idioma use, etcétera.

Considera describir una ubicación junto al mar. Puede llamarse "junto al agua", "frente a la playa", "caminar desde tu habitación hasta el océano", "sur la mer", "на берегу океана", etc. Todos estos términos se ven diferentes, pero su significado semántico o, en la terminología del aprendizaje automático, sus incorporaciones deben ser muy similares entre sí.

Ahora que los datos y el contexto están listos, ejecutaremos la sentencia SQL para agregar las incorporaciones de la descripción del producto a la tabla en el campo embedding. Existen varios modelos de incorporación que puedes usar. Usamos text-embedding-005 de Vertex AI. Asegúrate de usar el mismo modelo de incorporación en todo el proyecto.

Nota: Si usas un proyecto de Google Cloud existente que se creó hace tiempo, es posible que debas seguir usando versiones anteriores del modelo de incorporación de texto, como textembedding-gecko.

Regresa a la pestaña AlloyDB Studio y escribe la siguiente DML:

UPDATE toys set text_embeddings = embedding( 'text-embedding-005', description);

Vuelve a mirar la tabla toys para ver algunas incorporaciones. Asegúrate de volver a ejecutar la sentencia SELECT para ver los cambios.

SELECT id, name, description, price, quantity, image_url, text_embeddings FROM toys;

Se debería mostrar el vector de incorporaciones, que se parece a un array de números de punto flotante, para la descripción del juguete, como se muestra a continuación:

7d32f7cd7204e1f3.png

Nota: Es posible que los proyectos de Google Cloud creados recientemente en el nivel gratuito tengan problemas de cuota en relación con la cantidad de solicitudes de incorporación permitidas por segundo a los modelos de incorporación. Te sugerimos que uses una consulta de filtro para el ID y, luego, elijas de forma selectiva entre 1 y 5 registros, y así sucesivamente, mientras generas la incorporación.

7. Realiza una búsqueda vectorial

Ahora que la tabla, los datos y los embeddings están listos, realicemos la búsqueda de vectores en tiempo real para el texto de búsqueda del usuario.

Supongamos que el usuario pregunta lo siguiente:

"I want a white plush teddy bear toy with a floral pattern".

Para encontrar coincidencias, ejecuta la siguiente consulta:

select * from toys
ORDER BY text_embeddings <=> CAST(embedding('text-embedding-005', 'I want a white plush teddy bear toy with a floral pattern') as vector(768))
LIMIT 5;

Veamos esta consulta en detalle:

En esta consulta,

  1. El texto de búsqueda del usuario es: "I want a white plush teddy bear toy with a floral pattern."
  2. Lo convertimos en incorporaciones en el método embedding() con el modelo: text-embedding-005. Este paso debería resultarte familiar después del último, en el que aplicamos la función de incorporación a todos los elementos de la tabla.
  3. "<=>" representa el uso del método de distancia COSINE SIMILARITY. Puedes encontrar todas las medidas de similitud disponibles en la documentación de pgvector.
  4. Convertimos el resultado del método de incorporación en un tipo de vector para que sea compatible con los vectores almacenados en la base de datos.
  5. LIMIT 5 representa que queremos extraer 5 vecinos más cercanos para el texto de búsqueda.

El resultado se verá de la siguiente manera:

fa7f0fc3a4c68804.png

Como puedes observar en los resultados, las coincidencias son bastante cercanas al texto de la búsqueda. Intenta cambiar el texto para ver cómo cambian los resultados.

Nota importante:

Ahora supongamos que queremos aumentar el rendimiento (tiempo de consulta), la eficiencia y la recuperación de este resultado de la Búsqueda de vectores con el índice ScaNN. Lee los pasos que se indican en este blog para comparar la diferencia en los resultados con y sin el índice.

Paso opcional: Mejora la eficiencia y la recuperación con el índice ScaNN

A continuación, se indican los pasos para crear el índice:

  1. Como ya tenemos creado el clúster, la instancia, el contexto y las incorporaciones, solo tenemos que instalar la extensión ScaNN con la siguiente sentencia:
CREATE EXTENSION IF NOT EXISTS alloydb_scann;
  1. A continuación, crearemos el índice (ScaNN):
CREATE INDEX toysearch_index ON toys
USING scann (text_embeddings cosine)
WITH (num_leaves=9);

En el DDL anterior, apparel_index es el nombre del índice.

"toys" es mi tabla

"scann" es el método de indexación.

"embedding" es la columna de la tabla que quiero indexar.

"cosine" es el método de distancia que quiero usar con el índice.

“8” es la cantidad de particiones que se aplicarán a este índice. Se establece en cualquier valor entre 1 y 1048576. Para obtener más información sobre cómo decidir este valor, consulta Cómo ajustar un índice de ScaNN.

Usé la RAIZ CUADRADA de la cantidad de datos como se recomienda en el repositorio de ScaNN (cuando se particiona, num_leaves debe ser aproximadamente la raíz cuadrada de la cantidad de datos).

  1. Verifica si se creó el índice con la siguiente consulta:
SELECT * FROM pg_stat_ann_indexes;
  1. Realiza una búsqueda vectorial con la misma consulta que usamos sin el índice:
select * from toys
ORDER BY text_embeddings <=> CAST(embedding('text-embedding-005', 'I want a white plush teddy bear toy with a floral pattern') as vector(768))
LIMIT 5;

La consulta anterior es la misma que usamos en el lab en el paso 8. Sin embargo, ahora tenemos el campo indexado.

  1. Prueba con una consulta de búsqueda simple con y sin el índice (suelta el índice):

Este caso de uso solo tiene 72 registros, por lo que el índice no tiene efecto. Para una prueba realizada en otro caso de uso, los resultados son los siguientes:

La misma consulta de Búsqueda vectorial en los datos de incorporaciones INDEXED genera resultados de búsqueda de calidad y eficiencia. La eficiencia mejora considerablemente (en términos de tiempo de ejecución: 10.37 ms sin ScaNN y 0.87 ms con ScaNN) con el índice. Para obtener más información sobre este tema, consulta este blog.

8. Validación de coincidencias con el LLM

Antes de continuar y crear un servicio para mostrar las mejores coincidencias a una aplicación, usemos un modelo de IA generativa para validar si estas posibles respuestas son realmente relevantes y seguras para compartir con el usuario.

Cómo garantizar que la instancia esté configurada para Gemini

Primero, verifica si la integración de Google ML ya está habilitada para tu clúster y tu instancia. En AlloyDB Studio, ingresa el siguiente comando:

show google_ml_integration.enable_model_support;

Si el valor se muestra como "on", puedes omitir los siguientes 2 pasos y pasar directamente a configurar la integración de modelos de AlloyDB y Vertex AI.

  1. Ve a la instancia principal de tu clúster de AlloyDB y haz clic en EDITAR INSTANCIA PRINCIPAL.

cb76b934ba3735bd.png

  1. Navega a la sección Marcas en las opciones de configuración avanzada. y asegúrate de que google_ml_integration.enable_model_support flag esté configurado como “on”, como se muestra a continuación:

6a59351fcd2a9d35.png

Si no está activado, hazlo y, luego, haz clic en el botón UPDATE INSTANCE. Este paso tardará unos minutos.

Integración de modelos de AlloyDB y Vertex AI

Ahora puedes conectarte a AlloyDB Studio y ejecutar la siguiente sentencia DML para configurar el acceso al modelo de Gemini desde AlloyDB. Para ello, usa el ID de tu proyecto donde se indique. Es posible que se te advierta un error de sintaxis antes de ejecutar el comando, pero debería ejecutarse correctamente.

Primero, creamos la conexión del modelo Gemini 1.5 como se muestra a continuación. Recuerda reemplazar $PROJECT_ID en el siguiente comando por el ID de tu proyecto de Google Cloud.

CALL
 google_ml.create_model( model_id => 'gemini-1.5',
   model_request_url => 'https://us-central1-aiplatform.googleapis.com/v1/projects/$PROJECT_ID/locations/us-central1/publishers/google/models/gemini-1.5-pro:streamGenerateContent',
   model_provider => 'google',
   model_auth_type => 'alloydb_service_agent_iam');

Puedes verificar los modelos configurados para el acceso con el siguiente comando en AlloyDB Studio:

select model_id,model_type from google_ml.model_info_view;        

Por último, debemos otorgar permiso a los usuarios de la base de datos para que ejecuten la función ml_predict_row y ejecuten predicciones a través de los modelos de Google Vertex AI. Ejecuta el siguiente comando:

GRANT EXECUTE ON FUNCTION ml_predict_row to postgres;

Nota: Si usas un proyecto de Google Cloud existente y un clúster o una instancia de AlloyDB existentes que se crearon hace un tiempo, es posible que debas descartar las referencias anteriores al modelo gemini-1.5 y volver a crearlo con la sentencia CALL anterior y ejecutar grant execute en la función ml_predict_row nuevamente en caso de que tengas problemas en las próximas invocaciones de gemini-1.5.

Evalúa las respuestas

Si bien usaremos una consulta grande en la siguiente sección para asegurarnos de que las respuestas sean razonables, puede ser difícil de entender. Ahora veremos las piezas y cómo se unen en unos minutos.

  1. Primero, enviaremos una solicitud a la base de datos para obtener las 10 coincidencias más cercanas a una consulta del usuario.
  2. Para determinar qué tan válidas son las respuestas, usaremos una consulta externa en la que explicaremos cómo evaluarlas. Usa el campo recommended_text, que es el texto de búsqueda, y content (que es el campo de descripción del juguete) de la tabla interna como parte de la consulta.
  3. Con eso, revisaremos la “calidad” de las respuestas que se muestran.
  4. predict_row muestra su resultado en formato JSON. El código "-> 'candidates' -> 0 -> 'content' -> 'parts' -> 0 -> 'text'" se usa para extraer el texto real de ese JSON. Para ver el JSON real que se muestra, puedes quitar este código.
  5. Por último, para obtener la respuesta del LLM, la extraemos con REGEXP_REPLACE(gemini_validation, '[^a-zA-Z,: ]', '', 'g').
SELECT id,
       name,
       content,
       quantity,
       price,
       image_url,
       recommended_text,
       REGEXP_REPLACE(gemini_validation, '[^a-zA-Z,: ]', '', 'g') AS gemini_validation
  FROM (SELECT id,
               name,
               content,
               quantity,
               price,
               image_url,
               recommended_text,
               CAST(ARRAY_AGG(LLM_RESPONSE) AS TEXT) AS gemini_validation
          FROM (SELECT id,
                       name,
                       content,
                       quantity,
                       price,
                       image_url,
                       recommended_text,
                       json_array_elements(google_ml.predict_row(model_id => 'gemini-1.5',
                                                                   request_body => CONCAT('{ "contents": [ { "role": "user", "parts": [ { "text": "User wants to buy a toy and this is the description of the toy they wish to buy: ',                                                                                              recommended_text,                                                                                              '. Check if the following product items from the inventory are close enough to really, contextually match the user description. Here are the items: ',                                                                                         content,                                                                                         '. Return a ONE-LINE response with 3 values: 1) MATCH: if the 2 contexts are reasonably matching in terms of any of the color or color family specified in the list, approximate style match with any of the styles mentioned in the user search text: This should be a simple YES or NO. Choose NO only if it is completely irrelevant to users search criteria. 2) PERCENTAGE: percentage of match, make sure that this percentage is accurate 3) DIFFERENCE: A clear one-line easy description of the difference between the 2 products. Remember if the user search text says that some attribute should not be there, and the record has it, it should be a NO match. " } ] } ] }')::JSON)) -> 'candidates' -> 0 -> 'content' -> 'parts' -> 0 -> 'text' :: TEXT AS LLM_RESPONSE
                  FROM (SELECT id,
                               name,
                               description AS content,
                               quantity,
                               price,
                               image_url,
                               'Pink panther standing' AS recommended_text
                          FROM toys
                         ORDER BY text_embeddings <=> embedding('text-embedding-005',
                                                                'Pink panther standing')::VECTOR
                         LIMIT 10) AS xyz) AS X
         GROUP BY id,
                  name,
                  content,
                  quantity,
                  price,
                  image_url,
                  recommended_text) AS final_matches
 WHERE REGEXP_REPLACE(gemini_validation, '[^a-zA-Z,: ]', '', 'g') LIKE '%MATCH%:%YES%';

Si bien puede parecer abrumador, espero que puedas entenderlo mejor. Los resultados indican si hay o no una coincidencia, qué porcentaje es y una explicación de la calificación.

Ten en cuenta que el modelo de Gemini tiene la transmisión activada de forma predeterminada, por lo que la respuesta real se distribuye en varias líneas:

c2b006aeb3f3a2fc.png

9. Lleva Toy Search a Cloud sin servidores

¿Todo listo para llevar esta app a la Web? Sigue los pasos que se indican a continuación para crear este motor de conocimiento sin servidores con funciones de Cloud Run:

  1. Ve a Funciones de Cloud Run en la consola de Google Cloud para CREAR una nueva función de Cloud Run o usa el vínculo: https://console.cloud.google.com/functions/add.
  2. Selecciona el entorno como “Función de Cloud Run”. Proporciona el nombre de la función "get-toys-alloydb" y elige la región "us-central1". Establece la autenticación en "Permitir invocaciones no autenticadas" y haz clic en NEXT. Elige Java 17 como entorno de ejecución y Editor intercalado para el código fuente.
  3. De forma predeterminada, se establecería el punto de entrada en "gcfv2.HelloHttpFunction". Reemplaza el código del marcador de posición en HelloHttpFunction.java y pom.xml de tu función de Cloud Run por el código de HelloHttpFunction.java y pom.xml, respectivamente.
  4. Recuerda cambiar el marcador de posición <<YOUR_PROJECT>> y las credenciales de conexión de AlloyDB por tus valores en el archivo Java. Las credenciales de AlloyDB son las que usamos al comienzo de este codelab. Si usaste valores diferentes, modifica los mismos en el archivo Java.
  5. Haz clic en Implementar.

Una vez que se implemente, para permitir que Cloud Function acceda a nuestra instancia de base de datos de AlloyDB, crearemos el conector de VPC.

PASO IMPORTANTE:

Una vez que todo esté listo para la implementación, deberías poder ver las funciones en la consola de Cloud Run Functions de Google. Busca la función recién creada (get-toys-alloydb), haz clic en ella y, luego, en EDITAR. Cambia lo siguiente:

  1. Ve a Configuración del entorno de ejecución, la compilación, las conexiones y la seguridad
  2. Aumenta el tiempo de espera a 180 segundos
  3. Ve a la pestaña CONEXIONES:

4e83ec8a339cda08.png

  1. En la configuración de entrada, asegúrate de que esté seleccionada la opción "Permitir todo el tráfico".
  2. En la configuración de salida, haz clic en el menú desplegable Red y selecciona la opción "Agregar nuevo conector de VPC". Luego, sigue las instrucciones que aparecen en el cuadro de diálogo emergente:

8126ec78c343f199.png

  1. Proporciona un nombre para el conector de VPC y asegúrate de que la región sea la misma que la de tu instancia. Deja el valor de red como predeterminado y establece la subred como rango de IP personalizado con el rango de IP 10.8.0.0 o algo similar que esté disponible.
  2. Expande MOSTRAR LA CONFIGURACIÓN DE ESCALA y asegúrate de que la configuración sea exactamente la siguiente:

7baf980463a86a5c.png

  1. Haz clic en CREATE y este conector debería aparecer en la configuración de salida.
  2. Selecciona el conector recién creado
  3. Elige que todo el tráfico se enrute a través de este conector de VPC.
  4. Haz clic en NEXT y, luego, en DEPLOY.

10. Prueba la función de Cloud Run

Una vez que se implemente la Cloud Function actualizada, deberías ver el extremo en el siguiente formato:

https://us-central1-YOUR_PROJECT_ID.cloudfunctions.net/get-toys-alloydb

Como alternativa, puedes probar la función de Cloud Run de la siguiente manera:

PROJECT_ID=$(gcloud config get-value project)

curl -X POST https://us-central1-$PROJECT_ID.cloudfunctions.net/get-toys-alloydb \
  -H 'Content-Type: application/json' \
  -d '{"search":"I want a standing pink panther toy"}' \
  | jq .

Y el resultado sería el siguiente:

23861e9091565a64.png

Eso es todo. Es así de simple realizar una búsqueda de vectores de similitud con el modelo de embeddings en los datos de AlloyDB.

11. Compila el cliente de la aplicación web

En esta parte, crearemos una aplicación web para que el usuario interactúe con ella y encuentre juguetes que coincidan en función del texto, la imagen y hasta cree uno nuevo según sus necesidades. Como la aplicación ya está compilada, puedes seguir los pasos que se indican a continuación para copiarla en tu IDE y ponerla en funcionamiento.

  1. Dado que usamos Gemini 2.0 Flash para describir la imagen que el usuario puede subir para encontrar juguetes que coincidan, debemos obtener la CLAVE DE API para esta aplicación. Para ello, ve a https://aistudio.google.com/apikey, obtén la clave de API de tu proyecto activo de Google Cloud en el que implementas esta aplicación y guárdala en algún lugar:

ae2db169e6a94e4a.png

  1. Navega a la terminal de Cloud Shell
  2. Usa el siguiente comando para clonar el repo:
git clone https://github.com/AbiramiSukumaran/toysearch

cd toysearch
  1. Una vez que se haya clonado el repositorio, deberías poder acceder al proyecto desde el editor de Cloud Shell.
  2. Debes borrar las carpetas "get-toys-alloydb" y "toolbox-toys" del proyecto clonado porque son código de funciones de Cloud Run al que se puede hacer referencia desde el repositorio cuando lo necesites.
  3. Asegúrate de que se hayan establecido todas las variables de entorno necesarias antes de compilar e implementar la app. Navega a la terminal de Cloud Shell y ejecuta lo siguiente:
PROJECT_ID=$(gcloud config get-value project)

export PROJECT_ID $PROJECT_ID

export GOOGLE_API_KEY <YOUR API KEY that you saved>
  1. Compila y ejecuta la app de forma local:

Asegúrate de estar en el directorio del proyecto y ejecuta los siguientes comandos:

mvn package

mvn spring-boot:run 
  1. Implementa en Cloud Run
gcloud run deploy --source .

12. Comprende los detalles de la IA generativa

No se requiere ninguna acción. Ten en cuenta lo siguiente:

Ahora que tienes la aplicación para implementar, tómate un momento para comprender cómo realizamos la búsqueda (texto e imagen) y la generación.

  1. Búsqueda vectorial basada en el texto del usuario:

Esto ya se abordó en las funciones de Cloud Run que implementamos en la sección "Explora la aplicación web de Vector Search".

  1. Búsqueda de vectores basada en la carga de imágenes:

En lugar de escribir el contexto como texto, supongamos que el usuario quiere subir una foto de un juguete conocido con el que quiere realizar la búsqueda. Los usuarios pueden subir una imagen de un juguete que les guste y obtener funciones relevantes con ella.

Aprovechamos el modelo de Flash Gemini 2.0 de Google, invocado con LangChain4j, para analizar la imagen y extraer el contexto relevante, como el color, el material, el tipo y el grupo etario objetivo del juguete.

En solo 5 pasos, tomamos la entrada de datos multimodal del usuario para hacer coincidir los resultados con la invocación de un modelo de lenguaje extenso con un framework de código abierto. Aquí te indicamos cómo hacerlo:

package cloudcode.helloworld.web;

import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.googleai.GoogleAiGeminiChatModel;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.model.output.Response;
import dev.langchain4j.data.message.ImageContent;
import dev.langchain4j.data.message.TextContent;
import java.util.Base64;
import java.util.Optional;

public class GeminiCall {
  public String imageToBase64String(byte[] imageBytes) {
    String base64Img = Base64.getEncoder().encodeToString(imageBytes);
    return base64Img;
  }

  public String callGemini(String base64ImgWithPrefix) throws Exception {
    String searchText = "";

    // 1. Remove the prefix
    String base64Img = base64ImgWithPrefix.replace("data:image/jpeg;base64,", "");

    // 2. Decode base64 to bytes
    byte[] imageBytes = Base64.getDecoder().decode(base64Img);
    String image = imageToBase64String(imageBytes);

    // 3. Get API key from environment variable
        String apiKey = Optional.ofNullable(System.getenv("GOOGLE_API_KEY"))
                .orElseThrow(() -> new IllegalArgumentException("GOOGLE_API_KEY environment variable not set"));

    // 4. Invoke Gemini 2.0
    ChatLanguageModel gemini = GoogleAiGeminiChatModel.builder()
        .apiKey(apiKey)
        .modelName("gemini-2.0-flash-001")
        .build();

    Response<AiMessage> response = gemini.generate(
        UserMessage.from(
            ImageContent.from(image, "image/jpeg"),
            TextContent.from(
                "The picture has a toy in it. Describe the toy in the image in one line. Do not add any prefix or title to your description. Just describe that toy that you see in the image in one line, do not describe the surroundings and other objects around the toy in the image. If you do not see any toy in the image, send  response stating that no toy is found in the input image.")));
   
    // 5. Get the text from the response and send it back to the controller
    searchText = response.content().text().trim();
    System.out.println("searchText inside Geminicall: " + searchText);
    return searchText;
  }
}
  1. Comprende cómo usamos la Imagen 3 para crear un juguete personalizado según la solicitud del usuario con la IA generativa.

Luego, la Imagen 3 genera una imagen del juguete diseñado a medida, lo que le brinda al usuario una visualización clara de su creación. Así es como lo hicimos en solo 5 pasos:

// Generate an image using a text prompt using an Imagen model
    public String generateImage(String projectId, String location, String prompt)
        throws ApiException, IOException {
      final String endpoint = String.format("%s-aiplatform.googleapis.com:443", location);
      PredictionServiceSettings predictionServiceSettings =
      PredictionServiceSettings.newBuilder().setEndpoint(endpoint).build();
     
      // 1. Set up the context and prompt
      String context = "Generate a photo-realistic image of a toy described in the following input text from the user. Make sure you adhere to all the little details and requirements mentioned in the prompt. Ensure that the user is only describing a toy. If it is anything unrelated to a toy, politely decline the request stating that the request is inappropriate for the current context. ";
      prompt = context + prompt;

      // 2. Initialize a client that will be used to send requests. This client only needs to be created
      // once, and can be reused for multiple requests.
      try (PredictionServiceClient predictionServiceClient =
          PredictionServiceClient.create(predictionServiceSettings)) {
 
      // 3. Invoke Imagen 3
        final EndpointName endpointName =
            EndpointName.ofProjectLocationPublisherModelName(
                projectId, location, "google", "imagen-3.0-generate-001"); //"imagegeneration@006"; imagen-3.0-generate-001
        Map<String, Object> instancesMap = new HashMap<>();
        instancesMap.put("prompt", prompt);
        Value instances = mapToValue(instancesMap);
        Map<String, Object> paramsMap = new HashMap<>();
        paramsMap.put("sampleCount", 1);
        paramsMap.put("aspectRatio", "1:1");
        paramsMap.put("safetyFilterLevel", "block_few");
        paramsMap.put("personGeneration", "allow_adult");
        paramsMap.put("guidanceScale", 21);
        paramsMap.put("imagenControlScale", 0.95); //Setting imagenControlScale
        Value parameters = mapToValue(paramsMap);
       
      // 4. Get prediction response image
        PredictResponse predictResponse =
            predictionServiceClient.predict(
                endpointName, Collections.singletonList(instances), parameters);

      // 5. Return the Base64 Encoded String to the controller
        for (Value prediction : predictResponse.getPredictionsList()) {
          Map<String, Value> fieldsMap = prediction.getStructValue().getFieldsMap();
          if (fieldsMap.containsKey("bytesBase64Encoded")) {
            bytesBase64EncodedOuput = fieldsMap.get("bytesBase64Encoded").getStringValue();
        }
      }
      return bytesBase64EncodedOuput.toString();
    }
  }

Predicción de precios

En la sección anterior, analizamos cómo Imagen genera la imagen de un juguete que el usuario desea diseñar por su cuenta. Para que puedan comprarlo, la aplicación debe establecer un precio, y usamos una lógica intuitiva para definir un precio para el juguete personalizado hecho a pedido. La lógica es usar el precio promedio de los 5 juguetes más cercanos que coincidan (en términos de descripción) con el que diseña el usuario.

La predicción del precio del juguete generado es una parte importante de esta aplicación, y usamos un enfoque de agente para generarla. Presentamos Gen AI Toolbox para bases de datos.

13. Gen AI Toolbox para bases de datos

Gen AI Toolbox for Databases es un servidor de código abierto de Google que facilita la compilación de herramientas de IA generativa para interactuar con bases de datos. Te permite desarrollar herramientas de forma más fácil, rápida y segura, ya que controla las complejidades, como el grupo de conexiones, la autenticación y mucho más. Te ayuda a crear herramientas de IA generativa que permitan a tus agentes acceder a los datos de tu base de datos.

Estos son los pasos que debes seguir para configurar la herramienta y hacer que nuestra aplicación sea agente: Vínculo al codelab de Toolbox

Tu aplicación ahora puede usar este extremo de la función de Cloud Run implementado para propagar el precio junto con el resultado de la imagen generada para la imagen de juguete personalizada a pedido.

14. Prueba tu aplicación web

Ahora que se compilaron y se implementaron todos los componentes de tu aplicación, está lista para publicarse en la nube. Prueba tu aplicación en todas las situaciones. Este es un vínculo a un video sobre lo que puedes esperar:

https://www.youtube.com/shorts/ZMqUAWsghYQ

Así es como se ve la página de destino:

241db19e7176e93e.png

15. Limpia

Sigue estos pasos para evitar que se apliquen cargos a tu cuenta de Google Cloud por los recursos que usaste en esta publicación:

  1. En la consola de Google Cloud, ve a la página Administrar recursos.
  2. En la lista de proyectos, elige el proyecto que deseas borrar y haz clic en Borrar.
  3. En el diálogo, escribe el ID del proyecto y, luego, haz clic en Cerrar para borrarlo.

16. Felicitaciones

¡Felicitaciones! Realizaste correctamente una búsqueda y generación contextual de tiendas de juguetes con AlloyDB, pgvector, Imagen y Gemini 2.0, a la vez que aprovechaste las bibliotecas de código abierto para crear integraciones sólidas. Combinando las capacidades de AlloyDB, Vertex AI y Búsqueda de vectores, dimos un gran paso adelante para que las búsquedas contextuales y vectoriales sean accesibles, eficientes y realmente orientadas al significado.