ADK এবং CloudSQL ব্যবহার করে স্থায়ী AI এজেন্ট তৈরি করা

১. ভূমিকা

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

এই হলো সেই সিস্টেম আর্কিটেকচার যা আমরা তৈরি করব

a98bbd65ddedd29c.jpeg

পূর্বশর্ত

  • ট্রায়াল বিলিং অ্যাকাউন্ট সহ একটি গুগল ক্লাউড অ্যাকাউন্ট
  • পাইথন সম্পর্কে প্রাথমিক ধারণা
  • ADK, AI এজেন্ট বা ক্লাউড SQL-এ পূর্ব অভিজ্ঞতার প্রয়োজন নেই।

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

  • কাস্টম টুল ব্যবহার করে গুগলের এজেন্ট ডেভেলপমেন্ট কিট (ADK) দিয়ে একটি এআই এজেন্ট তৈরি করুন।
  • ToolContext মাধ্যমে সেশন স্টেট পড়া ও লেখার জন্য টুলগুলো সংজ্ঞায়িত করুন।
  • সেশন-স্কোপড স্টেট এবং ইউজার-স্কোপড স্টেটের ( user: প্রিফিক্স) মধ্যে পার্থক্য করুন।
  • একটি ক্লাউড SQL PostgreSQL ইনস্ট্যান্স প্রস্তুত করুন এবং ক্লাউড শেল থেকে এতে সংযোগ করুন।
  • ডেডিকেটেড ডাটাবেস সহ স্থায়ী স্টোরেজের জন্য লোকাল স্টোরেজ (যা adk web কমান্ড ব্যবহার করার সময় ডিফল্ট থাকে) থেকে DatabaseSessionService এ মাইগ্রেট করুন।
  • যাচাই করুন যে অ্যাপ্লিকেশন পুনরায় চালু করার পরেও এবং পৃথক কথোপকথন সেশন জুড়েও এজেন্টের মেমরি অক্ষুণ্ণ থাকে।

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

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

২. আপনার পরিবেশ প্রস্তুত করুন

এই ধাপে আপনার ক্লাউড শেল পরিবেশ প্রস্তুত করা হয় এবং আপনার গুগল ক্লাউড প্রজেক্ট কনফিগার করা হয়।

ওপেন ক্লাউড শেল

আপনার ব্রাউজারে ক্লাউড শেল খুলুন। ক্লাউড শেল এই কোডল্যাবের জন্য আপনার প্রয়োজনীয় সমস্ত সরঞ্জাম সহ একটি পূর্ব-কনফিগার করা পরিবেশ প্রদান করে। অনুরোধ করা হলে অনুমোদন করুন (Authorize) বোতামে ক্লিক করুন।

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

86307fac5da2f077.png

এটাই হবে আমাদের মূল ইন্টারফেস, উপরে IDE, নিচে টার্মিনাল।

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

আপনার ওয়ার্কিং ডিরেক্টরি তৈরি করুন। এই কোডল্যাবে আপনার লেখা সমস্ত কোড এখানে থাকবে — রেফারেন্স রিপো থেকে আলাদাভাবে:

# Create your working directory
mkdir -p ~/build-agent-adk-cloudsql

# Change cloudshell workspace and working directory into previously created dir
cloudshell workspace ~/build-agent-adk-cloudsql && cd ~/build-agent-adk-cloudsql

আপনার টার্মিনাল চালু করতে ভিউ -> টার্মিনাল-এ যান।

ccc3214812750f1c.png

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

প্রজেক্ট সেটআপ স্ক্রিপ্টটি আপনার ওয়ার্কিং ডিরেক্টরিতে ডাউনলোড করুন:

curl -sL https://raw.githubusercontent.com/alphinside/cloud-trial-project-setup/main/setup_verify_trial_project.sh -o setup_verify_trial_project.sh

স্ক্রিপ্টটি চালান। এটি আপনার ট্রায়াল বিলিং অ্যাকাউন্ট যাচাই করে, একটি নতুন প্রজেক্ট তৈরি করে (অথবা বিদ্যমান কোনো প্রজেক্টকে বৈধতা দেয়), আপনার প্রজেক্ট আইডি বর্তমান ডিরেক্টরির একটি .env ফাইলে সংরক্ষণ করে এবং টার্মিনালে সক্রিয় প্রজেক্টটি সেট করে।

bash setup_verify_trial_project.sh && source .env

এটি চালানোর সময়, আপনাকে একটি প্রস্তাবিত প্রজেক্ট আইডি নাম দেখানো হবে, চালিয়ে যাওয়ার জন্য আপনি Enter চাপতে পারেন।

54b615cd15f2a535.png

কিছুক্ষণ অপেক্ষা করার পর যদি আপনি আপনার কনসোলে এই আউটপুটটি দেখতে পান, তাহলে আপনি পরবর্তী ধাপে যাওয়ার জন্য প্রস্তুত। e576b4c13d595156.png

সম্পাদিত স্ক্রিপ্টটি নিম্নলিখিত ধাপগুলো সম্পন্ন করে:

  1. আপনার একটি সক্রিয় ট্রায়াল বিলিং অ্যাকাউন্ট আছে কিনা তা যাচাই করুন।
  2. .env ফাইলে কোনো বিদ্যমান প্রজেক্ট আছে কিনা তা পরীক্ষা করুন (যদি থাকে)।
  3. একটি নতুন প্রকল্প তৈরি করুন অথবা বিদ্যমান প্রকল্পটি পুনরায় ব্যবহার করুন।
  4. ট্রায়াল বিলিং অ্যাকাউন্টটি আপনার প্রোজেক্টের সাথে লিঙ্ক করুন।
  5. প্রজেক্ট আইডিটি .env ফাইলে সংরক্ষণ করুন।
  6. প্রজেক্টটিকে সক্রিয় gcloud প্রজেক্ট হিসেবে সেট করুন।

