Application de recherche dans un magasin de jouets avec des bases de données Cloud, des environnements d'exécution sans serveur et des intégrations Open Source

1. Présentation

Imaginez-vous entrer dans un magasin de jouets, virtuellement ou en personne, où trouver le cadeau idéal est un jeu d'enfant. Vous pouvez décrire ce que vous recherchez, importer une photo d'un jouet ou même concevoir votre propre création. Le magasin comprend instantanément vos besoins et vous offre une expérience personnalisée. Il ne s'agit pas d'un fantasme futuriste, mais d'une réalité rendue possible par l'IA, la technologie cloud et une vision de l'e-commerce personnalisé.

Le défi : trouver le produit idéal qui correspond à votre imagination peut être difficile. Les termes de recherche génériques, les mots clés et les recherches approximatives sont souvent insuffisants. Parcourir des pages sans fin peut être fastidieux, et le décalage entre ce que vous imaginez et ce qui est disponible peut être frustrant.

La solution : l'application de démonstration relève ce défi de front, en exploitant la puissance de l'IA pour offrir une expérience véritablement personnalisée et fluide grâce à la recherche contextuelle et à la génération personnalisée du produit correspondant au contexte de recherche.

Ce que vous allez faire

Au cours de cet atelier, vous allez :

  1. Créer une instance AlloyDB et charger l'ensemble de données Toys
  2. Activer les extensions pgvector et de modèle d'IA générative dans AlloyDB
  3. Générer des embeddings à partir de la description du produit et effectuer une recherche de similarité cosinus en temps réel pour le texte de recherche de l'utilisateur
  4. Invoquer Gemini 2.0 Flash pour décrire l'image importée par l'utilisateur pour une recherche contextuelle de jouets
  5. Invoque Imagen 3 pour créer un jouet personnalisé en fonction des centres d'intérêt de l'utilisateur.
  6. Appeler un outil de prédiction des prix créé à l'aide de la boîte à outils d'IA générative pour les bases de données afin d'obtenir des informations sur le prix du jouet personnalisé
  7. Déployer la solution dans Cloud Run Functions sans serveur

Conditions requises

  • Un navigateur tel que Chrome ou Firefox
  • Un projet Google Cloud avec facturation activée.

2. Architecture

Flux de données : examinons de plus près comment les données circulent dans notre système :

  1. Recherche contextuelle avec la génération augmentée par récupération (RAG) optimisée par l'IA

Imaginez que, au lieu de simplement rechercher "voiture rouge", le système comprenne les éléments suivants :

"petit véhicule adapté à un garçon de 3 ans"

AlloyDB comme base : nous utilisons AlloyDB, la base de données Google Cloud entièrement gérée et compatible avec PostgreSQL, pour stocker nos données de jouets, y compris les descriptions, les URL des images et d'autres attributs pertinents.

pgvector pour la recherche sémantique : pgvector, une extension PostgreSQL, nous permet de stocker les embeddings vectoriels des descriptions de jouets et des requêtes de recherche des utilisateurs. Cela permet d'effectuer une recherche sémantique, ce qui signifie que le système comprend la signification des mots, et pas seulement les mots clés exacts.

Similarité cosinus pour la pertinence : nous utilisons la similarité cosinus pour mesurer la similarité sémantique entre le vecteur de recherche de l'utilisateur et les vecteurs de description des jouets, afin de présenter les résultats les plus pertinents.

Index ScaNN pour la vitesse et la précision : pour garantir des résultats rapides et précis, en particulier à mesure que notre inventaire de jouets augmente, nous intégrons l'index ScaNN (Scalable Nearest Neighbors). Cela améliore considérablement l'efficacité et le rappel de notre recherche vectorielle.

  1. Recherche et compréhension basées sur les images avec Gemini 2.0 Flash

Au lieu de saisir le contexte sous forme de texte, imaginons que l'utilisateur souhaite importer une photo d'un jouet familier avec lequel il veut effectuer une recherche. Les utilisateurs peuvent importer une image d'un jouet qui leur plaît et obtenir des fonctionnalités pertinentes. Nous utilisons le modèle Gemini 2.0 Flash de Google, invoqué à l'aide de LangChain4j, pour analyser l'image et extraire le contexte pertinent, comme la couleur, le matériau, le type et la tranche d'âge du jouet.

  1. Créez le jouet de vos rêves personnalisé avec l'IA générative : Imagen 3

