1. مقدمة
نظرة عامة
في هذا الدرس التطبيقي حول الترميز، ستتعرّف على كيفية إنشاء برنامج تتبُّع أساسي للمحادثات مكتوب في العقدة باستخدام Vertex AI Gemini API ومكتبة برامج Vertex AI. يستخدم هذا التطبيق متجر جلسات Express مدعومًا من Google Cloud Firestore.
المعلومات التي ستطّلع عليها
- كيفية استخدام htmx وtailwindcss وexpress.js لإنشاء خدمة تشغيل السحابة
- كيفية استخدام مكتبات عملاء Vertex AI للمصادقة على واجهات Google APIs
- كيفية إنشاء روبوت دردشة للتفاعل مع نموذج Gemini
- كيفية النشر إلى خدمة تعمل على السحابة الإلكترونية بدون ملف Docker
- كيفية استخدام متجر جلسات سريعة مدعوم من 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 إلى هذا المفتاح السرّي كمتغيرات للبيئة، حيث يمكن حله في وقت بدء التشغيل على سبيل المثال. يمكنك التعرّف على مزيد من المعلومات حول العمليات السرية وتشغيل السحابة الإلكترونية.
gcloud secrets create $SECRET_ID --replication-policy="automatic" printf "keyboard-cat" | gcloud secrets versions add $SECRET_ID --data-file=-
وامنح حساب الخدمة إذن الوصول إلى سر الجلسة السريع في "المدير السري".
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 ودور مستخدم Datastore) أو 2) تسجيل الدخول من خلال انتحال هوية حساب الخدمة المستخدَم في هذا الدرس التطبيقي حول الترميز.
الخيار 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
إذا أردت استخدام حساب الخدمة الذي تم إنشاؤه في هذا الدرس التطبيقي حول الترميز، يجب أن يكون لحساب المستخدم دور "منشئ الرموز المميّزة لحساب الخدمة". يمكنك الحصول على هذا الدور من خلال تنفيذ الأمر التالي:
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
تشغيل التطبيق على الجهاز
وأخيرًا، يمكنك تشغيل التطبيق من خلال تشغيل النص البرمجي التالي. سينشئ هذا النص البرمجي أيضًا ملف exit.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، افتح عنوان URL الخاص بالخدمة في متصفّح الويب. بعد ذلك، يمكنك طرح سؤال على Gemini، مثلاً "أمارس العزف على الغيتار، ولكني أيضًا مهندسة برمجيات. عندما أرى "C# "، هل يجب أن أعتبرها لغة برمجة أم نوتة موسيقية؟ أيهما ينبغي أن أختار؟"
10. تهانينا
تهانينا على إكمال الدرس التطبيقي حول الترميز.
ننصحك بمراجعة المستندَين Cloud Run وVertex AI Gemini API.
النقاط التي تناولناها
- كيفية استخدام htmx وtailwindcss وexpress.js لإنشاء خدمة تشغيل السحابة
- كيفية استخدام مكتبات عملاء Vertex AI للمصادقة على واجهات Google APIs
- كيفية إنشاء روبوت دردشة للتفاعل مع نموذج Gemini
- كيفية النشر إلى خدمة تعمل على السحابة الإلكترونية بدون ملف Docker
- كيفية استخدام متجر جلسات سريعة مدعوم من Google Cloud Firestore
11. تَنظيم
لتجنب دفع رسوم غير مقصودة، (على سبيل المثال، إذا تم استدعاء خدمات Cloud Run عن غير قصد أكثر من تخصيص استدعاء Cloud Run الشهري في الفئة المجانية)، يمكنك إما حذف Cloud Run أو حذف المشروع الذي أنشأته في الخطوة 2.
لحذف خدمة Cloud Run، انتقِل إلى Cloud Run Console على https://console.cloud.google.com/run واحذف خدمة chat-with-gemini
. يمكنك أيضًا حذف حساب خدمة "vertex-ai-caller
" أو إبطال دور مستخدم Vertex AI، وذلك لتجنُّب تلقّي أي مكالمات غير مقصودة إلى Gemini.
إذا اخترت حذف المشروع بالكامل، يمكنك الانتقال إلى https://console.cloud.google.com/cloud-resource-manager، واختيار المشروع الذي أنشأته في الخطوة الثانية، ثم اختيار "حذف". إذا حذفت المشروع، ستحتاج إلى تغيير المشاريع في حزمة تطوير البرامج (SDK) للسحابة الإلكترونية. يمكنك عرض قائمة بجميع المشاريع المتاحة من خلال تشغيل gcloud projects list
.