1. Wprowadzenie
Omówienie
Z tego ćwiczenia w Codelabs dowiesz się, jak utworzyć podstawowego bota na czacie napisanego w węźle przy użyciu interfejsu Vertex AI Gemini API i biblioteki klienta Vertex AI. Ta aplikacja używa magazynu sesji ekspresowych obsługiwanych przez Google Cloud Firestore.
Czego się nauczysz
- Jak utworzyć usługę Cloud Run za pomocą plików htmx, tailwindcss i express.js
- Jak używać bibliotek klienta Vertex AI do uwierzytelniania w interfejsach API Google
- Jak utworzyć czatbota do interakcji z modelem Gemini
- Jak wdrożyć w usłudze Cloud Run bez pliku Dockera
- Jak korzystać z magazynu sesji ekspresowych obsługiwanej przez Google Cloud Firestore
2. Konfiguracja i wymagania
Wymagania wstępne
- Jesteś zalogowany w konsoli Google Cloud.
- Masz już wdrożoną usługę Cloud Run. Możesz na przykład zapoznać się z krótkim wprowadzeniem do wdrażania usługi sieciowej z kodu źródłowego.
Aktywowanie Cloud Shell
- W konsoli Cloud kliknij Aktywuj Cloud Shell .
Jeśli uruchamiasz Cloud Shell po raz pierwszy, zobaczysz ekran pośredni z opisem tej usługi. Jeśli wyświetlił się ekran pośredni, kliknij Dalej.
Uzyskanie dostępu do Cloud Shell i połączenie się z nim powinno zająć tylko kilka chwil.
Ta maszyna wirtualna ma wszystkie potrzebne narzędzia dla programistów. Zawiera stały katalog domowy o pojemności 5 GB i działa w Google Cloud, co znacznie zwiększa wydajność sieci i uwierzytelnianie. Większość zadań w ramach tego ćwiczenia z programowania można wykonać w przeglądarce.
Po nawiązaniu połączenia z Cloud Shell powinno pojawić się potwierdzenie, że użytkownik jest uwierzytelniony, a projekt jest ustawiony na identyfikator Twojego projektu.
- Uruchom to polecenie w Cloud Shell, aby potwierdzić, że jesteś uwierzytelniony:
gcloud auth list
Dane wyjściowe polecenia
Credentialed Accounts ACTIVE ACCOUNT * <my_account>@<my_domain.com> To set the active account, run: $ gcloud config set account `ACCOUNT`
- Uruchom to polecenie w Cloud Shell, aby sprawdzić, czy polecenie gcloud zna Twój projekt:
gcloud config list project
Dane wyjściowe polecenia
[core] project = <PROJECT_ID>
Jeśli tak nie jest, możesz go ustawić za pomocą tego polecenia:
gcloud config set project <PROJECT_ID>
Dane wyjściowe polecenia
Updated property [core/project].
3. Włączanie interfejsów API i ustawianie zmiennych środowiskowych
Włącz interfejsy API
Zanim zaczniesz korzystać z tego ćwiczenia z programowania, musisz włączyć kilka interfejsów API. To ćwiczenie w Codelabs wymaga używania poniższych interfejsów API. Możesz włączyć te interfejsy API, uruchamiając to polecenie:
gcloud services enable run.googleapis.com \ cloudbuild.googleapis.com \ aiplatform.googleapis.com \ secretmanager.googleapis.com
Skonfiguruj zmienne środowiskowe
Możesz ustawić zmienne środowiskowe, które będą używane podczas tego ćwiczenia z programowania.
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. Tworzenie i konfigurowanie projektu Firebase
- W konsoli Firebase kliknij Dodaj projekt.
- Wpisz <IDENTYFIKATOR_TWOJEGO_PROJEKTU> aby dodać Firebase do jednego z istniejących projektów Google Cloud
- W razie potrzeby przeczytaj i zaakceptuj warunki korzystania z Firebase.
- Kliknij Dalej.
- Kliknij Potwierdź abonament, aby potwierdzić abonament Firebase.
- W tym ćwiczeniu z programowania można opcjonalnie włączyć Google Analytics.
- Kliknij Dodaj Firebase.
- Po utworzeniu projektu kliknij Dalej.
- W menu Build (Tworzenie) kliknij Firestore database (Baza danych Firestore).
- Kliknij Utwórz bazę danych.
- Wybierz region z menu Lokalizacja i kliknij Dalej.
- Użyj domyślnej opcji Rozpocznij w trybie produkcyjnym, a następnie kliknij Utwórz.
5. Tworzenie konta usługi
To konto usługi będzie używane przez Cloud Run do wywoływania interfejsu Vertex AI Gemini API. To konto usługi będzie też miało uprawnienia do odczytu i zapisu w Firestore oraz do odczytu obiektów tajnych z usługi Secret Manager.
Najpierw utwórz konto usługi, uruchamiając to polecenie:
gcloud iam service-accounts create $SERVICE_ACCOUNT \ --display-name="Cloud Run to access Vertex AI APIs"
Następnie przypisz do konta usługi rolę użytkownika Vertex AI.
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \ --role=roles/aiplatform.user
Teraz utwórz obiekt tajny w usłudze Secret Manager. Usługa Cloud Run uzyska dostęp do tego obiektu tajnego jako zmiennych środowiskowych, co jest rozwiązywane w momencie uruchomienia instancji. Dowiedz się więcej o obiektach tajnych i Cloud Run.
gcloud secrets create $SECRET_ID --replication-policy="automatic" printf "keyboard-cat" | gcloud secrets versions add $SECRET_ID --data-file=-
Przyznaj kontu usługi dostęp do obiektu tajnego sesji ekspresowej w usłudze Secret Manager.
gcloud secrets add-iam-policy-binding $SECRET_ID \ --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \ --role='roles/secretmanager.secretAccessor'
Na koniec przyznaj kontu usługi uprawnienia do odczytu i zapisu w Firestore.
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \ --role=roles/datastore.user
6. Tworzenie usługi Cloud Run
Najpierw utwórz katalog na kod źródłowy i cd w tym katalogu.
mkdir chat-with-gemini && cd chat-with-gemini
Następnie utwórz plik package.json
z tą zawartością:
{ "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" } }
Następnie utwórz plik źródłowy app.js
z poniższą zawartością. Ten plik zawiera punkt wejścia usługi i główną logikę aplikacji.
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(); });
Utwórz plik tailwind.config.js
dla tailwindCSS.
/** @type {import('tailwindcss').Config} */ module.exports = { content: ["./**/*.{html,js}"], theme: { extend: {} }, plugins: [] };
Utwórz plik metadataService.js
, aby pobrać identyfikator projektu i region wdrożonej usługi Cloud Run. Wartości te zostaną wykorzystane do utworzenia instancji bibliotek klienta 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; } };
Utwórz plik o nazwie 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>`;
Na koniec utwórz plik input.css
dla tailwindCSS.
@tailwind base; @tailwind components; @tailwind utilities;
Teraz utwórz nowy katalog public
.
mkdir public cd public
W tym katalogu publicznym utwórz plik index.html
dla interfejsu o nazwie 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. Uruchamianie usługi lokalnie
Najpierw sprawdź, czy jesteś w katalogu głównym chat-with-gemini
, w którym wykonujesz ćwiczenia z programowania.
cd .. && pwd
Następnie zainstaluj zależności, uruchamiając to polecenie:
npm install
Korzystanie z ADC w przypadku uruchomienia lokalnego
Jeżeli korzystasz z Cloud Shell, oznacza to, że korzystasz już z maszyny wirtualnej Google Compute Engine. Twoje dane logowania powiązane z tą maszyną wirtualną (wyświetlane przez uruchomienie gcloud auth list
) będą automatycznie używane przez domyślne dane uwierzytelniające aplikacji, dlatego nie musisz używać polecenia gcloud auth application-default login
. Możesz przejść do sekcji Tworzenie obiektu tajnego sesji lokalnej.
Jeśli jednak korzystasz z terminala lokalnego (tzn. nie w Cloud Shell), do uwierzytelniania w interfejsach API Google musisz używać domyślnych danych logowania aplikacji. Możesz 1) zalogować się za pomocą swoich danych logowania (pod warunkiem, że masz zarówno role użytkownika Vertex AI, jak i Datastore) lub 2) zalogować się, używając konta usługi użytego w tym ćwiczeniu z programowania.
Opcja 1. Używanie Twoich danych logowania do ADC
Jeśli chcesz użyć danych logowania, najpierw uruchom polecenie gcloud auth list
, aby sprawdzić sposób uwierzytelniania w gcloud. Następnie może być konieczne przypisanie do tożsamości roli użytkownika Vertex AI. Jeśli Twoja tożsamość ma rolę właściciela, masz już tę rolę użytkownika Vertex AI. Jeśli nie, możesz uruchomić to polecenie, aby przypisać rolę użytkownika Vertex AI tożsamości i rolę użytkownika 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
Następnie uruchom to polecenie
gcloud auth application-default login
Opcja 2. Odgrywanie roli konta usługi na potrzeby ADC
Jeśli chcesz używać konta usługi utworzonego w ramach tego ćwiczenia w Codelabs, Twoje konto użytkownika musi mieć przypisaną rolę twórcy tokenów konta usługi. Aby uzyskać tę rolę, uruchom następujące polecenie:
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member user:$USER \ --role=roles/iam.serviceAccountTokenCreator
Następnie uruchom poniższe polecenie, aby użyć ADC z kontem usługi
gcloud auth application-default login --impersonate-service-account=$SERVICE_ACCOUNT_ADDRESS
Tworzenie obiektu tajnego sesji lokalnej
Teraz utwórz tajny klucz sesji lokalnej na potrzeby programowania lokalnego.
export SESSION_SECRET=local-secret
Lokalne uruchamianie aplikacji
Na koniec możesz uruchomić aplikację, uruchamiając poniższy skrypt. Skrypt ten wygeneruje też plikoutput.css z tailwindCSS.
npm run dev
Aby wyświetlić podgląd strony, otwórz przycisk Podgląd w przeglądarce i wybierz Port podglądu 8080
8. Wdrażanie usługi
Najpierw uruchom to polecenie, aby rozpocząć wdrożenie i określić konto usługi, które ma być używane. Jeśli konto usługi nie jest określone, zostanie użyte domyślne konto usługi Compute.
gcloud run deploy $SERVICE \ --service-account $SERVICE_ACCOUNT_ADDRESS \ --source . \ --region $REGION \ --allow-unauthenticated \ --set-secrets="SESSION_SECRET=$(echo $SECRET_ID):1"
Jeśli pojawi się komunikat „Wdrażanie ze źródła wymaga użycia repozytorium Dockera w Artifact Registry do przechowywania utworzonych kontenerów. Zostanie utworzone repozytorium o nazwie [cloud-run-source-deploy] w regionie [us-central1].", naciśnij „y” aby zaakceptować i kontynuować.
9. Testowanie usługi
Po wdrożeniu otwórz adres URL usługi w przeglądarce. Następnie zadaj Gemini pytanie, np. „Ja gram na gitarze, ale jestem też inżynierem oprogramowania. Czy gdy widzę „C#”, czy należy traktować go jako język programowania czy nutę? Który wybrać?”
10. Gratulacje!
Gratulujemy ukończenia ćwiczeń z programowania.
Zalecamy zapoznanie się z dokumentacją Cloud Run oraz Vertex AI Gemini API.
Omówione zagadnienia
- Jak utworzyć usługę Cloud Run za pomocą plików htmx, tailwindcss i express.js
- Jak używać bibliotek klienta Vertex AI do uwierzytelniania w interfejsach API Google
- Jak utworzyć bota na czacie do interakcji z modelem Gemini
- Jak wdrożyć w usłudze Cloud Run bez pliku Dockera
- Jak korzystać z magazynu sesji ekspresowych obsługiwanej przez Google Cloud Firestore
11. Czyszczenie danych
Aby uniknąć niezamierzonych opłat (na przykład jeśli usługi Cloud Run były wywoływane więcej razy niż przez miesięczny przydział wywołań Cloud Run na poziomie bezpłatnym), możesz usunąć Cloud Run albo projekt utworzony w kroku 2.
Aby usunąć usługę Cloud Run, otwórz konsolę Cloud Run na stronie https://console.cloud.google.com/run i usuń usługę chat-with-gemini
. Aby uniknąć niezamierzonych wywołań Gemini, możesz też usunąć konto usługi vertex-ai-caller
lub anulować rolę użytkownika Vertex AI.
Jeśli zdecydujesz się usunąć cały projekt, możesz otworzyć stronę https://console.cloud.google.com/cloud-resource-manager, wybrać projekt utworzony w kroku 2 i kliknąć Usuń. Jeśli usuniesz projekt, musisz zmienić projekty w pakiecie SDK Cloud. Aby wyświetlić listę wszystkich dostępnych projektów, uruchom polecenie gcloud projects list
.