1. מבוא
סקירה כללית
בשיעור Lab הזה תלמדו איך ליצור צ'אט בוט בסיסי שנכתב בצומת באמצעות Vertex AI Gemini API וספריית הלקוח של Vertex AI. האפליקציה הזו משתמשת בחנות לסשנים של Express שמגובה על ידי Google Cloud Firestore.
מה תלמדו
- איך להשתמש ב-htmx, ב-tailwindcss וב-exp.js כדי ליצור שירות Cloud Run
- איך משתמשים בספריות הלקוח של Vertex AI כדי לבצע אימות ל-Google APIs
- איך יוצרים צ'אט בוט לאינטראקציה עם המודל של Gemini
- איך לפרוס בשירות שפועל בענן ללא קובץ Docer
- איך משתמשים במאגר סשנים אקספרס שמגובה על ידי Google Cloud Firestore
2. הגדרה ודרישות
דרישות מוקדמות
- אתם מחוברים למסוף Cloud.
- פרסתם בעבר שירות של Cloud Run. לדוגמה, אפשר להיעזר במדריך למתחילים לפריסת שירות אינטרנט מקוד מקור כדי להתחיל בעבודה.
הפעלת Cloud Shell
- במסוף Cloud, לוחצים על 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 ומאשרים אותם.
- לוחצים על המשך.
- לוחצים על Confirm Plan (אישור התוכנית) כדי לאשר את תוכנית החיוב של Firebase.
- לא חובה להפעיל את Google Analytics ב-Codelab הזה.
- לוחצים על הוספת Firebase.
- לאחר יצירת הפרויקט, לוחצים על Continue.
- בתפריט Build, לוחצים על Firestore repository.
- לוחצים על Create dataset.
- בוחרים את האזור שלכם מהתפריט הנפתח מיקום ולוחצים על הבא.
- משתמשים באפשרות ברירת המחדל Start inProduction mode ואז לוחצים על Create.
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=-
ומעניקים לחשבון השירות גישה לסוד הסשן האקספרס ב-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
עבור ממשק הקצה, שישתמש ב-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 User. אם לזהות שלכם יש תפקיד 'בעלים', כבר יש לכם את תפקיד המשתמש הזה ב-Vertex AI. אם לא, תוכלו להריץ את הפקודה הזו כדי להקצות לזהות שלכם את תפקיד המשתמש ב-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
הפעלה מקומית של האפליקציה
לסיום, אפשר להפעיל את האפליקציה על ידי הרצת הסקריפט הבא. הסקריפט הזה גם יפיק את הקובץoutput.css מ-tailwindCSS.
npm run dev
כדי לראות תצוגה מקדימה של האתר, אפשר לפתוח את לחצן Web Preview ולבחור באפשרות Preview Port 8080
8. פריסת השירות
קודם כול מריצים את הפקודה הזו כדי להתחיל את הפריסה ולציין את חשבון השירות שבו רוצים להשתמש. אם לא מציינים חשבון שירות, ייעשה שימוש בחשבון השירות שמשמש כברירת מחדל ב-Compute Engine.
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 וב-exp.js כדי ליצור שירות Cloud Run
- איך משתמשים בספריות הלקוח של Vertex AI כדי לבצע אימות ל-Google APIs
- איך יוצרים צ'אט בוט לאינטראקציה עם המודל של Gemini
- איך לפרוס בשירות שפועל בענן ללא קובץ Docer
- איך משתמשים במאגר סשנים אקספרס שמגובה על ידי Google Cloud Firestore
11. הסרת המשאבים
כדי להימנע מחיובים לא מכוונים (לדוגמה, אם שירותי Cloud Run מופעלים בטעות יותר פעמים מהקצאת ההפעלה החודשית של Cloud Run בתוכנית ללא תשלום), אפשר למחוק את Cloud Run או למחוק את הפרויקט שיצרתם בשלב 2.
כדי למחוק את השירות Cloud Run, צריך להיכנס למסוף Cloud Run Cloud בכתובת https://console.cloud.google.com/run ולמחוק את השירות chat-with-gemini
. כדאי גם למחוק את חשבון השירות vertex-ai-caller
או לבטל את התפקיד Vertex AI User כדי למנוע קריאות לא מכוונות ל-Gemini.
אם בוחרים למחוק את הפרויקט כולו, נכנסים לכתובת https://console.cloud.google.com/cloud-resource-manager, בוחרים את הפרויקט שיצרתם בשלב 2 ובוחרים באפשרות 'מחיקה'. אם תמחקו את הפרויקט, יהיה צריך לבצע שינויים בפרויקטים ב-Cloud SDK. כדי להציג את הרשימה של כל הפרויקטים הזמינים, אפשר להריץ את הפקודה gcloud projects list
.