জেমিনি (পাইথন) এর সাথে ক্লাউডে মাল্টিমোডাল সহকারী তৈরি করুন এবং স্থাপন করুন, জেমিনি (পাইথন) এর সাথে ক্লাউডে মাল্টিমোডাল সহকারী তৈরি করুন এবং স্থাপন করুন, জেমিনি (পাইথন) এর সাথে ক্লাউডে মাল্টিমোডাল সহকারী তৈরি করুন এবং স্থাপন করুন

১. ভূমিকা

এই কোডল্যাবে, আপনি একটি চ্যাট ওয়েব ইন্টারফেসের আকারে একটি অ্যাপ্লিকেশন তৈরি করবেন, যার মাধ্যমে আপনি যোগাযোগ করতে, কিছু ডকুমেন্ট বা ছবি আপলোড করতে এবং সেগুলো নিয়ে আলোচনা করতে পারবেন। অ্যাপ্লিকেশনটি ফ্রন্টএন্ড এবং ব্যাকএন্ড—এই দুটি সার্ভিসে বিভক্ত; যা আপনাকে দ্রুত একটি প্রোটোটাইপ তৈরি করতে এবং এর কার্যকারিতা পরখ করে দেখতে সক্ষম করবে। এছাড়াও, এই দুটিকে একত্রিত করার জন্য এপিআই কন্ট্রাক্টটি কেমন হবে, তাও আপনি বুঝতে পারবেন।

কোডল্যাবের মাধ্যমে, আপনি নিম্নলিখিত ধাপে ধাপে পদ্ধতিটি অনুসরণ করবেন:

  1. আপনার গুগল ক্লাউড প্রজেক্টটি প্রস্তুত করুন এবং এতে প্রয়োজনীয় সকল এপিআই (API) সক্রিয় করুন।
  2. Gradio লাইব্রেরি ব্যবহার করে ফ্রন্টএন্ড সার্ভিস - চ্যাট ইন্টারফেস তৈরি করুন।
  3. FastAPI ব্যবহার করে ব্যাকএন্ড সার্ভিস (HTTP সার্ভার) তৈরি করুন, যা আগত ডেটাকে Gemini SDK স্ট্যান্ডার্ড অনুযায়ী রিফর্ম্যাট করবে এবং Gemini API-এর সাথে যোগাযোগ সক্ষম করবে।
  4. ক্লাউড রানে অ্যাপ্লিকেশনটি ডেপ্লয় করার জন্য প্রয়োজনীয় এনভায়রনমেন্ট ভেরিয়েবল ও ফাইলগুলো সেটআপ করুন।
  5. অ্যাপ্লিকেশনটি ক্লাউড রানে স্থাপন করুন

5bcfa1cce6618305.png

স্থাপত্যের সংক্ষিপ্ত বিবরণ

b102df2c3f1adabf.jpeg

পূর্বশর্ত

আপনি যা শিখবেন

  • জেমিনি এসডিকে ব্যবহার করে কীভাবে টেক্সট এবং অন্যান্য ডেটা টাইপ (মাল্টিমোডাল) জমা দেওয়া যায় এবং টেক্সট প্রতিক্রিয়া তৈরি করা যায়
  • কথোপকথনের প্রেক্ষাপট বজায় রাখতে জেমিনি এসডিকে-তে চ্যাট হিস্ট্রি কীভাবে গঠন করবেন
  • Gradio দিয়ে ফ্রন্টএন্ড ওয়েব প্রোটোটাইপিং
  • FastAPI এবং Pydantic ব্যবহার করে ব্যাকএন্ড পরিষেবা উন্নয়ন
  • Pydantic-settings ব্যবহার করে YAML ফাইলে এনভায়রনমেন্ট ভেরিয়েবল পরিচালনা করুন
  • Dockerfile ব্যবহার করে Cloud Run-এ অ্যাপ্লিকেশনটি ডেপ্লয় করুন এবং YAML ফাইলের মাধ্যমে এনভায়রনমেন্ট ভেরিয়েবল সরবরাহ করুন।

আপনার যা যা লাগবে

  • ক্রোম ওয়েব ব্রাউজার
  • একটি জিমেইল অ্যাকাউন্ট
  • বিলিং সক্ষম একটি ক্লাউড প্রজেক্ট

সকল স্তরের (শিক্ষানবিশ সহ) ডেভেলপারদের জন্য ডিজাইন করা এই কোডল্যাবটির নমুনা অ্যাপ্লিকেশনে পাইথন ব্যবহার করা হয়েছে। তবে, এখানে উপস্থাপিত ধারণাগুলো বোঝার জন্য পাইথন জ্ঞানের প্রয়োজন নেই।

২. শুরু করার আগে

ক্লাউড শেল এডিটরে ক্লাউড প্রজেক্ট সেটআপ করুন

এই কোডল্যাবটি ধরে নেয় যে আপনার ইতিমধ্যেই বিলিং চালু করা একটি গুগল ক্লাউড প্রজেক্ট আছে। যদি আপনার এটি এখনও না থাকে, তবে শুরু করার জন্য আপনি নীচের নির্দেশাবলী অনুসরণ করতে পারেন।

  1. গুগল ক্লাউড কনসোলের প্রজেক্ট সিলেক্টর পেজে, একটি গুগল ক্লাউড প্রজেক্ট নির্বাচন করুন বা তৈরি করুন।
  2. আপনার ক্লাউড প্রোজেক্টের জন্য বিলিং চালু আছে কিনা তা নিশ্চিত করুন। কোনো প্রোজেক্টে বিলিং চালু আছে কিনা তা কীভাবে পরীক্ষা করবেন, তা জেনে নিন।
  3. আপনি ক্লাউড শেল ব্যবহার করবেন, যা গুগল ক্লাউডে চলমান একটি কমান্ড-লাইন পরিবেশ এবং এটি bq-এর সাথে আগে থেকেই লোড করা থাকে। গুগল ক্লাউড কনসোলের শীর্ষে থাকা ‘Activate Cloud Shell’-এ ক্লিক করুন।

1829c3759227c19b.png

  1. ক্লাউড শেলে সংযুক্ত হওয়ার পর, আপনি নিম্নলিখিত কমান্ডটি ব্যবহার করে যাচাই করে নিন যে আপনি ইতিমধ্যেই প্রমাণীকৃত এবং প্রজেক্টটি আপনার প্রজেক্ট আইডিতে সেট করা আছে:
