إنشاء وكلاء الذكاء الاصطناعي الدائمين باستخدام "حزمة تطوير الوكلاء" وCloudSQL

1. مقدمة

في هذه الجلسة العملية، ستنتقل إلى ما هو أبعد من برامج الدردشة الأساسية التي لا تحتفظ بأي بيانات لإنشاء "بواب مقهى ذكي"، وهو عبارة عن وكيل ذكاء اصطناعي يستند إلى Gemini ويعمل كصانع قهوة ودود. يستقبل التطبيق طلبات القهوة التي يتم تتبّعها في حالة الجلسة، ويتذكّر الإعدادات المفضّلة الغذائية على المدى الطويل في الحالة على مستوى المستخدم، ويحفظ كل شيء في قاعدة بيانات Cloud SQL PostgreSQL. في النهاية، يتذكّر المساعد أنّك تعاني من عدم تحمّل اللاكتوز حتى بعد إعادة تشغيل التطبيق وبدء محادثة جديدة تمامًا.

في ما يلي بنية النظام التي سننشئها

a98bbd65ddedd29c.jpeg

المتطلبات الأساسية

  • حساب Google Cloud يتضمّن حساب فوترة تجريبيًا
  • الإلمام بأساسيات لغة Python
  • لا يلزم توفّر خبرة سابقة في "حزمة تطوير التطبيقات" أو وكلاء الذكاء الاصطناعي أو Cloud SQL

ما ستتعلمه

  • إنشاء وكيل مستند إلى الذكاء الاصطناعي باستخدام "حزمة تطوير الوكلاء" (ADK) من Google مع أدوات مخصّصة
  • تحديد الأدوات التي تقرأ حالة الجلسة وتكتبها من خلال ToolContext
  • التمييز بين الحالة على مستوى الجلسة والحالة على مستوى المستخدِم (البادئة user:)
  • توفير مثيل Cloud SQL PostgreSQL والاتصال به من Cloud Shell
  • نقل البيانات من مساحة التخزين المحلية (وهي الإعداد التلقائي عند استخدام الأمر adk web) إلى DatabaseSessionService لتوفير مساحة تخزين ثابتة مع قاعدة بيانات مخصّصة
  • التأكّد من استمرار ذاكرة الوكيل عند إعادة تشغيل التطبيق وفي جلسات المحادثات المنفصلة

المتطلبات

  • جهاز كمبيوتر يعمل واتصال إنترنت موثوق به
  • متصفّح، مثل Chrome، للوصول إلى Google Cloud Console
  • عقل فضولي وشغف بالتعلّم

2. إعداد البيئة

تُعدّ هذه الخطوة بيئة Cloud Shell وتضبط مشروعك على Google Cloud.

فتح Cloud Shell

افتح Cloud Shell في المتصفّح. توفّر Cloud Shell بيئة مُعدّة مسبقًا تتضمّن جميع الأدوات التي تحتاج إليها في هذا الدرس العملي. انقر على تفويض عندما يُطلب منك ذلك

يجب أن تبدو واجهتك مشابهة لما يلي

86307fac5da2f077.png

ستكون هذه واجهتنا الرئيسية، مع وضع بيئة التطوير المتكاملة في الأعلى والوحدة الطرفية في الأسفل.

إعداد دليل العمل

أنشئ دليل العمل. يتم تخزين كل الرموز البرمجية التي تكتبها في هذا الدرس التطبيقي هنا، بشكل منفصل عن مستودع الرموز البرمجية المرجعية:

# 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

تفعيل واجهات برمجة التطبيقات المطلوبة

فعِّل واجهات Google Cloud APIs اللازمة لهذا الدرس العملي:

gcloud services enable \
  aiplatform.googleapis.com \
  sqladmin.googleapis.com \
  compute.googleapis.com
  • واجهة برمجة التطبيقات Vertex AI API (aiplatform.googleapis.com): يستخدم الوكيل نماذج Gemini من خلال Vertex AI.
  • واجهة برمجة التطبيقات Cloud SQL Admin API (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

لننتقل إلى الخطوة التالية

3- إعداد Cloud 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، وهي كافية لهذا الدرس التطبيقي حول الترميز.
  • يضبط --root-password كلمة المرور لمستخدم postgres التلقائي.
  • يؤدي اللاحقة & في الأمر إلى تشغيل الأمر في الخلفية حتى تتمكّن من مواصلة العمل.

سيتم تشغيل العملية في الخلفية، ولكن سيتم عرض ناتج وحدة التحكّم أحيانًا في الوحدة الطرفية الحالية. لنفتح علامة تبويب جديدة في نافذة Terminal في Cloud Shell (انقر على الرمز +) حتى نتمكّن من التركيز بشكل أكبر.

b01e3fbd89f17332.png

انتقِل إلى دليل العمل مرة أخرى وفعِّل المشروع باستخدام نص الإعداد البرمجي السابق.

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

بعد ذلك، لننتقل إلى القسم التالي

4. إنشاء وكيل Concierge في المقهى

تنشئ هذه الخطوة بنية المشروع الخاص بعميل ADK وتحدّد برنامج Cafe Concierge أساسيًا يتضمّن أداة قائمة.

إعداد مشروع Python

يستخدم هذا الدرس التطبيقي uv، وهي أداة سريعة لإدارة حِزم Python تتعامل مع البيئات الافتراضية والتبعيات في أداة واحدة. ويكون مثبَّتًا مسبقًا في Cloud Shell.

ابدأ مشروع Python وأضِف حزمة تطوير التطبيقات (ADK) كعنصر تابع:

uv init
uv add google-adk==1.25.0 asyncpg

ينشئ uv init pyproject.toml وبيئة افتراضية. يضيف الأمر uv التبعية ويسجّلها في pyproject.toml.

إعداد بنية مشروع الوكيل

يتوقّع ADK تنسيق مجلد محدّد: دليل مسمّى باسم وكيلك يحتوي على __init__.py وagent.py و.env أيضًا داخل دليل الوكيل

يتضمّن "حزمة تطوير التطبيقات" أمرًا مضمّنًا لمساعدتك في إعداد ذلك بسرعة، لذا شغِّل الأمر التالي

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 من دليل العمل:

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

افتح عنوان URL المعروض في الوحدة الطرفية (عادةً http://localhost:8000) باستخدام ميزة "معاينة الويب" في Cloud Shell. انقر على cafe_concierge من القائمة المنسدلة الخاصة بالوكيل في أعلى يمين الصفحة.

اكتب النص التالي في شريط المحادثة وتأكَّد من أنّ الموظف يردّ بقائمة الطعام والأسعار.

What's on the menu?

376ee6b189657e7a.png

أوقِف واجهة المستخدم الخاصة بأدوات المطوّرين بالضغط على Ctrl+C قبل المتابعة.

5- إضافة ميزة "إدارة الطلبات مع الاحتفاظ بالحالة"

يمكن أن يعرض لك المساعد القائمة، ولكن لا يمكنه تلقّي الطلبات أو تذكُّر الإعدادات المفضّلة. تضيف هذه الخطوة أربع أدوات تستخدم نظام الحالة في "حزمة تطوير التطبيقات" لتتبُّع الطلبات ضِمن محادثة وتخزين الخيارات الغذائية المفضّلة في المحادثات.

فهم أحداث الجلسة وحالتها

تكون كل محادثة في ADK داخل عنصر Session. تتتبّع الجلسة عنصرَين مختلفَين: الأحداث والحالة. ويُعدّ فهم الفرق بينهما أمرًا أساسيًا لإنشاء وكلاء يتذكّرون المعلومات الصحيحة بالطريقة الصحيحة.

الأحداث هي السجلّ الزمني لكل ما يحدث في محادثة. يتم تسجيل كل رسالة من المستخدم وكل رد من الوكيل وكل طلب أداة وقيمة إرجاعه كـ Event وإضافته إلى قائمة الأحداث في الجلسة. الأحداث غير قابلة للتغيير: بمجرد تسجيلها، لا تتغير أبدًا. يمكنك اعتبار الأحداث بمثابة النص الكامل للمحادثة.

الحالة هي مساحة تخزين مؤقتة تتضمّن أزواجًا من المفاتيح والقيم، يقرأها الوكيل ويكتب فيها أثناء المحادثة. على عكس الأحداث، تكون الحالة قابلة للتغيير، أي تتغيّر القيم مع تطوّر المحادثة. الحالة هي المكان الذي يخزّن فيه الوكيل البيانات المنظَّمة التي يحتاج إليها لاتّخاذ إجراء، مثل الطلب الحالي، وخيارات العميل المفضّلة، والإجمالي المتداول. يمكنك التفكير في الحالة على أنّها ملاحظات لاصقة يحتفظ بها الوكيل بجانب نص المحادثة.

في ما يلي كيفية ارتباطهما:

cd9871699451867d.png

تقرأ الأدوات الحالة وتكتبها من خلال ToolContext، وهو عنصر يدرجه ADK تلقائيًا في أي دالة أداة تعرّفها كمَعلمة. لا يمكنك إنشاء هذا المحتوى بنفسك. من خلال tool_context.state، يمكن لأداة قراءة وكتابة لوحة الخدش الخاصة بحالة الجلسة. يفحص ADK توقيع الدالة: يتم إدخال المَعلمات من النوع ToolContext، ويملأ النموذج اللغوي الكبير جميع المَعلمات الأخرى استنادًا إلى المحادثة.

عندما تكتب أداة في tool_context.state، يسجِّل حِزمة تطوير التطبيقات على Android هذا التغيير كـ state_delta داخل الحدث. بعد ذلك، يطبّق SessionService التغيير على الحالة الحالية للجلسة. وهذا يعني أنّه يمكن دائمًا تتبُّع تغييرات الحالة وصولاً إلى الحدث الذي تسبّب فيها. ينطبق ذلك أيضًا على أشكال السياق الأخرى، مثل callback_context

التعرّف على بادئات الولايات

تستخدم مفاتيح الحالة بادئات للتحكّم في نطاقها:

البادئة

المستوى

هل تظل البيانات محفوظة بعد إعادة التشغيل؟ (مع قاعدة البيانات)

(بلا)

الجلسة الحالية فقط

نعم

user:

جميع جلسات هذا المستخدم

نعم

app:

جميع الجلسات وجميع المستخدمين

نعم

temp:

الاستدعاء الحالي فقط

لا

في هذا الدرس العملي، ستستخدم اثنين من هذه البادئات: المفاتيح التي لا تتضمّن بادئة للبيانات ذات النطاق المحدود بالجلسة (الطلب الحالي - ذو صلة بهذه المحادثة فقط) ومفاتيح user: للبيانات ذات النطاق المحدود بالمستخدم (التفضيلات الغذائية - ذات صلة بجميع المحادثات لهذا المستخدم).

إضافة الأدوات التي تحتفظ بحالتها

افتح 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 باستخدام الأدوات الخمس كلها

6. اختبار الوكيل باستخدام واجهة المستخدم المخصّصة للمطوّرين في "حزمة تطوير الوكلاء"

تنفّذ هذه الخطوة الوكيل وتستخدِم جميع الميزات التي تحتفظ بالحالة: الترتيب وتتبُّع التفضيلات والذاكرة المشتركة بين الجلسات (ضمن العملية نفسها). ستفحص أيضًا لوحتَي "الأحداث" و"الحالة" لمعرفة كيف يتتبّع ADK المحادثة داخليًا.

بدء واجهة مستخدم المطوّرين

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

افتح "معاينة الويب" على المنفذ 8000 واختَر cafe_concierge من القائمة المنسدلة.

المحادثة 1: تقديم طلب وتحديد الإعدادات المفضّلة

جرِّب هذه الطلبات بالتسلسل:

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"]}

يمكن تتبُّع كل تغيير في الحالة إلى حدث معيّن. بهذه الطريقة، تضمن "حزمة تطوير التطبيقات" بقاء مساحة التخزين المؤقت للحالة متزامنة مع سجلّ المحادثات.

فحص حالة الجلسة

انقر على علامة التبويب الحالة. على عكس سجلّ الأحداث (الذي يعرض السجلّ الكامل)، تعرض علامة التبويب "الحالة" لقطة لما يعرفه الوكيل الآن، أي القيمة الحالية لكل مفتاح حالة.

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)، ولكنّه يتحكّم في مدى وصول البيانات. ستظهر لك هذه النتيجة في الاختبار التالي.

المحادثة 2: التحقّق من حالة المستخدم على مستوى الجلسات

انقر على الزر جلسة جديدة في واجهة المستخدم المخصّصة للمطوّرين لبدء محادثة جديدة. يؤدي ذلك إلى إنشاء جلسة جديدة للمستخدم نفسه.

57408cfae5f041ac.png

جرِّب الطلب التالي:

What do you recommend for me?

انقر على علامة التبويب الحالة في الجلسة الجديدة. يتم الاحتفاظ بالمفتاح user:dietary_preferences، ولكن تتم إزالة current_order لأنّ هذه الحالة كانت مرتبطة بالجلسة السابقة.

764eb3885251307d.png

7. مراعاة الحد الأقصى لمساحة التخزين المحلية

يتذكّر الوكيل الإعدادات المفضّلة في جميع الجلسات، ولكن فقط طالما أنّ مساحة التخزين المحلية متوفّرة. توضّح هذه الخطوة القيد الأساسي لميزة "التخزين المحلي".

إعادة تشغيل الوكيل

لقد أوقفت واجهة مستخدم أدوات المطوّرين في نهاية الخطوة السابقة. لنُزِل الآن وحدة التخزين المحلية ونبدأ تشغيلها مرة أخرى لمحاكاة بيئة بلا خادم لا تحتفظ بأي بيانات:

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 في Cloud SQL. يظل رمز الوكيل والأدوات كما هما تمامًا، ولا يتغيّر سوى الخلفية المستخدَمة في التخزين.

أوقِف واجهة المستخدم الخاصة بأدوات المطوّرين باستخدام Ctrl+C قبل المتابعة.

8. إعادة النظر في إعداد قاعدة البيانات

في هذه المرحلة، من المفترض أن يكون إنشاء مثيل قاعدة البيانات قد انتهى. لنتأكّد من ذلك، شغِّل الأمر التالي

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

من المفترض أن يظهر لك الناتج التالي، ضع علامة "تمت قراءتها" عليه

RUNNABLE

إنشاء قاعدة البيانات

أنشئ قاعدة بيانات مخصّصة لبيانات جلسات الوكيل:

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

بدء الخادم الوكيل للمصادقة في Cloud SQL

يوفّر الخادم الوكيل للمصادقة في Cloud SQL اتصالاً آمنًا ومصادقًا عليه من Cloud Shell إلى مثيل Cloud SQL بدون الحاجة إلى إدراج عناوين IP في القائمة البيضاء. يكون مثبَّتًا مسبقًا على Cloud Shell.

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)

