context window ทำงานยังไง — และทำไม prompt cache ถูกลง ~10 เท่า
TL;DR — LLM เป็น stateless: มัน “ลืม” ทุกอย่างทันทีที่ตอบจบแต่ละ turn. ที่ AI ดู เหมือน “จำ” บทสนทนาได้ เพราะ harness ส่ง context ทั้งก้อนใหม่ทุก turn (system + memory + บทสนทนาที่ผ่านมา + ผลลัพธ์ tool). ก้อนนี้คือ context window — มี cap ต่อ turn (หลักแสนถึง หลักล้าน token แล้วแต่รุ่น) และ ไม่ใช่ไฟล์ประวัติแชท (ที่โตได้แทบไม่จำกัด). เข้าใจอันนี้แล้ว จะรู้ว่าทำไม “อ่านไฟล์เยอะ context พุ่ง” และทำไม คุยรัว ๆ ถูกกว่าทิ้งไว้นาน (prompt cache อ่านซ้ำได้ ~0.1× ของราคาปกติ แต่หมดอายุใน ~5 นาที)
🌐 vendor-neutral — หลัก “stateless + context ต่อ turn” จริงกับ LLM ทุกค่าย (Claude, GPT, Gemini, Llama). ส่วนตัวเลข caching (0.1× / 1.25× / TTL) ยกตัวอย่างด้วย Anthropic/Claude — ค่ายอื่นมี cache คล้ายกันแต่ตัวคูณและเงื่อนไขต่างกัน. ป้าย 🔷 = เจาะจง Claude / Claude Code, 🌐 = หลักการทั่วไป
ความสงสัย: AI จำบทสนทนาได้ ทั้งที่ LLM “ลืม” ทุก turn
คุณคุยกับ AI agent มา 30 นาที มันอ้างอิงสิ่งที่คุณพูดเมื่อ 10 ข้อความก่อนได้เป๊ะ — เหมือนมัน “จำ” ได้. แต่ความจริงทางเทคนิคคือ ตัว model ไม่มีความจำเลย. มันลืมทุกอย่างหลังตอบจบทุกครั้ง.
สองอย่างนี้จริงพร้อมกันได้ยังไง? คำตอบอยู่ที่คำว่า context window — และพอเข้าใจมัน คุณจะตอบ
ได้อีกหลายคำถามที่ดูไม่เกี่ยวกัน: ทำไมสั่งอ่านไฟล์ใหญ่ ๆ ทีเดียว context เต็มเร็ว, ทำไม /clear
แล้วมันลืม, และทำไมบิลค่า token ถึงขึ้น ๆ ลง ๆ ทั้งที่ถามยาวพอกัน
หลักการ: LLM ไม่มีความจำ — ทุก turn คือการเริ่มใหม่
model ตัวหนึ่งคือ ฟังก์ชัน stateless: รับ input ก้อนหนึ่ง → คืน output → จบ ไม่เหลือ state อะไรไว้เลย. มันไม่ได้ “จดจำ” turn ที่แล้ว
แล้วมันอ้างอิงของเก่าได้ยังไง? เพราะมี harness (โปรแกรมที่ห่อ model อยู่ — Claude Code, Cursor, Copilot, ChatGPT ฯลฯ) คอย ประกอบ context ขึ้นใหม่ทุก turn แล้วส่งให้ model ทั้งก้อน:
ทุก turn harness ส่งก้อนนี้ให้ model:
[ system prompt + tool definitions ] ← กติกา + เครื่องมือที่ใช้ได้
[ memory / instruction files ] ← ความรู้ถาวรที่โหลดเข้ามา
[ บทสนทนาทั้งหมดที่ผ่านมา ] ← ข้อความ user + assistant ก่อนหน้า
[ ผลลัพธ์ tool ที่เพิ่งรัน ] ← ไฟล์ที่อ่าน, output คำสั่ง
→ model อ่านทั้งหมด → ตอบ → ลืมทั้งหมด
→ turn ถัดไป: ประกอบใหม่หมด แล้วส่งใหม่
จุดที่คนเข้าใจผิดบ่อย: นี่ ไม่ใช่ RAG / semantic retrieval — harness ไม่ได้เลือกเฉพาะ ส่วนที่ “เกี่ยวข้อง” มาส่ง. โดยพื้นฐานมันเอา ทั้งเส้น มาส่งทุกครั้ง แล้วค่อยไปจัดการตอน ยาวเกิน (ดูหัวข้อถัดไป). “ความจำ” ของ agent = การถูกป้อนอดีตซ้ำ ไม่ใช่การระลึกได้
นี่คือเหตุผลที่ซีรีส์นี้ย้ำเรื่อง ย้าย state ออกไปไว้ในไฟล์ และ ความรู้ควรอยู่ที่ไหน — เพราะสิ่งเดียวที่ agent “จำ” ได้จริงข้าม session คือสิ่งที่ถูกประกอบกลับเข้า context ทุกครั้ง
จุดที่สับสนสุด: context window ≠ ไฟล์ประวัติแชท
หลายคนเห็นข่าว “model นี้รับได้ 1M token” แล้วคิดว่าหมายถึง “แชทเก็บได้ 1M token”. คนละอัน:
| ไฟล์ประวัติแชท (transcript) | context window | |
|---|---|---|
| คืออะไร | log ทุกข้อความของ session | payload ที่ส่งให้ model ต่อ 1 turn |
| อยู่ที่ไหน | ไฟล์บนดิสก์ | ส่งผ่าน network ทุก turn |
| โตได้แค่ไหน | แทบไม่จำกัด (หลายล้าน token) | cap ที่ limit ของรุ่น (เช่น 200k, บางรุ่น 1M) |
| “1M limit” วัดอันไหน | ❌ ไม่ใช่ | ✅ ใช่ — วัดก้อนต่อ turn |
พอบทสนทนายาวจน context window ใกล้เต็ม (ไม่ใช่ตอนไฟล์ใหญ่) harness จะ compact / สรุป
ส่วนเก่าให้เหลือพอ fit — ทำ เฉพาะตอนใกล้ชน limit ไม่ใช่ทุก turn. ของเก่าที่ถูกสรุปจะเสีย
รายละเอียดไป (นี่แหละเหตุผลที่ agent “ลืม” กลาง session ยาว ๆ ทั้งที่ยังไม่ได้ /clear)
🔷 Claude Code:
/clear= ทิ้ง context window ทั้งก้อน เริ่มใหม่จากศูนย์ (transcript file ยังอยู่บนดิสก์ แต่ไม่ถูกโหลดกลับ). auto-compact = ตอนใกล้เต็มมันสรุปให้เองแล้วไปต่อ. ทั้งสองอย่างจัดการ window ไม่ใช่ ไฟล์🌐 ทั่วไป: ทุกเครื่องมือมีกลไกทำนองนี้ — บางตัวสรุปอัตโนมัติ บางตัวให้เริ่ม session ใหม่. หลักเดียวกัน: window เต็ม = ต้องตัด/สรุป ไม่งั้นส่งไม่ได้
อะไรกิน context window บ้าง
window เต็มเพราะทุกอย่างต่อไปนี้ถูกนับรวมกัน ทุก turn:
- system prompt + tool definitions — ก้อนใหญ่และค่อนข้างคงที่ (นิยาม tool ยิ่งเยอะยิ่งกิน)
- memory index + instruction file ที่ auto-load — เช่น
MEMORY.md(ดัชนี memory) ·CLAUDE.md/AGENTS.md· ไม่ใช่ทุกไฟล์ memory (ไฟล์ fact ย่อย recall เฉพาะตอน relevant) - บทสนทนาที่ผ่านมา — โตขึ้นเรื่อย ๆ ตามความยาว session
- ผลลัพธ์ tool — 👈 ตัวที่โตเร็วที่สุด: อ่านไฟล์ใหญ่ 1 ครั้ง,
catทั้งไฟล์, รันคำสั่ง ที่ output ยาว ๆ = อัดเข้า window ทีละพันหมื่น token
นี่คือเหตุผลเชิงปฏิบัติที่ว่า “ให้ agent อ่านเฉพาะส่วนที่ต้องใช้” ประหยัดกว่า “อ่านทั้งไฟล์ แล้วให้มันหาเอง” — ไม่ใช่แค่เร็วกว่า แต่ทุก turn ถัด ๆ ไปต้องแบกผลลัพธ์ก้อนนั้นต่อด้วย
ทำไม cache ถูกลง ~10 เท่า: prompt caching
สังเกตจากหัวข้อก่อน: context ที่ส่งทุก turn เกือบทั้งก้อนซ้ำของเดิม (system + memory + ประวัติที่ผ่านมา) เปลี่ยนแค่ท้าย ๆ (ข้อความใหม่ + ผลลัพธ์ tool ล่าสุด). ตรงนี้แหละที่ prompt caching เข้ามาช่วย
prompt caching เป็นกลไก ฝั่ง server ของผู้ให้บริการ model (เช่น Anthropic) ไม่ใช่ของ harness. มันจำ prefix (ส่วนหน้าที่เหมือนเดิม) ของ request ไว้ พอ turn ถัดมาส่ง prefix เดิมซ้ำ ก็ไม่ต้อง ประมวลผลใหม่ — คิดเงินถูกลงมาก (ยกตัวเลข Anthropic เป็นค่าประมาณ):
| ส่วนของ input | คิดราคา (เทียบ token ปกติ) |
|---|---|
| cache read — prefix ที่ซ้ำ เคยเขียน cache ไว้แล้ว | ~0.1× (ถูกลง ~10 เท่า) |
| cache write — เขียน cache ครั้งแรก | ~1.25× (แพงกว่านิดหน่อยครั้งเดียว) |
| ของใหม่ turn นี้ (ยังไม่เคย cache) | 1× เต็ม |
เพราะ prefix ที่ซ้ำมันยาวมาก (system + memory + ประวัติทั้งหมด) การได้ cache read = ประหยัดก้อนใหญ่
แต่มีเงื่อนไขเวลา: cache มี TTL ~5 นาที (นับจากใช้ครั้งล่าสุด). แปลว่า:
- คุยรัว ๆ ต่อเนื่อง → cache ยัง warm → จ่าย ~0.1× กับส่วนที่ซ้ำ ✅ ถูก
- หายไปเกิน ~5 นาทีแล้วกลับมา → cache หมดอายุ → turn แรกต้อง เขียน cache ใหม่ 1.25× อีกรอบ
💡 practical takeaway: ทำงานเป็น burst (คุยรัว ๆ ให้จบเป็นช่วง) ประหยัดกว่าถาม-แล้ว-หาย- เป็นชั่วโมงแล้วกลับมาถามต่อ. ไม่ใช่เพราะ “model เหนื่อย” แต่เพราะทุกครั้งที่ cache เย็น คุณจ่าย ค่าเขียน cache ก้อนใหญ่ใหม่หมด
อ่านบิลเป็น: 3 ตัวเลขใน usage
เวลาดู token usage (ใน API response หรือ dashboard) จะเห็น input แยกเป็น 3 ก้อน — อ่านให้ออกจะ รู้ทันทีว่าจ่ายค่าอะไร:
| field | คือ | ตัวคูณ |
|---|---|---|
cache_read_input_tokens | prefix ที่ hit cache | ~0.1× — ยิ่งเยอะยิ่งดี |
cache_creation_input_tokens | ส่วนที่เพิ่งเขียนลง cache | ~1.25× — จ่ายครั้งแรก |
input_tokens | ของใหม่ที่ไม่ได้ cache | 1× เต็ม |
cache_read สูง = คุณกำลังใช้ cache คุ้ม. cache_creation เด้งบ่อย = คุณปล่อยให้ cache เย็น
บ่อย (เว้นช่วงนาน หรือเปลี่ยน prefix — เช่นแก้ system prompt/memory กลางคัน ทำให้ prefix ไม่ match)
🔷 Claude Code จัดการ cache breakpoint ให้อัตโนมัติ — คุณไม่ต้องตั้งเอง. แต่รู้กลไกไว้จะ เข้าใจว่าทำไมงานที่ทำติด ๆ กันในหนึ่งช่วงถึงคุ้มกว่าเปิด-ปิด session ถี่ ๆ
best practices: ใช้ context window ให้คุ้ม + ประหยัด token
ทุกข้อด้านล่างต่อยอดจากกลไกข้างบนตรง ๆ — ไม่ใช่เคล็ดลับลอย ๆ:
🅰️ ลดของที่กิน window (จ่ายน้อยลงทุก turn)
- อ่านเฉพาะที่ต้องใช้ อย่า
catทั้งไฟล์ — ใช้ grep/search หาบรรทัดก่อน แล้วอ่านเฉพาะช่วง. ผลลัพธ์ tool คือตัวโตเร็วสุด และมันถูกแบกต่อ ทุก turn ถัดไป ไม่ใช่ครั้งเดียว - ให้คำสั่งคืน output กระชับ —
git log --onelineแทน log เต็ม ·head/tail/--quiet· filter ด้วย grep ก่อนให้ agent อ่าน. dump ยาว = ตะกอนใน window - อย่าให้ agent อ่านซ้ำสิ่งที่อ่านแล้ว — ถ้ามันมีอยู่ใน context แล้ว ไม่ต้องสั่งอ่านใหม่
🅱️ ใช้ cache ให้คุ้ม (จ่าย 0.1× แทน 1×)
- ทำงานเป็น burst — คุยรัว ๆ ให้จบเป็นช่วงภายใน ~5 นาที cache ยัง warm. หายไปนานแล้วกลับมา = จ่ายค่าเขียน cache ใหม่
- อย่าแก้ system prompt / memory / instruction file กลางคันพร่ำเพรื่อ — พวกนี้อยู่ ต้น ของ prefix. แก้ทีนึง = prefix ไม่ match = cache miss ยาวทั้งก้อนตั้งแต่จุดที่แก้ลงไป
- ของคงที่ไว้ต้น ของแปรผันไว้ท้าย — ยิ่ง prefix ส่วนหน้านิ่งนานเท่าไร cache read ยิ่งกินยาว
🅲️ จัดการ session ยาว (กัน compact กินรายละเอียด)
- สรุปสถานะลงไฟล์ แล้ว
/clearเริ่มใหม่ เมื่อ context เริ่มใหญ่ — คุมจังหวะตัดเอง ดีกว่าปล่อย auto-compact สรุปมั่ว ๆ. นี่คือเหตุผลที่ซีรีส์นี้ให้ ย้าย state ไปไฟล์ handoff - แตกงานใหญ่เป็นเฟส/subtask — งานยาวมากใน session เดียวจะโดน compact ซ้ำ ๆ (เสียรายละเอียด + แพง). แตกเป็นเฟส แต่ละเฟส resume จากไฟล์
- งานย่อยที่อยากได้แค่ผลลัพธ์ → ยกไปให้ subagent — subagent มี context window ของตัวเอง: มัน คุ้ยไฟล์/รันคำสั่งเป็นสิบ turn แล้วส่งกลับแค่ สรุปก้อนเดียว → session หลักจ่ายแค่ “คำสั่ง + สรุป” ไม่ต้องแบก tool result ระหว่างทาง. นี่คือวิธี save context หลักที่ทรงพลังสุด
- เปลี่ยนเรื่อง = เปลี่ยน context — งานใหม่ที่ไม่เกี่ยวของเก่า อย่าแบกประวัติเดิมต่อ
🔷 Claude Code:
/clear(เริ่มใหม่) ·/compact(สั่งสรุปเอง) ·/btw(ถามแทรกสั้น ๆ ไม่บวก เข้าบทสนทนาหลัก) · subagent (ยกงานย่อยไปทำแยก ส่งกลับแค่สรุป) · อ่านไฟล์แบบระบุช่วง. 🌐 ทั่วไป: ทุกเครื่องมือมี “new chat” + ค้นก่อนอ่าน — หลักเดียวกัน: ใส่เข้า window เท่าที่จำเป็น
สรุป
- LLM stateless — model ลืมทุก turn. “ความจำ” ของ agent = harness ประกอบ context ทั้งก้อน ส่งใหม่ทุกครั้ง ไม่ใช่ model ระลึกได้
- context window ≠ ไฟล์ประวัติแชท — window = payload ต่อ turn (cap ที่ limit ของรุ่น) · transcript = log บนดิสก์ (โตแทบไม่จำกัด). “1M limit” วัด window
- ยาวเกิน window → compact/สรุป (เฉพาะตอนใกล้ชน) — นี่คือที่มาของอาการ “ลืมกลาง session”
- tool results คือตัวกิน window เร็วสุด — อ่านไฟล์/รันคำสั่งเท่าที่จำเป็น ประหยัดทุก turn ถัดไป
- prompt caching ทำให้ prefix ที่ซ้ำถูกลง ~10 เท่า (~0.1×) แต่ TTL ~5 นาที → ทำงานเป็น
burst ถูกกว่าเว้นนาน · อ่านบิลจาก
cache_read(คุ้ม) vscache_creation(เพิ่งเขียน) vsinput(เต็ม) - ประหยัดจริงทำ 3 อย่าง: ใส่เข้า window เท่าที่จำเป็น (อ่านเจาะจง · output กระชับ) · คุยเป็น
burst ให้ cache warm · session ยาวให้ สรุปลงไฟล์แล้ว
/clearแทนปล่อย auto-compact กินรายละเอียด
ต่อไปในซีรีส์ “Working with AI agents”: เมื่อรู้แล้วว่า agent จำได้แค่สิ่งที่ถูกประกอบกลับ เข้า context — คำถามถัดมาคือ แล้วความรู้แต่ละอย่างควรไปอยู่ชั้นไหน ถึงจะรอด
/clear? (ความรู้ 3 ชั้น: chat / memory / repo)

