1. מבוא
סקירה כללית
ב-Codelab הזה נסביר איך אפשר לתת ל-Gemini גישה לנתונים בזמן אמת באמצעות תכונה חדשה בשם קריאה לפונקציות. כדי לדמות נתונים בזמן אמת, צריך ליצור נקודת קצה (endpoint) של שירות מזג אוויר שמחזירה את מזג האוויר הנוכחי בשני מיקומים. לאחר מכן בונים אפליקציית צ'אט, שמבוססת על Gemini, שמשתמשת בקריאות פונקציות כדי לאחזר את מזג האוויר הנוכחי.
נשתמש בתצוגה חזותית מהירה כדי להבין את התכונה 'קריאות לפונקציות'.
- הבקשה נשלחת למיקומים שבהם יש מזג אוויר נוכחי במיקום נתון
- ההנחיה הזו + החוזה של הפונקציה getWeather() נשלחים אל Gemini
- Gemini מבקש שאפליקציית הצ'אט בוט תקרא 'getWeather(Seattle)' מטעמו
- האפליקציה שולחת חזרה את התוצאות (40 מעלות F וגשם)
- Gemini שולח בחזרה את התוצאות למי שהתקשר
לסיכום, Gemini לא קורא לפונקציה. כמפתח, אתם צריכים לקרוא לפונקציה ולשלוח את התוצאות בחזרה ל-Gemini.
מה תלמדו
- הסבר על שליחת פונקציות של Gemini
- איך פורסים אפליקציית צ'אט בוט שמבוססת על Gemini בתור שירות Cloud Run
2. הגדרה ודרישות
דרישות מוקדמות
- אתם מחוברים למסוף Cloud.
- פרסתם בעבר פונקציה של דור שני. לדוגמה, תוכלו להיעזר במדריך לפריסת מדריך למתחילים של Cloud Functions דור שני.
הפעלת 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
הגדרה של משתני סביבה
אתם יכולים להגדיר משתני סביבה שישמשו ב-Codelab הזה.
PROJECT_ID=<YOUR_PROJECT_ID> REGION=<YOUR_REGION, e.g. us-central1> WEATHER_SERVICE=weatherservice FRONTEND=frontend SERVICE_ACCOUNT="vertex-ai-caller" SERVICE_ACCOUNT_ADDRESS=$SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com
הפעלת ממשקי API
לפני שתוכלו להשתמש ב-Codelab הזה, יש כמה ממשקי API שתצטרכו להפעיל. ה-Codelab הזה מחייב שימוש בממשקי ה-API הבאים. כדי להפעיל את ממשקי ה-API האלה, מריצים את הפקודה הבאה:
gcloud services enable run.googleapis.com \ cloudbuild.googleapis.com \ aiplatform.googleapis.com
4. צריך ליצור חשבון שירות כדי לקרוא ל-Vertex AI
חשבון השירות הזה ישמש את Cloud Run כדי לקרוא ל-Vertex AI Gemini API.
קודם כול, יוצרים את חשבון השירות באמצעות הפקודה הבאה:
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
5. יצירת שירות Cloud Run לקצה העורפי
קודם כל, יוצרים ספרייה עבור קוד המקור וה-cd בספרייה הזו.
mkdir -p gemini-function-calling/weatherservice gemini-function-calling/frontend && cd gemini-function-calling/weatherservice
לאחר מכן, יוצרים קובץ package.json
עם התוכן הבא:
{ "name": "weatherservice", "version": "1.0.0", "description": "", "main": "app.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "express": "^4.18.3" } }
בשלב הבא, יוצרים קובץ מקור app.js
עם התוכן שלמטה. הקובץ הזה מכיל את נקודת הכניסה לשירות ומכיל את הלוגיקה העיקרית של האפליקציה.
const express = require("express"); const app = express(); app.get("/getweather", (req, res) => { const location = req.query.location; let temp, conditions; if (location == "New Orleans") { temp = 99; conditions = "hot and humid"; } else if (location == "Seattle") { temp = 40; conditions = "rainy and overcast"; } else { res.status(400).send("there is no data for the requested location"); } res.json({ weather: temp, location: location, conditions: conditions }); }); const port = parseInt(process.env.PORT) || 8080; app.listen(port, () => { console.log(`weather service: listening on port ${port}`); }); app.get("/", (req, res) => { res.send("welcome to hard-coded weather!"); });
פריסת שירות מזג האוויר
אפשר להשתמש בפקודה הזו כדי לפרוס את שירות מזג האוויר.
gcloud run deploy $WEATHER_SERVICE \ --source . \ --region $REGION \ --allow-unauthenticated
בדיקת שירות מזג האוויר
אפשר לבדוק את מזג האוויר בשני המיקומים באמצעות curl:
WEATHER_SERVICE_URL=$(gcloud run services describe $WEATHER_SERVICE \ --platform managed \ --region=$REGION \ --format='value(status.url)') curl $WEATHER_SERVICE_URL/getweather?location=Seattle curl $WEATHER_SERVICE_URL/getweather?location\=New%20Orleans
הטמפרטורה בסיאטל היא 40 מעלות צלזיוס, והטמפרטורה בניו אורלינס היא 99 מעלות צלזיוס ולחות תמיד.
6. יצירת שירות Frontend
קודם כול, cd לספריית הקצה הקדמי.
cd gemini-function-calling/frontend
לאחר מכן, יוצרים קובץ package.json
עם התוכן הבא:
{ "name": "demo1", "version": "1.0.0", "description": "", "main": "index.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/vertexai": "^0.4.0", "axios": "^1.6.7", "express": "^4.18.2", "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"); const expressWs = require("express-ws")(app); app.use(express.static("public")); const { VertexAI, FunctionDeclarationSchemaType } = require("@google-cloud/vertexai"); // get project and location from metadata service const metadataService = require("./metadataService.js"); // instance of Gemini model let generativeModel; // 1: define the function const functionDeclarations = [ { function_declarations: [ { name: "getweather", description: "get weather for a given location", parameters: { type: FunctionDeclarationSchemaType.OBJECT, properties: { location: { type: FunctionDeclarationSchemaType.STRING }, degrees: { type: FunctionDeclarationSchemaType.NUMBER, "description": "current temperature in fahrenheit" }, conditions: { type: FunctionDeclarationSchemaType.STRING, "description": "how the weather feels subjectively" } }, required: ["location"] } } ] } ]; // on instance startup const port = parseInt(process.env.PORT) || 8080; app.listen(port, async () => { console.log(`demo1: listening on port ${port}`); 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" }); }); const axios = require("axios"); const baseUrl = "https://weatherservice-k6msmyp47q-uc.a.run.app"; app.ws("/sendMessage", async function (ws, req) { // this chat history will be pinned to the current // Cloud Run instance. Consider using Firestore & // Firebase anonymous auth instead. // start ephemeral chat session with Gemini const chatWithModel = generativeModel.startChat({ tools: functionDeclarations }); ws.on("message", async function (message) { 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); // Function calling demo let response1 = await results.response; let data = response1.candidates[0].content.parts[0]; let methodToCall = data.functionCall; if (methodToCall === undefined) { console.log("Gemini says: ", data.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"> ${data.text} </div>`); // bail out - Gemini doesn't want to return a function return; } // otherwise Gemini wants to call a function console.log( "Gemini wants to call: " + methodToCall.name + " with args: " + util.inspect(methodToCall.args, { showHidden: false, depth: null, colors: true }) ); // make the external call let jsonReturned; try { const responseFunctionCalling = await axios.get( baseUrl + "/" + methodToCall.name, { params: { location: methodToCall.args.location } } ); jsonReturned = responseFunctionCalling.data; } catch (ex) { // in case an invalid location was provided jsonReturned = ex.response.data; } console.log("jsonReturned: ", jsonReturned); // tell the model what function we just called const functionResponseParts = [ { functionResponse: { name: methodToCall.name, response: { name: methodToCall.name, content: { jsonReturned } } } } ]; // // Send a follow up message with a FunctionResponse const result2 = await chatWithModel.sendMessage( functionResponseParts ); // This should include a text response from the model using the response content // provided above const response2 = await result2.response; let answer = response2.candidates[0].content.parts[0].text; console.log("answer: ", answer); 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>`); }); ws.on("close", () => { console.log("WebSocket was closed"); }); }); function sleep(ms) { return new Promise((resolve) => { setTimeout(resolve, ms); }); }
יוצרים קובץ input.css
בשביל tailwindCSS.
@tailwind base; @tailwind components; @tailwind utilities;
יוצרים את הקובץ 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 { // 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 { // 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>`;
יוצרים ספריית 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 2</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 > What's is the current weather in Seattle?</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. הפעלת שירות Frontend באופן מקומי
קודם כל, צריך לוודא שאתם נמצאים בספרייה frontend
של ה-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
הפעלה מקומית של האפליקציה
לסיום, אפשר להפעיל את האפליקציה על ידי הרצת הסקריפט הבא. סקריפט הפיתוח הזה יפיק גם את הקובץoutput.css מ-tailwindCSS.
npm run dev
כדי לראות תצוגה מקדימה של האתר, אפשר לפתוח את לחצן Web Preview ולבחור באפשרות Preview Port 8080
8. פריסה ובדיקה של שירות Frontend
קודם כול מריצים את הפקודה הזו כדי להתחיל את הפריסה ולציין את חשבון השירות שבו רוצים להשתמש. אם לא מציינים חשבון שירות, ייעשה שימוש בחשבון השירות שמשמש כברירת מחדל ב-Compute Engine.
gcloud run deploy $FRONTEND \ --service-account $SERVICE_ACCOUNT_ADDRESS \ --source . \ --region $REGION \ --allow-unauthenticated
פותחים את כתובת ה-URL של השירות עבור ממשק הקצה בדפדפן. אפשר לשאול "What is the current weather in Seattle? " ו-Gemini צריך להשיב "עכשיו 40 מעלות וגשם". אם שואלים "What is the current weather in Boston? ", התשובה של Gemini היא "אין לי אפשרות למלא את הבקשה הזו. ה-API הזמין של מזג האוויר לא מכיל נתונים עבור בוסטון."
9. מעולה!
מזל טוב, השלמת את Codelab!
מומלץ לעיין במסמכים בנושא Cloud Run, Vertex AI Gemini APIs ושליחת קריאות לפעולות.
אילו נושאים דיברנו?
- הסבר על שליחת פונקציות של Gemini
- איך פורסים אפליקציית צ'אט בוט שמבוססת על Gemini בתור שירות Cloud Run
10. הסרת המשאבים
כדי להימנע מחיובים לא מכוונים (למשל, אם השירות הזה של Cloud Run מופעל בטעות יותר פעמים מהקצאת ההפעלה החודשית של Cloud Run בתוכנית ללא תשלום), אפשר למחוק את השירות Cloud Run או למחוק את הפרויקט שיצרתם בשלב 2.
כדי למחוק את שירותי Cloud Run, עוברים אל Cloud Run Cloud Console בכתובת https://console.cloud.google.com/functions/ ומוחקים את השירותים $WEATHER_SERVICE ו-$FRONTEND שיצרתם ב-Codelab הזה.
כדאי גם למחוק את חשבון השירות vertex-ai-caller
או לבטל את התפקיד Vertex AI User כדי למנוע קריאות לא מכוונות ל-Gemini.
אם בוחרים למחוק את הפרויקט כולו, נכנסים לכתובת https://console.cloud.google.com/cloud-resource-manager, בוחרים את הפרויקט שיצרתם בשלב 2 ובוחרים באפשרות 'מחיקה'. אם תמחקו את הפרויקט, יהיה צריך לבצע שינויים בפרויקטים ב-Cloud SDK. כדי להציג את הרשימה של כל הפרויקטים הזמינים, אפשר להריץ את הפקודה gcloud projects list
.