كيفية استخدام ميزة "التشغيل السحابي" مع ميزة "الاستدعاء من خلال وظيفة Gemini"

1. مقدمة

نظرة عامة

في هذا الدرس التطبيقي حول الترميز، ستتعرّف على كيفية منح Gemini إذن الوصول إلى البيانات في الوقت الفعلي باستخدام ميزة جديدة تُعرف باسم دالّة الاستدعاء. لمحاكاة البيانات في الوقت الفعلي، ستنشئ نقطة نهاية لخدمة الطقس تعرض الطقس الحالي لموقعَين. بعد ذلك، ستنشئ تطبيقًا للمحادثات من Gemini يستخدم الاتصال الوظيفي للاطّلاع على حالة الطقس الحالية.

لنستخدم صورة مرئية سريعة لفهم دالة الاستدعاء.

  • الطلبات الفورية للمواقع الجغرافية الحالية للطقس في موقع جغرافي معيّن
  • يتم إرسال هذا الطلب + عقد الدالة getWeather() إلى Gemini
  • Gemini يطلب من تطبيق الدردشة المبرمَجة تسمية "getWeather(سياتل)" نيابةً عنه
  • يرسل التطبيق النتائج مرة أخرى (40 درجة فهرنهايت ومطر ممطر)
  • Gemini يرسل النتائج إلى المتصل

باختصار، لا يستدعي Gemini الدالة. وعليك بصفتك المطوّر استدعاء الدالة وإعادة إرسال النتائج إلى Gemini.

رسم تخطيطي لمسار استدعاء الدالة

المعلومات التي ستطّلع عليها

  • طريقة عمل الاتصال بالوظائف في Gemini
  • كيفية نشر تطبيق دردشة مبرمجة مستند إلى Gemini كخدمة تشغيل في السحابة الإلكترونية

2. الإعداد والمتطلبات

المتطلبات الأساسية

تفعيل Cloud Shell

  1. من Cloud Console، انقر على تفعيل Cloud Shell d1264ca30785e435.png.

cb81e7c8e34bc8d.png

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

d95252b003979716.png

من المفترَض أن تستغرق عملية إدارة الحسابات والاتصال بخدمة Cloud Shell بضع دقائق فقط.

7833d5e1c5d18f54.png

يتم تحميل هذا الجهاز الافتراضي مع جميع أدوات التطوير اللازمة. وتوفّر هذه الشبكة دليلاً رئيسيًا دائمًا بسعة 5 غيغابايت ويتم تشغيله في Google Cloud، ما يحسّن بشكل كبير من أداء الشبكة والمصادقة. يمكنك تنفيذ معظم عملك، إن لم يكن كلّه، في هذا الدرس التطبيقي حول الترميز باستخدام متصفّح.

بعد الربط بخدمة Cloud Shell، من المفترض أن تتأكّد من أنّه تمّت مصادقتك وأنّ المشروع مضبوط على رقم تعريف مشروعك.

  1. شغِّل الأمر التالي في 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`
  1. شغّل الأمر التالي في 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

سياتل

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&apos;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 ودور مستخدم 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

تشغيل التطبيق على الجهاز

وأخيرًا، يمكنك تشغيل التطبيق من خلال تشغيل النص البرمجي التالي. سينشئ هذا النص البرمجي لمطوِّر أيضًا ملف exit.css من tailwindCSS.

npm run dev

يمكنك معاينة الموقع الإلكتروني من خلال فتح زر "معاينة الويب" واختيار "معاينة المنفذ 8080"

معاينة الويب - زر المعاينة على المنفذ 8080

8. نشر خدمة الواجهة الأمامية واختبارها

أولاً، شغِّل هذا الأمر لبدء عملية النشر واختَر حساب الخدمة المطلوب استخدامه. إذا لم يتم تحديد حساب خدمة، يتم استخدام حساب خدمة 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 APIs وطلب الوظائف.

النقاط التي تناولناها

  • طريقة عمل الاتصال بالوظائف في Gemini
  • كيفية نشر تطبيق دردشة مبرمجة مستند إلى Gemini كخدمة تشغيل في السحابة الإلكترونية

10. تَنظيم

لتجنُّب تحصيل رسوم غير مقصودة، (على سبيل المثال، إذا تم استدعاء خدمة Cloud Run هذه عن غير قصد أكثر من تخصيص استدعاء Cloud Run الشهري في الفئة المجانية)، يمكنك إما حذف خدمة Cloud Run أو حذف المشروع الذي أنشأته في الخطوة 2.

لحذف خدمات Cloud Run، انتقِل إلى Cloud Run Console على الرابط https://console.cloud.google.com/functions/ واحذف الخدمتَين $WEATHER_SERVICE و $FRONTEND اللتين أنشأتهما ضِمن هذا الدرس التطبيقي حول الترميز.

يمكنك أيضًا حذف حساب خدمة "vertex-ai-caller" أو إبطال دور مستخدم Vertex AI، وذلك لتجنُّب تلقّي أي مكالمات غير مقصودة إلى Gemini.

إذا اخترت حذف المشروع بالكامل، يمكنك الانتقال إلى https://console.cloud.google.com/cloud-resource-manager، واختيار المشروع الذي أنشأته في الخطوة الثانية، ثم اختيار "حذف". إذا حذفت المشروع، ستحتاج إلى تغيير المشاريع في حزمة تطوير البرامج (SDK) للسحابة الإلكترونية. يمكنك عرض قائمة بجميع المشاريع المتاحة من خلال تشغيل gcloud projects list.