Google ADK এবং Cloud Run ব্যবহার করে Streamlit-এ একটি RAG AI এজেন্ট স্থাপন করুন

১. ভূমিকা

এই কোডল্যাবে, আপনি একটি কফি শপের জন্য একটি ইন্টারেক্টিভ এআই বারিস্তা এজেন্ট তৈরি করবেন। গুগলের ওপেন-সোর্স এজেন্ট ডেভেলপমেন্ট কিট (ADK) এবং জেমিনি ৩.৫ ফ্ল্যাশ মডেল ব্যবহার করে, আপনি একটি মক মেনু ডেটাসেটের উপর ভিত্তি করে এজেন্টের সুপারিশগুলো তৈরি করতে রিট্রিভাল-অগমেন্টেড জেনারেশন (RAG) পদ্ধতি প্রয়োগ করবেন। সবশেষে, আপনি এজেন্টটিকে একটি স্ট্রিমলিট ইউজার ইন্টারফেসে যুক্ত করে ক্লাউড রান -এ ডেপ্লয় করবেন।

আপনি যা করবেন

  • কফি আইটেম, ট্যাগ এবং অ্যালার্জেন সম্বলিত একটি RAG ডেটা সোর্স ( menu.json ) তৈরি করুন।
  • ADK LlmAgent ব্যবহার করে একটি AI এজেন্ট তৈরি করুন এবং মেনু ডেটা লোড করার জন্য একটি পাইথন টুল সংযুক্ত করুন।
  • এজেন্টটিকে একটি স্ট্রিমলিট চ্যাট অ্যাপ্লিকেশনের মধ্যে রাখুন যা কথোপকথনের ইতিহাস পরিচালনা করে।
  • সোর্স-ভিত্তিক ডেপ্লয়মেন্ট ব্যবহার করে ক্লাউড রান-এ স্ট্রিমলিট অ্যাপটি ডেপ্লয় করুন।
  • RAG গ্রাউন্ডিং পরীক্ষা এবং অ্যালার্জেন সচেতনতা।

Architecture Diagram

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

  • ক্রোমের মতো একটি ওয়েব ব্রাউজার।
  • বিলিং সক্ষম একটি গুগল ক্লাউড প্রজেক্ট।
  • পাইথন সম্পর্কে প্রাথমিক ধারণা।

এই কোডল্যাবটি নতুনদের সহ সকল স্তরের ডেভেলপারদের জন্য।

আনুমানিক খরচ: ১.০০ মার্কিন ডলারের কম।

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

একটি গুগল ক্লাউড প্রজেক্ট তৈরি করুন

  1. গুগল ক্লাউড কনসোলে , একটি গুগল ক্লাউড প্রজেক্ট নির্বাচন করুন বা তৈরি করুন
  2. আপনার ক্লাউড প্রজেক্টের জন্য বিলিং চালু আছে কিনা তা নিশ্চিত করুন।

ক্লাউড শেল শুরু করুন

  1. Google Cloud কনসোলের শীর্ষে থাকা Activate Cloud Shell-এ ক্লিক করুন।

Activate Cloud\nShell

  1. প্রমাণীকরণ যাচাই করুন:

Authorize Cloud Shell

gcloud auth list
  1. আপনার সক্রিয় প্রজেক্টটি সেট করা আছে কিনা তা নিশ্চিত করুন:
gcloud config get project

প্রদর্শিত প্রজেক্ট আইডি সঠিক না হলে বা কোনো আইডি সেট করা না থাকলে, চালান:

gcloud config set project <YOUR_PROJECT_ID>

এপিআই সক্ষম করুন

প্রয়োজনীয় সকল API সক্রিয় করতে এই কমান্ডটি চালান:

gcloud services enable \
 run.googleapis.com \
 aiplatform.googleapis.com \
 cloudbuild.googleapis.com

৩. আপনার প্রজেক্টটি সেট আপ করুন।

এই ধাপে, আপনি আপনার প্রোজেক্টের এনভায়রনমেন্ট ভেরিয়েবলগুলো নির্ধারণ করবেন এবং প্রোজেক্টের জন্য একটি ওয়ার্কিং ডিরেক্টরি তৈরি করবেন।

  1. আপনার সক্রিয় ক্লাউড শেল সেশনে, নিম্নলিখিত প্রজেক্ট এনভায়রনমেন্ট ভেরিয়েবলগুলো ইনিশিয়ালাইজ করুন:
export PROJECT_ID=$(gcloud config get-value project)

দ্রষ্টব্য: নিকটতম অঞ্চল ব্যবহার করুন

আপনার নিকটতম অঞ্চলটি খুঁজুন এবং নিম্নলিখিত কমান্ডে insert-region-here-এর জায়গায় সেটি বসান:

export REGION=[insert-region-here]
  1. coffee-barista-agent নামে একটি নতুন প্রজেক্ট ডিরেক্টরি তৈরি করুন এবং তাতে প্রবেশ করুন:
mkdir coffee-barista-agent && cd coffee-barista-agent

৪. মক মেনু ডেটা সোর্স তৈরি করুন

এআই বারিস্তাকে বাস্তবসম্মত করতে এবং অস্তিত্বহীন জিনিসের অলীক কল্পনা থেকে বিরত রাখতে, আপনাকে একটি স্থানীয় মেনু ডেটাসেট তৈরি করতে হবে। এজেন্টটি রানটাইমে একটি কাস্টম টুলের মাধ্যমে এই ফাইলটি পড়বে।

  1. ক্লাউড শেল এডিটরে menu.json তৈরি করে খুলুন:
  cloudshell edit menu.json
  1. নিম্নলিখিত JSON কন্টেন্টটি এডিটরে পেস্ট করুন এবং ফাইলটি সেভ করুন:
[
  {
    "name": "Espresso Solo",
    "description": "A single shot of rich, bold espresso.",
    "price": 2.50,
    "tags": ["strong", "hot", "dairy-free", "sugar-free"],
    "allergens": []
  },
  {
    "name": "Oat Milk Honey Latte",
    "description": "Creamy steamed oat milk with espresso and a touch of honey.",
    "price": 5.00,
    "tags": ["sweet", "hot", "dairy-free"],
    "allergens": []
  },
  {
    "name": "Cold Brew Coffee",
    "description": "Smooth, slow-steeped cold brew served over ice.",
    "price": 4.00,
    "tags": ["strong", "cold", "dairy-free", "sugar-free"],
    "allergens": []
  },
  {
    "name": "Seasonal Pumpkin Latte",
    "description": "Spiced pumpkin sauce, espresso, and steamed milk, topped with whipped cream.",
    "price": 5.50,
    "tags": ["sweet", "hot", "seasonal"],
    "allergens": ["dairy"]
  },
  {
    "name": "Classic Croissant",
    "description": "Flaky, buttery traditional French pastry.",
    "price": 3.50,
    "tags": ["bakery", "savory"],
    "allergens": ["wheat", "dairy"]
  },
  {
    "name": "Vegan Blueberry Muffin",
    "description": "Soft, sweet muffin packed with real blueberries, entirely plant-based.",
    "price": 3.75,
    "tags": ["bakery", "sweet", "dairy-free", "vegan"],
    "allergens": ["wheat"]
  },
  {
    "name": "Nitro Cold Brew",
    "description": "Cold brew infused with nitrogen for a super smooth, creamy head.",
    "price": 4.50,
    "tags": ["strong", "cold", "dairy-free", "sugar-free"],
    "allergens": []
  },
  {
    "name": "Iced Caramel Macchiato",
    "description": "Chilled milk and vanilla syrup marked with espresso and caramel drizzle.",
    "price": 5.25,
    "tags": ["sweet", "cold"],
    "allergens": ["dairy"]
  }
]
  1. JSON ফাইলটি সঠিকভাবে ফরম্যাট করা হয়েছে কিনা তা যাচাই করুন:
  cat menu.json | python3 -m json.tool > /dev/null && echo "Valid JSON!"

💬 আলোচনা: স্থানীয় JSON বনাম লাইভ ডেটাবেস

আমরা লাইভ ডাটাবেসের পরিবর্তে কেন একটি সাধারণ লোকাল menu.json ফাইল ব্যবহার করছি?

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

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

৫. ADK এজেন্ট তৈরি করুন

এখন আপনি প্রয়োজনীয় প্যাকেজগুলো ইনস্টল করবেন এবং মূল ADK এজেন্ট লজিকটি তৈরি করবেন। আপনি একটি get_menu() টুল সংজ্ঞায়িত করবেন এবং এটিকে একটি LlmAgent এ পাস করবেন।

  1. ক্লাউড শেল এডিটরে requirements.txt তৈরি করুন এবং খুলুন:
cloudshell edit requirements.txt
  1. নিম্নলিখিত ডিপেন্ডেন্সিগুলো এডিটরে পেস্ট করুন এবং ফাইলটি সেভ করুন:
google-adk==2.2.0
streamlit==1.56.0
  1. ক্লাউড শেল এডিটরে agent.py তৈরি করে খুলুন:
cloudshell edit agent.py
  1. নিম্নলিখিত কোডটি agent.py তে পেস্ট করুন:
# agent.py
import json

from google.adk.agents import LlmAgent

# [START get_menu]
def get_menu() -> str:
    """Retrieves the coffee shop menu from menu.json.

    Returns:
        str: A JSON string representing the list of menu items.
    """
    try:
        with open("menu.json", "r") as f:
            menu_data = json.load(f)
            return json.dumps(menu_data)
    except Exception as e:
        return json.dumps({"error": f"Could not retrieve menu: {str(e)}"})
# [END get_menu]