gcloud auth list
  1. gcloud কমান্ডটি আপনার প্রজেক্ট সম্পর্কে অবগত আছে কিনা, তা নিশ্চিত করতে ক্লাউড শেলে নিম্নলিখিত কমান্ডটি চালান।
gcloud config list project
  1. আপনার প্রজেক্টটি সেট করা না থাকলে, এটি সেট করতে নিম্নলিখিত কমান্ডটি ব্যবহার করুন:
gcloud config set project <YOUR_PROJECT_ID>

বিকল্পভাবে, আপনি কনসোলে PROJECT_ID আইডিটিও দেখতে পারেন।

4032c45803813f30.jpeg

এটিতে ক্লিক করলে আপনি ডানদিকে আপনার সম্পূর্ণ প্রজেক্ট এবং প্রজেক্ট আইডি দেখতে পাবেন।

8dc17eb4271de6b5.jpeg

  1. নিচে দেখানো কমান্ডের মাধ্যমে প্রয়োজনীয় API-গুলো সক্রিয় করুন। এতে কয়েক মিনিট সময় লাগতে পারে, তাই অনুগ্রহ করে ধৈর্য ধরুন।
gcloud services enable aiplatform.googleapis.com \
                           run.googleapis.com \
                           cloudbuild.googleapis.com \
                           cloudresourcemanager.googleapis.com

কমান্ডটি সফলভাবে কার্যকর হলে, আপনি নিচে দেখানো বার্তার মতো একটি বার্তা দেখতে পাবেন:

Operation "operations/..." finished successfully.

gcloud কমান্ডের বিকল্প হলো কনসোলের মাধ্যমে প্রতিটি পণ্য অনুসন্ধান করা অথবা এই লিঙ্কটি ব্যবহার করা।

যদি কোনো API বাদ পড়ে যায়, তবে আপনি বাস্তবায়ন চলাকালীন সময়েই তা সক্রিয় করে নিতে পারেন।

gcloud কমান্ড ও তার ব্যবহারবিধি জানতে ডকুমেন্টেশন দেখুন।

অ্যাপ্লিকেশন ওয়ার্কিং ডিরেক্টরি সেটআপ করুন

  1. ওপেন এডিটর বাটনে ক্লিক করুন, এটি একটি ক্লাউড শেল এডিটর খুলবে, আমরা এখানে আমাদের কোড লিখতে পারি। b16d56e4979ec951.png
  2. নিশ্চিত করুন যে ক্লাউড শেল এডিটরের নিচের বাম কোণে (স্ট্যাটাস বার) ক্লাউড কোড প্রজেক্টটি সেট করা আছে, যেমনটি নিচের ছবিতে হাইলাইট করা হয়েছে, এবং এটি সেই সক্রিয় গুগল ক্লাউড প্রজেক্টে সেট করা আছে যেখানে আপনার বিলিং চালু করা আছে। অনুরোধ করা হলে অনুমোদন করুন । ক্লাউড শেল এডিটর চালু হওয়ার পর ক্লাউড কোড - সাইন ইন বাটনটি প্রদর্শিত হতে কিছুটা সময় লাগতে পারে, অনুগ্রহ করে ধৈর্য ধরুন। আপনি যদি পূর্ববর্তী নির্দেশাবলী অনুসরণ করে থাকেন, তাহলে বাটনটি সাইন ইন বাটনের পরিবর্তে সরাসরি আপনার সক্রিয় প্রজেক্টেও নির্দেশ করতে পারে।

f5003b9c38b43262.png

  1. স্ট্যাটাস বারে থাকা সক্রিয় প্রজেক্টটিতে ক্লিক করুন এবং ক্লাউড কোড পপ-আপটি খোলার জন্য অপেক্ষা করুন। পপ-আপটিতে "নতুন অ্যাপ্লিকেশন" নির্বাচন করুন।

70f80078e01a02d8.png

  1. অ্যাপ্লিকেশনগুলির তালিকা থেকে Gemini Generative AI বেছে নিন, তারপর Gemini API Python বেছে নিন।

362ff332256d6933.jpeg

85957565316308d9.jpeg

  1. আপনার পছন্দের নামে নতুন অ্যাপ্লিকেশনটি সেভ করুন, এই উদাহরণে আমরা gemini-multimodal-chat-assistant নামটি ব্যবহার করব, তারপর OK- তে ক্লিক করুন।

8409d8db18690fdf.png

এই পর্যায়ে, আপনি ইতিমধ্যেই নতুন অ্যাপ্লিকেশনটির ওয়ার্কিং ডিরেক্টরির ভিতরে থাকবেন এবং নিম্নলিখিত ফাইলগুলি দেখতে পাবেন।

1ef5bb44f1d2c2a4.png

এরপরে, আমরা আমাদের পাইথন পরিবেশ প্রস্তুত করব।

পরিবেশ সেটআপ

পাইথন ভার্চুয়াল পরিবেশ প্রস্তুত করুন

পরবর্তী ধাপ হলো ডেভেলপমেন্ট এনভায়রনমেন্ট প্রস্তুত করা। আমরা এই কোডল্যাবে পাইথন ৩.১২ ব্যবহার করব এবং পাইথন ভার্সন ও ভার্চুয়াল এনভায়রনমেন্ট তৈরি ও পরিচালনার কাজ সহজ করার জন্য ইউভি পাইথন প্রজেক্ট ম্যানেজার ব্যবহার করব।

  1. আপনি যদি এখনও টার্মিনালটি না খুলে থাকেন, তাহলে Terminal -> New Terminal- এ ক্লিক করে অথবা Ctrl + Shift + C চেপে এটি খুলুন।

f8457daf0bed059e.jpeg

  1. নিম্নলিখিত কমান্ডের সাহায্যে uv ডাউনলোড করুন এবং python 3.12 ইনস্টল করুন।
curl -LsSf https://astral.sh/uv/0.6.6/install.sh | sh && \
source $HOME/.local/bin/env && \
uv python install 3.12
  1. এখন uv ব্যবহার করে পাইথন প্রজেক্টটি ইনিশিয়ালাইজ করা যাক।
