1. บทนำ

ปัญหา "ปลาทอง"
สมมติว่าคุณจ้างตัวแทนท่องเที่ยวเพื่อวางแผนทริปพักผ่อนในฝันที่โตเกียว ใช้ตัวแทนเซสชันเพื่อดู "ปัญหาปลาทอง" ในการทำงาน
คุณเดินเข้าไปในออฟฟิศของพวกเขาและพูดว่า
"สวัสดี ฉันอยากวางแผนทริป 2 วันไปโตเกียว ฉันสนใจสถานที่ทางประวัติศาสตร์และซูชิ"
ตัวแทนตอบกลับอย่างกระตือรือร้นว่า
"เยี่ยม ฉันวางแผนที่จะไปเยี่ยมชมพระราชวังอิมพีเรียลและทานซูชิที่ Sukiyabashi Jiro"
คุณยิ้มและพูดว่า
"ฟังดูดีมากเลย คุณช่วยส่งกำหนดการให้ฉันได้ไหม"
ตัวแทนมองคุณด้วยสีหน้าว่างเปล่าและถามว่า
"สวัสดี วันนี้จะให้เราช่วยคุณวางแผนการเดินทางได้อย่างไร
นี่คือ "ปัญหาปลาทอง" หากไม่มีหน่วยความจำ ทุกการโต้ตอบก็เหมือนกระดาษเปล่า ความสามารถนั้นมีอยู่แล้ว ตัวแทนรู้วิธีวางแผนการเดินทาง แต่ขาดความต่อเนื่อง เอเจนต์ AI ต้องจดจำได้จึงจะมีประโยชน์อย่างแท้จริง
ภารกิจของคุณในวันนี้
ในเวิร์กช็อปนี้ คุณจะได้แก้ปัญหาปลาทองด้วยการสร้างตัวแทนการท่องเที่ยวที่จดจำ เรียนรู้ และปรับตัวได้ คุณจะก้าวหน้าผ่านระดับความจำของเอเจนต์ 6 ระดับ ซึ่งจะสร้างระบบที่ทำงานได้ไม่เหมือนแชทบอท แต่เหมือนผู้ช่วยส่วนตัวที่ทุ่มเทมากกว่า
ระดับ | แนวคิด | "พลังวิเศษ" |
ระดับ 1 | เซสชันและสถานะ | สนทนาโดยไม่ลืม |
ระดับ 2 | สถานะของหลายเอเจนต์ | การแชร์โน้ตระหว่างสมาชิกในทีม |
ระดับ 3 | ความคงทน | จดจำคุณได้แม้หลังจากรีบูตระบบ |
ระดับ 4 | การโทรกลับ | อัปเดตหน่วยความจำโดยอัตโนมัติอย่างสมบูรณ์ |
ระดับ 5 | เครื่องมือที่กำหนดเอง | การอ่านและการเขียนโปรไฟล์ผู้ใช้ที่มีโครงสร้าง |
ระดับ 6 | หน่วยความจำมัลติโมดัล | "การมองเห็น" และการจดจำรูปภาพและวิดีโอ |
สแต็กหน่วยความจำ ADK
ก่อนเขียนโค้ด มาทำความเข้าใจเครื่องมือที่เราจะใช้กันก่อน ชุดพัฒนาเอเจนต์ของ Google (ADK) มีวิธีจัดการหน่วยความจำที่เป็นระบบดังนี้
- เซสชัน: คอนเทนเนอร์สำหรับการสนทนา ซึ่งจะเก็บประวัติของสิ่งที่พูด
- สถานะ: "กระดาษทด" คีย์-ค่าที่แนบกับเซสชัน เอเจนต์ใช้สิ่งนี้เพื่อจัดเก็บข้อเท็จจริงที่เฉพาะเจาะจง (เช่น
destination="Tokyo") - MemoryService: ที่เก็บข้อมูลระยะยาว ซึ่งเป็นที่ที่เราเก็บข้อมูลไว้ตลอดไป เช่น ค่ากำหนดของผู้ใช้หรือเอกสารที่วิเคราะห์แล้ว
2. ตั้งค่า
เราต้องมี 2 สิ่งเพื่อขับเคลื่อนเอเจนต์ AI ได้แก่ โปรเจ็กต์ Google Cloud เพื่อเป็นรากฐาน
ส่วนที่ 1: เปิดใช้บัญชีสำหรับการเรียกเก็บเงิน
- การอ้างสิทธิ์บัญชีการเรียกเก็บเงินพร้อมเครดิต 5 ดอลลาร์ คุณจะต้องใช้เครดิตนี้ในการติดตั้งใช้งาน โปรดตรวจสอบบัญชี gmail
ส่วนที่ 2: สภาพแวดล้อมแบบเปิด
- 👉 คลิกลิงก์นี้เพื่อไปยัง Cloud Shell Editor โดยตรง
- 👉 หากระบบแจ้งให้ให้สิทธิ์ในวันนี้ ให้คลิกให้สิทธิ์เพื่อดำเนินการต่อ