# Create the barista agent
barista_agent = LlmAgent(
    name="barista_agent",
    model="gemini-3.5-flash",
    instruction="""You are a friendly barista at ☕ Coffee Shop.
Your job is to recommend drinks and pastries to customers based on their preferences.

Rules you MUST follow:
1.  You must recommend items ONLY from the menu returned by get_menu().
2.  Do NOT recommend or suggest any item that is not present in the menu.
3.  If a user's preference is vague or unclear, ask exactly ONE friendly clarifying question to narrow down what they want (e.g., cold or hot, sweet or strong, coffee or pastry).
4.  Be warm and welcoming, but remain professional.
5.  Ground your recommendations in the actual tags, descriptions, and allergens listed in the menu (e.g., if a user is dairy-free, recommend ONLY items tagged 'dairy-free' or with no dairy allergens).
""",
    tools=[get_menu]
)

from google.adk.apps import App

# Define the App object
app = App(
    name="coffee_barista_app",
    root_agent=barista_agent
)
  1. ক্লাউড শেল এডিটরে app.py তৈরি করে খুলুন:
cloudshell edit app.py
  1. নিম্নলিখিত কোডটি app.py তে পেস্ট করুন:
# app.py
import streamlit as st
import json

# Set page config for a premium look
st.set_page_config(
    page_title="☕ Coffee Shop - Barista Bot",
    page_icon="☕",
    layout="wide",
    initial_sidebar_state="expanded"
)

# Custom CSS to make the header sticky (adapts to light/dark themes)
st.markdown("""
<style>
    div[data-testid="element-container"]:has(.header-container),
    div.element-container:has(.header-container) {
        position: sticky;
        top: 2.875rem;
        z-index: 999;
        background-color: transparent;
        padding-bottom: 10px;
    }
</style>
""", unsafe_allow_html=True)

# App Header (using inline styles for the permanent coffee theme look)
st.markdown("""
<div class="header-container" style="text-align: center; padding: 20px; background: linear-gradient(135deg, #8B5E3C, #6F4E37); color: white; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.1);">
    <h1 style="margin: 0; font-size: 2.5rem; font-weight: 700; color: white;">☕ ☕ Coffee Shop</h1>
    <p style="margin: 5px 0 0 0; font-size: 1.1rem; opacity: 0.9; color: white;">Your friendly AI Barista is ready to help you find the perfect drink or pastry!</p>
</div>
""", unsafe_allow_html=True)

# Load Menu for the sidebar
# [START load_menu]
try:
    with open("menu.json", "r") as f:
        menu_items = json.load(f)
except Exception as e:
    st.error(f"Error loading menu: {e}")
    menu_items = []
# [END load_menu]

# Sidebar Menu & Configuration
with st.sidebar:
    st.markdown("## ☕ Coffee Shop Menu")
    st.markdown("Explore our offerings and ask the barista for recommendations.")
    st.markdown("---")

    for item in menu_items:
        with st.container(border=True):
            st.markdown(f"**{item['name']}**  •  **${item['price']:.2f}**")
            st.caption(item['description'])

            # Tags & Allergens as native badges
            tags = " ".join([f"`{t}`" for t in item.get("tags", [])])
            if tags:
                st.markdown(tags)

            allergens = ", ".join(item.get("allergens", []))
            if allergens:
                st.markdown(f"⚠️ *Allergens: {allergens}*")

# Chat Interface
if "session_id" not in st.session_state:
    import uuid
    st.session_state.session_id = str(uuid.uuid4())

if "runner" not in st.session_state:
    from google.adk.runners import InMemoryRunner
    from agent import app
    st.session_state.runner = InMemoryRunner(app=app)

if "messages" not in st.session_state:
    st.session_state.messages = [
        {"role": "assistant", "content": "Welcome to ☕ Coffee Shop! What can I get started for you today?"}
    ]

# Display existing messages
for msg in st.session_state.messages:
    with st.chat_message(msg["role"]):
        st.markdown(msg["content"])

# User Input
if prompt := st.chat_input("Ask for recommendations (e.g., 'What dairy-free pastries do you have?')"):
    # Display user message
    with st.chat_message("user"):
        st.markdown(prompt)
    st.session_state.messages.append({"role": "user", "content": prompt})

    # Generate response
    with st.chat_message("assistant"):
        try:
            import asyncio

            # Run the ADK runner asynchronously using asyncio.run
            async def fetch_response():
                return await st.session_state.runner.run_debug(
                    prompt,
                    session_id=st.session_state.session_id
                )

            res_events = asyncio.run(fetch_response())

            response_text = "".join([
                part.text
                for event in res_events
                if event.content and event.content.parts
                for part in event.content.parts
                if part.text
            ])

            st.markdown(response_text)
            st.session_state.messages.append({"role": "assistant", "content": response_text})
        except Exception as e:
            st.error(f"Apologies, I ran into an error: {e}")

💬 আলোচনা: মডেলের সুবিধা-অসুবিধা এবং পুনরুদ্ধার টোকেনের কার্যকারিতা