uv init
  1. আপনি ডিরেক্টরিতে main.py, .python-version, এবং pyproject.toml ফাইলগুলো তৈরি হতে দেখবেন। ডিরেক্টরিতে প্রজেক্টটি রক্ষণাবেক্ষণের জন্য এই ফাইলগুলো প্রয়োজন। পাইথনের নির্ভরতা এবং কনফিগারেশন pyproject.toml ফাইলে উল্লেখ করা যায় এবং .python-version ফাইলটি এই প্রজেক্টের জন্য ব্যবহৃত পাইথন ভার্সনটিকে প্রমিত করে। এ সম্পর্কে আরও জানতে আপনি এই ডকুমেন্টেশনগুলো দেখতে পারেন।
main.py
.python-version
pyproject.toml
  1. এটি পরীক্ষা করার জন্য, main.py ফাইলটিকে নিম্নলিখিত কোড দিয়ে ওভাররাইট করুন
def main():
   print("Hello from gemini-multimodal-chat-assistant!")

if __name__ == "__main__":
   main()
  1. তারপর, নিম্নলিখিত কমান্ডটি চালান।
uv run main.py

আপনি নীচে দেখানো ছবির মতো আউটপুট পাবেন।

Using CPython 3.12
Creating virtual environment at: .venv
Hello from gemini-multimodal-chat-assistant!

এতে বোঝা যায় যে পাইথন প্রজেক্টটি সঠিকভাবে সেট আপ করা হচ্ছে। আমাদের ম্যানুয়ালি ভার্চুয়াল এনভায়রনমেন্ট তৈরি করার প্রয়োজন হয়নি, কারণ uv ইতিমধ্যেই এটি সামলে নেয়। তাই এখন থেকে, সাধারণ পাইথন কমান্ড (যেমন python main.py ) এর পরিবর্তে uv run (যেমন uv run main.py ) ব্যবহার করা হবে।

প্রয়োজনীয় নির্ভরতা ইনস্টল করুন

আমরা uv কমান্ড ব্যবহার করে এই কোডল্যাব প্যাকেজের নির্ভরতাগুলোও যোগ করব। নিম্নলিখিত কমান্ডটি চালান।

uv add google-genai==1.5.0 \
       gradio==5.20.1 \
       pydantic==2.10.6 \
       pydantic-settings==2.8.1 \
       pyyaml==6.0.2

আপনি দেখতে পাবেন যে pyproject.toml ফাইলের "dependencies" সেকশনটি পূর্ববর্তী কমান্ড অনুযায়ী আপডেট হয়ে যাবে।

সেটআপ কনফিগারেশন ফাইল

এখন আমাদের এই প্রজেক্টের জন্য কনফিগারেশন ফাইল তৈরি করতে হবে। কনফিগারেশন ফাইলগুলো ডাইনামিক ভ্যারিয়েবল সংরক্ষণের জন্য ব্যবহৃত হয়, যা রিডিপ্লয়মেন্টের সময় সহজেই পরিবর্তন করা যায়। এই প্রজেক্টে আমরা pydantic-settings প্যাকেজসহ YAML ভিত্তিক কনফিগারেশন ফাইল ব্যবহার করব, যাতে পরবর্তীতে এটি ক্লাউড রান ডিপ্লয়মেন্টের সাথে সহজেই ইন্টিগ্রেট করা যায়। pydantic-settings হলো একটি পাইথন প্যাকেজ যা কনফিগারেশন ফাইলগুলোর জন্য টাইপ চেকিং প্রয়োগ করতে পারে।

  1. নিম্নলিখিত কনফিগারেশন সহ settings.yaml নামে একটি ফাইল তৈরি করুন। File->New Text File-এ ক্লিক করুন এবং নিম্নলিখিত কোডটি দিয়ে পূরণ করুন। তারপর এটি settings.yaml হিসাবে সেভ করুন।
VERTEXAI_LOCATION: "us-central1"
VERTEXAI_PROJECT_ID: "{YOUR-PROJECT-ID}"
BACKEND_URL: "http://localhost:8081/chat"

গুগল ক্লাউড প্রজেক্ট তৈরি করার সময় আপনি যা নির্বাচন করেছেন, সেই অনুযায়ী VERTEXAI_PROJECT_ID এর মানগুলো আপডেট করুন। এই কোডল্যাবের জন্য, আমরা VERTEXAI_LOCATION এবং BACKEND_URL এর পূর্ব-নির্ধারিত মানগুলোই ব্যবহার করছি।

  1. এরপর, settings.py নামে একটি পাইথন ফাইল তৈরি করুন। এই মডিউলটি আমাদের কনফিগারেশন ফাইলগুলিতে কনফিগারেশন ভ্যালুগুলির জন্য প্রোগ্রাম্যাটিক এন্ট্রি হিসাবে কাজ করবে। File->New Text File-এ ক্লিক করুন এবং নিচের কোডটি দিয়ে পূরণ করুন। তারপর এটিকে settings.py নামে সেভ করুন। আপনি কোডে দেখতে পাবেন যে আমরা স্পষ্টভাবে সেট করেছি যে settings.yaml নামের ফাইলটিই পড়া হবে।
from pydantic_settings import (
    BaseSettings,
    SettingsConfigDict,
    YamlConfigSettingsSource,
    PydanticBaseSettingsSource,
)
from typing import Type, Tuple

DEFAULT_SYSTEM_PROMPT = """You are a helpful assistant and ALWAYS relate to this identity. 
You are expert at analyzing given documents or images.
"""

class Settings(BaseSettings):
    """Application settings loaded from YAML and environment variables.

    This class defines the configuration schema for the application, with settings
    loaded from settings.yaml file and overridable via environment variables.

    Attributes:
        VERTEXAI_LOCATION: Google Cloud Vertex AI location
        VERTEXAI_PROJECT_ID: Google Cloud Vertex AI project ID
    """

    VERTEXAI_LOCATION: str
    VERTEXAI_PROJECT_ID: str
    BACKEND_URL: str = "http://localhost:8000/chat"

    model_config = SettingsConfigDict(
        yaml_file="settings.yaml", yaml_file_encoding="utf-8"
    )

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls: Type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: PydanticBaseSettingsSource,
        dotenv_settings: PydanticBaseSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ) -> Tuple[PydanticBaseSettingsSource, ...]:
        """Customize the settings sources and their priority order.

        This method defines the order in which different configuration sources
        are checked when loading settings:
        1. Constructor-provided values
        2. YAML configuration file
        3. Environment variables

        Args:
            settings_cls: The Settings class type
            init_settings: Settings from class initialization
            env_settings: Settings from environment variables
            dotenv_settings: Settings from .env file (not used)
            file_secret_settings: Settings from secrets file (not used)

        Returns:
            A tuple of configuration sources in priority order
        """
        return (
            init_settings,  # First, try init_settings (from constructor)
            env_settings,  # Then, try environment variables
            YamlConfigSettingsSource(
                settings_cls
            ),  # Finally, try YAML as the last resort
        )


