1. บทนำ

คุณทำช่องแนวอบอุ่นปนหลอนบน VibeTube โดยอัปโหลด Shorts 1 คลิปทุกคืนจนถึงวันฮาโลวีน ผู้ชมต้องการเวทมนตร์เล็กๆ ในห้องเล็กๆ และส่งมาให้คุณในทุกช่องทางที่ทำได้ ไม่ว่าจะเป็นความคิดเห็น บันทึกเสียง รูปภาพลิ้นชักผีสิงของตนเอง และคลิประเบียงบ้านตอนพลบค่ำ งานค้างมีไอเดียมากมาย ทุกเย็นเขาจะทำกิจวัตรเดิมๆ นั่นคือค้นหาว่ามีอะไรกำลังเป็นกระแสในฟีด ค้นหาข้อมูลย้อนหลัง อ่านสิ่งที่ผู้ชมส่งมา เลือกทิศทาง ตรวจสอบว่าสอดคล้องกับนโยบายของช่องหรือไม่ เขียนสคริปต์ วาดโปสเตอร์ แสดงผลคลิป แล้วเผยแพร่
โมเดล Generative สามารถทำงานบ้านแต่ละอย่างได้ด้วยตัวเอง Gemini จะค้นคว้า ฟัง ดู และเขียน โมเดลรูปภาพจะวาด และ Veo จะแสดงผล สิ่งที่ AI ทำเองไม่ได้คือการจัดลำดับงานในแต่ละคืนตามลำดับ หยุดรอการตัดสินใจของคุณ ปฏิเสธคำสั่งที่ไม่ถูกต้องก่อนที่จะเสียเงิน รอการเรนเดอร์เป็นนาทีๆ โดยไม่เปิดอะไรค้างไว้ และบันทึกข้อมูลไว้
Codelab นี้จะเปลี่ยนช่วงเย็นนั้นให้เป็น ADK Workflow ซึ่งเป็นกราฟที่วาดขึ้นจากโหนดและขอบที่ทำงานด้วยตัวเอง โดยจะถามคุณเฉพาะเมื่อจำเป็นต้องใช้การตัดสินใจของคุณ และสิ้นสุดการทำงานแต่ละครั้งด้วยคลิปและโปสเตอร์บน VibeTube
พรอมต์ไม่สามารถรับประกันลำดับ การหยุด หรือการตรวจสอบได้ แต่กราฟทำได้
สิ่งที่คุณจะได้เรียนรู้
- มี Agent 1 ราย และโมเดลรูปภาพเป็นเครื่องมือ
Agentคือโมเดล คำสั่ง และเครื่องมือ เครื่องมือที่ 3 จะเรียกใช้โมเดลรูปภาพ และการเรียกใช้ครั้งแรกจะสิ้นสุดลงด้วยโปสเตอร์ จากนั้นก็เป็นประโยคที่ขับเคลื่อนส่วนที่เหลือของห้องทดลอง นั่นคือ พรอมต์ไม่สามารถรับประกันลำดับ การหยุด หรือการตรวจสอบได้ - โครงสร้างที่ประกอบขึ้นเป็นกราฟ
Workflowคือรายการขอบ คุณสร้าง Fan-out และ Join (ตัวอ่านแบบขนานที่มาบรรจบกันที่JoinNode) และเราเตอร์ที่ตั้งชื่อทางออกของตัวเอง โดยมี Agent โหมดงานอยู่เบื้องหลังทางออกที่ถูกปฏิเสธซึ่งจะล้างทิศทางในลูปของการเรียกใช้เครื่องมือและกลับเข้าสู่เส้นทางหลัก - ไปป์ไลน์การนำเข้าข้อมูลแบบหลายโมดัล สิ่งที่ผู้ชมส่งมา 4 ประเภท โดยมีผู้อ่าน 1 คนต่อประเภท Gemini จะถอดเสียงบันทึกเสียง บรรยายภาพ ดูคลิปและฟังซาวด์แทร็ก และผู้อ่านทุกคนจะเขียนรูปร่างเส้นเดียวกันลงในคลังข้อมูล RAG Engine เดียวกัน เครื่องมืออ่านที่ 3 ใน Fan-out จะดึงข้อมูลตามความหมายและส่งสื่อกลับมาพร้อมกับข้อความที่ตัดตอนมา
- การระงับ 2 ประเภท กลไกเดียว การวิ่งหยุดลงเมื่อเจอคน (
RequestInput: ไพ่ 4 ใบ คุณตอบ) และเมื่อเจอเครื่องมือที่ช้า (Veo อยู่หลังLongRunningFunctionTool: ใบเสร็จ) ทั้ง 2 อย่างนี้เป็นสิ่งเดียวกัน นั่นคือการเรียกแบบเปิดในที่เก็บเซสชัน ซึ่งกลับมาทำงานอีกครั้งด้วยรหัสจากแบบฟอร์มหรือจากกระบวนการอื่น The two branches of the media desk share the poster by name, in state. - ความทรงจำในแต่ละคืน Lifecycle Callback 2 รายการใน Memory Bank:
before_model_callbackจะเรียกคืนรสนิยมของครีเอเตอร์ก่อนที่ผู้เสนอจะอ่าน ส่วนafter_agent_callbackจะจดจำตัวเลือกของคืนนี้หลังจากผู้เขียนสคริปต์ - การเปิดตัว กราฟภายในแอปที่มี ADK
Runner, เหตุการณ์ที่เซิร์ฟเวอร์ส่งไปยังหน้าเว็บ, Cloud Run ที่มีเซสชันแอฟฟินิตี้ และ POST แบบหลายส่วน 1 รายการที่อัปโหลดคลิปไปยัง VibeTube โดยใช้โปสเตอร์เป็นภาพปก
สิ่งที่คุณจะสร้าง
แอปที่ทำงานอยู่ในเบราว์เซอร์: VibeStudio Workbench ซึ่งเป็นหน้าคู่หูที่มีตัวแก้ไขโค้ดในหน้าเว็บ ตัวตรวจสอบที่อ่านไฟล์ในดิสก์และเซสชันที่การเรียกใช้ของคุณเขียน และ adk web ที่ฝังอยู่ โดยทั้ง 7 เซสชันคือ 7 หน้าของ Codelab นี้ ซึ่งมีหมายเลขและตัวอักษรส่วนเดียวกันเพื่อให้ทั้ง 2 ส่วนสอดคล้องกัน แต่ละหน้าจะเปิดขึ้นในแถบที่ระบุเซสชันในประโยคเดียวและสิ่งที่คุณทำ 3 อย่าง ได้แก่ แก้ไข เรียกใช้ และยืนยัน อ่านส่วนนี้ จากนั้นทำการแก้ไขในเวิร์กเบนช์ การแก้ไขทุกครั้งจะสิ้นสุดด้วยแผงการยืนยันที่อ่านสิ่งที่เกิดขึ้นจริง
สิ่งที่จะทำงานมี 3 อย่าง ได้แก่ Workbench (หน้าในพอร์ต 4600, เอดิเตอร์, ตัวตรวจสอบ), แบ็กเอนด์ของคุณ (Workflow ใน agent/ และแอปแซนด์บ็อกซ์ 1 แอปต่อเซสชันใน stage0_prompt/ ถึง stage6_video/ ซึ่ง adk web แสดง) และ Google Cloud (Gemini, โมเดลรูปภาพ, RAG Engine, Veo, Memory Bank ซึ่งทั้งหมดเข้าถึงผ่าน Vertex AI ด้วยข้อมูลเข้าสู่ระบบของคุณเอง)
เซสชันทั้ง 7 รายการ
เซสชัน | สิ่งที่คุณสร้าง | การแก้ไขของคุณ |
1 · เรื่องราว | ยังไม่มีอะไรเลย — คืนนี้และกราฟที่กำลังจะเกิดขึ้น | — |
2 · การสร้างรูปภาพ | ตัวแทนคนหนึ่งวาดโปสเตอร์แรก |
|
3 · เวิร์กโฟลว์ | ค่ำคืนที่เหมือนกราฟ: การแยกออกและการรวมเข้า เราเตอร์และลูปของมัน |
|
4 · การนำเข้าข้อมูลหลายรูปแบบ | สิ่งที่ผู้ชมส่งมา อ่านเป็นคลังข้อมูลเดียว ผู้อ่านคนที่ 3 |
|
5 · การสร้างวิดีโอ | การระงับ 2 ประเภท ได้แก่ โปสเตอร์ที่แชร์ตามชื่อในรัฐ |
|
6 · ความทรงจำ | สิ่งที่ช่องจดจำระหว่างคืนต่างๆ |
|
7 · ทำให้ใช้งานได้และเผยแพร่ | VibeStudio บน Cloud Run, ค่ำคืนบน VibeTube | รหัสกิจกรรมของห้อง แล้วเผยแพร่ |
วิธีใช้ Codelab นี้ โดยจะเน้นที่แนวคิดเป็นหลัก โดยแต่ละเซสชันจะอธิบายแนวคิด จากนั้นแสดงโค้ดตามที่อ่านเมื่อมีการเปลี่ยนแปลงและอธิบายโค้ดนั้น ระบบจะไม่ขอให้คุณแก้ไขไฟล์ที่นี่ การแก้ไขจะเกิดขึ้นในเวิร์กเบนช์ ซึ่งจะแสดงการแก้ไขให้คุณทีละรายการ พร้อมคำแนะนำข้างๆ และแผงยืนยันด้านล่าง อ่านเซสชัน แล้วปล่อยให้หน้าเว็บนำคุณไปตลอดเซสชัน
ตำแหน่งของฟีเจอร์นี้
กราฟ ธีม และห้องใน VibeTube มาจาก VibeStudio เวอร์ชัน Haunted ของ Lin โดยนี่คือการตัดต่อแบบหลายโมดัล ซึ่งเน้นไปที่สิ่งที่ผู้ชมส่งมาและการหยุดชะงัก 2 ประเภทในรูปแบบอินเทอร์แอกทีฟหน้าเดียวของ Agent Valley Labs สตูดิโอคือฉากหลัง กลไกทุกอย่างที่อยู่เบื้องหลังคือกลไกทั่วไปที่คุณจะใช้สำหรับไปป์ไลน์การสนับสนุน ขั้นตอนการตรวจสอบเนื้อหา หรือสิ่งอื่นๆ ที่ต้องหยุดชั่วคราวเพื่อรอให้บุคคลดำเนินการและรอให้งานที่ใช้เวลานานเสร็จสิ้น
2. 🧰 การตั้งค่า
- สิ่งที่ต้องมี — บัญชีที่สามารถชำระเงินได้, โปรเจ็กต์, ห้องทดลองที่ติดตั้ง, โมเดลที่พิสูจน์แล้วว่าตอบคำถามได้ และเวิร์กเบนช์ที่ทำงานอยู่เบื้องหลังในพอร์ตเดียว
- เหตุผล - กระบวนการเดียวในพอร์ตเดียว: หน้าเว็บ, เอดิเตอร์, ตัวตรวจสอบ,
/api/*และadk webแชร์ต้นทางเดียวกัน ดังนั้นจึงไม่มีสิ่งใดในห้องทดลองนี้ที่ต้องใช้เทอร์มินัลที่ 2 หรือพอร์ตที่ 2 - วิธี — โคลนสคริปต์ 2 รายการ ส่วนที่ 2 จะทำการเรียกโมเดลจริง เริ่มเวิร์กเบนช์ และเริ่มสร้างคลังข้อมูล RAG และคลังความทรงจำขณะที่คุณอ่าน เพื่อให้เซสชันที่ 4 และ 6 พร้อมใช้งาน
ทุกอย่างในห้องทดลองนี้ทำงานใน Cloud Shell โดยมีการติดตั้ง gcloud, Python, Node, ffmpeg และ git ไว้แล้ว และสามารถเข้าถึง Gemini, โมเดลรูปภาพ, Veo, RAG Engine และ Memory Bank ได้ทั้งหมดผ่าน Vertex AI โดยใช้ข้อมูลเข้าสู่ระบบของคุณเอง ไม่มีคีย์ API ในห้องปฏิบัติการนี้ และไม่มีอะไรให้ติดตั้งในแล็ปท็อปของคุณ แล็ปท็อปที่มี Google Cloud SDK, Python 3.12 และ Node 20 ขึ้นไปจะทำงานในลักษณะเดียวกัน
มีสิ่งหนึ่งที่ห้องทดลองนี้ให้คุณไม่ได้ นั่นคือโปรเจ็กต์ที่อยู่ในระบบคลาวด์ของ Google ที่มีการเรียกเก็บเงิน ขั้นตอนที่ 1 จะช่วยให้คุณมีบัญชีที่สามารถชำระเงินได้ ส่วนขั้นตอนที่ 4 จะเปลี่ยนบัญชีนั้นเป็นโปรเจ็กต์ ทำตามลำดับดังกล่าว เนื่องจากทุกขั้นตอนหลังจากขั้นตอนที่ 4 จะอ่านโปรเจ็กต์ที่ขั้นตอนที่ 4 กำหนดไว้
1 · สร้างบัญชีที่มีการเรียกเก็บเงิน (~1 นาที)
ลงชื่อเข้าใช้ด้วยบัญชี Google ที่เปิดใช้การเรียกเก็บเงิน บัญชีดังกล่าวใช้ได้ทั้งหมด หากไม่มี ให้เริ่มทดลองใช้ฟรี ซึ่งจะให้บัญชีสำหรับการเรียกเก็บเงินแก่คุณ ซึ่งเป็นสิ่งเดียวที่จำเป็นในขั้นตอนที่ 4 หากคุณอยู่ในห้องทดลองที่มีผู้สอน ให้แลกใช้เครดิตสำหรับเซสชันนี้ก่อน ซึ่งจะเป็นการสร้างบัญชีสำหรับการเรียกเก็บเงินให้คุณ
2 · เปิด Cloud Shell (~2 นาที)
👉🖥 คลิกเปิดใช้งาน Cloud Shell ที่ด้านบนของคอนโซล Google Cloud ซึ่งเป็นไอคอนรูปเทอร์มินัลในส่วนหัว

👉🖥 คลิกเปิดเครื่องมือแก้ไข (ไอคอนโฟลเดอร์เปิดที่มีดินสอ) ซึ่งจะเปิดเครื่องมือแก้ไข Cloud Shell พร้อมเครื่องมือสำรวจไฟล์ทางด้านซ้าย ซึ่งเป็นที่ที่คุณจะไปเมื่อต้องการอ่านทั้งไฟล์

👉🖥 หากระบบแจ้งให้คุณให้สิทธิ์ในขั้นตอนใดก็ตามในวันนี้ ให้คลิกให้สิทธิ์เพื่อดำเนินการต่อ
👉🖥 เปิดเทอร์มินัลภายในเครื่องมือแก้ไข ซึ่งก็คือแท็บ 1 และทุกขั้นตอน 👉💻 จะทำงานที่นั่น แท็บที่ 2 คือ Workbench ซึ่งคุณจะเปิดในขั้นตอนที่ 7 หากเทอร์มินัลไม่ได้อยู่ที่ด้านล่างของหน้าจอ ให้เปิดด้วย View → Terminal

👉💻 ตรวจสอบว่าคุณลงชื่อเข้าใช้บัญชีใดอยู่ ซึ่งควรเป็นบัญชีจากขั้นตอนที่ 1
gcloud auth list
3 · รับห้องทดลอง (~1 นาที)
👉💻 โคลนที่เก็บ:
cd ~
git clone https://github.com/cuppibla/vibe-studio-multimodal
cd vibe-studio-multimodal
4 · สร้างโปรเจ็กต์ (~2 นาที)
👉💻 สคริปต์หนึ่งจะค้นหาบัญชีสำหรับการเรียกเก็บเงิน สร้างโปรเจ็กต์ในบัญชีนั้น ลิงก์การเรียกเก็บเงิน และชี้ Cloud Shell ไปที่โปรเจ็กต์นั้น
cd ~/vibe-studio-multimodal
./setup_project.sh
คุณจะเห็น ✓ สำหรับบัญชีสำหรับการเรียกเก็บเงินที่เลือก ✓ สำหรับโปรเจ็กต์ที่สร้าง และส่วนท้ายดังนี้
Project ready: vibe-studio-4271 Next: ./setup_codelab.sh
vibe-studio-4271 คือรหัสโปรเจ็กต์ของคุณ ซึ่งจะมีตัวเลขที่แตกต่างกัน โดยมี 4 สิ่งที่เกิดขึ้นในนั้น ได้แก่
billing | จะใช้บัญชี | |
project | สร้าง | |
เลือก | เขียนรหัสลงใน | |
waits | จะทำการสำรวจจนกว่าโปรเจ็กต์จะได้รับอนุญาตให้แสดงจริง โดยใช้เวลาสูงสุด 2 นาที | |
👉🖥 ตอนนี้โปรเจ็กต์เดียวกันจะอยู่ในคอนโซล โดยรหัสจะอยู่ในการ์ดข้อมูลโปรเจ็กต์บนแดชบอร์ด

5 · ติดตั้งและตั้งค่าห้องทดลอง (~4 นาที)
👉💻
cd ~/vibe-studio-multimodal
./setup_codelab.sh
สคริปต์เดียวที่เรียกใช้ซ้ำได้อย่างปลอดภัย การเรียกใช้ครั้งที่ 2 จะเก็บคำตอบทั้งหมดที่คุณให้ไว้ในครั้งแรก โดยจะถามคำถาม 2 ข้อในช่วงเริ่มต้น และ Enter จะใช้ค่าเริ่มต้นสำหรับทั้ง 2 ข้อ ดังนี้
รหัสเหตุการณ์ | ห้อง VibeTube ที่จะเผยแพร่วิดีโอของคุณในเซสชันที่ 7 พิมพ์รหัสห้องจากผู้สอน หรือกด Enter เพื่อเข้าสู่ | |
ชื่อที่แสดง | ชื่อที่ห้องแชทให้เครดิตคุณ หรือกด Enter เพื่อใช้ชื่อที่สร้างจากบัญชีของคุณ | |
ทั้ง 2 รายการจะอยู่ใน .env และสามารถเปลี่ยนแปลงได้ในภายหลังใน .env หรือในหน้าเผยแพร่ จากนั้นระบบจะดำเนินการส่วนที่เหลือเองตามลำดับต่อไปนี้
uv env | ติดตั้ง uv หากไม่มี จากนั้น | |
apis | เปิดใช้ API 6 รายการในโปรเจ็กต์ที่เลือกในขั้นตอนที่ 4 และยืนยันว่า API แต่ละรายการตอบกลับก่อนดำเนินการต่อ ซึ่งได้แก่ | |
account | ตรวจสอบว่าบัญชีบริการเริ่มต้นของ Compute ของโปรเจ็กต์มีอยู่และมีบทบาทที่บิลด์และบริการของเซสชัน 7 ต้องการ | |
.env | เขียน | |
veo · room | ยืนยันว่า | |
model | ทำการเรียก Gemini จริง 1 ครั้ง เพื่อให้โปรเจ็กต์ที่ไม่ดีล้มเหลวที่นี่ในเวลาตั้งค่า ไม่ใช่ภายในเซสชัน | |
starter | วางไฟล์ 9 ไฟล์ที่คุณจะแก้ไขไว้ในตำแหน่งจาก | |
page | ติดตั้งและสร้าง | |
cloud | เริ่มสร้างคลังข้อมูล RAG Engine และ Memory Bank ในเบื้องหลัง เพื่อให้เซสชันที่ 4 และ 6 พร้อมใช้งาน | |
preflight | เรียกใช้ | |
6 · อ่านข้อมูลก่อนเริ่ม (~1 นาที)
สิ่งสุดท้ายที่ขั้นตอนที่ 5 พิมพ์ออกมาคือการตรวจสอบก่อนเริ่มงาน โดยทุกบรรทัดเป็นการตรวจสอบจริง ไม่ใช่การคาดเดา
✓ python 3.12 ✓ auth path A: Vertex via ADC (STUDIO_VERTEX=1) ✓ Google Cloud ADC (project vibe-studio-4271) ✓ Memory Bank SDK ✓ stage0_prompt loads ✓ stage1_fanout loads (0 edges) ✓ stage3_router loads (... edges) ✓ stage4_memory loads (... edges) ✓ stage5_rag loads (... edges) ✓ stage6_video loads (... edges) ✓ node and npm (build the learning center's page) ✓ learning center page built (web/dist) ✓ aiplatform.googleapis.com enabled (Gemini, Veo, Memory Bank, RAG Engine) ... - Memory Bank: still being created in the background - RAG corpus: still being created in the background ✓ session 7 runtime account 123456789-compute@developer.gserviceaccount.com has its roles ✓ learning center running on port 4600 PREFLIGHT GREEN Open session 1 here: https://4600-cs-...cloudshell.dev/step/story
stage1_fanout loads (0 edges) พูดถูก กราฟนั้นว่างเปล่า และเซสชันที่ 3 คือที่ที่คุณจะเติมข้อมูลลงไป บรรทัด - ทั้ง 2 บรรทัดก็ถูกต้องเช่นกัน โดยคลังข้อมูลจะใช้เวลา 1-2 นาทีในการแสดงผล และธนาคารจะใช้เวลาประมาณ 20 วินาที และคุณไม่จำเป็นต้องใช้ทั้ง 2 อย่างก่อนเซสชันที่ 4
👉💻 เรียกใช้คำสั่งนี้อีกครั้งเมื่อใดก็ได้เพื่อดูว่าคุณดำเนินการถึงขั้นตอนใดแล้ว โดยคำสั่งนี้จะอ่านไฟล์ โปรเจ็กต์ และเซิร์ฟเวอร์ที่ทำงานอยู่ ไม่ใช่รายการตรวจสอบ
cd ~/vibe-studio-multimodal
uv run python scripts/preflight.py
7 · เปิด Workbench (~1 นาที)
Workbench ทำงานอยู่แล้ว ขั้นตอนที่ 5 เริ่มต้นกระบวนการในเบื้องหลัง โดยมีกระบวนการ uvicorn 1 กระบวนการในพอร์ต 4600 ที่แสดงหน้าเว็บ ทุกเส้นทาง /api/* และ adk web ที่ต่อเชื่อมที่ /workbench ไม่มีอะไรให้เริ่ม และไม่มีอะไรในห้องทดลองนี้ที่ขอให้คุณรีสตาร์ท การแก้ไขจะปรากฏในการเรียกใช้ครั้งถัดไป
👉🖥 คลิกที่ลิงก์ที่ Preflight พิมพ์ไว้ หรือใน Cloud Shell: Web Preview → Change port → 4600 เรียกแท็บนั้นว่าแท็บ 2 โดยจะเปิดขึ้นในเซสชันที่ 1 · เรื่องราว
คำสั่ง 3 รายการที่คุณอาจต้องการใช้ในภายหลัง ซึ่งทั้งหมดมาจาก ~/vibe-studio-multimodal มีดังนี้
| หยุด Workbench | |
| จะเริ่มทำงานในเบื้องหน้า โดยกด Ctrl+C เพื่อหยุดการทำงานและทุกกระบวนการที่เริ่มทำงาน | |
|
| |
เลย์เอาต์ที่เก็บ
vibe-studio-multimodal/
├── agent/ # the workflow and the platform clients
│ ├── graph.py # the graph: three readers, the proposer, the gate, the router, the script, the desk
│ ├── desk.py # render_desk and render_submit, the LongRunningFunctionTool
│ ├── deliver.py # the delivery console: find the pending call, wait for Veo, answer by id
│ ├── schemas.py # Direction, Directions, CleanedDirection, Script, each with a poster idea
│ ├── cleanup_tools.py # find_policy_hits, suggest_replacement: the task agent's tools
│ ├── policy_words.txt # the policy, as data; policy_replacements.txt, the approved stand-ins
│ ├── backlog.txt # the creator's Halloween ideas; comments.md, the audience's comments
│ ├── audience/ # what else the audience sent: voice/*.mp3, photo/*.jpg, clip/*.mp4
│ ├── media/past/ # six past posters and captions.md, what Gemini sees in each
│ └── platform/ # config, audience (the readers), rag (RAG Engine), images, videogen (Veo), memory (Memory Bank)
├── stage0_prompt/ ... stage6_video/ # one sandbox app per session, runnable in adk web
├── server/ & web/ # the VibeStudio Workbench (FastAPI + React, port 4600)
├── vibestudio/ # the app deployed in session 7, with its own copy of agent/
├── checks/ & scripts/ # the hole registry, the verifiers, preflight, start/stop/restart
└── starter/ # the nine files you edit, as students receive them
3. 1 · เรื่องราว — ค่ำคืน และกราฟที่เกิดขึ้น
👉🖥 แท็บที่ 2 จะเปิดขึ้นในเซสชันที่ 1 · เรื่องราว ลองนึกภาพตามดูสิ งาน 6 อย่างที่เรียงรายอยู่รอบตัวคนๆ เดียว ได้แก่ ค้นคว้าเทรนด์ ค้นหาข้อมูลเก่าๆ เลือกทิศทาง ตรวจสอบนโยบาย เขียนสคริปต์ แสดงคลิป และวงจรนี้ก็วนซ้ำทุกคืน Gemini สามารถค้นคว้าข้อมูล เขียนสคริปต์ และสร้างงานที่ค้างอยู่ได้ด้วยตัวเอง Veo สามารถแสดงผลได้ด้วยตัวเอง ส่วนการเลือกและการตรวจสอบเป็นหน้าที่ของมนุษย์
ใต้แหวนคือแผนภาพที่ห้องทดลองที่เหลือสร้างขึ้น โดยมีโหนด 13 โหนดตั้งแต่แนวคิดไปจนถึงคลิปและโปสเตอร์ วางเมาส์เหนือโหนดเพื่อดูว่าโหนดทำอะไรและเซสชันที่สร้างโหนด ไม่มีอะไรให้แก้ไขที่นี่ ปุ่มสร้างโปสเตอร์แรกของหน้าคือเซสชัน 2
คำศัพท์ ดังนั้น 6 หน้าถัดไปจะอ่านโดยไม่หยุด
คำ | ความหมายในที่นี้ |
agent | ADK |
node | 1 ขั้นตอนของกราฟ ซึ่งอาจเป็นฟังก์ชัน Python, Agent, การรวม หรือเราเตอร์ |
edge | ทูเพิลของโหนดที่ทำงานตามลำดับ โดยกราฟคือรายการของโหนดเหล่านั้น เซสชัน 3 |
session | บันทึกการเรียกใช้ 1 รายการใน |
state | พจนานุกรมที่โหนดแชร์ตามชื่อคีย์ โหนดหนึ่งเขียน |
ความทรงจำ | สิ่งที่คงอยู่หลังจบเซสชันคือ Memory Bank ผ่าน Callback เซสชัน 6 |
4. 2 · การสร้างรูปภาพ — Agent 1 ราย และโมเดลรูปภาพเป็นเครื่องมือ

👉🖥 ใน Workbench ให้เปิดเซสชันที่ 2 · การสร้างรูปภาพ ก่อนที่จะมีกราฟ สิ่งที่ง่ายที่สุดที่อาจใช้ได้คือ Agent 1 รายการที่มีคำสั่งเป็นข้อความบรรยายตลอดทั้งคืน, ฟังก์ชัน Python 3 รายการเป็นมือ และการรันที่จบลงด้วยรูปภาพ
Agent คือโมเดล คำสั่ง และเครื่องมือ
stage0_prompt/agent.py กำหนด root_agent ด้วยช่อง 3 ช่องของตัวแทน ADK model เป็นโมเดล Gemini จาก agent/platform/config.py instruction คือพรอมต์ของระบบ: งาน 6 อย่างในคราวเดียว โดยงานสุดท้ายคือรูปภาพ tools มีฟังก์ชัน 3 อย่าง
root_agent = Agent(
name="solo_channel", model=config.MODEL,
tools=[check_trends, read_backlog, make_poster],
instruction=(
"You run the creator's cozy-spooky short-video channel, alone, every "
"night until Halloween.\n"
"When the creator gives you an idea (or nothing), do ALL of this:\n"
"check what is trending. look at your backlog of ideas. propose a "
"direction and agree on it with the creator. refuse blacklisted "
"subjects (competitor, hateful, gore). then describe the video you "
"would make: a title (<=60 chars) and 3 shots, one visual sentence "
"each. then draw its poster: call make_poster once with one line that "
"names the single image that sells the video, and tell the creator "
"where the poster is.\n"
"Ask the creator to confirm the direction before describing the "
"video."))
โมเดลเป็นระบบปิด โดยจะให้เหตุผลตามน้ำหนักและโทเค็นในบริบทของโมเดล เครื่องมือคือฟังก์ชัน Python ที่โมเดลอาจขอ ADK จะอ่านซิกเนเจอร์และ Docstring ของแต่ละฟังก์ชันและประกาศให้กับโมเดล เมื่อโมเดลต้องการฟังก์ชันใดฟังก์ชันหนึ่ง ก็จะส่งเหตุการณ์ function_call ออกมา รันไทม์จะเรียกใช้ Python และเขียนเหตุการณ์ function_response กลับเข้าไปในบริบท และโมเดลจะดำเนินการต่อด้วยผลลัพธ์ที่อยู่ตรงหน้า
3 ฟังก์ชัน และ 1 ในนั้นคือการวาด
2 ใน 3 คนอ่านงานวิจัย ส่วนพรอมต์ที่ 3 จะขอให้โมเดลรูปภาพสร้างโปสเตอร์
def check_trends() -> dict:
"""Ten formats trending on the platform right now, with a heat score each."""
from agent.trends import sample_trends
return {"trends": sample_trends()}
def read_backlog() -> dict:
"""The creator's backlog: ideas they noted down to make someday."""
from agent.graph import backlog_notes
return {"backlog": backlog_notes()}
def make_poster(subject: str) -> dict:
"""Draw tonight's poster: one image of `subject` in the channel's cozy
low-poly style. Returns where the PNG landed (path, url) in a few seconds."""
...
make_poster เรียกใช้ agent/platform/images.py ซึ่งเป็นโมเดลรูปภาพที่รวมเป็นฟังก์ชันธรรมดาในลักษณะเดียวกับที่ videogen.py รวม Veo: คำนำหน้าสไตล์บ้าน (รูปลักษณ์ฮาโลวีนแบบโลว์โพลีที่อบอุ่นของช่อง สีส้มฟักทองและสีครีมแสงเทียนตัดกับสีม่วงพลัมยามค่ำคืน) วัตถุ เฟรมแนวตั้ง 9:16, PNG 1 รายการที่มีขนาดต่ำกว่า runs/media/posters ซึ่งแสดงที่ /static/posters เมื่อใช้ STUDIO_REAL_IMAGES=0 ระบบจะคัดลอกโปสเตอร์ตัวแทนและแจ้งผลลัพธ์ prebaked เพื่อให้ห้องทดลองทำงานได้ทุกที่
MODEL = os.environ.get("STUDIO_IMAGE_MODEL", "gemini-2.5-flash-image")
ASPECT = "9:16"
def generate(subject: str, visual_style: str = "", name: str | None = None) -> dict:
"""One poster for `subject`. Returns where it landed: status, path, url,
prompt, and prebaked=True when the stand-in was used instead of the model."""
การเรียกเดียวกันนี้เป็นโหนดในกราฟในเซสชันที่ 5
ลงมือปฏิบัติ: การแก้ไขเดียว
ตามที่จัดส่ง make_poster จะแสดงผลเป็น Dict ที่ระบุว่าข้ามโปสเตอร์ไปแล้ว 👉🖥 ในเครื่องมือแก้ไข Workbench ให้เรียกใช้โมเดลรูปภาพแทนบรรทัด TODO ดังนี้
from agent.platform import images
return images.generate(subject, name="solo")
บันทึก แถวการยืนยันจะอ่านไฟล์ ซึ่งก็คือเครื่องมือ 3 รายการใน Agent และการเรียกใช้โมเดลรูปภาพที่พร้อมใช้งาน 👉🖥 คลิกตรวจสอบการโหลดเวิร์กโฟลว์ แล้วคลิกเปิด adk web และส่งข้อความต่อไปนี้
tonight's idea: a tiny ghost learning to haunt a sock drawer
สิ่งที่จะเกิดขึ้นและเหตุผล
- เหตุการณ์เครื่องมือ 2 รายการก่อนการตอบกลับ ตามด้วยคำถาม
check_trendsและread_backlogทำงานเนื่องจากคำสั่งบอกให้ตรวจสอบและดู และโมเดลไม่มีทั้ง 2 อย่างในน้ำหนัก การตอบกลับจะเสนอแนวทางและขอให้คุณยืนยัน เนื่องจากคำสั่งขอให้มีการตกลงก่อน - ข้อความที่ 2 จะได้รับทุกอย่าง ส่ง
skip the questions, describe the video and draw the posterโมเดลจะอธิบายวิดีโอ เรียกใช้make_poster1 ครั้งพร้อมบรรทัดเรื่องที่เขียนเอง และบอกคุณว่า PNG อยู่ที่ใด แผงยืนยันจะแสดงโปสเตอร์ข้างแถว ซึ่งเป็นฉากฮาโลวีนในรูปลักษณ์ของช่องที่วาดในเวลาประมาณ 8 วินาที - เหตุใดจึงไม่ถือว่านี่เป็นช่องทาง การค้นคว้าข้อมูลจะถูกพับเป็นร้อยแก้วที่ไม่มีอะไรอ้างอิงได้ นโยบายได้รับการรับรองโดยโมเดลที่เขียนคำสั่ง และโปสเตอร์ได้รับการวาดก่อนที่จะมีการตรวจสอบใดๆ มีการพูดถึงข้อตกลงในข้อความเดียว ข้อความแจ้งไม่ใช่เกต ส่วนที่เหลือของห้องทดลองจะแยกงานทั้ง 6 งานนี้ออกเป็นกราฟที่วาดขึ้น
หน้าเว็บนี้จะจบลงด้วยองค์ประกอบของ Agent ADK ซึ่งเป็นข้อมูลอ้างอิงที่คุณกลับมาดูได้ ได้แก่ โมเดล บริบทที่ใช้ในการให้เหตุผล เครื่องมือและผู้ทำงานร่วมกันที่ใช้ในการดำเนินการ คอลแบ็กที่รวมการเรียกแต่ละครั้ง ตลอดจนเซสชันและหน่วยความจำที่อยู่นอกโมเดล
5. 3 · เวิร์กโฟลว์ — การกระจายและการรวม การกำหนดเส้นทางและการวนซ้ำ

👉🖥 ใน Workbench ให้เปิดเซสชัน 3 · เวิร์กโฟลว์ ส่วน 3a และ 3b แต่ละส่วนสร้างขึ้นในแอปแซนด์บ็อกซ์เพื่อให้คุณเรียกใช้ใน adk web ได้ด้วยตัวเอง โดย Fan-out อยู่ใน stage1_fanout ส่วนเราเตอร์และลูปอยู่ใน stage3_router
Fan-out and join (3a)
ADK Workflow คือรายการขอบ แต่ละรายการคือเชน ซึ่งเป็นทูเพิลของโหนดที่ทำงานตามลำดับ เชน 2 รายการที่ออกจาก START จะทำงานพร้อมกัน เชนที่สิ้นสุดใน JoinNode จะรายงานไปยังเชนดังกล่าว และการรวมจะรอเชนทั้งหมดที่สิ้นสุดที่นั่นก่อนที่จะดำเนินการต่อ โดยส่งต่อ Dict 1 รายการที่คีย์ตามชื่อโหนด เป้าหมายที่เขียนเป็น dict จะแมปชื่อเส้นทางของเราเตอร์กับโหนด หน้า 3a ของโต๊ะทำงานจะวาดรูปทรงและมีแบบฝึกหัดเขียนขอบ
ประเภทโหนด | เขียนเป็น | การทำงาน |
โหนดฟังก์ชัน |
| Python ธรรมดา และอาจเขียนสถานะที่แชร์กับ |
โหนดการรวม |
| รอจนกว่าเชนขาเข้าทุกรายการจะรายงาน จากนั้นส่งเอาต์พุตทั้งหมดเป็น Dict รายการเดียว |
โหนด Agent |
| การเรียกใช้โมเดล 1 ครั้งในโหมด |
เราเตอร์ |
| โหนดฟังก์ชันที่การแสดงผลตั้งชื่อ Edge ขาออก |
โหนดอินพุตจากมนุษย์ |
| ระงับกราฟจนกว่าจะมีผู้ตอบแบบฟอร์ม เซสชัน 5 |
กราฟแรกคือการวิจัยของเซสชันที่ 2 เป็นโหนด scan_trends และ read_backlog ใน agent/graph.py เป็นผู้อ่าน 2 คน โดย read_backlog จะเขียนไอเดียของคืนนี้ลงในสถานะที่แชร์ด้วย stage1_fanout/agent.py กำหนดการรวมและสร้าง Workflow จากรายการชื่อ EDGES ซึ่งว่างเปล่า (0 edges ของการตรวจสอบก่อนการดำเนินการ) 👉🖥 เติมด้วยเชน 2 รายการต่อไปนี้
EDGES = [(START, scan_trends, join_research),
(START, read_backlog, join_research)]
👉🖥 เรียกใช้ใน adk web โหนด 2 โหนดสว่างขึ้นพร้อมกัน จากนั้นก็รวมกัน เปิดเหตุการณ์ของ join_research และอ่านพจนานุกรม 1 รายการที่มีคีย์ scan_trends และคีย์ read_backlog ไม่มีการเรียกใช้โมเดล ไม่มีอะไรจัดรูปแบบพจนานุกรมของโหนดรวมใหม่ โหนดที่อยู่ถัดจากโหนดรวมใน 3b คือ Agent และโหนด Agent จะได้รับ node_input เป็นข้อความ ดังนั้นพจนานุกรมจึงมาถึงในรูปแบบ JSON ที่โมเดลอ่านโดยตรง
เราเตอร์และลูป (3b)
Agent ภายใน Workflow จะทำงานในโหมด single_turn โดยจะรับเอาต์พุตของโหนดก่อนหน้าเป็นข้อความ ทำการเรียกโมเดล 1 ครั้ง และตอบกลับ 1 ครั้ง เมื่อใช้ output_schema คำตอบจะได้รับการตรวจสอบเทียบกับโมเดล Pydantic ก่อนที่โหนดถัดไปจะเห็น ดังนั้นกราฟจึงส่งผ่านออบเจ็กต์ที่พิมพ์ ไม่ใช่ข้อความธรรมดา propose_directions อ่านพจนานุกรมการวิจัยของ Join และเสนอผู้สมัคร 4 คน ซึ่งทุกคนเป็นฉากฮาโลวีน ผู้สมัคร 4 เป็นเหยื่อล่อ: ชื่อของผู้สมัครมีคำที่ช่องปฏิเสธ ดังนั้นเราเตอร์จึงมีบางอย่างให้จับ ผู้สมัครทุกคนจะมี poster_idea ซึ่งเป็นข้อความบรรทัดเดียวที่ตั้งชื่อรูปภาพเดี่ยวที่ขายวิดีโอ โดยผู้เขียนสคริปต์จะเก็บไว้และโต๊ะสื่อจะวาดภาพในเซสชันที่ 5
เราเตอร์คือโหนดฟังก์ชันที่ผลลัพธ์จะระบุชื่อ Edge ที่การเรียกใช้เลือกใช้ ไม่มีการเรียกใช้โมเดล: policy_check จะอ่าน agent/policy_words.txt ในเวลาที่ตัดสินใจ จับคู่ทั้งคำกับชื่อและมุมมองของคำสั่ง และกำหนดเส้นทางก่อนที่จะเขียนสคริปต์หรือใช้จ่ายเงิน นโยบายคือข้อมูล: แก้ไขไฟล์ แล้วการเรียกใช้ครั้งถัดไปจะอ่านไฟล์นั้น
def policy_check(node_input):
"""The policy gate: a router. Refused words in the direction send it to
quarantine; a clean one goes to the scripter. Code, not a model."""
chosen = _as_dict(node_input)
bad = policy_hits(f"{chosen.get('title', '')} {chosen.get('angle', '')}")
return Event(output=node_input, route="BLOCK" if bad else "OK")
stage3_router/agent.py คือ Fan-out บวกกับ propose_directions และ choose_direction ซึ่งเป็นตัวแทนของประตูมนุษย์ที่คุณสร้างในเซสชันที่ 5 โดยจะเลือกผู้สมัครที่มีหมายเลขลงท้ายด้วยแนวคิดของคืนนี้ (..., pick 4) ไม่เช่นนั้นจะเลือกผู้สมัครคนแรก และเขียนตัวเลือกนั้นลงในสถานะ scripter เป็นตัวแทน single_turn ที่เปลี่ยนแนวทางที่ได้รับอนุมัติให้เป็น Script ซึ่งประกอบด้วยชื่อ คำอธิบาย แท็ก ประโยคเปิด ช็อต 3 ช็อต และไอเดียโปสเตอร์ โดยทุกช็อตเป็นรูปภาพฮาโลวีนในรูปลักษณ์ของช่อง
การวนซ้ำหลังจากการปฏิเสธการออก quarantine มาพร้อมกับ Agent โหมดงาน Agent มี mode: chat คือลูปการสนทนาที่ Agent ระดับรูทเรียกใช้กับบุคคล single_turn คือการเรียกใช้ 1 ครั้งพร้อมคำตอบที่มีโครงสร้าง และ task คือลูปของการเรียกใช้เครื่องมือที่ Agent เรียกใช้ด้วยตนเองจนกว่าจะเรียกใช้ finish_task ในตัว ซึ่ง ADK จะรับอาร์กิวเมนต์มาจาก output_schema Quarantine จะเรียกใช้ find_policy_hits ในทิศทางที่ถูกปฏิเสธ ขอให้ suggest_replacement แทนที่คำแต่ละคำด้วยคำที่ได้รับอนุมัติของช่อง (agent/policy_replacements.txt, ข้อมูลอีกครั้ง) เขียนใหม่ ตรวจสอบอีกครั้ง และจบด้วย CleanedDirection ที่พิมพ์ไว้ อ่านข้อความในหน้าเว็บ ไม่ต้องพิมพ์
quarantine = Agent(
name="quarantine",
model=config.MODEL,
mode="task",
instruction=QUARANTINE_INSTRUCTION,
tools=[find_policy_hits, suggest_replacement],
output_schema=CleanedDirection)
เมื่อจัดส่งแล้ว เชนจะทำงานตั้งแต่การเลือกไปจนถึงสคริปต์โดยตรง 👉🖥 วางเราเตอร์ไว้ระหว่างทั้ง 2 ฝั่ง โดยมีทางออก 2 ทางและทางกลับจากเขตกักกัน ซึ่งจะเปลี่ยนจาก 1 บรรทัดเป็น 3 บรรทัด ดังนี้
(join_research, propose_directions, choose_direction, policy_check),
(policy_check, {"OK": scripter, "BLOCK": quarantine}),
(quarantine, scripter),
👉🖥 ใช้ทั้ง 2 เส้นทาง ส่งไอเดียตามที่เป็นอยู่: choose_direction รับตัวเลือกที่ 1, policy_check แสดง OK และผู้เขียนสคริปต์จะเขียนสคริปต์ที่มีโครงสร้าง คลิกเซสชันใหม่ แล้วส่งไอเดียอีกครั้งโดยมี , pick 4 ต่อท้าย: ผู้สมัคร 4 เป็นเหยื่อล่อ policy_check ปล่อย BLOCK ออกมา การกักบริเวณจะเรียกใช้เครื่องมือของตน เขียนคำที่ถูกปฏิเสธใหม่ เรียกใช้ finish_task และคำสั่งที่ได้รับการแก้ไขแล้วจะกลับไปที่ผู้เขียนสคริปต์ การอ่านแถวที่ยืนยันจะเรียกใช้ทั้ง 2 รายการจากที่เก็บเซสชัน
ตอนนี้ทุกประโยคของพรอมต์ในเซสชันที่ 2 มีโหนดแล้ว โดยการค้นคว้าข้อมูลคือผู้อ่าน 2 คนและการรวม การปฏิเสธคือเราเตอร์และ Agent งาน ส่วนสคริปต์คือโหนด Agent ในกราฟทั้งหมด ตัวเลือกตัวแทนคือโหนดฟังก์ชัน 2 โหนด ได้แก่ direction_gate และ persist_direction ซึ่งคุณจะเห็นว่าทำงานต่อเนื่องไปจนถึงเซสชันที่ 5 ที่ทำให้ประตูหยุดทำงาน ข้อตกลงกับครีเอเตอร์ยังคงเป็นแบบชั่วคราว เช่นเดียวกับการวิจัยที่ยังไม่ได้ฟังความคิดเห็นจากกลุ่มเป้าหมาย
6. 4 · การนำเข้าข้อมูลหลายรูปแบบ — สิ่งที่ผู้ชมส่งมาจะถูกอ่านเป็นคลังข้อมูลเดียว

👉🖥 ใน Workbench ให้เปิดเซสชัน 4 · การนำเข้าข้อมูลหลายรูปแบบ ส่วน 4a และ 4b ผู้ชมไม่ได้แค่เขียนเท่านั้น โดยมีตัวอ่าน 1 ตัวต่อประเภทข้อมูลที่จะเปลี่ยนทุกอย่างที่ส่งมาให้เป็นข้อความที่มีรูปแบบเดียวกัน จากนั้นคลังข้อมูล RAG Engine 1 คลังจะเก็บข้อมูลทั้งหมดไว้ และตัวอ่านตัวที่ 3 ใน Fan-out จะค้นหาข้อมูลตามความหมายและส่งสื่อกลับมา
สิ่งที่ผู้ชมส่ง (4ก)
agent/comments.md มีความคิดเห็น 12 รายการที่เขียนไว้ใต้วิดีโอฮาโลวีน 6 รายการของช่อง agent/audience/ มีส่วนที่เหลือ ได้แก่ บันทึกเสียง 8 รายการ (voice/*.mp3) รูปภาพห้องของผู้ชมเอง 14 รูป (photo/*.jpg) และคลิป 6 คลิป (clip/*.mp4) ซึ่งเป็นภาพแพนช้าๆ เหนือรูปภาพพร้อมเสียงบรรยายของผู้ชม หน้าจอของ 4a เปิดขึ้นมาและแสดงทุกอย่าง ทั้งเล่นโน้ตเสียง แสดงรูปภาพ และเล่นคลิป

RAG Engine จะดึงข้อมูลข้อความ บันทึกเสียง รูปภาพ หรือคลิปจะเข้าร่วมคลังข้อมูลได้ในลักษณะเดียวกับที่ผู้โพสต์ในอดีตทำใน rag.caption_posters นั่นคือโมเดลจะเปลี่ยนข้อมูลดังกล่าวให้เป็นบรรทัดข้อความที่ตั้งชื่อไฟล์ บรรทัดนั้นจะกลายเป็นข้อความ และเมื่อข้อความกลับมา ไฟล์ก็จะกลับมาด้วย agent/platform/audience.py มีเครื่องอ่าน 1 เครื่องต่อประเภท แต่ละรายการจะส่งไฟล์ 1 ไฟล์และพรอมต์ 1 รายการไปยัง Gemini และเขียน 1 บรรทัดต่อไฟล์ ดังนี้
VOICE_PROMPT = ("Transcribe this voice note word for word inside double quotes. Then add: mood: and one word. "
"Plain text on one line, nothing else.")
PHOTO_PROMPT = ("Two sentences: what is in the picture, the palette, the mood. Then: tags: and three short tags. "
"Plain text on one line, no headings, no markdown.")
CLIP_PROMPT = ("Describe the shots in two sentences, then quote what you HEAR on the soundtrack as: heard: \"...\". "
"Plain text on one line, no headings, no markdown.")
def _ask(path: pathlib.Path, mime: str, prompt: str) -> str:
"""One media file in, one line of text out."""
from google.genai import types as gt
client = images._client()
resp = client.models.generate_content(
model=config.MODEL,
contents=[gt.Part.from_bytes(data=path.read_bytes(), mime_type=mime), prompt],
config=gt.GenerateContentConfig(automatic_function_calling=gt.AutomaticFunctionCallingConfig(disable=True)))
return " ".join((resp.text or "").split())
def transcribe_voice() -> pathlib.Path:
"""voice note -> text: Gemini listens."""
files = sorted((AUDIENCE / "voice").glob("*.mp3"))
lines = []
for p in files:
said = _ask(p, "audio/mpeg", VOICE_PROMPT)
lines.append(f"- voice: {p.name} — {said}")
return _write(AUDIENCE / "voice" / "transcripts.md", "voice", "What Gemini heard in the audience's voice notes", lines)
caption_photos ก็ทำแบบเดียวกันกับ image/jpeg และ PHOTO_PROMPT ส่วน watch_clips ก็ทำแบบเดียวกันกับ video/mp4 และ CLIP_PROMPT และ Gemini จะดูภาพและฟังเพลงประกอบในการโทรครั้งเดียว ผู้อ่านทุกคนเขียนรูปร่างบรรทัดเดียวกัน ดังนั้นการแยกส่วน การฝัง และการดึงข้อมูลจึงไม่ทราบว่าข้อความมาจากประเภทใด
- voice: note-03.mp3 — "I want a whole series of the cat judging costumes ..." mood: delighted
- photo: photo-01-ghost-drawer.jpg — A cream wooden drawer, striped socks, a paper-cup ghost ... tags: drawer, socks, cozy
- clip: clip-05.mp4 — A hallway floor, a snail in a sheet and a round vacuum ... heard: "The snail won."
- poster: sock-ghost.png — A sheet ghost one inch above a striped sock ...

คลังข้อมูล Vertex AI RAG Engine คือการดึงข้อมูลจากเอกสาร โดยระบบจะแบ่งคลังข้อมูลออกเป็นข้อความสั้นๆ ประมาณ 120 โทเค็น ข้อความแต่ละรายการจะฝังด้วย text-embedding-005 และคำค้นหาจะฝังในลักษณะเดียวกันและตอบด้วยข้อความที่ใกล้เคียงที่สุดตามความหมาย ไม่ใช่ตามคำที่ใช้ร่วมกัน "cozy little hauntings at home" จะค้นหาความคิดเห็นที่ขอให้มีเวทมนตร์เล็กๆ ในห้องเล็กๆ, บันทึกเสียงเกี่ยวกับค้างคาวที่กอดหลอดไฟ และคลิปที่มีซาวด์แทร็กที่บอกว่าผีเป็นถ้วยกระดาษ
corpus = rag.create_corpus(
display_name="vibestudio-feedback",
backend_config=rag.RagVectorDbConfig(
rag_embedding_model_config=rag.RagEmbeddingModelConfig(
vertex_prediction_endpoint=rag.VertexPredictionEndpoint(
publisher_model="publishers/google/models/text-embedding-005"))))
rag.upload_file(
corpus_name=corpus.name, path="agent/comments.md", display_name="comments.md",
transformation_config=rag.TransformationConfig(
chunking_config=rag.ChunkingConfig(chunk_size=120, chunk_overlap=20)))
การตั้งค่าได้เริ่มสร้างคลังข้อมูลแล้ว แถวการเชื่อมต่อของหน้า 4a จะระบุว่าคลังข้อมูลพร้อมใช้งานหรือไม่ และจะสร้างคลังข้อมูลหากการตั้งค่าทำไม่ได้ จากปุ่มของหน้าหรือเทอร์มินัล: python -m agent.platform.rag สร้างคลังข้อมูลและบันทึกไว้ใน runs/ragcorpus.json; python -m agent.platform.rag load --audience เรียกใช้โปรแกรมอ่านทุกโปรแกรมใน audience.READERS จากนั้นอัปโหลดสิ่งที่เขียนไว้ข้างความคิดเห็นและคำบรรยายของผู้โพสต์ โดยแต่ละไฟล์จะอยู่ภายใต้ชื่อของตัวเอง และรอให้ดัชนีตอบกลับ; python -m agent.platform.rag query "cozy little hauntings at home" แสดงการดึงข้อมูลที่พบข้อความที่ไม่มีคำใดตรงกับคำค้นหา
การแก้ไข เมื่อจัดส่ง READERS จะมีเพียง read_comments เท่านั้น โดยเครื่องอ่านสื่อ 3 เครื่องจะอยู่หลัง # 1 อัน 👉🖥 เรียกใช้การนำเข้าข้อมูลหนึ่งครั้งตามที่จัดส่ง: เอาต์พุตจะระบุว่า not read (no reader wired in audience.READERS): voice, photo, clip และระบบจะนำเข้าเฉพาะความคิดเห็นและคำบรรยายแทนเสียงเท่านั้น จากนั้นลบ # ดังนี้
transcribe_voice, caption_photos, watch_clips,
👉🖥 บันทึกและเรียกใช้การนำเข้าอีกครั้ง โปรแกรมอ่านหน้าจอจะใช้เวลาประมาณ 3 นาทีในการอ่านทั้ง 40 รายการ โดยคอนโซลจะพูดว่า heard, saw และ watched ทีละบรรทัด และการ์ดทุกใบในตารางกริดจะแสดงข้อมูลตามบรรทัดที่อ่าน แถวตรวจสอบจะอ่านไฟล์ เอาต์พุตของผู้อ่าน และคลังข้อมูล โดยมีผู้อ่าน 4 คนที่เชื่อมต่ออยู่, บันทึกเสียง 8 รายการที่ได้ยิน, รูปภาพ 14 รูปที่เห็น, คลิป 6 คลิปที่ดู และไฟล์ 5 ไฟล์ในคลังข้อมูล
ผู้อ่านคนที่ 3 (4b)
ความคิดเห็นของกลุ่มเป้าหมายคือการวิจัยที่แชร์โดยการเรียกใช้ทุกครั้ง ดังนั้นจึงเข้าสู่กราฟในตำแหน่งเดียวกับการวิจัย นั่นคือเป็นผู้อ่านรายที่ 3 ในการรวม read_feedback ใน agent/graph.py จะนำแนวคิดของคืนนี้ไปใช้ ถามคลังข้อมูลเพื่อหาข้อความที่ใกล้เคียงที่สุด และรายงานไปยัง join_research ซึ่งรออยู่ 3 รายการ ขอบ (START, read_feedback, join_research) ส่งใน stage5_rag/agent.py ซึ่งเป็นรูปร่างที่สอนในเซสชัน 3 การแก้ไขของคุณอยู่ภายในโหนด การดึงข้อมูล ไม่ใช่การเรียกใช้โมเดล และเป็นแบบเชิงกำหนด: แนวคิดเดียวกันจะค้นหาข้อความเดียวกันในขณะที่ผู้เสนอแตกต่างกัน
def read_feedback(node_input):
"""The third reader: what the audience sent - comments, and what Gemini
heard and saw in their voice notes, photos and clips - the passages
nearest to tonight's idea, and the media those passages came with.
Retrieval, not a model call."""
from .platform import rag
idea = idea_text(node_input)
query = idea or "what viewers liked and what they complained about"
try:
hits = rag.retrieve(query)
except Exception as e:
print(f" [rag] feedback unavailable ({str(e)[:80]})")
return Event(output={"query": query, "feedback": [], "media": [],
"note": "no corpus connected - run: python -m agent.platform.rag"})
return Event(output={"query": query, "feedback": [h["text"] for h in hits]}) # TODO: RAG_MEDIA
ทุกบรรทัดในคลังข้อมูลที่มาจากบันทึกเสียง รูปภาพ คลิป หรือโปสเตอร์จะระบุชื่อไฟล์ rag.media_in(hits) จะอ่านชื่อเหล่านั้นจากข้อความและเปลี่ยนเป็น URL ที่หน้าเว็บสามารถแสดงและเล่นได้ 👉🖥 ส่งสื่อกลับไปพร้อมกับข้อความ เพื่อให้สิ่งที่ผู้ชมส่งมาเดินทางไปพร้อมกับสิ่งที่พวกเขาพูด
return Event(output={"query": query, "feedback": [h["text"] for h in hits],
"media": rag.media_in(hits)})
👉🖥 วิ่ง stage5_rag ด้วยไอเดีย เปิด read_feedback: คำค้นหาคือแนวคิดของคุณ เอาต์พุตมีข้อความที่ใกล้เคียงที่สุด 5 ข้อความและรายการ media ที่มี URL ต่อไฟล์ เปิด join_research: 3 คีย์ เปิด propose_directions: ผู้สมัคร 1 ถึง 3 อ้างอิง feedback ในหลักฐาน แผงยืนยันจะแสดงสิ่งที่ผู้อ่านดูใต้แถว ซึ่งได้แก่ คลิป บันทึกเสียง 3 รายการ และโปสเตอร์ผีในตู้เย็น

7. 5 · การสร้างวิดีโอ - การระงับ 2 ประเภท กลไกเดียว

👉🖥 ใน Workbench ให้เปิดเซสชัน 5 · การสร้างวิดีโอ ส่วน 5a และ 5b ซึ่งสร้างใน stage6_video ทั้งกราฟ การติดคลิปใช้เวลาไม่กี่นาทีและการติดโปสเตอร์ใช้เวลาไม่กี่วินาที และการวิ่งจะหยุด 2 ครั้งระหว่างทาง ครั้งแรกสำหรับคุณ และครั้งที่ 2 สำหรับ Veo The two stops are one mechanism, and the two branches of the desk share what they made by name.
การระงับ 2 ประเภท (5ก)
ไม่ต้องรอ การเรียกใช้ที่ต้องหยุดจะเขียนการเรียกแบบเปิดลงในเซสชันและสิ้นสุดลง โดยใครก็ตามที่ตอบรับการเรียกนั้น ไม่ว่าจะเป็นบุคคลหรือกระบวนการ จะเป็นผู้ดำเนินการต่อ หน้าเว็บจะเปิดขึ้นในตารางเดียว ดังนี้
สำหรับ 1 คน · | สำหรับเครื่องมือที่ทำงานช้า · | |
ผู้ที่หยุดการวิ่ง |
|
|
สิ่งที่รออยู่ | บุคคล: การ์ด 1 ใน 4 ใบ | งานที่ช้า: การทำงานของ Veo, นาที |
สิ่งที่อยู่ในเซสชัน | การเรียก | การเรียก |
วิธีที่ระบบจะกลับมาทำงานต่อ |
|
|
ผู้ส่ง |
|
|
ซึ่งเป็นที่ที่มันอาศัยอยู่ในระหว่างนั้น |
| แถวเดียวกัน รหัสเดียวกัน · ไม่มีอะไรทำงาน |

สำหรับบุคคลธรรมดา ในพรอมต์เดียว "ขอให้ครีเอเตอร์ยืนยัน" เป็นคำแนะนำ และเซสชันที่ 2 ได้พูดถึงเรื่องนี้ในข้อความเดียว ในเวิร์กโฟลว์ การหยุดชั่วคราวคือเครื่องมือ โหนดที่ให้ผลลัพธ์เป็น RequestInput จะระงับการเรียกใช้: ADK จะเขียนการเรียกแบบเปิดชื่อ adk_request_input ลงในเซสชันด้วย message, response_schema และ payload ของคุณ โดยกำหนด interrupt_id ให้ และส่งกลับไปยังผู้เรียก ไม่มีการรอชุดข้อความ สิ่งเดียวที่จะทำให้กลับมาทำงานต่อได้คือ function_response ที่มีรหัสนั้น ซึ่งได้รับการตรวจสอบเทียบกับสคีมาของคุณ ข้อความแชทไปยังเวิร์กโฟลว์คือเทิร์นใหม่ ไม่ใช่คำตอบ สคีมายังเป็นแบบฟอร์มด้วย โดย adk web จะแสดงผลสคีมา และแอป VibeStudio จะแสดงผลสคีมาเดียวกันเป็นการ์ด 4 ใบ direction_gate เขียนตัวเลือกไปยังสถานะแล้ว 👉🖥 วิธีหยุดการแจ้งเตือน
yield RequestInput(
message="Pick tonight's direction: 1, 2, 3 or 4.",
response_schema={
"type": "object",
"properties": {
"pick": {"type": "string", "enum": ["1", "2", "3", "4"]}}},
payload={"candidates": cands})
คำตอบของคุณจะมาถึงที่ persist_direction เป็น node_input ซึ่งเป็น Dict ที่มีคีย์เดียวคือ pick ผู้สมัครทั้ง 4 รายมาถึงด้วยวิธีอื่น โดยเกตเวย์จะเขียนผู้สมัครไปยังสถานะที่แชร์ภายใต้ candidates และ ADK จะเชื่อมโยงคีย์สถานะกับพารามิเตอร์ที่มีชื่อเดียวกัน โหนดจะแก้ไขการเลือกเป็นผู้สมัคร 1 รายและเขียน direction, angle, hook และ user:prefs กลับด้วย Event(state=...) คีย์ที่มีคำนำหน้า user: จะคงอยู่ตลอดเซสชันสำหรับผู้ใช้รายนั้น
สำหรับเครื่องมือที่ทำงานช้า การเรนเดอร์ Veo ใช้เวลาหลายนาที และกราฟไม่ควรรอนานขนาดนั้น render_submit ใน agent/desk.py จะเริ่มงานและแสดงผลทันทีพร้อมใบเสร็จที่มีสถานะเป็น pending:
def render_submit(prompt: str) -> dict:
"""Submit one Veo render of `prompt`. Returns at once with a pending
receipt; the clip is delivered later, to this call, by id."""
receipt = videogen.start(f"{prompt} {videogen.NO_TEXT}")
return {"status": "pending", "operation": receipt["operation"], "prompt": receipt["prompt"]}
เมื่อรวมอยู่ใน LongRunningFunctionTool ใบเสร็จนั้นจะมีความหมายต่อ ADK นั่นคือเทิร์นของ Agent สิ้นสุดลง เวิร์กโฟลว์จะหยุดชั่วคราวที่ render_desk และการโทรที่เปิดอยู่พร้อมรหัสและใบเสร็จจะเป็นแถวใน runs/sessions.db 👉🖥 ใน stage6_video/agent.py ให้รวมเครื่องมือไว้ดังนี้
tools=[LongRunningFunctionTool(render_submit)])
ดำเนินการต่อตามรหัสจากกระบวนการอื่น agent/deliver.py จะค้นหาการเรียกที่รอดำเนินการในที่เก็บเซสชัน โพล Veo จนกว่าจะมีคลิป เขียนผลลัพธ์ลงใน runs/state.json และส่ง function_response ที่มีรหัสของการเรียกนั้นไปยังเซสชันเดียวกัน 👉🖥 สร้างส่วนคำตอบ:
part = Part(function_response=FunctionResponse(
id=row["call_id"], name=row["name"], response=response))
👉🖥 เปิด stage6_video จนกว่าจะหยุด 2 ครั้ง ส่งไอเดีย: การเรียกใช้จะหยุดที่แบบฟอร์มที่สร้างจากสคีมาของคุณ โดยมีผู้สมัคร 4 คนในเพย์โหลด พิมพ์ 2 ใน pick แล้วส่ง กราฟจะกลับมาทำงานต่อในเวลา persist_direction ลองทดสอบการหยุดทำงานที่นี่: ระงับที่แบบฟอร์ม หยุดเซิร์ฟเวอร์ (scripts/stop.sh ในแท็บ 1) เริ่มเซิร์ฟเวอร์อีกครั้ง (scripts/restart.sh) เปิดเซสชันอีกครั้ง รูปแบบยังคงอยู่ เพราะการหยุดชั่วคราวคือแถว ไม่ใช่ชุดข้อความ หลังจาก script_ready แล้ว render_desk จะเรียกใช้ render_submit รับใบเสร็จ ตอบกลับ WAITING และการเรียกใช้จะสิ้นสุดลง โดยเซสชันจะเก็บรหัสการเรียกใช้ไว้ เรียกใช้การแสดงโฆษณาจากคอนโซลหรือแท็บ 1 ของหน้าเว็บ
cd ~/vibe-studio-multimodal
uv run python -m agent.deliver status # the pending renders, by session, call id and operation
uv run python -m agent.deliver # wait for Veo, answer the newest pending call by id
👉🖥 คลิกรีเฟรช adk web: function_response จะติดตามการโทรที่รอดำเนินการ join_media จะเผยแพร่ และ store_release จะทำงาน แถวการยืนยัน 8 แถวจะอ่านจุดหยุด 2 จุดจากเซสชัน ได้แก่ การเรียกของเกตเวย์และคำตอบของคุณ ใบเสร็จของโปรแกรมแสดงผลและการนำส่ง
รัฐและโต๊ะทำงาน (5ข)
โต๊ะสื่อมีโครงสร้างแบบ 3a อีกครั้ง โดยมีการแยกสาขาและการรวมเข้าด้วยกัน โดยมีสาขาที่ช้า 1 สาขา script_ready ส่งสคริปต์ 2 วิธี render_desk ส่งคลิปและระงับการวิ่ง make_poster คือเครื่องมือเซสชันที่ 2 ในรูปแบบโหนด โดยposter_ideaของสคริปต์ สไตล์ของช่อง และสิ่งที่คลังความทรงจำรู้เกี่ยวกับรสนิยมของครีเอเตอร์ในเรื่องโปสเตอร์ (เชื่อมโยงจากสถานะเป็น memory_facts, เซสชันที่ 6) จะส่งไปยังโมเดลรูปภาพ และ PNG ขนาด 9:16 จะส่งกลับมาในไม่กี่วินาที join_media จะรอการดำเนินการที่ช้า และ store_release จะเขียนสิ่งที่พบลงในสถานะ
แชร์ตามชื่อ store_release ไม่เคยได้รับโปสเตอร์ โดยจะประกาศพารามิเตอร์ชื่อ poster_url และ ADK จะเชื่อมโยงพารามิเตอร์ดังกล่าวจากสถานะที่แชร์ เนื่องจากโหนดในสาขาอื่นเขียนคีย์นั้น ไม่มีเพย์โหลดข้ามระหว่างสาขา:
def store_release(node_input, poster_url: str = "", poster_path: str = ""):
"""`poster_url` and `poster_path` are not passed by anyone: ADK binds them
from shared STATE because the parameter names match the keys make_poster
wrote (the STATE_SHARE edit). The two branches of the desk never hand each
other payloads; they share what they made by name."""
render = state.load().get("render") or {} # the delivered clip, written by deliver
url, status = render.get("url") or "", render.get("status", "")
...
if not poster_url:
said += " · poster: not in state (did make_poster write poster_url to state? the STATE_SHARE edit)"
yield Event(output={"status": status, "url": url, "poster_url": poster_url},
state={"render_url": url, "render_status": status, "poster_url": poster_url},
message=f"video: {said} · poster: {poster_url or 'none'}")
เมื่อจัดส่งแล้ว make_poster จะแสดงผลเป็นโปสเตอร์และข้อความ และ store_release จะแสดงข้อความว่า poster: not in state 👉🖥 เพิ่มการเขียนสถานะลงในผลลัพธ์สุดท้าย
state={"poster_url": rec.get("url", ""), "poster_path": rec.get("path", "")},
message=f"poster: {said}")
👉🖥 ดำเนินการอีกครั้งและส่งมอบ เปิดเหตุการณ์ของ make_poster: URL ของโปสเตอร์ในเอาต์พุตและเดลต้าสถานะที่มี poster_url เปิด store_release: ข้อความจะระบุชื่อผู้โพสต์ บล็อกเซสชันของการเรียกใช้นี้ของหน้าจะแสดงแท็บสถานะของแถว adk web เดียวกัน ซึ่งแสดงรหัสเซสชันที่การดำเนินการต่อต้องการ การเรียกที่เปิดอยู่ และคีย์ที่โหนดเขียน poster_url และอื่นๆ แผงยืนยันจะแสดงคลิปและโปสเตอร์เคียงข้างกัน

8. 6 · ความทรงจำ - สิ่งที่ช่องจดจำได้ระหว่างคืนต่างๆ

👉🖥 ใน Workbench ให้เปิดเซสชัน 6 · หน่วยความจำ ส่วน 6a และ 6b ซึ่งสร้างใน stage4_memory เซสชันที่ 5 เป็นเรื่องเกี่ยวกับสถานะของการวิ่ง 1 ครั้ง เซสชันนี้จะพูดถึงสิ่งที่ช่องจำได้ระหว่างการเรียกใช้ ซึ่งก็คือสิ่งที่กราฟได้ยินจากภายนอกผ่าน Callback 2 รายการ
Memory Bank (6ก)
Vertex AI Memory Bank คือความทรงจำระยะยาวที่มีการจัดการเกี่ยวกับบุคคล ข้อเท็จจริงจะอยู่ภายใต้ขอบเขต ชื่อแอป และรหัสผู้ใช้ รวมถึงอยู่ภายใต้หัวข้อที่คุณกำหนด เมื่อคุณส่งการสนทนากับ memories.generate ให้ NotebookLM ระบบจะดึงข้อมูลที่ตรงกับหัวข้อ ฝังข้อมูลนั้น และรวมเข้ากับข้อมูลที่มีอยู่แล้ว โดยจะสร้างข้อเท็จจริงใหม่ อัปเดตข้อมูลที่เปลี่ยนแปลง และรวมข้อมูลที่ซ้ำกัน memories.retrieve แสดงผลข้อเท็จจริงสำหรับขอบเขต ทั้ง 2 สายจะอยู่ใน agent/platform/memory.py
SCOPE = {"app_name": config.APP, "user_id": config.USER}
TOPICS = {
"CREATOR_TASTE": "Which video directions this creator picks and passes on, "
"and how that preference changes over time.",
"CHANNEL_RULES": "Standing instructions the creator states for every video "
"(style, subjects to avoid, format rules).",
"VISUAL_STYLE": "What this creator approves and rejects in a poster: palettes, "
"framing, props and mood of the posters they shipped.",
}
หัวข้อที่ 3 คือรสนิยมของช่องในเรื่องรูปภาพ โดยโมเดลได้เขียนลงในหน่วยความจำนี้ในเซสชันที่ 5 ซึ่งมีการอธิบายโปสเตอร์ที่จัดส่งแล้วในประโยคเดียวและจดจำไว้ จากนั้นจึงวาดโปสเตอร์ถัดไปโดยใช้ประโยคนั้นในพรอมต์
การตั้งค่าเริ่มสร้างแบงก์แล้ว แถวการเชื่อมต่อของหน้า 6a จะระบุว่าแบงก์พร้อมใช้งานหรือไม่ จากปุ่มของหน้าหรือเทอร์มินัล: python -m agent.platform.bank สร้างคลังและหัวข้อต่างๆ และแคชชื่อทรัพยากรใน runs/memorybank.json; python -m agent.platform.bank load สร้างเซสชันที่ผ่านมา 4 เซสชัน (ยุคสัตว์ 2 ยุคที่มีกฎสไตล์ ยุคแกดเจ็ต ยุคแฟนตาซีล่าสุด) python -m agent.platform.bank list แสดงผลลัพธ์ของข้อความถอดเสียง ซึ่งก็คือข้อความสรุปข้อเท็จจริง ไม่ใช่ข้อความถอดเสียง
การติดต่อกลับ (6b)
หน่วยความจำไม่ใช่โหนด Callback คือฟังก์ชันที่คุณส่งไปยัง Agent โดย ADK จะเรียกใช้ฟังก์ชันนี้ในเวลาที่แน่นอนพร้อมบริบทแบบเรียลไทม์ และการแสดงผล None จะช่วยให้สิ่งต่างๆ ดำเนินไปตามปกติ 3 คู่: รอบเทิร์นของ Agent (before_agent_callback, after_agent_callback พร้อม CallbackContext), รอบการเรียกโมเดลแต่ละครั้ง (before_model_callback พร้อม LlmRequest, after_model_callback พร้อม LlmResponse), รอบการเรียกใช้เครื่องมือแต่ละครั้ง Guardrail, การตรวจวัดระยะไกล, แคช และการแทรกบริบทจะอยู่ที่นี่ โดยไม่มีโหนดในกราฟ
ใน stage4_memory/agent.py propose_directions จะอ่านคลังข้อมูลก่อนที่จะเรียกใช้โมเดล โดย recall_taste จะดึงข้อมูลข้อเท็จจริงมาต่อท้ายคำขอเป็นบล็อก MEMORY โดยเรียงลำดับจากเก่าที่สุดก่อน และบอกให้โมเดลปรับตัวเลือกที่ 1 ถึง 3 ให้สอดคล้องกับรสนิยมปัจจุบันและถือว่ากฎเป็นข้อจำกัด นอกจากนี้ยังเขียนข้อเท็จจริงลงในสถานะเป็น memory_facts ซึ่ง make_poster จะค้นหาได้ 👉🖥 เชื่อมต่อสาย:
output_schema=Directions,
before_model_callback=recall_taste)
ผู้เขียนสคริปต์จะเขียนสิ่งที่ครีเอเตอร์เลือกหลังจากถึงตาของตนเอง โดย remember_pick จะอ่าน direction จากสถานะ เขียนประโยค 1 ประโยคเกี่ยวกับตัวเลือกนั้น แล้วส่งให้ memories.generate 👉🖥 เชื่อมต่อสาย:
output_schema=Script,
after_agent_callback=remember_pick)
👉🖥 เรียกใช้ด้วยข้อความว่างเปล่าและเลือกการ์ด เปิดกิจกรรม propose_directions: คำขอมี 3 ยุค และผู้สมัคร 1 ถึง 3 มีแนวโน้มที่จะชอบรสนิยมปัจจุบัน ขณะที่เทรนด์แนะนำอย่างอื่น หลังจากสคริปต์ ให้คลิกแสดงคลัง: ความทรงจำมีการเปลี่ยนแปลงหรือเพิ่มเข้ามาสำหรับรายการที่เลือกในคืนนี้ เรียกใช้คืนที่ 2: ระบบจะจดจำคืนแรก
9. 7 · ติดตั้งใช้งานและเผยแพร่ — กราฟภายในแอป, ค่ำคืนใน VibeTube

👉🖥 ใน Workbench ให้เปิดเซสชัน 7 · ติดตั้งใช้งานและเผยแพร่ ส่วน 7a และ 7b
ติดตั้งใช้งาน VibeStudio (7a)
ในแซนด์บ็อกซ์ adk web เป็นผู้ขับเคลื่อนกราฟ VibeStudio ขับเคลื่อนด้วย Runner ของ ADK ในเทรดผู้ปฏิบัติงานที่มีวนรอบเหตุการณ์ของตัวเอง และพับทุกเหตุการณ์ให้อยู่ในสถานะการเรียกใช้เดียวที่หน้าเว็บอ่านผ่านเหตุการณ์ที่เซิร์ฟเวอร์ส่งมา ซึ่งได้แก่ ผู้อ่าน การเลือกของคุณเป็นการ์ด 4 ใบ โปสเตอร์เมื่อมาถึง และการแสดงผลเมื่อส่ง คำตอบของ Gate และการแสดงผลของ Render เป็นการเรียกเดียวกัน ซึ่งเป็นส่วน function_response และแอปจะสำรวจ Veo เองแทนที่จะรอคอนโซล
self._svc = DatabaseSessionService(db_url=config.DB_URL)
self._runner = Runner(app_name=config.APP, agent=wf, session_service=self._svc)
async for ev in self._runner.run_async(user_id=config.USER, session_id=run_id, new_message=message):
self._absorb(ev) # fold the ADK event into the run state, publish one app event
part = Part(function_response=FunctionResponse(id=call_id, name=name, response=response))
vibestudio/server/agent/ เป็นสำเนาแบบไบต์ต่อไบต์ของ agent/ ของห้องปฏิบัติการ ซึ่งตรวจสอบโดย checks/verify_app.py รายการ Edge ที่สมบูรณ์ใน agent/graph.py คือทุกโครงสร้างของ Codelab นี้ในที่เดียว ซึ่งมี 13 โหนด โดยจะส่งมาแบบสมบูรณ์เพื่อให้คุณอ่าน ไม่ใช่พิมพ์
(START, scan_trends, join_research),
(START, read_backlog, join_research),
(START, read_feedback, join_research),
(join_research, propose_directions, direction_gate,
persist_direction, policy_check),
(policy_check, {"OK": scripter, "BLOCK": quarantine}),
(quarantine, scripter),
(scripter, script_ready),
(script_ready, render_desk, join_media),
(script_ready, make_poster, join_media),
(join_media, store_release),
👉🖥 ติดตั้งใช้งานด้วยปุ่มติดตั้งใช้งานของ Workbench หรือ 👉💻 จากแท็บ 1 โดยทำดังนี้
cd ~/vibe-studio-multimodal
uv run python vibestudio/deploy.py
ซึ่งจะทำงานโดยมีค่าจาก .env ของคุณที่กรอกไว้ ดังนี้
gcloud run deploy vibestudio --source vibestudio \
--project $GOOGLE_CLOUD_PROJECT --region us-central1 \
--labels dev-tutorial-codelab=vibetube --allow-unauthenticated \
--memory 2Gi --cpu 2 --timeout 3600 --concurrency 40 \
--max-instances 1 --min-instances 1 --session-affinity \
--set-env-vars GOOGLE_CLOUD_PROJECT=...,STUDIO_VERTEX=1,STUDIO_MEMORY_BANK=...,STUDIO_RAG_CORPUS=...,VIBETUBE_URL=...,VIBETUBE_EVENT=...,VIBETUBE_NAME=...,VIBETUBE_PROJECT=...
--source จะสร้างคอนเทนเนอร์ด้วย Cloud Build และทำให้ใช้งานได้ในขั้นตอนเดียว โดยการสร้างครั้งแรกจะใช้เวลา 4-5 นาที --session-affinity ที่มีอินสแตนซ์แบบวอร์ม 1 รายการจะเก็บการเรียกใช้ที่ถูกระงับไว้ในอินสแตนซ์ที่เก็บข้อมูลเซสชันของเซสชัน 5 อีกครั้ง ในการติดตั้งใช้งานขนาดใหญ่ขึ้น พื้นที่เก็บข้อมูลเซสชันจะย้ายไปที่ VertexAiSessionService และความสัมพันธ์จะหายไป บิลด์และบริการจะทำงานเป็นบัญชีบริการ Compute เริ่มต้นของโปรเจ็กต์ ซึ่งการตั้งค่าจะให้สิทธิ์ตามบทบาทที่จำเป็น
👉🖥 เปิด URL ของบริการและสร้างวิดีโอในแอป โดยเริ่มจากไอเดีย เลือก รอคลิปและโปสเตอร์ แท็บประวัติจะเก็บทั้ง 2 รายการไว้
เผยแพร่ (7b)
การเผยแพร่คือการส่ง POST แบบหลายส่วนจากแอปไปยังปลายทางของห้องแชทบน VibeTube ในรูปแบบ vibestudio/server/platform/publish.py คลิปคือวิดีโอ โปสเตอร์คือภาพปก และโปรไฟล์จะตั้งชื่อห้องและคุณ ดังนี้
url = f"{prof['platform_url']}/api/events/{prof['event_code']}/videos"
data = {"title": state.script["title"], "description": state.script["description"],
"duration": "0:08", "displayName": prof["display_name"],
"projectId": prof["project_id"]} # one video per project and room: publishing again replaces it
files = {"videoFile": (clip.name, clip.open("rb"), "video/mp4"),
"thumbnailFile": (poster.name, poster.open("rb"), "image/png")} # the poster, not a frame
r = httpx.post(url, data=data, files=files, timeout=120)
video_id = r.json()["id"] # watch it at {platform}/e/{event}?v={video_id}
VibeTube จะเก็บวิดีโอไว้ 1 รายการต่อ projectId และห้อง และโปรเจ็กต์ที่แอปส่งคือโปรเจ็กต์ Google Cloud ดังนั้นการเผยแพร่อีกครั้งจะแทนที่วิดีโอของคุณแทนที่จะเพิ่มวิดีโอ ระบบจะไม่อัปโหลดโปสเตอร์ตัวแทน แต่จะใช้โปสเตอร์จริงเป็นภาพขนาดย่อแบบไบต์ต่อไบต์
การแก้ไขล่าสุดเป็นการกำหนดค่า ไม่ใช่บรรทัดโค้ด 👉🖥 ในหน้า 7b: URL ของ VibeStudio (กรอกข้อมูลจากการติดตั้งใช้งานของ 7a) รหัสกิจกรรมของห้องตามที่ VibeTube สะกด (รหัสที่คุณตั้งค่าไว้ เว้นแต่ผู้สอนจะเปิดรหัสอื่น) และชื่อของคุณ กดเผยแพร่ Workbench จะส่งต่อการคลิกไปยังแอป แอปจะโพสต์คืนนั้น และแถวด้านล่างจะอ่านผลลัพธ์จากแอปและจาก VibeTube โดยแอปจะตอบ คืนนั้นจะจบลงด้วยโปสเตอร์จริง ชื่อเวอร์ชันจะตั้งชื่อห้องเปิด VibeTube จะแสดงรหัสวิดีโอ วิดีโอจะอยู่บนผนัง และภาพขนาดย่อจะเป็นโปสเตอร์ของคุณ การ์ดผลการค้นหาจะแสดงภาพปกบน VibeTube ข้างโปสเตอร์ในแอป

10. สิ่งที่คุณสร้าง
👉🖥 ตอนจบของหน้า 7b จะวาดกราฟทั้งหมด ซึ่งมีโหนด 13 โหนดตั้งแต่แนวคิดไปจนถึงคลิปและโปสเตอร์ของคลิป วางเมาส์เหนือโหนดเพื่อดูว่าโหนดนั้นทำอะไรและเซสชันที่สร้างโหนดนั้นขึ้นมา
เซสชัน | สิ่งที่สอน | เขียนเป็น |
2 · การสร้างรูปภาพ | Agent: โมเดล, วิธีการ, เครื่องมือ, โมเดลรูปภาพเป็นเครื่องมือ, สิ่งที่พรอมต์รับประกันไม่ได้ |
|
3 · เวิร์กโฟลว์ | รายการขอบ การกระจายและการรวม เราเตอร์ที่มีลูปโหมดงานอยู่ด้านหลัง |
|
4 · การนำเข้าข้อมูลหลายรูปแบบ | ผู้อ่าน 1 คนต่อประเภท รูปทรงเส้น 1 รูป คลังข้อมูล 1 ชุด ผู้อ่านคนที่ 3 ตามความหมาย พร้อมสื่อ |
|
5 · การสร้างวิดีโอ | การระงับ 2 ประเภท กลไกเดียว, พื้นที่เก็บข้อมูลเซสชัน, โปสเตอร์ที่แชร์ตามชื่อ |
|
6 · ความทรงจำ | ความทรงจำข้ามคืน, การโทรกลับ 2 ครั้ง |
|
7 · ทำให้ใช้งานได้และเผยแพร่ | กราฟภายในแอปบน Cloud Run, ค่ำคืนบน VibeTube |
|
กฎ 5 ข้อที่กราฟปฏิบัติตามมีดังนี้
- ระงับแทนการรอ การหยุดชั่วคราวคือแถวในที่เก็บเซสชัน ไม่ใช่ชุดข้อความ
- กลับมาทำงานต่อในลักษณะเดิม โดยใช้รหัสของการโทรที่เปิดอยู่ ไม่ว่าจะเป็นบุคคลหรือกระบวนการที่ตอบรับก็ตาม
- แชร์ข้อมูลตามชื่อในสถานะ ไม่ใช่การส่งผ่านเพย์โหลดทุกรายการ
- ตัดสินใจด้วยโค้ดก่อนจ่ายเงินซื้อโมเดล นโยบายคือไฟล์ข้อความและเราเตอร์ และจะทำงานก่อนการแสดงผล
- อ่านทุกอย่างที่ไม่ใช่ข้อความให้เป็นข้อความก่อนที่จะเข้าถึงคลังข้อมูล โดยมีชื่อไฟล์อยู่ภายในบรรทัดเพื่อให้ไฟล์กลับมาได้
พรอมต์ไม่สามารถรับประกันลำดับ การหยุด หรือการตรวจสอบได้ แต่กราฟทำได้
ขั้นตอนถัดไป
VertexAiSessionServiceสำหรับเซสชันที่คงอยู่ได้นานกว่าอินสแตนซ์ และยกเลิก--session-affinity- เว็บฮุคที่ตอบรับการเรียกใช้ของโปรแกรมแสดงผลแทนที่จะเป็น Poller:
FunctionResponse(id=...)เหมือนกัน แต่ผู้ส่งต่างกัน RequestInputอีกครั้งก่อนเผยแพร่ สคีมาคือแบบฟอร์ม จึงเป็นโหนดเดียวและไม่มีโค้ดหน้าเว็บ- ความคิดเห็น บันทึกเสียง และคลิปใหม่ๆ ของผู้ชมจะได้รับการเพิ่มลงในคลังข้อมูลหลังจากเปิดตัวทุกครั้ง พร้อมกับโปสเตอร์ที่ส่งมาด้วย
ล้างข้อมูล
ทุกอย่างที่ห้องทดลองนี้สร้างขึ้นจะอยู่ในโปรเจ็กต์ที่สร้างขึ้นในขั้นตอนที่ 4 ดังนั้นวิธีล้างข้อมูลที่ประหยัดที่สุดคือการลบโปรเจ็กต์ ใน Cloud Shell ให้ทำดังนี้
gcloud projects delete $(cat ~/project_id.txt)
หากต้องการเก็บโปรเจ็กต์ไว้และนำเฉพาะส่วนที่ต้องชำระเงินออก ให้ทำดังนี้ บริการ Cloud Run (gcloud run services delete vibestudio --region us-central1), คลังข้อมูล RAG และคลังความทรงจำ (ชื่อทรัพยากรอยู่ใน runs/ragcorpus.json และ runs/memorybank.json และหน้า Agent Engine และ RAG Engine ของคอนโซลจะลบออก) และอิมเมจคอนเทนเนอร์ใน Artifact Registry