9- التحقّق من استمرار الذاكرة بين الجلسات

تثبت هذه الخطوة أنّ ذاكرة الوكيل لا تتأثر بإعادة الضبط عندما نتأكّد من إزالة cafe_concierge/.adk/session_db (قاعدة البيانات المحلية) وأنّها تغطي جلسات المحادثة.

بدء الوكيل

تأكَّد من أنّ الخادم الوكيل للمصادقة في Cloud SQL لا يزال يعمل (تحقَّق من المهام). إذا لم يكن كذلك، أعِد تشغيله باتّباع الخطوات التالية:

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

افتح "المعاينة على الويب" على المنفذ 8000 واختَر cafe_concierge.

الاختبار 1: تقديم طلب وضبط الإعدادات المفضّلة

في الجلسة الأولى، نفِّذ الطلبات التالية:

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

الاختبار 2: إمكانية مواصلة العمل بعد إعادة التشغيل

أوقِف واجهة المستخدم الخاصة بأدوات المطوّرين باستخدام Ctrl+C وتأكَّد من إزالة session.db المحلي.

rm -f cafe_concierge/.adk/session.db

بعد ذلك، أعِد تشغيل خادم واجهة المستخدم المخصّصة للمطوّرين.

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

فحص قاعدة البيانات مباشرةً