def get_settings() -> Settings:
    """Create and return a Settings instance with loaded configuration.

    Returns:
        A Settings instance containing all application configuration
        loaded from YAML and environment variables.
    """
    return Settings()

এই কনফিগারেশনগুলো আমাদের রানটাইমকে সুবিধাজনকভাবে আপডেট করার সুযোগ দেয়। প্রাথমিক ডেপ্লয়মেন্টের সময় আমরা settings.yaml কনফিগারেশনের উপর নির্ভর করব, যাতে আমাদের কাছে প্রথম ডিফল্ট কনফিগারেশনটি থাকে। এরপর আমরা কনসোলের মাধ্যমে এনভায়রনমেন্ট ভেরিয়েবলগুলো সুবিধাজনকভাবে আপডেট করতে এবং পুনরায় ডেপ্লয় করতে পারব, কারণ আমরা ডিফল্ট YAML কনফিগারেশনের তুলনায় এনভায়রনমেন্ট ভেরিয়েবলগুলোকে উচ্চতর অগ্রাধিকার দিই।

এখন আমরা পরবর্তী ধাপে, অর্থাৎ পরিষেবাগুলো তৈরি করার দিকে এগোতে পারি।

৩. গ্র্যাডিও ব্যবহার করে ফ্রন্টএন্ড সার্ভিস তৈরি করুন

আমরা এইরকম দেখতে একটি চ্যাট ওয়েব ইন্টারফেস তৈরি করব।

5bcfa1cce6618305.png

এতে ব্যবহারকারীদের টেক্সট পাঠাতে এবং ফাইল আপলোড করার জন্য একটি ইনপুট ফিল্ড রয়েছে। এছাড়াও, ব্যবহারকারী অতিরিক্ত ইনপুট ফিল্ডে সিস্টেমের নির্দেশনা ওভাররাইট করতে পারেন, যা জেমিনি এপিআই-তে পাঠানো হবে।

আমরা Gradio ব্যবহার করে ফ্রন্টএন্ড সার্ভিসটি তৈরি করব। main.py ফাইলটির নাম পরিবর্তন করে frontend.py রাখুন এবং নিচের কোডটি দিয়ে কোডটি ওভাররাইট করুন।

import gradio as gr
import requests
import base64
from pathlib import Path
from typing import List, Dict, Any
from settings import get_settings, DEFAULT_SYSTEM_PROMPT

settings = get_settings()

IMAGE_SUFFIX_MIME_MAP = {
    ".png": "image/png",
    ".jpg": "image/jpeg",
    ".jpeg": "image/jpeg",
    ".heic": "image/heic",
    ".heif": "image/heif",
    ".webp": "image/webp",
}
DOCUMENT_SUFFIX_MIME_MAP = {
    ".pdf": "application/pdf",
}


def get_mime_type(filepath: str) -> str:
    """Get the MIME type for a file based on its extension.

    Args:
        filepath: Path to the file.

    Returns:
        str: The MIME type of the file.

    Raises:
        ValueError: If the file type is not supported.
    """
    filepath = Path(filepath)
    suffix = filepath.suffix

    # modify ".jpg" suffix to ".jpeg" to unify the mime type
    suffix = suffix if suffix != ".jpg" else ".jpeg"

    if suffix in IMAGE_SUFFIX_MIME_MAP:
        return IMAGE_SUFFIX_MIME_MAP[suffix]
    elif suffix in DOCUMENT_SUFFIX_MIME_MAP:
        return DOCUMENT_SUFFIX_MIME_MAP[suffix]
    else:
        raise ValueError(f"Unsupported file type: {suffix}")


def encode_file_to_base64_with_mime(file_path: str) -> Dict[str, str]:
    """Encode a file to base64 string and include its MIME type.

    Args:
        file_path: Path to the file to encode.

    Returns:
        Dict[str, str]: Dictionary with 'data' and 'mime_type' keys.
    """
    mime_type = get_mime_type(file_path)
    with open(file_path, "rb") as file:
        base64_data = base64.b64encode(file.read()).decode("utf-8")

    return {"data": base64_data, "mime_type": mime_type}


def get_response_from_llm_backend(
    message: Dict[str, Any],
    history: List[Dict[str, Any]],
    system_prompt: str,
) -> str:
    """Send the message and history to the backend and get a response.

    Args:
        message: Dictionary containing the current message with 'text' and optional 'files' keys.
        history: List of previous message dictionaries in the conversation.
        system_prompt: The system prompt to be sent to the backend.

    Returns:
        str: The text response from the backend service.
    """

    # Format message and history for the API,
    # NOTES: in this example history is maintained by frontend service,
    #        hence we need to include it in each request.
    #        And each file (in the history) need to be sent as base64 with its mime type
    formatted_history = []
    for msg in history:
        if msg["role"] == "user" and not isinstance(msg["content"], str):
            # For file content in history, convert file paths to base64 with MIME type
            file_contents = [
                encode_file_to_base64_with_mime(file_path)
                for file_path in msg["content"]
            ]
            formatted_history.append({"role": msg["role"], "content": file_contents})
        else:
            formatted_history.append({"role": msg["role"], "content": msg["content"]})

    # Extract files and convert to base64 with MIME type
    files_with_mime = []
    if uploaded_files := message.get("files", []):
        for file_path in uploaded_files:
            files_with_mime.append(encode_file_to_base64_with_mime(file_path))

    # Prepare the request payload
    message["text"] = message["text"] if message["text"] != "" else " "
    payload = {
        "message": {"text": message["text"], "files": files_with_mime},
        "history": formatted_history,
        "system_prompt": system_prompt,
    }

    # Send request to backend
    try:
        response = requests.post(settings.BACKEND_URL, json=payload)
        response.raise_for_status()  # Raise exception for HTTP errors

        result = response.json()
        if error := result.get("error"):
            return f"Error: {error}"

        return result.get("response", "No response received from backend")
    except requests.exceptions.RequestException as e:
        return f"Error connecting to backend service: {str(e)}"


