1. ภาพรวม
บรรทัดแรกของ ADK 2 คือรูปแบบการจัดระเบียบ 3 รูปแบบ Codelab นี้จะสอนทั้ง 3 อย่างโดยการสร้างแอป 1 แอป ซึ่งก็คือโค้ชวันแข่งมาราธอน โดยจะสอนทีละขั้นตอน แต่ละระดับจะตอบคำถามเดียว เพิ่มแนวคิด 1 อย่าง และทำงานด้วยตัวเอง
สิ่งที่คุณจะได้เรียนรู้
- เวิร์กโฟลว์กราฟ (เสาหลักที่ 1) - เมื่อคุณวาดโฟลว์ก่อนที่อินพุตจะมาถึง
- เอเจนต์ที่ทำงานร่วมกัน (เสาหลักที่ 2) - เมื่อคุณทราบทีม แต่คำขอเลือกชุดย่อย และโหมดการทำงานร่วมกันทั้ง 3 โหมด (
chat/task/single_turn) ซึ่งแต่ละโหมดจะทำงานแบบเรียลไทม์ - เวิร์กโฟลว์แบบไดนามิก (เสาหลักที่ 3) - เมื่อลักษณะของงานขึ้นอยู่กับอินพุต
- วิธีเลือก - แผนผังการตัดสินใจแบบคำถามเดียว และวิธีที่รูปแบบต่างๆ ประกอบกัน
เส้นเรื่อง
โครงสร้างที่ทราบ → ทีม / ชุดตัวแปรย่อยที่ทราบ → รูปร่างที่ไม่ทราบ → เลือกรูปร่างที่ถูกต้อง

สิ่งที่คุณจะสร้าง
แอปหนึ่งชื่อ Marathon Race Day Coach สร้างเลเวลที่เล่นได้ทีละเลเวล แต่ละเลเวลเป็นโมดูล Python ธรรมดาที่คุณเรียกใช้จากเทอร์มินัล และเมื่อถึง L5 คุณจะได้รับชิ้นส่วนทั้งหมดด้านล่าง
รูปภาพวาดจากโค้ดที่ทำงานอยู่ โดยเส้นทึบทุกเส้นอ่านมาจาก Workflow.graph.edges ซึ่งเป็นบทเรียนแรก นั่นคือ ส่วนที่วาดล่วงหน้าได้คือเสาหลักที่ 1 และส่วนที่วาดไม่ได้คือเหตุผลที่เสาหลักที่ 2 และ 3 มีอยู่