La vraie magie opère lorsque les utilisateurs décident de créer leur propre jouet. Grâce à Imagen 3, nous leur permettons de décrire le jouet de leurs rêves à l'aide de requêtes textuelles simples. Imaginez pouvoir dire : "Je veux une peluche de dragon avec des ailes violettes et un visage sympathique" et voir ce dragon prendre vie sur votre écran ! Imagen 3 génère ensuite une image du jouet personnalisé, ce qui permet à l'utilisateur de visualiser clairement sa création.

  1. Prédiction des prix grâce aux agents et à la boîte à outils d'IA générative pour les bases de données

Nous avons implémenté une fonctionnalité de prédiction des prix qui estime le coût de production du jouet personnalisé. Cette fonctionnalité est optimisée par un agent qui inclut un outil sophistiqué de calcul des prix.

Boîte à outils d'IA générative pour les bases de données : cet agent est parfaitement intégré à notre base de données à l'aide du nouvel outil Open Source de Google, la boîte à outils d'IA générative pour les bases de données. L'agent peut ainsi accéder à des données en temps réel sur les coûts des matériaux, les processus de fabrication et d'autres facteurs pertinents pour fournir une estimation précise du prix. Pour en savoir plus, cliquez ici.

  1. Java Spring Boot, Gemini Code Assist et Cloud Run pour un développement simplifié et un déploiement sans serveur

L'ensemble de l'application est conçu à l'aide de Java Spring Boot, un framework robuste et évolutif. Nous avons utilisé Gemini Code Assist tout au long du processus de développement, en particulier pour le développement du frontend, ce qui a permis d'accélérer considérablement le cycle de développement et d'améliorer la qualité du code. Nous avons utilisé Cloud Run pour déployer l'ensemble de l'application et Cloud Run Functions pour déployer les fonctionnalités de base de données et d'agent en tant que points de terminaison indépendants.

3. Avant de commencer

Créer un projet

  1. Dans la console Google Cloud, sur la page du sélecteur de projet, sélectionnez ou créez un projet Google Cloud.
  2. Assurez-vous que la facturation est activée pour votre projet Cloud. Découvrez comment vérifier si la facturation est activée sur un projet .
  3. Vous allez utiliser Cloud Shell, un environnement de ligne de commande exécuté dans Google Cloud et fourni avec bq. Cliquez sur "Activer Cloud Shell" en haut de la console Google Cloud.

Image du bouton "Activer Cloud Shell"

  1. Une fois connecté à Cloud Shell, vérifiez que vous êtes déjà authentifié et que le projet est défini sur votre ID de projet à l'aide de la commande suivante :
gcloud auth list
  1. Exécutez la commande suivante dans Cloud Shell pour vérifier que la commande gcloud connaît votre projet.
gcloud config list project
  1. Si votre projet n'est pas défini, utilisez la commande suivante pour le définir :
gcloud config set project <YOUR_PROJECT_ID>
  1. Activez les API requises en exécutant les commandes suivantes une par une dans votre terminal Cloud Shell :

Il existe également une seule commande pour exécuter les opérations ci-dessous, mais si vous utilisez un compte d'essai, vous risquez de rencontrer des problèmes de quota en essayant de les activer de manière groupée. C'est pourquoi les commandes sont listées une par ligne.

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

Vous pouvez également accéder à la console en recherchant chaque produit ou en utilisant ce lien.

Si vous oubliez d'activer une API, vous pourrez toujours le faire au cours de l'implémentation.

Consultez la documentation pour connaître les commandes gcloud ainsi que leur utilisation.

4. Configuration de la base de données

Dans cet atelier, nous allons utiliser AlloyDB comme base de données pour stocker les données du magasin de jouets. Il utilise des clusters pour stocker toutes les ressources, telles que les bases de données et les journaux. Chaque cluster possède une instance principale qui fournit un point d'accès aux données. Les tables contiennent les données réelles.

Commençons par créer un cluster, une instance et une table AlloyDB dans lesquels l'ensemble de données e-commerce sera chargé.

Créer un cluster et une instance

  1. Accédez à la page AlloyDB de la console Cloud. Pour trouver la plupart des pages de la console Cloud, le plus simple est de les rechercher à l'aide de la barre de recherche de la console.
  2. Sélectionnez CRÉER UN CLUSTER sur cette page :

