การจัดการเป็นกลุ่มของ ADK 2: เวิร์กโฟลว์แบบกราฟ การทำงานร่วมกัน และแบบไดนามิก

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 ที่สร้างโปรเจ็กต์ที่อยู่ในระบบคลาวด์ได้

คีย์ API ของ AI Studio ฟรี

ทำงานบน

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 นาที)

  1. เปิดลิงก์รับสิทธิ์ที่ผู้สอนแชร์ ดูเหมือนว่า https://me.developers.google.com/benefits/claim/your-workshop-name
  2. ลงชื่อเข้าใช้และไปที่หน้าดังกล่าวเพื่อยอมรับเครดิต
  3. จดบันทึกบัญชี 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 นาที)

  1. เปิด aistudio.google.com/app/apikey ในแท็บเบราว์เซอร์ใหม่
  2. ลงชื่อเข้าใช้ด้วยบัญชี Google
  3. คลิกสร้างคีย์ API (ด้านขวาบน)
  4. เลือกโปรเจ็กต์ Google ที่มีอยู่หรือปล่อยให้ระบบสร้างโปรเจ็กต์
  5. คัดลอกคีย์ ซึ่งจะขึ้นต้นด้วย AIza... และมีความยาวประมาณ 40 อักขระ

4 · เพิ่มคีย์ลงใน Colab (~1 นาที)

ตัวเลือก ก - Colab Secrets (แนะนำ; ระบบจะซ่อนคีย์ไว้):

  1. คลิกไอคอนกุญแจ 🔑 ในแถบด้านข้างซ้ายของ Colab
  2. คลิก + เพิ่มข้อมูลลับใหม่
  3. ตั้งค่าชื่อเป็น GOOGLE_API_KEY
  4. วางคีย์ลงในค่า
  5. สลับสิทธิ์เข้าถึง 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 อย่างที่ควรตั้งชื่อ

  1. คุณไม่ควรเชื่อถือ เพราะข้อมูลนั้นสร้างขึ้นมาอย่างคล่องแคล่ว
  2. คุณทดสอบไม่ได้ - การกำหนดเส้นทางของขั้นตอนที่ 4 อยู่ภายในข้อความธรรมดา จึงไม่มี if สำหรับการทดสอบหน่วย
  3. คุณไม่สามารถสลับขั้นตอนได้ เนื่องจากไม่มีรอยต่อที่ API สภาพอากาศจริงจะเสียบได้
  4. คุณต้องชำระเงินสำหรับทุกอย่างทุกครั้ง - 5 ขั้นตอน การเรียกใช้ครั้งใหญ่ 1 ครั้ง ไม่มีการแคชส่วนที่กำหนด

คงความรู้สึกนั้นไว้ ส่วนอีก 9 ระดับถัดไปจะนำขั้นตอนเหล่านั้นออกจากพรอมต์ทีละขั้นตอน ได้แก่ ฟังก์ชัน fetch (L1–L2a), เส้นทางของคำสั่ง if (L2b), ผู้เชี่ยวชาญแบ่งงาน (L3a–L3b) และโค้ดกำหนดรูปร่าง (L4a–L4b)

โค้ชเมกาพรอมต์ - มั่นใจโดยไม่มีอะไรซ่อนอยู่เบื้องหลังชาร์ต

💻 ในพื้นที่: python -m shared.prologue

5. L0 · Your first ADK 2 agent

แผนกลยุทธ์ - คุณอยู่ที่นี่: L0

⚡ สรุป: เอเจนต์คือโมเดล + คำสั่ง + เครื่องมือที่อาจเรียกใช้ ส่วน 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

โฟลว์ L0

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 · เวิร์กโฟลว์แรกของคุณ

แผนกลยุทธ์ — คุณอยู่ที่นี่: 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

โฟลว์ L1

โหนดฟังก์ชันจะพิมพ์ข้อมูลที่สร้างขึ้น (ไม่มีการเรียกโมเดล) จากนั้นเอเจนต์จะให้คำแนะนำที่อ้างอิงถึงอุณหภูมิและลมจริงที่ได้รับ

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)

แผนกลยุทธ์ - คุณอยู่ที่นี่: L2a