สิ่งที่คุณต้องมี
- บัญชี Google (สำหรับ Colab) - ไม่ต้องตั้งค่าในเครื่อง
- ประมาณ 50 นาที (L4 ทั้ง 2 ระดับเป็นระดับที่ใช้เวลานานที่สุด โปรดจัดสรรเวลาให้)
- วิธีใดวิธีหนึ่งในการเข้าถึงโมเดล Gemini เลือกเส้นทางของคุณ - คุณจะเรียกใช้ขั้นตอนการตั้งค่าหนึ่งขั้นตอนและข้ามขั้นตอนอื่นๆ
🎓 เวิร์กช็อป | 🏠 ข้อคิด | |
ผู้มีส่วนเกี่ยวข้อง | คุณกำลังเข้าร่วมเวิร์กช็อปแบบสดและผู้สอนได้ให้ลิงก์การอ้างสิทธิ์เครดิตแก่คุณ | ผู้ใช้รายอื่นทั้งหมด ซึ่งรวมถึงผู้เข้าร่วมเวิร์กช็อปในภายหลัง |
สิ่งที่คุณต้องมี | ลิงก์การอ้างสิทธิ์และบัญชี Google ที่สร้างโปรเจ็กต์ที่อยู่ในระบบคลาวด์ได้ | |
ทำงานบน | Vertex AI ในโปรเจ็กต์ที่เรียกเก็บเงินจากเครดิตเวิร์กช็อป | Google AI Studio |
ค่าใช้จ่าย | ครอบคลุมโดยเครดิต | ระดับฟรี |
ขั้นตอนการตั้งค่า | การตั้งค่าเวิร์กช็อป (ขั้นตอนถัดไป) | การตั้งค่าที่บ้าน (ขั้นตอนถัดไป) |
ทุกอย่างตั้งแต่ Prologue เป็นต้นไปจะเหมือนกันไม่ว่าจะใช้เลนใดก็ตาม โดยเลนจะกำหนดเฉพาะปลายทางของโมเดลที่ Notebook สื่อสารด้วย
2 วิธีในการติดตาม
ทุกขั้นตอนด้านล่างจะแมปกับเซลล์ 1 เซลล์ใน Colab Notebook และโฟลเดอร์ 1 โฟลเดอร์ในที่เก็บ GitHub เลือกอย่างใดอย่างหนึ่งต่อไปนี้
- ▶ Colab (แนะนำ): เปิด Notebook → เรียกใช้เซลล์จากบนลงล่าง
- 💻 ในเครื่อง:
git cloneที่เก็บ./setup_venv.shจากนั้นเรียกใช้แต่ละระดับเป็นโมดูล (python -m ...) หรือเรียกดูทั้งหมดด้วย./run.sh(adk web)
2. การตั้งค่าเวิร์กช็อป · อ้างสิทธิ์เครดิตและเปลี่ยนไปใช้ Vertex AI
คุณจะได้รับเครดิต Google Cloud ในเวิร์กช็อป คุณจะอ้างสิทธิ์ สร้างโปรเจ็กต์ที่เรียกเก็บเงินจากบัญชีนั้น และชี้ Notebook ไปที่ Vertex AI แทน AI Studio โดยเซลล์เดียวจะทำทุกอย่างหลังจากมีการอ้างสิทธิ์
1 · รับเครดิต (ประมาณ 1 นาที)
- เปิดลิงก์รับสิทธิ์ที่ผู้สอนแชร์ ดูเหมือนว่า
https://me.developers.google.com/benefits/claim/your-workshop-name - ลงชื่อเข้าใช้และไปที่หน้าดังกล่าวเพื่อยอมรับเครดิต
- จดบันทึกบัญชี Google ที่คุณใช้ ทุกขั้นตอนด้านล่างต้องเรียกใช้ในบัญชีเดียวกัน
2 · เปิด Notebook และติดตั้ง ADK 2 (~1 นาที)
คลิกเปิดใน Colab ▶ แล้วเรียกใช้โค้ดเซลล์แรก โดยจะตรึงเวอร์ชัน ADK 2 ที่แน่นอนซึ่ง Codelab นี้ได้รับการยืนยันแล้ว และพิมพ์ ✓ installed
3 · เรียกใช้เซลล์ "การตั้งค่าเวิร์กช็อป" (~3 นาที)
นี่คือเซลล์ที่มีชื่อว่า 🎓 เส้นทาง A · เวิร์กชอป เรียกใช้ แล้ว Colab จะขอให้คุณให้สิทธิ์ ให้เลือกบัญชี Google เดียวกันกับที่คุณเพิ่งอ้างสิทธิ์เครดิต แล้วอนุญาตการเข้าถึง
โดยจะทำ 4 อย่าง ได้แก่ สร้างโปรเจ็กต์ชื่อ adk-2-tutorial-XXXX ในเครดิตของคุณ เปิดใช้ Vertex AI API ในโปรเจ็กต์นั้น ตั้งค่าตัวแปรสภาพแวดล้อม 4 รายการที่เซลล์ทุกเซลล์ในภายหลังจะอ่าน และทำการเรียกทดสอบไปยัง Vertex และรอจนกว่าจะได้รับคำตอบ เพื่อให้การตั้งค่าเสร็จสมบูรณ์หรือบอกเหตุผลให้คุณทราบแทนที่จะล้มเหลวในภายหลังภายในระดับ
ผลลัพธ์ที่คาดไว้ - บรรทัดสุดท้ายคือสิ่งที่สำคัญ
Signed in as: you@example.com
...
Successfully created GCP project 'adk-2-tutorial-4817'.
Successfully linked 'adk-2-tutorial-4817' to billing account '01ABCD-...'.
waiting for Vertex AI to come up on the new project... (10s)
waiting for Vertex AI to come up on the new project... (20s)
✅ Vertex AI on adk-2-tutorial-4817 · us-central1 · gemini-2.5-flash — answered a test call
4 · ข้ามขั้นตอน "การตั้งค่าที่บ้าน"
อย่าเรียกใช้เซลล์คีย์ AI Studio เพราะจะเปลี่ยน Notebook กลับไปเป็น AI Studio และยกเลิกสิ่งที่คุณเพิ่งทำ (เซลล์จะป้องกันไม่ให้เกิดเหตุการณ์นี้และจะไม่ยอมรัน แต่การดำเนินการที่เรียบร้อยกว่าคือการข้ามไปเลย) ไปที่เซลล์บล็อกที่ใช้ร่วมกันได้โดยตรงจากที่นี่
5 · เรียกใช้เซลล์ "Shared building blocks"
ดำเนินการครั้งเดียว โดยจะกำหนดสคีมา Pydantic + สถานการณ์จำลองการวิ่งมาราธอนที่ทุกระดับตั้งแต่ L2 เป็นต้นไปจะนำกลับมาใช้ซ้ำ คุณจะเห็น✓ schemas + scenarios ready
หลังเวิร์กช็อป
เครดิตและโปรเจ็กต์ที่สร้างขึ้นจะหมดอายุ หากต้องการเรียกใช้ระดับเหล่านี้อีกครั้งโดยไม่มีค่าใช้จ่ายเมื่อเวิร์กช็อปสิ้นสุดลง ให้เรียกใช้ขั้นตอนการตั้งค่าสำหรับนำกลับไปใช้แทน ซึ่งเป็นคีย์ AI Studio ฟรี ไม่ต้องมีโปรเจ็กต์ที่อยู่ในระบบคลาวด์ และไม่มีการเรียกเก็บเงิน มีเพียงเซลล์เดียวเท่านั้นที่จะเปลี่ยนแปลง
หากต้องการล้างข้อมูลเร็วกว่านี้ ให้เปิด Cloud Console เลือก adk-2-tutorial-XXXX แล้วลบ ไม่มีส่วนใดใน Codelab นี้ที่สร้างทรัพยากรที่เรียกเก็บเงินได้
3. การตั้งค่าที่บ้าน · คีย์ API ของ AI Studio
ทุกอย่างในเส้นทางนี้ทำงานด้วยคีย์ API ของ Google AI Studio ฟรี โดยไม่ต้องมีโปรเจ็กต์ที่อยู่ในระบบคลาวด์ของ Google, ไม่มีการเรียกเก็บเงิน และไม่ต้องติดตั้งในเครื่อง ทั้งขั้นตอนนี้ใช้เวลาประมาณ 3 นาที
1 · เปิด Notebook
คลิกเปิดใน Colab ▶ คุณจะไปที่ Notebook ซึ่งมีคำแนะนำ Markdown จากนั้นจะมีเซลล์ที่เรียกใช้ได้ 1 เซลล์ต่อระดับ คุณเรียกใช้เซลล์จากบนลงล่าง โดยแต่ละเซลล์จะพิมพ์เอาต์พุตของตัวเองไว้ใต้เซลล์นั้น
2 · ติดตั้ง ADK 2 (~1 นาที)
เรียกใช้เซลล์โค้ดแรก ซึ่งจะตรึงเวอร์ชันที่แน่นอนที่ใช้ยืนยัน Codelab นี้
%pip install -q "google-adk==2.3.0" python-dotenv pydantic nest_asyncio
รอจนกว่าจะเสร็จสิ้น คุณจะเห็น ✓ installed (การติดตั้งจะใช้เวลาประมาณ 30-60 วินาทีในครั้งแรก และจะแคชไว้หลังจากนั้น)
3 · รับคีย์ Gemini API จาก AI Studio (~1 นาที)
- เปิด aistudio.google.com/app/apikey ในแท็บเบราว์เซอร์ใหม่
- ลงชื่อเข้าใช้ด้วยบัญชี Google
- คลิกสร้างคีย์ API (ด้านขวาบน)
- เลือกโปรเจ็กต์ Google ที่มีอยู่หรือปล่อยให้ระบบสร้างโปรเจ็กต์
- คัดลอกคีย์ ซึ่งจะขึ้นต้นด้วย
AIza...และมีความยาวประมาณ 40 อักขระ
4 · เพิ่มคีย์ลงใน Colab (~1 นาที)
ตัวเลือก ก - Colab Secrets (แนะนำ; ระบบจะซ่อนคีย์ไว้):
- คลิกไอคอนกุญแจ 🔑 ในแถบด้านข้างซ้ายของ Colab
- คลิก + เพิ่มข้อมูลลับใหม่
- ตั้งค่าชื่อเป็น
GOOGLE_API_KEY - วางคีย์ลงในค่า
- สลับสิทธิ์เข้าถึง Notebook เป็นเปิด
ตัวเลือก B - วางเมื่อได้รับแจ้ง (รวดเร็ว): ข้ามข้อมูลลับ เมื่อเรียกใช้เซลล์ถัดไป ระบบจะแสดงข้อความแจ้งที่ซ่อนอยู่ 🔑 Enter your Google AI Studio API key: - วางแล้วกด Enter
5 · เรียกใช้เซลล์คีย์
โดยจะอ่านข้อมูลลับ (หรือกลับไปใช้พรอมต์ที่วาง) จากนั้นจะชี้ ADK ไปที่ AI Studio (ไม่ใช่ Vertex AI)
import os
# 🏠 TAKE-HOME ONLY — if you ran the Workshop setup cell, skip this one.
if os.environ.get("GOOGLE_GENAI_USE_VERTEXAI") == "True":
raise SystemExit("✋ You're set up on the workshop path (Vertex AI). Skip this cell.")
# Google AI Studio API key — add GOOGLE_API_KEY in the 🔑 Secrets panel (or paste when prompted).
try:
from google.colab import userdata
key = userdata.get("GOOGLE_API_KEY")
except Exception:
import getpass
key = getpass.getpass("Enter your Google AI Studio API key: ")
os.environ["GOOGLE_API_KEY"] = "".join(key.split()) # drop any stray whitespace/newlines
os.environ["GOOGLE_GENAI_USE_VERTEXAI"] = "False" # use AI Studio, not Vertex AI
print("✅ API key set — using Google AI Studio.")
ผลลัพธ์ที่คาดไว้: ✅ API key set — using Google AI Studio.
6 · เรียกใช้เซลล์ "Shared building blocks"
เรียกใช้เซลล์องค์ประกอบที่ใช้ร่วมกัน 1 ครั้ง โดยจะกำหนดสคีมา Pydantic + สถานการณ์จำลองการวิ่งมาราธอนที่ทุกระดับตั้งแต่ L2 เป็นต้นไปจะนำกลับมาใช้ซ้ำ คุณจะเห็น✓ schemas + scenarios ready
คุณพร้อมใช้งานแล้ว 🎽 ขอแวะไปดู L0 สักครู่ ซึ่งเป็นเวอร์ชันที่ทุกคนสร้างเป็นเวอร์ชันแรก
4. คำนำ · ทำไมจึงไม่ใช้พรอมต์ขนาดใหญ่เพียงพรอมต์เดียว
⚡ ก่อนที่จะเรียกใช้ ให้ระบุสิ่งหนึ่งที่ต้องจับตาดู นั่นคือ ตัวเลขแต่ละตัวมาจากไหน นั่นคือแบบฝึกหัดทั้งหมด ส่วนที่เหลือเป็นการตกแต่ง
ก่อนจะใช้บันได ให้ลองใช้สิ่งที่บันไดจะมาแทนที่ นั่นคือเอเจนต์ตัวเดียวที่พรอมต์รับประกันทุกอย่าง ไม่ว่าจะเป็นการดึงข้อมูลสภาพอากาศ วิเคราะห์หลักสูตร อ่านบันทึกการฝึก เส้นทางตามเงื่อนไข และแสดงแผน
สิ่งที่คุณจะเห็น: กลยุทธ์ที่มั่นใจ เจาะจง และจัดรูปแบบอย่างดี... ซึ่งมีตัวเลขที่แต่งขึ้น ในการทดสอบแบบเรียลไทม์ครั้งหนึ่ง ระบบเปิดด้วยข้อความ "ฉันดึงเมตริกสภาพอากาศของวันนี้มาแล้ว" และรายงานอุณหภูมิ 52°F ลม 9 ไมล์ต่อชั่วโมง และการวิเคราะห์บันทึกการฝึกที่ระบบไม่เคยเห็น ไม่มี API สภาพอากาศ ไม่มีข้อมูลหลักสูตร ไม่มีบันทึก ซึ่งการเรียกโมเดลแบบทึบแสงจะสร้างอินพุตหรือป้องกันไม่ให้อินพุตมีประโยชน์
นั่นคือโรค และมีอาการ 4 อย่างที่ควรตั้งชื่อ
- คุณไม่ควรเชื่อถือ เพราะข้อมูลนั้นสร้างขึ้นมาอย่างคล่องแคล่ว
- คุณทดสอบไม่ได้ - การกำหนดเส้นทางของขั้นตอนที่ 4 อยู่ภายในข้อความธรรมดา จึงไม่มี
ifสำหรับการทดสอบหน่วย - คุณไม่สามารถสลับขั้นตอนได้ เนื่องจากไม่มีรอยต่อที่ API สภาพอากาศจริงจะเสียบได้
- คุณต้องชำระเงินสำหรับทุกอย่างทุกครั้ง - 5 ขั้นตอน การเรียกใช้ครั้งใหญ่ 1 ครั้ง ไม่มีการแคชส่วนที่กำหนด
คงความรู้สึกนั้นไว้ ส่วนอีก 9 ระดับถัดไปจะนำขั้นตอนเหล่านั้นออกจากพรอมต์ทีละขั้นตอน ได้แก่ ฟังก์ชัน fetch (L1–L2a), เส้นทางของคำสั่ง if (L2b), ผู้เชี่ยวชาญแบ่งงาน (L3a–L3b) และโค้ดกำหนดรูปร่าง (L4a–L4b)

