ข้าม​ไป​ยัง​เนื้อหา

loop คือ​เครื่องยนต์

จบ​สอง​คอร์ส​ที่​ผ่าน​มา คุณ​ถือ​ของ​จริง​มา​แล้ว​สอง​ชั้น คอร์ส​ที่ 15 ให้ agent support ลูกค้า​บน domain Order ที่ รัน​ได้ — model claude-opus-4-8 (ใช้​เป็น​ตัวอย่าง ณ 2026-07 — id เลื่อน​ไหว​ตาม​เวลา) บวก tool สาม​ตัว getOrder / getDeliveryStatus / issueRefund บวก loop ที่​ป้อน​ผล​ของ tool กลับ​เข้า model จน​งาน​เสร็จ คอร์ส​ที่ 16 ให้ context layer คร่อม​มัน — retrieval, memory, การ​จัด​งบ token, compaction #15 สอน​ให้ ถือ model ถูก #16 สอน​ว่า จะ​ป้อน​อะไร คอร์ส​นี้​ลง​ลึก​อีก​ชั้น​เดียว​ที่​ทั้ง​สอง​คอร์ส​นั้น​วาง​ไว้​บน​โต๊ะ​แต่​ยัง​ไม่​ได้​เปิด​กล่อง — runtime ที่ agent อาศัย​อยู่: harness และ​ตัว loop เอง

Anthropic นิยาม agent ไว้​ประโยค​เดียว — “Agents are typically just LLMs using tools based on environmental feedback in a loop.” — คอร์ส #15 ยึด​ประโยค​นี้​เป็น​แกน คอร์ส​นี้​จะ เดิน​เข้าไป​ข้าง​ใน ประโยค​นั้น ลึก​กว่า​บท​เกริ่น “THE LOOP” ของ #15 (บท6) ที่​หยุด loop ด้วย predicate เดียว

📦 code ตัวอย่าง

คอร์ส​นี้​ต่อยอด agent Order ตัว​เดิม​จาก​คอร์ส #15 (พร้อม context layer จาก​คอร์ส #16) — repo kaen-food-ordering (กำลัง​จัด​ทำ) #17 ไม่​เพิ่ม tool หรือ store ใหม่ แต่​ห่อ runtime คร่อม​มัน บท​นี้​เป็น​บท​วาง​กรอบ​ความคิด code ที่​แสดง​คือ รูปทรง​ของ loop สอง​แบบ ที่​ทุก​บท​หลัง​จาก​นี้​จะ​หยิบ​ไป​ดัดแปลง​ที​ละ clause ของ stop policy ทุก​กติกา API ฝั่ง​แชต​จาก​คอร์ส #15 ยัง​ยึด​เดิม​ทุก​ข้อ — บท​นี้​ตั้ง precedent ของ​กติกา​เหล่า​นั้น​ไว้​ให้​ทั้ง​คอร์ส