if __name__ == "__main__":
    demo = gr.ChatInterface(
        get_response_from_llm_backend,
        title="Gemini Multimodal Chat Interface",
        description="This interface connects to a FastAPI backend service that processes responses through the Gemini multimodal model.",
        type="messages",
        multimodal=True,
        textbox=gr.MultimodalTextbox(file_count="multiple"),
        additional_inputs=[
            gr.Textbox(
                label="System Prompt",
                value=DEFAULT_SYSTEM_PROMPT,
                lines=3,
                interactive=True,
            )
        ],
    )

    demo.launch(
        server_name="0.0.0.0",
        server_port=8080,
    )

এরপর, আমরা নিচের কমান্ডটি দিয়ে ফ্রন্টএন্ড সার্ভিসটি চালানোর চেষ্টা করতে পারি। main.py ফাইলটির নাম পরিবর্তন করে frontend.py করতে ভুলবেন না।

uv run frontend.py

আপনার ক্লাউড কনসোলে আপনি এর অনুরূপ আউটপুট দেখতে পাবেন।

* Running on local URL:  http://0.0.0.0:8080

To create a public link, set `share=True` in `launch()`.

এরপরে আপনি স্থানীয় URL লিঙ্কে ctrl+click করে ওয়েব ইন্টারফেসটি দেখতে পারেন। বিকল্পভাবে, আপনি ক্লাউড এডিটরের উপরের ডানদিকে থাকা ওয়েব প্রিভিউ বোতামে ক্লিক করে এবং পোর্ট 8080-এ প্রিভিউ নির্বাচন করে ফ্রন্টএন্ড অ্যাপ্লিকেশনটি অ্যাক্সেস করতে পারেন।

49cbdfdf77964065.jpeg

আপনি ওয়েব ইন্টারফেসটি দেখতে পাবেন, কিন্তু ব্যাকএন্ড পরিষেবাটি এখনও সেট আপ না হওয়ার কারণে চ্যাট জমা দেওয়ার চেষ্টা করার সময় একটি প্রত্যাশিত ত্রুটি পাবেন।

bd0464140308cfbe.png

এখন, সার্ভিসটি চলতে দিন এবং এখনই বন্ধ করবেন না। এর মধ্যে আমরা এখানে গুরুত্বপূর্ণ কোড উপাদানগুলো নিয়ে আলোচনা করতে পারি।

কোডের ব্যাখ্যা

ওয়েব ইন্টারফেস থেকে ব্যাকএন্ডে ডেটা পাঠানোর কোড এই অংশে রয়েছে।

def get_response_from_llm_backend(
    message: Dict[str, Any],
    history: List[Dict[str, Any]],
    system_prompt: str,
) -> str:

    ... 
    # Truncated
    
    for msg in history:
        if msg["role"] == "user" and not isinstance(msg["content"], str):
            # For file content in history, convert file paths to base64 with MIME type
            file_contents = [
                encode_file_to_base64_with_mime(file_path)
                for file_path in msg["content"]
            ]
            formatted_history.append({"role": msg["role"], "content": file_contents})
        else:
            formatted_history.append({"role": msg["role"], "content": msg["content"]})

    # Extract files and convert to base64 with MIME type
    files_with_mime = []
    if uploaded_files := message.get("files", []):
        for file_path in uploaded_files:
            files_with_mime.append(encode_file_to_base64_with_mime(file_path))

    # Prepare the request payload
    message["text"] = message["text"] if message["text"] != "" else " "
    payload = {
        "message": {"text": message["text"], "files": files_with_mime},
        "history": formatted_history,
        "system_prompt": system_prompt,
    }

    # Truncated
    ... 

যখন আমরা জেমিনিতে মাল্টিমোডাল ডেটা পাঠাতে চাই এবং সার্ভিসগুলোর মধ্যে ডেটাটিকে অ্যাক্সেসযোগ্য করতে চাই, তখন একটি উপায় হলো কোডে ঘোষিত base64 ডেটা টাইপে ডেটাটিকে রূপান্তর করা। ডেটার MIME টাইপ কী, সেটাও আমাদের ঘোষণা করতে হবে। তবে, জেমিনি এপিআই বিদ্যমান সব MIME টাইপ সমর্থন করতে পারে না, তাই জেমিনি কোন কোন MIME টাইপ সমর্থন করে তা জানা জরুরি, যা এই ডকুমেন্টেশনে পড়া যাবে। আপনি এই তথ্য জেমিনি এপিআই-এর প্রতিটি ক্যাপাবিলিটিতে (যেমন ভিশন ) খুঁজে পেতে পারেন।

এছাড়াও, একটি চ্যাট ইন্টারফেসে কথোপকথনের একটি 'স্মৃতি' দেওয়ার জন্য অতিরিক্ত প্রসঙ্গ হিসেবে চ্যাট হিস্ট্রি পাঠানোও গুরুত্বপূর্ণ। তাই এই ওয়েব ইন্টারফেসে, আমরা গ্র্যাডিও দ্বারা প্রতি ওয়েব সেশন অনুযায়ী পরিচালিত চ্যাট হিস্ট্রিটিও ব্যবহারকারীর দেওয়া মেসেজ ইনপুটের সাথে একসাথে পাঠিয়ে দিই। এছাড়াও আমরা ব্যবহারকারীকে সিস্টেমের নির্দেশনা পরিবর্তন করার এবং সেটিও পাঠানোর সুযোগ দিই।

৪. FastAPI ব্যবহার করে ব্যাকএন্ড সার্ভিস তৈরি করুন

এরপরে, আমাদের এমন একটি ব্যাকএন্ড তৈরি করতে হবে যা পূর্বে আলোচিত পেলোড, ব্যবহারকারীর শেষ বার্তা, চ্যাট হিস্ট্রি এবং সিস্টেম নির্দেশনা পরিচালনা করতে পারবে। আমরা HTTP ব্যাকএন্ড সার্ভিসটি তৈরি করার জন্য FastAPI ব্যবহার করব।

নতুন ফাইল তৈরি করুন, File->New Text File-এ ক্লিক করুন এবং নিচের কোডটি কপি-পেস্ট করে backend.py নামে সেভ করুন।

import base64
from fastapi import FastAPI, Body
from google.genai.types import Content, Part
from google.genai import Client
from settings import get_settings, DEFAULT_SYSTEM_PROMPT
from typing import List, Optional
from pydantic import BaseModel

