1. مقدمة
نظرة عامة
في هذا الدرس التطبيقي حول الترميز، ستتعرّف على كيفية منح Gemini إذن الوصول إلى البيانات في الوقت الفعلي باستخدام ميزة جديدة تُسمى استدعاء الدوال. لمحاكاة البيانات في الوقت الفعلي، عليك إنشاء نقطة نهاية لخدمة الطقس تعرض حالة الطقس الحالية في موقعَين جغرافيَين. بعد ذلك، ستنشئ تطبيق دردشة يستند إلى Gemini ويستخدم ميزة "استدعاء الدوال" لاسترداد حالة الطقس الحالية.
لنستعرض سريعًا صورة توضيحية لفهم ميزة "استدعاء الدوال".
- يطلب الطلب الحصول على مواقع جغرافية حالية للطقس في موقع جغرافي معيّن
- يتم إرسال هذا الطلب بالإضافة إلى عقد الدالة getWeather() إلى Gemini
- يطلب Gemini من تطبيق روبوت الدردشة إجراء مكالمة إلى "getWeather(Seattle)" نيابةً عنه
- يرسل التطبيق النتائج (40 درجة فهرنهايت وممطر)
- يرسل Gemini النتائج إلى المتصل
باختصار، لا يستدعي Gemini الدالة. عليك كمطوّر استدعاء الدالة وإرسال النتائج إلى Gemini.

ما ستتعلمه
- طريقة عمل ميزة استدعاء الدوال في Gemini
- كيفية نشر تطبيق روبوت دردشة مستنِد إلى Gemini كخدمة Cloud Run
2. الإعداد والمتطلبات
المتطلبات الأساسية
- يجب أن تكون مسجّلاً الدخول إلى Cloud Console.
- سبق لك نشر دالة من الجيل الثاني. على سبيل المثال، يمكنك اتّباع دليل البدء السريع في الجيل الثاني من Cloud Functions للبدء.
تفعيل Cloud Shell
- من Cloud Console، انقر على تفعيل Cloud Shell
.

إذا كانت هذه هي المرة الأولى التي تبدأ فيها Cloud Shell، ستظهر لك شاشة وسيطة توضّح ماهيتها. إذا ظهرت لك شاشة وسيطة، انقر على متابعة.

يستغرق توفير Cloud Shell والاتصال به بضع لحظات فقط.