ใต้ context ที่​เรา​ป้อน (คอร์ส #16) มี​อะไร​อยู่? มี​สอง​ชั้น​ที่​ต้อง​แยก​ให้​ขาด

ชั้น​แรก​คือ อะตอมaugmented LLMAugmented LLMอะตอม​ที่ harness ห่อ​อยู่: LLM ที่​เสริม​ด้วย retrieval, tools และ memory (BEA — 'the basic building block of agentic systems') ผู้​เรียน​สร้าง​มัน​ไว้​แล้ว​ใน #15 (tools) + #16 (retrieval/memory) — #17 คือ runtime ที่​เรียก​อะตอม​ตัว​นี้​ซ้ำ ๆ ใน loop; retrieval/memory คือ 'ส่วน​เสริม' ส่วน #17 คือ 'ตัว runtime' ที่​เรียก​ใช้​มันArchitecture ที่ Anthropic เรียก​ว่า “The basic building block of agentic systems is an LLM enhanced with augmentations such as retrieval, tools, and memory.” คุณ​สร้าง​อะตอม​ตัว​นี้​เสร็จ​แล้ว: tool สาม​ตัว​จาก #15 คือ augmentation แบบ tool, retrieval/memory จาก #16 คือ augmentation อีก​สอง​ชนิด อะตอม​ตัว​นี้​ทำงาน​ได้ หนึ่ง​ครั้ง ต่อ​การ​เรียก​หนึ่ง​ครั้ง

ชั้น​ที่​สอง​คือ runtime ที่​เรียก​อะตอม​ตัว​นั้น​ซ้ำๆ — และ​นั่น​คือ​สิ่ง​ที่​คอร์ส​นี้​เปิด เรา​จะ​เรียก​มัน​ด้วย​ชื่อ​ที่ Anthropic ตั้ง​ไว้​ว่า harnessAgent Harnesscode scaffolding ที่​ห่อ​รอบ​การ​เรียก model หนึ่ง​ครั้ง — คือ​ส่วน​ที่​จัดการ tool dispatch, การ​รักษา​ประวัติ​ข้อความ (message history), ตัว `while` ที่​วน​ซ้ำ และ​การ​ตรวจ​เงื่อนไข​หยุด (stop check) Anthropic เรียก​ชื่อ 'agent harness' ไว้ (Claude Agent SDK) แต่​การ​แยก​ส่วน​ออก​เป็น​สี่​ชิ้น​นี้​คือ​กรอบ​สอน​ของ​คอร์ส​นี้ ต่าง​จาก #16 ที่ engineer 'สิ่ง​ที่​ป้อน' (context) — harness คือ runtime ที่​ตอบ​ว่า 'ป้อน​อย่างไร' เทิร์น​แล้ว​เทิร์น​เล่าArchitecture Anthropic พูด​ถึง “the agent harness that powers Claude Code” โดย​ไม่​ได้​ให้​นิยาม​เชิง​โครงสร้าง — การ​แยก​ส่วน harness ออก​เป็น​สี่​ชิ้น​ต่อ​ไป​นี้​คือ​กรอบ​สอน​ของ​คอร์ส​นี้ ไม่ใช่​คำ​นิยาม​ทางการ​ของ Anthropic เรา​เป็น​คน​ตั้ง​กรอบ ยืม​แค่ ชื่อ มา​จาก​เขา:

  1. tool dispatch — รับ tool call ที่ model ขอ แล้ว​เรียก method C# ตัว​จริง
  2. การ​รักษา​ประวัติ​ข้อความ (message history) — สะสม ChatMessage ทุก​เทิร์น​แล้ว​ส่ง​กลับ​เข้าไป​ซ้ำ
  3. ตัว while — วง​วน​ที่​พา model กลับ​มา​เรียก​อีกรอบ​หลัง​เห็น​ผล​ของ tool
  4. การ​ตรวจ​เงื่อนไข​หยุด (stop check) — ตัดสิน​ว่า​รอบ​นี้​ควร​จบ​หรือ​วน​ต่อ

harness ไม่ใช่ model ไม่ใช่ tool ไม่ใช่ context มัน​คือ โครง code ที่​ประสาน​ทั้ง​สาม​อย่าง​ให้​ทำงาน​เป็น​วงจร — engine ที่​หมุน​อะตอม

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 คือ​รูปทรง​ที่ harness วน​ซ้ำ Anthropic อธิบาย​รูปทรง​นี้​จาก Claude Code ไว้ตรงๆ — “In Claude Code, Claude often operates in a specific feedback loop: gather context, take action, verify work, repeat.” — model รวบรวม context, ลงมือ​ทำ (เรียก tool), ตรวจ​งาน​ที่​ทำ แล้ว​วน​ใหม่ โดยวน​ไป​ต่อ​เนื่อง “iteratively until the code succeeds.”

ราก​ทาง​ความคิด​ของ​วงจร​นี้​มีชื่อ: ReActReAct (Reasoning + Acting)ราก​ทาง​ความคิด​ของ loop (Yao et al., 2022): model reason (thinking trace) แล้ว act (tool call) แล้ว​ผล​ของ tool คือ observation ที่​ป้อน​กลับ — `observe → decide → act → repeat` reasoning trace ช่วย​วางแผน​และ​จัดการ exception ส่วน action ต่อ​กับ​แหล่ง​ภายนอก​เพื่อ​ดึง​ข้อมูล​เพิ่ม ⚠️ ผล​ที่​อ้าง (ALFWorld/WebShop เหนือ baseline 34%/10%) เป็น​ตัวเลข​เฉพาะ​งาน​และ​ลง​วัน​ที่ นำ​เสนอ​เป็น​ผลลัพธ์ ไม่ใช่​กฎArchitecture (Yao et al., 2022) ที่​จับ reasoning กับ acting มา​สาน​สลับ​กัน — “we explore the use of LLMs to generate both reasoning traces and task-specific actions in an interleaved manner… reasoning traces help the model induce, track, and update action plans as well as handle exceptions, while actions allow it to interface with external sources, such as knowledge bases or environments, to gather additional information.” กติกา​ข้อ​นี้​คือ​แม่​แบบ​ของ loop ที่​คุณ​เขียน​ใน #15: model คิด (reason) → เรียก tool (act) → ผล​ของ tool คือ observation ที่​ป้อน​กลับ → คิด​ต่อ ⚠️ ผล​ที่ ReAct รายงาน (เหนือ baseline บน ALFWorld/WebShop ที่ “an absolute success rate of 34% and 10% respectively”) เป็น​ตัวเลข เฉพาะ​งาน​และ​ลง​วัน​ที่ — นำ​เสนอ​เป็น​ผลลัพธ์​ของ​งาน​วิจัย​หนึ่ง ไม่ใช่​กฎ​ที่​การันตี​ทุก domain

เฟส verify คือ​แกน​ใหม่​ที่​ลึก​กว่า #15 บท6 loop ของ #15 หยุด​ด้วย predicate เดียว: ถ้า​ไม่มี tool call แล้ว​ก็​จบ แต่​วงจร​ของ Anthropic เพิ่ม​ขั้น ตรวจ​งาน เข้า​มา — หลัง write action อย่าง issueRefund ควร อ่าน ground truth ซ้ำ ด้วย getOrder ก่อน​ประกาศ​ว่า​เสร็จ ไม่ใช่​เชื่อ​ว่า tool คืน success แล้ว​จบ

เส้น​แบ่ง read/write นี้​สำคัญ Chip Huyen เขียน​ไว้​ว่า “Actions that allow an agent to perceive the environment are read-only actions, whereas actions that allow an agent to act upon the environment are write actions.” map ลงบน agent ของ​เรา (เรียบเรียง​จาก Huyen): getOrder / getDeliveryStatus คือ sensor (อ่าน​อย่าง​เดียว รัน​ซ้ำ​ได้​ไม่​เจ็บ) ส่วน issueRefund คือ actuator (เขียน มี​ผล​ข้าง​เคียง) — และ​เพราะ​มัน​เป็น write action นี่เอง มัน​จึง​ต้อง​มี​เฟส verify และ checkpoint กำกับในบทหลังๆ

นี่​คือ​ประโยค​ที่​คอร์ส​ทั้ง​คอร์ส​หมุน​รอบ stop check ของ #15 ตรวจ​สิ่ง​เดียว — stop_reason != "tool_use" (raw) หรือ “ไม่มี FunctionCallContent แล้ว” (IChatClient) งาน​ของ #17 คือ​แสดง​ว่า predicate เดียว​นั้น​คือ​เคส degenerate ของ​นโยบาย​หยุด​แบบ​ประกอบ (compound stop policyStopping 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):

task-complete เกิน max-iterations เกิน​งบ token/cost เจอ error ที่​กู้​ไม่​ได้ ถึง human-checkpoint

stop_reason != "tool_use" เป็น​แค่ clause แรก (task-complete) ของ​นโยบาย​ที่​มี5 clause ทุก​บท​ลงมือ​ของ​คอร์ส​นี้​จะ​ไป แทนที่​ที​ละ clause — บท7 เติม clause “error ที่​กู้​ไม่​ได้”, บท8 ประกอบ​ทั้ง5 clause เข้า​เป็น LoopController ตัว​เดียว​ที่​ตรวจ ที่​หัว​ของ​ทุกรอบ ก่อน​เรียก model ครั้ง​ถัด​ไป Anthropic บอก​ไว้​ว่า​เรื่อง​นี้​เป็น​เรื่อง​ของ​การ ควบคุม“The task often terminates upon completion, but it’s also common to include stopping conditions (such as a maximum number of iterations) to maintain control.” จำ​ภาพ​นี้​ไว้ แล้ว​บท8 จะ​จ่าย​คืน

มี​สอง​วิธี​เขียน loop ใน .NET และ​คอร์ส​นี้​จะ​ใช้ ทั้ง​สอง ตาม​งาน อย่า​ปน​กัน เริ่ม​จาก​เชื่อม agent เดิม​เข้า​กับ IChatClient — ไม่มี connector first-party ของ Anthropic ใน Microsoft.Extensions.AI ดังนั้น Claude ยัง​อยู่​หลัง NuGet Anthropic ตัว​ทางการ​ผ่าน AsIChatClient เหมือน​ที่​ทำ​ใน #15/#16:

using Anthropic; // AnthropicClient, .AsIChatClient
using Microsoft.Extensions.AI; // IChatClient, ChatOptions, AIFunctionFactory, FunctionCallContent, ...
AnthropicClient anthropic = new(); // อ่าน ANTHROPIC_API_KEY จาก env
IChatClient raw = anthropic.AsIChatClient("claude-opus-4-8"); // bare id, ตัวอย่าง ณ 2026-07 — ไม่ต่อ suffix วันที่
ChatOptions options = new() {
Tools = [
AIFunctionFactory.Create(GetOrder), // 3 tool เดิมจาก #15 เป็น method C# ธรรมดา
AIFunctionFactory.Create(GetDeliveryStatus),
AIFunctionFactory.Create(IssueRefund),
],
};
กติกา API ที่​บท​นี้​ตั้ง​เป็น precedent ให้​ทั้ง​คอร์ส

สะพาน​เชื่อม​นี้​แบก invariant เดิม​ของ #15 มา​ครบ ทุก snippet ที่​แตะ Messages API / IChatClient ใน​คอร์ส​นี้​ต้อง​ยึด: header เป็น x-api-key ไม่ใช่ Authorization: Bearer · anthropic-version: 2023-06-01 เสมอ · ห้าม​ส่ง temperature / top_p / top_k ไป​กับ model รุ่น​ล่าสุด (steer ผ่าน prompt) · model id เป็น alias เปล่า claude-opus-4-8 (ลง​วัน​ที่ 2026-07 กำกับ ไม่​ต่อ suffix) · คำ​ตอบ​ดิบ ไม่มี .text ระดับ​บน​สุด — มัน​คือ array content[] ที่​ต้อง​ไล่ match type; ใต้ IChatClient ตัว​เทียบ​คือ ChatResponse.Messages[].Contents[] ที่ match ด้วย FunctionCallContent / FunctionResultContent ส่วน ChatResponse.Text เป็น​แค่ convenience concatenation ไม่ใช่​รูป​ดิบ

สถานะ package (พูด​ครั้ง​เดียว​ที่​นี่): Anthropic 12.8.0 เป็น beta (“breaking changes may occur in minor or patch releases”) — pin version ไว้ และ​พึง​ระวัง​ว่า​รายละเอียด​การ map ของ AsIChatClient (field usage, รูปทรง tool-content) อาจ​ขยับ ส่วน Microsoft.Extensions.AI 10.x เป็น GA — สอน​มัน​ใน​ฐานะ layer ที่​ทนทาน แล้ว​สอน​สะพาน Anthropic → AsIChatClient ใน​ฐานะ beta-ที่​อาจ​เปลี่ยน

นี่​คือ loop ของ #15 บท6 ที่​เขียน​ออก​มา​ให้​เห็น​ทุก​กลไก คุณ​เรียก model เอง ไล่​หา FunctionCallContent เอง ผนวก FunctionResultContent กลับ​เข้า ChatRole.Tool message เอง และ​คุม​เงื่อนไข​หยุด​เอง:

List<ChatMessage> messages = [ new(ChatRole.User, "ออเดอร์ A-1002 อยู่ไหน ถ้าส่งช้าให้คืนเงิน") ];
for (int step = 0; step < 10; step++) { // for = while ที่มีขอบ: ตัวนับนี้คือ step budget — clause หนึ่งของ stop policy (จ่ายคืนบท8)
ChatResponse resp = await raw.GetResponseAsync(messages, options, ct);
messages.AddRange(resp.Messages); // ผนวก assistant turn ตามจริง — resp.Messages เป็นพหูพจน์
var calls = resp.Messages.SelectMany(m => m.Contents)
.OfType<FunctionCallContent>().ToList();
if (calls.Count == 0) break; // ไม่มี tool call → จบ (predicate ของ #15; degenerate stop)
List<AIContent> results = [];
foreach (var call in calls) {
object? output;
try { output = await Dispatch(call.Name, call.Arguments); } // registry ของคุณ
catch (Exception ex) { output = $"ERROR: {ex.Message}"; } // จุดต่อ tool-error recovery (บท7)
results.Add(new FunctionResultContent(call.CallId, output));
}
messages.Add(new ChatMessage(ChatRole.Tool, results)); // ผลของ tool อยู่ใน ChatRole.Tool message
}

สังเกต: FunctionCallContent พก .Name / .CallId / .Arguments; ผลกลับ​เป็น FunctionResultContent(callId, output); และ​ผล​ของ tool ต้อง​อยู่​ใน message ที่​มี role เป็น ChatRole.Tool การ​อ่าน​คำ​ตอบ​ทำ​ผ่าน resp.Messages[].Contents[] ไม่ใช่ resp.Text

FunctionInvokingChatClient เป็น IChatClient ตัว​หนึ่ง​ที่​วน call→execute→append→repeat ให้​อัตโนมัติ เสียบ​เข้าไป​ใน pipeline ด้วย​บรรทัด​เดียว แล้ว loop ก็​รัน​เอง​ข้าง​ใน​การ​เรียก​ครั้ง​เดียว:

IChatClient agent = anthropic.AsIChatClient("claude-opus-4-8")
.AsBuilder()
.UseFunctionInvocation() // เพิ่ม FunctionInvokingChatClient เข้าไปใน pipeline
.Build();
ChatResponse resp = await agent.GetResponseAsync(
"ออเดอร์ A-1002 อยู่ไหน ถ้าส่งช้าให้คืนเงิน", options, ct); // loop รันจบแล้ว; tool ถูกเรียกให้แล้ว

code สอง​ก้อน​นี้ ทำงาน​เหมือน​กัน บน​เคส​ธรรมดา ต่าง​กัน​ที่​ใคร​เป็น​เจ้าของ while แล้ว​จะ​เลือก​อัน​ไหน?

  • middleware = ค่า​เริ่มต้น สำหรับ agent ที่​ใช้ custom tool ทั่วไป: ให้ compose caching/telemetry/retry เป็น layer ใน pipeline และ​คุณ​ไม่​ได้​แทรก​งาน​อื่น​กลางวง นี่​คือ runtime แบบ middlewareFunction Invocationรูป loop แบบ middleware: `FunctionInvokingChatClient` (ผ่าน `.AsBuilder().UseFunctionInvocation().Build()`) วน call→execute→append→repeat ให้​อัตโนมัติ เป็น IChatClient ตัว​หนึ่ง​ใน pipeline — คือ runtime ค่า​เริ่มต้น​ของ single-agent (สอดคล้อง #12) knob สำคัญ: `MaximumIterationsPerRequest`, `MaximumConsecutiveErrorsPerRequest` ฯลฯ ⚠️ เป็น​พื้น​ผิว​ที่​ขยับ​เร็ว pin ให้​ตรง version ที่​ติดตั้ง; ตรง​ข้าม​กับ loop แบบ hand-rolled `while` ที่​จอง​ไว้​สำหรับ stop policy ที่ middleware แสดงออก​ไม่​ได้ (บท6, บท8)Architecture ของ single-agent มาตรฐาน (สอดคล้อง​กับ default “1 agent 1 loop” ของ​คอร์ส #12)
  • hand-roll เมื่อ ต้องการ​เงื่อนไข​หยุด​ที่ knob ของ middleware แสดงออก​ไม่​ได้ (compound stop policy — บท8), ต้อง mutate ประวัติ​กลาง​เทิร์น, ต้อง​แทรก​งาน​ที่​ไม่ใช่ tool ระหว่าง​เทิร์น (reflection — บท6) หรือ​ไม่​อยาก​ให้ loop เป็นกล่อง​ดำ

คอร์ส​นี้​จอง​รูป hand-rolled ไว้​ให้​บท6 กับ​บท8 โดย​เฉพาะ เพราะ2 stop policy นั้น knob ของ FunctionInvokingChatClient (MaximumIterationsPerRequest, MaximumConsecutiveErrorsPerRequest ฯลฯ) แสดงออก​ไม่​หมด บท​อื่น​ที่​เหลือ​จะ​เดิน​บน middleware เป็น​หลัก

flowchart TD
  U["ข้อความจากผู้ใช้"] --> P{"ตรวจ stop policy ก่อนเริ่มรอบ<br/>เกิน max-iters · เกินงบ token · error · human-checkpoint"}
  P -->|"trip clause ใด clause หนึ่ง"| STOP["หยุดแบบมีเหตุผล<br/>escalate หรือคืนคำตอบบางส่วน"]
  P -->|"ผ่านทุก clause"| C["เรียก model<br/>raw.GetResponseAsync"]
  C --> D{"มี FunctionCallContent ไหม"}
  D -->|"ไม่มี — done signal"| DONE["ตอบผู้ใช้ แล้วจบ"]
  D -->|"มี"| T["tool dispatch<br/>getOrder · getDeliveryStatus · issueRefund"]
  T --> R["ผนวกผลกลับเข้า context<br/>FunctionResultContent ใน ChatRole.Tool"]
  R --> P
  classDef engine fill:#b91c1c,stroke:#7f1d1d,color:#f8fafc;
  class C,T,R engine;

คำ​บรรยาย​ภาพ: เครื่องยนต์​ของ harness หมุน​เป็น​วง​เดียว — เรียก model → ตรวจ​ว่า​มี tool call ไหม → dispatch tool → ผนวก​ผลกลับ​เข้า context → กลับ​ไป​ตรวจ stop policy แล้ว​วน​ใหม่ สาม​ช่อง​สี​แดง​คือ engine ที่ harness หมุน (การ​เรียก model, tool dispatch, การ​ผนวก​ผล) ส่วน done signal (“ไม่มี FunctionCallContent”) คือ​ทางออก​สำเร็จ เพียง1 clause ของ stop policy ที่​หัว​วง — ที่​เหลือ (max-iters, งบ token, error, checkpoint) คือ clause อื่นๆ ที่บทหลังๆ จะ​เติม​เข้า​มา​ที​ละ​อัน

agent ของ​คุณ​ไม่​ได้​ฉลาก​เพราะ model ใหญ่​ขึ้น มัน​ทำงาน​ได้​เพราะ เครื่องยนต์​ที่​หมุน​อะตอม​นั้น​ซ้ำๆ — harness (โครง code สี่​ชิ้น), loop (gather → act → verify → repeat แบบ ReAct) และ stop policy ที่​เป็น นโยบาย​ประกอบ5 clause ไม่ใช่ predicate เดียว บท​หน้า​เรา​ถอย​ออก​มา​หนึ่ง​ก้าว​เพื่อ​ถาม​คำถาม​ที่​ต้อง​ตอบ ก่อน เอื้อมหา harness เลย​ด้วย​ซ้ำ — เมื่อไร​ควร​สร้าง agent และ​เมื่อไร workflow ธรรมดา​ดี​กว่า


🔗 อ้างอิง​ต้นทาง​ของ​บท​นี้

บท​นี้​อิง​ต้นทาง​ที่​ลง​วัน​ที่​กำกับ อ่าน​ต่อ​ได้​โดยตรง:

เช็กความเข้าใจ — บทที่ 1

ข้อ 1 / 3

ในกรอบสอนของคอร์สนี้ 'agent harness' หมายถึงอะไร?