f76ff480c8c889aa.png

  1. Un écran semblable à celui ci-dessous s'affiche. Créez un cluster et une instance avec les valeurs suivantes (assurez-vous que les valeurs correspondent si vous clonez le code de l'application à partir du dépôt) :
  • ID du cluster : "vector-cluster"
  • password : "alloydb"
  • Compatible avec PostgreSQL 15
  • Région : "us-central1"
  • Networking : "default"

538dba58908162fb.png

  1. Lorsque vous sélectionnez le réseau par défaut, un écran semblable à celui ci-dessous s'affiche.

Sélectionnez CONFIGURER LA CONNEXION.
7939bbb6802a91bf.png

  1. Sélectionnez ensuite Utiliser une plage d'adresses IP automatiquement allouée, puis cliquez sur "Continuer". Après avoir vérifié les informations, sélectionnez CRÉER UNE CONNEXION. 768ff5210e79676f.png
  2. Une fois votre réseau configuré, vous pouvez continuer à créer votre cluster. Cliquez sur CRÉER UN CLUSTER pour terminer la configuration du cluster, comme indiqué ci-dessous :

e06623e55195e16e.png

Veillez à remplacer l'ID d'instance par

vector-instance

Si vous ne pouvez pas le modifier, n'oubliez pas de modifier l'ID d'instance dans toutes les références à venir.

Notez que la création du cluster prendra environ 10 minutes. Une fois l'opération terminée, un écran présentant le cluster que vous venez de créer devrait s'afficher.

5. Ingestion de données

Il est maintenant temps d'ajouter un tableau contenant les données sur le magasin. Accédez à AlloyDB, sélectionnez le cluster principal, puis AlloyDB Studio :

847e35f1bf8a8bd8.png

Vous devrez peut-être attendre que votre instance soit créée. Une fois le cluster créé, connectez-vous à AlloyDB à l'aide des identifiants que vous avez créés. Utilisez les données suivantes pour vous authentifier auprès de PostgreSQL :

  • Nom d'utilisateur : "postgres"
  • Base de données : "postgres"
  • Mot de passe : "alloydb"

Une fois l'authentification réussie dans AlloyDB Studio, les commandes SQL sont saisies dans l'éditeur. Vous pouvez ajouter plusieurs fenêtres de l'éditeur en cliquant sur le signe plus à droite de la dernière fenêtre.

91a86d9469d499c4.png

Vous saisirez des commandes pour AlloyDB dans des fenêtres d'éditeur, en utilisant les options "Exécuter", "Mettre en forme" et "Effacer" selon les besoins.

Activer les extensions

Pour créer cette application, nous allons utiliser les extensions pgvector et google_ml_integration. L'extension pgvector vous permet de stocker et de rechercher des embeddings vectoriels. L'extension google_ml_integration fournit les fonctions que vous utilisez pour accéder aux points de terminaison de prédiction Vertex AI afin d'obtenir des prédictions en SQL. Activez ces extensions en exécutant les LDD suivants :

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

Si vous souhaitez vérifier les extensions qui ont été activées dans votre base de données, exécutez la commande SQL suivante :

select extname, extversion from pg_extension;

Créer une table

Créez une table à l'aide de l'instruction LDD ci-dessous :

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 la commande ci-dessus s'exécute correctement, vous devriez pouvoir afficher le tableau dans la base de données.

Ingérer des données

Pour cet atelier, nous disposons de données de test d'environ 72 enregistrements dans ce fichier SQL. Il contient les champs id, name, description, quantity, price, image_url. Les autres champs seront remplis plus tard dans l'atelier.

Copiez uniquement les cinq premières lignes/instructions d'insertion, puis collez-les dans un onglet d'éditeur vide et sélectionnez EXÉCUTER. Si vous n'utilisez PAS un compte de facturation d'essai, vous pouvez probablement copier toutes les instructions d'insertion et les exécuter.

Pour afficher le contenu de la table, développez la section "Explorateur" jusqu'à ce que la table "apparels" s'affiche. Sélectionnez le tricolon (⋮) pour afficher l'option permettant d'interroger la table. Une instruction SELECT s'ouvre dans un nouvel onglet de l'éditeur.

cfaa52b717f9aaed.png

Accorder l'autorisation