⚡ สรุป: แยกย่อยแบบขนาน (ฟรี) รอทั้งหมด จัดกลุ่ม ส่งภาพรวมทั้งหมดให้ตัวแทน 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

ขั้นตอน L2a

🔍 เครื่องหมาย: ขอบ 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)

โรดแมป — คุณอยู่ที่นี่: L2b

⚡ สรุป: 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

โฟลว์ L2b

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

แผนการเรียนรู้ - คุณอยู่ที่นี่: L3a

⚡ สรุป: ทีมเดียวกัน Flag เดียว chat จะส่งการสนทนาทั้งหมดไปยังผู้เชี่ยวชาญคนเดียวและไม่กลับมาอีก single_turn จะเปลี่ยนผู้เชี่ยวชาญแต่ละคนให้เป็นเครื่องมือ ซึ่งเป็นชุดย่อยแบบคู่ขนาน การส่งคืนอัตโนมัติ และการสังเคราะห์ครั้งเดียว

คำถาม: คุณรู้จักทีม แต่คำขอจะกำหนดว่าสมาชิกคนใดควรตอบ คุณจะอนุญาตให้ LLM เลือกชุดย่อยและเรียกใช้พร้อมกันได้อย่างไร

บทบาท: ผู้ประสานงานผู้เชี่ยวชาญ 6 คน (การแพทย์ สภาพอากาศ การกำหนดเวลา อุปกรณ์ โภชนาการ และสุขภาพจิต) ระดับนี้จะให้ทีมเดิมทำงาน 2 ครั้ง โดยใช้พรอมต์ผู้ประสานงานและผู้เชี่ยวชาญ 6 คนชุดเดิม ความแตกต่างเพียงอย่างเดียวคือมีค่าสถานะ 1 รายการในตัวแทนย่อย ความแตกต่างคือบทเรียน

Colab: เรียกใช้L3aเซลล์ · 📁 GitHub: L3a_collaborative/ · 💻 ในเครื่อง: python -m L3a_collaborative.concierge --mode chat "What about fueling?"

โฟลว์ L3a

🔍 เครื่องหมาย: 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

แผนกลยุทธ์ - คุณอยู่ที่นี่: L3b

⚡ สรุป: โหมดกลาง - พูดคุยกับผู้ใช้จนกว่าจะรวบรวมฟิลด์ได้ จากนั้นส่งคืนโดยอัตโนมัติพร้อมออบเจ็กต์ที่ตรวจสอบแล้ว

คำถาม: 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

โฟลว์ L3b

gear_fitter ค้างงานไว้ - งานที่หยุดชั่วคราว ไม่ใช่แฮงก์

🔍 เครื่องหมาย: 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 ทำไม่ได้

  1. การเรียกใช้หยุดกลางคัน - งานหยุดชั่วคราว ไม่ใช่ค้างและไม่ใช่ล้มเหลว เอเจนต์ได้ถามคำถามเพื่อช่วยชี้แจงและยังคงเปิดงานไว้ (ใน adk web คุณเพียงแค่พิมพ์คำตอบ สคริปต์ของ Harness จะบันทึกเป็นข้อความที่ 2 ในเซสชันเดียวกัน)
  2. ข้อความถัดไปจะกลับไปใช้ Agent งานเดิม โดยไม่ต้องเปลี่ยนเส้นทางหรือมอบหมายงานใหม่ เซสชันจะทราบว่าใครรออยู่
  3. finish_task สิ้นสุด - เครื่องมือที่ ADK แทรกเนื่องจาก mode="task" Agent ต้องเรียกใช้เพื่อดำเนินการให้เสร็จสมบูรณ์ และเพย์โหลดต้องตรวจสอบกับ output_schema การสนทนาที่มีเส้นชัยที่พิมพ์ จากนั้นการควบคุมจะกลับไปที่ผู้ประสานงานโดยอัตโนมัติ พร้อมแนบผลลัพธ์

กฎข้อเดียวในการเลือกโหมด

💡 "ผู้ใช้จำเป็นต้องพูดกับโมเดลไหม และต้องพูดจนถึงเมื่อไหร่" แชท = ตลอดไป · งาน = จนกว่าจะรวบรวมฟิลด์ได้ · การสนทนาแบบผลัดกันพูด = ไม่ต้องพูดเลย

