ساخت عامل‌های هوش مصنوعی پایدار با ADK و CloudSQL

۱. مقدمه

در این جلسه عملی، شما فراتر از چت‌بات‌های ساده و بدون وضعیت، یک Smart Cafe Concierge ایجاد خواهید کرد - یک عامل هوش مصنوعی که توسط Gemini پشتیبانی می‌شود و به عنوان یک باریستای دوستانه عمل می‌کند. این عامل سفارش‌های قهوه ردیابی شده در حالت جلسه را دریافت می‌کند، ترجیحات غذایی بلندمدت را در حالت کاربر-محور به خاطر می‌سپارد و همه چیز را در یک پایگاه داده Cloud SQL PostgreSQL ذخیره می‌کند. در پایان، عامل شما حتی پس از راه‌اندازی مجدد برنامه و شروع یک مکالمه کاملاً جدید، به خاطر می‌سپارد که شما به لاکتوز حساسیت دارید.

این معماری سیستمی است که ما خواهیم ساخت

a98bbd65ddedd29c.jpeg

پیش‌نیازها

  • یک حساب Google Cloud با یک حساب پرداخت آزمایشی
  • آشنایی اولیه با پایتون
  • هیچ تجربه قبلی با ADK، عوامل هوش مصنوعی یا Cloud SQL لازم نیست

آنچه یاد خواهید گرفت

  • با استفاده از کیت توسعه عامل گوگل (ADK) و ابزارهای سفارشی، یک عامل هوش مصنوعی ایجاد کنید
  • ابزارهایی را تعریف کنید که وضعیت جلسه را از طریق ToolContext می‌خوانند و می‌نویسند
  • تمایز قائل شدن بین وضعیت محدود به جلسه و وضعیت محدود به کاربر (پیشوند user: :)
  • یک نمونه Cloud SQL PostgreSQL تهیه کنید و از Cloud Shell به آن متصل شوید
  • برای ذخیره‌سازی مداوم با پایگاه داده اختصاصی، از حافظه محلی (که هنگام استفاده از دستور adk web پیش‌فرض است) به DatabaseSessionService مهاجرت کنید.
  • تأیید کنید که حافظه عامل در طول راه‌اندازی مجدد برنامه و در طول جلسات مکالمه جداگانه همچنان پابرجاست

آنچه نیاز دارید

  • یک کامپیوتر سالم و یک اتصال اینترنتی قابل اعتماد.
  • یک مرورگر، مانند کروم ، برای دسترسی به کنسول ابری گوگل
  • ذهنی کنجکاو و مشتاق یادگیری.

۲. محیط خود را آماده کنید

این مرحله محیط Cloud Shell شما را آماده کرده و پروژه Google Cloud شما را پیکربندی می‌کند.

پوسته ابری را باز کنید

Cloud Shell را در مرورگر خود باز کنید. Cloud Shell یک محیط از پیش پیکربندی شده با تمام ابزارهای مورد نیاز برای این آزمایشگاه کد را فراهم می‌کند. در صورت درخواست، روی تأیید (Authorize) کلیک کنید.

رابط کاربری شما باید شبیه به این باشد

۸۶۳۰۷fac5da2f077.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

پروژه Google Cloud و متغیرهای اولیه محیط خود را تنظیم کنید

اسکریپت راه‌اندازی پروژه را در دایرکتوری کاری خود دانلود کنید:

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 تنظیم کنید

با بررسی متن زرد رنگ کنار دایرکتوری کاری خود در اعلان ترمینال Cloud Shell، مطمئن شوید که پروژه به درستی تنظیم شده است. باید شناسه پروژه شما نمایش داده شود.

9e11ee21cd23405f.png

فعال کردن API های مورد نیاز

APIهای Google Cloud مورد نیاز برای این آزمایشگاه کد را فعال کنید:

