1. บทนำ
ในเซสชันภาคปฏิบัตินี้ คุณจะได้ก้าวข้ามแชทบอทแบบพื้นฐานที่ไม่มีสถานะเพื่อสร้าง Smart Cafe Concierge ซึ่งเป็นเอเจนต์ AI ที่ขับเคลื่อนโดย Gemini และทำหน้าที่เป็นบาริสต้าที่เป็นมิตร โดยจะรับคำสั่งซื้อกาแฟที่ติดตามในสถานะเซสชัน จดจำความชอบด้านอาหารระยะยาวในสถานะระดับผู้ใช้ และบันทึกทุกอย่างลงในฐานข้อมูล PostgreSQL ของ Cloud SQL เมื่อถึงตอนท้าย ตัวแทนจะจำได้ว่าคุณแพ้แลคโตสแม้ว่าคุณจะรีสตาร์ทแอปพลิเคชันและเริ่มการสนทนาใหม่ก็ตาม
นี่คือสถาปัตยกรรมของระบบที่เราจะสร้าง

ข้อกำหนดเบื้องต้น
- บัญชี Google Cloud ที่มีบัญชีสำหรับการเรียกเก็บเงินเวอร์ชันทดลอง
- มีความคุ้นเคยกับ Python ในระดับพื้นฐาน
- ไม่จำเป็นต้องมีประสบการณ์เกี่ยวกับ ADK, เอเจนต์ AI หรือ Cloud SQL มาก่อน
สิ่งที่คุณจะได้เรียนรู้
- สร้างเอเจนต์ AI โดยใช้ Agent Development Kit (ADK) ของ Google ด้วยเครื่องมือที่กำหนดเอง
- กำหนดเครื่องมือที่อ่านและเขียนสถานะเซสชันผ่าน
ToolContext - แยกความแตกต่างระหว่างสถานะระดับเซสชันและสถานะระดับผู้ใช้ (คำนำหน้า
user:) - จัดสรรอินสแตนซ์ Cloud SQL PostgreSQL และเชื่อมต่อกับอินสแตนซ์จาก Cloud Shell
- ย้ายข้อมูลจากที่เก็บข้อมูลในเครื่อง (ซึ่งเป็นค่าเริ่มต้นเมื่อใช้คำสั่ง
adk web) ไปยังDatabaseSessionServiceเพื่อใช้พื้นที่เก็บข้อมูลแบบถาวรที่มีฐานข้อมูลเฉพาะ - ตรวจสอบว่าหน่วยความจำของเอเจนต์ยังคงอยู่เมื่อรีสตาร์ทแอปพลิเคชันและในเซสชันการสนทนาที่แยกกัน
สิ่งที่คุณต้องมี
- คอมพิวเตอร์ที่ใช้งานได้และการเชื่อมต่ออินเทอร์เน็ตที่เสถียร
- เบราว์เซอร์ เช่น Chrome เพื่อเข้าถึงคอนโซล Google Cloud
- มีความใฝ่รู้และกระตือรือร้นที่จะเรียนรู้
2. ตั้งค่าสภาพแวดล้อม
ขั้นตอนนี้จะเตรียมสภาพแวดล้อม Cloud Shell และกำหนดค่าโปรเจ็กต์ Google Cloud
เปิด Cloud Shell
เปิด Cloud Shell ในเบราว์เซอร์ Cloud Shell มีสภาพแวดล้อมที่กำหนดค่าไว้ล่วงหน้าพร้อมเครื่องมือทั้งหมดที่คุณต้องการสำหรับ Codelab นี้ คลิกให้สิทธิ์เมื่อมีข้อความแจ้งให้ทำ
อินเทอร์เฟซควรมีลักษณะคล้ายกับภาพนี้

นี่จะเป็นอินเทอร์เฟซหลักของเรา โดยมี IDE อยู่ด้านบนและเทอร์มินัลอยู่ด้านล่าง
ตั้งค่าไดเรกทอรีการทำงาน
สร้างไดเรกทอรีที่ใช้งานอยู่ โค้ดทั้งหมดที่คุณเขียนใน Codelab นี้จะอยู่ที่นี่ ซึ่งแยกจากที่เก็บอ้างอิง
# 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
หากต้องการเปิดเทอร์มินัล ให้ไปที่ดู -> เทอร์มินัล

ตั้งค่าโปรเจ็กต์ 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 เพื่อดำเนินการต่อ