- 👉 หากเทอร์มินัลไม่ปรากฏที่ด้านล่างของหน้าจอ ให้เปิดโดยทำดังนี้
- คลิกดู
- คลิก Terminal

- 👉💻 ในเทอร์มินัล ให้ตรวจสอบว่าคุณได้รับการตรวจสอบสิทธิ์แล้วและตั้งค่าโปรเจ็กต์เป็นรหัสโปรเจ็กต์โดยใช้คำสั่งต่อไปนี้
gcloud auth list - 👉💻 โคลนโปรเจ็กต์ Bootstrap จาก GitHub
git clone https://github.com/cuppibla/memory_agent_starter - 👉💻 เรียกใช้สคริปต์การตั้งค่าจากไดเรกทอรีโปรเจ็กต์
สคริปต์จะจัดการกระบวนการตั้งค่าที่เหลือโดยอัตโนมัติcd ~/memory_agent_starter ./init.sh - 👉💻 ตั้งค่ารหัสโปรเจ็กต์ที่จำเป็น
gcloud config set project $(cat ~/project_id.txt) --quiet
ส่วนที่ 3: การตั้งค่าสิทธิ์
- 👉💻 เปิดใช้ API ที่จำเป็นโดยใช้คำสั่งต่อไปนี้ การดำเนินการนี้อาจใช้เวลาสักครู่
gcloud services enable \ cloudresourcemanager.googleapis.com \ servicenetworking.googleapis.com \ run.googleapis.com \ aiplatform.googleapis.com \ compute.googleapis.com - 👉💻 ให้สิทธิ์ที่จำเป็นโดยเรียกใช้คำสั่งต่อไปนี้ในเทอร์มินัล
. ~/memory_agent_starter/set_env.sh
โปรดสังเกตว่าระบบได้สร้างไฟล์ .env ให้คุณแล้ว ซึ่งจะแสดงข้อมูลโปรเจ็กต์ของคุณ
3. พื้นฐาน - เซสชันและสถานะ

แนวคิด: บริบทคือสิ่งสำคัญที่สุด
รูปแบบพื้นฐานที่สุดของหน่วยความจำคือหน่วยความจำเซสชัน ซึ่งช่วยให้ตัวแทนทราบว่า "มัน" ในประโยค "ฉันต้องการซื้อมัน" หมายถึงรองเท้าที่คุณพูดถึงเมื่อ 10 วินาทีก่อน
ใน ADK เราจัดการเรื่องนี้ด้วยออบเจ็กต์ Session
- แนวทางแบบไม่เก็บสถานะ: สร้างเซสชันใหม่สำหรับทุกข้อความ
- แนวทางแบบมีสถานะ: สร้างเซสชันเดียวและนำกลับมาใช้ใหม่สำหรับการสนทนาทั้งหมด
ขั้นตอนที่ 1: ตรวจสอบเอเจนต์
👉💻 ในเทอร์มินัล Cloud Shell ให้เปิดไฟล์ใน Cloud Shell Editor โดยเรียกใช้คำสั่งต่อไปนี้
cloudshell edit ~/memory_agent_starter/01_session_agent/agent.py
เปิด ~/memory_agent_starter/01_session_agent/agent.py
👉 ค้นหาความคิดเห็น # TODO: Create a root agent ภายในฟังก์ชัน agent.py
แทนที่ทั้งบรรทัดนี้ด้วยโค้ดต่อไปนี้
root_agent = LlmAgent(
name="multi_day_trip_agent",
model="gemini-2.5-flash",
description="Agent that progressively plans a multi-day trip, remembering previous days and adapting to user feedback.",
instruction="""
You are the "Adaptive Trip Planner" 🗺️ - an AI assistant that builds multi-day travel itineraries step-by-step.
Your Defining Feature:
You have short-term memory. You MUST refer back to our conversation to understand the trip's context, what has already been planned, and the user's preferences. If the user asks for a change, you must adapt the plan while keeping the unchanged parts consistent.
Your Mission:
1. **Initiate**: Start by asking for the destination, trip duration, and interests.
2. **Plan Progressively**: Plan ONLY ONE DAY at a time. After presenting a plan, ask for confirmation.
3. **Handle Feedback**: If a user dislikes a suggestion (e.g., "I don't like museums"), acknowledge their feedback, and provide a *new, alternative* suggestion for that time slot that still fits the overall theme.
4. **Maintain Context**: For each new day, ensure the activities are unique and build logically on the previous days. Do not suggest the same things repeatedly.
5. **Final Output**: Return each day's itinerary in MARKDOWN format.
""",
tools=[google_search]
)
คำสั่งจะบอก LLM ให้จดจำ แต่โค้ดต้องมีความสามารถในการจดจำ
ขั้นตอนที่ 2: 2 สถานการณ์
เปิด ~/memory_agent_starter/01_session_agent/main.py
👉 ในเทอร์มินัล Cloud Shell ให้เปิดไฟล์ใน Cloud Shell Editor โดยเรียกใช้คำสั่งต่อไปนี้
cloudshell edit ~/memory_agent_starter/01_session_agent/main.py
เปิด ~/memory_agent_starter/01_session_agent/main.py แล้วค้นหาความคิดเห็น # TODO: Create a runner with in memorysession service ภายในฟังก์ชัน main.py
แทนที่ทั้งบรรทัดนี้ด้วยโค้ดต่อไปนี้
runner = Runner(
agent=agent,
session_service=session_service,
app_name=agent.name
)
👉 ค้นหาความคิดเห็น # TODO: create a different session to test ภายในฟังก์ชัน main.py
แทนที่ทั้งบรรทัดนี้ด้วยโค้ดต่อไปนี้
tokyo_session_2 = await session_service.create_session(
app_name=multi_day_agent.name,
user_id=user_id
)
ทดสอบ
เรามีฟังก์ชัน 2 อย่างที่แสดงให้เห็นความแตกต่างระหว่างความจำของ "ปลาทอง" กับ "ช้าง"
สถานการณ์ที่ 1: มีสถานะ (เซสชันที่แชร์)
async def run_trip_same_session_scenario(session_service, user_id):
# 1. Create ONE session
trip_session = await session_service.create_session(...)
# 2. Turn 1
await run_agent_query(..., trip_session, ...)
# 3. Turn 2 - REUSING the same session!
# The agent can "see" Turn 1 because it's in the session history.
await run_agent_query(..., trip_session, ...)
สถานการณ์ที่ 2: ไม่มีการเก็บสถานะ (เซสชันใหม่ทุกครั้ง)
async def run_trip_different_session_scenario(session_service, user_id):
# Turn 1
tokyo_session = await session_service.create_session(...)
await run_agent_query(..., tokyo_session, ...)
# Turn 2 - Creating a FREASH session
# The agent has NO IDEA what happened in Turn 1.
tokyo_session_2 = await session_service.create_session(...)
await run_agent_query(..., tokyo_session_2, ...)
ขั้นตอนที่ 3: เรียกใช้เอเจนต์
มาดูความแตกต่างในการใช้งานจริงกัน เรียกใช้สคริปต์
👉💻 ในบรรทัดคำสั่ง ให้เรียกใช้บรรทัดคำสั่งด้านล่าง
cd ~/memory_agent_starter
uv run python ~/memory_agent_starter/01_session_agent/main.py
ดูสถานการณ์ที่ 1: ตัวแทนจดจำค่ากำหนดของคุณจากข้อความแรกและปรับแผนในข้อความที่ 2
สังเกตสถานการณ์ที่ 2: ในเทิร์นที่ 2 ("คุณจำได้ไหมว่าฉันชอบอะไรเกี่ยวกับอาหาร") ตัวแทนล้มเหลวโดยสิ้นเชิงเนื่องจากเป็นเซสชันใหม่ ซึ่งหมายความว่า "ฉันไม่รู้ว่าคุณกำลังพูดถึงอะไร"
ประเด็นสำคัญ
กฎข้อที่ 1 ของฟีเจอร์ความจำ: ใช้ session.id ซ้ำเสมอเพื่อรักษาบริบทของการสนทนา ออบเจ็กต์ Session คือบัฟเฟอร์หน่วยความจำระยะสั้นของเอเจนต์
4. ทีม - สถานะตัวแทนหลายราย

