1. Introduction
Présentation
Dans cet atelier de programmation, vous allez apprendre à créer un chatbot basique écrit dans un nœud à l'aide de l'API Gemini Vertex AI et de la bibliothèque cliente Vertex AI. Cette application utilise un magasin de sessions Express reposant sur Google Cloud Firestore.
Points abordés
- Utiliser htmx, tailwindcss et express.js pour créer un service Cloud Run
- Utiliser les bibliothèques clientes de Vertex AI pour s'authentifier auprès des API Google
- Créer un chatbot pour interagir avec le modèle Gemini
- Déployer sur un service Cloud Run sans fichier Docker
- Utiliser un magasin de session Express sauvegardé par Google Cloud Firestore
2. Préparation
Prérequis
- Vous êtes connecté à la console Cloud.
- Vous avez déjà déployé un service Cloud Run. Par exemple, vous pouvez suivre le guide de démarrage rapide pour déployer un service Web depuis le code source.
Activer Cloud Shell
- Dans Cloud Console, cliquez sur Activer Cloud Shell .
Si vous démarrez Cloud Shell pour la première fois, un écran intermédiaire vous explique de quoi il s'agit. Si un écran intermédiaire s'est affiché, cliquez sur Continuer.
Le provisionnement et la connexion à Cloud Shell ne devraient pas prendre plus de quelques minutes.
Cette machine virtuelle contient tous les outils de développement nécessaires. Elle comprend un répertoire d'accueil persistant de 5 Go et s'exécute dans Google Cloud, ce qui améliore considérablement les performances du réseau et l'authentification. Une grande partie, voire la totalité, de votre travail dans cet atelier de programmation peut être effectué dans un navigateur.
Une fois connecté à Cloud Shell, vous êtes authentifié et le projet est défini sur votre ID de projet.
- Exécutez la commande suivante dans Cloud Shell pour vérifier que vous êtes authentifié :
gcloud auth list
Résultat de la commande
Credentialed Accounts ACTIVE ACCOUNT * <my_account>@<my_domain.com> To set the active account, run: $ gcloud config set account `ACCOUNT`
- Exécutez la commande suivante dans Cloud Shell pour vérifier que la commande gcloud connaît votre projet:
gcloud config list project
Résultat de la commande
[core] project = <PROJECT_ID>
Si vous obtenez un résultat différent, exécutez cette commande :
gcloud config set project <PROJECT_ID>
Résultat de la commande
Updated property [core/project].
3. Activer les API et définir des variables d'environnement
Activer les API
Avant de commencer à utiliser cet atelier de programmation, vous devez activer plusieurs API. Cet atelier de programmation nécessite l'utilisation des API suivantes. Vous pouvez activer ces API en exécutant la commande suivante:
gcloud services enable run.googleapis.com \ cloudbuild.googleapis.com \ aiplatform.googleapis.com \ secretmanager.googleapis.com
Configurer des variables d'environnement
Vous pouvez définir les variables d'environnement qui seront utilisées tout au long de cet atelier de programmation.
PROJECT_ID=<YOUR_PROJECT_ID> REGION=<YOUR_REGION, e.g. us-central1> SERVICE=chat-with-gemini SERVICE_ACCOUNT="vertex-ai-caller" SERVICE_ACCOUNT_ADDRESS=$SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com SECRET_ID="SESSION_SECRET"
4. Créer et configurer un projet Firebase
- Dans la console Firebase, cliquez sur Ajouter un projet.
- Saisissez <YOUR_PROJECT_ID>. pour ajouter Firebase à l'un de vos projets Google Cloud existants
- Si vous y êtes invité, lisez et acceptez les Conditions d'utilisation de Firebase.
- Cliquez sur Continuer.
- Cliquez sur Confirmer le mode de facturation pour confirmer le mode de facturation Firebase.
- L'activation de Google Analytics pour cet atelier de programmation est facultative.
- Cliquez sur Ajouter Firebase :
- Une fois le projet créé, cliquez sur Continuer.
- Dans le menu Build (Compiler), cliquez sur Firestore Database (Base de données Firestore).
- Cliquez sur Créer une base de données.
- Choisissez votre région dans le menu déroulant Emplacement, puis cliquez sur Suivant.
- Utilisez l'option par défaut Start in production mode (Démarrer en mode production), puis cliquez sur Create (Créer).
5. Créer un compte de service
Ce compte de service permettra à Cloud Run d'appeler l'API Gemini Vertex AI. Ce compte de service sera également autorisé à lire et écrire dans Firestore, et à lire des secrets depuis Secret Manager.
Commencez par créer le compte de service en exécutant la commande suivante:
gcloud iam service-accounts create $SERVICE_ACCOUNT \ --display-name="Cloud Run to access Vertex AI APIs"
Ensuite, attribuez le rôle "Utilisateur Vertex AI" au compte de service.
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \ --role=roles/aiplatform.user
À présent, créez un secret dans Secret Manager. Le service Cloud Run accédera à ce secret en tant que variable d'environnement, ce qui est résolu au moment du démarrage de l'instance. Obtenez plus d'informations sur les secrets et Cloud Run.
gcloud secrets create $SECRET_ID --replication-policy="automatic" printf "keyboard-cat" | gcloud secrets versions add $SECRET_ID --data-file=-
Accordez au compte de service l'accès au secret de session Express dans Secret Manager.
gcloud secrets add-iam-policy-binding $SECRET_ID \ --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \ --role='roles/secretmanager.secretAccessor'
Enfin, accordez au compte de service un accès en lecture et en écriture à Firestore.
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \ --role=roles/datastore.user
6. Créer le service Cloud Run
Tout d'abord, créez un répertoire pour le code source et utilisez la commande cd pour y accéder.
mkdir chat-with-gemini && cd chat-with-gemini
Ensuite, créez un fichier package.json
avec le contenu suivant:
{ "name": "chat-with-gemini", "version": "1.0.0", "description": "", "main": "app.js", "scripts": { "start": "node app.js", "nodemon": "nodemon app.js", "cssdev": "npx tailwindcss -i ./input.css -o ./public/output.css --watch", "tailwind": "npx tailwindcss -i ./input.css -o ./public/output.css", "dev": "npm run tailwind && npm run nodemon" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "@google-cloud/connect-firestore": "^3.0.0", "@google-cloud/firestore": "^7.5.0", "@google-cloud/vertexai": "^0.4.0", "axios": "^1.6.8", "express": "^4.18.2", "express-session": "^1.18.0", "express-ws": "^5.0.2", "htmx.org": "^1.9.10" }, "devDependencies": { "nodemon": "^3.1.0", "tailwindcss": "^3.4.1" } }
Ensuite, créez un fichier source app.js
avec le contenu ci-dessous. Ce fichier contient le point d'entrée du service et la logique principale de l'application.
const express = require("express"); const app = express(); app.use(express.urlencoded({ extended: true })); app.use(express.json()); const path = require("path"); const fs = require("fs"); const util = require("util"); const { spinnerSvg } = require("./spinnerSvg.js"); // cloud run retrieves secret at instance startup time const secret = process.env.SESSION_SECRET; const { Firestore } = require("@google-cloud/firestore"); const { FirestoreStore } = require("@google-cloud/connect-firestore"); var session = require("express-session"); app.set("trust proxy", 1); // trust first proxy app.use( session({ store: new FirestoreStore({ dataset: new Firestore(), kind: "express-sessions" }), secret: secret, /* set secure to false for local dev session history testing */ /* see more at https://expressjs.com/en/resources/middleware/session.html */ cookie: { secure: true }, resave: false, saveUninitialized: true }) ); const expressWs = require("express-ws")(app); app.use(express.static("public")); // Vertex AI Section const { VertexAI } = require("@google-cloud/vertexai"); // instance of Vertex model let generativeModel; // on startup const port = parseInt(process.env.PORT) || 8080; app.listen(port, async () => { console.log(`demo1: listening on port ${port}`); // get project and location from metadata service const metadataService = require("./metadataService.js"); const project = await metadataService.getProjectId(); const location = await metadataService.getRegion(); // Vertex client library instance const vertex_ai = new VertexAI({ project: project, location: location }); // Instantiate models generativeModel = vertex_ai.getGenerativeModel({ model: "gemini-1.0-pro-001" }); }); app.ws("/sendMessage", async function (ws, req) { if (!req.session.chathistory || req.session.chathistory.length == 0) { req.session.chathistory = []; } let chatWithModel = generativeModel.startChat({ history: req.session.chathistory }); ws.on("message", async function (message) { console.log("req.sessionID: ", req.sessionID); // get session id let questionToAsk = JSON.parse(message).message; console.log("WebSocket message: " + questionToAsk); ws.send(`<div hx-swap-oob="beforeend:#toupdate"><div id="questionToAsk" class="text-black m-2 text-right border p-2 rounded-lg ml-24"> ${questionToAsk} </div></div>`); // to simulate a natural pause in conversation await sleep(500); // get timestamp for div to replace const now = "fromGemini" + Date.now(); ws.send(`<div hx-swap-oob="beforeend:#toupdate"><div id=${now} class=" text-blue-400 m-2 text-left border p-2 rounded-lg mr-24"> ${spinnerSvg} </div></div>`); const results = await chatWithModel.sendMessage(questionToAsk); const answer = results.response.candidates[0].content.parts[0].text; ws.send(`<div id=${now} hx-swap-oob="true" hx-swap="outerHTML" class="text-blue-400 m-2 text-left border p-2 rounded-lg mr-24"> ${answer} </div>`); // save to current chat history let userHistory = { role: "user", parts: [{ text: questionToAsk }] }; let modelHistory = { role: "model", parts: [{ text: answer }] }; req.session.chathistory.push(userHistory); req.session.chathistory.push(modelHistory); // console.log( // "newly saved chat history: ", // util.inspect(req.session.chathistory, { // showHidden: false, // depth: null, // colors: true // }) // ); req.session.save(); }); ws.on("close", () => { console.log("WebSocket was closed"); }); }); function sleep(ms) { return new Promise((resolve) => { setTimeout(resolve, ms); }); } // gracefully close the web sockets process.on("SIGTERM", () => { server.close(); });
Créez le fichier tailwind.config.js
pour tailwindCSS.
/** @type {import('tailwindcss').Config} */ module.exports = { content: ["./**/*.{html,js}"], theme: { extend: {} }, plugins: [] };
Créez le fichier metadataService.js
pour obtenir l'ID du projet et la région du service Cloud Run déployé. Ces valeurs serviront à instancier une instance des bibliothèques clientes Vertex AI.
const your_project_id = "YOUR_PROJECT_ID"; const your_region = "YOUR_REGION"; const axios = require("axios"); module.exports = { getProjectId: async () => { let project = ""; try { // Fetch the token to make a GCF to GCF call const response = await axios.get( "http://metadata.google.internal/computeMetadata/v1/project/project-id", { headers: { "Metadata-Flavor": "Google" } } ); if (response.data == "") { // running locally on Cloud Shell project = your_project_id; } else { // running on Clodu Run. Use project id from metadata service project = response.data; } } catch (ex) { // running locally on local terminal project = your_project_id; } return project; }, getRegion: async () => { let region = ""; try { // Fetch the token to make a GCF to GCF call const response = await axios.get( "http://metadata.google.internal/computeMetadata/v1/instance/region", { headers: { "Metadata-Flavor": "Google" } } ); if (response.data == "") { // running locally on Cloud Shell region = your_region; } else { // running on Clodu Run. Use region from metadata service let regionFull = response.data; const index = regionFull.lastIndexOf("/"); region = regionFull.substring(index + 1); } } catch (ex) { // running locally on local terminal region = your_region; } return region; } };
Créer un fichier intitulé spinnerSvg.js
module.exports.spinnerSvg = `<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-blue-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" > <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" ></circle> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" ></path></svg>`;
Enfin, créez un fichier input.css
pour tailwindCSS.
@tailwind base; @tailwind components; @tailwind utilities;
Maintenant, créez un répertoire public
.
mkdir public cd public
Dans ce répertoire public, créez le fichier index.html
du frontal, qui utilisera htmx.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <script src="https://unpkg.com/htmx.org@1.9.10" integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC" crossorigin="anonymous" ></script> <link href="./output.css" rel="stylesheet" /> <script src="https://unpkg.com/htmx.org/dist/ext/ws.js"></script> <title>Demo 1</title> </head> <body> <div id="herewego" text-center> <!-- <div id="replaceme2" hx-swap-oob="true">Hello world</div> --> <div class="container mx-auto mt-8 text-center max-w-screen-lg" > <div class="overflow-y-scroll bg-white p-2 border h-[500px] space-y-4 rounded-lg m-auto" > <div id="toupdate"></div> </div> <form hx-trigger="submit, keyup[keyCode==13] from:body" hx-ext="ws" ws-connect="/sendMessage" ws-send="" hx-on="htmx:wsAfterSend: document.getElementById('message').value = ''" > <div class="mb-6 mt-6 flex gap-4"> <textarea rows="2" type="text" id="message" name="message" class="block grow rounded-lg border p-6 resize-none" required > Is C# a programming language or a musical note?</textarea > <button type="submit" class="bg-blue-500 text-white px-4 py-2 rounded-lg text-center text-sm font-medium" > Send </button> </div> </form> </div> </div> </body> </html>
7. Exécuter le service en local
Tout d'abord, vérifiez que vous vous trouvez bien dans le répertoire racine chat-with-gemini
de votre atelier de programmation.
cd .. && pwd
Ensuite, installez les dépendances en exécutant la commande suivante:
npm install
Utiliser ADC en cas d'exécution locale
Si vous exécutez Cloud Shell, vous exécutez déjà le projet sur une machine virtuelle Google Compute Engine. Vos identifiants associés à cette machine virtuelle (comme indiqué en exécutant gcloud auth list
) seront automatiquement utilisés par les identifiants par défaut de l'application. Il n'est donc pas nécessaire d'exécuter la commande gcloud auth application-default login
. Vous pouvez passer à la section Créer un secret de session locale.
Toutefois, si vous exécutez l'application sur votre terminal local (c'est-à-dire sans passer par Cloud Shell), vous devez utiliser les identifiants par défaut de l'application pour vous authentifier auprès des API Google. Vous pouvez 1) vous connecter à l'aide de vos identifiants (à condition de disposer des rôles Utilisateur Vertex AI et Utilisateur Datastore) ou 2) vous connecter en usurpant l'identité du compte de service utilisé dans cet atelier de programmation.
Option 1 : Utiliser vos identifiants pour l'ADC
Si vous souhaitez utiliser vos identifiants, vous pouvez d'abord exécuter gcloud auth list
pour vérifier votre authentification dans gcloud. Ensuite, vous devrez peut-être attribuer à votre identité le rôle "Utilisateur Vertex AI". Si votre identité dispose du rôle Propriétaire, vous disposez déjà de ce rôle utilisateur Vertex AI. Si ce n'est pas le cas, vous pouvez exécuter cette commande pour attribuer à votre identité les rôles d'utilisateur Vertex AI et d'utilisateur Datastore.
USER=<YOUR_PRINCIPAL_EMAIL> gcloud projects add-iam-policy-binding $PROJECT_ID \ --member user:$USER \ --role=roles/aiplatform.user gcloud projects add-iam-policy-binding $PROJECT_ID \ --member user:$USER \ --role=roles/datastore.user
Exécutez ensuite la commande suivante :
gcloud auth application-default login
Option 2 : Emprunter l'identité d'un compte de service pour ADC
Si vous souhaitez utiliser le compte de service créé dans cet atelier de programmation, votre compte utilisateur doit disposer du rôle Créateur de jetons du compte de service. Vous pouvez obtenir ce rôle en exécutant la commande suivante:
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member user:$USER \ --role=roles/iam.serviceAccountTokenCreator
Ensuite, vous exécuterez la commande suivante pour utiliser ADC avec le compte de service
gcloud auth application-default login --impersonate-service-account=$SERVICE_ACCOUNT_ADDRESS
Créer un secret de session locale
À présent, créez un secret de session locale pour le développement local.
export SESSION_SECRET=local-secret
Exécuter l'application en local
Enfin, vous pouvez démarrer l'application en exécutant le script suivant. Ce script générera également le fichier output.css à partir de tailwindCSS.
npm run dev
Pour prévisualiser le site Web, ouvrez le bouton "Aperçu sur le Web", puis sélectionnez le port d'aperçu 8080
8. Déployer le service
Commencez par exécuter cette commande pour démarrer le déploiement et spécifier le compte de service à utiliser. Si aucun compte de service n'est spécifié, le compte de service Compute par défaut est utilisé.
gcloud run deploy $SERVICE \ --service-account $SERVICE_ACCOUNT_ADDRESS \ --source . \ --region $REGION \ --allow-unauthenticated \ --set-secrets="SESSION_SECRET=$(echo $SECRET_ID):1"
Si le message "Le déploiement depuis la source nécessite un dépôt Docker Artifact Registry pour stocker les conteneurs créés : Un dépôt nommé [cloud-run-source-deploy], situé dans la région [us-central1], sera créé.", appuyez sur "y". pour accepter et continuer.
9. Tester le service
Une fois le service déployé, ouvrez l'URL du service dans votre navigateur Web. Ensuite, posez une question à Gemini, par exemple : "Je joue de la guitare, mais je suis aussi ingénieur logiciel. Quand je vois "C#", dois-je le considérer comme un langage de programmation ou comme une note de musique ? Lequel dois-je choisir ?"
10. Félicitations !
Félicitations ! Vous avez terminé cet atelier de programmation.
Nous vous recommandons de consulter la documentation sur les API Cloud Run et Vertex AI Gemini.
Points abordés
- Utiliser htmx, tailwindcss et express.js pour créer un service Cloud Run
- Utiliser les bibliothèques clientes de Vertex AI pour s'authentifier auprès des API Google
- Créer un chatbot pour interagir avec le modèle Gemini
- Déployer sur un service Cloud Run sans fichier Docker
- Utiliser un magasin de session Express sauvegardé par Google Cloud Firestore
11. Effectuer un nettoyage
Pour éviter des frais accidentels (par exemple, si les services Cloud Run sont invoqués plus de fois que l'allocation mensuelle des appels Cloud Run dans le niveau sans frais), vous pouvez supprimer Cloud Run ou le projet créé à l'étape 2.
Pour supprimer le service Cloud Run, accédez à la console Cloud de Cloud Run à l'adresse https://console.cloud.google.com/run et supprimez le service chat-with-gemini
. Vous pouvez également supprimer le compte de service vertex-ai-caller
ou révoquer le rôle "Utilisateur Vertex AI" pour éviter tout appel involontaire à Gemini.
Si vous choisissez de supprimer l'intégralité du projet, vous pouvez accéder à https://console.cloud.google.com/cloud-resource-manager, sélectionner le projet que vous avez créé à l'étape 2, puis cliquer sur "Supprimer". Si vous supprimez le projet, vous devrez le modifier dans Cloud SDK. Vous pouvez afficher la liste de tous les projets disponibles en exécutant gcloud projects list
.