1. Wprowadzenie
Przegląd
Z tego ćwiczenia dowiesz się, jak utworzyć prostego chatbota napisanego w Node.js za pomocą interfejsu Vertex AI Gemini API i biblioteki klienta Vertex AI. Ta aplikacja korzysta z sesyjnego magazynu ekspresowego opartego na Google Cloud Firestore.
Czego się nauczysz
- Jak używać htmx, tailwindcss i express.js do tworzenia usługi Cloud Run
- Jak używać bibliotek klienta Vertex AI do uwierzytelniania w interfejsach API Google
- Jak utworzyć czatbota do interakcji z modelem Gemini
- Jak wdrożyć usługę Cloud Run bez pliku Dockera
- Korzystanie z magazynu sesji Express opartego na Google Cloud Firestore
2. Konfiguracja i wymagania
Wymagania wstępne
- Jesteś zalogowany(-a) w konsoli Google Cloud.
- Usługa Cloud Run została już wdrożona. Na początek możesz na przykład skorzystać z krótkiego wprowadzenia dotyczącego 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 tego środowiska. Jeśli pojawił się ekran pośredni, kliknij Dalej.

Uzyskanie dostępu do środowiska Cloud Shell i połączenie się z nim powinno zająć tylko kilka chwil.

Ta maszyna wirtualna zawiera wszystkie potrzebne narzędzia dla programistów. Zawiera również stały katalog domowy o pojemności 5 GB i działa w Google Cloud, co znacznie zwiększa wydajność sieci i usprawnia proces uwierzytelniania. Większość zadań w tym ćwiczeniu, a być może wszystkie, możesz wykonać w przeglądarce.
Po połączeniu z Cloud Shell zobaczysz, że uwierzytelnianie zostało już przeprowadzone, a projekt jest już ustawiony na Twój identyfikator projektu.
- Aby potwierdzić, że uwierzytelnianie zostało przeprowadzone, uruchom w Cloud Shell to polecenie:
gcloud auth list
Wynik polecenia
Credentialed Accounts
ACTIVE ACCOUNT
* <my_account>@<my_domain.com>
To set the active account, run:
$ gcloud config set account `ACCOUNT`
- Aby potwierdzić, że polecenie gcloud zna Twój projekt, uruchom w Cloud Shell to polecenie:
gcloud config list project
Wynik polecenia
[core] project = <PROJECT_ID>
Jeśli nie, możesz go ustawić za pomocą tego polecenia:
gcloud config set project <PROJECT_ID>
Wynik 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 w Codelabs, musisz włączyć kilka interfejsów API. W tym ćwiczeniu musisz użyć tych 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
Konfigurowanie zmiennych środowiskowych
Możesz ustawić zmienne środowiskowe, których będziesz używać podczas naszych ćwiczeń 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 w Firebase
- W konsoli Firebase kliknij Dodaj projekt.
- Wpisz <YOUR_PROJECT_ID>, aby dodać Firebase do jednego z istniejących projektów Google Cloud.
- Jeśli pojawi się taka prośba, przeczytaj i zaakceptuj warunki usługi Firebase.
- Kliknij Dalej.
- Aby potwierdzić plan płatności Firebase, kliknij Confirm Plan (Potwierdź plan).
- Włączenie Google Analytics w tym samouczku jest opcjonalne.
- Kliknij Dodaj Firebase.
- Gdy projekt zostanie utworzony, kliknij Dalej.
- W menu Kompilacja kliknij Baza danych Firestore.
- Kliknij Utwórz bazę danych.
- Wybierz region z menu Lokalizacja, a potem kliknij Dalej.
- Użyj domyślnego ustawienia Uruchom 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ż mieć uprawnienia do odczytu i zapisu w Firestore oraz do odczytywania 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"
Po drugie, 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 będzie uzyskiwać dostęp do tego obiektu tajnego jako zmiennej środowiskowej, która jest rozpoznawana w momencie uruchomienia instancji. Więcej informacji 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 dostęp do Firestore z możliwością zapisu i odczytu.
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 kodu źródłowego i przejdź do niego.
mkdir chat-with-gemini && cd chat-with-gemini
Następnie utwórz plik package.json o tej treś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ą treś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 uzyskać identyfikator projektu i region wdrożonej usługi Cloud Run. Te wartości zostaną użyte 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, który będzie korzystać z 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 upewnij się, że znajdujesz się w katalogu głównym chat-with-gemini dla tego ćwiczenia.
cd .. && pwd
Następnie zainstaluj zależności, uruchamiając to polecenie:
npm install
Używanie ADC podczas uruchamiania lokalnego
Jeśli korzystasz z Cloud Shell, używasz już maszyny wirtualnej Google Compute Engine. Dane logowania powiązane z tą maszyną wirtualną (widoczne po uruchomieniu polecenia gcloud auth list) będą automatycznie używane przez domyślne uwierzytelnianie aplikacji, więc nie musisz używać polecenia gcloud auth application-default login. Możesz przejść do sekcji Tworzenie lokalnego klucza sesji.
Jeśli jednak korzystasz z lokalnego terminala (czyli nie z Cloud Shell), do uwierzytelniania w interfejsach API Google musisz używać domyślnego uwierzytelniania aplikacji. Możesz 1) zalogować się za pomocą swoich danych logowania (jeśli masz role Użytkownik Vertex AI i Użytkownik Datastore) lub 2) zalogować się, przyjmując tożsamość konta usługi użytego w tym ćwiczeniu.
Opcja 1. Używanie danych logowania do domyślnego uwierzytelniania aplikacji
Jeśli chcesz użyć swoich danych logowania, możesz najpierw uruchomić polecenie gcloud auth list, aby sprawdzić, jak uwierzytelniasz się w gcloud. Następnie może być konieczne przyznanie tożsamości roli użytkownika Vertex AI. Jeśli Twoja tożsamość ma rolę Właściciel, masz już tę rolę użytkownika Vertex AI. Jeśli nie, możesz uruchomić to polecenie, aby przyznać swojej tożsamości rolę użytkownika Vertex AI 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. Przyjmowanie tożsamości konta usługi na potrzeby ADC
Jeśli chcesz użyć konta usługi utworzonego w tym ćwiczeniu, Twoje konto użytkownika musi mieć rolę twórcy tokenów konta usługi. Aby uzyskać tę rolę, uruchom to polecenie:
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member user:$USER \ --role=roles/iam.serviceAccountTokenCreator
Następnie uruchom to polecenie, aby użyć ADC z kontem usługi:
gcloud auth application-default login --impersonate-service-account=$SERVICE_ACCOUNT_ADDRESS
Tworzenie lokalnego obiektu tajnego sesji
Teraz utwórz lokalny obiekt tajny sesji na potrzeby programowania lokalnego.
export SESSION_SECRET=local-secret
Lokalne uruchamianie aplikacji
Na koniec możesz uruchomić aplikację, wykonując ten skrypt. Ten skrypt wygeneruje też plik output.css z biblioteki tailwindCSS.
npm run dev
Aby wyświetlić podgląd witryny, otwórz przycisk Podgląd w przeglądarce i wybierz Podgląd na porcie 8080.