💻 ในพื้นที่: python -m shared.prologue
5. L0 · Your first ADK 2 agent

⚡ สรุป: เอเจนต์คือโมเดล + คำสั่ง + เครื่องมือที่อาจเรียกใช้ ส่วน Runner จะเป็นผู้ดำเนินการ หลังจากระดับนี้ไปแล้วก็จะมีเอเจนต์มากขึ้นเท่านั้น ซึ่งจัดเรียงในรูปแบบที่ดีขึ้น
คำถาม: คุณจะทำให้โมเดลตอบคำถามและใช้โค้ดจริงเมื่อต้องมีการคำนวณได้ไหม
ไอเดียเดียว 3 ส่วน
Agent— สิ่งที่ให้เหตุผล (โมเดล Gemini + คำสั่ง)Runner- สิ่งที่เรียกใช้เอเจนต์ภายในเซสชันและสตรีมเหตุการณ์- เครื่องมือ - ฟังก์ชัน Python ธรรมดา (
pace_splits) ที่โมเดลตัดสินใจเรียกใช้ ADK จะอ่านลายเซ็น + Docstring และส่งประกาศให้โมเดลโดยไม่ต้องเขียนสคีมา
หลังจากบทนำ นี่คือการซ่อมครั้งแรก: LLM ที่คำนวณอัตราการก้าวในหัวจะตอบผิดอย่างมีความสุข — pace_splits คือ Python ที่กำหนดได้ ดังนั้นตัวเลขในคำตอบจึงคำนวณแล้ว ไม่ใช่การด้นสด
▶ Colab: เรียกใช้L0เซลล์ · 📁 GitHub: L0_first_agent/ · 💻 ในเครื่อง: python -m L0_first_agent.agent

def pace_splits(target_finish: str) -> dict:
"""Convert a goal time like '3:30:00' into exact per-mile / per-km paces."""
... # deterministic Python — no LLM
pace_coach = Agent(
name="pace_coach", model=MODEL,
tools=[pace_splits], # the model may call it; ADK reads the signature
instruction="You are a friendly, concise marathon coach. ... If the runner "
"mentions a goal time, call pace_splits — never do arithmetic yourself.",
)
runner = Runner(node=pace_coach, session_service=InMemorySessionService(), auto_create_session=True)
async for event in runner.run_async(user_id="u1", session_id="s1", new_message=msg):
... # events carry the model's text
🔍 เครื่องหมาย: Agent(...) · tools=[pace_splits] · Runner(...) และในเอาต์พุต บรรทัด 🔧 คือโมเดลที่ตัดสินใจเรียกใช้โค้ดของคุณระหว่างตอบ
สิ่งที่คุณจะเห็นมีดังนี้
🔧 model called tool → pace_splits({'target_finish': '3:30:00'})
🔧 tool returned → {'per_mile': '8:00', 'per_km': '4:58', ...}
🧠 Coach: To finish in 3:30:00, you need an average pace of 8:00 per mile...
บรรทัด 🔧 คือบทเรียน: ในระหว่างการตอบ โมเดลเลือกที่จะเรียกฟังก์ชันของคุณ และ 8:00/mile ที่แน่นอนในการตอบกลับมาจากโค้ดของคุณ ไม่ใช่จากสถิติตัวอย่าง
❓ คุณอาจสงสัยว่า โมเดลจะเรียกใช้เครื่องมือเสมอไหม ไม่ ระบบจะตัดสินใจตามคำถาม ลองถามอะไรที่ไม่มีตัวเลข แล้วบรรทัด 🔧 จะหายไป (สนามเด็กเล่นจะให้คุณลองทำแบบนี้)
👀 อ่าน: pace_splits (ฟังก์ชันธรรมดา) และบรรทัด tools=[pace_splits] · ▶ เรียกใช้ · ✏️ เปลี่ยน: ถามคำถามทั่วไป (ไม่มีเวลาเป้าหมาย) — สังเกตว่าเส้น 🔧 หายไป: โมเดลจะตัดสินใจเมื่อควรเรียกใช้เครื่องมือ จากนั้นเขียน instruction ใหม่และเรียกใช้ซ้ำ โดยคำสั่งคือส่วนที่เหลือของโปรแกรม
6. L1 · เวิร์กโฟลว์แรกของคุณ

⚡ สรุป: ฟังก์ชันธรรมดาและเอเจนต์ LLM เป็นโหนดประเภทเดียวกัน งานที่คาดการณ์ได้ → ฟังก์ชัน (0 LLM, กำหนดได้); การให้เหตุผล → เอเจนต์
คำถาม: คุณจะผสมโค้ดธรรมดาและ LLM ในโฟลว์เดียวได้อย่างไรโดยไม่ต้องจ่ายค่าการเรียกโมเดลในส่วนที่เป็นเพียงโค้ด
แนวคิดเดียว: ใน Workflow ทั้งฟังก์ชัน Python ธรรมดาและเอเจนต์ LLM เป็นเพียงโหนดในรายการ edges เดียวกัน
START ──► fetch_conditions (function, 0 LLM) ──► advise (agent, 1 LLM)
▶ Colab: เรียกใช้L1เซลล์ · 📁 GitHub: L1_graph_basics/ · 💻 ในเครื่อง: python -m L1_graph_basics.workflow

โหนดฟังก์ชันจะพิมพ์ข้อมูลที่สร้างขึ้น (ไม่มีการเรียกโมเดล) จากนั้นเอเจนต์จะให้คำแนะนำที่อ้างอิงถึงอุณหภูมิและลมจริงที่ได้รับ
def fetch_conditions(node_input): # function node — 0 LLM
return Event(output=Conditions(temp_f=78, wind_mph=12, conditions="sunny").model_dump())
advise = Agent(name="advise", model=MODEL, mode="single_turn",
input_schema=Conditions, instruction="...give pacing + gear advice...")
workflow = Workflow(edges=[(START, fetch_conditions, advise)])
🔍 เครื่องหมาย: ทูเพิลขอบ 1 รายการ — (START, fetch_conditions, advise) — ที่มีฟังก์ชัน Python เปล่าๆ อยู่ตรงกลาง และ input_schema= ที่ตรวจสอบการส่งต่อ
สิ่งที่เปลี่ยนแปลงจาก L0: Workflow(edges=[...]), START (ที่ป้อนข้อมูล), โหนดฟังก์ชันที่แสดงผล Event(output=...) และ input_schema=Conditions เพื่อให้เอาต์พุตของฟังก์ชันได้รับการตรวจสอบกับสคีมานั้นก่อนที่ตัวแทนจะเห็น (เป็นข้อความ JSON - input_schema ตรวจสอบขอบเขต ไม่ได้ส่งออบเจ็กต์ Python ให้ตัวแทน)
❓ คุณอาจสงสัยว่า ลำดับฟังก์ชันแล้วเอเจนต์เป็นลำดับที่จำเป็นไหม ไม่ คุณจะใช้คำสั่งซื้อใดก็ได้ ผสมกันอย่างไรก็ได้ และใช้กี่ครั้งก็ได้ advise ทำงานเป็นอันดับ 2 เนื่องจากต้องใช้ข้อมูลของ fetch_conditions บทเรียนคือการจัดลำดับชั้น ไม่ใช่ลำดับ
👀 อ่าน: fetch_conditions แสดงข้อมูลโดยไม่มีการเรียกโมเดล advise มี input_schema=Conditions · ▶ เรียกใช้ · ✏️ เปลี่ยน: ตั้งค่า temp_f=30 ในฟังก์ชันและเรียกใช้ซ้ำ - คำแนะนำจะเปลี่ยนไป และฟังก์ชันยังคงมีค่าใช้จ่ายเป็น 0 LLM Call
7. L2a · Parallel fan-out + JoinNode (Pillar 1a)

⚡ สรุป: แยกย่อยแบบขนาน (ฟรี) รอทั้งหมด จัดกลุ่ม ส่งภาพรวมทั้งหมดให้ตัวแทน 1 คน
คำถาม: คุณวาดโฟลว์ก่อนที่อินพุตจะมาถึงได้ เริ่มต้นด้วยโครงร่าง: รวบรวมข้อมูลแบบคู่ขนาน จัดกลุ่มข้อมูล และส่งต่อให้ตัวแทน 1 คน
รูปร่าง:
START ──► fetch_weather ──┐
START ──► analyze_course ─┼─► JoinNode ─► strategy (1 agent)
START ──► pull_fitness ───┘ (bundles)
▶ Colab: เรียกใช้L2aเซลล์ · 📁 GitHub: L2a_parallel_join/ · 💻 ในเครื่อง: python -m L2a_parallel_join.workflow