Exécutez l'instruction ci-dessous pour accorder les droits d'exécution sur la fonction embedding à l'utilisateur postgres :

GRANT EXECUTE ON FUNCTION embedding TO postgres;

Attribuer le RÔLE Utilisateur Vertex AI au compte de service AlloyDB

Accédez au terminal Cloud Shell et exécutez la commande suivante :

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. Créer des embeddings pour le contexte

Il est beaucoup plus facile pour les ordinateurs de traiter des nombres que du texte. Un système d'embedding convertit le texte en une série de nombres à virgule flottante qui doivent représenter le texte, quelle que soit sa formulation, la langue utilisée, etc.

Essayez de décrire un lieu au bord de la mer. Il peut s'agir de termes tels que "au bord de l'eau", "en bord de plage", "à quelques pas de l'océan", "sur la mer", "на берегу океана", etc. Ces termes sont tous différents, mais leur signification sémantique, ou leur embedding en termes de machine learning, devrait être très proche.

Maintenant que les données et le contexte sont prêts, nous allons exécuter le code SQL pour ajouter les embeddings de la description du produit à la table dans le champ embedding. Vous pouvez utiliser différents modèles d'embedding. Nous utilisons text-embedding-005 de Vertex AI. Veillez à utiliser le même modèle d'embedding tout au long du projet.

Remarque : Si vous utilisez un projet Google Cloud existant créé il y a quelque temps, vous devrez peut-être continuer à utiliser d'anciennes versions du modèle d'embedding de texte, comme textembedding-gecko.

Revenez à l'onglet AlloyDB Studio et saisissez le code LMD suivant :

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

Consultez à nouveau le tableau toys pour voir quelques embeddings. N'oubliez pas de réexécuter l'instruction SELECT pour voir les modifications.

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

Le vecteur d'embedding, qui ressemble à un tableau de valeurs flottantes, devrait être renvoyé pour la description du jouet, comme indiqué ci-dessous :

7d32f7cd7204e1f3.png

Remarque : Les projets Google Cloud nouvellement créés avec le forfait sans frais peuvent rencontrer des problèmes de quota concernant le nombre de requêtes d'embedding autorisées par seconde pour les modèles d'embedding. Nous vous suggérons d'utiliser une requête de filtre pour l'ID, puis de sélectionner de manière sélective un à cinq enregistrements, et ainsi de suite, lors de la génération de l'embedding.

7. Effectuer une recherche vectorielle

Maintenant que la table, les données et les embeddings sont prêts, effectuons la recherche vectorielle en temps réel pour le texte de recherche de l'utilisateur.

Supposons que l'utilisateur pose la question suivante :

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

Pour trouver des correspondances, exécutez la requête ci-dessous :

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 2;

Examinons cette requête en détail :

Dans cette requête,

  1. Le texte de recherche de l'utilisateur est : "I want a white plush teddy bear toy with a floral pattern."
  2. Nous le convertissons en embeddings dans la méthode embedding() à l'aide du modèle text-embedding-005. Cette étape devrait vous sembler familière après la dernière étape, où nous avons appliqué la fonction d'embedding à tous les éléments du tableau.
  3. "<=>" représente l'utilisation de la méthode de distance COSINE SIMILARITY. Vous trouverez toutes les mesures de similarité disponibles dans la documentation de pgvector.
  4. Nous convertissons le résultat de la méthode d'embedding en type vectoriel pour le rendre compatible avec les vecteurs stockés dans la base de données.
  5. LIMIT 5 indique que nous souhaitons extraire les cinq voisins les plus proches du texte de recherche.

Le résultat ressemble à ceci :

fa7f0fc3a4c68804.png

Comme vous pouvez le voir dans vos résultats, les correspondances sont assez proches du texte de recherche. Essayez de modifier le texte pour voir comment les résultats changent.

Remarque importante :

Supposons maintenant que nous voulions améliorer les performances (temps de réponse), l'efficacité et le rappel de ce résultat de recherche vectorielle à l'aide de l'index ScaNN. Veuillez lire les étapes décrites dans cet article de blog pour comparer les résultats avec et sans index.

Étape facultative : Améliorer l'efficacité et le rappel avec l'index ScaNN

Ignorez cette étape si le nombre d'enregistrements est inférieur à 100.