แนวคิด: "เกมโทรศัพท์"
เมื่อตัวแทนหลายคนทำงานร่วมกัน พวกเขาจะเหมือนเพื่อนร่วมงานที่ส่งโฟลเดอร์ไฟล์ไปมา หากตัวแทนเขียนหมายเหตุในโฟลเดอร์ ตัวแทนคนถัดไปควรจะอ่านหมายเหตุนั้นได้
ใน ADK "โฟลเดอร์" นี้คือสถานะ
- State คือพจนานุกรม (
{"key": "value"}) ที่อยู่ในเซสชัน - Agent ทุกคนในเซสชันสามารถอ่านหรือเขียนข้อมูลในเซสชันได้
ขั้นตอนที่ 1: ตรวจสอบเวิร์กโฟลว์
👉💻 ในเทอร์มินัล Cloud Shell ให้เปิดไฟล์ใน Cloud Shell Editor โดยเรียกใช้คำสั่งต่อไปนี้
cloudshell edit ~/memory_agent_starter/02_multi_agent/agent.py
👉ค้นหาความคิดเห็น # TODO: foodie agent ในไฟล์ ~/memory_agent_starter/02_multi_agent/agent.py
แทนที่ทั้งบรรทัดนี้ด้วยโค้ดต่อไปนี้
foodie_agent = LlmAgent(
name="foodie_agent",
model="gemini-2.5-flash",
tools=[google_search],
instruction="""You are an expert food critic. Your goal is to find the best restaurant based on a user's request.
When you recommend a place, you must output *only* the name of the establishment and nothing else.
For example, if the best sushi is at 'Jin Sho', you should output only: Jin Sho
""",
output_key="destination" # ADK will save the agent's final response to state['destination']
)
👉 ค้นหาความคิดเห็น # TODO: transportation agent ภายในฟังก์ชัน agent.py
แทนที่ทั้งบรรทัดนี้ด้วยโค้ดต่อไปนี้
transportation_agent = LlmAgent(
name="transportation_agent",
model="gemini-2.5-flash",
tools=[google_search],
instruction="""You are a navigation assistant. Given a destination, provide clear directions.
The user wants to go to: {destination}.
Analyze the user's full original query to find their starting point.
Then, provide clear directions from that starting point to {destination}.
""",
)
👉 ค้นหาความคิดเห็น # TODO: root_agent ภายในฟังก์ชัน agent.py
แทนที่ทั้งบรรทัดนี้ด้วยโค้ดต่อไปนี้
root_agent = SequentialAgent(
name="find_and_navigate_agent",
sub_agents=[foodie_agent, transportation_agent],
description="A workflow that first finds a location and then provides directions to it."
)
ตอนนี้เรามีเอเจนต์ 2 คนที่ทำงานตามลำดับดังนี้
- ตัวแทนนักชิม: ค้นหาร้านอาหาร
- ตัวแทนการเดินทาง: บอกเส้นทางไปยังร้านอาหารนั้น
การส่งต่อแบบแมจิก: สังเกตว่า foodie_agent ส่งต่อให้ transportation_agent อย่างไร
foodie_agent = LlmAgent(
# ...
# CRITICAL: This tells ADK to save the agent's output to state['destination']
output_key="destination"
)
transportation_agent = LlmAgent(
# ...
# CRITICAL: This injects state['destination'] into the prompt
instruction="""
The user wants to go to: {destination}.
Provide clear directions...
""",
)
output_key="destination": ระบบจะบันทึกคำตอบของ Foodie Agent อย่างมีประสิทธิภาพ{destination}: ตัวแทนการขนส่งจะอ่านคำตอบนั้นโดยอัตโนมัติ
(ไม่ต้องดำเนินการใดๆ) ขั้นตอนที่ 2: Orchestrator
เปิด 02_multi_agent/main.py
เราใช้ SequentialAgent เพื่อเรียกใช้ตามลำดับ
# 1. Create a single session for the sequential agent
session = await session_service.create_session(...)
# 2. Run the query
# The SequentialAgent manages the state flow:
# Query -> Foodie -> state['destination'] -> Transportation -> Final Answer
await run_agent_query(root_agent, query, ...)
ผู้ใช้ส่งพรอมต์เดียว
"Find best sushi in Palo Alto and then tell me how to get there."
ตัวแทนจะทำงานร่วมกันเพื่อตอบคำถาม
ขั้นตอนที่ 3: เรียกใช้ทีม
👉💻 ในเทอร์มินัล Cloud Shell ให้เรียกใช้เวิร์กโฟลว์แบบหลายเอเจนต์
cd ~/memory_agent_starter
uv run python ~/memory_agent_starter/02_multi_agent/main.py
จะเกิดอะไรขึ้น
- Foodie Agent: ค้นหา "Jin Sho" (หรือร้านอาหารที่คล้ายกัน)
- ADK: บันทึก "จินโช" ไปยัง
state['destination'] - ตัวแทนการขนส่ง: ได้รับ "Jin Sho" ในคำสั่ง
- ผลลัพธ์: "หากต้องการไปร้านจินโชจากสถานี Caltrain ให้เดินลงไปตามถนน University..."
ประเด็นสำคัญ
กฎข้อที่ 2 ของหน่วยความจำ: ใช้สถานะเพื่อส่งข้อมูลที่มีโครงสร้างระหว่างเอเจนต์ ใช้ output_key เพื่อเขียนและ {placeholders} เพื่ออ่าน
5. การรีบูต - ความคงทน

