Parallelization — หั่นขนาน แล้วรวมผล
บท3 คุณประกอบ workflow สองตัวที่เดินเป็น เส้นตรง — prompt chaining (ทำทีละขั้นตามลำดับ) และ routing (จัดประเภทหนึ่งครั้งแล้วส่งต่อไปทางเดียว) ทั้งคู่ยังเป็น loop เดี่ยวๆ ที่เรียก model ทีละครั้ง บทนี้เพิ่มมิติใหม่ที่คอร์สยังไม่เคยแตะ — การเรียก model หลายตัว พร้อมกัน แล้วเอาผลมารวม นี่คือ multi-actor pattern ตัวแรกของคอร์ส และเพราะเป็นตัวแรก มันจึงเป็นบทที่เราต้อง เปิดด้วยกติกา ก่อนเปิด code
บทนี้ต่อยอด agent Order ตัวเดิมจากคอร์ส #15 (พร้อม context layer จาก #16) — repo kaen-food-ordering (กำลังจัดทำ) code ที่แสดงคือ รูปทรง fan-out → aggregate บน IChatClient เดิม ไม่มี tool หรือ store ใหม่ กติกา API ฝั่งแชตทุกข้อจากบท1 ยังยึดเหมือนเดิม: model id เปล่า claude-opus-4-8 (ตัวอย่าง ณ 2026-07), ห้ามส่ง temperature, และอ่านคำตอบผ่าน resp.Messages[].Contents[] / resp.Text (convenience) ไม่ใช่สมมติว่ามี .text ระดับบนสุด
เปิดด้วยกติกา: 1 agent 1 loop ยังเป็นค่าเริ่มต้น
หัวข้อที่มีชื่อว่า “เปิดด้วยกติกา: 1 agent 1 loop ยังเป็นค่าเริ่มต้น”ก่อนพูดถึง fan-out แม้แต่บรรทัดเดียว ต้องตอกเสาต้นนี้ให้แน่น เพราะบทนี้ (และบท5) เสี่ยงที่สุดที่จะอ่านผิดเป็น “agent จริงๆ ต้องแตกเป็นหลายตัว” — ซึ่ง ผิด
คอร์สนี้สอน workflowWorkflowตามนิยาม BEA: 'systems where LLMs and tools are orchestrated through predefined code paths' — เส้นทางถูกกำหนดไว้ใน code จึงคาดเดาได้และ test ได้ (prompt chaining, routing, parallelization, orchestrator-workers, evaluator-optimizer) เป็นที่มาของคุณค่าส่วนใหญ่ในโปรดักชัน และเป็นค่าเริ่มต้นที่ควรเอื้อมหาก่อน autonomous loop ตรงข้ามกับ Autonomous AgentArchitecture แบบ multi-actor ก็จริง แต่ loopAgent Loopวงจร runtime ที่ harness วนซ้ำ: gather context → take action → verify work → repeat (Claude Agent SDK) — เรียก model, dispatch tool ที่ถูกเรียก, ผนวกผลกลับเข้า context, ตรวจเงื่อนไขหยุด แล้ววนใหม่ ลึกกว่า loop ของ #15 บท6 ที่หยุดด้วย predicate เดียว ตรงที่เพิ่มเฟส verify — หลัง write action (เช่น issueRefund) อ่าน ground truth ซ้ำ (getOrder) ก่อนประกาศว่าเสร็จArchitecture เดี่ยวที่มีขอบเขตยังเป็น ค่าเริ่มต้น เสมอ Anthropic วางหลักไว้ชัดใน Building Effective Agents — “When building applications with LLMs, we recommend finding the simplest solution possible, and only increasing complexity when needed.” — และเจาะจงกว่านั้นว่า “For many applications, however, optimizing single LLM calls with retrieval and in-context examples is usually enough.” คือ การหั่นงานเป็นหลาย call ที่รันพร้อมกันไม่ใช่ อัปเกรด ที่คุณเอื้อมหา แต่เป็น ข้อยกเว้น ที่คุณต้อง “ได้มา” ด้วยเหตุผล
นี่คือกฎเดียวกับที่คอร์ส #12 (agent เป็น bounded context) ตั้งเป็น default “1 agent 1 loop” และที่ #16 เรียก sub-agent ว่า ทางเลือกสุดท้าย — บทนี้แค่พูดมันใหม่ในภาษา runtime: structure เป็นสิ่งที่ต้องได้มา ไม่ใช่รีเฟล็กซ์ เก็บประโยคนี้ไว้ แล้วเราจะกลับมาปิดด้วยมันตอนท้ายบท
Parallelization = sectioning + voting
หัวข้อที่มีชื่อว่า “Parallelization = sectioning + voting”ParallelizationParallelizationworkflow ตาม BEA: LLM หลายตัวทำงานพร้อมกันแล้วรวมผลด้วย code — สองแบบ: sectioning (หั่นเป็น subtask อิสระ รันขนานด้วย `Task.WhenAll`) และ voting (รันงานเดิม N ครั้งแล้วเอาเสียงข้างมาก) ⚠️ เป็นข้อยกเว้นที่ต้อง 'ได้มา' ไม่ใช่ค่าเริ่มต้น: ใช้ได้ต่อเมื่อ subtask เป็นอิสระต่อกันจริง ๆ ถ้า share context หรือพึ่งพากันให้ยุบกลับเป็น loop เดียว (#12) และพึงจำว่ามันคูณ token ตามจำนวน callProcess คือ workflow ที่ Anthropic นิยามไว้ว่า “LLMs can sometimes work simultaneously on a task and have their outputs aggregated programmatically.” — model หลายตัวทำงาน พร้อมกัน แล้วเอาผลมา รวมด้วย code (ไม่ใช่ให้ model อีกตัวมาตัดสินใจว่าจะรวมยังไง — การรวมเป็น code ของคุณ) มันมีสองรูปแบบที่ต่างกันชัด:
“Sectioning: Breaking a task into independent subtasks run in parallel. Voting: Running the same task multiple times to get diverse outputs.” — Anthropic (BEA)
- sectioning — หั่นงานหนึ่งเป็น subtask อิสระ หลายชิ้นที่ไม่พึ่งพากัน แล้วรันขนาน บน Order agent: ตรวจ “สิทธิ์คืนเงิน” + “SLA เดลิเวอรีถูกละเมิดไหม” + “ระดับสมาชิกของลูกค้า” — สามคำถามที่ตอบได้แยกกันโดยไม่ต้องรอผลของกันและกัน
- voting — รัน งานเดิม ซ้ำ N ครั้งแล้วรวมด้วยเสียงข้างมาก บน Order agent: เมื่อคำตัดสิน “ออเดอร์นี้เข้าเงื่อนไขคืนเงินไหม” จาก call เดียวไม่น่าเชื่อถือพอ ให้ถาม 5 ครั้งแล้วเอาเสียงข้างมาก
Anthropic สรุปเงื่อนไขที่มันเหมาะไว้ว่า “Parallelization is effective when the divided subtasks can be parallelized for speed, or when multiple perspectives are needed.” — sectioning ซื้อ ความเร็ว (subtask อิสระรันพร้อมกัน) ส่วน voting ซื้อ หลายมุมมอง (ลดความแปรปรวนของ call เดียว)
flowchart TD
IN["งานเข้า<br/>เช่น ตรวจ 3 มุมของออเดอร์"]
IN --> S{"หั่นเป็น subtask อิสระได้ไหม"}
S -->|"ไม่ — share context หรือพึ่งพากัน"| ONE["ยุบเป็น loop เดียว<br/>ค่าเริ่มต้น #12"]
S -->|"ใช่ — อิสระจริง"| FAN["fan-out ขนาน<br/>Task.WhenAll"]
FAN --> A["call 1<br/>สิทธิ์คืนเงิน"]
FAN --> B["call 2<br/>SLA เดลิเวอรี"]
FAN --> C["call 3<br/>ระดับสมาชิก"]
A --> AGG["รวมผลด้วย code<br/>aggregate"]
B --> AGG
C --> AGG
AGG --> OUT["คำตอบเดียว"]
classDef fan fill:#b91c1c,stroke:#7f1d1d,color:#f8fafc;
class FAN,A,B,C fan;
คำบรรยายภาพ: ทางแยกอยู่ที่คำถามเดียว — “หั่นเป็น subtask อิสระได้ไหม” ถ้าไม่ (share context หรือพึ่งพากัน) ให้ยุบกลับเป็น loop เดียวตามค่าเริ่มต้น ถ้าใช่ จึง fan-out ขนานด้วย Task.WhenAll (สามช่องสีแดงคือ call ที่รันพร้อมกัน) แล้วมาบรรจบที่ขั้น aggregate ที่ code ของคุณคุมเส้นทางไว้ตายตัว — voting รวมด้วย code ล้วน ส่วน sectioning อาจเรียก model ช่วยรวมก็จริง แต่ลำดับทั้งหมดเป็น path ที่ code กำหนด ต่างจาก orchestrator-workers (บท5) ที่ model เป็นผู้กำหนดการแตกงานและสังเคราะห์เอง
sectioning ใน C#: Task.WhenAll เหนือ call ที่อิสระ
หัวข้อที่มีชื่อว่า “sectioning ใน C#: Task.WhenAll เหนือ call ที่อิสระ”IChatClient แต่ละครั้งเรียกเป็น state อิสระ (ไม่ share mutable state ระหว่าง call) จึงยิงขนานได้ตรงๆ ด้วย Task.WhenAll แล้วรวมผลด้วย code:
using Anthropic; // AnthropicClient, .AsIChatClientusing Microsoft.Extensions.AI; // IChatClient, ChatOptions, ChatResponse
AnthropicClient anthropic = new(); // อ่าน ANTHROPIC_API_KEY จาก envIChatClient client = anthropic.AsIChatClient("claude-opus-4-8"); // bare id, ตัวอย่าง ณ 2026-07 — ไม่ต่อ suffix วันที่
// 3 subtask ที่ไม่พึ่งพากัน — เริ่มทั้งสามพร้อมกัน (ยังไม่ await ทีละตัว)Task<ChatResponse> refund = client.GetResponseAsync($"ตรวจสิทธิ์คืนเงินของ {orderId}", options, ct);Task<ChatResponse> delivery = client.GetResponseAsync($"ตรวจว่า {orderId} เกิน SLA เดลิเวอรีไหม", options, ct);Task<ChatResponse> loyalty = client.GetResponseAsync($"ระดับสมาชิกของลูกค้าออเดอร์ {orderId} คืออะไร", options, ct);
ChatResponse[] sections = await Task.WhenAll(refund, delivery, loyalty); // เวลารวม ≈ call ที่ช้าสุด ไม่ใช่ผลบวกของทั้งสาม
// ขั้น aggregate เป็น code ของคุณ — ที่นี่ให้ model รวมสามผลเป็นคำตัดสินเดียวChatResponse merged = await client.GetResponseAsync( "รวมสามผลนี้เป็นคำตัดสินเดียว:\n" + string.Join("\n---\n", sections.Select(s => s.Text)), options, ct);กำไรของ sectioning คือ latency: ถ้าแต่ละ call ใช้เวลา ~2 วินาที การรันขนานทำให้เวลารวมอยู่ที่ ~2 วินาที (call ที่ช้าสุด) ไม่ใช่ ~6 วินาที (ผลบวก) — แต่ จำนวน token ที่จ่ายคือผลบวกเต็มๆ ทั้ง3 call ความเร็วถูกลง แต่ต้นทุนไม่ลง จำแยกสองอย่างนี้ไว้
code ข้างบนอ่าน s.Text เพราะ subtask พวกนี้คืน ข้อความล้วน (สรุป/ตัดสิน ไม่เรียก tool) ChatResponse.Text คือ convenience concatenation ของ text ทุกชิ้นในคำตอบ ใช้กับผลข้อความล้วนได้สบาย แต่ถ้า subtask ตัวไหน เรียก tool เอง คุณต้องกลับไปไล่ resp.Messages[].Contents[] แล้ว match FunctionCallContent / FunctionResultContent เหมือนบท1 อย่าเผลอใช้ .Text แทนการอ่านรูปดิบเมื่อมี tool call อยู่ในคำตอบ
voting ใน C#: รูปทรงเดียวกัน N ครั้ง
หัวข้อที่มีชื่อว่า “voting ใน C#: รูปทรงเดียวกัน N ครั้ง”voting ใช้ Task.WhenAll แบบเดียวกัน แต่ยิง prompt เดิม N ครั้งแล้วรวมด้วยเสียงข้างมาก เหมาะเมื่อ call เดียวมีความแปรปรวนสูงเกินไปสำหรับการตัดสินที่มีผลข้างเคียง (เช่นคืนเงิน):
// รันคำตัดสินเดิม 5 ครั้ง แล้วเอาเสียงข้างมาก — ซื้อ "หลายมุมมอง" เพื่อลดความแปรปรวนRefundVerdict[] votes = await Task.WhenAll( Enumerable.Range(0, 5).Select(async _ => (await judge.GetResponseAsync<RefundVerdict>( $"ออเดอร์นี้เข้าเงื่อนไขคืนเงินไหม:\n{orderJson}", cancellationToken: ct)).Result));
RefundVerdict decision = MajorityVote(votes); // รวมด้วย code — คูณ token ไป 5 เท่าGetResponseAsync<T> คือ overload structured-output ที่คุณเห็นในบท3 (routing) — คืนผลเป็น type RefundVerdict ที่ parse แล้ว การรวม (MajorityVote) เป็น code ของคุณ ไม่ใช่ model และสังเกตราคา: voting 5 เสียง = จ่าย token ห้าเท่าของ call เดียว บิลนี้จะไปโผล่ที่ cost meter ในบท8 — voting ไม่ใช่ของฟรี มันซื้อความเชื่อถือด้วยเงิน
เมื่อไรที่ ไม่ ควร fan out — เส้นแบ่งอยู่ที่คำว่า “อิสระ”
หัวข้อที่มีชื่อว่า “เมื่อไรที่ ไม่ ควร fan out — เส้นแบ่งอยู่ที่คำว่า “อิสระ””นี่คือหัวใจของบท และเป็นจุดที่ต้องพูดตรงๆ Parallelization ใช้ได้ต่อเมื่อ subtask เป็นอิสระต่อกัน จริงๆ เท่านั้น — ถ้าพวกมัน share context หรือผลของตัวหนึ่งเป็น input ของอีกตัว มันไม่ขนาน มันเป็น chain และคุณต้องยุบกลับเป็น loop เดียว
ค่าเริ่มต้นจาก #12 ยังยืน: 1 agent 1 loop Parallelization ต้องการ subtask ที่ อิสระ ถ้า subtask share context หรือพึ่งพากัน คุณกำลังอยู่ในเคสที่ Anthropic ชี้ตรงๆ ว่าเป็น “[domains that] require all agents to share the same context or involve many dependencies between agents are not a good fit for multi-agent systems today.” (MARS, 2025-06-13) — ยุบกลับเป็น loop เดียว นี่คือกฎของ #16 พูดใหม่: structure เป็นสิ่งที่ต้องได้มา ไม่ใช่รีเฟล็กซ์
แล้วราคาล่ะ? multi-actor ไม่ใช่ของฟรี Anthropic วัดจากงานวิจัยของตัวเองว่า “agents typically use about 4× more tokens than chat interactions, and multi-agent systems use about 15× more tokens than chats.” — ⚠️ ตัวเลข 15× นี้เป็นข้อมูลจาก research workload ของ Anthropic เอง ลงวันที่ 2025-06-13 ไม่ใช่ค่าคงที่ที่การันตีทุก domain แต่ทิศทางชัด: fan-out คูณต้นทุน token ขึ้นเป็นเท่าตัว การตัดสินใจ fan-out จึงต้องมีคุณค่าที่ คุ้ม กับ token ที่จ่ายเพิ่ม ไม่ใช่ทำเพราะ “ดูเป็น agent มากกว่า”
map กลับลงบน Order agent ให้เห็นชัด: core loop ของมัน — get order → ตัดสินสิทธิ์ → คืนเงิน — เป็น dependency chain ที่ share context (จะตัดสินสิทธิ์ได้ต้องมี order ก่อน จะคืนเงินได้ต้องตัดสินก่อน) นั่นคือเคสที่ห้าม fan-out ตรงๆ มันต้องเป็น loop เดียว แต่ ขั้น รวบรวมข้อมูลประกอบการตัดสิน (สิทธิ์คืนเงิน / SLA / ระดับสมาชิก) เป็น subtask อิสระจริง — ตรงนั้น sectioning ถึงจะมีที่ยืน เส้นแบ่งไม่ใช่ “agent นี้ควรขนานไหม” แต่เป็น “ขั้นตอนไหน ของมันที่อิสระจริง”
“parallelization workflow” (LLM call หลายตัว รันขนานด้วย Task.WhenAll — ที่บทนี้สอน) เป็นคนละเรื่องกับ “การรัน tool หลายตัว ขนานภายในเทิร์นเดียว” ของ middleware loop อันหลังคือ knob FunctionInvokingChatClient.AllowConcurrentInvocation (ค่าเริ่มต้น false) ที่คุมว่าเมื่อ model ขอ tool call หลายตัวในเทิร์นเดียว จะ dispatch พร้อมกันหรือทีละตัว — มันคือ concurrency ภายใน 1 loop ไม่ใช่ workflow ที่มีหลาย LLM call อย่าเอาสองอันนี้มาปนกัน
สรุปก่อนไปต่อ
หัวข้อที่มีชื่อว่า “สรุปก่อนไปต่อ”Parallelization คือ workflow ที่เอา LLM หลายตัวมารันพร้อมกันแล้วรวมผลด้วย code — sectioning หั่น subtask อิสระเพื่อซื้อความเร็ว, voting รันงานเดิม N ครั้งเพื่อซื้อความเชื่อถือ ทั้งคู่เขียนด้วย Task.WhenAll เหนือ IChatClient เดิม แต่ประตูเข้าของมันคือคำถามเดียว — subtask อิสระ จริงไหม — และราคาของมันคือ token ที่คูณขึ้น (MARS วัด ~15× สำหรับ multi-agent) ค่าเริ่มต้นยังเป็น loop เดียว fan-out เป็นข้อยกเว้นที่ต้องได้มา
บทหน้าเราขยับไปอีกขั้นที่ ยาก กว่า — orchestrator-workers ที่ subtask ไม่ถูกกำหนดไว้ล่วงหน้า แต่ให้ model กลางเป็นคนหั่นเอง นั่นคือจุดที่การ reconcile กับ #12 เข้มข้นที่สุด และที่ป้ายราคา 15× ต้องแขวนคู่ทุกครั้งที่ pattern โผล่ 🔁 orchestrator-workers (บท5)
บทนี้อิงต้นทางที่ลงวันที่กำกับ อ่านต่อได้โดยตรง:
- Anthropic, “Building Effective Agents” (2024-12-19) — นิยาม parallelization (“work simultaneously… aggregated programmatically”), สองรูปแบบ sectioning/voting, เงื่อนไขที่มันเหมาะ, และหลัก “finding the simplest solution possible” ที่เป็นสมอของการ reconcile
- Anthropic, “How we built our multi-agent research system” (2025-06-13) — ป้ายราคา “multi-agent systems use about 15× more tokens than chats” และเคสที่ multi-agent ไม่ เหมาะ (“share the same context… many dependencies between agents”) ⚠️ ตัวเลขจาก research workload ของ Anthropic เอง เป็นสัญญาณลงวันที่ ไม่ใช่กฎ
- Microsoft Learn, “Use the IChatClient interface” —
GetResponseAsync,ChatResponse.Text(convenience concat) และGetResponseAsync<T>overload structured-output ที่ voting ใช้; แต่ละ call เป็น state อิสระจึงยิงขนานด้วยTask.WhenAllได้ - Microsoft Learn, “FunctionInvokingChatClient” (pkg v10.7.0, upd. 2026-06-12) — knob
AllowConcurrentInvocation(ค่าเริ่มต้นfalse) ที่คุม tool-call ขนานภายในเทิร์นเดียว — คนละเรื่องกับ parallelization workflow
เช็กความเข้าใจ — บทที่ 4
ข้อ 1 / 3Parallelization ตาม BEA มีสองรูปแบบอะไร?