এজেন্টের সিস্টেম নির্দেশাবলীতে পুরো মেনুর লেখাটি পেস্ট না করে, মেনুটি আনার জন্য কেন একটি ফাংশন টুল কল করা হয়?

টোকেন অর্থনীতি! প্রম্পটে ৮টি আইটেম রাখা সাশ্রয়ী, কিন্তু যদি কফি শপটি কাস্টম উপকরণসহ ৫০০টি আইটেম পর্যন্ত প্রসারিত হয়? সরাসরি সিস্টেম প্রম্পটে বড় ডেটাসেট পেস্ট করলে আপনার প্রম্পট টোকেনের সংখ্যা বেড়ে যায়, যা প্রতিটি কোয়েরিতে ট্রানজ্যাকশন খরচ এবং এপিআই রেসপন্স ল্যাটেন্সি বৃদ্ধি করে।

একটি ADK টুল ব্যবহার করে, এজেন্ট শুধুমাত্র প্রয়োজনের সময়ই ডায়নামিকভাবে মেনুটি পড়ার জন্য অনুরোধ করে। LLM শুধুমাত্র প্রাসঙ্গিক মেনু ডেটাকে কনটেক্সট হিসেবে গ্রহণ করে, যার ফলে প্রম্পট টোকেনের আকার ন্যূনতম থাকে।

💬 আলোচনা: মেমোরি স্টেট ও প্রোডাকশন স্টোর

ব্যবহারকারী যখন তার ব্রাউজার ট্যাব বন্ধ করে দেন, তখন কি Streamlit-এর st.session_state-এ সংরক্ষিত চ্যাট হিস্ট্রিটি থেকে যায়?

না, তা হয় না। st.session_state সম্পূর্ণরূপে মেমরিতে থাকে এবং এটি সক্রিয় ব্রাউজার সংযোগের জন্য অনন্য। যদি কোনো ব্যবহারকারী পৃষ্ঠাটি রিফ্রেশ করেন বা ট্যাবটি বন্ধ করেন, তাহলে বারিস্টার সাথে তার কথোপকথনের ইতিহাস হারিয়ে যায়।

প্রোডাকশন অ্যাপ্লিকেশনের জন্য, আপনাকে ADK রানারকে ক্লাউড ফায়ারস্টোর বা রেডিসের মতো একটি পারসিস্টেন্ট স্টোরেজ ব্যাকএন্ডের সাথে সংযুক্ত করতে হবে। ADK-তে বিল্ট-ইন সার্ভিস অ্যাবস্ট্রাকশন (যেমন SessionService ) রয়েছে, যা পেজ রিলোড এবং বিভিন্ন ডিভাইসে চ্যাট হিস্ট্রি সেভ করা ও পুনরায় চালু করা সহজ করে তোলে।

৬. এজেন্টটিকে ক্লাউড রান-এ স্থাপন করুন

আপনি ক্লাউড রানের বিল্ট-ইন বিল্ডপ্যাক ব্যবহার করে সরাসরি সোর্স থেকে স্ট্রিমলিট অ্যাপ্লিকেশনটি ডেপ্লয় করবেন। ন্যূনতম বিশেষাধিকারের নীতি অনুসরণ করতে, আপনি ডিফল্ট কম্পিউট ইঞ্জিন সার্ভিস অ্যাকাউন্ট ব্যবহার না করে একটি ডেডিকেটেড কাস্টম সার্ভিস অ্যাকাউন্ট ব্যবহার করে অ্যাপ্লিকেশনটি তৈরি ও ডেপ্লয় করবেন।

  1. একটি ডেডিকেটেড সার্ভিস অ্যাকাউন্ট তৈরি করুন:
  gcloud iam service-accounts create barista-agent-sa \
    --description="Service account for Coffee Barista ADK agent on Cloud Run" \
    --display-name="Barista Agent Service Account"
  1. নতুন সার্ভিস অ্যাকাউন্টটিকে Gemini Enterprise Agent Platform ইউজার রোল ( roles/aiplatform.user ) প্রদান করুন:
  gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:barista-agent-sa@$PROJECT_ID.iam.gserviceaccount.com" \
    --role="roles/aiplatform.user"
  1. gcloud run deploy ব্যবহার করে সার্ভিসটি ডিপ্লয় করুন এবং --service-account ফ্ল্যাগের মাধ্যমে নতুন সার্ভিস অ্যাকাউন্টের ইমেলটি পাস করুন:
gcloud run deploy coffee-barista \
  --source . \
  --region $REGION \
  --allow-unauthenticated \
  --labels dev-tutorial=codelab-streamlit-rag-adk \
  --command "/cnb/lifecycle/launcher" \
  --args "sh,-c,python3 -m streamlit run app.py --server.port=\$PORT --server.address=0.0.0.0 --server.enableCORS=false --server.enableXsrfProtection=false" \
  --service-account "barista-agent-sa@$PROJECT_ID.iam.gserviceaccount.com" \
  --set-env-vars GOOGLE_GENAI_USE_VERTEXAI=TRUE,GOOGLE_CLOUD_PROJECT=$PROJECT_ID,GOOGLE_CLOUD_LOCATION=global
  1. স্থাপন করা হয়ে গেলে, কমান্ড আউটপুট থেকে সার্ভিস URL-টি খুঁজে বের করুন।