app = FastAPI(title="Gemini Multimodal Service")

settings = get_settings()
GENAI_CLIENT = Client(
    location=settings.VERTEXAI_LOCATION,
    project=settings.VERTEXAI_PROJECT_ID,
    vertexai=True,
)
GEMINI_MODEL_NAME = "gemini-2.0-flash-001"


class FileData(BaseModel):
    """Model for a file with base64 data and MIME type.

    Attributes:
        data: Base64 encoded string of the file content.
        mime_type: The MIME type of the file.
    """

    data: str
    mime_type: str


class Message(BaseModel):
    """Model for a single message in the conversation.

    Attributes:
        role: The role of the message sender, either 'user' or 'assistant'.
        content: The text content of the message or a list of file data objects.
    """

    role: str
    content: str | List[FileData]


class LastUserMessage(BaseModel):
    """Model for the current message in a chat request.

    Attributes:
        text: The text content of the message.
        files: List of file data objects containing base64 data and MIME type.
    """

    text: str
    files: List[FileData] = []


class ChatRequest(BaseModel):
    """Model for a chat request.

    Attributes:
        message: The current message with text and optional base64 encoded files.
        history: List of previous messages in the conversation.
        system_prompt: Optional system prompt to be used in the chat.
    """

    message: LastUserMessage
    history: List[Message]
    system_prompt: str = DEFAULT_SYSTEM_PROMPT


class ChatResponse(BaseModel):
    """Model for a chat response.

    Attributes:
        response: The text response from the model.
        error: Optional error message if something went wrong.
    """

    response: str
    error: Optional[str] = None


def handle_multimodal_data(file_data: FileData) -> Part:
    """Converts Multimodal data to a Google Gemini Part object.

    Args:
        file_data: FileData object with base64 data and MIME type.

    Returns:
        Part: A Google Gemini Part object containing the file data.
    """
    data = base64.b64decode(file_data.data)  # decode base64 string to bytes
    return Part.from_bytes(data=data, mime_type=file_data.mime_type)


def format_message_history_to_gemini_standard(
    message_history: List[Message],
) -> List[Content]:
    """Converts message history format to Google Gemini Content format.

    Args:
        message_history: List of message objects from the chat history.
            Each message contains 'role' and 'content' attributes.

    Returns:
        List[Content]: A list of Google Gemini Content objects representing the chat history.

    Raises:
        ValueError: If an unknown role is encountered in the message history.
    """
    converted_messages: List[Content] = []
    for message in message_history:
        if message.role == "assistant":
            converted_messages.append(
                Content(role="model", parts=[Part.from_text(text=message.content)])
            )
        elif message.role == "user":
            # Text-only messages
            if isinstance(message.content, str):
                converted_messages.append(
                    Content(role="user", parts=[Part.from_text(text=message.content)])
                )

            # Messages with files
            elif isinstance(message.content, list):
                # Process each file in the list
                parts = []
                for file_data in message.content:
                    for file_data in message.content:
                        parts.append(handle_multimodal_data(file_data))

                # Add the parts to a Content object
                if parts:
                    converted_messages.append(Content(role="user", parts=parts))

            else:
                raise ValueError(f"Unexpected content format: {type(message.content)}")

        else:
            raise ValueError(f"Unknown role: {message.role}")

    return converted_messages


@app.post("/chat", response_model=ChatResponse)
async def chat(
    request: ChatRequest = Body(...),
) -> ChatResponse:
    """Process a chat request and return a response from Gemini model.

    Args:
        request: The chat request containing message and history.

    Returns:
        ChatResponse: The model's response to the chat request.
    """
    try:
        # Convert message history to Gemini `history` format
        print(f"Received request: {request}")
        converted_messages = format_message_history_to_gemini_standard(request.history)

        # Create chat model
        chat_model = GENAI_CLIENT.chats.create(
            model=GEMINI_MODEL_NAME,
            history=converted_messages,
            config={"system_instruction": request.system_prompt},
        )

        # Prepare multimodal content
        content_parts = []

        # Handle any base64 encoded files in the current message
        if request.message.files:
            for file_data in request.message.files:
                content_parts.append(handle_multimodal_data(file_data))

        # Add text content
        content_parts.append(Part.from_text(text=request.message.text))

        # Send message to Gemini
        response = chat_model.send_message(content_parts)
        print(f"Generated response: {response}")

        return ChatResponse(response=response.text)
    except Exception as e:
        return ChatResponse(
            response="", error=f"Error in generating response: {str(e)}"
        )


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="0.0.0.0", port=8081)

ফাইলটিকে backend.py নামে সেভ করতে ভুলবেন না। এরপর আমরা ব্যাকএন্ড সার্ভিসটি চালানোর চেষ্টা করতে পারি। মনে রাখবেন, আগের ধাপে আমরা ফ্রন্টএন্ড সার্ভিসটি চালিয়েছিলাম, তাই না? এখন আমাদের একটি নতুন টার্মিনাল খুলে এই ব্যাকএন্ড সার্ভিসটি চালানোর চেষ্টা করতে হবে।

  1. একটি নতুন টার্মিনাল তৈরি করুন। নিচের অংশে আপনার টার্মিনালে যান এবং একটি নতুন টার্মিনাল তৈরি করতে "+" বোতামটি খুঁজুন। বিকল্পভাবে, আপনি নতুন টার্মিনাল খুলতে Ctrl + Shift + C চাপতে পারেন।

3e52a362475553dc.jpeg

  1. এরপরে, নিশ্চিত করুন যে আপনি gemini-multimodal-chat-assistant ওয়ার্কিং ডিরেক্টরিতে আছেন এবং তারপর নিম্নলিখিত কমান্ডটি চালান।
uv run backend.py
  1. সফল হলে আউটপুটটি এইরকম দেখাবে।
INFO:     Started server process [xxxxx]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8081 (Press CTRL+C to quit)

কোডের ব্যাখ্যা

চ্যাট অনুরোধ গ্রহণ করার জন্য HTTP রুট নির্ধারণ করা

