1. מבוא
סקירה כללית
ב-codelab הזה תלמדו איך ליצור בוט צ'אט בסיסי שנכתב ב-node באמצעות Vertex AI Gemini API וספריית הלקוח של Vertex AI. האפליקציה הזו משתמשת במאגר סשנים של Express שמגובה על ידי Google Cloud Firestore.
מה תלמדו
- איך משתמשים ב-htmx, ב-tailwindcss וב-express.js כדי ליצור שירות Cloud Run
- איך משתמשים בספריות הלקוח של Vertex AI כדי לבצע אימות ל-Google APIs
- איך יוצרים צ'אטבוט כדי לקיים אינטראקציה עם מודל Gemini
- איך פורסים לשירות Cloud Run בלי קובץ Docker
- איך משתמשים במאגר סשנים של Express שמגובה על ידי Google Cloud Firestore
2. הגדרה ודרישות
דרישות מוקדמות
- אתם מחוברים ל-Cloud Console.
- כבר פרסתם שירות Cloud Run. לדוגמה, אפשר לפעול לפי ההוראות שבמדריך למתחילים בנושא פריסת שירות אינטרנט מקוד מקור.
הפעלת Cloud Shell
- ב-Cloud Console, לוחצים על Activate Cloud Shell
.

אם זו הפעם הראשונה שאתם מפעילים את Cloud Shell, יוצג לכם מסך ביניים עם תיאור של השירות. אם הוצג לכם מסך ביניים, לחצו על המשך.

הקצאת המשאבים והחיבור ל-Cloud Shell נמשכים רק כמה רגעים.

המכונה הווירטואלית הזו כוללת את כל הכלים הדרושים למפתחים. יש בה ספריית בית בנפח מתמיד של 5GB והיא פועלת ב-Google Cloud, מה שמשפר מאוד את הביצועים והאימות ברשת. אפשר לבצע את רוב העבודה ב-codelab הזה, אם לא את כולה, באמצעות דפדפן.
אחרי שמתחברים ל-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
לפני שמתחילים להשתמש ב-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> כדי להוסיף את Firebase לאחד מהפרויקטים הקיימים ב-Google Cloud
- אם תופיע בקשה, תצטרכו לקרוא את התנאים של Firebase ולאשר אותם.
- לוחצים על המשך.
- לוחצים על אישור תוכנית התשלומים כדי לאשר את תוכנית התשלומים של Firebase.
- הפעלת Google Analytics ב-codelab הזה היא אופציונלית.
- לוחצים על הוספת Firebase.
- אחרי שהפרויקט נוצר, לוחצים על המשך.
- בתפריט Build (פיתוח), לוחצים על Firestore database (מסד נתונים של 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 User.
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=-
נותנים לחשבון השירות גישה לסוד של סשן Express ב-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
קודם יוצרים ספרייה לקוד המקור ועוברים לספרייה הזו.
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 עבור ממשק הקצה, שבו ישמש 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. הפעלת השירות באופן מקומי
קודם כל, מוודאים שאתם בספריית הבסיס chat-with-gemini של ה-codelab.
cd .. && pwd
לאחר מכן, מריצים את הפקודה הבאה כדי להתקין את יחסי התלות:
npm install
שימוש ב-ADC בזמן הרצה מקומית
אם אתם מריצים את הפקודה ב-Cloud Shell, אתם כבר מריצים אותה במכונה וירטואלית של Google Compute Engine. פרטי הכניסה שמשויכים למכונה הווירטואלית הזו (כפי שמוצג בהרצת הפקודה gcloud auth list) ישמשו אוטומטית את Application Default Credentials, כך שלא צריך להשתמש בפקודה gcloud auth application-default login. אפשר לדלג לקטע יצירת סוד מקומי לסשן
אבל אם אתם מריצים את הפקודה במסוף המקומי (כלומר לא ב-Cloud Shell), תצטרכו להשתמש ב-Application Default Credentials כדי לבצע אימות ל-Google APIs. אתם יכולים 1) להתחבר באמצעות פרטי הכניסה שלכם (בתנאי שיש לכם את התפקידים 'משתמש Vertex AI' ו'משתמש Datastore') או 2) להתחבר באמצעות התחזות לחשבון השירות שבו נעשה שימוש ב-codelab הזה.
אפשרות 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
אם רוצים להשתמש בחשבון השירות שנוצר ב-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
הפעלת האפליקציה באופן מקומי
לבסוף, מריצים את הסקריפט הבא כדי להפעיל את האפליקציה. הסקריפט הזה ייצור גם את הקובץ output.css מ-tailwindCSS.
npm run dev
כדי לראות תצוגה מקדימה של האתר, פותחים את הלחצן Web Preview (תצוגה מקדימה של אתר) ובוחרים באפשרות Preview Port 8080 (תצוגה מקדימה של יציאה 8080).