💬 আলোচনা: কন্টেইনার বনাম সোর্স ডেপ্লয়মেন্ট এবং আইএএম নিরাপত্তা

আমরা কোনো Dockerfile বা Procfile তৈরি না করেই gcloud run deploy –source কমান্ড ব্যবহার করে Cloud Run-এ ডেপ্লয় করেছি। Cloud Run কীভাবে আমাদের পাইথন অ্যাপটি কম্পাইল এবং এক্সিকিউট করার উপায় বের করল?

ক্লাউড রান আপনার রিপোজিটরি বিশ্লেষণ করার জন্য অভ্যন্তরীণভাবে বিল্ডপ্যাকস ব্যবহার করে। requirements.txt এবং পাইথন সোর্স ফাইলের উপস্থিতি শনাক্ত করার পর, ইঞ্জিনটি স্বয়ংক্রিয়ভাবে একটি পাইথন রানটাইম কন্টেইনার কম্পাইল ও প্যাকেজ করে।

একটি কাস্টম Dockerfile লিখলে আপনি আপনার কন্টেইনারের সিস্টেম প্যাকেজ এবং বেস লেয়ারগুলোর উপর সম্পূর্ণ নিয়ন্ত্রণ পান। একটি কন্টেইনারকে সম্পূর্ণরূপে কনফিগার না করেই স্টার্টআপ কমান্ড ঘোষণা করার জন্য Procfile একটি সহজ উপায়। কিন্তু দ্রুত ডেপ্লয়মেন্টের জন্য, সোর্স থেকে ডেপ্লয় করা ( --source ) অত্যন্ত কার্যকর।

ডিফল্ট Compute Engine সার্ভিস অ্যাকাউন্টটি ব্যবহার না করে, আমরা কেন barista-agent-sa নামের একটি কাস্টম সার্ভিস অ্যাকাউন্ট তৈরি করার অতিরিক্ত পদক্ষেপটি নিলাম?

নিরাপত্তাই সর্বাগ্রে! ডিফল্ট Compute Engine সার্ভিস অ্যাকাউন্টের ডিফল্টরূপে অত্যন্ত ব্যাপক এডিটর পারমিশন থাকে। ডিফল্ট সার্ভিস অ্যাকাউন্টের অধীনে আমাদের Cloud Run কন্টেইনারটি চালানোর অর্থ হলো, যদি আমাদের অ্যাপে কোনো নিরাপত্তা ত্রুটি থাকে, তাহলে একজন আক্রমণকারী আমাদের Google Cloud প্রজেক্টের অন্যান্য রিসোর্স পড়তে, লিখতে বা মুছে ফেলতে পারে।

একটি ডেডিকেটেড সার্ভিস অ্যাকাউন্ট তৈরি করে এবং সেটিকে শুধুমাত্র roles/aiplatform.user রোলটি অ্যাসাইন করার মাধ্যমে, আমরা ন্যূনতম বিশেষাধিকারের নীতি (Principle of Least Privilege) অনুসরণ করি: অ্যাপটির কাছে জেমিনিকে কল করার জন্য ঠিক ততটুকুই অ্যাক্সেস থাকে, এর বেশি কিছু নয়।

৭. RAG আচরণ পরীক্ষা করুন

একটি ওয়েব ব্রাউজারে ক্লাউড রান সার্ভিসের ইউআরএলটি খুলুন এবং এর গ্রাউন্ডিং ও নিরাপত্তা সংক্রান্ত সীমাবদ্ধতা পরীক্ষা করার জন্য এআই বারিস্তাকে প্রশ্ন করুন।

  1. মেন্যুতে অনুরোধ: জিজ্ঞাসা: "কড়া ও গরম কিছু সুপারিশ করুন।" প্রত্যাশিত: প্রতিনিধি এসপ্রেসো সুপারিশ করবেন।
  2. মেনুর বাইরের ফাঁদ: জিজ্ঞাসা করুন: "আপনাদের কাছে কি মাচা ফ্রাপুচিনো আছে?" প্রত্যাশিত: প্রতিনিধি বিনয়ের সাথে প্রত্যাখ্যান করবেন এবং ব্যাখ্যা করবেন যে এটি মেনুতে নেই।
  3. অ্যালার্জেন-সচেতন অনুরোধ: জিজ্ঞাসা করুন: "আমার ল্যাকটোজ অসহিষ্ণুতা আছে, আমি কী নিতে পারি?" প্রত্যাশিত: এজেন্ট শুধুমাত্র দুগ্ধমুক্ত মেনু আইটেমগুলির (যেমন ওট মিল্ক ল্যাটে, এসপ্রেসো, কোল্ড ব্রু) সুপারিশ করবে। এটি ক্যাপুচিনো বা ক্রোসাঁ-এর সুপারিশ করবে না।