โหมด

ต้องมีคนคอยตรวจสอบ

Parallel?

กลับไปที่โฟลเดอร์หลัก

chat (ค่าเริ่มต้นของเอเจนต์ย่อย) - ผู้ช่วยสนับสนุน โคไพลอตแบบเปิด

การสนทนาแบบเต็ม

ไม่

ด้วยตนเอง (ผ่านการโอน)

task — การรับลูกค้า การจอง การแก้ปัญหา

คำถามเพื่อขอข้อมูลเพิ่มเติมเท่านั้น

ไม่

อัตโนมัติ (ผ่าน finish_task โดยมีออบเจ็กต์ที่ตรวจสอบแล้ว)

single_turn — จัดประเภท · แยก · ตัดสิน · สร้าง

ไม่มี

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_fittermode="task" + output_schema คือสัญญาฉบับเต็ม · ▶ เรียกใช้ · ✏️ เปลี่ยน: run_desk("I need a hydration vest", "2 liters, medium") — คำถามเพื่อขอข้อมูลเพิ่มเติมจะปรับเปลี่ยนไป แต่เส้นชัยจะยังคงเป็นข้อความที่พิมพ์ไว้

11. L4a · แฟนเอาต์ (Fan-Out) แบบขนานที่ปรับขนาดตามรันไทม์ (เสาหลัก 3a)

แผนการเดินทาง - คุณอยู่ที่นี่: L4a

⚡ สรุป: โครงสร้างยังคงเป็น 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

โฟลว์ L4a

🔍 เครื่องหมาย — ไม่มี

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_topicparallel_worker และ rerun_on_resume · ▶ เรียกใช้ · ✏️ เปลี่ยน: สลับคำถามปลายเปิดของคุณเอง - N จะเปลี่ยนเนื่องจากอินพุตกำหนดความกว้าง

12. L4b · เพิ่มการเกิดแบบเรียกซ้ำ (เสาหลัก 3b)

แผนกลยุทธ์ - คุณอยู่ที่นี่: L4b

⚡ สรุป: การเรียกซ้ำต้องเขียนขึ้นเอง ไม่ใช่มีมาให้ โดย 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

โฟลว์ L4b

@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 · คุณควรใช้รูปแบบใด

แผนงาน - คุณอยู่ที่นี่: 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)

L5 · รูปแบบใด

การเปรียบเทียบ 1.x กับ 2 อย่างตรงไปตรงมา

นี่ไม่ใช่ "2.0 ทำสิ่งที่ 1.x ทำไม่ได้" เพราะ 1.x ก็สร้างทุกอย่างได้ การเปลี่ยนแปลงคือ 2.0 จะให้แต่ละรูปร่างมีตำแหน่งที่ชัดเจนยิ่งขึ้น ดังนั้นโฟลว์การควบคุมที่รู้จักจะออกจากพรอมต์และกลายเป็นโครงสร้างที่คุณเห็นและทดสอบได้

รูปแบบ

ค่าใช้จ่าย 1.x

หน้าแรกของ ADK 2

กราฟ

การเรียกใช้ LLM 4 ครั้งในการบิลด์ทั่วไป โดยมีการกำหนดเส้นทางที่ซ่อนอยู่ในพรอมต์

ฟังก์ชัน + โหนด Agent เป็นเพียร์ → 1 การเรียก if-เราเตอร์คำสั่ง

การทำงานร่วมกัน

สร้างได้ผ่านAgentToolการเชื่อมต่อParallelAgentแบบอนุกรมtransfer_to_agent

ทีมที่ประกาศ: sub_agents + mode="single_turn"

ไดนามิก

การเรียกซ้ำจะทำให้คุณออกจากเฟรมเวิร์ก

parallel_worker + ctx.run_node แบบเรียกซ้ำภายในเฟรมเวิร์ก

ทั้งแอปและสิ่งที่กราฟแสดงให้คุณดูไม่ได้

ตอนนี้คุณได้สร้างทุกชิ้นส่วนด้านล่างแล้ว Workflow จะแสดงโครงสร้างที่ graph.edges ดังนั้นรูปภาพนี้จึงสร้างจากโค้ดแทนที่จะวาดด้วยมือ และสิ่งที่การตรวจสอบพบคือสรุปของห้องทดลองนี้