หลังจากรอสักครู่ หากคุณเห็นเอาต์พุตนี้ในคอนโซล แสดงว่าคุณพร้อมที่จะไปยังขั้นตอนถัดไปแล้ว 
สคริปต์ที่ดำเนินการจะทำตามขั้นตอนต่อไปนี้
- ตรวจสอบว่าคุณมีบัญชีสำหรับการเรียกเก็บเงินสำหรับการทดลองใช้ที่ใช้งานอยู่
- ตรวจสอบโปรเจ็กต์ที่มีอยู่ใน
.env(หากมี) - สร้างโปรเจ็กต์ใหม่หรือใช้โปรเจ็กต์ที่มีอยู่
- ลิงก์บัญชีสำหรับการเรียกเก็บเงินของช่วงทดลองใช้กับโปรเจ็กต์
- บันทึกรหัสโปรเจ็กต์ลงใน .env
- ตั้งค่าโปรเจ็กต์เป็นโปรเจ็กต์ gcloud ที่ใช้งานอยู่
ยืนยันว่าได้ตั้งค่าโปรเจ็กต์อย่างถูกต้องโดยตรวจสอบข้อความสีเหลืองข้างไดเรกทอรีการทำงานในพรอมต์เทอร์มินัล Cloud Shell โดยควรแสดงรหัสโปรเจ็กต์ของคุณ

เปิดใช้ API ที่จำเป็น
เปิดใช้ Google Cloud APIs ที่จำเป็นสำหรับ Codelab นี้
gcloud services enable \
aiplatform.googleapis.com \
sqladmin.googleapis.com \
compute.googleapis.com
- Vertex AI API (
aiplatform.googleapis.com) - Agent ใช้โมเดล 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 และเปลี่ยนตัวแทนจากที่เก็บข้อมูลในหน่วยความจำเป็นการจัดเก็บข้อมูลที่สำรองฐานข้อมูล การสร้างอินสแตนซ์ใช้เวลา 2-3 นาที ดังนั้นคุณจะเริ่มสร้างก่อนและเราจะพูดคุยกันต่อในหัวข้อถัดไปขณะรอให้การสร้างเสร็จสมบูรณ์
เริ่มสร้างอินสแตนซ์
เพิ่มรหัสผ่านฐานข้อมูลลงในไฟล์ .env แล้วโหลดซ้ำ เราจะใช้ cafe-agent-pwd-2025 เป็นรหัสผ่าน
echo "DB_PASSWORD=cafe-agent-pwd-2025" >> .env
source .env
เรียกใช้คำสั่งนี้เพื่อสร้างอินสแตนซ์ Cloud SQL PostgreSQL การดำเนินการนี้จะใช้เวลา 2-3 นาที ปล่อยให้ดำเนินการต่อไปและไปยังส่วนถัดไป
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 (คลิกไอคอน +) เพื่อให้เราโฟกัสได้มากขึ้น