Testing the RAG Behavior

৮. ঐচ্ছিক: ভেক্টর সার্চ ব্যবহার করে ফায়ারস্টোরে আপনার এজেন্টকে গ্রাউন্ড করুন।

প্রোডাকশন পরিস্থিতিতে, আপনার মেনু আইটেমগুলো একটি স্থানীয় menu.json ফাইলে সংরক্ষণ করা আদর্শ নয়, কারণ মেনুতে কোনো পরিবর্তন করলে কন্টেইনার ইমেজটি রি-বিল্ড করতে এবং ক্লাউড রান সার্ভিসটি পুনরায় ডিপ্লয় করতে হয়।

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

Integrating Firestore Using Vector Search

১. ফায়ারস্টোর এপিআই সক্রিয় করুন এবং ডাটাবেস ইনিশিয়ালাইজ করুন।

নেটিভ মোডে ফায়ারস্টোর এপিআই সক্রিয় করতে এবং coffee-menu নামের একটি ফায়ারস্টোর ডেটাবেস তৈরি করতে নিম্নলিখিত কমান্ডগুলি চালান:

gcloud services enable firestore.googleapis.com

gcloud firestore databases create --database="coffee-menu" --location=$REGION

দ্রষ্টব্য: এপিআই (API) সক্রিয়করণ কার্যকর হতে ১-২ মিনিট সময় লাগতে পারে। যদি ডাটাবেস তৈরির কমান্ডটি আপনাকে API [firestore.googleapis.com] not enabled on project... Would you like to enable and retry? , তাহলে এগিয়ে যেতে Y টাইপ করুন, অথবা এক মিনিট অপেক্ষা করে কমান্ডটি পুনরায় চালান।

২. মেনু ডেটা দিয়ে ফায়ারস্টোরকে সিড করুন

আপনার menu.json ফাইল থেকে মেনু আইটেমগুলো দিয়ে আপনার ফায়ারস্টোর ডেটাবেস দ্রুত পূরণ করতে, আপনি ক্লাউড শেলে স্থানীয়ভাবে একটি পাইথন স্ক্রিপ্ট চালাতে পারেন।

  1. সিডিং স্ক্রিপ্টটি চালানোর জন্য ক্লাউড শেলে স্থানীয়ভাবে ফায়ারস্টোর এবং জেনএআই ক্লায়েন্ট লাইব্রেরি ইনস্টল করুন:
pip3 install google-cloud-firestore==2.27.0 google-genai==2.11.0
  1. একটি সিডিং স্ক্রিপ্ট seed.py তৈরি করুন:
cloudshell edit seed.py
  1. নিম্নলিখিত কোডটি seed.py তে পেস্ট করুন:
# seed.py
import json
import os
from google import genai
from google.cloud import firestore
from google.cloud.firestore_v1.vector import Vector

db = firestore.Client(database="coffee-menu")
client = genai.Client(
   vertexai=True,
   project=os.environ.get("PROJECT_ID"),
   location=os.environ.get("REGION", "us-central1")
)

with open("menu.json", "r") as f:
   menu_items = json.load(f)

for item in menu_items:
   # Use the name as the document ID
   doc_id = item["name"].lower().replace(" ", "-")

   # Generate text embedding using Vertex AI text-embedding-004 model
   text_to_embed = f"{item['name']}: {item['description']}"
   response = client.models.embed_content(
       model="text-embedding-004",
       contents=text_to_embed,
   )
   embedding = response.embeddings[0].values

   # Add embedding vector to the menu item data
   item["embedding"] = Vector(embedding)

   db.collection("menu").document(doc_id).set(item)

print("Firestore menu collection seeded with vector embeddings successfully!")
  1. স্ক্রিপ্টটি চালান:
python3 seed.py

৩. ফায়ারস্টোর ভেক্টর ইনডেক্স তৈরি করুন

আপনার মেনু আইটেমগুলিতে ভেক্টর অনুসন্ধান চালানোর জন্য, আপনাকে আপনার ফায়ারস্টোর ডেটাবেসের embedding ফিল্ডে একটি কম্পোজিট ভেক্টর ইনডেক্স তৈরি করতে হবে।

ক্লাউড শেল টার্মিনালে নিম্নলিখিত কমান্ডটি চালান:

gcloud firestore indexes composite create \
 --collection-group=menu \
 --query-scope=COLLECTION \
 --database="coffee-menu" \
 --field-config=field-path=embedding,vector-config='{"dimension":"768", "flat": "{}"}'

দ্রষ্টব্য: ফায়ারস্টোর ইনডেক্স তৈরির প্রক্রিয়াটি ব্যাকগ্রাউন্ডে চলে এবং এটি সম্পন্ন হতে কয়েক মিনিট সময় লাগতে পারে। ইনডেক্স তৈরি হওয়ার সময়েও আপনি কোডল্যাবের পরবর্তী ধাপগুলো নিয়ে এগিয়ে যেতে পারেন।