افتح علامة تبويب جديدة في 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:

المستخدم

نعم

10. تهانينا / حذف البيانات

تهانينا! لقد أنشأت بنجاح وكيل ذكاء اصطناعي مستمرًا ومحتفظًا بالحالة باستخدام "حزمة تطوير التطبيقات" وCloud SQL.

ما تعلّمته

  • كيفية إنشاء وكيل ADK باستخدام أدوات مخصّصة تقرأ حالة الجلسة وتكتبها
  • الفرق بين الحالة على مستوى الجلسة (بدون بادئة) والحالة على مستوى المستخدم (البادئة user:)
  • لماذا لا يكون إعداد adk المحلي التلقائي session.db مناسبًا إلا للتطوير؟ يتم فقدان جميع البيانات عند إزالتها (ومن السهل إزالتها، ولا يتم الاحتفاظ بنسخة احتياطية منها)، ولا يكون مناسبًا للنشر بدون خادم الذي لا يحتفظ بأي حالة
  • كيفية توفير مثيل PostgreSQL في Cloud SQL والاتصال به باستخدام الخادم الوكيل للمصادقة في Cloud SQL
  • كيفية الاتصال بـ DatabaseSessionService باستخدام PostgreSQL على CloudSQL مع إجراء الحد الأدنى من التغييرات على الرمز البرمجي، أي استخدام الأدوات نفسها والوكيل نفسه والخلفية المختلفة
  • كيفية استمرار حالة المستخدم على مستوى جلسات المحادثة المنفصلة

إخلاء مساحة

لتجنُّب تحمّل رسوم في حسابك على Google Cloud، عليك تنظيف الموارد التي تم إنشاؤها في هذا الدرس العملي.

أسهل طريقة لتنظيف حسابك هي حذف المشروع. يؤدي هذا الإجراء إلى إزالة جميع الموارد المرتبطة بالمشروع.

gcloud projects delete ${GOOGLE_CLOUD_PROJECT}

الخيار 2: حذف موارد فردية

إذا كنت تريد الاحتفاظ بالمشروع ولكن إزالة الموارد التي تم إنشاؤها في هذا الدرس العملي فقط، اتّبِع الخطوات التالية:

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