gcloud services enable \
  aiplatform.googleapis.com \
  sqladmin.googleapis.com \
  compute.googleapis.com
  • رابط برنامه‌نویسی کاربردی هوش مصنوعی ورتکس ( aiplatform.googleapis.com ) — نماینده شما از مدل‌های Gemini از طریق هوش مصنوعی ورتکس استفاده می‌کند.
  • رابط برنامه‌نویسی کاربردی مدیریت SQL ابری ( sqladmin.googleapis.com ) - شما یک نمونه PostgreSQL را برای ذخیره‌سازی پایدار فراهم و مدیریت می‌کنید.
  • رابط برنامه‌نویسی کاربردی موتور محاسبات (compute Engine API ) ( compute.googleapis.com ) — برای ایجاد نمونه‌های Cloud SQL مورد نیاز است.

پیکربندی منطقه محصولات Gemini و Cloud

قبل از ادامه، بیایید پیکربندی مکان/منطقه لازم را برای محصولی که با آن تعامل داریم نیز تنظیم کنیم. پیکربندی زیر را به فایل .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 ابری

این مرحله یک نمونه Cloud SQL PostgreSQL را فراهم می‌کند و عامل شما را از حافظه داخلی به فضای ذخیره‌سازی تحت پشتیبانی پایگاه داده تغییر می‌دهد. ایجاد نمونه چند دقیقه طول می‌کشد، بنابراین ابتدا آن را شروع می‌کنید و ما می‌توانیم در حالی که منتظر پایان آن هستیم، بحث خود را در مورد موضوع بعدی ادامه دهیم.

شروع ایجاد نمونه

رمز عبور پایگاه داده را به فایل .env خود اضافه کنید و آن را مجدداً بارگذاری کنید، ما cafe-agent-pwd-2025 به عنوان رمز عبور استفاده خواهیم کرد.

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

این دستور را اجرا کنید تا یک نمونه Cloud 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 کوچکترین (و ارزان‌ترین) لایه Cloud SQL است - که برای این codelab کافی است.
  • --root-password ‎ رمز عبور را برای کاربر پیش‌فرض postgres تنظیم می‌کند.
  • پسوند & در دستور، دستور را در پس‌زمینه اجرا می‌کند تا بتوانید به کار خود ادامه دهید.

این فرآیند در پس‌زمینه اجرا خواهد شد، با این حال خروجی کنسول گاهی اوقات در ترمینال فعلی نمایش داده می‌شود. بیایید یک تب ترمینال جدید در Cloud Shell باز کنیم (روی نماد + کلیک کنیم) تا بتوانیم بیشتر متمرکز شویم.

b01e3fbd89f17332.png

دوباره به دایرکتوری کاری خود بروید و پروژه را با استفاده از اسکریپت راه‌اندازی قبلی فعال کنید.

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

سپس، به بخش بعدی ادامه می‌دهیم

۴. مسئول دربان کافه را بسازید

این مرحله ساختار پروژه را برای عامل ADK شما ایجاد می‌کند و یک Cafe Concierge پایه با یک ابزار منو تعریف می‌کند.

مقداردهی اولیه پروژه پایتون

این آزمایشگاه کد از uv ، یک مدیر بسته سریع پایتون که محیط‌های مجازی و وابستگی‌ها را در یک ابزار مدیریت می‌کند، استفاده می‌کند. این ابزار از قبل در Cloud Shell نصب شده است.

یک پروژه پایتون را راه‌اندازی کنید و ADK را به عنوان یک وابستگی اضافه کنید:

uv init
uv add google-adk==1.25.0 asyncpg

uv init یک pyproject.toml و یک محیط مجازی ایجاد می‌کند. uv add وابستگی را نصب کرده و آن را در pyproject.toml ثبت می‌کند.

ساختار پروژه عامل را مقداردهی اولیه کنید

ADK انتظار یک طرح پوشه خاص را دارد: یک پوشه به نام agent شما که شامل __init__.py ، agent.py و همچنین .env درون پوشه agent باشد.

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 را در ویرایشگر Cloud Shell باز کنید.

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 dev را از دایرکتوری کاری خود شروع کنید:

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

