1. مقدمة
نظرة عامة
في هذا الدرس التطبيقي حول الترميز، ستتعرّف على كيفية إنشاء روبوت دردشة أساسي مكتوب بلغة Node باستخدام Vertex AI Gemini API ومكتبة عملاء Vertex AI. يستخدم هذا التطبيق مخزن جلسات سريعًا يستند إلى 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، انقر على تفعيل 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- تفعيل واجهات برمجة التطبيقات وضبط متغيرات البيئة
تفعيل واجهات برمجة التطبيقات
قبل البدء في استخدام هذا الدرس التطبيقي حول الترميز، عليك تفعيل العديد من واجهات برمجة التطبيقات. يتطلّب هذا الدرس التطبيقي حول الترميز استخدام واجهات برمجة التطبيقات التالية. يمكنك تفعيل واجهات برمجة التطبيقات هذه من خلال تنفيذ الأمر التالي:
gcloud services enable run.googleapis.com \
cloudbuild.googleapis.com \
aiplatform.googleapis.com \
secretmanager.googleapis.com
إعداد متغيرات البيئة
يمكنك ضبط متغيّرات البيئة التي سيتم استخدامها في جميع مراحل هذا الدرس البرمجي.
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" لهذا الدرس العملي.
- انقر على إضافة Firebase.
- بعد إنشاء المشروع، انقر على متابعة.
- من قائمة إنشاء، انقر على قاعدة بيانات 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".
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 لبرنامج التدريب العملي.
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
إنشاء سرّ جلسة محلّي
الآن، أنشئ سر جلسة محلية للتطوير على الجهاز.
export SESSION_SECRET=local-secret
تشغيل التطبيق محليًا
أخيرًا، يمكنك بدء تشغيل التطبيق من خلال تنفيذ النص البرمجي التالي. سينشئ هذا النص البرمجي أيضًا ملف output.css من tailwindCSS.
npm run dev
يمكنك معاينة موقع الويب من خلال فتح الزر "معاينة الويب" واختيار "معاينة المنفذ 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"
إذا ظهرت لك الرسالة "يتطلّب النشر من المصدر مستودع Artifact Registry Docker لتخزين الحاويات التي تم إنشاؤها". سيتم إنشاء مستودع باسم [cloud-run-source-deploy] في المنطقة [us-central1]، اضغط على "y" للقبول والمتابعة.
9- اختبار الخدمة
بعد نشر الخدمة، افتح عنوان URL الخاص بها في متصفّح الويب. بعد ذلك، اطرح سؤالاً على Gemini، مثل "أتدرب على العزف على الغيتار وأعمل أيضًا كمهندس برمجيات. عندما أرى "C#"، هل يجب أن أعتبرها لغة برمجة أم نوتة موسيقية؟ أيّ منهما يجب أن أختار؟"
10. تهانينا!
تهانينا على إكمال هذا الدرس العملي.
ننصحك بمراجعة المستندات Cloud Run وواجهات برمجة التطبيقات في Vertex AI Gemini.
المواضيع التي تناولناها
- كيفية استخدام 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. يمكنك أيضًا حذف حساب الخدمة vertex-ai-caller أو إبطال دور "مستخدم Vertex AI" لتجنُّب أي طلبات غير مقصودة إلى Gemini.
إذا اخترت حذف المشروع بأكمله، يمكنك الانتقال إلى https://console.cloud.google.com/cloud-resource-manager، واختيار المشروع الذي أنشأته في الخطوة 2، ثم النقر على "حذف". إذا حذفت المشروع، عليك تغيير المشاريع في Cloud SDK. يمكنك الاطّلاع على قائمة بجميع المشاريع المتاحة من خلال تنفيذ gcloud projects list.