8. Wdrażanie usługi
Najpierw uruchom to polecenie, aby rozpocząć wdrażanie i określić konto usługi, które ma być używane. Jeśli nie podasz konta usługi, użyte zostanie 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 repozytorium Dockera Artifact Registry do przechowywania skompilowanych kontenerów. Zostanie utworzone repozytorium o nazwie [cloud-run-source-deploy] w regionie [us-central1]. Aby zaakceptować i kontynuować, naciśnij „y”.
9. Testowanie usługi
Po wdrożeniu otwórz URL usługi w przeglądarce. Następnie zadaj Gemini pytanie, np. „Gram na gitarze, ale jestem też inżynierem oprogramowania. Gdy widzę „C#”, czy mam myśleć o tym jako o języku programowania czy o nucie? Który z nich mam wybrać?”.
10. Gratulacje!
Gratulujemy ukończenia ćwiczenia!
Zalecamy zapoznanie się z dokumentacją Cloud Run i interfejsów Vertex AI Gemini API.
Omówione zagadnienia
- Jak używać htmx, tailwindcss i express.js do tworzenia usługi Cloud Run
- Jak używać bibliotek klienta Vertex AI do uwierzytelniania w interfejsach API Google
- Jak utworzyć chatbota do interakcji z modelem Gemini
- Jak wdrożyć usługę Cloud Run bez pliku Dockera
- Korzystanie z magazynu sesji Express opartego na Google Cloud Firestore
11. Czyszczenie danych
Aby uniknąć przypadkowych opłat (np. jeśli usługi Cloud Run zostaną przypadkowo wywołane więcej razy niż miesięczna liczba wywołań Cloud Run w bezpłatnej wersji), możesz usunąć Cloud Run lub projekt utworzony w kroku 2.
Aby usunąć usługę Cloud Run, otwórz konsolę Cloud Run w Cloud Console na stronie https://console.cloud.google.com/run i usuń usługę chat-with-gemini. Możesz też usunąć vertex-ai-caller konto usługi lub cofnąć rolę użytkownika Vertex AI, aby uniknąć przypadkowych wywołań Gemini.
Jeśli zdecydujesz się usunąć cały projekt, otwórz stronę https://console.cloud.google.com/cloud-resource-manager, wybierz projekt utworzony w kroku 2 i kliknij Usuń. Jeśli usuniesz projekt, musisz zmienić projekty w Cloud SDK. Listę wszystkich dostępnych projektów możesz wyświetlić, uruchamiając polecenie gcloud projects list.