با استفاده از قابلیت پیش‌نمایش وب Cloud Shell، آدرس اینترنتی (URL) نمایش داده شده در ترمینال (معمولاً http://localhost:8000 ) را باز کنید. از منوی کشویی agent در گوشه بالا سمت چپ، گزینه cafe_concierge را انتخاب کنید.

متن زیر را در نوار چت تایپ کنید و مطمئن شوید که اپراتور با موارد منو و قیمت‌ها پاسخ می‌دهد.

What's on the menu?

376ee6b189657e7a.png

قبل از ادامه، رابط کاربری توسعه‌دهندگان را با Ctrl+C متوقف کنید.

۵. مدیریت سفارش‌های با وضعیت (Stateful Order Management) را اضافه کنید

عامل می‌تواند منو را نشان دهد، اما نمی‌تواند سفارش‌ها را دریافت کند یا ترجیحات را به خاطر بسپارد. این مرحله چهار ابزار اضافه می‌کند که از سیستم وضعیت ADK برای ردیابی سفارش‌ها در یک مکالمه و ذخیره ترجیحات غذایی در مکالمات استفاده می‌کنند.

درک رویدادهای جلسه و وضعیت آن

هر مکالمه ADK درون یک شیء Session قرار دارد. یک Session دو چیز متمایز را ردیابی می‌کند: رویدادها و وضعیت . درک این تفاوت، کلید ساخت عامل‌هایی است که چیزهای درست را به روش درست به خاطر می‌سپارند.

رویدادها ، گزارش زمانی هر اتفاقی هستند که در یک مکالمه رخ می‌دهد. هر پیام کاربر، هر پاسخ عامل، هر فراخوانی ابزار و مقدار بازگشتی آن - هر یک به عنوان یک Event ثبت شده و به لیست رویدادهای جلسه اضافه می‌شود. رویدادها تغییرناپذیر هستند: پس از ثبت، هرگز تغییر نمی‌کنند. رویدادها را به عنوان متن کامل یک مکالمه در نظر بگیرید.

وضعیت (State) یک دفترچه یادداشت کلید-مقدار است که عامل در طول مکالمه آن را می‌خواند و می‌نویسد. برخلاف رویدادها، وضعیت (state) قابل تغییر است - مقادیر با پیشرفت مکالمه تغییر می‌کنند. وضعیت جایی است که عامل داده‌های ساختاریافته‌ای را که برای اقدام روی آنها نیاز دارد، ذخیره می‌کند: سفارش فعلی، تنظیمات برگزیده مشتری، یک جمع کل در حال اجرا. وضعیت را به عنوان یادداشت‌های چسبانی در نظر بگیرید که عامل در کنار متن یادداشت نگه می‌دارد.

در اینجا نحوه ارتباط آنها آورده شده است:

cd9871699451867d.png

ابزارها از طریق ToolContext وضعیت را می‌خوانند و می‌نویسند - شیء‌ای که ADK به طور خودکار به هر تابع ابزاری که آن را به عنوان پارامتر اعلام می‌کند، تزریق می‌کند. شما خودتان آن را ایجاد نمی‌کنید. از طریق tool_context.state ، یک ابزار می‌تواند دفترچه یادداشت وضعیت جلسه را بخواند و بنویسد. ADK امضای تابع را بررسی می‌کند: پارامترهایی با نوع ToolContext تزریق می‌شوند، تمام پارامترهای دیگر توسط LLM بر اساس مکالمه پر می‌شوند.

وقتی ابزاری در tool_context.state می‌نویسد، ADK آن تغییر را به عنوان یک state_delta درون رویداد ثبت می‌کند. سپس SessionService دلتا را به وضعیت فعلی جلسه اعمال می‌کند. این بدان معناست که تغییرات وضعیت همیشه تا رویدادی که باعث آنها شده است قابل ردیابی هستند. این موضوع در مورد سایر انواع context مانند callback_context نیز صادق است.

پیشوندهای ایالتی را درک کنید

کلیدهای حالت از پیشوندها برای کنترل دامنه خود استفاده می‌کنند:

پیشوند

محدوده

زنده می ماند و دوباره راه اندازی می شود؟ (با DB)

(هیچکدام)

فقط جلسه فعلی

بله

user:

تمام جلسات این کاربر

بله

app:

همه جلسات، همه کاربران

بله

temp:

فقط فراخوانی فعلی

خیر

در این آزمایشگاه کد، شما از دو مورد از این پیشوندها استفاده می‌کنید: کلیدهای بدون پیشوند برای داده‌های مربوط به جلسه (ترتیب فعلی - فقط مربوط به این مکالمه) و کلیدهای user: برای داده‌های مربوط به کاربر (ترجیحات غذایی - مربوط به تمام مکالمات این کاربر).

ابزارهای stateful را اضافه کنید

cafe_concierge/agent.py را در ویرایشگر Cloud Shell باز کنید.

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

این مرحله، عامل را اجرا می‌کند و تمام ویژگی‌های حالت‌مند (stateful) را اعمال می‌کند: مرتب‌سازی، ردیابی ترجیحات و حافظه بین جلسات (در همان فرآیند). همچنین پنل‌های رویدادها و وضعیت را بررسی خواهید کرد تا ببینید ADK چگونه مکالمه را به صورت داخلی ردیابی می‌کند.

رابط کاربری توسعه‌دهنده را شروع کنید

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?

رویدادهای جلسه را بررسی کنید

تمام رویداد ضبط شده و در رابط کاربری وب نمایش داده می‌شود، خواهید دید که در چت باکس نه تنها اعلان و پاسخ شما، بلکه 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

به دنبال فیلد state_delta در رویداد tool_response بگردید. این فیلد دقیقاً نشان می‌دهد که چه وضعیتی در نتیجه‌ی فراخوانی این ابزار تغییر کرده است:

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

هر تغییر وضعیت تا یک رویداد خاص قابل ردیابی است. اینگونه است که ADK تضمین می‌کند که دفترچه یادداشت وضعیت با تاریخچه مکالمات همگام می‌ماند.

بررسی وضعیت جلسه

روی برگه وضعیت (State) کلیک کنید. برخلاف گزارش رویدادها (event log) (که تاریخچه کامل را نشان می‌دهد)، برگه وضعیت (state tab) تصویری از آنچه عامل در حال حاضر می‌داند را نشان می‌دهد - مقدار فعلی هر کلید وضعیت (state key).

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 استفاده می‌کنند)، اما میزان دسترسی داده‌ها را کنترل می‌کند. در آزمایش بعدی این موضوع را مشاهده خواهید کرد.

