1. Introducción
Descripción general
En este codelab, verás cómo puedes crear un chat bot básico escrito en un nodo usando la API de Vertex AI Gemini y la biblioteca cliente de Vertex AI. Esta app usa un almacén de sesiones de Express respaldado por Google Cloud Firestore.
Qué aprenderás
- Cómo usar htmx, tailwindcss y express.js para compilar un servicio de Cloud Run
- Cómo usar las bibliotecas cliente de Vertex AI para autenticarte en las APIs de Google
- Cómo crear un chatbot para interactuar con el modelo de Gemini
- Cómo implementar en un servicio de Cloud Run sin un archivo Docker
- Cómo usar un almacén de sesiones exprés respaldado por Google Cloud Firestore
2. Configuración y requisitos
Requisitos previos
- Accediste a la consola de Cloud.
- Ya implementaste un servicio de Cloud Run. Por ejemplo, puedes seguir la guía de inicio rápido para implementar un servicio web desde el código fuente para comenzar.
Activar Cloud Shell
- En la consola de Cloud, haz clic en Activar Cloud Shell.
Si es la primera vez que inicias Cloud Shell, verás una pantalla intermedia que describe en qué consiste. Si apareció una pantalla intermedia, haz clic en Continuar.
El aprovisionamiento y la conexión a Cloud Shell solo tomará unos minutos.
Esta máquina virtual está cargada con todas las herramientas de desarrollo necesarias. Ofrece un directorio principal persistente de 5 GB y se ejecuta en Google Cloud, lo que mejora considerablemente el rendimiento de la red y la autenticación. Gran parte de tu trabajo en este codelab, si no todo, se puede hacer con un navegador.
Una vez que te conectes a Cloud Shell, deberías ver que estás autenticado y que el proyecto está configurado con tu ID del proyecto.
- En Cloud Shell, ejecuta el siguiente comando para confirmar que tienes la autenticación:
gcloud auth list
Resultado del comando
Credentialed Accounts ACTIVE ACCOUNT * <my_account>@<my_domain.com> To set the active account, run: $ gcloud config set account `ACCOUNT`
- Ejecuta el siguiente comando en Cloud Shell para confirmar que el comando de gcloud conoce tu proyecto:
gcloud config list project
Resultado del comando
[core] project = <PROJECT_ID>
De lo contrario, puedes configurarlo con el siguiente comando:
gcloud config set project <PROJECT_ID>
Resultado del comando
Updated property [core/project].
3. Habilita las APIs y configura las variables de entorno
Habilita las APIs
Antes de comenzar a usar este codelab, debes habilitar varias APIs. Este codelab requiere el uso de las siguientes APIs. Para habilitar esas APIs, ejecuta el siguiente comando:
gcloud services enable run.googleapis.com \ cloudbuild.googleapis.com \ aiplatform.googleapis.com \ secretmanager.googleapis.com
Configura variables de entorno
Puedes establecer variables de entorno que se usarán en este codelab.
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. Crea y configura un proyecto de Firebase
- En Firebase console, haz clic en Agregar proyecto.
- Ingresa <YOUR_PROJECT_ID> para agregar Firebase a uno de tus proyectos de Google Cloud existentes
- Si se te solicita, revisa y acepta las condiciones de Firebase.
- Haz clic en Continuar.
- Haz clic en Confirmar plan para confirmar el plan de facturación de Firebase.
- Habilitar Google Analytics para este codelab es opcional.
- Haz clic en Agregar Firebase:
- Cuando se haya creado el proyecto, haz clic en Continuar.
- En el menú Compilar, haz clic en Base de datos de Firestore.
- Haz clic en Crear base de datos.
- Elige tu región en el menú desplegable Ubicación y haz clic en Siguiente.
- Usa la opción predeterminada Comenzar en modo de producción y, luego, haz clic en Crear.
5. Crea una cuenta de servicio
Cloud Run usará esta cuenta de servicio para llamar a la API de Gemini en Vertex AI. Esta cuenta de servicio también tendrá permisos de lectura y escritura en Firestore, y de lectura de secretos desde Secret Manager.
Primero, ejecuta este comando para crear la cuenta de servicio:
gcloud iam service-accounts create $SERVICE_ACCOUNT \ --display-name="Cloud Run to access Vertex AI APIs"
Segundo, otorga el rol de usuario de Vertex AI a la cuenta de servicio.
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \ --role=roles/aiplatform.user
Ahora, crea un Secret en Secret Manager. El servicio de Cloud Run accederá a este Secret como una variable de entorno, que se resuelve al momento de iniciar la instancia. Puedes obtener más información sobre los secretos y Cloud Run.
gcloud secrets create $SECRET_ID --replication-policy="automatic" printf "keyboard-cat" | gcloud secrets versions add $SECRET_ID --data-file=-
Otorga a la cuenta de servicio acceso al secreto de la sesión exprés en Secret Manager.
gcloud secrets add-iam-policy-binding $SECRET_ID \ --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \ --role='roles/secretmanager.secretAccessor'
Por último, otorga a la cuenta de servicio acceso de lectura y escritura a Firestore.
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \ --role=roles/datastore.user
6. Crea el servicio de Cloud Run
Primero, crea un directorio para el código fuente y desplázate a ese directorio con el comando cd.
mkdir chat-with-gemini && cd chat-with-gemini
Luego, crea un archivo package.json
con el siguiente contenido:
{ "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" } }
A continuación, crea un archivo fuente app.js
con el siguiente contenido. Este archivo contiene el punto de entrada del servicio y la lógica principal de la app.
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(); });
Crea el archivo tailwind.config.js
para tailwindCSS.
/** @type {import('tailwindcss').Config} */ module.exports = { content: ["./**/*.{html,js}"], theme: { extend: {} }, plugins: [] };
Crea el archivo metadataService.js
para obtener el ID del proyecto y la región del servicio implementado de Cloud Run. Estos valores se usarán para crear una instancia de las bibliotecas cliente de 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; } };
Crea un archivo llamado 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>`;
Por último, crea un archivo input.css
para tailwindCSS.
@tailwind base; @tailwind components; @tailwind utilities;
Ahora, crea un directorio public
nuevo.
mkdir public cd public
En ese directorio público, crea el archivo index.html
para el frontend, que usará 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. Ejecuta el servicio de manera local
Primero, asegúrate de estar en el directorio raíz chat-with-gemini
de tu codelab.
cd .. && pwd
Luego, ejecuta el siguiente comando para instalar las dependencias:
npm install
Usa ADC en la ejecución local
Si estás ejecutando en Cloud Shell, ya estás ejecutando en una máquina virtual de Google Compute Engine. Las credenciales predeterminadas de la aplicación usarán automáticamente tus credenciales asociadas con esta máquina virtual (como se muestra cuando se ejecuta gcloud auth list
), por lo que no es necesario usar el comando gcloud auth application-default login
. Puedes pasar a la sección Crea un secreto de sesión local.
Sin embargo, si estás ejecutando en tu terminal local (es decir, no en Cloud Shell), deberás usar las credenciales predeterminadas de la aplicación para autenticarte en las APIs de Google. Puedes 1) acceder con tus credenciales (siempre que tengas roles de usuario de Vertex AI y usuario de Datastore) o 2) acceder usando la identidad de la cuenta de servicio que se usa en este codelab.
Opción 1) Usa tus credenciales para ADC
Si quieres usar tus credenciales, primero puedes ejecutar gcloud auth list
para verificar cómo te autenticas en gcloud. A continuación, es posible que debas otorgar a tu identidad el rol de usuario de Vertex AI. Si tu identidad tiene el rol de propietario, ya tienes este rol de usuario de Vertex AI. De lo contrario, puedes ejecutar este comando para otorgar a tu identidad el rol de usuario de Vertex AI y el rol de usuario de 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
Luego, ejecuta el siguiente comando:
gcloud auth application-default login
Opción 2: Suplantación de identidad de una cuenta de servicio para ADC
Si deseas usar la cuenta de servicio que se creó en este codelab, tu cuenta de usuario deberá tener el rol de creador de tokens de cuentas de servicio. Para obtener este rol, ejecuta el siguiente comando:
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member user:$USER \ --role=roles/iam.serviceAccountTokenCreator
Luego, ejecutarás el siguiente comando para usar ADC con la cuenta de servicio
gcloud auth application-default login --impersonate-service-account=$SERVICE_ACCOUNT_ADDRESS
Crea un secreto de sesión local
Ahora, crea un secreto de sesión local para el desarrollo local.
export SESSION_SECRET=local-secret
Ejecuta la app de forma local
Por último, puedes iniciar la app ejecutando la siguiente secuencia de comandos. Esta secuencia de comandos también generará el archivo output.css desde tailwindCSS.
npm run dev
Para obtener una vista previa del sitio web, abre el botón Vista previa en la Web y selecciona Vista previa del puerto 8080.
8. Implemente el servicio
Primero, ejecuta este comando para iniciar la implementación y especifica la cuenta de servicio que se usará. Si no se especifica una cuenta de servicio, se usa la cuenta de servicio de procesamiento predeterminada.
gcloud run deploy $SERVICE \ --service-account $SERVICE_ACCOUNT_ADDRESS \ --source . \ --region $REGION \ --allow-unauthenticated \ --set-secrets="SESSION_SECRET=$(echo $SECRET_ID):1"
Si aparece el mensaje “La implementación desde el código fuente requiere un repositorio de Docker de Artifact Registry para almacenar contenedores compilados. Se creará un repositorio llamado [cloud-run-source-deploy] en la región [us-central1].", presiona “y” aceptar y continuar.
9. Prueba el servicio
Una vez implementado, abre la URL del servicio en tu navegador web. Luego, hazle una pregunta a Gemini, p.ej., "Practico guitarra, pero también soy ingeniera de software. Cuando veo "C#", ¿debo considerarlo como un lenguaje de programación o una nota musical? ¿Cuál debería elegir?".
10. ¡Felicitaciones!
¡Felicitaciones por completar el codelab!
Te recomendamos revisar la documentación de Cloud Run y las APIs de Vertex AI Gemini.
Temas abordados
- Cómo usar htmx, tailwindcss y express.js para compilar un servicio de Cloud Run
- Cómo usar las bibliotecas cliente de Vertex AI para autenticarte en las APIs de Google
- Cómo crear un chat bot para interactuar con el modelo de Gemini
- Cómo implementar en un servicio de Cloud Run sin un archivo Docker
- Cómo usar un almacén de sesiones exprés respaldado por Google Cloud Firestore
11. Limpia
Para evitar cargos involuntarios (por ejemplo, si los servicios de Cloud Run se invocan de forma involuntaria más veces que tu asignación mensual de invocación de Cloud Run en el nivel gratuito), puedes borrar Cloud Run o el proyecto que creaste en el paso 2.
Para borrar el servicio de Cloud Run, ve a la consola de Cloud Run en https://console.cloud.google.com/run y borra el servicio chat-with-gemini
. También puedes borrar la cuenta de servicio vertex-ai-caller
o revocar el rol del usuario de Vertex AI para evitar llamadas involuntarias a Gemini.
Si decides borrar el proyecto completo, puedes ir a https://console.cloud.google.com/cloud-resource-manager, seleccionar el proyecto que creaste en el paso 2 y elegir Borrar. Si borras el proyecto, deberás cambiar los proyectos en tu SDK de Cloud. Para ver la lista de todos los proyectos disponibles, ejecuta gcloud projects list
.