🔍 เครื่องหมาย: ขอบ 3 ด้านที่เริ่มต้นที่ START ซึ่งก็คือการแผ่ออก และ JoinNode ซึ่งเป็นจุดบรรจบ
- การดึงข้อมูลทั้ง 3 รายการคือฟังก์ชัน ซึ่งจะทำงานแบบขนาน โดยไม่มีการเรียก LLM
JoinNodeจะรอทั้ง 3 รายการและรวมไว้ในเพย์โหลดที่พิมพ์ (BundledRunData) รายการเดียว โดยมีชื่อฟังก์ชันเป็นคีย์strategyเอเจนต์อ่านแพ็กเกจและเขียนRaceStrategy
สิ่งที่คุณจะเห็น: การดึงข้อมูลแต่ละครั้งจะพิมพ์การประทับเวลา started / finished ทั้ง 3 รายการจะเริ่มที่ 0.0 วินาที และแฟนเอาต์ (Fan-Out) จะสิ้นสุดที่ 2.0 วินาที ซึ่งเป็นการดึงข้อมูลที่ช้าที่สุด ไม่ใช่ 4.5 วินาทีตามระยะเวลาที่รวมกัน ความทับซ้อนนั้นคือความขนาน (เวลาจริงทั้งหมดที่พิมพ์ที่ส่วนท้ายคือประมาณ 8 วินาที เนื่องจากมีการเรียก LLM ของเอเจนต์กลยุทธ์ด้วย โปรดอ่านการประทับเวลาการดึงข้อมูลสำหรับการอ้างสิทธิ์แบบคู่ขนาน ไม่ใช่เวลาทั้งหมด)
💡 การเรียกกลับจาก Prologue: เมกาพรอมต์ประดิษฐ์สภาพอากาศขึ้นมา ในที่นี้ อุณหภูมิมาจากฟังก์ชันการดึงข้อมูล ซึ่งเป็นโค้ดจริงและรอยต่อจริง สลับพจนานุกรมที่กำหนดไว้ล่วงหน้าเป็น API สภาพอากาศจริง แล้วจะไม่มีการเปลี่ยนแปลงอื่นๆ
❓ คุณอาจสงสัยว่า ต้องใช้เงินเท่าไหร่
JoinNode
ฉันต้องเข้าใจอะไรบ้าง กล่าวโดยย่อคือ ฟังก์ชันนี้จะรอจนกว่าทุกกิ่งแบบขนานจะเสร็จสิ้น จากนั้นจะแพ็กเอาต์พุตเป็นพจนานุกรมเดียวที่มีคีย์เป็นชื่อของฟังก์ชันต้นทาง และจะไม่คำนวณอะไรด้วยตัวเอง พจนานุกรมดังกล่าวเป็นเหตุผลที่ทำให้เราเตอร์ของ L2b เขียน node_input["fetch_weather"]["temp_f"] ได้
👀 อ่าน: ขอบ 3 ขอบแผ่ออกมาจาก START JoinNode จะรวมขอบเหล่านั้นไว้สำหรับเอเจนต์ 1 คน · ▶ เรียกใช้และอ่านการประทับเวลา ไม่ใช่เวลาทั้งหมด · ✏️ การเปลี่ยนแปลง: ทำให้การดึงข้อมูลครั้งเดียวเป็นแบบสลีป 3.0 — คาดการณ์เวลาสิ้นสุดแฟนเอาต์ใหม่ก่อน แล้วจึงยืนยัน
8. L2b · เพิ่มเราเตอร์ที่กำหนดได้ (เสาหลัก 1b)

⚡ สรุป: L2a ที่ไม่มีการเปลี่ยนแปลง + if ธรรมดาจะกำหนดว่าเอเจนต์ตัวใดทำงาน การแยกสาขาโดยไม่ต้องถามโมเดล
คำถาม: แผนควรแตกต่างกันสำหรับสภาพอากาศร้อนและเย็น คุณจะแยกสาขาโดยไม่ขอให้โมเดลตัดสินใจได้อย่างไร
รูปร่าง (L2a + เราเตอร์):
... JoinNode ─► route_by_weather ─► hot_strategy
(if-statement) ─► normal_strategy
─► cold_strategy
▶ Colab: เรียกใช้L2bเซลล์ — ลอง run("NORMAL") / run("COLD") · 📁 GitHub: L2b_router/ · 💻 ในเครื่อง: python -m L2b_router.workflow COLD

def route_by_weather(node_input): # an if-statement, 0 LLM
temp = node_input["fetch_weather"]["temp_f"]
route = "HOT" if temp >= 70 else "COLD" if temp <= 40 else "NORMAL"
return Event(output=node_input, route=route)
(route_by_weather, {"HOT": hot_strategy, "NORMAL": normal_strategy, "COLD": cold_strategy})
🔍 เครื่องหมาย: Event(output=..., route=...) — โหนดฟังก์ชันการตั้งชื่อเส้นทาง — และขอบ dict {"HOT": ..., "NORMAL": ..., "COLD": ...} ที่แมปชื่อกับโหนด
ข้อควรจำ - งาน 3 ประเภท บ้าน 3 หลัง
- งานที่คาดการณ์ได้ → ฟังก์ชัน (การดึงข้อมูลแบบขนาน 3 รายการ)
- กฎที่ชัดเจน → การกำหนดเส้นทางที่ชัดเจน (
route_by_weatherเป็นคำสั่งifไม่ใช่การตัดสินใจของโมเดล) - การให้เหตุผล → โมเดล (เอเจนต์กลยุทธ์หนึ่งเดียวที่ทำงาน)
สิ่งที่คุณจะเห็น: temp=78F -> route=HOT จากนั้นจะเป็น RaceStrategy ที่มีโครงสร้าง ค่าใช้จ่ายสุทธิ: การเรียกใช้ LLM 1 ครั้ง
⚠️ หากเพิ่ม Branch ที่ 4 ให้ระบุรายการ DEFAULT_ROUTE ใน route-dict ด้วย เส้นทางที่พจนานุกรมไม่ตรงกันไม่ใช่ข้อผิดพลาด แต่เป็นเพียงการสิ้นสุดของกิ่งก้าน และโปรแกรมจะออกด้วย 0 โดยไม่มีเอาต์พุต ซึ่งเป็นจุดสิ้นสุดที่สับสนในการแก้ไขข้อบกพร่อง
❓ คุณอาจสงสัยว่า L2b คือ L2a บวกเราเตอร์ใช่ไหม ใช่ การดึงข้อมูลและการรวมยังคงเหมือนเดิม และยังคงเป็นการเรียกใช้ LLM 1 ครั้ง สิ่งที่เปลี่ยนแปลง: "ตัวแทนคนเดิมเสมอ" เปลี่ยนเป็น "1 ใน 3 คนที่ระบบเลือกจากข้อมูล"
👀 อ่าน: route_by_weather — เราเตอร์เป็นifคำสั่ง ไม่ใช่ตัวแทน · ▶ วิ่ง run("COLD") ด้วย · ✏️ เปลี่ยน: เพิ่มสาขา WINDY ที่มีตัวแทนรายที่ 4 และอ่านคำเตือน DEFAULT_ROUTE ด้านบนก่อนดำเนินการ
9. L3a · Collaborative agents: one flag, two worlds — Pillar 2

⚡ สรุป: ทีมเดียวกัน Flag เดียว chat จะส่งการสนทนาทั้งหมดไปยังผู้เชี่ยวชาญคนเดียวและไม่กลับมาอีก single_turn จะเปลี่ยนผู้เชี่ยวชาญแต่ละคนให้เป็นเครื่องมือ ซึ่งเป็นชุดย่อยแบบคู่ขนาน การส่งคืนอัตโนมัติ และการสังเคราะห์ครั้งเดียว
คำถาม: คุณรู้จักทีม แต่คำขอจะกำหนดว่าสมาชิกคนใดควรตอบ คุณจะอนุญาตให้ LLM เลือกชุดย่อยและเรียกใช้พร้อมกันได้อย่างไร
บทบาท: ผู้ประสานงานผู้เชี่ยวชาญ 6 คน (การแพทย์ สภาพอากาศ การกำหนดเวลา อุปกรณ์ โภชนาการ และสุขภาพจิต) ระดับนี้จะให้ทีมเดิมทำงาน 2 ครั้ง โดยใช้พรอมต์ผู้ประสานงานและผู้เชี่ยวชาญ 6 คนชุดเดิม ความแตกต่างเพียงอย่างเดียวคือมีค่าสถานะ 1 รายการในตัวแทนย่อย ความแตกต่างคือบทเรียน
▶ Colab: เรียกใช้L3aเซลล์ · 📁 GitHub: L3a_collaborative/ · 💻 ในเครื่อง: python -m L3a_collaborative.concierge --mode chat "What about fueling?"