مکالمه ۲: بررسی وضعیت کاربر در جلسات متقابل

برای شروع یک مکالمه جدید، روی دکمه «جلسه جدید» در رابط کاربری توسعه‌دهندگان کلیک کنید. این کار یک جلسه جدید برای همان کاربر ایجاد می‌کند.

57408cfae5f041ac.png

این دستور را امتحان کنید:

What do you recommend for me?

تب State را در جلسه جدید بررسی کنید. کلید user:dietary_preferences باقی می‌ماند، اما current_order حذف شده است - آن وضعیت به جلسه قبلی گره خورده بود.

764eb3885251307d.png

۷. محدودیت فضای ذخیره‌سازی محلی را رعایت کنید

عامل تنظیمات برگزیده را در طول جلسات به خاطر می‌سپارد - اما فقط تا زمانی که فضای ذخیره‌سازی محلی وجود داشته باشد. این مرحله محدودیت اساسی فضای ذخیره‌سازی محلی را نشان می‌دهد.

دوباره عامل را شروع کنید

شما رابط کاربری توسعه (dev UI) را در پایان مرحله قبل متوقف کردید. حالا بیایید حافظه محلی را حذف کرده و دوباره آن را راه‌اندازی کنیم تا محیط بدون سرور (serverless) که بدون وضعیت (stateless) است را شبیه‌سازی کنیم:

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

حالا، پیش‌نمایش وب را روی پورت ۸۰۰۰ باز کنید و cafe_concierge را انتخاب کنید.

یادآوری ترجیحات آزمون

نوع:

Do you remember my dietary preferences?

