1. Introdução
Visão geral
Neste codelab, você vai aprender a criar um chatbot básico escrito em nó usando a API Gemini da Vertex AI e a biblioteca de cliente da Vertex AI. Este app usa um armazenamento de sessões do Express com suporte do Google Cloud Firestore.
O que você vai aprender
- Como usar htmx, tailwindcss e express.js para criar um serviço do Cloud Run
- Como usar as bibliotecas de cliente da Vertex AI para se autenticar nas APIs do Google
- Como criar um chatbot para interagir com o modelo Gemini
- Como implantar um serviço do Cloud Run sem um arquivo do Docker
- Como usar um armazenamento de sessão expresso com suporte do Google Cloud Firestore
2. Configuração e requisitos
Pré-requisitos
- Você fez login no console do Cloud.
- Você já implantou um serviço do Cloud Run. Por exemplo, siga o guia de início rápido sobre a implantação de um serviço da Web pelo código-fonte.
Ativar o Cloud Shell
- No Console do Cloud, clique em Ativar o Cloud Shell.
Se você estiver iniciando o Cloud Shell pela primeira vez, verá uma tela intermediária com a descrição dele. Se aparecer uma tela intermediária, clique em Continuar.
Leva apenas alguns instantes para provisionar e se conectar ao Cloud Shell.
Essa máquina virtual tem todas as ferramentas de desenvolvimento necessárias. Ela oferece um diretório principal persistente de 5 GB, além de ser executada no Google Cloud. Isso aprimora o desempenho e a autenticação da rede. Grande parte do trabalho neste codelab, se não todo, pode ser feito em um navegador.
Depois de se conectar ao Cloud Shell, você verá sua autenticação e o projeto estará configurado com o ID do seu projeto.
- Execute o seguinte comando no Cloud Shell para confirmar se a conta está autenticada:
gcloud auth list
Resposta ao comando
Credentialed Accounts ACTIVE ACCOUNT * <my_account>@<my_domain.com> To set the active account, run: $ gcloud config set account `ACCOUNT`
- Execute o seguinte comando no Cloud Shell para confirmar que o comando gcloud sabe sobre seu projeto:
gcloud config list project
Resposta ao comando
[core] project = <PROJECT_ID>
Se o projeto não estiver configurado, configure-o usando este comando:
gcloud config set project <PROJECT_ID>
Resposta ao comando
Updated property [core/project].
3. Ative APIs e defina variáveis de ambiente
Ativar APIs
Antes de começar a usar este codelab, você precisa ativar várias APIs. Este codelab requer o uso das APIs a seguir. É possível ativar essas APIs executando o seguinte comando:
gcloud services enable run.googleapis.com \ cloudbuild.googleapis.com \ aiplatform.googleapis.com \ secretmanager.googleapis.com
Configurar as variáveis de ambiente.
Você pode definir variáveis de ambiente que serão usadas neste 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. Criar e configurar um projeto do Firebase
- No Console do Firebase, clique em Adicionar projeto.
- Insira <YOUR_PROJECT_ID> para adicionar o Firebase a um projeto atual do Google Cloud
- Se solicitado, leia e aceite os Termos do Firebase.
- Clique em Continuar.
- Clique em Confirmar plano para confirmar o plano de faturamento do Firebase.
- A ativação do Google Analytics neste codelab é opcional.
- Clique em Adicionar Firebase:
- Quando o projeto estiver pronto, clique em Continuar.
- No menu Build, clique em Banco de dados do Firestore.
- Clique em Criar banco de dados.
- Escolha sua região no menu suspenso Local e clique em Próxima.
- Use a opção padrão Iniciar no modo de produção e depois clique em Criar.
5. Criar uma conta de serviço
Essa conta de serviço será usada pelo Cloud Run para chamar a API Gemini da Vertex AI. Essa conta de serviço também terá permissões para ler e gravar no Firestore e ler secrets do Secret Manager.
Primeiro, execute este comando para criar a conta de serviço:
gcloud iam service-accounts create $SERVICE_ACCOUNT \ --display-name="Cloud Run to access Vertex AI APIs"
Depois, conceda o papel de usuário da Vertex AI à conta de serviço.
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \ --role=roles/aiplatform.user
Agora, crie um secret no Secret Manager. O serviço do Cloud Run vai acessar esse secret como uma variável de ambiente, que é resolvida no momento da inicialização da instância. Saiba mais sobre os secrets e o Cloud Run.
gcloud secrets create $SECRET_ID --replication-policy="automatic" printf "keyboard-cat" | gcloud secrets versions add $SECRET_ID --data-file=-
E conceda à conta de serviço acesso ao secret de sessão expresso no Secret Manager.
gcloud secrets add-iam-policy-binding $SECRET_ID \ --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \ --role='roles/secretmanager.secretAccessor'
Por fim, conceda à conta de serviço acesso de leitura e gravação no Firestore.
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \ --role=roles/datastore.user
6. Criar o serviço do Cloud Run
Primeiro, crie um diretório para o código-fonte e use cd nele.
mkdir chat-with-gemini && cd chat-with-gemini
Em seguida, crie um arquivo package.json
com o seguinte conteúdo:
{ "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" } }
Em seguida, crie um arquivo de origem app.js
com o conteúdo abaixo. Esse arquivo contém o ponto de entrada para o serviço e a lógica principal do 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(); });
Crie o arquivo tailwind.config.js
para tailwindCSS.
/** @type {import('tailwindcss').Config} */ module.exports = { content: ["./**/*.{html,js}"], theme: { extend: {} }, plugins: [] };
Crie o arquivo metadataService.js
para receber o ID do projeto e a região do serviço implantado do Cloud Run. Esses valores serão usados para instanciar uma instância das bibliotecas de cliente da 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; } };
Crie um arquivo com o nome 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 fim, crie um arquivo input.css
para tailwindCSS.
@tailwind base; @tailwind components; @tailwind utilities;
Agora, crie um novo diretório public
.
mkdir public cd public
Dentro desse diretório público, crie o arquivo index.html
para o front-end, 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. Executar o serviço localmente
Primeiro, verifique se você está no diretório raiz chat-with-gemini
do seu codelab.
cd .. && pwd
Em seguida, instale as dependências executando o seguinte comando:
npm install
Como usar o ADC ao executar localmente
Se você estiver executando no Cloud Shell, já está em execução em uma máquina virtual do Google Compute Engine. Suas credenciais associadas a essa máquina virtual, conforme mostrado ao executar gcloud auth list
, serão usadas automaticamente pelo Application Default Credentials. Por isso, não é necessário usar o comando gcloud auth application-default login
. Pule para a seção Criar uma chave secreta de sessão local.
No entanto, se você estiver executando no seu terminal local (ou seja, não no Cloud Shell), será necessário usar o Application Default Credentials para se autenticar nas APIs do Google. Você pode 1) fazer login usando suas credenciais (desde que tenha os papéis de usuário da Vertex AI e do Datastore) ou 2) fazer login representando a conta de serviço usada neste codelab.
Opção 1: usar suas credenciais para o ADC
Se você quiser usar suas credenciais, primeiro execute gcloud auth list
para verificar como você está autenticado na gcloud. Em seguida, pode ser necessário conceder à sua identidade o papel de Usuário da Vertex AI. Caso sua identidade tenha o papel de Proprietário, você já tem esse papel do usuário da Vertex AI. Caso contrário, execute este comando para conceder à sua identidade o papel de usuário da Vertex AI e o papel de usuário do 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
Em seguida, execute o seguinte comando:
gcloud auth application-default login
Opção 2: Como representar uma conta de serviço para o ADC
Para usar a conta de serviço criada neste codelab, sua conta de usuário precisará ter o papel de Criador de token da conta de serviço. Você pode conseguir esse papel executando o seguinte comando:
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member user:$USER \ --role=roles/iam.serviceAccountTokenCreator
Em seguida, execute o comando a seguir para usar o ADC com a conta de serviço
gcloud auth application-default login --impersonate-service-account=$SERVICE_ACCOUNT_ADDRESS
Criar um secret da sessão local
Agora, crie um secret de sessão local para desenvolvimento local.
export SESSION_SECRET=local-secret
Executar o aplicativo localmente
Por fim, inicie o app executando o script a seguir. Esse script também gera o arquivo output.css a partir do tailwindCSS.
npm run dev
Para visualizar o site, abra o botão "Visualização na Web" e selecione "Visualizar porta 8080"
8. Implante o serviço
Primeiro, execute este comando para iniciar a implantação e especifique a conta de serviço a ser usada. Se nenhuma conta de serviço for especificada, será usada a conta de serviço padrão do Compute.
gcloud run deploy $SERVICE \ --service-account $SERVICE_ACCOUNT_ADDRESS \ --source . \ --region $REGION \ --allow-unauthenticated \ --set-secrets="SESSION_SECRET=$(echo $SECRET_ID):1"
Se aparecer a mensagem "A implantação da origem exige um repositório Docker do Artifact Registry para armazenar contêineres criados. Um repositório chamado [cloud-run-source-deploy] na região [us-central1] será criado.", pressione "y" para aceitar e continuar.
9. Testar o serviço
Depois de implantado, abra o URL do serviço no navegador da Web. Em seguida, faça uma pergunta ao Gemini. Por exemplo: "Eu pratico violão, mas também sou engenheiro de software. Quando vejo "C#", devo pensar nisso como uma linguagem de programação ou uma nota musical? Qual devo escolher?”
10. Parabéns!
Parabéns por concluir o codelab.
É recomendável consultar a documentação do Cloud Run e das APIs Gemini da Vertex AI.
O que vimos
- Como usar htmx, tailwindcss e express.js para criar um serviço do Cloud Run
- Como usar as bibliotecas de cliente da Vertex AI para se autenticar nas APIs do Google
- Como criar um chatbot para interagir com o modelo Gemini
- Como implantar um serviço do Cloud Run sem um arquivo do Docker
- Como usar um armazenamento de sessão expresso com suporte do Google Cloud Firestore
11. Limpar
Para evitar cobranças acidentais, por exemplo, se os serviços do Cloud Run forem invocados por engano mais vezes do que sua alocação mensal de invocação do Cloud Run no nível sem custo financeiro, exclua o Cloud Run ou o projeto criado na etapa 2.
Para excluir o serviço do Cloud Run, acesse o console do Cloud Run do Cloud em https://console.cloud.google.com/run e exclua o serviço chat-with-gemini
. Também é possível excluir a conta de serviço vertex-ai-caller
ou revogar o papel de Usuário da Vertex AI para evitar chamadas acidentais ao Gemini.
Se você optar por excluir o projeto inteiro, acesse https://console.cloud.google.com/cloud-resource-manager, selecione o projeto criado na etapa 2 e escolha "Excluir". Se você excluir o projeto, precisará alterar os projetos no SDK Cloud. Para conferir a lista de todos os projetos disponíveis, execute gcloud projects list
.