แนวคิด: "ปัญหาการรีบูต"
ตอนนี้ความจำของเราอยู่ที่ InMemory หากคุณหยุดสคริปต์และเริ่มอีกครั้ง เอเจนต์จะลืมทุกอย่าง ซึ่งก็เหมือนกับคอมพิวเตอร์ที่ล้างฮาร์ดไดรฟ์ทุกครั้งที่คุณปิดเครื่อง
เราจึงต้องใช้การคงอยู่เพื่อแก้ไขปัญหานี้ เราจะเปลี่ยน InMemorySessionService เป็น DatabaseSessionService
ขั้นตอนที่ 1: การเปลี่ยนฐานข้อมูล
👉💻 ในเทอร์มินัล Cloud Shell ให้เปิดไฟล์ใน Cloud Shell Editor โดยเรียกใช้คำสั่งต่อไปนี้
cloudshell edit ~/memory_agent_starter/03_persistent_agent/main.py
👉 ค้นหาความคิดเห็น # TODO: Configuration for Persistent Sessions ในไฟล์ ~/memory_agent_starter/03_persistent_agent/main.py
แทนที่ทั้งบรรทัดนี้ด้วยโค้ดต่อไปนี้
SESSIONS_DIR = Path(os.path.expanduser("~")) / ".adk_codelab" / "sessions"
os.makedirs(SESSIONS_DIR, exist_ok=True)
SESSION_DB_FILE = SESSIONS_DIR / "trip_planner.db"
SESSION_URL = f"sqlite:///{SESSION_DB_FILE}"
ตอนนี้ระบบจะบันทึกเซสชันและเหตุการณ์ทั้งหมดลงในไฟล์ SQLite
ขั้นตอนที่ 2: การดึงข้อมูลข้ามเซสชัน
ความต่อเนื่องไม่เพียงช่วยให้สนทนาต่อได้ แต่ยังช่วยเรียนรู้จากแชทที่ผ่านมาได้ด้วย
ในไฟล์เดียวกัน~/memory_agent_starter/03_persistent_agent/main.py ให้ดูกรณีทดสอบที่ 3: การดึงข้อมูลข้ามเซสชัน
👉 ค้นหาความคิดเห็น # TODO: retrieve the previous session manually
แทนที่ทั้งบรรทัดนี้ด้วยโค้ดต่อไปนี้
old_session = await session_service.get_session(
app_name=root_agent.name, user_id="user_01", session_id=session_id
)
👉 ค้นหาความคิดเห็น # TODO: Extract content from the OLD session ภายในฟังก์ชัน main.py
แทนที่ทั้งบรรทัดนี้ด้วยโค้ดต่อไปนี้
previous_context += f"- {role}: {text}\n"
👉 ค้นหาความคิดเห็น # TODO: Manually inject the context to the query ภายในฟังก์ชัน main.py
แทนที่ทั้งบรรทัดนี้ด้วยโค้ดต่อไปนี้
query_3 = f"""
{previous_context}
I'm planning a new trip to Osaka this time.
Based on my previous preferences (above), what should I eat?
"""
ซึ่งจำลองผู้ใช้ที่กลับมาในอีกหลายเดือนต่อมา คุณจะดึงประวัติเก่าๆ นั้นได้ก็ต่อเมื่อมีฐานข้อมูลเท่านั้น
ขั้นตอนที่ 3: การอยู่รอดหลังการรีบูต
👉💻 เรียกใช้สคริปต์ในเทอร์มินัลโดยใช้คำสั่งต่อไปนี้
cd ~/memory_agent_starter
uv run python ~/memory_agent_starter/03_persistent_agent/main.py
ซึ่งจะสร้างไฟล์ ~/memory_agent_starter/trip_planner.db ลองทำดังนี้ เรียกใช้สคริปต์2 ครั้ง
- ในการเรียกใช้ครั้งที่ 2 ให้มองหา "เซสชันที่มีอยู่ซึ่งกลับมาทำงานต่อ"
- เอเจนต์จะจดจำบริบทจากการเรียกใช้ครั้งแรกได้เนื่องจากโหลดจากไฟล์ฐานข้อมูล
ประเด็นสำคัญ
กฎข้อที่ 3 ของหน่วยความจำ: ใช้ DatabaseSessionService สำหรับเวอร์ชันที่ใช้งานจริง ซึ่งจะช่วยให้การสนทนาของผู้ใช้ยังคงอยู่แม้จะมีการรีสตาร์ทเซิร์ฟเวอร์ และช่วยให้วิเคราะห์ประวัติในระยะยาวได้
6. The Spy - Callbacks