نماینده هیچ چیزی به خاطر نمی‌آورد. ترجیحات غذایی، سابقه سفارش - همه از بین رفته‌اند.

82a5e05434cafe83.png

وقتی حافظه محلی را حذف کردیم، همه چیز پاک شد، که معمولاً وقتی از یک محیط بدون سرور استفاده می‌کنیم اتفاق می‌افتد. session.db تمام وضعیت‌ها را در حافظه پردازش ذخیره می‌کند. حذف آن، همه چیز را پاک می‌کند.

راه حل: DatabaseSessionService را مشخص کنید، که در این آموزش تمام داده‌های جلسه را در یک پایگاه داده PostgreSQL در Cloud SQL ذخیره می‌کند. کد و ابزارهای عامل دقیقاً یکسان باقی می‌مانند - فقط backend ذخیره‌سازی تغییر می‌کند.

قبل از ادامه، رابط کاربری توسعه‌دهندگان را با Ctrl+C متوقف کنید.

۸. تنظیمات پایگاه داده را دوباره بررسی کنید

در این مرحله، ایجاد نمونه پایگاه داده ما قرار است از قبل تمام شده باشد. بیایید آن را تأیید کنیم، دستور زیر را اجرا کنید

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

باید خروجی زیر را ببینید، آن را به عنوان پایان یافته علامت گذاری کنید

RUNNABLE

ایجاد پایگاه داده

یک پایگاه داده اختصاصی برای داده‌های جلسه عامل ایجاد کنید:

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

پروکسی احراز هویت Cloud SQL را شروع کنید

پروکسی احراز هویت ابری SQL، بدون نیاز به قرار دادن آدرس‌های IP در لیست سفید، یک اتصال امن و احراز هویت شده از Cloud Shell به نمونه Cloud 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 (پایگاه داده محلی) حذف شده و در جلسات مکالمه اعمال می‌شود، همچنان قابل استفاده است.

عامل را شروع کنید

مطمئن شوید که Cloud SQL Auth Proxy هنوز در حال اجرا است (با jobs بررسی کنید). اگر اینطور نیست، آن را مجدداً راه اندازی کنید:

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 را با مشخص کردن پایگاه داده به عنوان سرویس جلسه شروع کنیم.

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 متوقف کنید و مطمئن شوید که session.db محلی حذف شده است.

rm -f cafe_concierge/.adk/session.db

سپس، سرور رابط کاربری توسعه‌دهنده (dev 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: به هر جلسه جدید برای این کاربر منتقل می‌شود.

9c139bf89becb748.png

مستقیماً پایگاه داده را بررسی کنید

یک تب ترمینال جدید در Cloud Shell باز کنید و برای مشاهده داده‌های ذخیره شده، از پایگاه داده کوئری بگیرید:

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 پیش‌فرض local session.db فقط برای توسعه مناسب است - تمام داده‌ها هنگام حذف از بین می‌روند (و حذف آنها آسان است، بدون پشتیبان)، برای استقرار بدون سرور که بدون وضعیت است مناسب نیست.
  • نحوه تهیه یک نمونه Cloud SQL PostgreSQL و اتصال به آن با Cloud SQL Auth Proxy
  • نحوه اتصال به DatabaseSessionService با PostgreSQL در CloudSQL با حداقل تغییر کد - همان ابزارها، همان عامل، backend متفاوت
  • چگونه وضعیتِ تحتِ محدوده‌ی کاربر در جلسات مکالمه‌ی جداگانه باقی می‌ماند

تمیز کردن

برای جلوگیری از تحمیل هزینه به حساب Google Cloud خود، منابع ایجاد شده در این codelab را پاک کنید.

ساده‌ترین راه برای پاکسازی، حذف پروژه است. این کار تمام منابع مرتبط با پروژه را حذف می‌کند.

gcloud projects delete ${GOOGLE_CLOUD_PROJECT}

گزینه ۲: حذف منابع تکی

اگر می‌خواهید پروژه را نگه دارید اما فقط منابع ایجاد شده در این codelab را حذف کنید:

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