ไปที่ไดเรกทอรีการทำงานอีกครั้งและเปิดใช้งานโปรเจ็กต์โดยใช้สคริปต์การตั้งค่าก่อนหน้า
cd ~/build-agent-adk-cloudsql
bash setup_verify_trial_project.sh && source .env
จากนั้นไปที่ส่วนถัดไป
4. สร้าง Agent คาเฟ่คอนเซียร์จ
ขั้นตอนนี้จะสร้างโครงสร้างโปรเจ็กต์สำหรับตัวแทน ADK และกำหนด Cafe Concierge พื้นฐานด้วยเครื่องมือเมนู
เริ่มต้นโปรเจ็กต์ Python
Codelab นี้ใช้ uv ซึ่งเป็นตัวจัดการแพ็กเกจ Python ที่รวดเร็วซึ่งจัดการสภาพแวดล้อมเสมือนจริงและการอ้างอิงในเครื่องมือเดียว ซึ่งติดตั้งไว้ล่วงหน้าใน Cloud Shell
เริ่มต้นโปรเจ็กต์ Python และเพิ่ม ADK เป็นทรัพยากร Dependency
uv init
uv add google-adk==1.25.0 asyncpg
uv init จะสร้าง pyproject.toml และสภาพแวดล้อมเสมือน uv จะติดตั้งการอ้างอิงและบันทึกไว้ใน pyproject.toml
เริ่มต้นโครงสร้างโปรเจ็กต์ของ Agent
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/ └── ...
เขียน Agent
เปิด cafe_concierge/agent.py ใน Cloud Shell Editor
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],
)
ซึ่งจะกำหนด Agent พื้นฐานที่มีเครื่องมือ 1 อย่างคือ get_menu() ตัวแทนตอบคำถามเกี่ยวกับเมนูได้ แต่ยังติดตามคำสั่งซื้อหรือจดจำค่ากำหนดไม่ได้
ยืนยันว่า Agent ทำงาน
เริ่ม UI สำหรับนักพัฒนาแอป ADK จากไดเรกทอรีการทำงานโดยทำดังนี้
cd ~/build-agent-adk-cloudsql
uv run adk web
เปิด URL ที่แสดงในเทอร์มินัล (โดยปกติคือ http://localhost:8000) โดยใช้ฟีเจอร์ตัวอย่างเว็บของ Cloud Shell เลือก cafe_concierge จากเมนูแบบเลื่อนลงของเอเจนต์ที่มุมซ้ายบน
พิมพ์ข้อความต่อไปนี้ในแถบแชทและตรวจสอบว่าตัวแทนตอบกลับด้วยรายการเมนูและราคา
What's on the menu?

หยุด UI สำหรับนักพัฒนาซอฟต์แวร์ด้วย Ctrl+C ก่อนดำเนินการต่อ
5. เพิ่มการจัดการคำสั่งซื้อแบบมีสถานะ
เอเจนต์จะแสดงเมนูได้ แต่รับออเดอร์หรือจดจำค่ากำหนดไม่ได้ ขั้นตอนนี้จะเพิ่มเครื่องมือ 4 อย่างที่ใช้ระบบสถานะของ ADK เพื่อติดตามคำสั่งซื้อในการสนทนาและจัดเก็บค่ากำหนดด้านอาหารในการสนทนาต่างๆ
ทําความเข้าใจเหตุการณ์และสถานะเซสชัน
การสนทนา ADK ทุกรายการจะอยู่ในออบเจ็กต์ Session เซสชันจะติดตาม 2 สิ่งที่แตกต่างกัน ได้แก่ เหตุการณ์และสถานะ การทำความเข้าใจความแตกต่างนี้เป็นกุญแจสำคัญในการสร้างเอเจนต์ที่จดจำสิ่งที่ถูกต้องในวิธีที่ถูกต้อง
เหตุการณ์คือบันทึกตามลำดับเวลาของทุกสิ่งที่เกิดขึ้นในการสนทนา ข้อความของผู้ใช้ทุกข้อความ การตอบกลับของเอเจนต์ทุกครั้ง การเรียกใช้เครื่องมือทุกครั้ง และค่าที่ส่งคืนของเครื่องมือแต่ละรายการจะได้รับการบันทึกเป็น Event และต่อท้ายรายการเหตุการณ์ของเซสชัน เหตุการณ์จะเปลี่ยนแปลงไม่ได้ เมื่อบันทึกแล้วจะไม่มีการเปลี่ยนแปลง ให้คิดว่าเหตุการณ์คือข้อความถอดเสียงแบบเต็มของการสนทนา
สถานะคือสมุดร่างคีย์-ค่าที่เอเจนต์อ่านและเขียนระหว่างการสนทนา สถานะเปลี่ยนแปลงได้ ซึ่งแตกต่างจากเหตุการณ์ โดยค่าจะเปลี่ยนไปเมื่อการสนทนาพัฒนาขึ้น State คือที่ที่เอเจนต์จัดเก็บ Structured Data ที่จำเป็นต่อการดำเนินการ ได้แก่ คำสั่งซื้อปัจจุบัน ค่ากำหนดของลูกค้า และยอดรวมที่กำลังดำเนินการ ลองนึกถึงสถานะเป็นโพสต์อิตที่ตัวแทนเก็บไว้ข้างๆ ข้อความถอดเสียง
ความสัมพันธ์ของทั้ง 2 อย่างมีดังนี้

เครื่องมือจะอ่านและเขียนสถานะผ่าน ToolContext ซึ่งเป็นออบเจ็กต์ที่ ADK จะแทรกลงในฟังก์ชันเครื่องมือใดก็ตามโดยอัตโนมัติที่ประกาศเป็นพารามิเตอร์ คุณไม่ต้องสร้างเอง tool_context.state ช่วยให้เครื่องมืออ่านและเขียนพื้นที่ทดลองของสถานะเซสชันได้ ADK จะตรวจสอบลายเซ็นของฟังก์ชัน โดยจะแทรกพารามิเตอร์ที่มีประเภท ToolContext ส่วนพารามิเตอร์อื่นๆ ทั้งหมด LLM จะกรอกตามการสนทนา
เมื่อเครื่องมือเขียนไปยัง tool_context.state ADK จะบันทึกการเปลี่ยนแปลงนั้นเป็น state_delta ภายในเหตุการณ์ จากนั้น SessionService จะใช้ส่วนต่างกับสถานะปัจจุบันของเซสชัน ซึ่งหมายความว่าการเปลี่ยนแปลงสถานะจะติดตามกลับไปยังเหตุการณ์ที่ทำให้เกิดการเปลี่ยนแปลงนั้นได้เสมอ ซึ่งรวมถึงบริบทในรูปแบบอื่นๆ เช่น callback_context
ทำความเข้าใจคำนำหน้าของรัฐ
คีย์สถานะใช้คำนำหน้าเพื่อควบคุมขอบเขตของคีย์
คำนำหน้า | ขอบเขต | ยังคงอยู่หลังรีสตาร์ทไหม (พร้อม DB) |
(ไม่มี) | เซสชันปัจจุบันเท่านั้น | ใช่ |
| เซสชันทั้งหมดของผู้ใช้รายนี้ | ใช่ |
| เซสชันทั้งหมด ผู้ใช้ทั้งหมด | ใช่ |
| การเรียกใช้ปัจจุบันเท่านั้น | ไม่ |
ในโค้ดแล็บนี้ คุณจะใช้คำนำหน้า 2 รายการ ได้แก่ คีย์ที่ไม่มีคำนำหน้าสำหรับข้อมูลระดับเซสชัน (คำสั่งซื้อปัจจุบัน ซึ่งเกี่ยวข้องกับการสนทนานี้เท่านั้น) และคีย์ user: สำหรับข้อมูลระดับผู้ใช้ (ค่ากำหนดด้านอาหาร ซึ่งเกี่ยวข้องกับการสนทนาทั้งหมดสำหรับผู้ใช้รายนี้)
เพิ่มเครื่องมือแบบมีสถานะ
เปิด cafe_concierge/agent.py ใน Cloud Shell Editor
cloudshell edit cafe_concierge/agent.py
จากนั้นเพิ่มฟังก์ชัน 4 รายการต่อไปนี้เหนือคำจำกัดความของ 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."}
สิ่งที่ควรทราบมี 2 อย่างดังนี้
place_orderและget_order_summaryใช้คีย์ที่ไม่มีคำนำหน้า (current_order) สถานะนี้เชื่อมโยงกับเซสชันปัจจุบัน โดยการสนทนาใหม่จะเริ่มต้นด้วยคำสั่งซื้อที่ว่างเปล่า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พจนานุกรม- ฟังก์ชันเครื่องมือ 5 อย่าง:
get_menu,place_order,get_order_summary,set_dietary_preference,get_dietary_preferences - คำจำกัดความของ
root_agentด้วยเครื่องมือทั้ง 5
6. ทดสอบ Agent ด้วย UI สำหรับนักพัฒนาแอป ADK
ขั้นตอนนี้จะเรียกใช้เอเจนต์และใช้ฟีเจอร์แบบมีสถานะทั้งหมด ได้แก่ การจัดลำดับ การติดตามค่ากำหนด และหน่วยความจำข้ามเซสชัน (ภายในกระบวนการเดียวกัน) นอกจากนี้ คุณยังจะตรวจสอบแผงเหตุการณ์และสถานะเพื่อดูว่า ADK ติดตามการสนทนาภายในอย่างไร
เริ่ม UI สำหรับนักพัฒนาซอฟต์แวร์
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?
ตรวจสอบเหตุการณ์เซสชัน
ระบบจะบันทึกเหตุการณ์ทั้งหมดและแสดงใน UI บนเว็บ คุณจะเห็นว่าในแชทบ็อกซ์นั้นไม่ใช่แค่พรอมต์และคำตอบของคุณ แต่ยังมี tool_call และ tool_response ด้วย

คุณควรเห็นรายการเหตุการณ์ตามลำดับ แต่ละเหตุการณ์จะมีผู้เขียน (ผู้ที่สร้างเหตุการณ์) และประเภท (ประเภทของการโต้ตอบที่แสดง) ดังนี้
ผู้เขียน | ประเภท | สิ่งที่แสดง |
|
| ข้อความที่คุณพิมพ์ในแชท |
|
| การตอบกลับด้วยข้อความของตัวแทน |
|
| เอเจนต์ตัดสินใจเรียกใช้เครื่องมือ (แสดงชื่อฟังก์ชัน + อาร์กิวเมนต์) |
|
| ค่าที่ส่งคืนจากการเรียกใช้เครื่องมือ |
คลิกtool_callเหตุการณ์ใดเหตุการณ์หนึ่ง เช่น set_dietary_preferenceการโทร คุณควรเห็นข้อมูลต่อไปนี้
- ชื่อฟังก์ชัน:
set_dietary_preference - อาร์กิวเมนต์:
{"preference": "lactose intolerant"}
ตอนนี้ให้คลิกtool_responseกิจกรรมที่เกี่ยวข้องด้านล่าง คุณควรเห็นค่าที่ส่งคืนต่อไปนี้
- คำตอบ:
{"saved": "lactose intolerant", "all_preferences": ["lactose intolerant"]}

มองหาฟิลด์ state_delta ภายในเหตุการณ์ tool_response ซึ่งจะแสดงสถานะที่เปลี่ยนแปลงอย่างชัดเจนอันเป็นผลมาจากการเรียกใช้เครื่องมือนี้
state_delta: {"user:dietary_preferences": ["lactose intolerant"]}
การเปลี่ยนแปลงสถานะทุกครั้งจะติดตามได้จนถึงเหตุการณ์ที่เฉพาะเจาะจง วิธีนี้ช่วยให้ ADK ซิงค์สมุดร่างสถานะกับประวัติการสนทนาได้
ตรวจสอบสถานะเซสชัน
คลิกแท็บรัฐ แท็บสถานะจะแสดงภาพรวมของสิ่งที่ตัวแทนทราบในขณะนี้ ซึ่งก็คือค่าปัจจุบันของคีย์สถานะทั้งหมด ซึ่งแตกต่างจากบันทึกเหตุการณ์ (ซึ่งแสดงประวัติทั้งหมด)

คุณควรเห็นรายการ 2 รายการ ได้แก่
current_order—{"items": ["oat milk latte", "banana bread"], "total": 9.0}user:dietary_preferences—["lactose intolerant"]
โปรดสังเกตความแตกต่างของชื่อคีย์
current_orderไม่มีคำนำหน้า เนื่องจากเป็นระดับเซสชัน โดยจะอยู่ในเฉพาะการสนทนานี้และจะหายไปเมื่อเซสชันสิ้นสุดลงuser:dietary_preferencesมีคําต่อท้ายว่าuser:ซึ่งเป็นระดับผู้ใช้ โดยจะแชร์ในทุกเซสชันของผู้ใช้รายนี้
ความแตกต่างนี้จะมองไม่เห็นในโค้ด (ทั้ง 2 อย่างใช้ tool_context.state) แต่จะควบคุมว่าข้อมูลจะไปถึงที่ใด คุณจะเห็นการเปลี่ยนแปลงนี้ในการทดสอบครั้งถัดไป
การสนทนา 2: ยืนยันสถานะผู้ใช้ข้ามเซสชัน
คลิกปุ่มเซสชันใหม่ใน UI สำหรับนักพัฒนาแอปเพื่อเริ่มการสนทนาใหม่ ซึ่งจะสร้างเซสชันใหม่สำหรับผู้ใช้รายเดียวกัน

ลองใช้พรอมต์นี้
What do you recommend for me?
ตรวจสอบแท็บสถานะในเซสชันใหม่ user:dietary_preferences คีย์จะยังคงอยู่ แต่ current_order จะหายไป เนื่องจากสถานะดังกล่าวเชื่อมโยงกับเซสชันก่อนหน้า

7. สังเกตข้อจำกัดของพื้นที่เก็บข้อมูลในเครื่อง
เอเจนต์จะจดจำค่ากำหนดในเซสชันต่างๆ ได้ แต่จะทำได้เฉพาะในขณะที่ที่เก็บข้อมูลในเครื่องยังคงอยู่ ขั้นตอนนี้แสดงให้เห็นข้อจำกัดพื้นฐานของพื้นที่เก็บข้อมูลในเครื่อง
เริ่ม Agent อีกครั้ง
คุณหยุด 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?
ตัวแทนจำไม่ได้ ความชอบด้านอาหาร ประวัติการสั่งซื้อ ทั้งหมดจะหายไป

ทุกอย่างจะถูกล้างออกเมื่อเราลบพื้นที่เก็บข้อมูลในเครื่อง ซึ่งมักจะเกิดขึ้นเมื่อเราใช้สภาพแวดล้อมแบบไม่ใช้เซิร์ฟเวอร์ session.db จะจัดเก็บสถานะทั้งหมดไว้ในหน่วยความจำของกระบวนการ การนำออกจะลบข้อมูลทั้งหมด
โซลูชัน: ระบุ DatabaseSessionService ซึ่งในบทแนะนำนี้จะจัดเก็บข้อมูลเซสชันทั้งหมดในฐานข้อมูล PostgreSQL ใน Cloud SQL รหัสตัวแทนและเครื่องมือจะยังคงเหมือนเดิมทุกประการ มีเพียงส่วนหลังของพื้นที่เก็บข้อมูลเท่านั้นที่จะเปลี่ยนแปลง
หยุด UI สำหรับนักพัฒนาซอฟต์แวร์ด้วย 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 (ฐานข้อมูลภายใน) ออกแล้วและครอบคลุมเซสชันการสนทนา
เริ่มการทำงานของ Agent
ตรวจสอบว่าพร็อกซีการตรวจสอบสิทธิ์ 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
จากนั้นมาเริ่ม UI สำหรับนักพัฒนาแอป 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: รอดพ้นจากการรีสตาร์ท
หยุด UI สำหรับนักพัฒนาซอฟต์แวร์ด้วย Ctrl+C และตรวจสอบว่าได้นำ 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: จะส่งต่อไปยังทุกเซสชันใหม่ของผู้ใช้รายนี้

ตรวจสอบฐานข้อมูลโดยตรง
เปิดแท็บเทอร์มินัลใหม่ใน 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)
สรุปลักษณะการทำงานของสถานะ
คีย์สถานะ | คำนำหน้า | ขอบเขต | แชร์ในเซสชันต่างๆ ไหม |
| (ไม่มี) | เซสชัน | ไม่ |
|
| ผู้ใช้ | ใช่ |
10. ขอแสดงความยินดี / ล้างข้อมูล
ยินดีด้วย คุณสร้างเอเจนต์ AI แบบมีสถานะที่ทำงานอย่างต่อเนื่องได้สำเร็จโดยใช้ ADK และ Cloud SQL
สิ่งที่คุณได้เรียนรู้
- วิธีสร้างเอเจนต์ ADK ด้วยเครื่องมือที่กำหนดเองซึ่งอ่านและเขียนสถานะเซสชัน
- ความแตกต่างระหว่างสถานะระดับเซสชัน (ไม่มีคำนำหน้า) กับสถานะระดับผู้ใช้ (คำนำหน้า
user:) - เหตุผลที่
session.dbในเครื่องของ adk เริ่มต้นเหมาะสําหรับการพัฒนาเท่านั้น เนื่องจากข้อมูลทั้งหมดจะสูญหายเมื่อนําออก (และนําออกได้ง่าย ไม่มีการสํารองข้อมูล) จึงไม่เหมาะสําหรับการติดตั้งใช้งานแบบ Serverless ซึ่งไม่มีสถานะ - วิธีจัดสรรอินสแตนซ์ Cloud SQL PostgreSQL และเชื่อมต่อกับอินสแตนซ์ด้วยพร็อกซีการตรวจสอบสิทธิ์ Cloud SQL
- วิธีเชื่อมต่อกับ DatabaseSessionService ด้วย PostgreSQL ใน CloudSQL โดยมีการเปลี่ยนแปลงโค้ดน้อยที่สุด - เครื่องมือเดียวกัน เอเจนต์เดียวกัน แบ็กเอนด์ต่างกัน
- วิธีที่สถานะระดับผู้ใช้คงอยู่ตลอดเซสชันการสนทนาที่แยกกัน
ล้างข้อมูล
โปรดล้างข้อมูลทรัพยากรที่สร้างขึ้นในโค้ดแล็บนี้เพื่อหลีกเลี่ยงการเรียกเก็บเงินจากบัญชี Google Cloud
ตัวเลือกที่ 1: ลบโปรเจ็กต์ (แนะนำ)
วิธีที่ง่ายที่สุดในการล้างข้อมูลคือการลบโปรเจ็กต์ ซึ่งจะเป็นการนำทรัพยากรทั้งหมดที่เชื่อมโยงกับโปรเจ็กต์ออก
gcloud projects delete ${GOOGLE_CLOUD_PROJECT}
ตัวเลือกที่ 2: ลบทรัพยากรแต่ละรายการ
หากต้องการเก็บโปรเจ็กต์ไว้แต่ต้องการนำเฉพาะทรัพยากรที่สร้างขึ้นในโค้ดแล็บนี้ออก ให้ทำดังนี้
gcloud sql instances delete cafe-concierge-db --quiet