1. מבוא
סקירה כללית
ב-Codelab הזה נסביר איך אפשר לתת ל-Gemini גישה לנתונים בזמן אמת באמצעות תכונה חדשה שנקראת בקשה להפעלת פונקציה. כדי לדמות נתונים בזמן אמת, תבנו נקודת קצה של שירות מזג אוויר שמחזירה את מזג האוויר הנוכחי בשני מיקומים. לאחר מכן, תיצרו אפליקציית צ'אט שמבוססת על Gemini ומשתמשת בקריאה לפונקציה כדי לאחזר את נתוני מזג האוויר הנוכחיים.
כדי להבין את התכונה Function Calling, נשתמש בהדגמה ויזואלית קצרה.
- ההנחיה מבקשת מידע על מזג האוויר במיקום מסוים
- ההנחיה הזו + חוזה הפונקציה של getWeather() נשלחים ל-Gemini
- Gemini מבקש מאפליקציית הצ'אט בוט להתקשר אל getWeather(Seattle) בשמו
- האפליקציה מחזירה את התוצאות (40 מעלות פרנהייט וגשם)
- Gemini מחזיר את התוצאות למתקשר
לסיכום, Gemini לא מפעיל את הפונקציה. המפתח צריך לקרוא לפונקציה ולשלוח את התוצאות בחזרה ל-Gemini.

מה תלמדו
- איך פועלת בקשה להפעלת פונקציה ב-Gemini
- איך פורסים אפליקציית צ'אטבוט מבוססת Gemini כשירות Cloud Run
2. הגדרה ודרישות
דרישות מוקדמות
- אתם מחוברים ל-Cloud Console.
- כבר פרסתם פונקציה מדור שני. לדוגמה, אפשר לפעול לפי ההוראות שבמדריך למתחילים של Cloud Functions מדור שני כדי להתחיל.
הפעלת 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
הגדרה של משתני סביבה
אתם יכולים להגדיר משתני סביבה שישמשו אתכם לאורך כל ה-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
קודם יוצרים ספרייה לקוד המקור ועוברים לספרייה הזו.
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
בסיאטל הטמפרטורה היא 4 מעלות צלזיוס ויורד גשם, ובניו אורלינס הטמפרטורה היא 37 מעלות צלזיוס והלחות גבוהה, תמיד.
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'. אם לזהות שלכם יש תפקיד של בעלים, כבר יש לכם את תפקיד המשתמש הזה ב-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
הפעלת האפליקציה באופן מקומי
לבסוף, מריצים את הסקריפט הבא כדי להפעיל את האפליקציה. סקריפט הפיתוח הזה ייצור גם את הקובץ output.css מ-tailwindCSS.
npm run dev
כדי לראות תצוגה מקדימה של האתר, פותחים את הלחצן Web Preview (תצוגה מקדימה של אתר) ובוחרים באפשרות Preview Port 8080 (תצוגה מקדימה של יציאה 8080).

8. פריסה ובדיקה של שירות הקצה הקדמי
קודם מריצים את הפקודה הזו כדי להתחיל את הפריסה ולציין את חשבון השירות שבו רוצים להשתמש. אם לא מציינים חשבון שירות, המערכת משתמשת בחשבון השירות שמוגדר כברירת מחדל בשירות Compute.
gcloud run deploy $FRONTEND \ --service-account $SERVICE_ACCOUNT_ADDRESS \ --source . \ --region $REGION \ --allow-unauthenticated
פותחים את כתובת ה-URL של השירות לחלק הקדמי בדפדפן. שואלים את השאלה "What is the current weather in Seattle?" (מה מזג האוויר הנוכחי בסיאטל?) ו-Gemini אמור לענות "It is currently 40 degrees and rainy" (כרגע 40 מעלות ויש גשם). אם שואלים "מה מזג האוויר הנוכחי בבוסטון?" 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 הזה.
כדי למנוע קריאות לא מכוונות ל-Gemini, מומלץ גם למחוק את חשבון השירות vertex-ai-caller או לבטל את התפקיד 'משתמש ב-Vertex AI'.
אם אתם רוצים למחוק את הפרויקט כולו, אתם יכולים להיכנס לכתובת https://console.cloud.google.com/cloud-resource-manager, לבחור את הפרויקט שיצרתם בשלב 2 וללחוץ על 'מחיקה'. אם תמחקו את הפרויקט, תצטרכו לשנות את הפרויקטים ב-Cloud SDK. כדי לראות את רשימת כל הפרויקטים הזמינים, מריצים את הפקודה gcloud projects list.