ক্লাউড শেল টার্মিনাল প্রম্পটে আপনার ওয়ার্কিং ডিরেক্টরির পাশে থাকা হলুদ লেখাটি দেখে প্রজেক্টটি সঠিকভাবে সেট করা হয়েছে কিনা তা যাচাই করুন। সেখানে আপনার প্রজেক্ট আইডি প্রদর্শিত হওয়া উচিত।

9e11ee21cd23405f.png

প্রয়োজনীয় এপিআইগুলি সক্রিয় করুন

এই কোডল্যাবের জন্য প্রয়োজনীয় গুগল ক্লাউড এপিআইগুলো সক্রিয় করুন:

gcloud services enable \
  aiplatform.googleapis.com \
  sqladmin.googleapis.com \
  compute.googleapis.com
  • ভার্টেক্স এআই এপিআই ( aiplatform.googleapis.com ) — আপনার এজেন্ট ভার্টেক্স এআই-এর মাধ্যমে জেমিনি মডেল ব্যবহার করে।
  • ক্লাউড এসকিউএল অ্যাডমিন এপিআই ( sqladmin.googleapis.com ) — এর মাধ্যমে আপনি স্থায়ী স্টোরেজের জন্য একটি পোস্টগ্রেসকিউএল ইনস্ট্যান্স প্রোভিশন এবং ম্যানেজ করেন।
  • কম্পিউট ইঞ্জিন এপিআই ( compute.googleapis.com ) — ক্লাউড এসকিউএল ইনস্ট্যান্স তৈরি করার জন্য এটি প্রয়োজন।

কনফিগ জেমিনি এবং ক্লাউড পণ্য অঞ্চল

এগিয়ে যাওয়ার আগে, চলুন আমরা যে প্রোডাক্টটি ব্যবহার করব তার জন্য প্রয়োজনীয় অবস্থান/অঞ্চল কনফিগারেশনও সেট আপ করে নিই। আমাদের .env ফাইলে নিম্নলিখিত কনফিগারেশনটি যোগ করুন।

# This is for our Gemini endpoint
echo "GOOGLE_CLOUD_LOCATION=global" >> .env

# This is for our other Cloud products
echo "REGION=us-central1" >> .env

source .env

চলুন পরবর্তী ধাপে এগিয়ে যাই।

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

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

ইনস্ট্যান্স তৈরি শুরু করুন

আপনার .env ফাইলে ডাটাবেস পাসওয়ার্ডটি যোগ করুন এবং ফাইলটি রিলোড করুন, আমরা পাসওয়ার্ড হিসেবে cafe-agent-pwd-2025 ব্যবহার করব।

echo "DB_PASSWORD=cafe-agent-pwd-2025" >> .env
source .env

একটি ক্লাউড এসকিউএল পোস্টগ্রেসকিউএল ইনস্ট্যান্স তৈরি করতে এই কমান্ডটি চালান। এটি সম্পন্ন হতে কয়েক মিনিট সময় লাগবে — এটিকে চলতে দিন এবং পরবর্তী বিভাগে যান

gcloud sql instances create cafe-concierge-db \
  --database-version=POSTGRES_17 \
  --edition=ENTERPRISE \
  --region=${REGION} \
  --availability-type=ZONAL \
  --project=${GOOGLE_CLOUD_PROJECT} \
  --tier=db-f1-micro \
  --root-password=${DB_PASSWORD} \
  --quiet &

উপরোক্ত কমান্ড সম্পর্কে কয়েকটি বিষয় উল্লেখ্য:

  • db-f1-micro হলো সবচেয়ে ছোট (এবং সবচেয়ে সস্তা) ক্লাউড এসকিউএল টায়ার — যা এই কোডল্যাবের জন্য যথেষ্ট।
  • --root-password ডিফল্ট postgres ব্যবহারকারীর পাসওয়ার্ড সেট করে।
  • কমান্ডের শেষে & চিহ্নটি কমান্ডটিকে ব্যাকগ্রাউন্ডে চালায়, ফলে আপনি আপনার কাজ চালিয়ে যেতে পারেন।

প্রক্রিয়াটি ব্যাকগ্রাউন্ডে চলবে, তবে এর কনসোল আউটপুট মাঝে মাঝে বর্তমান টার্মিনালে দেখা যাবে। চলুন ক্লাউড শেলে একটি নতুন টার্মিনাল ট্যাব খুলি (+ আইকনে ক্লিক করুন), যাতে আমরা আরও মনোযোগ দিতে পারি।

b01e3fbd89f17332.png

পুনরায় আপনার ওয়ার্কিং ডিরেক্টরিতে যান এবং পূর্ববর্তী সেটআপ স্ক্রিপ্ট ব্যবহার করে প্রজেক্টটি সক্রিয় করুন।

cd ~/build-agent-adk-cloudsql
bash setup_verify_trial_project.sh && source .env

তাহলে, চলুন পরবর্তী বিভাগে যাওয়া যাক।

৪. ক্যাফে কনসিয়ার্জ এজেন্ট তৈরি করুন

এই ধাপে আপনার ADK এজেন্টের জন্য প্রোজেক্ট কাঠামো তৈরি করা হয় এবং একটি মেনু টুলসহ প্রাথমিক ক্যাফে কনসিয়ার্জ সংজ্ঞায়িত করা হয়।

