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

1. ভূমিকা

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

আমরা যে সিস্টেম আর্কিটেকচারটি তৈরি করব তা এখানে

অনুসরণ

পূর্বশর্ত

  • একটি Google ক্লাউড অ্যাকাউন্ট যার একটি ট্রায়াল বিলিং অ্যাকাউন্ট রয়েছে
  • পাইথনের সাথে প্রাথমিক পরিচিতি
  • ADK, AI এজেন্ট, অথবা Cloud SQL এর সাথে পূর্ব অভিজ্ঞতার প্রয়োজন নেই।

তুমি কি শিখবে

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

তোমার যা লাগবে

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

2. আপনার পরিবেশ সেট আপ করুন

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

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

আপনার ব্রাউজারে Cloud Shell খুলুন। Cloud Shell এই কোডল্যাবের জন্য প্রয়োজনীয় সমস্ত সরঞ্জাম সহ একটি পূর্ব-কনফিগার করা পরিবেশ প্রদান করে। অনুরোধ করা হলে 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

আপনার টার্মিনাল তৈরি করতে View -> Terminal খুঁজুন

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 সম্পর্কে

প্রয়োজনীয় API গুলি সক্ষম করুন

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

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

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

এগিয়ে যাওয়ার আগে, আমরা যে পণ্যটির সাথে ইন্টারঅ্যাক্ট করব তার জন্য প্রয়োজনীয় অবস্থান/অঞ্চল কনফিগারেশন সেট আপ করা যাক। আমাদের .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

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

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

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

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

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

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

একটি ক্লাউড SQL PostgreSQL ইনস্ট্যান্স তৈরি করতে এই কমান্ডটি চালান। এটি সম্পূর্ণ হতে কয়েক মিনিট সময় লাগে — এটি চালু রেখে পরবর্তী বিভাগে যান

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 হল সবচেয়ে ছোট (এবং সবচেয়ে সস্তা) ক্লাউড SQL স্তর — এই কোডল্যাবের জন্য যথেষ্ট।
  • --root-password ডিফল্ট পোস্টগ্রেস ব্যবহারকারীর জন্য পাসওয়ার্ড সেট করে।
  • কমান্ডের মধ্যে & suffix কমান্ডটি ব্যাকগ্রাউন্ডে রান করে যাতে আপনি কাজ চালিয়ে যেতে পারেন।

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

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 অবজেক্টের ভেতরে থাকে। একটি Session দুটি স্বতন্ত্র জিনিস ট্র্যাক করে: ইভেন্ট এবং state । পার্থক্য বোঝা এজেন্ট তৈরির মূল চাবিকাঠি যারা সঠিক জিনিসগুলি সঠিক উপায়ে মনে রাখে।

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

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

এখানে তারা কীভাবে সম্পর্কিত:

cd9871699451867d.png সম্পর্কে

ToolContext এর মাধ্যমে Tools read and write state — একটি অবজেক্ট যা 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_preferencesuser: প্রিফিক্স আছে — এটি ব্যবহারকারী-পরিধিভুক্ত। এই ব্যবহারকারীর জন্য প্রতিটি সেশনে এটি ভাগ করা হয়।

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

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

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

57408cfae5f041ac.png - [অনলাইন].

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

What do you recommend for me?

নতুন সেশনে State ট্যাবটি চেক করুন। 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 নির্দিষ্ট করুন, যা এই টিউটোরিয়ালে সমস্ত সেশন ডেটা PostgreSQL in Cloud SQL ডাটাবেসে সংরক্ষণ করবে। এজেন্ট কোড এবং টুলগুলি ঠিক একই থাকে — শুধুমাত্র স্টোরেজ ব্যাকএন্ড পরিবর্তন হয়।

এগিয়ে যাওয়ার আগে 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 Auth প্রক্সি শুরু করুন

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

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

কমান্ডের & suffix প্রক্সিটিকে ব্যাকগ্রাউন্ডে চালায়। আপনি নীচের চিত্রের মতো প্রক্সি প্রস্তুত কিনা তা নিশ্চিত করে আউটপুট দেখতে পাবেন।

[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 (স্থানীয় ডাটাবেস) সরানো হয়েছে এবং কথোপকথন সেশন জুড়ে বিস্তৃত।

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

নিশ্চিত করুন যে ক্লাউড SQL Auth প্রক্সিটি এখনও চলছে (জবস দিয়ে চেক করুন)। যদি তা না হয়, তাহলে এটি পুনরায় চালু করুন:

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

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

What are my dietary preferences?

এজেন্ট আপনার সংরক্ষিত পছন্দগুলি - vegan - এর সাথে সাড়া দেয়। ডেটা পুনঃসূচনা থেকে বেঁচে গেছে কারণ এটি এখন PostgreSQL এ সংরক্ষিত আছে, স্থানীয় স্টোরেজে নয়। আমরা যদি নতুন সেশন তৈরি করি তবে এটি একই রকম হবে কারণ user: জন্য প্রতিটি নতুন সেশনে state বহন করে।

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 ব্যবহার করে একটি স্থায়ী, স্টেটফুল AI এজেন্ট সফলভাবে তৈরি করেছেন।

তুমি যা শিখেছো

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

পরিষ্কার কর

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

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

gcloud projects delete ${GOOGLE_CLOUD_PROJECT}

বিকল্প ২: পৃথক সম্পদ মুছে ফেলুন

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

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