Pour plus de commodité, voici les étapes de création de l'index :

  1. Comme nous avons déjà créé le cluster, l'instance, le contexte et les embeddings, il nous suffit d'installer l'extension ScaNN à l'aide de l'instruction suivante :
CREATE EXTENSION IF NOT EXISTS alloydb_scann;
  1. Nous allons ensuite créer l'index (ScaNN) :
CREATE INDEX toysearch_index ON toys
USING scann (text_embeddings cosine)
WITH (num_leaves=9);

Dans le LDD ci-dessus, "apparel_index" est le nom de l'index.

"jouets" est ma table

"scann" est la méthode d'index

"embedding" est la colonne du tableau que je souhaite indexer.

"cosine" est la méthode de distance que je souhaite utiliser avec l'index.

"8" correspond au nombre de partitions à appliquer à cet index. Définissez une valeur comprise entre 1 et 1 048 576. Pour savoir comment déterminer cette valeur, consultez Régler un index ScaNN.

J'ai utilisé la RACINE CARRÉE du nombre de points de données, comme recommandé dans le dépôt ScaNN (lors du partitionnement, num_leaves doit être à peu près égal à la racine carrée du nombre de points de données).

  1. Vérifiez si l'index a été créé à l'aide de la requête suivante :
SELECT * FROM pg_stat_ann_indexes;
  1. Effectuez une recherche vectorielle à l'aide de la même requête que celle que nous avons utilisée sans l'index :
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 requête ci-dessus est la même que celle que nous avons utilisée à l'étape 8 de l'atelier. Cependant, le champ est désormais indexé.

  1. Testez avec une requête de recherche simple avec et sans l'index (en supprimant l'index) :

Ce cas d'utilisation ne comporte que 72 enregistrements. L'index n'a donc pas vraiment d'effet. Pour un test effectué dans un autre cas d'utilisation, les résultats sont les suivants :

La même requête Vector Search sur les données d'embedding INDEXÉES permet d'obtenir des résultats de recherche de qualité et de l'efficacité. L'efficacité est considérablement améliorée (en termes de temps d'exécution : 10,37 ms sans ScaNN et 0,87 ms avec ScaNN) avec l'index. Pour en savoir plus à ce sujet, consultez cet article de blog.

8. Validation des correspondances avec le LLM

Avant de passer à la création d'un service permettant de renvoyer les meilleures correspondances à une application, utilisons un modèle d'IA générative pour valider si ces réponses potentielles sont réellement pertinentes et sûres à partager avec l'utilisateur.

S'assurer que l'instance est configurée pour Gemini

Vérifiez d'abord si l'intégration Google ML est déjà activée pour votre cluster et votre instance. Dans AlloyDB Studio, saisissez la commande suivante :

show google_ml_integration.enable_model_support;

Si la valeur est activée, vous pouvez ignorer les deux étapes suivantes et passer directement à la configuration de l'intégration d'AlloyDB et de Vertex AI Model.

  1. Accédez à l'instance principale de votre cluster AlloyDB, puis cliquez sur MODIFIER L'INSTANCE PRINCIPALE.

cb76b934ba3735bd.png

  1. Accédez à la section "Options" dans les options de configuration avancées. et assurez-vous que google_ml_integration.enable_model_support flag est défini sur "on", comme indiqué ci-dessous :

6a59351fcd2a9d35.png

Si elle n'est pas activée, activez-la, puis cliquez sur le bouton UPDATE INSTANCE (METTRE À JOUR L'INSTANCE). Cette étape prendra quelques minutes.

Intégration d'AlloyDB et de Vertex AI Model

Vous pouvez maintenant vous connecter à AlloyDB Studio et exécuter l'instruction DML suivante pour configurer l'accès au modèle Gemini depuis AlloyDB, en utilisant votre ID de projet là où il est indiqué. Vous pouvez recevoir un avertissement concernant une erreur de syntaxe avant d'exécuter la commande, mais elle devrait s'exécuter correctement.

Tout d'abord, nous créons la connexion au modèle Gemini 1.5, comme indiqué ci-dessous. N'oubliez pas de remplacer $PROJECT_ID dans la commande ci-dessous par l'ID de votre projet 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');

Vous pouvez vérifier les modèles configurés pour l'accès à l'aide de la commande suivante dans AlloyDB Studio :

select model_id,model_type from google_ml.model_info_view;        