পাইথন প্রজেক্টটি শুরু করুন

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

একটি পাইথন প্রজেক্ট শুরু করুন এবং ADK-কে একটি ডিপেন্ডেন্সি হিসেবে যুক্ত করুন:

uv init
uv add google-adk==1.25.0 asyncpg

uv init একটি pyproject.toml এবং একটি ভার্চুয়াল এনভায়রনমেন্ট তৈরি করে। uv add ডিপেন্ডেন্সিটি ইনস্টল করে এবং pyproject.toml ফাইলে তা নথিভুক্ত করে।

এজেন্ট প্রকল্পের কাঠামো প্রারম্ভিক করুন

ADK একটি নির্দিষ্ট ফোল্ডার বিন্যাস আশা করে: আপনার এজেন্টের নামে একটি ডিরেক্টরি, যার ভেতরে __init__.py , agent.py এবং .env ফাইলগুলো থাকবে।

এটি দ্রুত প্রতিষ্ঠা করতে ADK-তে একটি বিল্ট-ইন কমান্ড রয়েছে, নিম্নলিখিত কমান্ডটি চালান।

uv run adk create cafe_concierge \
    --model gemini-2.5-flash \
    --project ${GOOGLE_CLOUD_PROJECT} \
    --region ${GOOGLE_CLOUD_LOCATION}

এই কমান্ডটি gemini-2.5-flash মূল অংশ হিসেবে ব্যবহার করে একটি এজেন্ট কাঠামো তৈরি করবে। আপনার ডিরেক্টরিটি এখন দেখতে এইরকম হবে:

build-agent-adk-cloudsql/
├── cafe_concierge/
│   ├── __init__.py
│   ├── agent.py
│   └── .env
├── pyproject.toml
├── .env      
├── .venv/
└── ...

এজেন্টকে লিখুন

ক্লাউড শেল এডিটরে cafe_concierge/agent.py খুলুন।

cloudshell edit cafe_concierge/agent.py

এবং নিম্নলিখিত কোড দিয়ে ফাইলটি ওভাররাইট করুন

# cafe_concierge/agent.py
from google.adk.agents import LlmAgent
from google.adk.tools import ToolContext

CAFE_MENU = {
    "espresso": {
        "price": 3.50,
        "description": "Rich and bold single shot",
        "tags": ["vegan", "dairy-free", "gluten-free"],
    },
    "latte": {
        "price": 5.00,
        "description": "Espresso with steamed milk",
        "tags": ["gluten-free"],
    },
    "oat milk latte": {
        "price": 5.50,
        "description": "Espresso with steamed oat milk",
        "tags": ["vegan", "dairy-free", "gluten-free"],
    },
    "cappuccino": {
        "price": 4.50,
        "description": "Espresso with equal parts steamed milk and foam",
        "tags": ["gluten-free"],
    },
    "cold brew": {
        "price": 4.00,
        "description": "Slow-steeped for 12 hours, served over ice",
        "tags": ["vegan", "dairy-free", "gluten-free"],
    },
    "matcha latte": {
        "price": 5.50,
        "description": "Ceremonial grade matcha with steamed milk",
        "tags": ["gluten-free"],
    },
    "croissant": {
        "price": 3.00,
        "description": "Buttery, flaky French pastry",
        "tags": [],
    },
    "banana bread": {
        "price": 3.50,
        "description": "Homemade with walnuts",
        "tags": ["vegan"],
    },
}


def get_menu() -> dict:
    """Returns the full cafe menu with prices, descriptions, and dietary tags.

    Use this tool when the customer asks what's available, wants to see
    the menu, or asks about specific items.
    """
    return CAFE_MENU


root_agent = LlmAgent(
    name="cafe_concierge",
    model="gemini-2.5-flash",
    instruction="""You are a friendly and knowledgeable barista at "The Cloud Cafe".

Your job:
- Help customers browse the menu and answer questions about items.
- Take coffee and food orders.
- Remember and respect dietary preferences.

Be conversational, warm, and concise. If a customer mentions a dietary
restriction, acknowledge it and suggest suitable options from the menu.
""",
    tools=[get_menu],
)

এটি get_menu() নামক একটি টুলসহ একটি বেসিক এজেন্টকে সংজ্ঞায়িত করে। এজেন্টটি মেনু সম্পর্কিত প্রশ্নের উত্তর দিতে পারে, কিন্তু এখনও অর্ডার ট্র্যাক করতে বা পছন্দ মনে রাখতে পারে না।

এজেন্টটি চলে কিনা তা যাচাই করুন

আপনার ওয়ার্কিং ডিরেক্টরি থেকে ADK ডেভ UI চালু করুন:

cd ~/build-agent-adk-cloudsql
uv run adk web