8. פריסת השירות
קודם מריצים את הפקודה הזו כדי להתחיל את הפריסה ולציין את חשבון השירות שבו רוצים להשתמש. אם לא מציינים חשבון שירות, המערכת משתמשת בחשבון השירות שמוגדר כברירת מחדל בשירות Compute.
gcloud run deploy $SERVICE \ --service-account $SERVICE_ACCOUNT_ADDRESS \ --source . \ --region $REGION \ --allow-unauthenticated \ --set-secrets="SESSION_SECRET=$(echo $SECRET_ID):1"
אם מוצגת ההודעה 'פריסה ממקור דורשת מאגר Docker ב-Artifact Registry לאחסון קונטיינרים שנבנו. ייווצר מאגר בשם [cloud-run-source-deploy] באזור [us-central1].", מקישים על 'y' כדי לאשר ולהמשיך.
9. בדיקת השירות
אחרי הפריסה, פותחים את כתובת ה-URL של השירות בדפדפן האינטרנט. אחר כך שואלים את Gemini שאלה, למשל: "אני מתאמן בגיטרה אבל אני גם מהנדס תוכנה. כשאני רואה את הסימן C#, האם אני צריך להתייחס אליו כשפת תכנות או כתו מוזיקלי? באיזה מהם כדאי לבחור?"
10. מעולה!
כל הכבוד, סיימתם את ה-Codelab!
מומלץ לעיין במסמכי התיעוד בנושא Cloud Run ו-Vertex AI Gemini APIs.
מה נכלל
- איך משתמשים ב-htmx, ב-tailwindcss וב-express.js כדי ליצור שירות Cloud Run
- איך משתמשים בספריות הלקוח של Vertex AI כדי לבצע אימות ל-Google APIs
- איך יוצרים צ'אטבוט לאינטראקציה עם מודל Gemini
- איך פורסים לשירות Cloud Run בלי קובץ Docker
- איך משתמשים במאגר סשנים של Express שמגובה על ידי Google Cloud Firestore
11. הסרת המשאבים
כדי להימנע מחיובים לא מכוונים (לדוגמה, אם שירותי Cloud Run מופעלים בטעות יותר פעמים מההקצאה החודשית של הפעלות Cloud Run בתוכנית בחינם), אפשר למחוק את Cloud Run או את הפרויקט שיצרתם בשלב 2.
כדי למחוק את שירות Cloud Run, עוברים אל Cloud Run Cloud Console בכתובת https://console.cloud.google.com/run ומוחקים את השירות chat-with-gemini. כדי למנוע קריאות לא מכוונות ל-Gemini, מומלץ גם למחוק את חשבון השירות vertex-ai-caller או לבטל את התפקיד 'משתמש ב-Vertex AI'.
אם אתם רוצים למחוק את הפרויקט כולו, אתם יכולים להיכנס לכתובת https://console.cloud.google.com/cloud-resource-manager, לבחור את הפרויקט שיצרתם בשלב 2 וללחוץ על 'מחיקה'. אם תמחקו את הפרויקט, תצטרכו לשנות את הפרויקטים ב-Cloud SDK. כדי לראות את רשימת כל הפרויקטים הזמינים, מריצים את הפקודה gcloud projects list.