يتم تحميل هذا الجهاز الافتراضي بجميع أدوات التطوير اللازمة. توفّر هذه الخدمة دليلًا رئيسيًا دائمًا بسعة 5 غيغابايت وتعمل في Google Cloud، ما يؤدي إلى تحسين أداء الشبكة والمصادقة بشكل كبير. يمكن إنجاز معظم عملك في هذا الدرس العملي، إن لم يكن كله، باستخدام متصفح.
بعد الاتصال بـ 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- إعداد متغيّرات البيئة وتفعيل واجهات برمجة التطبيقات
إعداد متغيرات البيئة
يمكنك ضبط متغيّرات البيئة التي سيتم استخدامها في جميع مراحل هذا الدرس البرمجي.
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
تفعيل واجهات برمجة التطبيقات
قبل البدء في استخدام هذا الدرس التطبيقي حول الترميز، عليك تفعيل العديد من واجهات برمجة التطبيقات. يتطلّب هذا الدرس التطبيقي حول الترميز استخدام واجهات برمجة التطبيقات التالية. يمكنك تفعيل واجهات برمجة التطبيقات هذه من خلال تنفيذ الأمر التالي:
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".
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
تبلغ درجة الحرارة في سياتل 40 درجة فهرنهايت مع هطول الأمطار، بينما تبلغ في نيو أورلينز 99 درجة فهرنهايت مع رطوبة عالية دائمًا.
6. إنشاء خدمة الواجهة الأمامية
أولاً، انتقِل إلى دليل الواجهة الأمامية.
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 الخاص ببرنامج التدريب العملي.
cd .. && pwd
بعد ذلك، ثبِّت التبعيات من خلال تنفيذ الأمر التالي:
npm install
استخدام ADC عند التشغيل محليًا
إذا كنت تستخدم Cloud Shell، يعني ذلك أنّك تستخدم جهازًا افتراضيًا على Google Compute Engine. سيتم تلقائيًا استخدام بيانات الاعتماد المرتبطة بهذا الجهاز الافتراضي (كما هو موضّح عند تنفيذ gcloud auth list) من خلال "بيانات الاعتماد التلقائية للتطبيق"، لذلك ليس من الضروري استخدام الأمر gcloud auth application-default login. يمكنك الانتقال إلى القسم تشغيل التطبيق على الجهاز.
ومع ذلك، إذا كنت تستخدم الجهاز الطرفي المحلي (أي ليس في Cloud Shell)، عليك استخدام بيانات الاعتماد التلقائية للتطبيق للمصادقة على Google APIs. يمكنك إما 1) تسجيل الدخول باستخدام بيانات الاعتماد (شرط أن يكون لديك دورا "مستخدم Vertex AI" و"مستخدم مخزن البيانات") أو 2) تسجيل الدخول من خلال انتحال هوية حساب الخدمة المستخدَم في هذا الدرس التطبيقي حول الترميز.
الخيار 1: استخدام بيانات اعتمادك في "مركز مطوّري التطبيقات"
إذا أردت استخدام بيانات الاعتماد، يمكنك أولاً تشغيل gcloud auth list للتحقّق من طريقة مصادقتك في gcloud. بعد ذلك، قد تحتاج إلى منح هويتك دور "مستخدم Vertex AI". إذا كانت هويتك تتضمّن دور "المالك"، يكون لديك دور مستخدم 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
إذا كنت تريد استخدام حساب الخدمة الذي تم إنشاؤه في هذا الدرس التطبيقي حول الترميز، يجب أن يتضمّن حساب المستخدم دور "منشئ الرموز المميزة لحساب الخدمة". يمكنك الحصول على هذا الدور من خلال تنفيذ الأمر التالي:
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
يمكنك معاينة موقع الويب من خلال فتح الزر "معاينة الويب" واختيار "معاينة المنفذ 8080".

8. تفعيل خدمة Frontend واختبارها
أولاً، شغِّل هذا الأمر لبدء عملية النشر وحدِّد حساب الخدمة الذي سيتم استخدامه. في حال عدم تحديد حساب خدمة، يتم استخدام حساب خدمة Compute Engine التلقائي.
gcloud run deploy $FRONTEND \ --service-account $SERVICE_ACCOUNT_ADDRESS \ --source . \ --region $REGION \ --allow-unauthenticated
افتح عنوان URL للخدمة الخاصة بالواجهة الأمامية في المتصفّح. اطرح سؤالاً مثل "ما هي حالة الطقس الحالية في دبي؟" ومن المفترض أن يردّ Gemini قائلاً "تبلغ درجة الحرارة حاليًا 40 درجة مئوية والطقس ممطر". إذا سألت "ما هي حالة الطقس الحالية في الرياض؟"، سيردّ Gemini بعبارة "لا يمكنني تلبية هذا الطلب. لا يتضمّن واجهة برمجة التطبيقات المتاحة الخاصة بالطقس بيانات عن بوسطن."
9- تهانينا!
تهانينا على إكمال هذا الدرس العملي.
ننصحك بمراجعة المستندات Cloud Run وواجهات برمجة التطبيقات Vertex AI Gemini واستدعاء الدوال.
المواضيع التي تناولناها
- طريقة عمل ميزة استدعاء الدوال في 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 التي أنشأتها في هذا الدرس التطبيقي حول الترميز.
يمكنك أيضًا حذف حساب الخدمة vertex-ai-caller أو إبطال دور "مستخدم Vertex AI" لتجنُّب أي طلبات غير مقصودة إلى Gemini.
إذا اخترت حذف المشروع بأكمله، يمكنك الانتقال إلى https://console.cloud.google.com/cloud-resource-manager، واختيار المشروع الذي أنشأته في الخطوة 2، ثم النقر على "حذف". إذا حذفت المشروع، عليك تغيير المشاريع في Cloud SDK. يمكنك الاطّلاع على قائمة بجميع المشاريع المتاحة من خلال تنفيذ gcloud projects list.