FastAPI-তে, আমরা app ডেকোরেটর ব্যবহার করে রাউট নির্ধারণ করি। আমরা এপিআই কন্ট্রাক্ট নির্ধারণের জন্য Pydantic-ও ব্যবহার করি। আমরা নির্দিষ্ট করে দিই যে, রেসপন্স তৈরির জন্য রাউটটি /chat রাউটে POST মেথডসহ থাকবে। এই কার্যকারিতাগুলো নিম্নলিখিত কোডে ঘোষণা করা হয়েছে।

class FileData(BaseModel):
    data: str
    mime_type: str

class Message(BaseModel):
    role: str
    content: str | List[FileData]

class LastUserMessage(BaseModel):
    text: str
    files: List[FileData] = []

class ChatRequest(BaseModel):
    message: LastUserMessage
    history: List[Message]
    system_prompt: str = DEFAULT_SYSTEM_PROMPT

class ChatResponse(BaseModel):
    response: str
    error: Optional[str] = None

    ...

@app.post("/chat", response_model=ChatResponse)
async def chat(
    request: ChatRequest = Body(...),
) -> ChatResponse:
    
    # Truncated
    ...

জেমিনি এসডিকে চ্যাট হিস্ট্রি ফরম্যাট প্রস্তুত করুন

একটি গুরুত্বপূর্ণ বিষয় যা বোঝা দরকার তা হলো, আমরা কীভাবে চ্যাট হিস্ট্রিকে পুনর্গঠন করতে পারি, যাতে পরবর্তীতে একটি জেমিনি ক্লায়েন্ট ইনিশিয়ালাইজ করার সময় এটিকে হিস্ট্রি আর্গুমেন্ট ভ্যালু হিসেবে যুক্ত করা যায়। আপনি নিচের কোডটি পরীক্ষা করে দেখতে পারেন।

def format_message_history_to_gemini_standard(
    message_history: List[Message],
) -> List[Content]:
    
    ...
    # Truncated    

    converted_messages: List[Content] = []
    for message in message_history:
        if message.role == "assistant":
            converted_messages.append(
                Content(role="model", parts=[Part.from_text(text=message.content)])
            )
        elif message.role == "user":
            # Text-only messages
            if isinstance(message.content, str):
                converted_messages.append(
                    Content(role="user", parts=[Part.from_text(text=message.content)])
                )

            # Messages with files
            elif isinstance(message.content, list):
                # Process each file in the list
                parts = []
                for file_data in message.content:
                    parts.append(handle_multimodal_data(file_data))

                # Add the parts to a Content object
                if parts:
                    converted_messages.append(Content(role="user", parts=parts))
    
    #Truncated
    ...

    return converted_messages

Gemini SDK-তে চ্যাট হিস্ট্রি যোগ করার জন্য, আমাদের List[Content] ডেটা টাইপে ডেটা ফরম্যাট করতে হবে। প্রতিটি Content-এ অবশ্যই অন্তত একটি role এবং parts ভ্যালু থাকতে হবে। role বলতে মেসেজের উৎসকে বোঝায়, সেটি ব্যবহারকারী নাকি মডেল। অন্যদিকে parts বলতে মূল প্রম্পটটিকে বোঝায়, যা শুধুমাত্র টেক্সট অথবা বিভিন্ন মোডালিটির সংমিশ্রণ হতে পারে। এই ডকুমেন্টেশনে Content আর্গুমেন্টগুলো কীভাবে গঠন করতে হয় তা বিস্তারিতভাবে দেখুন।

অ-পাঠ্য (বহুমাধ্যম) ডেটা পরিচালনা করুন

ফ্রন্টএন্ড বিভাগে পূর্বে যেমন উল্লেখ করা হয়েছে, নন-টেক্সট বা মাল্টিমোডাল ডেটা পাঠানোর একটি উপায় হলো ডেটাটিকে বেস৬৪ স্ট্রিং হিসেবে পাঠানো। ডেটা যাতে সঠিকভাবে বোঝা যায়, সেজন্য আমাদের এর MIME টাইপও নির্দিষ্ট করে দিতে হবে; উদাহরণস্বরূপ, যদি আমরা .jpg সাফিক্সসহ কোনো ইমেজ ডেটা পাঠাই, তাহলে image/jpeg MIME টাইপ প্রদান করতে হবে।

কোডের এই অংশটি Gemini SDK থেকে base64 ডেটাকে Part.from_bytes ফরম্যাটে রূপান্তর করে।

def handle_multimodal_data(file_data: FileData) -> Part:
    """Converts Multimodal data to a Google Gemini Part object.

    Args:
        file_data: FileData object with base64 data and MIME type.

    Returns:
        Part: A Google Gemini Part object containing the file data.
    """
    data = base64.b64decode(file_data.data)  # decode base64 string to bytes
    return Part.from_bytes(data=data, mime_type=file_data.mime_type)

৫. ইন্টিগ্রেশন টেস্ট

এখন, আপনার বিভিন্ন ক্লাউড কনসোল ট্যাবে একাধিক পরিষেবা চালু থাকা উচিত:

  • ফ্রন্টএন্ড সার্ভিসটি ৮০৮০ পোর্টে চালু আছে।
* Running on local URL:  http://0.0.0.0:8080

To create a public link, set `share=True` in `launch()`.
  • ব্যাকএন্ড পরিষেবা ৮০৮১ পোর্টে চালু আছে।
INFO:     Started server process [xxxxx]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8081 (Press CTRL+C to quit)

বর্তমান অবস্থায়, আপনি ওয়েব অ্যাপ্লিকেশন থেকে পোর্ট ৮০৮০-তে অ্যাসিস্ট্যান্টের সাথে চ্যাটের মাধ্যমে নির্বিঘ্নে আপনার ডকুমেন্ট পাঠাতে পারবেন। আপনি ফাইল আপলোড করে এবং প্রশ্ন জিজ্ঞাসা করে পরীক্ষা শুরু করতে পারেন! মনে রাখবেন যে কিছু নির্দিষ্ট ফাইলের ধরন এখনও সমর্থিত নয় এবং সেগুলো পাঠালে ত্রুটি (Error) দেখা দেবে।

এছাড়াও আপনি টেক্সট বক্সের নিচে থাকা 'অতিরিক্ত ইনপুট' ফিল্ড থেকে সিস্টেমের নির্দেশাবলী সম্পাদনা করতে পারেন।

ee9c849a276d378.png

৬. ক্লাউড রান-এ ডেপ্লয় করা