Enfin, nous devons accorder aux utilisateurs de la base de données l'autorisation d'exécuter la fonction ml_predict_row pour effectuer des prédictions à l'aide des modèles Google Vertex AI. Exécutez la commande suivante :

GRANT EXECUTE ON FUNCTION ml_predict_row to postgres;

Remarque : Si vous utilisez un projet Google Cloud existant et un cluster/une instance AlloyDB existants créés il y a quelque temps, vous devrez peut-être supprimer les anciennes références au modèle gemini-1.5 et les recréer avec l'instruction CALL ci-dessus. Exécutez à nouveau la commande "grant execute on function ml_predict_row" en cas de problème lors des prochaines invocations de gemini-1.5.

Évaluer les réponses

Bien que nous finissions par utiliser une grande requête dans la section suivante pour nous assurer que les réponses de la requête sont raisonnables, la requête peut être difficile à comprendre. Nous allons examiner les éléments maintenant et voir comment ils s'assemblent dans quelques minutes.

  1. Nous allons d'abord envoyer une requête à la base de données pour obtenir les 10 correspondances les plus proches d'une requête utilisateur.
  2. Pour déterminer la validité des réponses, nous utiliserons une requête externe dans laquelle nous expliquerons comment évaluer les réponses. Il utilise le champ recommended_text (texte de recherche) et content (champ de description du jouet) de la table interne dans la requête.
  3. Nous l'utiliserons ensuite pour évaluer la qualité des réponses renvoyées.
  4. predict_row renvoie son résultat au format JSON. Le code -> 'candidates' -> 0 -> 'content' -> 'parts' -> 0 -> 'text'" est utilisé pour extraire le texte réel de ce JSON. Pour afficher le code JSON renvoyé, vous pouvez supprimer ce code.
  5. Enfin, pour obtenir la réponse du LLM, nous l'extrayons à l'aide de 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 1) 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%';

Bien que cela puisse encore sembler intimidant, nous espérons que vous y voyez un peu plus clair. Les résultats indiquent s'il y a une correspondance ou non, le pourcentage de correspondance et une explication de la classification.

Notez que le modèle Gemini est configuré par défaut pour le streaming. La réponse réelle est donc répartie sur plusieurs lignes :

c2b006aeb3f3a2fc.png

9. Migrer l'application de recherche de jouets vers le cloud sans serveur

Prêt à déployer cette application sur le Web ? Pour rendre ce moteur de connaissances sans serveur avec Cloud Run Functions, procédez comme suit :

  1. Accédez à Cloud Run Functions dans la console Google Cloud pour CRÉER une fonction Cloud Run ou utilisez le lien https://console.cloud.google.com/functions/add.
  2. Sélectionnez l'environnement Fonction Cloud Run. Indiquez le nom de la fonction get-toys-alloydb et sélectionnez la région "us-central1". Définissez l'authentification sur "Autoriser les appels non authentifiés", puis cliquez sur SUIVANT. Choisissez Java 17 comme environnement d'exécution et Éditeur intégré pour le code source.
  3. Par défaut, le point d'entrée est défini sur "gcfv2.HelloHttpFunction". Remplacez le code de l'espace réservé dans HelloHttpFunction.java et pom.xml de votre fonction Cloud Run par le code de HelloHttpFunction.java et pom.xml, respectivement.
  4. N'oubliez pas de remplacer l'espace réservé <<YOUR_PROJECT>> et les identifiants de connexion AlloyDB par vos valeurs dans le fichier Java. Les identifiants AlloyDB sont ceux que nous avons utilisés au début de cet atelier de programmation. Si vous avez utilisé des valeurs différentes, veuillez les modifier dans le fichier Java.
  5. Cliquez sur Déployer.

Une fois la fonction Cloud déployée, nous allons créer le connecteur VPC pour lui permettre d'accéder à notre instance de base de données AlloyDB.

ÉTAPE IMPORTANTE :

Une fois le déploiement lancé, vous devriez pouvoir voir les fonctions dans la console Cloud Run Functions de Google. Recherchez la fonction que vous venez de créer (get-toys-alloydb), cliquez dessus, puis sur MODIFIER et modifiez les éléments suivants :

  1. Accéder aux paramètres d'exécution, de compilation, de connexion et de sécurité
  2. Augmentez le délai avant expiration à 180 secondes.
  3. Accédez à l'onglet "CONNEXIONS" :

