ตัว บทความก่อนหน้า กล่าวถึงการทำให้เอเจนต์ตัวเดียวทำงานได้อย่างน่าเชื่อถือ: วงรอบการทำงาน การส่งต่อไปยังเครื่องมือ การย่อบริบท และเซสชันที่ทนต่อการล่ม ชั้นนั้นตอบคำถามว่า “เอเจนต์ตัวเดียวทำงานให้เสร็จโดยไม่ล้มได้อย่างไร?” บทความนี้กล่าวถึงชั้นที่อยู่เหนือขึ้นไป ว่าเกิดอะไรขึ้นเมื่อเอเจนต์ตัวเดียวไม่พอ และต้องแบ่งงานให้ ทีม.
นี่คือสิ่งที่ผู้คนมักหมายถึงเมื่อพูดถึง การประสานงานหลายเอเจนต์: เอเจนต์หลักที่ดูแลบทสนทนาแบ่งคำขอเป็นส่วน ๆ ส่งแต่ละส่วนให้เอเจนต์ย่อยเฉพาะทาง รอส่วนที่จำเป็นให้เสร็จก่อนเริ่มขั้นถัดไป ส่งต่อผลลัพธ์ และคอยให้ทุกอย่างเดินต่อได้เมื่อขั้นตอนหนึ่งล้มเหลว Orkas ทำทั้งหมดนี้บนเครื่องของผู้ใช้เอง ด้านล่างคือวิธีสร้างชั้นประสานงานนี้ โค้ดผ่านการลบข้อมูลเฉพาะและปรับให้ใช้เป็นตัวอย่างทั่วไปแล้ว แต่โครงสร้างเป็นของจริง
เอเจนต์หลักและเอเจนต์ย่อย
ให้นึกถึงทีมเล็ก ๆ ที่มีสายบังคับบัญชาชัดเจน โดย เอเจนต์หลัก (เราเรียกว่าผู้บัญชาการ) ดูแลบทสนทนาและบริบทโดยรวม มันไม่ได้ทำงานทุกอย่างเอง หน้าที่ของมันคือตัดสินใจว่า ต้องทำอะไร ตามลำดับใด และใครเป็นคนทำ. ส่วน เอเจนต์ย่อย เป็นผู้เชี่ยวชาญ แต่ละตัวมีพรอมป์ต์ระบบ เครื่องมือที่อนุญาต และชุดทักษะของตัวเอง เอเจนต์ย่อยเก่งงานส่วนหนึ่งและจะถูกเรียกเมื่อมีงานส่วนนั้น
มีสองสิ่งที่ทำให้เรื่องนี้เป็นมากกว่าคำฮิต อย่างแรก เอเจนต์ย่อยและทักษะเป็น หน่วยหลักที่ระบบรองรับโดยตรง, ไม่ใช่ลูกเล่นพรอมป์ต์ เอเจนต์ย่อยคือเอเจนต์จริงที่ตั้งค่าแยกต่างหาก และการส่งงานให้คือการส่งต่องานจริงพร้อมบริบทของตัวเอง อย่างที่สอง การประสานงานไม่ได้ฝากไว้กับความตั้งใจดีของโมเดล แต่ขับเคลื่อนด้วยสิ่งที่บันทึกไว้อย่างชัดเจน ซึ่งระบบเป็นผู้รักษาความถูกต้อง ไม่ใช่โมเดล สิ่งนั้นคือแผน
แผนคือกราฟ ไม่ใช่สคริปต์
เมื่อเอเจนต์หลักตัดสินว่าคำขอต้องใช้มากกว่าหนึ่งขั้นตอน มันจะเขียน แผน. แผนไม่ใช่ข้อความอิสระและไม่ใช่เช็กลิสต์เส้นตรง แต่เป็นกราฟการพึ่งพาขนาดเล็ก (DAG) แต่ละโหนดคือขั้นตอน และแต่ละขั้นตอนมีทุกอย่างที่ตัวประสานงานต้องใช้เพื่อส่งงาน:
interface PlanStep {
index: number; // 1-based, stable, never renumbered
title: string; // human-readable, shown in the UI
assignee: string; // who runs it: "user" | "commander" | a sub-agent
input?: string; // the dispatch payload — a template (see below)
wait_for?: number[]; // upstream step indexes; defaults to [index - 1]
on_failure?: "abort_plan" | "continue" | "ask_commander";
// --- runtime state, owned by the orchestrator, NOT written by the model ---
status: "pending" | "in_progress" | "done" | "failed" | "skipped" | "blocked";
output_summary?: string; // short summary of what the step produced
output_files?: string[]; // files the step produced
failure_reason?: string;
}ตัว wait_for คือฟิลด์ที่เปลี่ยนรายการให้เป็นกราฟ โดยปริยาย ขั้นตอนหนึ่งจะรอขั้นก่อนหน้า (เป็นสายง่าย ๆ) แต่ขั้นตอนสามารถระบุว่าพึ่งพาหลายขั้นก่อนหน้าได้ เช่น “สรุป” อาจรอทั้ง “วิจัยตลาด” และ “สำรวจคู่แข่ง” นั่นคือรูปเพชร ไม่ใช่เส้นตรง และตัวประสานงานก็มองมันเช่นนั้น
ใครเป็นเจ้าของข้อเท็จจริง: ตัวประสานงาน ไม่ใช่โมเดล
ดูการแบ่งส่วนในโครงสร้างนั้นอีกครั้ง โมเดลกรอก เจตนา — ชื่อขั้นตอน ผู้รับมอบหมาย ข้อมูลนำเข้า และการพึ่งพา — เมื่อเขียนแผนครั้งแรก แต่ทุกอย่างเกี่ยวกับ สถานะการดำเนินงาน — status, output_summary, failure_reason — เป็นของตัวประสานงานแต่เพียงผู้เดียว โมเดลเสนอแผนครั้งเดียว และไม่มีสิทธิ์ทำเครื่องหมายขั้นตอนของตัวเองว่า “เสร็จแล้ว”
การแยกส่วนนี้เป็นความตั้งใจ และเป็นการตัดสินใจที่สำคัญที่สุดในการออกแบบทั้งหมด โมเดลภาษาสามารถประกาศอย่างร่าเริงว่า “ขั้นตอน 3 เสร็จแล้ว” ทั้งที่ขั้นตอน 3 เกิดข้อผิดพลาด หรือหลงลืมว่าขั้นตอนใดยังค้างอยู่เมื่อคุยกันยาว ๆ หากสถานะอยู่ในหัวของโมเดล แผนจะค่อย ๆ ห่างจากความจริง การทำให้สถานะเป็นข้อมูลแบบมีโครงสร้างที่มีเพียงตัวดำเนินงานเขียนได้ และเขียนเฉพาะเมื่อขั้นตอนจบจริง ทำให้แผนยังสะท้อนสิ่งที่เกิดขึ้นได้อย่างถูกต้อง โมเดลกำหนดรูปแบบงาน ส่วนรันไทม์ตัดสินข้อเท็จจริงเกี่ยวกับความคืบหน้า
ส่งงานให้ขั้นตอนที่พร้อม
เมื่อมีแผนแล้ว กลไกเล็ก ๆ ที่เรียกว่าตัวดำเนินงานจะขับเคลื่อนแผนต่อไป การทำงานหลักคือ “ค้นหาขั้นตอนที่พร้อมแล้วส่งงานให้” ขั้นตอนหนึ่งจะ พร้อม เมื่อยังอยู่ในสถานะ pending และทุกขั้นตอนที่มันรอเข้าสู่สถานะสิ้นสุดที่ถือว่าสำเร็จเพียงพอแล้ว:
function findReadySteps(plan): PlanStep[] {
return plan.steps.filter((s) => {
if (s.status !== "pending") return false;
const deps = s.wait_for ?? (s.index > 1 ? [s.index - 1] : []);
return deps.every((d) => isTerminal(plan.step(d).status)); // done or skipped
});
}กระบวนการนี้ไม่ได้ทำงานตามตัวจับเวลา แต่ตอบสนองต่อเหตุการณ์ ทุกครั้งที่ขั้นตอนจบ ไม่ว่าจะเป็นเอเจนต์ย่อยส่งผลกลับมาหรือผู้บัญชาการจบรอบสังเคราะห์ ตัวดำเนินงานจะปรับสถานะให้ตรงกัน โดยบันทึกผลของขั้นตอนที่เพิ่งจบ จากนั้นสแกนใหม่ว่าขั้นตอนใดพร้อมขึ้นจากผลนั้น แล้วส่งงานให้ การประสานงานก็คือวงรอบปรับสถานะนี้ที่ทำซ้ำไปจนไม่มีขั้นตอนเหลือ
ส่งงานผ่านแชตเดียวกัน ไม่ใช่ช่องทางแยก
นี่คือตัวเลือกที่ทำให้ระบบตรงกับสิ่งที่เห็น: การส่งงานให้ขั้นตอนไม่ได้ใช้ช่องทาง RPC ที่ซ่อนอยู่ แต่โพสต์ข้อความลงในบทสนทนากลุ่มเดียวกับที่ผู้ใช้กำลังดู, จากเอเจนต์หลัก โดย @-กล่าวถึงเอเจนต์ย่อย. สำหรับเอเจนต์ย่อย การได้รับมอบหมายงานไม่ต่างจากการถูกเรียกในแชต มันเพียงทำงานตามรอบปกติของตัวเอง ไม่มีเส้นทางดำเนินงานที่สองให้ต้องรักษาให้ตรงกับเส้นทางแรก
ผู้รับมอบหมายมีได้สามประเภท และแต่ละประเภทรับงานต่างกันเล็กน้อย:
- เอเจนต์ย่อย — กรณีทั่วไป ตัวดำเนินงานแปลงชื่อเอเจนต์เป็น id แล้วโพสต์
@<agent> <rendered input>จากผู้บัญชาการ เอเจนต์ย่อยรับข้อความแล้วทำงานหนึ่งรอบเต็ม - ผู้ใช้ — เมื่อขั้นตอนต้องการข้อมูลจากมนุษย์จริง ๆ ขั้นตอนนั้นจะกลายเป็นแบบฟอร์มและหยุดแผนชั่วคราว (จะอธิบายเพิ่มเติมด้านล่าง) ขั้นตอนปลายทางทั้งหมดจะไม่เดินต่อจนกว่าผู้ใช้จะตอบ
- ตัวผู้บัญชาการเอง — สำหรับขั้นตอนสังเคราะห์หรือตัดสินใจ (“อ่านทุกอย่างข้างต้นแล้วเขียนสรุป”) นี่คือการปลุกให้ทำงานภายในที่ไม่โพสต์ข้อความซ้ำซ้อนให้ผู้ใช้เห็น เอเจนต์หลักเพียงเริ่มทำงานหนึ่งรอบพร้อมบริบทที่รวบรวมมา
ส่งต่อบริบทจากขั้นตอนหนึ่งไปยังขั้นตอนถัดไป
ทีมจะมีประโยชน์ก็ต่อเมื่องานไหลระหว่างสมาชิกได้ กลไกที่ใช้คือแม่แบบ input เมื่อเอเจนต์หลักเขียนขั้นตอน ข้อมูลนำเข้าไม่ได้เป็นสตริงตายตัว แต่สามารถอ้างอิงผลลัพธ์ก่อนหน้าได้ และตัวดำเนินงานจะแทนค่าการอ้างอิงเหล่านั้นตอนส่งงาน:
// step 3.input, as written by the lead agent
"Using the findings below, draft the launch note.\n\n{{step_1.output_summary}}"
// what the sub-agent actually receives at dispatch time
"Using the findings below, draft the launch note.\n\n- Market is growing ~20% YoY; two incumbents…"สังเกตสิ่งที่ส่งต่อไป: output_summary, หรือ สั้น ๆ คือสรุปของแต่ละขั้นตอนที่เสร็จแล้ว ไม่ใช่บทสนทนาทั้งหมด นี่คือการตัดสินใจด้านงบประมาณ และใช้แนวคิดเดียวกับการย่อบริบทของระบบควบคุมเอเจนต์ หากทุกขั้นตอนปลายทางรับประวัติทั้งหมดก่อนหน้าแบบครบทุกโทเคน บริบทจะพองตัวและค่าใช้จ่ายจะพุ่งขึ้นหลังส่งต่อเพียงไม่กี่ทอด สรุปช่วยให้แต่ละการส่งต่อมีต้นทุนต่ำ และให้เอเจนต์ย่อยแต่ละตัวจดจ่อกับสิ่งที่ต้องใช้จากขั้นก่อนหน้า แทนที่จะต้องอ่านว่าขั้นก่อนหน้าได้ผลนั้นมาอย่างไร ข้อความแรกของผู้ใช้และไฟล์แนบจะถูกส่งต่อไปด้วย ดังนั้นแม้ขั้นตอนที่อยู่ถัดไปสามทอดก็ยังรู้คำขอเดิม
ทำตามลำดับเป็นค่าเริ่มต้น — และเหตุผล
คุณอาจคาดว่าเมื่อหลายขั้นตอนพร้อมพร้อมกัน เช่น สองแขนงของรูปเพชร ตัวประสานงานจะเริ่มทั้งหมดพร้อมกัน มันทำได้ แต่ปัจจุบันมันส่งงานให้ ขั้นตอนที่พร้อมทีละขั้น, โดยเลือกดัชนีที่มาก่อน และให้ส่วนที่เหลือรอการปรับสถานะรอบถัดไป การให้ทั้งทีมทำงานทีละงานอย่างเคร่งครัดเป็นทางเลือกที่ตั้งใจและระมัดระวัง และควรอธิบายเหตุผลอย่างตรงไปตรงมา
เหตุผลคือความถูกต้องเมื่อทำงานพร้อมกัน ลองนึกภาพเอเจนต์ย่อยสองตัวเสร็จแทบจะพร้อมกัน ทั้งสองเหตุการณ์เรียกให้ปรับสถานะ ทั้งสองรอบอ่านแผน และทั้งสองเห็นขั้นตอนปลายทางเดียวกันยังอยู่ที่ pending — แล้วทั้งคู่ก็ส่งงานให้ ตอนนี้ขั้นตอนเดียวกันจึงทำงานสองครั้ง เพื่อไม่ให้เกิดเรื่องนี้ ทุกวงรอบอ่าน-แก้ไข-ส่งงานของบทสนทนาหนึ่งจะถูกเรียงให้ทำทีละรอบภายใต้ล็อกประจำบทสนทนา:
// all state-changing paths for one conversation run under one mutex
planLock(uid, cid).runExclusive(async () => {
const plan = await readPlan(uid, cid);
applyOutcomeOfFinishedStep(plan); // mark done / failed / skipped
await dispatchReady(plan); // dispatch the next ready step
});ล็อกรับประกันว่า “บันทึกสิ่งที่เสร็จแล้ว” และ “ตัดสินใจว่าจะทำอะไรต่อ” เกิดเป็นหน่วยเดียวที่แบ่งแยกไม่ได้ ขั้นตอนปลายทางจึงไม่มีวันถูกส่งงานให้ซ้ำ เมื่อมีล็อกนี้แล้ว การส่งงานทีละขั้นเป็นวิธีที่เรียบง่ายที่สุดซึ่งเห็นได้ชัดว่าถูกต้อง การกระจายงานแบบขนานจริงเป็นส่วนขยายที่พัฒนาต่อจากฐานนี้ได้ แต่รากฐานคือตัวดำเนินงานที่ทำตามลำดับและไม่มีปัญหาแย่งกันทำงาน และการจัดลำดับความสำคัญเช่นนี้ คือถูกต้องก่อน เร็วทีหลัง เป็นประเด็นสำคัญ
เมื่อขั้นตอนเกิดปัญหา
เมื่อทำงานบนเครื่องจริงกับ API โมเดลภายนอก ความล้มเหลวเป็นเรื่องปกติ และตัวประสานงานจะแยกเป็นหลายกรณี แทนที่จะจัดการทุกข้อผิดพลาดเหมือนกัน
ก่อนอื่นจะตรวจว่าความล้มเหลวนั้นเป็นเพียง เหตุขัดข้องชั่วคราว — การเชื่อมต่อหลุด การจำกัดอัตราคำขอ หรือความขัดข้องชั่วคราว หากเป็นเช่นนั้น และขั้นตอนยังใช้โควตาการลองใหม่จำนวนเล็กน้อยไม่หมด ระบบจะย้อนสถานะขั้นตอนกลับอย่างเงียบ ๆ เป็น pending เพื่อให้การตรวจปรับสถานะรอบถัดไปส่งขั้นตอนนั้นไปทำงานอีกครั้ง (กลไกนี้อยู่ เหนือ การลองใหม่ภายในการรันของระบบควบคุมเอเจนต์เอง แผนจะส่งงานใหม่ก็ต่อเมื่อเอเจนต์ลองจนครบแล้ว โดยมีเพดานตายตัวเพื่อไม่ให้ขั้นตอนที่เสียจริงวนซ้ำไม่รู้จบ)
หากเป็นความล้มเหลวจริง นโยบายที่ขั้นตอนประกาศไว้ใน on_failure จะกำหนดว่าทีมทำอะไรต่อ:
abort_plan— ขั้นตอนนี้สำคัญมากจนงานถัดไปไม่มีความหมายหากขาดมัน ทำเครื่องหมายว่าล้มเหลว แล้วส่งผลต่อเนื่อง: ทุกขั้นตอนที่ยังรอดำเนินการจะถูกทำเครื่องหมายเป็นskippedแผนหยุดอย่างเป็นระเบียบ แทนที่จะเดินหน้าต่อบนรากฐานที่ขาดหายcontinue— ขั้นตอนนี้เป็นทางเลือก ทำเครื่องหมายเป็นskippedแล้วปล่อยให้ขั้นตอนถัดไปดำเนินต่อเสมือนว่าขั้นตอนนี้ไม่ได้สร้างผลลัพธ์ใด ๆask_commander(ค่าเริ่มต้น) — ไม่ยกเลิกหรือเดินหน้าต่อโดยไม่พิจารณา ทำเครื่องหมายว่าล้มเหลวแล้วปลุกเอเจนต์หัวหน้าขึ้นมาตรวจสอบสิ่งที่เกิดขึ้นและตัดสินใจว่าจะลองใหม่ด้วยวิธีอื่น หาทางเลี่ยง หรือหยุดแล้วถามผู้ใช้
ยังมีสถานะที่หกที่ควรกล่าวถึง: blocked เอเจนต์ย่อยที่กำลังทำขั้นตอนอยู่ อาจพบว่าต้องการบางอย่างที่มีเพียงผู้ใช้เท่านั้นที่ให้ได้ แล้วแสดงแบบฟอร์มหรือคำถาม ขั้นตอนนั้นไม่ได้ล้มเหลว แต่จะเปลี่ยนเป็น blocked และทั้งแผนจะหยุดชั่วคราว ทันทีที่ผู้ใช้ตอบ ตัวดำเนินการจะตรวจปรับสถานะ แล้วทีมจะทำงานต่อจากจุดที่ค้างไว้พอดี แผนที่ติดขัดคือแผนที่พักอยู่ ไม่ใช่แผนที่เสีย
ทุกขั้นตอนคือการรันเอเจนต์เต็มรูปแบบ
ขอเชื่อมกลับไปยังบทความก่อนหน้า เมื่อระบบประสานงานส่งขั้นตอนไปให้เอเจนต์ย่อย เอเจนต์ย่อยนั้นไม่ได้รันกระบวนการแบบลดทอน แต่รัน วงจรควบคุมเอเจนต์เต็มรูปแบบ: มีวงจรการรันแบบสตรีมของตัวเอง การเรียกใช้เครื่องมือของตัวเอง หน้าต่างบริบทพร้อมการย่อบริบทของตัวเอง และเซสชันที่ทนต่อการล่มของตัวเอง ระบบประสานงานวางตัวอย่างชัดเจน อยู่บน ระบบรันเอเจนต์เดี่ยว โดยไม่เข้าไปยุ่งกับภายใน เอเจนต์หัวหน้ากำหนดรูปแบบงานและลำดับการส่งต่องาน ส่วนเอเจนต์ย่อยแต่ละตัว เมื่อได้รับส่วนงานแล้ว ก็เป็นเอเจนต์ที่สมบูรณ์ในตัวเอง
การแบ่งชั้นเช่นนี้ทำให้เนื้อหาทั้งสองบทความต่อกันได้ ระบบควบคุมทำให้เอเจนต์หนึ่งตัวเชื่อถือได้สำหรับงานหนึ่งงาน ส่วนระบบประสานงานนำเอเจนต์ที่เชื่อถือได้หลายตัวมารวมเป็นทีม เพื่อรับงานที่ใหญ่หรือหลากหลายเกินกว่าเอเจนต์ตัวใดตัวหนึ่งจะทำได้
การตัดสินใจสำคัญบางประการ
ระบบประสานงานเป็นเจ้าของสถานะการทำงาน โมเดลเป็นเจ้าของเจตนา โมเดลเสนอแผน แต่มีเพียงระบบรันเท่านั้นที่ทำเครื่องหมายว่าขั้นตอนเสร็จ ล้มเหลว หรือถูกข้ามได้ และทำได้เพื่อตอบสนองต่อสิ่งที่เกิดขึ้นจริงเท่านั้น ขอบเขตนี้ทำให้แผนสะท้อนความจริงอย่างตรงไปตรงมา แทนที่จะเป็นการคาดเดาในแง่ดีของโมเดล
ส่งงานผ่านบทสนทนาเดียวกัน ไม่ใช่ช่องทางแยก ขั้นตอนที่ส่งไปทำงานเป็นเพียงข้อความจากเอเจนต์หัวหน้าถึงเอเจนต์ย่อย มีเส้นทางการทำงานเดียว ไม่มีสิ่งซ่อนเร้นที่อาจคลาดเคลื่อนกัน และผู้ใช้ดูทีมทำงานได้ในเธรดเดียวกับที่กำลังอ่านอยู่
ส่งบทสรุประหว่างขั้นตอน ไม่ใช่บันทึกบทสนทนาทั้งหมด การส่งต่องานแต่ละครั้งแนบบทสรุปสั้น ๆ ของผลลัพธ์จากขั้นตอนก่อนหน้า ช่วยควบคุมโควตาบริบทตลอดสายงานยาว ๆ และทำให้เอเจนต์ย่อยแต่ละตัวจดจ่อกับสิ่งที่จำเป็น แทนที่จะสนใจว่าเอเจนต์ก่อนหน้าทำอย่างไรจึงได้ผลลัพธ์นั้น
ให้ถูกต้องก่อน แล้วค่อยขนาน การล็อกแยกตามบทสนทนาทำให้ทุกวงจรอ่าน–แก้ไข–ส่งงานเกิดขึ้นตามลำดับ และส่งขั้นตอนไปทำงานทีละขั้น การทำงานตามลำดับอย่างเคร่งครัดเป็นเวอร์ชันที่เห็นได้ชัดว่าปลอดจากภาวะแข่งขันที่ทำให้ส่งงานซ้ำ ส่วนการกระจายงานแบบขนานเป็นการเพิ่มประสิทธิภาพบนรากฐานที่ถูกต้องอยู่แล้ว
ส่งท้าย
แก่นของชั้นประสานงานใน Orkas ไม่ได้มีอัลกอริทึมแปลกใหม่ คุณค่าของมันอยู่ที่การรักษาขอบเขตไม่กี่ข้ออย่างมั่นคง: แผนเป็นกราฟความขึ้นต่อกันแทนที่จะเป็นสคริปต์ ระบบรันเป็นเจ้าของสถานะการทำงานแทนโมเดล การส่งงานไหลผ่านแชตเดียวกับที่ผู้ใช้กำลังดู บริบทส่งต่อระหว่างขั้นตอนในรูปบทสรุป และตัวดำเนินการให้ความสำคัญกับความถูกต้องก่อนการทำงานพร้อมกัน แต่ละข้อเรียบง่ายในตัวเอง เมื่อรวมกันจึงเปลี่ยนเอเจนต์เดี่ยวที่เชื่อถือได้ให้เป็นทีมที่แบ่งงาน ส่งต่องาน และฟื้นตัวได้เมื่อบางส่วนผิดพลาด
หากต้องการอ่านเรื่องชั้นที่อยู่ใต้ชั้นนี้ โปรดอ่าน วิธีออกแบบเอเจนต์เดี่ยวให้ทำงานได้อย่างน่าเชื่อถือ หากต้องการอ่านเรื่องชั้นที่ทำให้เอเจนต์แต่ละตัวเก่งขึ้นเมื่อใช้งาน โปรดอ่าน เอเจนต์ Orkas เรียนรู้จากงานของตัวเองอย่างไร และหากคุณอยากสั่งงานชั้นนี้มากกว่าสร้างขึ้นเอง Orkas มีให้ในรูปแบบ ระบบประสานงานเอเจนต์ AI แบบโอเพนซอร์ส ที่รันบนเครื่องของคุณเอง