เสา

สิ่งที่ graph.edges มี

ทำไม

1 · กราฟ (L2b)

10 ขอบ เส้นทาง และอื่นๆ

คุณวาดก่อนที่จะมีอินพุต

2 · ทำงานร่วมกัน (L3a)

0 ขอบ - เฉพาะ sub_agents + mode

LLM จะเลือกชุดย่อยต่อคำขอ

3 · ไดนามิก (L4a/L4b)

ขอบ 3 ด้าน — เหมือนกันทั้ง 2 ด้าน

การเรียกซ้ำเขียนด้วย Python ไม่ได้เชื่อมต่อในกราฟ

แถวสุดท้ายคือข้อพิสูจน์สำหรับคำถามที่ L4b ตอบ นั่นคือ L4a และ L4b มีกราฟเดียวกัน และมีเพียงกราฟเดียวที่เกิดซ้ำ

สิ่งที่คุณสร้างได้ในตอนนี้

รูปแบบแต่ละรูปแบบที่คุณเพิ่งเรียกใช้คือรูปร่างผลิตภัณฑ์จริง

คุณฝึก

ในธรรมชาติ

เริ่มจาก

กราฟ + เราเตอร์ (L2a/L2b)

ไปป์ไลน์เอกสาร, ขั้นตอน ETL ที่มี LLM, ห่วงโซ่การตรวจสอบ/อนุมัติ, ชุดเครื่องมือประเมิน

L2b ของที่เก็บนี้

ผู้ประสานงาน + ทีม single_turn (L3a)

ผู้ช่วยโคไพลอตด้านการสนับสนุนพร้อมทีมผู้เชี่ยวชาญ โต๊ะคัดกรอง และการตรวจสอบแบบหลายมุมมอง

โหมด marathon demo 2

ตัวแทน task (L3b)

แบบฟอร์มรับข้อมูล โฟลว์การจอง การเริ่มต้นใช้งาน KYC — "รวบรวมแล้วดำเนินการ"

22_agent_in_workflow

ความกว้าง/ความลึกแบบไดนามิก (L4a/L4b)

เอเจนต์การวิจัย เครื่องมือสร้างรายงาน การตรวจสอบอินพุตที่ไม่ทราบขนาด

โหมดการสาธิตแบบมาราธอน 3

โดยจะประกอบด้วย

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

ทั้งแอปและสิ่งที่กราฟแสดงไม่ได้

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

14. ขอแสดงความยินดี

นักวิ่ง 9 คน ไม้ 1 อัน และการเข้าเส้นชัยอย่างเป็นระเบียบ

คุณสร้างโค้ชวันแข่งมาราธอนและใช้รูปแบบการจัดระเบียบทั้ง 3 รูปแบบของ ADK 2

สิ่งที่คุณได้เรียนรู้

  • คำนำ - เมกะพรอมต์ที่สร้างสภาพอากาศของตัวเอง: เหตุผลที่โครงสร้างมีอยู่
  • L0–L1Agent, Runner, เครื่องมือจริงที่โมเดลเลือกเรียกใช้ และWorkflowแรกของคุณ (โหนดฟังก์ชัน + โหนดตัวแทนเป็นเพียร์)
  • L2a / L2b — เวิร์กโฟลว์กราฟ: แฟนเอาต์ (Fan-Out) แบบขนาน + JoinNode จากนั้นการกำหนดเส้นทางเชิงกำหนด — การเรียก LLM 1 ครั้ง
  • L3a - เอเจนต์ที่ทำงานร่วมกัน: ทีมเดียวกันทำงานใน chat (stranded) จากนั้น single_turn (parallel subset + synthesis) - 1 แฟล็ก 2 โลก
  • L3btask โหมด: คำถามเพื่อชี้แจงที่หยุดชั่วคราว การกลับมาทำงานตามสคริปต์ 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 ฟรี ไม่ต้องมีโปรเจ็กต์ที่อยู่ในระบบคลาวด์ และไม่ต้องมีการเรียกเก็บเงิน การสลับเซลล์นั้นเป็นเพียงการเปลี่ยนแปลงเดียว