৪. সার্ভিস অ্যাকাউন্টে ফায়ারস্টোর অ্যাক্সেস মঞ্জুর করুন

আপনার ক্লাউড রান সার্ভিসকে ফায়ারস্টোর থেকে ডেটা কোয়েরি করার জন্য, আপনাকে অবশ্যই এর সার্ভিস অ্যাকাউন্টকে ক্লাউড ডেটাস্টোর ইউজার ( roles/datastore.user ) রোলটি প্রদান করতে হবে:

gcloud projects add-iam-policy-binding $PROJECT_ID \
 --member="serviceAccount:barista-agent-sa@$PROJECT_ID.iam.gserviceaccount.com" \
 --role="roles/datastore.user"

দ্রষ্টব্য: যদিও আমরা ক্লাউড ফায়ারস্টোর নেটিভ মোডে ব্যবহার করছি, গুগল ক্লাউড অ্যাক্সেস কন্ট্রোল পরিচালনার জন্য সমন্বিত ক্লাউড ডেটাস্টোর আইএএম রোল (roles/datastore.viewer অথবা roles/datastore.user) ব্যবহার করে।

৫. কোডটি আপডেট করুন।

এখন, menu.json থেকে পড়ার পরিবর্তে Firestore থেকে মেনুটি নেওয়ার জন্য আপনার কোডটি আপডেট করুন।

  1. ক্লাউড শেল এডিটর-এ requirements.txt খুলুন:
cloudshell edit requirements.txt
  1. ফাইলটির শেষে Firestore এবং GenAI ক্লায়েন্ট লাইব্রেরিগুলো যুক্ত করুন এবং ফাইলটি সংরক্ষণ করুন:
google-cloud-firestore==2.27.0
google-genai==2.11.0
  1. ক্লাউড শেল এডিটর-এ agent.py খুলুন:
cloudshell edit agent.py
  1. agent.py ফাইলে # [START get_menu] ব্লকটি খুঁজুন এবং এটিকে সম্পূর্ণভাবে ( # [START get_menu] থেকে # [END get_menu] পর্যন্ত) নিম্নলিখিত Firestore ইমপ্লিমেন্টেশন দিয়ে প্রতিস্থাপন করুন:
# [START get_menu]
from google import genai
from google.cloud import firestore
from google.cloud.firestore_v1.base_vector_query import DistanceMeasure
from google.cloud.firestore_v1.vector import Vector

def get_menu(query: str) -> str:
   """Retrieves coffee shop menu items matching the user's query.

   Args:
       query: The search query or preference to find matching menu items.

   Returns:
       str: A JSON string representing the list of top matching menu items.
   """
   try:
       # Initialize clients
       db = firestore.Client(database="coffee-menu")
       client = genai.Client()

       # Generate embedding for the search query
       response = client.models.embed_content(
           model="text-embedding-004",
           contents=query,
       )
       query_vector = response.embeddings[0].values

       # Search the Firestore database using Vector Search
       results = db.collection("menu").find_nearest(
           vector_field="embedding",
           query_vector=Vector(query_vector),
           distance_measure=DistanceMeasure.COSINE,
           limit=3,
       ).stream()

       menu_data = []
       for doc in results:
           item = doc.to_dict()
           # Remove embedding field to save tokens
           item.pop("embedding", None)
           menu_data.append(item)

       return json.dumps(menu_data)
   except Exception as e:
       return json.dumps({"error": f"Could not retrieve menu: {str(e)}"})
# [END get_menu]
  1. ক্লাউড শেল এডিটরে app.py খুলুন:
cloudshell edit app.py
  1. app.py ফাইলে # [START load_menu] ব্লকটি খুঁজুন এবং এটিকে সম্পূর্ণভাবে ( # [START load_menu] থেকে # [END load_menu] পর্যন্ত) নিম্নলিখিত Firestore লোডিং লজিক দিয়ে প্রতিস্থাপন করুন:
# [START load_menu]
from google.cloud import firestore

try:
   db = firestore.Client(database="coffee-menu")
   docs = db.collection("menu").stream()
   menu_items = []
   for doc in docs:
       item = doc.to_dict()
       item.pop("embedding", None)
       menu_items.append(item)
except Exception as e:
   st.error(f"Error loading menu from Firestore: {e}")
   menu_items = []
# [END load_menu]

৬. ক্লাউড রানে পুনরায় স্থাপন করুন

আপডেট করা অ্যাপ্লিকেশনটি স্থাপন করুন:

gcloud run deploy coffee-barista \
  --source . \
  --region $REGION \
  --allow-unauthenticated \
  --labels dev-tutorial=codelab-streamlit-rag-adk \
  --command "/cnb/lifecycle/launcher" \
  --args "sh,-c,python3 -m streamlit run app.py --server.port=\$PORT --server.address=0.0.0.0 --server.enableCORS=false --server.enableXsrfProtection=false" \
  --service-account "barista-agent-sa@$PROJECT_ID.iam.gserviceaccount.com" \
  --set-env-vars GOOGLE_GENAI_USE_VERTEXAI=TRUE,GOOGLE_CLOUD_PROJECT=$PROJECT_ID,GOOGLE_CLOUD_LOCATION=global

৭. ফায়ারস্টোর ইন্টিগ্রেশন যাচাই করুন

ফায়ারস্টোরের সাথে এজেন্টের সংযোগ পরীক্ষা করতে, সরাসরি ফায়ারস্টোরে একটি একেবারে নতুন মেনু আইটেম যোগ করুন এবং যাচাই করুন যে এজেন্টটি সেটির সুপারিশ করছে কিনা।

  1. পাইথন ব্যবহার করে ফায়ারস্টোরের menu কালেকশনে একটি নতুন ডকুমেন্ট লেখার জন্য ক্লাউড শেলে নিম্নলিখিত কমান্ডটি চালান:
python3 -c "
import os
from google import genai
from google.cloud import firestore
from google.cloud.firestore_v1.vector import Vector

db = firestore.Client(database='coffee-menu')
client = genai.Client(
   vertexai=True,
   project=os.environ.get('PROJECT_ID'),
   location=os.environ.get('REGION', 'us-central1')
)

name = 'Matcha Green Tea Latte'
desc = 'Creamy steamed oat milk infused with premium Japanese matcha powder.'
res = client.models.embed_content(
   model='text-embedding-004',
   contents=f'{name}: {desc}'
)
embedding = res.embeddings[0].values

db.collection('menu').document('matcha-latte').set({
   'name': name,
   'description': desc,
   'price': 5.50,
   'tags': ['sweet', 'hot', 'dairy-free'],
   'allergens': [],
   'embedding': Vector(embedding)
})
print('Successfully added Matcha Latte with vector embeddings!')
"
  1. চ্যাট সেশনটি মুছে ফেলতে এবং নতুন ডেটাবেস স্টেট লোড করতে ব্রাউজারে আপনার Streamlit অ্যাপটি রিফ্রেশ করুন।
  2. লক্ষ্য করুন যে:
    • মাচা গ্রিন টি ল্যাটে স্বয়ংক্রিয়ভাবে সাইডবার মেনুতে প্রদর্শিত হয়।
    • চ্যাটবটকে জিজ্ঞাসা করুন: "আপনার কাছে কি কোনো মাচা পানীয় আছে?"
    • এজেন্টটির উচিত আপনার যোগ করা বিবরণ এবং মূল্য সহ নতুন মাচা গ্রিন টি ল্যাটেটি সফলভাবে সুপারিশ করা। এটি নিশ্চিত করে যে এজেন্টটি সরাসরি আপনার লাইভ ফায়ারস্টোর ডেটাবেসে কোয়েরি-ভিত্তিক!

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

আপনার গুগল ক্লাউড বিলিং অ্যাকাউন্টে চলমান চার্জ এড়াতে, ডেপ্লয় করা ক্লাউড রান সার্ভিস এবং কাস্টম সার্ভিস অ্যাকাউন্টটি ডিলিট করে দিন।

Cloud Run পরিষেবাটি মুছে ফেলুন:

gcloud run services delete coffee-barista --region $REGION --quiet

কাস্টম পরিষেবা অ্যাকাউন্টটি মুছে ফেলুন:

gcloud iam service-accounts delete barista-agent-sa@$PROJECT_ID.iam.gserviceaccount.com --quiet

(ঐচ্ছিক) ফায়ারস্টোর ডাটাবেসটি মুছে ফেলুন (যদি তৈরি করা হয়ে থাকে):

gcloud firestore databases delete --database="coffee-menu" --quiet

ঐচ্ছিক ধাপ: সম্পূর্ণ প্রজেক্টটি মুছে ফেলুন। ⚠️শুধুমাত্র তখনই এটি করুন, যদি আপনি এই ল্যাবের জন্য একটি নির্দিষ্ট প্রজেক্ট তৈরি করে থাকেন।

gcloud projects delete $PROJECT_ID

১০. অভিনন্দন

অভিনন্দন! আপনি গুগলের ADK এবং ক্লাউড রান ব্যবহার করে একটি রিট্রিভাল-অগমেন্টেড জেনারেশন (RAG) এআই বারিস্তা এজেন্ট তৈরি ও স্থাপন করেছেন।

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

  • পাইথনে সহজ RAG টুল তৈরি করা।
  • ADK LlmAgent এবং InMemoryRunner ব্যবহার।
  • Streamlit-এ স্টেটফুল চ্যাট অভিজ্ঞতা তৈরি করা।
  • সোর্স-ভিত্তিক বিল্ড ব্যবহার করে ক্লাউড রান-এ স্ট্রিমলিট স্থাপন করা হচ্ছে।

রেফারেন্স নথি