4e83ec8a339cda08.png

  1. Sous les paramètres d'entrée, assurez-vous que l'option "Autoriser tout le trafic" est sélectionnée.
  2. Sous les paramètres de sortie, cliquez sur le menu déroulant "Réseau", sélectionnez l'option "Ajouter un connecteur VPC", puis suivez les instructions qui s'affichent dans la boîte de dialogue qui s'ouvre :

8126ec78c343f199.png

  1. Donnez un nom au connecteur VPC et assurez-vous que la région est la même que celle de votre instance. Laissez la valeur du réseau par défaut et définissez le sous-réseau sur "Plage d'adresses IP personnalisée" avec la plage d'adresses IP 10.8.0.0 ou une plage similaire disponible.
  2. Développez AFFICHER LES PARAMÈTRES DE SCALING et assurez-vous que la configuration est exactement la suivante :

7baf980463a86a5c.png

  1. Cliquez sur CRÉER. Le connecteur devrait maintenant figurer dans les paramètres de sortie.
  2. Sélectionnez le connecteur que vous venez de créer.
  3. Choisissez d'acheminer tout le trafic via ce connecteur VPC.
  4. Cliquez sur SUIVANT, puis sur DÉPLOYER.

10. Tester la fonction Cloud Run

Une fois la fonction Cloud mise à jour déployée, le point de terminaison généré devrait s'afficher. Copiez-le et remplacez-le dans la commande suivante :

Vous pouvez également tester la fonction Cloud Run comme suit :

PROJECT_ID=$(gcloud config get-value project)

curl -X POST <<YOUR_ENDPOINT>> \
  -H 'Content-Type: application/json' \
  -d '{"search":"I want a standing pink panther toy"}' \
  | jq .

Résultat :

23861e9091565a64.png

Et voilà ! Il est aussi simple d'effectuer une recherche vectorielle de similarité à l'aide du modèle Embeddings sur les données AlloyDB.

11. Créer le client d'application Web

Dans cette partie, nous allons créer une application Web permettant à l'utilisateur d'interagir et de trouver des jouets correspondants en fonction du texte, de l'image et même de créer un nouveau jouet en fonction de ses besoins. Comme l'application est déjà créée, vous pouvez suivre les étapes ci-dessous pour la copier dans votre IDE et la faire fonctionner.

  1. Comme nous utilisons Gemini 2.0 Flash pour décrire l'image que l'utilisateur peut importer afin de trouver des jouets correspondants, nous devons obtenir la clé API pour cette application. Pour ce faire, accédez à https://aistudio.google.com/apikey, obtenez la clé API pour votre projet Google Cloud actif dans lequel vous implémentez cette application, puis enregistrez la clé quelque part :

ae2db169e6a94e4a.png

  1. Accéder au terminal Cloud Shell
  2. Clonez le dépôt à l'aide de la commande suivante :
git clone https://github.com/AbiramiSukumaran/toysearch

cd toysearch
  1. Une fois le dépôt cloné, vous devriez pouvoir accéder au projet depuis votre éditeur Cloud Shell.
  2. Vous devez supprimer les dossiers "get-toys-alloydb" et "toolbox-toys" du projet cloné, car il s'agit de code Cloud Run Functions auquel vous pouvez faire référence à partir du dépôt lorsque vous en avez besoin.
  3. Accédez à GenerateToy.java dans le dossier Web, recherchez la ligne suivante et supprimez-la, car l'autorisation de contenu pour adultes peut nécessiter des autorisations spéciales qui ne sont pas disponibles pour certains comptes de facturation d'essai :

paramsMap.put("personGeneration", "allow_adult");

  1. Assurez-vous que toutes les variables d'environnement nécessaires sont définies avant de compiler et de déployer l'application. Accédez au terminal Cloud Shell et exécutez la commande suivante :
PROJECT_ID=$(gcloud config get-value project)

export PROJECT_ID=$PROJECT_ID

export GOOGLE_API_KEY=<YOUR API KEY that you saved>
  1. Créez et exécutez l'application en local :

Assurez-vous d'être dans le répertoire du projet, puis exécutez les commandes suivantes :

mvn package

mvn spring-boot:run 
  1. Déployer sur Cloud Run
gcloud run deploy --source .

12. Comprendre les détails de l'IA générative

Aucune action de votre part n'est requise. Pour votre information :

