1. はじめに
概要
この Codelab では、Vertex AI Gemini API と Vertex AI クライアント ライブラリを使用して、Node で記述された基本的なチャットボットを作成する方法について説明します。このアプリは、Google Cloud Firestore を基盤とするエクスプレス セッション ストアを使用します。
学習内容
- htmx、tailwindcss、express.js を使用して Cloud Run サービスを構築する方法
- Vertex AI クライアント ライブラリを使用して Google APIs に対して認証する方法
- Gemini モデルとやり取りする chatbot を作成する方法
- docker ファイルなしで Cloud Run サービスにデプロイする方法
- Google Cloud Firestore を基盤とする Express セッション ストアの使用方法
2. 設定と要件
前提条件
- Cloud コンソールにログインしていること。
- Cloud Run サービスをデプロイ済みである。たとえば、ソースコードからウェブサービスをデプロイするクイックスタートに沿って操作します。
Cloud Shell をアクティブにする
- Cloud Console で、[Cloud Shell をアクティブにする]
をクリックします。

Cloud Shell を初めて起動する場合は、その内容を説明する中間画面が表示されます。中間画面が表示された場合は、[続行] をクリックします。

すぐにプロビジョニングが実行され、Cloud Shell に接続されます。

この仮想マシンには、必要な開発ツールがすべて用意されています。仮想マシンは Google Cloud で稼働し、永続的なホーム ディレクトリが 5 GB 用意されているため、ネットワークのパフォーマンスと認証が大幅に向上しています。この Codelab で行う作業のほとんどはブラウザから実行できます。
Cloud Shell に接続すると、認証が完了しており、プロジェクトに各自のプロジェクト ID が設定されていることがわかります。
- 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 を有効にする
この Codelab を開始する前に、いくつかの API を有効にする必要があります。この Codelab では、次の API を使用する必要があります。これらの API を有効にするには、次のコマンドを実行します。
gcloud services enable run.googleapis.com \
cloudbuild.googleapis.com \
aiplatform.googleapis.com \
secretmanager.googleapis.com
環境変数を設定する
この 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. Firebase プロジェクトを作成して構成する
- Firebase コンソールで [プロジェクトを追加] をクリックします。
- <YOUR_PROJECT_ID> を入力して、既存の Google Cloud プロジェクトのいずれかに Firebase を追加します。
- Firebase の利用規約が表示されたら、内容を読み、同意します。
- [続行] をクリックします。
- [プランを確認] をクリックして Firebase 料金プランを確認します。
- この Codelab で Google アナリティクスを有効にするかどうかは任意です。
- [Firebase を追加] をクリックします。
- プロジェクトが作成されたら、[続行] をクリックします。
- [Build] メニューから、[Firestore データベース] をクリックします。
- [データベースを作成] をクリックします。
- [地域] プルダウンから地域を選択し、[次へ] をクリックします。
- デフォルトの [本番環境モードで開始] を使用し、[作成] をクリックします。
5. サービス アカウントを作成する
このサービス アカウントは、Cloud Run が Vertex AI Gemini API を呼び出すために使用されます。このサービス アカウントには、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 の Express セッション シークレットへのアクセス権をサービス アカウントに付与します。
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 サービスを作成する
まず、ソースコードのディレクトリを作成し、そのディレクトリに移動します。
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();
});
tailwindCSS の tailwind.config.js ファイルを作成します。
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./**/*.{html,js}"],
theme: {
extend: {}
},
plugins: []
};
デプロイされた Cloud Run サービスのプロジェクト ID とリージョンを取得するための metadataService.js ファイルを作成します。これらの値は、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>`;
最後に、tailwindCSS の input.css ファイルを作成します。
@tailwind base; @tailwind components; @tailwind utilities;
次に、新しい public ディレクトリを作成します。
mkdir public cd public
その公開ディレクトリ内に、htmx を使用するフロントエンド用の index.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
ローカルで実行するときに ADC を使用する
Cloud Shell で実行している場合は、すでに Google Compute Engine 仮想マシンで実行されています。この仮想マシンに関連付けられている認証情報(gcloud auth list の実行で表示される)は、アプリケーションのデフォルト認証情報によって自動的に使用されるため、gcloud auth application-default login コマンドを使用する必要はありません。ローカル セッション シークレットを作成するセクションまでスキップできます。
ただし、ローカル ターミナル(Cloud Shell 以外)で実行している場合は、アプリケーションのデフォルト認証情報を使用して Google API に対する認証を行う必要があります。1)認証情報を使用してログインする(Vertex AI ユーザーロールと Datastore ユーザーロールの両方がある場合)、または 2)この Codelab で使用するサービス アカウントになり代わってログインできます。
オプション 1)ADC の認証情報を使用する
認証情報を使用する場合は、まず gcloud auth list を実行して、gcloud での認証方法を確認します。次に、ID に Vertex AI ユーザーロールを付与する必要がある場合があります。ID にオーナーロールがある場合は、この Vertex AI ユーザーロールがすでに付与されています。そうでない場合は、次のコマンドを実行して、ID に Vertex AI ユーザーロールと 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
次のコマンドを実行します。
gcloud auth application-default login
オプション 2)ADC のサービス アカウントの権限借用
この Codelab で作成したサービス アカウントを使用する場合は、ユーザー アカウントにサービス アカウント トークン作成者のロールが必要です。このロールを取得するには、次のコマンドを実行します。
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
アプリをローカルで実行する
最後に、次のスクリプトを実行してアプリを起動します。このスクリプトは、tailwindCSS から output.css ファイルも生成します。
npm run dev
ウェブサイトをプレビューするには、[ウェブでプレビュー] ボタンを開き、[プレビュー ポート 8080] を選択します。
![ウェブ プレビュー - [ポート 8080 でプレビュー] ボタン](https://codelabs.developers.google.com/static/codelabs/how-to-deploy-gemini-powered-chat-app-cloud-run/img/PreviewPort8080.png?hl=ja)
8. サービスをデプロイする
まず、次のコマンドを実行してデプロイを開始し、使用するサービス アカウントを指定します。サービス アカウントを指定しない場合は、デフォルトのコンピューティング サービス アカウントが使用されます。
gcloud run deploy $SERVICE \ --service-account $SERVICE_ACCOUNT_ADDRESS \ --source . \ --region $REGION \ --allow-unauthenticated \ --set-secrets="SESSION_SECRET=$(echo $SECRET_ID):1"
「Deploying from source requires an Artifact Registry Docker repository to store built containers. リージョン [us-central1] に [cloud-run-source-deploy] という名前のリポジトリが作成されます。」と表示されたら、「y」と入力して続行します。
9. サービスをテストする
デプロイが完了したら、ウェブブラウザでサービス URL を開きます。次に、Gemini に相談します(例: 「ギターの練習をしていますが、ソフトウェア エンジニアでもあります。「C#」と表示された場合、プログラミング言語と音符のどちらと考えるべきですか?どちらを選べばよいですか?」
10. 完了
以上で、この Codelab は完了です。
Cloud Run と Vertex AI Gemini API のドキュメントを確認することをおすすめします。
学習した内容
- htmx、tailwindcss、express.js を使用して Cloud Run サービスを構築する方法
- Vertex AI クライアント ライブラリを使用して Google APIs に対して認証する方法
- Gemini モデルとやり取りするチャットボットを作成する方法
- docker ファイルなしで Cloud Run サービスにデプロイする方法
- Google Cloud Firestore を基盤とする Express セッション ストアの使用方法
11. クリーンアップ
誤って課金されないようにするには(たとえば、Cloud Run サービスが誤って 無料枠の Cloud Run 呼び出しの月間割り当てよりも多く呼び出された場合など)、Cloud Run を削除するか、ステップ 2 で作成したプロジェクトを削除します。
Cloud Run サービスを削除するには、https://console.cloud.google.com/run で Cloud Run Cloud Console に移動し、chat-with-gemini サービスを削除します。Gemini への意図しない呼び出しを避けるため、vertex-ai-caller サービス アカウントを削除するか、Vertex AI ユーザーロールを取り消すこともできます。
プロジェクト全体を削除する場合は、https://console.cloud.google.com/cloud-resource-manager に移動し、ステップ 2 で作成したプロジェクトを選択して、[削除] を選択します。プロジェクトを削除した場合は、Cloud SDK でプロジェクトを変更する必要があります。gcloud projects list を実行すると、使用可能なすべてのプロジェクトのリストを表示できます。