บางครั้งคุณต้องอัปเดตหน่วยความจำโดยอัตโนมัติตามสิ่งที่เอเจนต์ทำ ไม่ใช่แค่สิ่งที่เอเจนต์พูด คุณต้องการ "สายลับ" ที่คอยสังเกต Agent และจดบันทึก
ใน ADK สปายนี้คือ Callback 
after_tool_callback: ฟังก์ชันที่ทำงานทุกครั้งที่เอเจนต์ทำงานToolContext: วิธีเขียนไปยังสถานะจากภายในฟังก์ชันนั้น
ขั้นตอนที่ 1: ตรรกะ
👉💻 ในเทอร์มินัล Cloud Shell ให้เปิดไฟล์ใน Cloud Shell Editor โดยเรียกใช้คำสั่งต่อไปนี้
cloudshell edit ~/memory_agent_starter/04_stateful_agent/agent.py
👉 ในไฟล์ ~/memory_agent_starter/04_stateful_agent/agent.py ให้ค้นหาความคิดเห็น # TODO: Implement call back logic
แทนที่ทั้งบรรทัดนี้ด้วยโค้ดต่อไปนี้
def save_activity_type_callback(
tool,
args: Dict[str, Any],
tool_context: ToolContext,
tool_response: Dict[str, Any],
) -> Optional[Dict[str, Any]]:
"""
Callback to save the TYPE of activity just planned into the session state.
"""
# 1. Get the actual agent name.
if tool.name == "transfer_to_agent":
agent_name = args.get("agent_name")
else:
agent_name = tool.name
activity_type = "unknown"
# 2. Determine the type based on which agent was actually used
if agent_name == "museum_expert":
activity_type = "CULTURAL"
elif agent_name == "restaurant_expert":
activity_type = "FOOD"
elif agent_name == "outdoor_expert":
activity_type = "OUTDOOR"
print(f"\n🔔 [CALLBACK] The planner transferred to '{agent_name}'.")
# 3. Update the state directly
tool_context.state["last_activity_type"] = activity_type
print(f"💾 [STATE UPDATE] 'last_activity_type' is now set to: {activity_type}\n")
return tool_response
👉 ในไฟล์เดียวกัน ให้ค้นหาความคิดเห็น # TODO: add callback to root agent ภายในฟังก์ชัน 04_stateful_agent/agent.py
แทนที่ทั้งบรรทัดนี้ด้วยโค้ดต่อไปนี้
after_tool_callback=save_activity_type_callback,
คำสั่งแบบไดนามิก: ตอนนี้คำสั่งของเอเจนต์เป็นฟังก์ชัน ไม่ใช่สตริง โดยจะเปลี่ยนไปตามสถานะ
def get_planner_instruction(context):
last_activity = context.state.get("last_activity_type", "None")
return f"""
The last activity was: {last_activity}
If last_activity is 'CULTURAL' -> `museum_expert` is BANNED.
"""
ขั้นตอนที่ 3: ทดสอบ Spy
👉💻 ในเทอร์มินัล ให้เรียกใช้สคริปต์โดยคัดลอกและวางคำสั่งด้านล่าง
cd ~/memory_agent_starter
uv run python ~/memory_agent_starter/04_stateful_agent/main.py
เมื่อเรียกใช้เอเจนต์นี้ คุณจะเห็นลูป
- เทิร์นที่ 1: คุณขอพิพิธภัณฑ์ ชุดสปาย
last_activity="CULTURAL" - เทิร์นที่ 2: คุณขอพิพิธภัณฑ์อื่น
- การอัปเดตคำสั่งของตัวแทน: "ห้ามใช้คำว่า CULTURAL"
- เอเจนต์กล่าวว่า "ฉันไม่สามารถทำพิพิธภัณฑ์อื่นได้ ไปสวนสาธารณะไหม"
ดูบันทึกคอนโซลสำหรับ [CALLBACK] และ [STATE UPDATE] คุณจะเห็นการเปลี่ยนแปลงหน่วยความจำแบบเรียลไทม์ขณะที่เอเจนต์ทำงาน
ประเด็นสำคัญ
กฎข้อที่ 4 ของ Memory: ใช้การเรียกกลับเพื่อทำให้การจัดการสถานะเป็นแบบอัตโนมัติ Agent จะสร้างบริบทของตัวเองได้เพียงแค่ทำงาน
7. ตู้เก็บเอกสาร - เครื่องมือที่กำหนดเอง
แนวคิด: "ความทรงจำที่มีโครงสร้าง"