Maintenant que vous avez l'application à déployer, prenez le temps de comprendre comment nous avons effectué la recherche (texte et image) et la génération.

  1. Recherche vectorielle basée sur le texte de l'utilisateur :

Ce problème est déjà résolu dans les fonctions Cloud Run que nous avons déployées dans la section "Déployer l'application Web Vector Search".

  1. Recherche vectorielle basée sur l'importation d'images :

Au lieu de saisir le contexte sous forme de texte, imaginons que l'utilisateur souhaite importer une photo d'un jouet familier avec lequel il veut effectuer une recherche. Les utilisateurs peuvent importer une image d'un jouet qui leur plaît et obtenir des fonctionnalités pertinentes.

Nous utilisons le modèle Gemini 2.0 Flash de Google, invoqué à l'aide de LangChain4j, pour analyser l'image et extraire le contexte pertinent, comme la couleur, le matériau, le type et la tranche d'âge du jouet.

En cinq étapes seulement, nous avons transformé les données multimodales saisies par l'utilisateur en résultats correspondants grâce à l'invocation d'un grand modèle linguistique à l'aide d'un framework Open Source. Vous apprendrez ce qui suit :

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. Découvrez comment nous avons utilisé Imagen 3 pour créer un jouet personnalisé en fonction de la demande de l'utilisateur avec l'IA générative.

Imagen 3 génère ensuite une image du jouet personnalisé, ce qui permet à l'utilisateur de visualiser clairement sa création. Voici comment nous avons procédé en cinq étapes :

// 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();
    }
  }

Prédiction des prix

Dans la section précédente, nous avons vu comment Imagen génère l'image d'un jouet que l'utilisateur souhaite concevoir lui-même. Pour que les utilisateurs puissent l'acheter, l'application doit définir un prix. Nous avons utilisé une logique intuitive pour définir le prix du jouet personnalisé sur commande. La logique consiste à utiliser le prix moyen des cinq jouets les plus proches (en termes de description) du jouet conçu par l'utilisateur.

La prédiction du prix du jouet généré est un élément important de cette application. Nous avons utilisé une approche agentique pour la générer. Découvrez la boîte à outils d'IA générative pour les bases de données.

13. Boîte à outils d'IA générative pour les bases de données

La boîte à outils d'IA générative pour les bases de données est un serveur Open Source de Google qui facilite la création d'outils d'IA générative pour interagir avec les bases de données. Il vous permet de développer des outils plus facilement, plus rapidement et de manière plus sécurisée en gérant les complexités telles que le regroupement de connexions, l'authentification, etc. Il vous aide à créer des outils d'IA générative qui permettent à vos agents d'accéder aux données de votre base de données.

Voici la procédure à suivre pour configurer votre outil et rendre votre application agentique : Atelier de programmation sur la boîte à outils

Votre application peut désormais utiliser ce point de terminaison de fonction Cloud Run déployé pour renseigner le prix ainsi que le résultat Imagen généré pour l'image du jouet personnalisé sur commande.

14. Tester votre application Web

Maintenant que tous les composants de votre application sont créés et déployés, elle est prête à être diffusée dans le cloud. Testez votre application pour tous les scénarios. Voici un lien vers une vidéo qui vous donnera une idée de ce à quoi vous pouvez vous attendre :

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

Voici à quoi ressemble la page de destination :

241db19e7176e93e.png

15. Effectuer un nettoyage

Pour éviter que les ressources utilisées dans cet article soient facturées sur votre compte Google Cloud, procédez comme suit :

  1. Dans la console Google Cloud, accédez à la page Gérer les ressources.
  2. Dans la liste des projets, sélectionnez le projet que vous souhaitez supprimer, puis cliquez sur Supprimer.
  3. Dans la boîte de dialogue, saisissez l'ID du projet, puis cliquez sur Arrêter pour supprimer le projet.

16. Félicitations

Félicitations ! Vous avez effectué une recherche et une génération contextuelles dans un magasin de jouets à l'aide d'AlloyDB, pgvector, Imagen et Gemini 2.0, tout en tirant parti des bibliothèques Open Source pour créer des intégrations robustes. En combinant les capacités d'AlloyDB, de Vertex AI et de Vector Search, nous avons fait un grand pas en avant pour rendre les recherches contextuelles et vectorielles accessibles, efficaces et réellement axées sur le sens.