🔍 เครื่องหมาย: mode="single_turn" ในโรงงาน และในเอาต์พุต TRANSFER → (บีตที่ 1) เทียบกับข้อความ DISPATCH → หลายบรรทัดที่แชร์การประทับเวลาเดียวกัน (บีตที่ 2)
Beat 1 · Run the default first — and watch it fail the job
หากไม่มีmode=ข้อตกลงเป็นลายลักษณ์อักษรchat Agent ย่อยจะใช้ค่าเริ่มต้นเป็น สิ่งที่คุณจะเห็น
TRANSFER → nutrition_specialist (transfer_to_agent — the only tool chat subagents provide)
Final speaker: nutrition_specialist
ผู้ประสานงานไม่มีเครื่องมือมอบหมายงาน ซึ่งแชทของตัวแทนย่อยจะให้เพียง transfer_to_agent ซึ่งเป็นการส่งต่อการสนทนาทั้งหมดแบบต่อเนื่องไปยังผู้เชี่ยวชาญคนเดียว ผู้เชี่ยวชาญจะตอบคำถามผู้ใช้โดยตรง และการดำเนินการจะสิ้นสุดลง ไม่มีการจัดส่งพร้อมกัน ไม่รับคืนสินค้า ไม่มีการสังเคราะห์ ถามคำถามกว้างๆ แล้วยิ่งแย่ลงไปอีก: ผู้เชี่ยวชาญ 6 คน โอนสาย 1 ครั้ง
นี่ไม่ใช่ข้อบกพร่อง แต่เป็นโหมดแชทที่ทำงานของมัน การสนทนาเป็นของผู้ที่ถือการสนทนานั้นจนกว่าจะมีผู้โอนการสนทนาอย่างชัดเจน เหมาะสำหรับผู้ช่วยแบบปลายเปิด แต่ไม่เหมาะสำหรับขั้นตอนในไปป์ไลน์
Beat 2 · One flag, two worlds
ความแตกต่างเพียงอย่างเดียวคือ mode="single_turn" ของผู้เชี่ยวชาญแต่ละคน คำถามเดียวกัน เรียกใช้อีกครั้ง
[t= 7.8s] DISPATCH → medical_specialist ← same timestamp =
[t= 7.8s] DISPATCH → weather_specialist one turn, many calls
[t=14.5s] ↩ medical_specialist replied ← replies land inside
[t=14.5s] ↩ weather_specialist replied one short window
🧠 Concierge (synthesized): <one answer>
ตอนนี้ ADK จะแทรกเครื่องมือมอบสิทธิ์ 1 รายการต่อผู้เชี่ยวชาญ 1 คน โดยตั้งชื่อตามตัวแทนย่อย ซึ่งอธิบายโดยdescription= (ข้อความนั้นคือสิ่งที่ผู้ประสานงานอ่านเมื่อเลือกชุดย่อย ข้ามข้อความนั้นแล้วคุณจะกำหนดเส้นทางตามชื่อเพียงอย่างเดียว) โดยผู้ประสานงานจะส่งการเรียกหลายครั้งในรอบเดียว ADK จะเรียกใช้การเรียกเหล่านั้นแบบขนาน แต่ละการเรียกจะส่งคืนผลลัพธ์โดยอัตโนมัติ และผู้ประสานงานจะสังเคราะห์ผลลัพธ์
คำถาม | ผู้เชี่ยวชาญที่ยิง |
"แล้วการเติมเชื้อเพลิงล่ะ" | โภชนาการเท่านั้น |
"ฉันปวดเข่าที่กิโลเมตรที่ 29" | การแพทย์เท่านั้น |
"วันนี้ฉันควรแข่งไหม" | การแพทย์ + สภาพอากาศ + การก้าว |
"มีอะไรที่ฉันควรเป็นกังวลไหม" | all 6 |
เหตุผลที่ผู้เชี่ยวชาญแต่ละคนได้รับสรุปข้อมูลทั้งหมด: single_turn เอเจนต์ย่อยแต่ละรายจะทำงานในกิ่งเซสชันที่แยกกัน ซึ่งไม่สามารถดูการสนทนาหรือเอเจนต์ย่อยอื่นๆ ได้ ไม่มีการส่งต่อโดยอัตโนมัติ ผู้ประสานงานต้องส่งต่อทั้ง SpecialistInput (คำถาม + กลยุทธ์ + ข้อมูลผู้วิ่ง) แยกกันไปยังการเรียกใช้แบบคู่ขนานทุกครั้ง
💡 ADK 2 มีที่อยู่โดยตรงสำหรับฟีเจอร์นี้: LLM จะเลือกชุดย่อยต่อคำขอและเรียกใช้แบบคู่ขนาน ซึ่งประกาศผ่าน sub_agents + mode="single_turn" คุณสามารถประกอบรูปร่างเดียวกันใน 1.x ได้โดยการห่อผู้เชี่ยวชาญแต่ละคนใน AgentTool สิ่งที่เปลี่ยนแปลงคือตอนนี้เป็นประกาศแทนที่จะเป็นการเชื่อมต่อ (ParallelAgent คือ always-all และ transfer_to_agent คือ serial)
⚠️ ข้อควรระวัง 2 ประการที่ควรทราบ (1) โมเดลจะเลือกชุดย่อย ดังนั้นจึงมีความแน่นอนน้อยกว่าเราเตอร์ที่ฮาร์ดโค้ดของ L2 ซึ่งชุดย่อยที่แน่นอนอาจแตกต่างกันไปในแต่ละครั้งที่รัน (2) บางครั้งคุณจะเห็นError validating input: ...สำหรับผู้เชี่ยวชาญ 1 คน เอาต์พุตของผู้เชี่ยวชาญแทบจะไม่เคยเป็น output_schema เลย ทำให้ Gemini บังคับใช้ฝั่งเซิร์ฟเวอร์ ซึ่งก็คืออินพุต: ผู้ประสานงานต้องทำซ้ำทั้งSpecialistInputที่ซ้อนกันทุกประการสำหรับการเรียกแบบขนานทุกครั้ง และบางครั้งก็อาจทำผิดพลาด ADK จะแสดงข้อผิดพลาดเป็นผลลัพธ์ของเครื่องมือนั้น ตัวประสานจะกู้คืน และการสังเคราะห์จะยังคงดำเนินการต่อไป
❓ คุณอาจสงสัยว่า
chat
การมอบสิทธิ์สไตล์ 1.x เพียงอย่างเดียว ซึ่งก็คือตัวแทน 1 รายในแต่ละครั้งใช่ไหม โดยพื้นฐานแล้วใช่ ซึ่งเป็นลักษณะการทำงานเริ่มต้นของเวอร์ชัน 1.x แต่ตอนนี้มีชื่อแล้ว ความแตกต่างของ single_turn มี 3 มิติ ได้แก่ สิ่งที่ผู้ประสานงานถือครอง (transfer_to_agent 1 รายการเทียบกับเครื่องมือ 1 รายการต่อผู้เชี่ยวชาญ) · จำนวนผู้ที่ทำงานได้ (1 คนที่เป็นเจ้าของบทสนทนาเทียบกับ N คนที่ทำงานพร้อมกัน) · การควบคุมจะกลับมาหรือไม่ (ไม่เลยเทียบกับกลับมาโดยอัตโนมัติพร้อมผลลัพธ์) และเกี่ยวกับโค้ด: Branch if mode == ของ Factory มีอยู่เพียงเพื่อให้ทีมหนึ่งสามารถสร้างได้ทั้ง 2 วิธีเพื่อเปรียบเทียบกัน นั่นคือ APP จริงจะฮาร์ดโค้ดโหมดหนึ่งและ if จะหายไป
👀 อ่าน: _specialist โรงงาน — พารามิเตอร์ mode คือระดับทั้งหมด · ▶ เรียกใช้ทั้ง 2 บีต · ✏️ เปลี่ยน: ถามว่า "เข่าของฉันเจ็บที่กิโลเมตรที่ 29" — คาดการณ์ชุดย่อยก่อน แล้วตรวจสอบบรรทัด DISPATCH
# The factory's mode parameter is THE variable this level teaches:
def _specialist(name, domain, focus, mode):
kwargs = {}
if mode == "single_turn": # the structured contract only makes sense for a TOOL
kwargs = dict(mode="single_turn",
input_schema=SpecialistInput, output_schema=SpecialistResponse)
return Agent(name=name, model=MODEL,
description=f"Marathon {domain} specialist. Consult for: {focus}.",
instruction=..., **kwargs)
race_concierge = Agent(name="race_concierge", model=MODEL,
sub_agents=[...six specialists...], # NOTE: no `mode` on the coordinator
instruction="...DECIDE which specialists are relevant... call them IN PARALLEL... SYNTHESIZE...")
10. L3b · โหมดงาน: การสนทนาที่มีจุดสิ้นสุด - เสาหลักที่ 2

⚡ สรุป: โหมดกลาง - พูดคุยกับผู้ใช้จนกว่าจะรวบรวมฟิลด์ได้ จากนั้นส่งคืนโดยอัตโนมัติพร้อมออบเจ็กต์ที่ตรวจสอบแล้ว
คำถาม: L3a เว้นช่องว่างไว้ chat เป็นเจ้าของการสนทนาทั้งหมด single_turn ไม่เคยพูดคุยกับผู้ใช้เลย แต่การรับข้อมูลจริงจะอยู่ระหว่างกลาง โดยมีข้อความว่า "คุยกับผู้ใช้จนกว่าคุณจะรวบรวมข้อมูล X ได้ จากนั้นกลับมาพร้อมกับออบเจ็กต์ที่ตรวจสอบแล้ว" โหมดนั้นคือโหมดใด
รูปร่าง:
race_desk (coordinator)
└─ gear_fitter (mode="task", output_schema=GearOrder)
▶ Colab: เรียกใช้L3bเซลล์ · 📁 GitHub: L3b_task_desk/ · 💻 ในเครื่อง: python -m L3b_task_desk.desk