ক্লাউড শেলের ওয়েব প্রিভিউ ফিচার ব্যবহার করে টার্মিনালে দেখানো URL-টি (সাধারণত http://localhost:8000 ) খুলুন। উপরের বাম কোণায় থাকা এজেন্ট ড্রপডাউন থেকে cafe_concierge নির্বাচন করুন।

চ্যাট বারে নিম্নলিখিত লেখাটি টাইপ করুন এবং এজেন্ট মেনুর আইটেম ও দামসহ উত্তর দেয় কিনা তা যাচাই করুন।

What's on the menu?

376ee6b189657e7a.png

এগিয়ে যাওয়ার আগে Ctrl+C চেপে ডেভ UI বন্ধ করুন।

৫. স্টেটফুল অর্ডার ম্যানেজমেন্ট যোগ করুন

এজেন্টটি মেনু দেখাতে পারে, কিন্তু অর্ডার নিতে বা পছন্দ মনে রাখতে পারে না। এই ধাপে চারটি টুল যোগ করা হয়েছে, যেগুলো ADK-এর স্টেট সিস্টেম ব্যবহার করে একটি কথোপকথনের মধ্যে অর্ডার ট্র্যাক করে এবং বিভিন্ন কথোপকথন জুড়ে খাদ্যাভ্যাসের পছন্দ সংরক্ষণ করে।

সেশনের ঘটনা এবং অবস্থা বুঝুন

প্রতিটি ADK কথোপকথন একটি Session অবজেক্টের মধ্যে থাকে। একটি সেশন দুটি স্বতন্ত্র জিনিস ট্র্যাক করে: ইভেন্ট এবং স্টেট । সঠিক জিনিসগুলো সঠিক উপায়ে মনে রাখতে পারে এমন এজেন্ট তৈরি করার জন্য এই পার্থক্যটি বোঝা অত্যন্ত গুরুত্বপূর্ণ।

ইভেন্ট হলো একটি কথোপকথনে ঘটে যাওয়া সমস্ত কিছুর কালানুক্রমিক বিবরণ। ব্যবহারকারীর প্রতিটি বার্তা, এজেন্টের প্রতিটি প্রতিক্রিয়া, প্রতিটি টুল কল এবং তার রিটার্ন ভ্যালু — এগুলোর প্রত্যেকটি একটি Event হিসাবে রেকর্ড করা হয় এবং সেশনের ইভেন্ট তালিকায় যুক্ত করা হয়। ইভেন্টগুলো অপরিবর্তনীয়: একবার রেকর্ড হয়ে গেলে, সেগুলো আর কখনো পরিবর্তন হয় না। ইভেন্টগুলোকে একটি কথোপকথনের সম্পূর্ণ প্রতিলিপি হিসাবে ভাবুন।

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

তাদের মধ্যে সম্পর্কটি হলো:

cd9871699451867d.png

টুলগুলো ToolContext মাধ্যমে স্টেট রিড ও রাইট করে — এটি এমন একটি অবজেক্ট যা ADK স্বয়ংক্রিয়ভাবে যেকোনো টুল ফাংশনে ইনজেক্ট করে, যদি সেই ফাংশন এটিকে একটি প্যারামিটার হিসেবে ডিক্লেয়ার করে। আপনাকে এটি নিজে তৈরি করতে হয় না। tool_context.state এর মাধ্যমে একটি টুল সেশনের স্টেট স্ক্র্যাচপ্যাড রিড ও রাইট করতে পারে। ADK ফাংশন সিগনেচারটি পরীক্ষা করে: ToolContext টাইপের প্যারামিটারগুলো ইনজেক্ট করা হয়, এবং অন্যান্য সমস্ত প্যারামিটার কথোপকথনের উপর ভিত্তি করে LLM দ্বারা পূরণ করা হয়।

যখন কোনো টুল tool_context.state এ লেখে, ADK সেই পরিবর্তনটিকে ইভেন্টের মধ্যে state_delta হিসেবে রেকর্ড করে। এরপর SessionService সেশনের বর্তমান স্টেটে সেই ডেল্টাটি প্রয়োগ করে। এর মানে হলো, স্টেটের পরিবর্তনগুলো সবসময় সেই ইভেন্ট পর্যন্ত শনাক্ত করা যায়, যা সেগুলোর কারণ। callback_context এর মতো অন্যান্য ধরনের কনটেক্সটের ক্ষেত্রেও এটি সত্য।

রাজ্যের উপসর্গগুলি বুঝুন

স্টেট কী-গুলো তাদের স্কোপ নিয়ন্ত্রণ করতে প্রিফিক্স ব্যবহার করে:

উপসর্গ

পরিধি

রিস্টার্ট কি টিকে থাকবে? (ডিবি সহ)

(কিছুই না)

শুধুমাত্র বর্তমান অধিবেশন

হ্যাঁ

user:

এই ব্যবহারকারীর সমস্ত সেশন

হ্যাঁ

app:

সমস্ত সেশন, সমস্ত ব্যবহারকারী

হ্যাঁ

temp:

শুধুমাত্র বর্তমান আহ্বান

না

এই কোডল্যাবে আপনি এই প্রিফিক্সগুলোর মধ্যে দুটি ব্যবহার করবেন: সেশন-স্কোপড ডেটার জন্য আনপ্রিফিক্সড কী (বর্তমান অর্ডার — যা শুধুমাত্র এই কথোপকথনের জন্য প্রাসঙ্গিক) এবং ইউজার-স্কোপড ডেটার জন্য user: কী (খাদ্যাভ্যাস সংক্রান্ত পছন্দ — যা এই ব্যবহারকারীর সমস্ত কথোপকথনের জন্য প্রাসঙ্গিক)।

স্টেটফুল টুলগুলো যোগ করুন

ক্লাউড শেল এডিটরে cafe_concierge/agent.py ফাইলটি খুলুন।

cloudshell edit cafe_concierge/agent.py

তারপর, root_agent সংজ্ঞার উপরে নিম্নলিখিত চারটি ফাংশন যোগ করুন:

# cafe_concierge/agent.py (add below get_menu, above root_agent)

def place_order(tool_context: ToolContext, items: list[str]) -> dict:
    """Places an order for the specified menu items.

    Use this tool when the customer confirms they want to order something.

    Args:
        tool_context: Provided automatically by ADK.
        items: A list of menu item names the customer wants to order.
    """
    valid_items = []
    invalid_items = []
    total = 0.0

    for item in items:
        item_lower = item.lower()
        if item_lower in CAFE_MENU:
            valid_items.append(item_lower)
            total += CAFE_MENU[item_lower]["price"]
        else:
            invalid_items.append(item)

    if not valid_items:
        return {"error": f"None of these items are on our menu: {invalid_items}"}

    order = {"items": valid_items, "total": round(total, 2)}
    tool_context.state["current_order"] = order

    result = {"order": order}
    if invalid_items:
        result["warning"] = f"These items are not on our menu: {invalid_items}"
    return result


def get_order_summary(tool_context: ToolContext) -> dict:
    """Returns the current order summary for this session.

    Use this tool when the customer asks about their current order,
    wants to review what they ordered, or asks for the total.

    Args:
        tool_context: Provided automatically by ADK.
    """
    order = tool_context.state.get("current_order")
    if order:
        return {"order": order}
    return {"message": "No order has been placed yet in this session."}


def set_dietary_preference(tool_context: ToolContext, preference: str) -> dict:
    """Saves a dietary preference that persists across all conversations.

    Use this tool when the customer mentions a dietary restriction or
    preference (e.g., "I'm vegan", "I'm lactose intolerant",
    "I have a nut allergy").

    Args:
        tool_context: Provided automatically by ADK.
        preference: The dietary preference to save (e.g., "vegan",
            "lactose intolerant", "nut allergy").
    """
    existing = tool_context.state.get("user:dietary_preferences", [])
    if not isinstance(existing, list):
        existing = []

    preference_lower = preference.lower().strip()
    if preference_lower not in existing:
        existing.append(preference_lower)

    tool_context.state["user:dietary_preferences"] = existing
    return {
        "saved": preference_lower,
        "all_preferences": existing,
    }


def get_dietary_preferences(tool_context: ToolContext) -> dict:
    """Retrieves the customer's saved dietary preferences.

    Use this tool when you need to check the customer's dietary
    restrictions before making recommendations.

    Args:
        tool_context: Provided automatically by ADK.
    """
    preferences = tool_context.state.get("user:dietary_preferences", [])
    if preferences:
        return {"preferences": preferences}
    return {"message": "No dietary preferences saved yet."}

দুটি বিষয় লক্ষণীয়:

  1. place_order এবং get_order_summary আনপ্রিফিক্সড কী ( current_order ) ব্যবহার করে। এই অবস্থাটি বর্তমান সেশনের সাথে যুক্ত — একটি নতুন কথোপকথন একটি খালি অর্ডার দিয়ে শুরু হয়।
  2. set_dietary_preference এবং get_dietary_preferences user: প্রিফিক্স ( user:dietary_preferences ) ব্যবহার করে। এই স্টেটটি একই ইউজারের সকল সেশনের জন্য শেয়ার করা হয়।

নতুন সরঞ্জাম ও নির্দেশাবলী দিয়ে এজেন্টকে আপডেট করুন।

ফাইলের শেষে থাকা root_agent ডেফিনিশনটি নিচেরটি দিয়ে প্রতিস্থাপন করুন:

# cafe_concierge/agent.py (replace the existing root_agent)

root_agent = LlmAgent(
    name="cafe_concierge",
    model="gemini-2.5-flash",
    instruction="""You are a friendly and knowledgeable barista at "The Cloud Cafe".

Your job:
- Help customers browse the menu and answer questions about items.
- Take coffee and food orders.
- Remember and respect dietary preferences.

The customer's saved dietary preferences are: {user:dietary_preferences?}

IMPORTANT RULES:
- When a customer mentions a dietary restriction, ALWAYS save it using the
  set_dietary_preference tool before doing anything else.
- Before recommending items, check the customer's dietary preferences. If they
  have preferences saved, only recommend items compatible with those
  restrictions. Check the menu item tags to determine compatibility.
- When placing an order, confirm the items and total with the customer.

Be conversational, warm, and concise.
""",
    tools=[
        get_menu,
        place_order,
        get_order_summary,
        set_dietary_preference,
        get_dietary_preferences,
    ],
)

এই নির্দেশনাটি গ্রাহকের সংরক্ষিত পছন্দসমূহ সরাসরি প্রম্পটে যুক্ত করার জন্য {user:dietary_preferences?} স্টেট ইনজেকশন টেমপ্লেটটি ব্যবহার করে।

সম্পূর্ণ ফাইলটি যাচাই করুন

আপনার cafe_concierge/agent.py ফাইলে এখন নিম্নলিখিত বিষয়গুলো থাকা উচিত:

  • CAFE_MENU অভিধান
  • পাঁচটি টুল ফাংশন: get_menu , place_order , get_order_summary , set_dietary_preference , get_dietary_preferences
  • পাঁচটি টুল সহ root_agent সংজ্ঞা

৬. ADK Dev UI ব্যবহার করে এজেন্টটি পরীক্ষা করুন।

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

ডেভ UI শুরু করুন

cd ~/build-agent-adk-cloudsql
uv run adk web

পোর্ট ৮০০০-এ ওয়েব প্রিভিউ খুলুন এবং ড্রপডাউন থেকে cafe_concierge নির্বাচন করুন।

কথোপকথন ১: অর্ডার দিন এবং পছন্দসমূহ নির্ধারণ করুন

এই নির্দেশগুলো ক্রমানুসারে চেষ্টা করুন:

What's on the menu?
I'm lactose intolerant
What would you recommend?
I'll have an oat milk latte and a banana bread
What's my order?

সেশন ইভেন্টগুলি পরিদর্শন করুন

সমস্ত ইভেন্ট ক্যাপচার করে ওয়েব UI-তে দেখানো হবে, আপনি চ্যাটবক্সে দেখবেন যে সেখানে শুধু আপনার প্রম্পট এবং রেসপন্সই নয়, বরং tool_call এবং tool_response রয়েছে।

9051b46978c8017b.png

আপনি ঘটনাগুলোর একটি ক্রমিক তালিকা দেখতে পাবেন। প্রতিটি ঘটনার একজন রচয়িতা (যিনি এটি তৈরি করেছেন) এবং একটি ধরন (এটি কোন ধরনের মিথস্ক্রিয়াকে প্রতিনিধিত্ব করে) থাকে:

লেখক

প্রকার

এটি যা প্রতিনিধিত্ব করে

user

message

চ্যাটে আপনার টাইপ করা একটি বার্তা

cafe_concierge

message

এজেন্টের টেক্সট প্রতিক্রিয়া

cafe_concierge

tool_call

এজেন্ট একটি টুল কল করার সিদ্ধান্ত নিয়েছে (ফাংশনের নাম ও আর্গুমেন্ট দেখায়)

cafe_concierge

tool_response

একটি টুল কল থেকে প্রাপ্ত রিটার্ন মান

tool_call ইভেন্টগুলোর মধ্যে একটিতে ক্লিক করুন — উদাহরণস্বরূপ, set_dietary_preference কলটিতে। আপনি দেখতে পাবেন:

  • ফাংশনের নাম : set_dietary_preference
  • আর্গুমেন্ট : {"preference": "lactose intolerant"}

এখন এর ঠিক নিচে থাকা সংশ্লিষ্ট tool_response ইভেন্টটিতে ক্লিক করুন। আপনি রিটার্ন ভ্যালুটি দেখতে পাবেন:

  • প্রতিক্রিয়া : {"saved": "lactose intolerant", "all_preferences": ["lactose intolerant"]}

b528f4efd6a9f337.png

`tool_response` ইভেন্টের ভিতরে ` state_delta ফিল্ডটি খুঁজুন। এই টুল কলটির ফলে ঠিক কোন স্টেট পরিবর্তিত হয়েছে, তা এটি দেখায়:

state_delta: {"user:dietary_preferences": ["lactose intolerant"]}

প্রতিটি অবস্থার পরিবর্তন একটি নির্দিষ্ট ঘটনার সাথে সম্পর্কিত। এভাবেই ADK নিশ্চিত করে যে স্টেট স্ক্র্যাচপ্যাড কথোপকথনের ইতিহাসের সাথে সিঙ্ক থাকে।

সেশনের অবস্থা পরিদর্শন করুন

স্টেট ট্যাবে ক্লিক করুন। ইভেন্টস লগের (যা সম্পূর্ণ ইতিহাস দেখায়) বিপরীতে, স্টেট ট্যাবটি এজেন্ট এই মুহূর্তে যা জানে তার একটি স্ন্যাপশট দেখায় — অর্থাৎ প্রতিটি স্টেট কী-এর বর্তমান মান।

5e06fb54f3f0d8d6.png

আপনি দুটি এন্ট্রি দেখতে পাবেন:

  • current_order{"items": ["oat milk latte", "banana bread"], "total": 9.0}
  • user:dietary_preferences["lactose intolerant"]

কী-গুলোর নামে পার্থক্যটি লক্ষ্য করুন:

  • current_order কোনো প্রিফিক্স নেই — এটি সেশন-স্কোপড। এটি শুধুমাত্র এই কথোপকথনেই বিদ্যমান থাকে এবং সেশন শেষ হলে অদৃশ্য হয়ে যায়।
  • user:dietary_preferences এর শুরুতে user: উপসর্গটি থাকে — এটি ব্যবহারকারী-কেন্দ্রিক। এই ব্যবহারকারীর প্রতিটি সেশনে এটি ব্যবহৃত হয়।

কোডে এই পার্থক্যটি অদৃশ্য (উভয় ক্ষেত্রেই tool_context.state ব্যবহৃত হয়), কিন্তু ডেটা কতদূর পৌঁছাবে তা এটি নিয়ন্ত্রণ করে। পরবর্তী টেস্টে আপনি এর প্রয়োগ দেখতে পাবেন।

কথোপকথন ২: বিভিন্ন সেশনের ব্যবহারকারীর অবস্থা যাচাই করুন

নতুন করে কথোপকথন শুরু করতে ডেভ UI-তে থাকা 'New Session' বোতামটি ক্লিক করুন। এটি একই ব্যবহারকারীর জন্য একটি নতুন সেশন তৈরি করবে।

57408cfae5f041ac.png

এই প্রম্পটটি চেষ্টা করুন:

What do you recommend for me?

নতুন সেশনে স্টেট ট্যাবটি দেখুন। user:dietary_preferences কী-টি থেকে যায়, কিন্তু current_order চলে যায় — এই স্টেটটি আগের সেশনের সাথে যুক্ত ছিল।

764eb3885251307d.png

৭. স্থানীয় স্টোরেজ সীমাবদ্ধতা পর্যবেক্ষণ করুন

এজেন্ট বিভিন্ন সেশন জুড়ে পছন্দগুলো মনে রাখে — কিন্তু শুধুমাত্র যতক্ষণ লোকাল স্টোরেজ বিদ্যমান থাকে। এই ধাপটি লোকাল স্টোরেজের মৌলিক সীমাবদ্ধতা তুলে ধরে।

এজেন্টকে আবার শুরু করুন

আপনি আগের ধাপের শেষে ডেভ UI বন্ধ করেছিলেন। এখন চলুন লোকাল স্টোরেজটি মুছে ফেলে আবার চালু করি, যাতে স্টেটলেস সার্ভারলেস পরিবেশ অনুকরণ করা যায়:

cd ~/build-agent-adk-cloudsql
rm -f cafe_concierge/.adk/session.db
uv run adk web

এখন, পোর্ট 8000-এ ওয়েব প্রিভিউ খুলুন এবং cafe_concierge নির্বাচন করুন।

পরীক্ষার পছন্দের স্মরণ

প্রকার:

Do you remember my dietary preferences?

এজেন্টের কিছুই মনে নেই। খাদ্যাভ্যাস, অর্ডারের ইতিহাস—সব উধাও।

82a5e05434cafe83.png

লোকাল স্টোরেজ ডিলিট করার ফলে সবকিছু মুছে গেছে, যা সাধারণত সার্ভারলেস এনভায়রনমেন্ট ব্যবহার করার সময় ঘটে থাকে। session.db প্রসেস মেমরিতে সমস্ত স্টেট সংরক্ষণ করে। এটি সরিয়ে ফেললে সবকিছু মুছে যায়।

সমাধানটি হলো: DatabaseSessionService নির্দিষ্ট করা, যা এই টিউটোরিয়ালে সমস্ত সেশন ডেটা ক্লাউড SQL-এর একটি PostgreSQL ডেটাবেসে সংরক্ষণ করবে। এজেন্ট কোড এবং টুলস হুবহু একই থাকবে — শুধুমাত্র স্টোরেজ ব্যাকএন্ড পরিবর্তিত হবে।

এগিয়ে যাওয়ার আগে Ctrl+C চেপে ডেভ UI বন্ধ করুন।

৮. ডাটাবেস সেটআপ পুনরায় পর্যালোচনা করুন

এই পর্যায়ে, আমাদের ডাটাবেস ইনস্ট্যান্স তৈরি হয়ে যাওয়ার কথা। চলুন, এটি যাচাই করার জন্য নিচের কমান্ডটি চালান।

gcloud sql instances describe cafe-concierge-db --format="value(state)"

আপনি নিম্নলিখিত আউটপুটটি দেখতে পাবেন, এটিকে সমাপ্ত হিসাবে চিহ্নিত করুন।

RUNNABLE

ডাটাবেস তৈরি করুন

এজেন্টের সেশন ডেটার জন্য একটি ডেডিকেটেড ডেটাবেস তৈরি করুন:

gcloud sql databases create agent_db --instance=cafe-concierge-db

ক্লাউড SQL অথোরাইজেশন প্রক্সি চালু করুন

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

cloud-sql-proxy ${GOOGLE_CLOUD_PROJECT}:${REGION}:cafe-concierge-db --port 5432 &

কমান্ডের শেষে & চিহ্নটি প্রক্সিকে ব্যাকগ্রাউন্ডে চালু করে। প্রক্সি প্রস্তুত হলে আপনি নিচে দেখানো আউটপুটের মতো একটি নিশ্চিতকরণ দেখতে পাবেন।

[your-project-id:your-region:cafe-concierge-db] Listening on 127.0.0.1:5432
The proxy has started successfully and is ready for new connections!

সংযোগ যাচাই করুন

প্রক্সির মাধ্যমে ডাটাবেসে সংযোগ করতে পারছেন কিনা তা পরীক্ষা করুন:

psql "host=127.0.0.1 port=5432 dbname=agent_db user=postgres password=$DB_PASSWORD" -c "SELECT 'Connection ok' AS status;"

আপনার দেখা উচিত:

      status
---------------------
 Connection ok
(1 row)

৯. সেশন জুড়ে স্থায়ী স্মৃতি যাচাই করুন

এই ধাপটি প্রমাণ করে যে আপনার এজেন্টের মেমরি রিসেটের পরেও টিকে থাকে, যখন আমরা নিশ্চিত করি যে cafe_concierge/.adk/session_db (স্থানীয় ডেটাবেস) মুছে ফেলা হয়েছে এবং এটি একাধিক কথোপকথন সেশন জুড়ে বিস্তৃত।

এজেন্ট শুরু করুন

ক্লাউড এসকিউএল অথোরাইজেশন প্রক্সি এখনও চালু আছে কিনা তা নিশ্চিত করুন (জবস দিয়ে যাচাই করুন)। যদি এটি চালু না থাকে, তবে এটিকে পুনরায় চালু করুন:

if ss -tlnp | grep -q ':5432 '; then
  echo "Cloud SQL Auth Proxy is already running."
else
  cloud-sql-proxy ${GOOGLE_CLOUD_PROJECT}:${REGION}:cafe-concierge-db --port 5432 &
fi

এরপরে, সেশন সার্ভিস হিসেবে ডাটাবেস নির্দিষ্ট করে ADK ডেভ UI চালু করা যাক।

uv run adk web --session_service_uri postgresql+asyncpg://postgres:${DB_PASSWORD}@127.0.0.1:5432/agent_db

পোর্ট ৮০০০-এ ওয়েব প্রিভিউ খুলুন এবং cafe_concierge নির্বাচন করুন।

পরীক্ষা ১: অর্ডার দিন এবং পছন্দসমূহ নির্ধারণ করুন

প্রথম সেশনে, এই নির্দেশাবলী অনুসরণ করুন:

Show me the menu
I'm vegan
What can I eat?
I'll have a cold brew and banana bread

পরীক্ষা ২: রিস্টার্টে টিকে থাকা

Ctrl+C চেপে ডেভ UI বন্ধ করুন এবং নিশ্চিত করুন যে স্থানীয় session.db ফাইলটি মুছে ফেলা হয়েছে।

rm -f cafe_concierge/.adk/session.db

তারপর, ডেভ UI সার্ভারটি পুনরায় চালান।

uv run adk web --session_service_uri postgresql+asyncpg://postgres:${DB_PASSWORD}@127.0.0.1:5432/agent_db

পোর্ট 8000-এ ওয়েব প্রিভিউ খুলুন, cafe_concierge নির্বাচন করুন এবং একটি নতুন সেশন শুরু করুন। তারপর জিজ্ঞাসা করুন

What are my dietary preferences?

এজেন্ট আপনার সংরক্ষিত পছন্দ— ভেগান— দিয়ে সাড়া দেয়। ডেটাটি রিস্টার্টের পরেও অক্ষত থাকে, কারণ এটি এখন লোকাল স্টোরেজে নয়, PostgreSQL-এ সংরক্ষিত আছে। আমরা যদি এই user: হিসেবে একটি নতুন সেশন তৈরি করি , তাহলেও একই ঘটনা ঘটবে: এই ব্যবহারকারীর প্রতিটি নতুন সেশনে স্টেটটি স্থানান্তরিত হয়।

9c139bf89becb748.png

সরাসরি ডাটাবেস পরিদর্শন করুন

ক্লাউড শেলে একটি নতুন টার্মিনাল ট্যাব খুলুন এবং সংরক্ষিত ডেটা দেখতে ডাটাবেসে কোয়েরি করুন:

psql "host=127.0.0.1 port=5432 dbname=agent_db user=postgres password=$DB_PASSWORD" -c "\dt"

এই উদাহরণের মতো, সেশন, ইভেন্ট এবং স্টেট সংরক্ষণের জন্য ADK স্বয়ংক্রিয়ভাবে যে টেবিলগুলো তৈরি করে, সেগুলো আপনার দেখা উচিত।

                List of relations
 Schema |         Name          | Type  |  Owner   
--------+-----------------------+-------+----------
 public | adk_internal_metadata | table | postgres
 public | app_states            | table | postgres
 public | events                | table | postgres
 public | sessions              | table | postgres
 public | user_states           | table | postgres
(5 rows)

রাষ্ট্রীয় আচরণের সারসংক্ষেপ

রাষ্ট্রীয় চাবি

উপসর্গ

পরিধি

বিভিন্ন সেশনে শেয়ার করা হয়েছে?

current_order

(কিছুই না)

অধিবেশন

না

user:dietary_preferences

user:

ব্যবহারকারী

হ্যাঁ

১০. অভিনন্দন / পরিচ্ছন্নতা

অভিনন্দন! আপনি ADK এবং Cloud SQL ব্যবহার করে সফলভাবে একটি স্থায়ী, স্টেটফুল এআই এজেন্ট তৈরি করেছেন।

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

  • কাস্টম টুল ব্যবহার করে কীভাবে একটি ADK এজেন্ট তৈরি করবেন যা সেশন স্টেট পড়তে ও লিখতে পারে
  • সেশন-স্কোপড স্টেট (কোনো প্রিফিক্স নেই) এবং ইউজার-স্কোপড স্টেট ( user: প্রিফিক্স) এর মধ্যে পার্থক্য
  • কেন ডিফল্ট adk লোকাল session.db শুধুমাত্র ডেভেলপমেন্টের জন্য উপযুক্ত — এটি মুছে ফেললে সমস্ত ডেটা হারিয়ে যায় (এবং এটি মুছে ফেলা সহজ, কোনো ব্যাকআপ নেই), এবং স্টেটলেস সার্ভারলেস ডেপ্লয়মেন্টের জন্য উপযুক্ত নয়।
  • কীভাবে একটি ক্লাউড এসকিউএল পোস্টগ্রেসকিউএল ইনস্ট্যান্স প্রোভিশন করবেন এবং ক্লাউড এসকিউএল অথ প্রক্সি ব্যবহার করে সেটিতে সংযোগ স্থাপন করবেন
  • সামান্য কোড পরিবর্তন করে কীভাবে CloudSQL-এ PostgreSQL-এর সাথে DatabaseSessionService-এ সংযোগ করবেন — একই টুল, একই এজেন্ট, ভিন্ন ব্যাকএন্ড
  • কীভাবে ব্যবহারকারী-নির্দিষ্ট অবস্থা আলাদা কথোপকথন সেশন জুড়ে বজায় থাকে

পরিষ্কার করা

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

পরিষ্কার করার সবচেয়ে সহজ উপায় হলো প্রজেক্টটি ডিলিট করে দেওয়া। এর ফলে প্রজেক্টটির সাথে যুক্ত সমস্ত রিসোর্স মুছে যায়।

gcloud projects delete ${GOOGLE_CLOUD_PROJECT}

বিকল্প ২: স্বতন্ত্র রিসোর্সগুলো মুছে ফেলুন

আপনি যদি প্রজেক্টটি রাখতে চান কিন্তু শুধুমাত্র এই কোডল্যাবে তৈরি করা রিসোর্সগুলো সরাতে চান:

gcloud sql instances delete cafe-concierge-db --quiet