Compaction สู้ context rot
บทที่ 4 คุณลงมือ วัด งบ token ของ agent #15 เทิร์นต่อเทิร์น แล้วเห็นกับตาว่ามันพอกขึ้นต่อเนื่อง — ทุกรอบที่ loop เรียก getOrder หรือ getDeliveryStatus แล้วป้อน blob กลับเข้าไป ประวัติก็ยาวขึ้นอีกก้อน คำถามของบทนี้คือ แล้วทำอย่างไรกับมัน? คุณจะปล่อยให้หน้าต่างพอกจนใกล้เพดานไม่ได้ เพราะบทที่ 4 พิสูจน์แล้วว่าหน้าต่างที่เต็มไม่ใช่แค่แพงขึ้น แต่ทำให้ model เชื่อถือได้น้อยลง คำตอบหลักของบทนี้ชื่อ CompactionCompactionการสรุป/บีบอัดประวัติบทสนทนาเมื่อใกล้ชนเพดานหน้าต่าง แล้วตั้งหน้าต่างใหม่จากบทสรุปนั้น (Anthropic) — ต้องเป็นการสรุปแบบ high-fidelity ศิลปะอยู่ที่ 'เก็บอะไร ทิ้งอะไร': รักษาการตัดสินใจ + เรื่องที่ยังค้าง (order id, เหตุผล refund) ทิ้ง blob ผลของ tool ที่จัดการไปแล้ว; รูปที่เบาที่สุดคือแค่เคลียร์ tool result เก่าProcess — สรุปประวัติที่ยาวให้กระชับ แล้วเริ่มหน้าต่างใหม่จากบทสรุปนั้น
บทนี้ลงมือสร้าง CompactAsync คร่อม List<ChatMessage> ตัวเดียวกับที่ agent Order ของคอร์สที่ 15 ใช้ (repo kaen-food-ordering กำลังจัดทำ) — ยังเป็น IChatClient ตัวเดิม กติกา API เดิมทุกข้อจากคอร์ส #15: header x-api-key (ไม่ใช่ Authorization: Bearer), anthropic-version: 2023-06-01, model claude-opus-4-8 (ใช้เป็นตัวอย่าง ณ 2026-07 — id เลื่อนไหวตามเวลา) และ ไม่ส่ง temperature ไปกับ model รุ่นล่าสุด
compaction คือการสรุปประวัติแล้วเริ่มหน้าต่างใหม่
หัวข้อที่มีชื่อว่า “compaction คือการสรุปประวัติแล้วเริ่มหน้าต่างใหม่”Anthropic นิยาม compaction ไว้ตรงๆ — “Compaction is the practice of taking a conversation nearing the context window limit, summarizing its contents, and reinitiating a new context window with the summary.” แกะออกมาเป็นสามจังหวะ: (1) เห็นว่าประวัติใกล้ชนเพดาน (2) ให้ model สรุปเนื้อหาทั้งกอง (3) สร้างหน้าต่างใหม่ที่มีบทสรุปแทนประวัติดิบทั้งหมด
หัวใจที่ต้องขีดเส้นใต้คือคำว่า high-fidelity — บทสรุปนี้ไม่ใช่การย่อแบบลวกๆ “Compaction distills the contents of a context window in a high-fidelity manner, enabling the agent to continue with minimal performance degradation.” กลั่นเนื้อหาให้เข้มข้นแบบรักษาความเที่ยงตรงสูง เพื่อให้ agent ทำงานต่อได้โดยประสิทธิภาพแทบไม่ตก คำว่า แทบไม่ตก นี่แหละคือเส้นบางๆ ที่ compaction ที่ดีกับ compaction ที่พังต่างกัน
วิธีที่ล่อตาที่สุดคือ “ประวัติยาวไป ก็ตัด 10 ข้อความแรกทิ้งซะ” — เร็ว code สั้น และ พังเงียบๆ เพราะข้อความเก่าคือที่ที่ลูกค้าบอก order id และเหตุผลที่ขอคืนเงิน พอตัดทิ้ง agent ก็ลืมว่ากำลังทำเคสอะไรอยู่ การตัดตามตำแหน่ง (เก่า = ทิ้ง) ไม่รู้จัก สัญญาณ มันทิ้ง blob ที่ใช้แล้วกับข้อเท็จจริงที่ชี้ขาดด้วยเกณฑ์เดียวกัน compaction ต่างตรงที่ให้ model เป็นคนตัดสินว่าอะไรชี้ขาด ไม่ใช่ตัดตามอายุ
ทำไมต้องรักษาหน้าต่างให้แน่น: context rot กับ lost in the middle
หัวข้อที่มีชื่อว่า “ทำไมต้องรักษาหน้าต่างให้แน่น: context rot กับ lost in the middle”เหตุผลที่ compaction ไม่ใช่ของฟุ่มเฟือยมาจากสองงานวิจัยที่ลงวันที่ชัด ทั้งคู่ชี้ไปทางเดียวกัน — หน้าต่างที่ยาวขึ้นไม่ได้ฟรี
อย่างแรกคือ Context RotContext Rotปรากฏการณ์ที่ความแม่นในการเรียกใช้ข้อมูลของ model ค่อย ๆ แย่ลงเมื่อ context ยาวขึ้น — model ไม่ได้ประมวลผลทุกตำแหน่งเท่ากัน (Chroma Research) คอร์สสอน 'หลักการ' นี้ ไม่อ้างตัวเลข % เจาะจง (เลื่อนไหวเร็ว) บทเรียนที่ได้คือรักษาหน้าต่างให้กระชับด้วย Compaction + Curation แทนที่จะปล่อยให้เต็มจนเสื่อมArchitecture จาก Chroma Research: model ไม่ได้ อ่าน token ทุกตัวด้วยความเชื่อถือได้เท่ากัน “Large Language Models (LLMs) are typically presumed to process context uniformly—that is, the model should handle the 10,000th token just as reliably as the 100th. However, in practice, this assumption does not hold.” และสรุปหลักไว้ว่า “models do not use their context uniformly; instead, their performance grows increasingly unreliable as input length grows.” — ประสิทธิภาพยิ่งไม่แน่นอนเมื่อ input ยิ่งยาว แม้กับงานง่ายๆ อย่างการดึงข้อมูลตรงๆ ก็ตาม
Context rot เป็น หลักการ: recall เสื่อมลงเมื่อหน้าต่างยิ่งเต็ม เท่านี้พอสำหรับการตัดสินใจทางวิศวกรรม — รักษาหน้าต่างให้แน่น อย่ายึดติดตัวเลขเปอร์เซ็นต์การเสื่อมจากงานวิจัยชิ้นใดชิ้นหนึ่งมาเป็นสัจธรรม เพราะมันแปรตาม model และยุคของ benchmark สิ่งที่คงทนคือทิศทาง: เต็มมากขึ้น = เชื่อถือได้น้อยลง
อย่างที่สองคือ Lost in the MiddleLost in the Middleผลวิจัย (Liu et al., TACL 2024) ที่ว่า model ใช้ข้อมูลที่อยู่ 'ต้น' หรือ 'ท้าย' context ได้ดีกว่าที่อยู่ 'กลาง' อย่างมีนัยสำคัญ แม้กับ model long-context — บทเรียนเชิงปฏิบัติ: วางข้อเท็จจริงชี้ขาด (order id, เหตุผล) ไว้ที่หัวหรือท้าย ไม่ฝังไว้กลาง promptArchitecture จาก Liu และคณะ ซึ่งเจาะจงว่า ตำแหน่ง ของข้อมูลในหน้าต่างสำคัญ — “Performance is often highest when relevant information occurs at the beginning or end of the input context, and significantly degrades when models must access relevant information in the middle of long contexts, even for explicitly long-context models.” ข้อมูลที่จมอยู่กลางหน้าต่างยาวๆ ถูก model ใช้ได้แย่ที่สุด แม้จะเป็น model ที่โฆษณาว่ารองรับ context ยาวก็ตาม
สองข้อนี้ประกอบกันเป็นคำสั่งทางวิศวกรรมข้อเดียว: รักษาหน้าต่างให้แน่น และวางข้อเท็จจริงที่ชี้ขาดไว้ที่หัวหรือท้าย ไม่ใช่กลาง compaction คือเครื่องมือที่ทำทั้งสองอย่างพร้อมกัน — มันหดหน้าต่างให้เล็กลง (สู้ context rot) และเมื่อคุณประกอบหน้าต่างใหม่เอง คุณเป็นคนเลือกว่าบทสรุปกับเทิร์นล่าสุดจะอยู่ตรงไหน (สู้ lost in the middle)
ศิลปะของ compaction อยู่ที่เก็บอะไร ทิ้งอะไร
หัวข้อที่มีชื่อว่า “ศิลปะของ compaction อยู่ที่เก็บอะไร ทิ้งอะไร”Anthropic พูดชัดว่าจุดที่ยากที่สุดไม่ใช่การเขียน code สรุป แต่คือ การเลือก — “The art of compaction lies in the selection of what to keep versus what to discard, as overly aggressive compaction can result in the loss of subtle but critical context.” บีบแรงเกินไปก็เสียรายละเอียดที่ละเอียดอ่อนแต่ชี้ขาด บีบน้อยเกินไปก็ไม่ได้ประหยัดงบอะไร
กติกาเก็บ-ทิ้งที่ Anthropic ให้ไว้เป็นรูปธรรม (ในบริบทของ coding agent) คือ “The model preserves architectural decisions, unresolved bugs, and implementation details while discarding redundant tool outputs or messages.” map ลงบน agent Order ของเราตรงๆ:
- เก็บ (รักษาไว้): ปัญหาของลูกค้า,
order id, การตัดสินใจเรื่องคืนเงินและ เหตุผล, thread ที่ยังค้างยังไม่ปิด - ทิ้ง: record ออเดอร์ดิบทั้งก้อนที่
getOrderคืนมาแล้ว และจัดการไปแล้ว, blob สถานะเดลิเวอรีที่อ่านค่าเอาไปใช้เรียบร้อยแล้ว, ข้อความซ้ำซ้อน
สังเกตว่าเกณฑ์ไม่ใช่ “เก่า/ใหม่” แต่เป็น “ยังชี้ขาดอยู่ไหม” — order id จากเทิร์นแรกสุดต้องอยู่ต่อ ส่วน blob JSON ที่เพิ่งดึงมาเมื่อเทิร์นก่อนแต่ใช้เสร็จแล้วทิ้งได้
flowchart LR H1["เทิร์น 1-2<br/>ปัญหา + order id"] H2["blob getOrder<br/>ดิบ ใช้แล้ว"] H3["blob getDeliveryStatus<br/>ดิบ ใช้แล้ว"] H4["เทิร์นล่าสุด"] H1 --> C H2 --> C H3 --> C H4 --> C C["compaction<br/>model สรุปประวัติ"] C --> S["สรุปความละเอียดสูง<br/>เก็บ: ปัญหา · order id · เหตุผลคืนเงิน"] S --> W["หน้าต่างใหม่ แน่น<br/>system + สรุป + เทิร์นล่าสุด"] classDef drop fill:#7f1d1d,stroke:#450a0a,color:#f8fafc; classDef keep fill:#0891b2,stroke:#064e5b,color:#f8fafc; class H2,H3 drop; class W keep;
คำบรรยายภาพ: ประวัติที่พอกยาว — ข้อเท็จจริงที่ชี้ขาด (เทิร์นแรก, เทิร์นล่าสุด) ปนกับ blob ดิบที่ใช้แล้ว (สีแดง) — ไหลเข้า compaction ซึ่งให้ model สรุปเป็นก้อนความละเอียดสูง เก็บเฉพาะสิ่งที่ชี้ขาด แล้วประกอบหน้าต่างใหม่ที่แน่น (สีฟ้า) เหลือแค่ system + บทสรุป + เทิร์นล่าสุด เกณฑ์คัดคือ “ยังชี้ขาดอยู่ไหม” ไม่ใช่ “เก่าหรือใหม่”
ลงมือ: CompactAsync คร่อมประวัติของ agent #15
หัวข้อที่มีชื่อว่า “ลงมือ: CompactAsync คร่อมประวัติของ agent #15”วิธี implement ที่ Anthropic ใช้ตรงไปตรงมา — “we implement this by passing the message history to the model to summarize and compress the most critical details.” คือส่งประวัติทั้งกองกลับเข้า model เอง พร้อมคำสั่งที่ระบุชัดว่าเก็บอะไรทิ้งอะไร แล้วเอาบทสรุปที่ได้มาประกอบหน้าต่างใหม่ นี่คือ code คร่อม List<ChatMessage> ตัวเดิมจากคอร์ส #15:
using Microsoft.Extensions.AI;
// history คือ running List<ChatMessage> ตัวเดิมจากคอร์ส #15// chat คือ IChatClient ตัวเดิม (x-api-key, anthropic-version 2023-06-01,// "claude-opus-4-8" เป็นตัวอย่าง ณ 2026-07 — ไม่ส่ง temperature)async Task<List<ChatMessage>> CompactAsync(IList<ChatMessage> history, IChatClient chat){ var toSummarize = new List<ChatMessage>(history) { new(ChatRole.User, "สรุปเคส support นี้จนถึงตอนนี้ " + "KEEP: ปัญหาของลูกค้า, order id, การตัดสินใจเรื่องคืนเงินและเหตุผล, thread ที่ยังค้าง " + "DROP: record ออเดอร์ดิบที่จัดการไปแล้ว") };
// ไม่มี ChatOptions ที่ตั้ง temperature — กำกับผ่าน prompt เท่านั้น var summary = await chat.GetResponseAsync(toSummarize, new ChatOptions());
return [ history[0], // system message เดิม new(ChatRole.Assistant, summary.Text), // บทสรุปความละเอียดสูงก้อนเดียว history[^1] // เทิร์นล่าสุด ];}หน้าต่างใหม่ที่ return ออกมามีแค่สามชิ้น: system message เดิม, บทสรุปหนึ่งก้อน, และเทิร์นล่าสุด — จากประวัติที่อาจยาวหลายสิบข้อความ สังเกตว่าเราวางบทสรุปไว้ ต้น หน้าต่างและเทิร์นล่าสุดไว้ ท้าย โดยตั้งใจ ตาม lost in the middle — สิ่งที่ชี้ขาดอยู่ที่ขอบ ไม่จมกลาง
summary.Text บน ChatResponse เป็นตัวช่วยอ่านข้อความแบบรวบรัด แต่ เนื้อในจริง ของคำตอบยังเป็น content[] block array เหมือนที่คุณเห็นตอนยิง Messages API ดิบในคอร์ส #15 — ไม่มี .text ระดับบนสุดของ response จริงๆ accessor ตัวนี้แค่ไล่ block แล้วต่อ text ให้ กติกาข้อนี้ไม่เปลี่ยนแม้ผ่าน IChatClient
ควร trigger เมื่อไร? ใช้ตัวเลข token ที่คุณวัดได้จริงในบทที่ 4 — พอ input tokens ข้ามเกณฑ์ที่ตั้งไว้ (เช่น สัดส่วนหนึ่งของเพดานหน้าต่าง) ก็เรียก CompactAsync ก่อนเทิร์นถัดไป ไม่ใช่รอจนชนเพดานแล้วค่อยทำ
test ที่ต้องเขียน: ป้อน transcript หกเทิร์นที่อัดแน่นด้วย blob getOrder/getDeliveryStatus เข้าไป เรียก CompactAsync แล้ว assert ว่า order id กับ เหตุผลการคืนเงิน ยังโผล่อยู่ในหน้าต่างใหม่ที่ประกอบเสร็จ — นี่คือการพิสูจน์ “minimal performance degradation” ด้วยมือ: ถ้า assertion ผ่าน แปลว่า compaction รักษาสิ่งที่ชี้ขาดไว้ครบ ถ้าตก แปลว่าคุณบีบแรงเกินไป
รูปที่เบาที่สุดของ compaction: เคลียร์ผลของ tool
หัวข้อที่มีชื่อว่า “รูปที่เบาที่สุดของ compaction: เคลียร์ผลของ tool”ไม่ใช่ทุกครั้งที่ต้องเรียก model มาสรุป Anthropic ชี้ว่ารูปที่ปลอดภัยและเบาที่สุดคือแค่ลบผลของ tool เก่าทิ้ง — “One of the safest lightest touch forms of compaction is tool result clearing, most recently launched as a feature on the Claude Developer Platform.” พอ agent อ่านค่าจาก blob getOrder เอาไปตัดสินใจคืนเงินเรียบร้อยแล้ว ตัว blob ดิบก้อนนั้นก็ไม่มีหน้าที่อีกต่อไป — เคลียร์มันออกจากประวัติได้เลย โดยไม่ต้องเรียก model มาสรุปอะไร
tool result clearing คือประตูสู่บทถัดไปพอดี — ในบทที่ 6 จัดระเบียบผลของ tool เราจะปั้น สิ่งที่ tool คืนกลับมา ตั้งแต่แรกให้เหลือแต่ field ที่ agent ต้องใช้จริง แทนที่จะคืน JSON ทั้งก้อนแล้วค่อยมาเคลียร์ทีหลัง สองเทคนิคนี้เสริมกัน: บทที่ 6 ทำให้ payload เล็กแต่ต้น บทนี้จัดการก้อนที่สะสมไว้แล้ว
compaction คู่กับ memory: หน้าต่างแน่น ความจำไม่หาย
หัวข้อที่มีชื่อว่า “compaction คู่กับ memory: หน้าต่างแน่น ความจำไม่หาย”compaction แก้ปัญหาหน้าต่างพอก แต่ตัวมันเองก็ ทิ้ง ข้อมูลบางส่วนถาวรทุกครั้งที่สรุป ถ้ามีข้อเท็จจริงที่ต้องอยู่รอดข้าม session ไม่ใช่แค่ข้ามการสรุป คุณจับคู่มันกับ memory จากบทที่ 3 ได้ — compaction รักษา หน้าต่างที่กำลังทำงาน ให้เล็กโดยไม่ต้องจดบันทึกฝั่ง client ส่วน memory รักษาสิ่งที่ต้อง รอดจากการสรุป ไว้นอกหน้าต่าง
Anthropic รายงานว่าเมื่อใช้ memory คู่กับการตัดแต่ง context บน benchmark งานค้นเว็บ 100 เทิร์น ได้ “84% token savings and a 39% performance improvement” — ตัวเลขนี้มาจากการวัดชิ้นเดียว (eval 100 เทิร์นหนึ่งชุด) ใช้เป็น ทิศทาง ว่าการจับคู่นี้ให้ผล ไม่ใช่ตัวเลขที่จะได้เป๊ะทุกงาน
กติกาเก็บ-ทิ้งของ compaction ประกอบเข้ากับ memory ระยะยาวในบทที่ 3 ได้ตรงๆ: ก่อนสรุป ให้ agent saveMemory() สิ่งที่ต้องรอดข้าม session ออกไปก่อน แล้วค่อย compact — บทที่ 8 จะร้อยทั้งสองชั้นนี้เข้าด้วยกันใน context layer ตัวเต็ม
ปลายทางของบทนี้คือวินัยเดียว: อย่าปล่อยให้หน้าต่างพอกจนล้นแล้วเชื่อถือไม่ได้ ให้สรุปมันให้แน่นอย่างมีศิลปะ เก็บสิ่งที่ชี้ขาด ทิ้งสิ่งที่ใช้แล้ว บทหน้าเราขยับไปที่ ต้นทาง ของการพอก — ปั้นสิ่งที่ tool คืนกลับมาตั้งแต่แรก
บทนี้อิงต้นทางที่ลงวันที่กำกับ อ่านต่อได้โดยตรง:
- Anthropic, “Effective context engineering for AI agents” (2025-09-29) — นิยาม compaction, การกลั่นแบบ high-fidelity, ศิลปะของการเลือกเก็บ-ทิ้ง, กติกาเก็บ architectural decisions/unresolved bugs ทิ้ง redundant tool outputs, วิธี implement ด้วยการส่งประวัติกลับเข้า model และ tool result clearing ในฐานะรูปที่เบาที่สุด
- Anthropic, “Context management” (Sept 2025) — ตัวเลข 84% token savings / 39% performance improvement จาก eval งานค้นเว็บ 100 เทิร์น (memory คู่กับการตัดแต่ง context — ดัชนีเดียว ใช้เป็นทิศทาง)
- Chroma Research, “Context Rot: How Increasing Input Tokens Impacts LLM Performance” (2025-07-14) — หลักการ context rot: model ไม่ได้ประมวลผล context อย่างสม่ำเสมอ ประสิทธิภาพยิ่งไม่แน่นอนเมื่อ input ยิ่งยาว (หลักการ ไม่ใช่ตัวเลขเปอร์เซ็นต์)
- Liu et al., “Lost in the Middle: How Language Models Use Long Contexts” (arXiv 2023-07-06; TACL 2024) — ประสิทธิภาพสูงสุดเมื่อข้อมูลที่เกี่ยวข้องอยู่ต้นหรือท้ายหน้าต่าง และเสื่อมชัดเมื่อจมอยู่กลาง
เช็กความเข้าใจ — บทที่ 5
ข้อ 1 / 3ตามนิยามของ Anthropic compaction คืออะไร?