1. Введение
Обзор
В этом практическом занятии вы узнаете, как создать простой чат-бот, написанный на Node.js, используя API Vertex AI Gemini и клиентскую библиотеку Vertex AI. Это приложение использует хранилище сессий Express, поддерживаемое Google Cloud Firestore .
Что вы узнаете
- Как использовать htmx, tailwindcss и express.js для создания сервиса Cloud Run
- Как использовать клиентские библиотеки Vertex AI для аутентификации в API Google.
- Как создать чат-бота для взаимодействия с моделью Gemini
- Как развернуть приложение в облачном сервисе без Docker-файла.
- Как использовать хранилище экспресс-сессий на базе Google Cloud Firestore
2. Настройка и требования
Предварительные требования
- Вы вошли в облачную консоль.
- Вы ранее развернули службу Cloud Run. Например, для начала вы можете воспользоваться руководством по быстрому развертыванию веб-службы из исходного кода .
Активировать Cloud Shell
- В консоли Cloud нажмите «Активировать Cloud Shell» .
.

Если вы запускаете Cloud Shell впервые, вам будет показан промежуточный экран с описанием его возможностей. Если вы увидели промежуточный экран, нажмите «Продолжить» .

Подготовка и подключение к Cloud Shell займут всего несколько минут.

Эта виртуальная машина оснащена всеми необходимыми инструментами разработки. Она предоставляет постоянный домашний каталог объемом 5 ГБ и работает в облаке Google, что значительно повышает производительность сети и аутентификацию. Большая часть, если не вся, ваша работа в этом практическом задании может быть выполнена с помощью браузера.
После подключения к Cloud Shell вы увидите, что прошли аутентификацию и что проект настроен на ваш идентификатор проекта.
- Выполните следующую команду в Cloud Shell, чтобы подтвердить свою аутентификацию:
gcloud auth list
вывод команды
Credentialed Accounts
ACTIVE ACCOUNT
* <my_account>@<my_domain.com>
To set the active account, run:
$ gcloud config set account `ACCOUNT`
- Выполните следующую команду в Cloud Shell, чтобы убедиться, что команда gcloud знает о вашем проекте:
gcloud config list project
вывод команды
[core] project = <PROJECT_ID>
Если это не так, вы можете установить это с помощью следующей команды:
gcloud config set project <PROJECT_ID>
вывод команды
Updated property [core/project].
3. Включите API и установите переменные среды.
Включить API
Прежде чем начать использовать этот практический пример, вам потребуется включить несколько API. Для работы с этим практическим примером необходимы следующие API. Вы можете включить эти API, выполнив следующую команду:
gcloud services enable run.googleapis.com \
cloudbuild.googleapis.com \
aiplatform.googleapis.com \
secretmanager.googleapis.com
Настройка переменных среды
Вы можете установить переменные окружения, которые будут использоваться на протяжении всего этого практического занятия.
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. Создайте и настройте проект Firebase.
- В консоли Firebase нажмите «Добавить проект» .
- Введите <YOUR_PROJECT_ID>, чтобы добавить Firebase в один из ваших существующих проектов Google Cloud.
- При появлении запроса ознакомьтесь с условиями использования Firebase и примите их.
- Нажмите «Продолжить» .
- Нажмите «Подтвердить план» , чтобы подтвердить тарифный план Firebase.
- Включение Google Analytics для этого практического занятия является необязательным.
- Нажмите «Добавить Firebase» .
- После создания проекта нажмите «Продолжить» .
- В меню «Сборка» выберите «База данных Firestore» .
- Нажмите «Создать базу данных» .
- Выберите свой регион из выпадающего списка «Местоположение» , затем нажмите «Далее» .
- Используйте значение по умолчанию «Запустить в производственном режиме» , затем нажмите «Создать» .
5. Создайте учетную запись службы.
Эта учетная запись службы будет использоваться Cloud Run для вызова API Vertex AI Gemini. У этой учетной записи службы также будут разрешения на чтение и запись в Firestore, а также на чтение секретов из Secret Manager.
Сначала создайте учетную запись службы, выполнив следующую команду:
gcloud iam service-accounts create $SERVICE_ACCOUNT \ --display-name="Cloud Run to access Vertex AI APIs"
Во-вторых, предоставьте учетной записи службы роль пользователя Vertex AI.
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \ --role=roles/aiplatform.user
Теперь создайте секрет в Secret Manager. Сервис Cloud Run будет использовать этот секрет в качестве переменной среды, которая определяется при запуске экземпляра. Подробнее о секретах и Cloud Run можно узнать здесь.
gcloud secrets create $SECRET_ID --replication-policy="automatic" printf "keyboard-cat" | gcloud secrets versions add $SECRET_ID --data-file=-
И предоставьте учетной записи службы доступ к секретному ключу экспресс-сессии в Secret Manager.
gcloud secrets add-iam-policy-binding $SECRET_ID \
--member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \
--role='roles/secretmanager.secretAccessor'
Наконец, предоставьте учетной записи службы права на чтение и запись в Firestore.
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \ --role=roles/datastore.user
6. Создайте службу Cloud Run.
Сначала создайте директорию для исходного кода и перейдите в неё с помощью команды `cd`.
mkdir chat-with-gemini && cd chat-with-gemini
Затем создайте файл package.json со следующим содержимым:
{
"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"
}
}
Далее создайте исходный файл app.js со следующим содержимым. Этот файл содержит точку входа для сервиса и основную логику приложения.
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();
});
Создайте файл tailwind.config.js для tailwindCSS.
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./**/*.{html,js}"],
theme: {
extend: {}
},
plugins: []
};
Создайте файл metadataService.js для получения идентификатора проекта и региона для развернутой службы Cloud Run. Эти значения будут использоваться для создания экземпляра клиентских библиотек 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;
}
};
Создайте файл с именем 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>`;
Наконец, создайте файл input.css для tailwindCSS.
@tailwind base; @tailwind components; @tailwind utilities;
Теперь создайте новую public директорию.
mkdir public cd public
Внутри этой общедоступной директории создайте файл index.html для внешнего интерфейса, который будет использовать HTML.
<!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. Запустите службу локально.
Во-первых, убедитесь, что вы находитесь в корневом каталоге chat-with-gemini в котором находится ваш кодовый семинар.
cd .. && pwd
Далее установите зависимости, выполнив следующую команду:
npm install
Использование АЦП при локальном запуске
Если вы работаете в Cloud Shell, вы уже используете виртуальную машину Google Compute Engine. Ваши учетные данные, связанные с этой виртуальной машиной (как показано при выполнении команды gcloud auth list ), будут автоматически использоваться в Application Default Credentials, поэтому нет необходимости использовать команду gcloud auth application-default login . Вы можете перейти к разделу «Создание локального секрета сессии».
Однако, если вы работаете на локальном терминале (то есть не в Cloud Shell), вам потребуется использовать учетные данные приложения по умолчанию для аутентификации в API Google. Вы можете либо 1) войти в систему, используя свои учетные данные (при условии, что у вас есть роли пользователя Vertex AI и пользователя хранилища данных), либо 2) войти в систему, выдав себя за учетную запись службы, используемую в этом практическом задании.
Вариант 1) Использование ваших учетных данных для ADC
Если вы хотите использовать свои учетные данные, сначала выполните gcloud auth list , чтобы проверить, как вы прошли аутентификацию в gcloud. Затем вам может потребоваться предоставить вашей учетной записи роль пользователя Vertex AI. Если у вашей учетной записи есть роль владельца, значит, у вас уже есть эта роль пользователя Vertex AI. В противном случае вы можете выполнить эту команду, чтобы предоставить вашей учетной записи роль пользователя Vertex AI и роль пользователя хранилища данных.
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
Затем выполните следующую команду.
gcloud auth application-default login
Вариант 2) Использование служебной учетной записи для ADC
Если вы хотите использовать учетную запись службы, созданную в этом практическом задании, вашей учетной записи пользователя потребуется роль «Создатель токена учетной записи службы» . Получить эту роль можно, выполнив следующую команду:
gcloud projects add-iam-policy-binding $PROJECT_ID \ --member user:$USER \ --role=roles/iam.serviceAccountTokenCreator
Далее вам потребуется выполнить следующую команду, чтобы использовать ADC с учетной записью службы.
gcloud auth application-default login --impersonate-service-account=$SERVICE_ACCOUNT_ADDRESS
Создайте локальный секретный ключ сессии.
Теперь создайте локальный секретный ключ сессии для локальной разработки.
export SESSION_SECRET=local-secret
Запустите приложение локально
Наконец, вы можете запустить приложение, выполнив следующий скрипт. Этот скрипт также сгенерирует файл output.css из tailwindCSS.
npm run dev
Вы можете предварительно просмотреть веб-сайт, открыв кнопку «Предварительный просмотр веб-сайта» и выбрав «Предварительный просмотр порта 8080».

8. Разверните сервис
Сначала выполните эту команду, чтобы начать развертывание и указать используемую учетную запись службы. Если учетная запись службы не указана, будет использоваться учетная запись вычислительной службы по умолчанию.
gcloud run deploy $SERVICE \ --service-account $SERVICE_ACCOUNT_ADDRESS \ --source . \ --region $REGION \ --allow-unauthenticated \ --set-secrets="SESSION_SECRET=$(echo $SECRET_ID):1"
Если появится сообщение "Для развертывания из исходного кода требуется репозиторий Docker в реестре артефактов для хранения собранных контейнеров. Будет создан репозиторий с именем [cloud-run-source-deploy] в регионе [us-central1]", нажмите "y", чтобы принять условия и продолжить.
9. Проверьте работу сервиса.
После развертывания откройте URL-адрес сервиса в веб-браузере. Затем задайте Gemini вопрос, например: «Я играю на гитаре, но также являюсь программистом. Когда я вижу «C#», следует ли мне думать о нем как о языке программирования или как о музыкальной ноте? Что мне следует выбрать?»
10. Поздравляем!
Поздравляем с завершением практического занятия!
Рекомендуем ознакомиться с документацией по API Cloud Run и Vertex AI Gemini .
Что мы рассмотрели
- Как использовать htmx, tailwindcss и express.js для создания сервиса Cloud Run
- Как использовать клиентские библиотеки Vertex AI для аутентификации в API Google.
- Как создать чат-бота для взаимодействия с моделью Близнецов
- Как развернуть приложение в облачном сервисе без Docker-файла.
- Как использовать хранилище экспресс-сессий на базе Google Cloud Firestore
11. Уборка
Чтобы избежать непреднамеренных списаний средств (например, если сервисы Cloud Run будут случайно запущены больше раз, чем предусмотрено вашим ежемесячным лимитом на запуск Cloud Run в бесплатном тарифе ), вы можете либо удалить Cloud Run, либо удалить проект, созданный на шаге 2.
Чтобы удалить службу Cloud Run, перейдите в консоль Cloud Run по адресу https://console.cloud.google.com/run и удалите службу chat-with-gemini . Также вы можете удалить учетную запись службы vertex-ai-caller или отозвать роль пользователя Vertex AI, чтобы избежать случайных звонков в Gemini.
Если вы решите удалить весь проект, перейдите по ссылке https://console.cloud.google.com/cloud-resource-manager , выберите проект, созданный на шаге 2, и нажмите «Удалить». После удаления проекта вам потребуется изменить проекты в вашем Cloud SDK. Список всех доступных проектов можно просмотреть, выполнив gcloud projects list .