ที่ผ่านมา "ความจำ" เป็นบันทึกการแชทหรือคู่คีย์-ค่าอย่างง่าย แต่จะเกิดอะไรขึ้นหากคุณต้องจดจำโปรไฟล์ผู้ใช้ที่ซับซ้อน เช่น diet: vegan, budget: high, pets: [cat, dog]
เราจึงถือว่าหน่วยความจำเป็นเครื่องมือ เอเจนต์จะตัดสินใจอย่างชัดเจนว่าจะเปิดตู้เก็บเอกสาร (อ่าน) เมื่อใด และจะยื่นรายงาน (เขียน) เมื่อใด 
ขั้นตอนที่ 1: เครื่องมือ
👉💻 ในเทอร์มินัล Cloud Shell ให้เปิดไฟล์ใน Cloud Shell Editor โดยเรียกใช้คำสั่งต่อไปนี้
cloudshell edit ~/memory_agent_starter/05_profile_agent/tools.py
👉 ในไฟล์นี้: ~/memory_agent_starter/05_profile_agent/tools.py
เราต้องใช้เครื่องมือ 2 อย่างนี้
save_user_preferences: เขียนไปยังฐานข้อมูลrecall_user_preferences: อ่านจากฐานข้อมูล
ค้นหาความคิดเห็น # TODO: implement save_user_preferences tools ภายในฟังก์ชัน ~/memory_agent_starter/05_profile_agent/tools.py
แทนที่ทั้งบรรทัดนี้ด้วยโค้ดต่อไปนี้
def save_user_preferences(tool_context: ToolContext, new_preferences: Dict[str, Any]) -> str:
user_id = tool_context.session.user_id
with sqlite3.connect(USER_DB_FILE) as conn:
for key, value in new_preferences.items():
conn.execute("INSERT INTO user_preferences (user_id, pref_key, pref_value) VALUES (?, ?, ?) ON CONFLICT(user_id, pref_key) DO UPDATE SET pref_value = excluded.pref_value;",
(user_id, key, json.dumps(value)))
return f"Preferences updated: {list(new_preferences.keys())}"
👉 ค้นหาความคิดเห็น # TODO: implement recall_user_preferences tools ภายในฟังก์ชัน 05/tools.py
แทนที่ทั้งบรรทัดนี้ด้วยโค้ดต่อไปนี้
def recall_user_preferences(tool_context: ToolContext) -> Dict[str, Any]:
user_id = tool_context.session.user_id
preferences = {}
with sqlite3.connect(USER_DB_FILE) as conn:
rows = conn.execute("SELECT pref_key, pref_value FROM user_preferences WHERE user_id = ?", (user_id,)).fetchall()
if not rows: return {"message": "No preferences found."}
for key, value_str in rows: preferences[key] = json.loads(value_str)
return preferences
คำสั่งจะบังคับให้เวิร์กโฟลว์ทำดังนี้
instruction="""
1. RECALL FIRST: First action MUST be `recall_user_preferences`.
3. LEARN: If a user states a new preference, use `save_user_preferences`.
"""
ขั้นตอนที่ 2: การดำเนินการ
👉💻 ในเทอร์มินัล Cloud Shell ให้เปิดไฟล์ใน Cloud Shell Editor โดยเรียกใช้คำสั่งต่อไปนี้
cloudshell edit ~/memory_agent_starter/05_profile_agent/main.py
เปิด ~/memory_agent_starter/05_profile_agent/main.py
Agent จะเป็นผู้ควบคุม ซึ่งแตกต่างจากโมดูลก่อนหน้าที่ ADK จัดการสถานะโดยอัตโนมัติ
- โดยจะเลือกโทรหา
recall_user_preferencesก่อน - โดยจะเลือกโทรหา
save_user_preferencesเมื่อคุณพูดว่า "ฉันเป็นมังสวิรัติ"
ขั้นตอนที่ 3: สร้างโปรไฟล์
👉💻 เรียกใช้สคริปต์
cd ~/memory_agent_starter
uv run python ~/memory_agent_starter/05_profile_agent/main.py
ลองใช้โฟลว์การสนทนานี้
- "สวัสดี วางแผนมื้อค่ำหน่อย" -> Agent ตรวจสอบ DB แต่ไม่พบอะไร ขอค่ากำหนด
- "ฉันเป็นวีแกน" -> Agent บันทึก "มังสวิรัติ" ลงใน DB
- รีสตาร์ทสคริปต์
- "สวัสดี วางแผนมื้อค่ำหน่อย" -> ตัวแทนตรวจสอบฐานข้อมูล เห็นคำว่า "มังสวิรัติ" และแนะนำร้านอาหารมังสวิรัติทันที
ประเด็นสำคัญ
กฎข้อที่ 5 ของหน่วยความจำ: สำหรับข้อมูลที่มีโครงสร้างซับซ้อน ให้เครื่องมืออ่าน/เขียนแก่เอเจนต์ ให้ LLM จัดการพื้นที่เก็บข้อมูลระยะยาวของตัวเอง
8. สมอง - ความทรงจำแบบมัลติโมดัล