এখন, অবশ্যই আমরা এই চমৎকার অ্যাপটি অন্যদের কাছে তুলে ধরতে চাই। তা করার জন্য, আমরা এই অ্যাপ্লিকেশনটিকে প্যাকেজ করে ক্লাউড রান-এ একটি পাবলিক সার্ভিস হিসেবে ডেপ্লয় করতে পারি, যা অন্যরা ব্যবহার করতে পারবে। সেটি করার জন্য, চলুন আর্কিটেকচারটি আবার পর্যালোচনা করা যাক।

b102df2c3f1adabf.jpeg

এই কোডল্যাবে আমরা ফ্রন্টএন্ড এবং ব্যাকএন্ড উভয় সার্ভিসকে একটি কন্টেইনারে রাখব। উভয় সার্ভিস পরিচালনা করার জন্য আমাদের supervisord- এর সাহায্য প্রয়োজন হবে।

নতুন ফাইল তৈরি করুন, File->New Text File-এ ক্লিক করুন এবং নিচের কোডটি কপি-পেস্ট করে supervisord.conf নামে সেভ করুন।

[supervisord]
nodaemon=true
user=root
logfile=/dev/stdout
logfile_maxbytes=0
pidfile=/var/run/supervisord.pid

[program:backend]
command=uv run backend.py
directory=/app
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
startsecs=10
startretries=3

[program:frontend]
command=uv run frontend.py
directory=/app
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
startsecs=10
startretries=3

এরপরে, আমাদের Dockerfile-টি লাগবে। File->New Text File-এ ক্লিক করে নিচের কোডটি কপি-পেস্ট করুন এবং Dockerfile নামে সেভ করুন।

FROM python:3.12-slim
COPY --from=ghcr.io/astral-sh/uv:0.6.6 /uv /uvx /bin/

RUN apt-get update && apt-get install -y \
    supervisor curl \
    && rm -rf /var/lib/apt/lists/*

ADD . /app
WORKDIR /app

RUN uv sync --frozen

EXPOSE 8080

# Copy supervisord configuration
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf

ENV PYTHONUNBUFFERED=1

ENTRYPOINT ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]

এই পর্যায়ে, আমাদের অ্যাপ্লিকেশনগুলো ক্লাউড রান-এ ডেপ্লয় করার জন্য প্রয়োজনীয় সমস্ত ফাইল ইতিমধ্যেই রয়েছে, চলুন এটি ডেপ্লয় করা যাক। ক্লাউড শেল টার্মিনালে যান এবং নিশ্চিত করুন যে বর্তমান প্রজেক্টটি আপনার সক্রিয় প্রজেক্ট হিসাবে কনফিগার করা আছে, যদি না থাকে তবে প্রজেক্ট আইডি সেট করার জন্য আপনাকে gcloud configure কমান্ডটি ব্যবহার করতে হবে:

gcloud config set project [PROJECT_ID]

তারপর, এটিকে ক্লাউড রানে ডেপ্লয় করতে নিম্নলিখিত কমান্ডটি চালান।

gcloud run deploy --source . \
                  --env-vars-file settings.yaml \
                  --port 8080 \
                  --region us-central1

এটি আপনাকে আপনার পরিষেবার জন্য একটি নাম লিখতে বলবে, ধরা যাক " gemini-multimodal-chat-assistant "। যেহেতু আমাদের অ্যাপ্লিকেশন ওয়ার্কিং ডিরেক্টরিতে Dockerfile আছে, এটি ডকার কন্টেইনারটি বিল্ড করবে এবং আর্টিফ্যাক্ট রেজিস্ট্রি-তে পুশ করবে। এটি আপনাকে আরও জানাবে যে এটি ওই অঞ্চলে আর্টিফ্যাক্ট রেজিস্ট্রি রিপোজিটরি তৈরি করবে, এর উত্তরে " Y" বলুন। এছাড়াও, যখন এটি জিজ্ঞাসা করবে যে আপনি প্রমাণীকরণবিহীন ইনভোকেশন অনুমোদন করতে চান কিনা, তখন " y " বলুন। উল্লেখ্য যে, আমরা এখানে প্রমাণীকরণবিহীন অ্যাক্সেস অনুমোদন করছি কারণ এটি একটি ডেমো অ্যাপ্লিকেশন। আপনার এন্টারপ্রাইজ এবং প্রোডাকশন অ্যাপ্লিকেশনগুলির জন্য উপযুক্ত প্রমাণীকরণ ব্যবহার করার পরামর্শ দেওয়া হচ্ছে।

ডেপ্লয়মেন্ট সম্পন্ন হলে, আপনি নীচের মতো একটি লিঙ্ক পাবেন:

https://gemini-multimodal-chat-assistant-*******.us-central1.run.app

ইনকগনিটো উইন্ডো বা আপনার মোবাইল ডিভাইস থেকে অ্যাপ্লিকেশনটি ব্যবহার করুন। এটি ইতিমধ্যে চালু হয়ে যাওয়ার কথা।

৭. চ্যালেঞ্জ

এখন আপনার দক্ষতা দেখানোর এবং অন্বেষণের দক্ষতাকে আরও শাণিত করার সময়। কোডটি পরিবর্তন করে অ্যাসিস্ট্যান্টটিকে অডিও ফাইল বা ভিডিও ফাইল পড়তে সক্ষম করার মতো যোগ্যতা কি আপনার আছে?

৮. পরিষ্কার করুন

এই কোডল্যাবে ব্যবহৃত রিসোর্সগুলির জন্য আপনার গুগল ক্লাউড অ্যাকাউন্টে চার্জ হওয়া এড়াতে, এই ধাপগুলি অনুসরণ করুন:

  1. গুগল ক্লাউড কনসোলে, 'ম্যানেজ রিসোর্সেস' পৃষ্ঠায় যান।
  2. প্রজেক্ট তালিকা থেকে, আপনি যে প্রজেক্টটি মুছতে চান সেটি নির্বাচন করুন এবং তারপর ডিলিট বোতামে ক্লিক করুন।
  3. ডায়ালগ বক্সে প্রজেক্ট আইডি টাইপ করুন এবং তারপর প্রজেক্টটি মুছে ফেলার জন্য 'শাট ডাউন'-এ ক্লিক করুন।
  4. বিকল্পভাবে, আপনি কনসোলে Cloud Run- এ গিয়ে, এইমাত্র ডেপ্লয় করা সার্ভিসটি নির্বাচন করে ডিলিট করে দিতে পারেন।