ประกอบร่าง: autonomous agent กับเงื่อนไขหยุดแบบประกอบ
เจ็ดบทที่ผ่านมาคุณถอด loop ของ #15 ออกเป็นชิ้นๆ แล้วเปลี่ยนทีละ clause: บท1 วางกรอบว่าเงื่อนไขหยุดคือ นโยบายประกอบ ไม่ใช่ predicate เดียว, บท2 ลากเส้นแบ่ง workflow กับ agent แล้วตั้งด่านความเรียบง่ายก่อนเอื้อมหา agent, บท3–5 สำรวจ workflow pattern (chaining, routing, parallelization, orchestrator-workers) และเมื่อไรมันคุ้มค่า, บท6 ใส่ reflection แบบมีสติ, บท7 ทำให้ทนทานด้วย tool-error recovery / retry / timeout / idempotency บทนี้คือบทประกอบร่าง — เอาทุกชิ้นมารวมเป็น agent อิสระตัวเดียว คร่อม agent Order เดิม แล้วจ่ายคืนสัญญาที่บท1 ตั้งไว้: stop policy 5 clause ที่ทำงานจริง
Anthropic ปิดวงด้วยประโยคเดียวกับที่บท1 เปิด — “Agents are typically just LLMs using tools based on environmental feedback in a loop.” agent อิสระAutonomous Agentตามนิยาม BEA: 'systems where LLMs dynamically direct their own processes and tool usage, maintaining control over how they accomplish tasks' — model กำหนดทิศทางของตัวเองแบบ dynamic; harness + loop ของบท1 คือสิ่งที่ทำให้ระบบเป็น agent ไม่ใช่ workflow แลกความยืดหยุ่นมาด้วย latency + cost + โอกาสสะสม error จึงต้องคุ้มค่าก่อนจึงจะใช้ (ประกอบจริงในบท8)Architecture ที่เราจะประกอบก็แค่นั้น: augmented LLM (context จาก #16) วางอยู่ข้างใน loop ที่ มีขอบ (harness จาก #17) มนุษย์เป็นคนสตาร์ท จากนั้นมันวางแผนและเดินเองจนกว่าจะชน clause ใด clause หนึ่งของนโยบายหยุด
บทนี้ประกอบทุกอย่างจากบท1–7 เข้าเป็น LoopController ตัวเดียวคร่อม agent Order จากคอร์ส #15 (พร้อม context layer จาก #16) — repo kaen-food-ordering (กำลังจัดทำ) ไม่มี tool หรือ store ใหม่มีแต่ runtime ที่โตเต็มวัย: loop แบบ hand-rolled ที่ตรวจนโยบายหยุดแบบประกอบ บวกมิเตอร์วัด token ต่อ run กติกา API ฝั่งแชตทุกข้อยังยึดเดิมจากบท1 ทุกประการ
agent อิสระเริ่มและเดินอย่างไร
หัวข้อที่มีชื่อว่า “agent อิสระเริ่มและเดินอย่างไร”เส้นแบ่งจาก workflow (บท2) คือ agent อิสระ กำหนดทิศทางของตัวเอง Anthropic อธิบายวงจรชีวิตของมันไว้ตรงๆ — “Agents begin their work with either a command from, or interactive discussion with, the human user. Once the task is clear, agents plan and operate independently, potentially returning to the human for further information or judgement.” และจุดสำคัญที่จ่ายคืน clause สุดท้ายของนโยบายหยุด: agent “can then pause for human feedback at checkpoints or when encountering blockers.”
map ลงบน agent Order: มนุษย์พิมพ์ “ออเดอร์ A-1002 อยู่ไหน ถ้าส่งช้าให้คืนเงิน” → agent วางแผนเอง (เรียก getDeliveryStatus, ตัดสินความล่าช้า, เรียก issueRefund) → แต่ issueRefund เป็น write action (บท1) ที่มีผลข้างเคียงกับเงินจริง จึงเป็นจุดที่เหมาะจะ pause for human feedback at a checkpoint — checkpoint ไม่ใช่ของแถม แต่คือ clause หนึ่งในนโยบายหยุด
รูปทรง loop คือ gather context → take action → verify work → repeat (Claude Agent SDK) เหมือนบท1 — สิ่งที่บทนี้เพิ่มไม่ใช่รูปทรง แต่คือ เพดาน รอบวงนั้น
นโยบายหยุดแบบประกอบ — จ่ายคืนบท1
หัวข้อที่มีชื่อว่า “นโยบายหยุดแบบประกอบ — จ่ายคืนบท1”บท1 เขียนสัญญานี้ไว้ว่า stop check ของ #15 (stop_reason != "tool_use") เป็นแค่เคส degenerate บทนี้ประกอบ นโยบายหยุดStopping Conditionเงื่อนไขหยุด loop — เป็น 'นโยบายประกอบ' (compound policy) ไม่ใช่ predicate เดียว: task-complete ∨ max-iterations ∨ token/cost-budget ∨ unrecoverable-error ∨ human-checkpoint (BEA + Huyen) `stop_reason != tool_use` ของ #15 คือเคส degenerate ของนโยบายนี้ ทุกบทลงมือแทนที่ทีละ clause; loop ออกทันทีที่ clause แรก trip และ log เหตุผลที่ออก (ตั้งไว้บท1 จ่ายคืนบท8)Process เต็มรูป5 clause แล้วห่อไว้ใน LoopController ที่ประเมิน ที่หัวของทุกรอบ ก่อนเรียก model ครั้งถัดไป — ออกจากวงทันทีที่ clause แรกทริป และ log เหตุที่ออกเสมอ:
- task-complete (done signal) — assistant turn ที่ไม่มี tool call เลย ในระดับ raw คือ
stop_reasonเป็นend_turnนี่คือ ทางออกสำเร็จเพียงหนึ่งเดียว Anthropic เรียกว่า “terminates upon completion” ⚠️ อย่าเช็กแค่ “ไม่มี tool call” — ถ้าstop_reasonเป็นmax_tokensหรือmodel_context_window_exceededแปลว่าคำตอบถูกตัดกลางคัน ไม่ใช่เสร็จ (ดูตารางstop_reasonของ Anthropic) ต้องแยกจาก done จริง - Step BudgetStep Budgetเพดานจำนวนรอบแบบตายตัว (`iterations >= MaxIterations`) ที่รักษาการควบคุม loop ไว้และกัน runaway 1,000 ขั้น — BEA: 'stopping conditions (such as a maximum number of iterations) to maintain control'; Huyen: heuristic 'ตัดแผนที่เกิน X ขั้น' บน middleware loop คือ knob `MaximumIterationsPerRequest`Process (เพดานจำนวนรอบ) —
iterations >= MaxIterationsAnthropic ระบุว่า stopping condition อย่าง “a maximum number of iterations… to maintain control” มีไว้กันแผนพันขั้นที่ไม่ไปไหน - Cost BudgetCost Budgetเพดาน token/เงินที่สะสมข้ามเทิร์น (จาก UsageDetails) แล้วหยุดเมื่อ runCost ถึงเพดาน — มีไว้เพราะ error สะสมแบบทวีคูณ (95%^N: 10 ขั้น ≈ 60%, 100 ขั้น ≈ 0.6% — Huyen, ตัวเลขประกอบ) และ multi-actor กิน token มาก (MARS 15×) จึงต้อง bound loop ให้แน่น ⚠️ `ChatResponse.Usage` เป็น connector-populated/beta-mapped — งานระดับบิลต้อง cross-check `RawRepresentation`Process (เพดาน token) — สะสม token จาก
usageข้ามทุกเทิร์น หยุดเมื่อrun.TotalTokenCount >= MaxRunTokensนี่คือ clause ที่มิเตอร์ในหัวข้อถัดไปป้อนข้อมูลให้ - error ที่กู้ไม่ได้ — บท7 แยก error กู้ได้ (ป้อนกลับเป็น instructive message ให้ model ลองใหม่) ออกจาก error กู้ไม่ได้ (business-logic ล้ม, tool หายถาวร) อย่างหลังไม่ควรวนต่อ — หยุดแล้ว escalate
- human-checkpoint — write action อย่าง
issueRefundต้องผ่านคนอนุมัติก่อน ถ้ายังไม่อนุมัติก็ pause ตรงนี้
task-complete ∨ เกิน Step Budget ∨ เกิน Cost Budget ∨ error ที่กู้ไม่ได้ ∨ ถึง human-checkpoint
นอก5 clause นี้ยังมี guard เชิงปฏิบัติ อีกตัวที่คุ้มค่าจะใส่: no-progress guard — hash ชื่อ+args ของทุก tool call ถ้า model เรียกซ้ำเดิมโดยไม่ได้ ground truth ใหม่ ก็แปลว่ามันติดวน Anthropic ย้ำว่า “During execution, it’s crucial for the agents to gain ‘ground truth’ from the environment at each step… to assess its progress.” — guard ตัวนี้คือการบังคับใช้ประโยคนั้น
flowchart TD
START["คำสั่งจากผู้ใช้<br/>เริ่ม run"] --> HEAD{"ตรวจนโยบายหยุดที่หัวรอบ"}
HEAD -->|"ครบ MaxIterations"| S2["หยุด: Step Budget"]
HEAD -->|"เกินงบ token"| S3["หยุด: Cost Budget"]
HEAD -->|"ผ่านทุก clause"| CALL["เรียก model + สะสม usage"]
CALL --> DONE{"มี tool call ไหม"}
DONE -->|"ไม่มี + end_turn"| OK["หยุด: task complete<br/>ทางออกสำเร็จเดียว"]
DONE -->|"มี write action"| GATE{"human-checkpoint<br/>อนุมัติ issueRefund?"}
GATE -->|"ยังไม่อนุมัติ"| S5["หยุด: รอคน"]
GATE -->|"อนุมัติ / read-only"| DISP["dispatch tool"]
DISP -->|"error กู้ไม่ได้"| S4["หยุด: unrecoverable"]
DISP -->|"ผลกลับ / error กู้ได้"| APP["ผนวก FunctionResultContent"]
APP --> HEAD
classDef stop fill:#b91c1c,stroke:#7f1d1d,color:#f8fafc;
class S2,S3,S4,S5 stop;
classDef okc fill:#15803d,stroke:#14532d,color:#f8fafc;
class OK okc;
คำบรรยายภาพ: loop อิสระวงเดียว โดยนโยบายหยุดถูกตรวจ ที่หัวรอบ ก่อนเรียก model ทุกครั้ง สี่ช่องแดงคือทางออกแบบ non-done (Step Budget, Cost Budget, unrecoverable, รอคน) ที่ต้อง escalate เสมอ ส่วนช่องเขียวคือ task-complete — ทางออกสำเร็จเพียงหนึ่งเดียวใน5 clause
ทำไมบทนี้ต้อง hand-roll ไม่ใช้ middleware
หัวข้อที่มีชื่อว่า “ทำไมบทนี้ต้อง hand-roll ไม่ใช้ middleware”บท1 บอกว่า middleware (.UseFunctionInvocation()) คือค่าเริ่มต้น แต่จองรูป hand-rolled ไว้ให้บทที่ stop policy เกิน knob ของ middleware — บทนี้คือหนึ่งในนั้น FunctionInvokingChatClient มี knob ครอบแค่บาง clause:
MaximumIterationsPerRequest→ Step Budget (clause 2) ✅MaximumConsecutiveErrorsPerRequest→ เพดาน error ติดกัน (ญาติของ clause 4) ✅TerminateOnUnknownCalls→ หยุดเมื่อ model เรียก tool ที่ไม่รู้จัก ✅
แต่ Cost Budget (clause 3), no-progress guard และ human-checkpoint (clause 5) ไม่มี knob — ต้อง hand-roll เพราะมันต้องสะสม state ข้ามเทิร์น (token รวม, hash ของ call ที่เคยเห็น) และแทรกการตัดสินใจกลางวง ซึ่งเป็นอย่างเดียวกับที่บท1 บอกว่าเป็นเหตุผลให้ hand-roll
ประกอบ LoopController ตัวจริง
หัวข้อที่มีชื่อว่า “ประกอบ LoopController ตัวจริง”รวมทุก clause เข้าเป็น loop เดียว สังเกตว่ามันคือ loop แบบ hand-rolled ของบท1 ที่เติม guard เข้าไปที่หัวและกลางวง — ไม่ใช่โครงใหม่ กติกา API เดิมทุกข้อยังอยู่ (claude-opus-4-8 เปล่า ไม่ต่อ suffix, ไม่ส่ง temperature, อ่านคำตอบผ่าน resp.Messages[].Contents[] ไม่ใช่ .text, ผ่าน NuGet Anthropic beta ด้วย AsIChatClient):
record StopReason(string Kind, string Detail); // log เหตุที่ออกจากวงเสมอ
async Task<StopReason> RunAsync(List<ChatMessage> messages, ChatOptions options, CancellationToken ct){ var run = new UsageDetails(); // มิเตอร์ token ทั้ง run (S19) var seen = new HashSet<string>(); // no-progress guard
for (int step = 0; step < MaxIterations; step++) // clause 2: Step Budget { if (run.TotalTokenCount >= MaxRunTokens) // clause 3: Cost Budget return new("cost", $"เกินงบ {MaxRunTokens} token ที่ step {step}");
ChatResponse resp = await raw.GetResponseAsync(messages, options, ct); if (resp.Usage is { } u) run.Add(u); // สะสม usage ทุกเทิร์น messages.AddRange(resp.Messages);
var calls = resp.Messages.SelectMany(m => m.Contents) .OfType<FunctionCallContent>().ToList(); if (calls.Count == 0) // clause 1: done signal return new("done", "task complete"); // (ทางออกสำเร็จเดียว)
// clause 5: human-checkpoint — write action ต้องผ่านคนก่อน foreach (var call in calls.Where(c => c.Name == nameof(IssueRefund))) if (!await ApproveRefund(call.Arguments)) return new("checkpoint", "รอมนุษย์อนุมัติการคืนเงิน");
// no-progress guard: เรียก tool ชุดเดิมซ้ำโดยไม่ได้ ground truth ใหม่ → ติดวน var sig = string.Join("|", calls.Select(c => c.Name + JsonSerializer.Serialize(c.Arguments))); if (!seen.Add(sig)) return new("stuck", "เรียก tool ซ้ำเดิมโดยไม่คืบหน้า");
List<AIContent> results = []; foreach (var call in calls) { object? output; try { output = await Dispatch(call.Name, call.Arguments); } catch (UnrecoverableToolException ex) { return new("error", ex.Message); } // clause 4 catch (Exception ex) { output = $"ERROR: {ex.Message}"; } // กู้ได้ → ป้อนกลับ (บท7) results.Add(new FunctionResultContent(call.CallId, output)); } messages.Add(new ChatMessage(ChatRole.Tool, results)); } return new("budget", $"ครบ {MaxIterations} รอบ"); // clause 2: Step Budget หมด}3 knob หลักคือ MaxIterations, MaxRunTokens และรายการ write action ที่ต้อง checkpoint ทุกทางออกที่ไม่ใช่ done ต้องคืน คำตอบบางส่วนอย่างซื่อสัตย์ + escalate ไม่ใช่วนเงียบๆ — Anthropic บอกให้ “pause for human feedback… when encountering blockers”
มิเตอร์ token ต่อ run — และคำเตือน billing-grade
หัวข้อที่มีชื่อว่า “มิเตอร์ token ต่อ run — และคำเตือน billing-grade”Cost Budget จะทำงานได้ต้องวัด token จริง ChatResponse.Usage เป็น UsageDetails? โดย field นับเป็น long? และ .Add() บวกสะสมข้ามเทิร์นให้:
var run = new UsageDetails();// ...ในวง ต่อ1 response:if (resp.Usage is { } u) run.Add(u); // UsageDetails.Add — บวก usage ทั้ง run// ...จบ run:Console.WriteLine($"in={run.InputTokenCount} out={run.OutputTokenCount} total={run.TotalTokenCount}");field ที่ยืนยันแล้วของ UsageDetails: InputTokenCount, OutputTokenCount, TotalTokenCount, CachedInputTokenCount, ReasoningTokenCount, AdditionalCounts และ .Add(UsageDetails)
ChatResponse.Usage เป็นค่าที่ connector เป็นคนเติม ไม่ใช่สิ่งที่ abstraction การันตี และการ map จาก Anthropic → AsIChatClient เป็น beta — field อย่าง CachedInputTokenCount / ReasoningTokenCount อาจถูกเติมไม่ครบหรือไม่ตรง สำหรับการคิดเงินจริง ให้ cross-check resp.RawRepresentation หรือ usage ดิบจาก client.Messages.* ของ SDK (input_tokens, output_tokens, cache_read_input_tokens, cache_creation_input_tokens) อย่าถือตัวเลขจาก M.E.AI เป็นทางการโดยไม่ตรวจ mapping บน version SDK ที่ pin ไว้ — มิเตอร์นี้พอสำหรับ คุมงบ แต่ยังไม่พอสำหรับ ออกใบเสร็จ
การ log ต่อรอบ (iteration, stop reason ถ้ามี, tool + args + ผล, token เข้า/ออก, cost สะสม, latency) ไม่ใช่แค่ debug — มันคือ hook สำหรับ online-eval ที่คอร์ส #13 ใช้นับ failure mode ต่อ 🔁 ความทนทาน (บท7) วางท่อ error ให้ ส่วนบทนี้วางท่อ ตัวเลข ให้
คำปิดที่ตรงไปตรงมา
หัวข้อที่มีชื่อว่า “คำปิดที่ตรงไปตรงมา”ทั้งคอร์สนี้สอน กลไก ของ runtime — แต่กลไกที่โตขึ้นไม่ได้แปลว่าควรใช้มากขึ้น Anthropic พูดชัดว่า “The autonomous nature of agents means higher costs, and the potential for compounding errors.” — สองราคานี้จริงและวัดได้ error ทบต้นเพราะ agent ต้องเดินหลายขั้น ความแม่นรวมลดลงเมื่อจำนวนขั้นเพิ่ม ยิ่งวนมากยิ่งไม่ใช่ยิ่งดี — นี่คือเหตุผลเชิงปริมาณที่ต้อง บีบ loop ให้แน่น ด้วยนโยบายหยุดข้างบน ไม่ใช่ปล่อยให้มันวิ่งฟรี
และราคาของ โครงสร้าง ก็จริงไม่แพ้กัน MARS ของ Anthropic วัดว่า “agents typically use about 4× more tokens than chat interactions, and multi-agent systems use about 15× more tokens than chats.” ⚠️ ตัวเลข 4×/15× นี้เป็นข้อมูลจาก workload วิจัยของ Anthropic เอง ลงวันที่ 2025-06-13 — เป็นผลวัด ไม่ใช่กฎสากล — แต่ทิศทางชัด: การ fan-out (บท4) และ orchestrator-workers (บท5) เผา token เป็นทวีคูณ
ดังนั้นคำปิดของคอร์สนี้ ไม่ คลาย default ของ #12 ลงเลย — single-agent loop ที่มีขอบยังเป็นค่าเริ่มต้น โครงสร้าง multi-actor คือ ข้อยกเว้นที่ต้องหาเหตุผลมารองรับ ไม่ใช่อัปเกรดที่เอื้อมหาโดยอัตโนมัติ Anthropic ปิดด้วยสองประโยคที่ควรจำ — “Maintain simplicity in your agent’s design.” และ “Success in the LLM space isn’t about building the most sophisticated system. It’s about building the right system for your needs.” มาตรวัดไม่เคยเป็น “มัน agentic กว่าไหม” แต่เป็น “มันทำให้ผลลัพธ์ดีขึ้นจริงไหม”
จบคอร์ส — ส่งต่อจากตรงนี้
หัวข้อที่มีชื่อว่า “จบคอร์ส — ส่งต่อจากตรงนี้”คุณประกอบ runtime ของ agent จบแล้ว: harness, loop, workflow pattern ห้าแบบ, ความทนทาน และ agent อิสระที่มีนโยบายหยุดแบบประกอบ #17 สอน กลไกของ runtime โดยตั้งใจ — มันเชื่อมกับคอร์สอื่นในเสาหลัก AI ที่ตอบคำถามคนละข้อ:
- ควรแบ่ง agent ยังไง / เมื่อไรควรมีหลายตัว → 🔁 Agent as a Bounded Context (#12) — เรื่อง ขอบเขต ที่ #17 อ้างถึงตลอดในฐานะ default “1 agent 1 loop”
- runtime นี้ช่วยจริงไหม / วัดยังไง → 🔁 Evals for AI Agents (#13) — trace ต่อรอบจากบทนี้คือ input ของการนับ failure mode
- ห่อ tool เป็น server share ได้ยังไง → 🔁 Designing MCP Servers (#14)
- agent ฐานที่เราต่อยอดมาทั้งคอร์ส → 🔁 Your First AI Agent in C# (#15)
- จะป้อน context อะไรเข้า loop นี้ → 🔁 Context Engineering (#16)
บทนี้อิงต้นทางที่ลงวันที่กำกับ อ่านต่อได้โดยตรง:
- Anthropic, “Building Effective Agents” (2024-12-19) — นิยาม agent (“LLMs using tools based on environmental feedback in a loop”), วงจรเริ่ม/เดิน/pause-for-human, “ground truth… to assess its progress”, stopping condition “to maintain control”, “higher costs, and the potential for compounding errors” และคำปิด simplicity
- Anthropic, “Building agents with the Claude Agent SDK” (2025-09-29) — รูปทรง loop
gather context → take action → verify work → repeatและคำว่า “agent harness” - Anthropic, “How we built our multi-agent research system” (2025-06-13) — “agents typically use about 4× more tokens… multi-agent systems use about 15× more tokens than chats” (ข้อมูล workload วิจัยของ Anthropic เอง — ผลวัด ไม่ใช่กฎ)
- Chip Huyen, “Agents” (2025-01-07) — ความแม่นรวมของ agent ลดลงเมื่อจำนวนขั้นเพิ่ม (ตัวเลขประกอบ 95%^N เช่น 10 ขั้น ≈ 60%, 100 ขั้น ≈ 0.6% เป็นการคำนวณเชิงอธิบาย ไม่ใช่ผลวัด)
- Anthropic, “Stop reasons and fallback” — enumeration ของ
stop_reason(end_turn,max_tokens,model_context_window_exceededฯลฯ) ที่ต้องแยก done จริงออกจากคำตอบที่ถูกตัด - Microsoft Learn, “UsageDetails” (pkg v10.7.0, upd. 2026-06-12) —
InputTokenCount/OutputTokenCount/TotalTokenCount(long?) และ.Add(UsageDetails)สำหรับมิเตอร์ token ต่อ run
เช็กความเข้าใจ — บทที่ 8
ข้อ 1 / 3clause 'task-complete' ของนโยบายหยุด ตรวจแค่ 'ไม่มี tool call' พอไหม?