แนวคิด: "ประสบการณ์ของมนุษย์"
มนุษย์จดจำภาพได้ดีกว่าข้อความ เราจดจำกลิ่นอายของรูปภาพ เสียงของเสียงพูด ความรู้สึกของวิดีโอ
Vertex AI Memory Bank ช่วยให้เอเจนต์ของคุณจัดการหน่วยความจำมัลติโมดอลได้ โดยสามารถรับรูปภาพ วิดีโอ และเสียง "ทำความเข้าใจ" และดึงข้อมูลเหล่านั้นในภายหลังได้
ขั้นตอนที่ 1: การกำหนดค่า
👉💻 ในเทอร์มินัล Cloud Shell ให้เปิดไฟล์ใน Cloud Shell Editor โดยเรียกใช้คำสั่งต่อไปนี้
cloudshell edit ~/memory_agent_starter/06_multimodal_agent/main.py
👉 เปิด 06_multimodal_agent/main.py ค้นหาความคิดเห็น # TODO: Configure Memory Bank Topic
แทนที่ทั้งบรรทัดนี้ด้วยโค้ดต่อไปนี้
travel_topics = [
MemoryTopic(
managed_memory_topic=ManagedMemoryTopic(
managed_topic_enum=ManagedTopicEnum.USER_PREFERENCES
)
),
MemoryTopic(
managed_memory_topic=ManagedMemoryTopic(
managed_topic_enum=ManagedTopicEnum.USER_PERSONAL_INFO
)
),
MemoryTopic(
custom_memory_topic=CustomMemoryTopic(
label="travel_experiences",
description="""Memorable travel experiences including:
- Places visited and impressions
- Favorite restaurants, cafes, and food experiences
- Preferred accommodation types and locations
- Activities enjoyed (museums, hiking, beaches, etc.)
- Travel companions and social preferences
- Photos and videos from trips with location context""",
)
),
MemoryTopic(
custom_memory_topic=CustomMemoryTopic(
label="travel_preferences",
description="""Travel style and preferences:
- Budget preferences (luxury, mid-range, budget)
- Transportation preferences (flying, trains, driving)
- Trip duration preferences
- Season and weather preferences
- Cultural interests and language abilities
- Dietary restrictions and food preferences""",
)
),
MemoryTopic(
custom_memory_topic=CustomMemoryTopic(
label="travel_logistics",
description="""Practical travel information:
- Passport and visa information
- Frequent flyer numbers and hotel loyalty programs
- Emergency contacts
- Medical considerations and insurance
- Packing preferences and essentials
- Time zone preferences and jet lag strategies""",
)
),
]
ค้นหาความคิดเห็น # TODO: Configure Memory Bank Customization
แทนที่ทั้งบรรทัดนี้ด้วยโค้ดต่อไปนี้
memory_bank_config = {
"customization_configs": [
{
"memory_topics": travel_topics,
}
],
"similarity_search_config": {
"embedding_model": f"projects/{PROJECT_ID}/locations/{LOCATION}/publishers/google/models/gemini-embedding-001"
},
"generation_config": {
"model": f"projects/{PROJECT_ID}/locations/{LOCATION}/publishers/google/models/gemini-2.5-flash"
},
}
ขั้นตอนที่ 2: การนำเข้าข้อมูลทั่วโลก
ใน test_trip_planner เราจะส่ง
- ข้อความ ("สวัสดี")
- รูปภาพ (จุดสังเกต)
- วิดีโอ (ทะเลเมดิเตอร์เรเนียน)
- คลิปเสียง (ข้อความเสียงเกี่ยวกับกาเอตา)
ค้นหาความคิดเห็น # TODO create session service and memory service ภายในฟังก์ชัน 6_multimodal_agent/main.py
แทนที่ทั้งบรรทัดนี้ด้วยโค้ดต่อไปนี้
session_service = VertexAiSessionService(
project=PROJECT_ID, location=LOCATION, agent_engine_id=agent_engine_id
)
memory_service = VertexAiMemoryBankService(
project=PROJECT_ID, location=LOCATION, agent_engine_id=agent_engine_id
)
👉 ในไฟล์เดียวกัน 06_multimodal_agent/main.py ให้ค้นหาความคิดเห็น # TODO: create memory from session
แทนที่ทั้งบรรทัดนี้ด้วยโค้ดต่อไปนี้
await memory_service.add_session_to_memory(final_session_state)
นี่คือเส้นมหัศจรรย์ โดยจะส่งสื่อสมบูรณ์ทั้งหมดไปยัง Vertex AI ซึ่งจะประมวลผลและจัดทำดัชนี
ขั้นตอนที่ 3: การดึงข้อมูล
👉💻 ในเทอร์มินัล Cloud Shell ให้เปิดไฟล์ใน Cloud Shell Editor โดยเรียกใช้คำสั่งต่อไปนี้
cloudshell edit ~/memory_agent_starter/06_multimodal_agent/agent.py
ตัวแทนมี PreloadMemoryTool
tools=[PreloadMemoryTool(), budget_tool]
เมื่อเซสชันใหม่เริ่มต้นขึ้น เครื่องมือนี้จะค้นหาประสบการณ์ที่ผ่านมาที่เกี่ยวข้องในธนาคารความทรงจำโดยอัตโนมัติและแทรกลงในบริบท
ขั้นตอนที่ 4: เรียกใช้ Brain
👉💻 เรียกใช้สคริปต์ในเทอร์มินัล Cloud Shell (หมายเหตุ: ต้องมีโปรเจ็กต์ Google Cloud ที่เปิดใช้ Vertex AI)
cd ~/memory_agent_starter
uv run python ~/memory_agent_starter/06_multimodal_agent/main.py
ดูขั้นตอนการยืนยันสุดท้าย
"จากรูปภาพ วิดีโอ และเสียงที่ฉันแชร์กับคุณก่อนหน้านี้..."
ตัวแทนจะตอบกลับว่า
"คุณควรไปเที่ยวกาเอตา คุณเคยเปิดวิดีโอทะเลเมดิเตอร์เรเนียนให้ฉันดู และเปิดคลิปเสียงที่คุณบอกว่าชอบกาเอตา"
ซึ่งเชื่อมโยงจุดต่างๆ ในสื่อประเภทต่างๆ จากอดีต
ประเด็นสำคัญ
กฎข้อที่ 6 ของหน่วยความจำ: ใช้คลังหน่วยความจำของ Vertex AI เพื่อประสบการณ์การใช้งานหน่วยความจำขั้นสุด โดยจะรวมข้อความ รูปภาพ และวิดีโอไว้ในสมองเดียวที่ค้นหาได้
9. บทสรุป
คุณได้เดินทางจากปลาทองที่ขี้หลงขี้ลืมไปจนถึงช้างที่ใช้หลายรูปแบบ
คุณสร้าง | ความสามารถ |
ตัวแทนเซสชัน | หน่วยความจำในการสนทนาระยะสั้น |
Multi-Agent | ความทรงจำของทีมที่แชร์ |
Persistent Agent | ประวัติระยะยาว |
Stateful Agent | หน่วยความจำแบบไดนามิกที่อัปเดตตัวเอง |
Profile Agent | หน่วยความจำของ Structured Data |
เอเจนต์มัลติโมดัล | หน่วยความจำประสาทสัมผัสคล้ายมนุษย์ |
ความไว้วางใจสร้างขึ้นจากความทรงจำ การใช้รูปแบบเหล่านี้จะช่วยให้คุณสร้างเอเจนต์ที่คำนึงถึงเวลาและประวัติของผู้ใช้ ซึ่งจะนำไปสู่การโต้ตอบที่มีประสิทธิภาพและลึกซึ้งยิ่งขึ้น
เริ่มสร้างเอเจนต์ที่ปรับเปลี่ยนในแบบของคุณได้เลยวันนี้