🔍 เครื่องหมาย: mode="task" + output_schema= ในเอเจนต์เดียวกัน และในเอาต์พุตจะมี ⏸ หยุดชั่วคราวและfinish_taskโทร
สิ่งที่คุณจะเห็นมีดังนี้
━━ TURN 1 ━━ user: 'I need shoes for the marathon.'
race_desk → delegate: gear_fitter
gear_fitter: What is your shoe size?
⏸ The run ENDED — but nothing failed. This is a PAUSED task.
━━ TURN 2 ━━ user: 'Size 9, wide.' (same session → resumes the task)
gear_fitter → finish_task (payload validates as GearOrder)
race_desk: Your order ... in size 9 Wide has been confirmed.
โดยมี 3 สิ่งที่เกิดขึ้นซึ่งโหมด L3a ทำไม่ได้
- การเรียกใช้หยุดกลางคัน - งานหยุดชั่วคราว ไม่ใช่ค้างและไม่ใช่ล้มเหลว เอเจนต์ได้ถามคำถามเพื่อช่วยชี้แจงและยังคงเปิดงานไว้ (ใน
adk webคุณเพียงแค่พิมพ์คำตอบ สคริปต์ของ Harness จะบันทึกเป็นข้อความที่ 2 ในเซสชันเดียวกัน) - ข้อความถัดไปจะกลับไปใช้ Agent งานเดิม โดยไม่ต้องเปลี่ยนเส้นทางหรือมอบหมายงานใหม่ เซสชันจะทราบว่าใครรออยู่
finish_taskสิ้นสุด - เครื่องมือที่ ADK แทรกเนื่องจากmode="task"Agent ต้องเรียกใช้เพื่อดำเนินการให้เสร็จสมบูรณ์ และเพย์โหลดต้องตรวจสอบกับoutput_schemaการสนทนาที่มีเส้นชัยที่พิมพ์ จากนั้นการควบคุมจะกลับไปที่ผู้ประสานงานโดยอัตโนมัติ พร้อมแนบผลลัพธ์
กฎข้อเดียวในการเลือกโหมด
💡 "ผู้ใช้จำเป็นต้องพูดกับโมเดลไหม และต้องพูดจนถึงเมื่อไหร่" แชท = ตลอดไป · งาน = จนกว่าจะรวบรวมฟิลด์ได้ · การสนทนาแบบผลัดกันพูด = ไม่ต้องพูดเลย
โหมด | ต้องมีคนคอยตรวจสอบ | Parallel? | กลับไปที่โฟลเดอร์หลัก |
| การสนทนาแบบเต็ม | ไม่ | ด้วยตนเอง (ผ่านการโอน) |
| คำถามเพื่อขอข้อมูลเพิ่มเติมเท่านั้น | ไม่ | อัตโนมัติ (ผ่าน |
| ไม่มี | yes | อัตโนมัติ (พร้อมผลลัพธ์) |
modeจะใช้กับตัวแทนย่อยเท่านั้น ไม่ใช่ผู้ประสานงาน และโหนดเวิร์กโฟลว์จะตั้งค่าเริ่มต้นเป็น single_turn (ซึ่งเป็นเหตุผลที่ L1–L2b ไม่เคยเขียน) ในขณะที่ตัวแทนย่อยจะตั้งค่าเริ่มต้นเป็น chat (ซึ่งเป็นเหตุผลที่ L3a ต้องเขียน)
⚠️ หมายเหตุ 2 เวอร์ชันก่อนที่จะสร้างบนนี้: (1) task
ในฐานะโหนดกราฟแบบคงที่นั้นขึ้นอยู่กับเวอร์ชัน — ใน 2.0.0b1–2.3.0 (การปักหมุดของ Codelab นี้) Workflow(...) จะเพิ่มขึ้นเมื่อสร้าง ใช้สิ่งที่ระดับนี้ทำ (ผู้ประสานงานแชทที่มีตัวแทนย่อยของงาน) หรือส่งผ่าน ctx.run_node เปิดตัวในเวอร์ชัน 2.5.0 (2) "Task agents must be leaf agents" (no subagents of their own) is a documented ADK limitation — but a contract, not a runtime guard: neither 2.3.0 nor 2.5.0 will stop you. อย่าตีความว่าการไม่มีข้อผิดพลาดหมายถึงการได้รับอนุญาต
💡 เจาะลึก: taskเอเจนต์ที่ฝังอยู่ในเวิร์กโฟลว์กราฟ (รูปร่าง 2.5.0 ขึ้นไป) พร้อมการกำหนดเส้นทางที่สามารถวนการสนทนากลับเพื่อลองอีกครั้ง: ที่เก็บข้อมูลเสริม 22_agent_in_workflow · คู่มือโหมดเต็ม: docs/agent-modes.md
❓ คุณอาจสงสัยว่า
task
อะไรที่ทำให้ฉันเลือกซื้อจากคุณแทนที่จะซื้อจากอีก 2 ราย 3 สิ่งที่ต้องมี ได้แก่ การกลับอัตโนมัติ (แชทจะนำการสนทนาออกไปแทน) เส้นชัยที่พิมพ์ (เพย์โหลดของ finish_task ต้องตรวจสอบกับสคีมา คุณจะได้รับข้อมูลกลับ ไม่ใช่ข้อความถอดเสียง) และหยุดชั่วคราว/ดำเนินการต่อ (⏸ คืองานที่รอให้มนุษย์ดำเนินการ ไม่ใช่การหยุดทำงาน)
👀 อ่าน: gear_fitter — mode="task" + output_schema คือสัญญาฉบับเต็ม · ▶ เรียกใช้ · ✏️ เปลี่ยน: run_desk("I need a hydration vest", "2 liters, medium") — คำถามเพื่อขอข้อมูลเพิ่มเติมจะปรับเปลี่ยนไป แต่เส้นชัยจะยังคงเป็นข้อความที่พิมพ์ไว้
11. L4a · แฟนเอาต์ (Fan-Out) แบบขนานที่ปรับขนาดตามรันไทม์ (เสาหลัก 3a)

⚡ สรุป: โครงสร้างยังคงเป็น 3 ขั้นตอนแบบคงที่ โดยส่วนแบบไดนามิกจะซ่อนอยู่ภายในส่วนตรงกลาง ซึ่งความกว้างจะกำหนดโดยข้อมูลในขณะรันไทม์
⚠️ โปรดทราบ: นี่คือขั้นที่ยากที่สุดในบันได ระดับก่อนหน้ามี 44 บรรทัด แต่ระดับนี้มีประมาณ 120 บรรทัด ซึ่งประกอบด้วยเอเจนต์ 3 คนและโหนดเวิร์กโฟลว์ 2 โหนด โดยไม่มีการเว้นวรรค ใช้เวลาประมาณ 15 นาที และดูบรรทัด Read/Run/Change ที่ส่วนท้าย คุณไม่จำเป็นต้องอ่านทุกบรรทัดในการอ่านครั้งแรก
คำถาม: รูปร่างของผลงานขึ้นอยู่กับอินพุต คุณไม่สามารถวาดกราฟล่วงหน้าได้ เริ่มด้วยความกว้างของรันไทม์: ให้ LLM ตัดสินใจจำนวนคำถามย่อย
รูปร่าง (ระดับเดียว):
START ─► decompose ─► research_topic (parallel_worker) ─► synthesize
│ │ │
└──┴──┴─ (flat: no children yet)
คำถามปลายเปิดจะแยกย่อยเป็นคำถามย่อย N ข้อ โดย LLM จะเลือก N ในขณะรันไทม์ (3-7) จากนั้นจะค้นคว้าข้อมูลแต่ละข้อแบบคู่ขนาน แล้วสังเคราะห์เป็นสรุปเดียว
▶ Colab: เรียกใช้L4aเซลล์ · 📁 GitHub: L4a_flat_research/ · 💻 ในเครื่อง: python -m L4a_flat_research.deep_research

🔍 เครื่องหมาย — ไม่มี
dynamic=True
เปลี่ยน Dynamic เป็นวิธีเขียน ไม่ใช่การกำหนดค่า มีเครื่องหมาย 2 รายการเท่านั้น ได้แก่ @node(parallel_worker=True) (ใช้รายการขนาดรันไทม์ เรียกใช้ Worker 1 รายการต่อ 1 รายการ) และ ctx.run_node(...) (โหนดการจัดกำหนดการโค้ดโดยตรง) หากเห็นข้อความใดข้อความหนึ่ง แสดงว่าคุณอยู่ในโหมดไดนามิก
สิ่งที่คุณจะเห็น: ตัวแยกส่วนจะพิมพ์คำถามย่อย เช่น 5 คำถาม จากนั้นจะค้นคว้าข้อมูลแบบคู่ขนาน แล้วสรุปข้อมูล หมายเลขจะแตกต่างกันในทุกครั้งที่เรียกใช้ ซึ่งกราฟแบบคงที่ทำไม่ได้
มี 2 แฟล็กใน Worker ที่ควรทำความเข้าใจ
rerun_on_resume=Trueต้องระบุในโหนดที่เรียกใช้ctx.run_node— ADK จะเพิ่มValueErrorหากไม่มี เมื่อกลับมาทำงานอีกครั้ง จะต้องเรียกใช้โหนดการส่งอีกครั้งเพื่อสร้างองค์ประกอบย่อยที่สร้างขึ้นใหม่ เนื่องจากองค์ประกอบเหล่านั้นไม่ได้อยู่ในกราฟแบบคงที่retry_config=จะกำหนดขอบเขตว่าการดำเนินการนี้จะล้มเหลวได้อย่างไร Worker แบบขนานจะยกเลิกทุกรายการที่เกี่ยวข้องและยกเลิกทันทีหากมีรายการใดรายการหนึ่งล้มเหลว ดังนั้นหากไม่มีการลองใหม่ ข้อผิดพลาด 429 ชั่วคราวเพียงรายการเดียวจะทำให้การเรียกใช้ทั้งหมดถูกทิ้ง รวมถึงการเรียกใช้ทุกรายการที่ชำระเงินไปแล้ว การลองอีกครั้งจะไปที่โหนดภายในต่อรายการ ดังนั้นแต่ละสาขาจะลองอีกครั้งโดยอิสระ
❓ คุณอาจสงสัยว่า ADK "รู้" ได้อย่างไรว่านี่คือโฆษณาแบบไดนามิก ไม่จำเป็นต้องทำเช่นนั้น เนื่องจากไม่มีการประกาศที่ใด ตัวแยกส่วนจะสร้างรายการที่รันไทม์ ส่วน Worker แบบขนานจะปรับขนาดตัวเองตามสิ่งที่ได้รับ ความสามารถในการเปลี่ยนแปลงเป็นคุณสมบัติของโฟลว์ข้อมูลที่คุณเขียน ไม่ใช่โหมดที่คุณเปิด
👀 อ่าน: ธง 2 อันบน research_topic — parallel_worker และ rerun_on_resume · ▶ เรียกใช้ · ✏️ เปลี่ยน: สลับคำถามปลายเปิดของคุณเอง - N จะเปลี่ยนเนื่องจากอินพุตกำหนดความกว้าง
12. L4b · เพิ่มการเกิดแบบเรียกซ้ำ (เสาหลัก 3b)

⚡ สรุป: การเรียกซ้ำต้องเขียนขึ้นเอง ไม่ใช่มีมาให้ โดย Worker จะเรียกตัวเองผ่าน ctx.run_node ซึ่งเป็น Python ปกติ ดังนั้นจึงต้องเขียนเบรกด้วย ซึ่งเท่ากับ MAX_DEPTH
คำถาม: บางครั้งผลการวิจัยหนึ่งๆ ก็เผยให้เห็นหัวข้อย่อยที่เฉพาะเจาะจงซึ่งควรค่าแก่การตรวจสอบ คุณจะสร้างงานแบบคู่ขนานเพิ่มเติมในสาขาได้อย่างไร และจะจำกัดขอบเขตของงานได้อย่างไร
รูปร่าง (ตอนนี้เป็นแบบเรียกซ้ำ):
START ─► decompose ─► research_topic (parallel_worker, recursive) ─► synthesize
│ │ │
│ │ └─ research(q3) ─► maybe spawn children
│ └─── research(q2) ─► maybe spawn children
└────── research(q1) ─► maybe spawn children
▶ Colab: เรียกใช้L4bเซลล์ · 📁 GitHub: L4b_recursion/ · 💻 ในเครื่อง: python -m L4b_recursion.deep_research

@node(parallel_worker=True, rerun_on_resume=True)
async def research_topic(ctx, node_input):
finding = coerce(await ctx.run_node(research_agent, node_input=...), ResearchFinding)
if finding.needs_deeper and finding.deeper_questions and depth < MAX_DEPTH: # boundary in CODE
children = await ctx.run_node(research_topic, node_input=deeper) # recursive fan-out
yield Event(output={..., "children": children})
🔍 เครื่องหมาย: ctx.run_node(research_topic, ...) ภายใน research_topic เอง ซึ่งเป็นการอ้างอิงตนเองคือการเรียกซ้ำ และการ์ด depth < MAX_DEPTH ที่อยู่เหนือบรรทัดนั้น
สิ่งที่คุณจะเห็น: โหนดการวิจัยพิมพ์ spawning N deeper ซึ่งเป็นการเรียกซ้ำที่เกิดขึ้นแบบเรียลไทม์ จากนั้นจะเป็นรูปร่างต้นไม้รันไทม์ (เช่น 5 top-level + 10 recursive children) ต้นไม้จะแตกต่างกันในแต่ละครั้งที่เรียกใช้
⚠️ ก่อนที่จะเพิ่มค่า: เพดานจะเพิ่มขึ้นอย่างรวดเร็ว โดย MAX_DEPTH=3 จะใช้กรณีที่แย่ที่สุดจากการเรียกใช้ประมาณ 30 ครั้งเป็นการเรียกใช้ประมาณ 93 ครั้ง และในตอนท้ายของการเรียกใช้ คุณอาจเห็นcancelling N leftover tasksบรรทัดบันทึก: นั่นคือ ADK จะทำลายกลุ่มงานแบบขนานหลังจากที่ผลลัพธ์เสร็จสมบูรณ์แล้ว ไม่มีอันตราย และคุณอาจไม่เห็นข้อความนี้เลย ทั้งนี้ขึ้นอยู่กับการกำหนดค่าการบันทึก
❓ คุณอาจสงสัยว่า ค่าเริ่มต้นของไดนามิกไม่ใช่แบบเรียกซ้ำใช่ไหม ไม่ L4a เป็นไดนามิกอย่างเต็มที่โดยไม่มีการเรียกซ้ำเลย Dynamic จะให้เฉพาะโฟลว์การควบคุม Python ปกติแก่คุณเท่านั้น ส่วน L4b เลือกที่จะเขียนการเรียกซ้ำด้วย และเนื่องจากคุณเป็นผู้เขียนการเรียกซ้ำ คุณจึงต้องเขียนขอบเขตของฟังก์ชันนี้ด้วย ซึ่งเป็นจุดที่คำกล่าวที่ว่า "ให้ LLM กำหนดรูปแบบงานและคงขอบเขตไว้ในโค้ด" ไม่ใช่แค่สโลแกนอีกต่อไป
👀 อ่าน: the guard: if finding.needs_deeper and depth < MAX_DEPTH · ▶ เรียกใช้ · ✏️ เปลี่ยน: ตั้งค่า MAX_DEPTH = 1 และเรียกใช้ซ้ำ — ทรีจะแบนราบ (และค่าใช้จ่ายในการเรียกใช้จะถูกลง) ขอบเขตเป็นของคุณในโค้ด
13. L5 · คุณควรใช้รูปแบบใด

⚡ สรุป: แกนใดแกนหนึ่งจะกำหนดทุกอย่าง นั่นคือ ใครเป็นผู้เลือกขั้นตอนถัดไป ไม่ว่าจะเป็นกราฟที่คุณวาด LLM หรือโค้ดของคุณ
คุณสร้างทั้ง 3 อย่างแล้ว โมเดลนี้ทำให้รูปแบบมีประโยชน์ นั่นคือจับคู่รูปแบบกับรูปร่างของปัญหา
แกน: ใครเป็นคนตัดสินใจว่าจะเรียกใช้สิ่งใดต่อไป
เสา | ใครเป็นผู้กำหนดว่าควรเล่นอะไรต่อไป | ในตัว |
1 · กราฟ | กราฟที่คุณวาด | L2a / L2b |
2 · ทำงานร่วมกัน | LLM | L3a / L3b |
3 · ไดนามิก | โค้ด Python ของคุณในขณะรันไทม์ | L4a / L4b |
ขั้นตอนที่ 0: คุณจำเป็นต้องใช้กราฟไหม
ADK จัดส่งเอเจนต์เวิร์กโฟลว์ที่สร้างไว้ล่วงหน้า ซึ่งได้แก่ SequentialAgent, ParallelAgent, LoopAgent สำหรับเชนของเอเจนต์แบบธรรมดา คำตอบที่ถูกต้องเหล่านั้นมีราคาถูกที่สุดและไม่มีกราฟให้ประกอบ ข้ามไปเมื่อคุณต้องการการกำหนดเส้นทางที่ชัดเจน (เราเตอร์ของ L2b) การเข้าร่วม (JoinNode ของ L2a) หรือโหนดที่ไม่ได้เป็นเอเจนต์ (ฟังก์ชันธรรมดา ไม่มีการเรียก LLM) ซึ่งโดยปกติแล้วเหตุผลก็คือข้อสุดท้าย
Would a prebuilt SequentialAgent / ParallelAgent / LoopAgent do?
│
├─ YES ──────────────────────────────► use it; stop here
│
└─ NO — I need routing, a join, or non-agent nodes
│
Can you draw the workflow before the input arrives?
│
├─ YES ───────────────────────────► Pillar 1 · Graph workflow (L2a/L2b)
│
└─ NO
├─ Known team, request picks the subset? ─► Pillar 2 · Collaborative (L3a/L3b)
└─ Does the shape depend on the input? ──► Pillar 3 · Dynamic (L4a/L4b)

การเปรียบเทียบ 1.x กับ 2 อย่างตรงไปตรงมา
นี่ไม่ใช่ "2.0 ทำสิ่งที่ 1.x ทำไม่ได้" เพราะ 1.x ก็สร้างทุกอย่างได้ การเปลี่ยนแปลงคือ 2.0 จะให้แต่ละรูปร่างมีตำแหน่งที่ชัดเจนยิ่งขึ้น ดังนั้นโฟลว์การควบคุมที่รู้จักจะออกจากพรอมต์และกลายเป็นโครงสร้างที่คุณเห็นและทดสอบได้
รูปแบบ | ค่าใช้จ่าย 1.x | หน้าแรกของ ADK 2 |
กราฟ | การเรียกใช้ LLM 4 ครั้งในการบิลด์ทั่วไป โดยมีการกำหนดเส้นทางที่ซ่อนอยู่ในพรอมต์ | ฟังก์ชัน + โหนด Agent เป็นเพียร์ → 1 การเรียก |
การทำงานร่วมกัน | สร้างได้ผ่าน | ทีมที่ประกาศ: |
ไดนามิก | การเรียกซ้ำจะทำให้คุณออกจากเฟรมเวิร์ก |
|
ทั้งแอปและสิ่งที่กราฟแสดงให้คุณดูไม่ได้
ตอนนี้คุณได้สร้างทุกชิ้นส่วนด้านล่างแล้ว Workflow จะแสดงโครงสร้างที่ graph.edges ดังนั้นรูปภาพนี้จึงสร้างจากโค้ดแทนที่จะวาดด้วยมือ และสิ่งที่การตรวจสอบพบคือสรุปของห้องทดลองนี้
เสา | สิ่งที่ | ทำไม |
1 · กราฟ (L2b) | 10 ขอบ เส้นทาง และอื่นๆ | คุณวาดก่อนที่จะมีอินพุต |
2 · ทำงานร่วมกัน (L3a) | 0 ขอบ - เฉพาะ | LLM จะเลือกชุดย่อยต่อคำขอ |
3 · ไดนามิก (L4a/L4b) | ขอบ 3 ด้าน — เหมือนกันทั้ง 2 ด้าน | การเรียกซ้ำเขียนด้วย Python ไม่ได้เชื่อมต่อในกราฟ |
แถวสุดท้ายคือข้อพิสูจน์สำหรับคำถามที่ L4b ตอบ นั่นคือ L4a และ L4b มีกราฟเดียวกัน และมีเพียงกราฟเดียวที่เกิดซ้ำ
สิ่งที่คุณสร้างได้ในตอนนี้
รูปแบบแต่ละรูปแบบที่คุณเพิ่งเรียกใช้คือรูปร่างผลิตภัณฑ์จริง
คุณฝึก | ในธรรมชาติ | เริ่มจาก |
กราฟ + เราเตอร์ (L2a/L2b) | ไปป์ไลน์เอกสาร, ขั้นตอน ETL ที่มี LLM, ห่วงโซ่การตรวจสอบ/อนุมัติ, ชุดเครื่องมือประเมิน | L2b ของที่เก็บนี้ |
ผู้ประสานงาน + ทีม | ผู้ช่วยโคไพลอตด้านการสนับสนุนพร้อมทีมผู้เชี่ยวชาญ โต๊ะคัดกรอง และการตรวจสอบแบบหลายมุมมอง | โหมด marathon demo 2 |
ตัวแทน | แบบฟอร์มรับข้อมูล โฟลว์การจอง การเริ่มต้นใช้งาน KYC — "รวบรวมแล้วดำเนินการ" | |
ความกว้าง/ความลึกแบบไดนามิก (L4a/L4b) | เอเจนต์การวิจัย เครื่องมือสร้างรายงาน การตรวจสอบอินพุตที่ไม่ทราบขนาด | โหมดการสาธิตแบบมาราธอน 3 |
โดยจะประกอบด้วย
รูปแบบทั้ง 3 นี้ไม่ได้แยกออกจากกันโดยสิ้นเชิง โหนดกราฟสามารถเรียกใช้ผู้ประสานงานการทำงานร่วมกันได้ ส่วนผู้เชี่ยวชาญสามารถเปิดตัวเวิร์กโฟลว์แบบไดนามิกได้ เลือกรูปแบบที่เหมาะสมสำหรับส่วนของปัญหา ซึ่งจะช่วยให้คุณไม่ต้องเปลี่ยนระบบเอเจนต์ทุกระบบให้กลายเป็นพรอมต์ขนาดใหญ่

💡 ลองใช้ในเวิร์กโฟลว์ของคุณเอง: สคริปต์ที่วาดภาพนี้คือ scripts/graph_dump.py เพียงเล็งไปที่Workflow ระบบก็จะพิมพ์ขอบจริงออกมา ซึ่งเป็นแผนภาพโครงสร้างฟรีของทุกสิ่งที่คุณสร้าง
14. ขอแสดงความยินดี

คุณสร้างโค้ชวันแข่งมาราธอนและใช้รูปแบบการจัดระเบียบทั้ง 3 รูปแบบของ ADK 2
สิ่งที่คุณได้เรียนรู้
- คำนำ - เมกะพรอมต์ที่สร้างสภาพอากาศของตัวเอง: เหตุผลที่โครงสร้างมีอยู่
- L0–L1 —
Agent,Runner, เครื่องมือจริงที่โมเดลเลือกเรียกใช้ และWorkflowแรกของคุณ (โหนดฟังก์ชัน + โหนดตัวแทนเป็นเพียร์) - L2a / L2b — เวิร์กโฟลว์กราฟ: แฟนเอาต์ (Fan-Out) แบบขนาน +
JoinNodeจากนั้นการกำหนดเส้นทางเชิงกำหนด — การเรียก LLM 1 ครั้ง - L3a - เอเจนต์ที่ทำงานร่วมกัน: ทีมเดียวกันทำงานใน
chat(stranded) จากนั้นsingle_turn(parallel subset + synthesis) - 1 แฟล็ก 2 โลก - L3b —
taskโหมด: คำถามเพื่อชี้แจงที่หยุดชั่วคราว การกลับมาทำงานตามสคริปต์finish_taskการแสดงผลออบเจ็กต์ที่ผ่านการตรวจสอบแล้ว - L4a / L4b - เวิร์กโฟลว์แบบไดนามิก: ความกว้างของรันไทม์ (แฟนเอาต์ (Fan-Out)) จากนั้นความลึกของรันไทม์ (การเรียกซ้ำ) โดยมีขอบเขตในโค้ด
- L5 - แผนผังการตัดสินใจและวิธีที่รูปแบบต่างๆ ประกอบกัน
บรรทัดที่ควรเก็บไว้
ฟังก์ชันจะเตรียมบริบท ขอบเขตกำหนดเวิร์กโฟลว์ เราเตอร์จะเลือกเส้นทาง โมเดลจะเขียนคำตอบ
ปล่อยให้ LLM ช่วยสร้างสรรค์ผลงาน แต่กำหนดขอบเขตไว้ในโค้ด
จับคู่รูปแบบกับรูปร่างของปัญหา
ขั้นตอนถัดไป
- เรียกใช้แอปฉบับเต็มที่กลั่นกรองระดับเหล่านี้ออกมา ซึ่งก็คือ Marathon Race Day Coach ซึ่งเป็น FastAPI + SSE ที่สร้างขึ้นพร้อม UI ของเบราว์เซอร์ที่แสดงทั้ง 3 โหมดแบบเรียลไทม์: github.com/cuppibla/adk-2-marathon-demo
- ดูเพิ่มเติม: adk-workflows-compared — ตัวอย่างเวิร์กโฟลว์ ADK 2 อย่างเป็นทางการทั้ง 23 รายการ โดยแต่ละรายการมีพอร์ต 1.x และคำแนะนำเกี่ยวกับเวลาที่ควรใช้ เริ่มจาก
docs/three-pillars.mdจากนั้นดูส่วนที่ Codelab นี้ข้ามไป ได้แก่07_loop,17_request_input,22_agent_in_workflow - ระบุปัญหาของคุณเอง: ส่วนใดเป็นโครงสร้างที่ทราบ (L2), ทีมที่ทราบ (L3a/L3b), รูปแบบที่ไม่ทราบ (L4)
- ดูโค้ดได้ที่ github.com/cuppibla/adk2-tutorial
- เข้าร่วมเวิร์กช็อปมาใช่ไหม เครดิตและโปรเจ็กต์ที่สร้างขึ้นจะหมดอายุ หากต้องการเรียกใช้ระดับเหล่านี้อีกครั้งโดยไม่เสียค่าใช้จ่าย ให้ทำตามขั้นตอนการตั้งค่าที่บ้านแทน ซึ่งก็คือคีย์ AI Studio ฟรี ไม่ต้องมีโปรเจ็กต์ที่อยู่ในระบบคลาวด์ และไม่ต้องมีการเรียกเก็บเงิน การสลับเซลล์นั้นเป็นเพียงการเปลี่ยนแปลงเดียว