Memory: ความจำสั้นกับความจำยาว
บทที่แล้วคุณต่อ tool ตัวที่สี่ searchOrderKnowledge ให้ agent ดึงข้อมูลที่เกี่ยวข้องจากคลังนโยบายเข้ามาใส่ context ตอน runtime แต่ retrieval ตอบได้แค่คำถามเดียว — “มีอะไรในคลังที่ตรงกับคำถามนี้บ้าง” มันตอบไม่ได้ว่า “เทิร์นที่แล้วเราคุยอะไรค้างไว้” หรือ “ลูกค้าคนนี้บอกอะไรกับเราเมื่อ session ก่อน” เพราะข้อมูลพวกนั้นไม่ได้อยู่ในคลัง มันคือสิ่งที่ agent เจอเองระหว่างทำงาน บทนี้คือเรื่องของ ความจำMemoryกลไกที่ให้ agent รักษาข้อมูลไว้ข้ามขอบเขตของ context window — แบ่งเป็น Short-term Memory (ประวัติที่อยู่ในหน้าต่าง) กับ Long-term Memory (โน้ตที่เขียนเก็บไว้ภายนอก) ต่างจาก Retrieval ตรงที่ memory คือ agent เขียนและอ่านโน้ต 'ของตัวเอง' ส่วน retrieval คือค้นจากคลังที่มีอยู่แยกต่างหากArchitecture — ที่ agent เก็บสิ่งที่มันได้เรียนรู้ไว้เอง แล้วเอากลับมาใช้
บทนี้ต่อยอด agent Order ตัวเดิม (repo kaen-food-ordering — กำลังจัดทำ) โดยเพิ่ม ชั้นความจำยาว ที่ agent เขียน/อ่านเองได้ เราจะสร้างสองเกรด: (ก) ที่เก็บแบน saveMemory(key, value) ลง file JSON — ไม่ต้องใช้ embeddings เลย และ (ข) ความจำเชิงความหมายที่ ใช้ vector store ตัวเดิมจากบทที่ 2 ซ้ำ โดยแยกด้วย field Kind = "memory" code ฝั่งแชตยังยึดกติกาเดิมจากคอร์ส #15 ทุกข้อ
ความจำมีสองชั้น: สั้นกับยาว
หัวข้อที่มีชื่อว่า “ความจำมีสองชั้น: สั้นกับยาว”เริ่มจากแยกให้ขาดก่อน เพราะสองคำนี้ปนกันบ่อยมาก
ความจำสั้นShort-term Memoryประวัติบทสนทนาที่อยู่ 'ใน' context window ตอนนี้ — คือ running `List<ChatMessage>` ที่สะสมทุกเทิร์น เป็นความจำที่หายไปเมื่อรีเซ็ต context หรือเมื่อถูก compact จึงต้องรักษาแต่สิ่งที่จำเป็นไว้ให้อยู่ในงบArchitecture คือประวัติบทสนทนาที่ อยู่ใน context window — running list ของ ChatMessage ที่คุณส่งเข้า model ซ้ำทุกเทิร์นตั้งแต่คอร์ส #15 มันคือความจำ “ใช้งาน” ที่ agent เห็นตอนนี้ แต่มันมีจุดอ่อนสองข้อที่ตายตัว: มัน กินงบ token (ยิ่งยาวยิ่งกิน — เรื่องของบทที่ 4) และมัน ไม่คงอยู่ พอ session จบ หรือพอเราทำ compaction (บทที่ 5) เพื่อรักษางบ ประวัติก้อนนั้นก็หายไป สิ่งที่ไม่ได้ถูกบันทึกไว้ที่อื่นก็หายตามไปด้วย
ความจำยาวLong-term Memoryโน้ตที่ agent เขียนเก็บไว้ 'นอก' context window แล้วอ่านกลับข้าม session (structured note-taking / agentic memory) — agent เป็นคนตัดสินใจเองว่าจะจดอะไร (เช่น file NOTES.md หรือ memory tool ของ Anthropic) สายวิชานี้ทอดจาก MemGPT (2023) มาถึง memory tool (2025) ควรจับคู่กับ Compaction: memory รักษาสิ่งที่ต้องรอดจากการสรุปArchitecture คือโน้ตที่ agent เขียนเก็บไว้ นอก context window แล้วอ่านกลับมาได้ — ข้าม session ข้ามการ reset หัวใจของมันคือคำว่า agent เขียนเอง: agent เป็นคนตัดสินใจว่าอะไรควรค่าแก่การจำ แล้วบันทึกลงที่เก็บของมันเอง (file JSON, ตาราง SQLite, หรือ vector store) พอ context ถูกล้าง มันก็อ่านโน้ตของตัวเองกลับมาแล้วทำงานต่อ Anthropic เรียกรูปแบบนี้ว่า “Structured note-taking, or agentic memory, is a technique where the agent regularly writes notes persisted to memory outside of the context window.”
เทียบเท่าในภาษาที่ Anthropic ใช้ — “Like Claude Code creating a to-do list, or your custom agent maintaining a NOTES.md file, this simple pattern allows the agent to track progress across complex tasks, maintaining critical context and dependencies that would otherwise be lost across dozens of tool calls.” มันเรียบง่ายถึงขั้นเป็น file NOTES.md file เดียวก็ได้ แต่ผลของมันคือสิ่งที่ความจำสั้นทำไม่ได้ — “After context resets, the agent reads its own notes and continues … This coherence across summarization steps enables long-horizon strategies that would be impossible when keeping all the information in the LLM’s context window alone.”
flowchart TB
subgraph WIN["context window — งบจำกัด"]
STM["ความจำสั้น<br/>ประวัติแชตในหน้าต่าง"]
end
subgraph OUT["นอกหน้าต่าง — คงอยู่ข้าม session"]
LTM["ความจำยาว<br/>โน้ตที่ agent เขียนเอง<br/>JSON · SQLite · vector store"]
end
A["agent"] -->|"เขียน: saveMemory"| LTM
LTM -->|"อ่านกลับ: just-in-time"| A
A --> STM
STM -->|"reset หรือ compaction"| X["หาย ถ้าไม่ได้บันทึกไว้"]
classDef longmem fill:#0891b2,stroke:#064e5b,color:#f8fafc;
class LTM longmem;
คำบรรยายภาพ: ความจำสั้นคือประวัติแชตที่อยู่ในหน้าต่าง — กินงบ token และหายเมื่อ reset หรือ compaction ส่วนความจำยาว (ช่องสีฟ้า) อยู่นอกหน้าต่าง agent เป็นคนเขียนเองผ่าน saveMemory และอ่านกลับแบบ just-in-time เมื่อต้องใช้ สิ่งที่บันทึกลงชั้นยาวเท่านั้นที่รอดข้าม session
Chip Huyen จัดกรอบความจำของ agent เป็นสามชั้น — (เรียบเรียงจาก AI Engineering ใช้เป็นโครง ไม่ใช่การอ้างคำต่อคำ): ความรู้ภายใน ที่ฝังอยู่ใน weights ของ model (ตายตัว แก้ไม่ได้ตอน runtime), ความจำสั้น คือหน้าต่าง context ที่ไม่คงอยู่, และ ความจำยาว คือที่เก็บภายนอกที่คงอยู่ข้าม session บทนี้โฟกัสสองชั้นหลัง — ชั้นที่ คุณ ในฐานะวิศวกรออกแบบได้
memory ต่างจาก retrieval ตรงไหน
หัวข้อที่มีชื่อว่า “memory ต่างจาก retrieval ตรงไหน”นี่คือเส้นแบ่งที่ต้องปักให้ชัด เพราะ code ของสองอย่างนี้ หน้าตาเหมือนกัน — ทั้งคู่ upsert ข้อความลง store แล้ว search กลับมา แต่แนวคิดต่างกันที่ ใครเป็นเจ้าของข้อมูล:
- retrieval (บทที่ 2) ค้นจาก คลังที่มีอยู่ก่อนแล้ว เป็นอิสระจาก agent — นโยบายคืนเงิน, FAQ, record ออเดอร์ ข้อมูลพวกนี้มีอยู่ไม่ว่าจะมี agent หรือไม่ agent แค่ไป อ่าน มัน
- memory คือ agent เขียนโน้ตของตัวเอง แล้วอ่านกลับ — สิ่งที่มัน ตัดสินใจว่าจะจำ จากสิ่งที่มันเจอ ข้อมูลนี้ไม่มีอยู่ก่อน จนกว่า agent จะสร้างมันขึ้น
กล่าวอีกแบบ: retrieval คือ การค้น จากคลังของคนอื่น memory คือ การจดแล้วเปิดกลับมาดู สมุดของตัวเอง จุดที่สับสนคือทั้งคู่ มักใช้โครงสร้างเก็บ/ค้นตัวเดียวกัน — ความจำยาวเชิงความหมายก็เก็บและเรียกคืนผ่าน vector store แบบเดียวกับ retrieval นั่นคือ สิ่งที่ทำให้มันเป็น “memory” ไม่ใช่โครงสร้างที่ใช้เก็บ แต่คือ การที่ agent เป็นผู้เขียน และ ตัดสินใจเองว่าจะบันทึกอะไร นี่คือเหตุผลที่บทนี้จะเอา collection ตัวเดิมจากบทที่ 2 มาใช้ซ้ำได้ตรงๆ — store เดียว หลาย Kind
รากของแนวคิดนี้ย้อนไปได้ถึงงาน MemGPT (2023) ที่ยืมอุปมาจากระบบปฏิบัติการมาใช้ตรงๆ — “the illusion of an extended virtual memory via paging between physical memory and disk.” มันแบ่งความจำเป็นสองชั้นเหมือนที่เราเพิ่งแยกไป — “Main context consists of the LLM prompt tokens—anything in main context is considered in-context… External context refers to any information that is held outside of the LLMs fixed context window.” — และหัวใจที่ยังจริงจนวันนี้คือ ตัว agent เป็นคนแก้ความจำเอง: “Memory edits and retrieval are entirely self-directed: MemGPT autonomously updates and searches through its own memory based on the current context.” สองปีต่อมา Anthropic ทำแนวคิดเดียวกันนี้ให้เป็น feature พร้อมใช้ในชื่อ memory tool
เกรด (ก): ที่เก็บความจำแบน — ไม่ต้องใช้ embeddings
หัวข้อที่มีชื่อว่า “เกรด (ก): ที่เก็บความจำแบน — ไม่ต้องใช้ embeddings”รูปที่ง่ายที่สุดของความจำยาวไม่ต้องมี vector อะไรทั้งนั้น มันคือ tool saveMemory(key, value) ที่เขียนลง file JSON file เดียว นี่คือ “two-layer memory” ของคอร์ส #12 (บทที่ 4) ที่จับต้องได้จริงเป็นครั้งแรก:
using Microsoft.Extensions.AI;using System.Text.Json;
// ที่เก็บความจำยาวฝั่ง client — file JSON file เดียวที่ agent เป็นเจ้าของconst string memoryPath = "order-agent-memory.json";
Dictionary<string, string> LoadMemory() => File.Exists(memoryPath) ? JsonSerializer.Deserialize<Dictionary<string, string>>(File.ReadAllText(memoryPath)) ?? new() : new();
AITool saveMemory = AIFunctionFactory.Create( (string key, string value) => { var mem = LoadMemory(); mem[key] = value; // agent เป็นคนเลือกว่าจะจำอะไร File.WriteAllText(memoryPath, JsonSerializer.Serialize(mem)); return $"saved: {key}"; }, name: "saveMemory", description: "บันทึกโน้ตถาวร (key -> value) ที่ต้องอยู่รอดข้าม session เช่นข้อจำกัดอาหารประจำตัวของลูกค้า");แล้วตอนเริ่ม session ใหม่ ให้ อ่านความจำกลับมาแล้วฉีดเข้า system prompt — นี่คือขา “อ่านกลับ” ของวงจร:
var mem = LoadMemory();var memoryBlock = mem.Count == 0 ? "(ยังไม่มีโน้ตความจำ)" : string.Join("\n", mem.Select(kv => $"- {kv.Key}: {kv.Value}"));
var system = new ChatMessage(ChatRole.System, "คุณคือผู้ช่วย support ลูกค้าของบริการฟู้ดเดลิเวอรี\n" + "โน้ตความจำระยะยาวที่คุณเคยบันทึกไว้:\n" + memoryBlock);
// system message นี้ต่อเข้า loop เดิมจาก #15 — x-api-key, anthropic-version: 2023-06-01,// model id "claude-opus-4-8" (ตัวอย่าง ณ 2026-07), ไม่ส่ง temperature, อ่านคำตอบจาก content[]var options = new ChatOptions { Tools = [saveMemory, searchOrderKnowledge, getOrder, getDeliveryStatus, issueRefund] };เท่านี้ก็ครบวงจรความจำยาวขั้นพื้นฐานแล้ว — agent เขียนผ่าน saveMemory ระหว่างทำงาน แล้วรอบหน้าอ่านกลับผ่าน system prompt ไม่มี embeddings ไม่มี vector store ไม่มี key ที่สองของ provider ไหนเลย ถ้าจำนวนโน้ตยังน้อยและคุณอยากจับคู่ตรงๆ ด้วย key นี่คือรูปที่ควรเริ่ม — อย่าเพิ่งเอื้อมไปหา vector DB โดยไม่จำเป็น
เกรด (ข): ความจำยาวเชิงความหมาย — ใช้ vector store ตัวเดิมซ้ำ
หัวข้อที่มีชื่อว่า “เกรด (ข): ความจำยาวเชิงความหมาย — ใช้ vector store ตัวเดิมซ้ำ”พอโน้ตเยอะขึ้นจนการจับคู่ด้วย key ตรงๆ ไม่พอ — คุณอยากถามว่า “ลูกค้าคนนี้เคยบอกข้อจำกัดอะไรไว้บ้าง” แล้วให้มันค้นด้วยความหมาย ไม่ใช่ key เป๊ะๆ — ตรงนี้แหละที่ความจำยาวกับ retrieval มาบรรจบกันที่โครงสร้างเดียวกัน เราเอา collection ตัวเดิมจากบทที่ 2 มาใช้ซ้ำได้เลย เพียงแยกด้วย field Kind:
// embedder และ col คือของเดิมจากบทที่ 2 (IEmbeddingGenerator + VectorStoreCollection<string, OrderDoc>)// จำกติกาจากบทที่ 2: Claude ไม่มี embeddings endpoint — embedder ตัวนี้คือ provider ที่สอง// (text-embedding-3-small, 1536 มิติ ตรงกับ [VectorStoreVector(Dimensions: 1536)])
// เขียนความจำ — Kind = "memory" แยกมันออกจาก policy/faq/order ใน store เดียวกันvar note = "ลูกค้า 10432 แพ้ถั่ว — เวลาแนะนำเมนูต้องเลี่ยงเมนูที่มีถั่วทุกครั้ง";await col.UpsertAsync(new OrderDoc{ Id = $"mem-{Guid.NewGuid():N}", Kind = "memory", // field IsIndexed จากบทที่ 2 — กรองได้ Text = note, Embedding = (await embedder.GenerateAsync(note)).Vector});
// อ่านกลับ — search เฉพาะ record ที่เป็น memory (กรองด้วย Kind ที่ indexed ไว้)ReadOnlyMemory<float> q = (await embedder.GenerateAsync("ลูกค้า 10432 มีข้อจำกัดอาหารอะไรไหม")).Vector;
var recallOptions = new VectorSearchOptions<OrderDoc> { Filter = d => d.Kind == "memory" };await foreach (VectorSearchResult<OrderDoc> r in col.SearchAsync(q, top: 3, recallOptions)) Console.WriteLine(r.Record.Text); // .Record คือโน้ต, .Score คือความใกล้1 store, สอง Kind — "policy"/"faq"/"order" คือ retrieval (คลังของคนอื่น) ส่วน "memory" คือความจำของ agent เอง กรองแยกกันด้วย Kind ที่ indexed ไว้แล้ว การค้นทั้งสองฝั่งใช้ semantic searchSemantic Searchการค้นด้วยความคล้ายของความหมาย (vector search) แทนการจับคู่คำตรงตัวแบบ keyword/BM25 — เปลี่ยน query เป็น embedding แล้วหา record ที่เวกเตอร์อยู่ใกล้ที่สุด เป็นแกนหลักของ RAG; แต่วิธี term-based อย่าง BM25 ก็เป็น baseline ที่แข็งแรงและเบากว่า ไม่ต้องรีบเอื้อมไปหา vector DB เสมอไปArchitecture กลไกเดียวกัน แต่ความหมายต่างกันตามที่แยกไว้ข้างบน
InMemoryVectorStore จากบทที่ 2 หายทั้งหมดเมื่อ process จบ ซึ่งขัดกับหัวใจของความจำยาว ถ้าต้องการให้โน้ตอยู่รอดข้ามการรีสตาร์ตโดยไม่ต้องตั้ง server ให้สลับไปใช้ SqliteVec (ยัง prerelease เหมือน concrete store ตัวอื่น — ใส่ --prerelease ตอนติดตั้ง) มันให้ file เดียวที่ persist ลง disk จริง โครง MEVD ที่เหลือ ([VectorStoreKey], [VectorStoreVector(Dimensions: 1536)], UpsertAsync, SearchAsync) ไม่ต้องแก้เลย
ควรจำอะไร: protocol ที่ Anthropic ฉีดให้เอง
หัวข้อที่มีชื่อว่า “ควรจำอะไร: protocol ที่ Anthropic ฉีดให้เอง”คำถามที่ยากกว่า code คือ เมื่อไรควรเขียน และเขียนอะไร Anthropic ทำ memory tool เป็น feature พร้อมใช้แล้ว — “The memory tool lets Claude store and retrieve information across conversations in a directory of memory files. Claude can create, read, update, and delete files that persist between sessions, building up knowledge over time without keeping everything in the context window.” — และมันทำงานแบบ just-in-time ตรงกับที่บทที่ 2 วางไว้: “Memory supports just-in-time context retrieval. Rather than loading all relevant information up front, an agent records what it learns in memory files and reads them back on demand.”
สิ่งที่น่าเรียนรู้ที่สุดคือ protocol ว่าเมื่อไรควรเขียน ที่ Anthropic ฉีดเข้าไปให้ model เองอัตโนมัติ — มันบอก model ว่า “IMPORTANT: ALWAYS VIEW YOUR MEMORY DIRECTORY BEFORE DOING ANYTHING ELSE.” และเตือนให้สมมติว่าจะถูกขัดจังหวะเสมอ — “ASSUME INTERRUPTION: Your context window might be reset at any moment, so you risk losing any progress that is not recorded in your memory directory.” กติกาเรียบง่ายที่ถอดมาใช้กับ agent Order ของคุณได้ตรงๆ: อ่านความจำก่อนเริ่มทำงานทุกครั้ง และ บันทึกทุกสิ่งที่ต้องรอดข้ามการ reset ทันทีที่รู้ — เช่นเหตุผลการคืนเงินที่ตัดสินไปแล้ว หรือข้อจำกัดอาหารของลูกค้า ไม่ใช่รอจนจบ session
tool entry ของ Anthropic คือ {"type": "memory_20250818", "name": "memory"} — แค่นั้นคือ config ทั้งหมด และตัว tool นี้ GA บน Messages API แล้ว (ไม่ต้องใส่ beta header) แต่ helper ฝั่ง C# ยังอยู่ใน namespace beta: Tools = [new MemoryTool20250818()] โดยมี subclass ของ BetaAbstractMemoryTool ขับผ่าน client.Beta.Messages.ToolRunner(...) จุดที่ต้องเข้าใจให้ชัด — “The memory tool operates client-side: Claude requests file operations, and your application executes them. You control where and how the data is stored through your own infrastructure.” คือ Anthropic ไม่ได้เก็บความจำให้คุณ model แค่ ขอ ให้อ่าน/เขียน file ส่วนที่เก็บจริง (JSON, SQLite, vector store อย่างในบทนี้) คือของคุณทั้งหมด กติกาฝั่งแชตยังเหมือน #15 ทุกข้อ
ความจำซื้องบ token กลับมา — แต่ต้องคู่กับ compaction
หัวข้อที่มีชื่อว่า “ความจำซื้องบ token กลับมา — แต่ต้องคู่กับ compaction”ทำไมเราถึงยอมลงแรงทำที่เก็บภายนอกทั้งหมดนี้? เพราะความจำยาว ซื้องบ token คืนมา — ทุกอย่างที่ย้ายออกไปอยู่นอกหน้าต่างคือ token ที่ไม่ต้องแบกไว้ในหน้าต่างทุกเทิร์น Chip Huyen สรุปเหตุผลไว้ตรง — “The agentic pattern often deals with information that exceeds a model’s context limit. A memory system that supplements the model’s context in handling information can significantly enhance an agent’s capabilities.”
แต่ความจำกับ compaction (บทที่ 5) แก้คนละครึ่งของปัญหาเดียวกัน และควรใช้คู่กัน Anthropic แนะตรงๆ — “For long-running agents, consider using both: compaction keeps the active context small without client-side bookkeeping, and memory preserves the information that must survive summarization.” compaction ย่อประวัติให้เล็กโดยไม่ต้องจัดการเอง ส่วน memory รักษาสิ่งที่ ต้อง รอดจากการย่อไว้อย่างตั้งใจ
Anthropic รายงานว่า memory คู่กับ context editing ให้ “84% token savings and a 39% performance improvement” — ตัวเลขนี้มาจาก eval แบบ web-search 100 เทิร์น ครั้งเดียว ในเงื่อนไขเฉพาะ ให้อ่านมันเป็น ทิศทางของหลักการ (ย้ายของออกนอกหน้าต่างช่วยได้ทั้งงบและคุณภาพ) ไม่ใช่ตัวเลขรับประกันที่จะได้ซ้ำกับ agent ของคุณ ผลจริงขึ้นกับงานและวิธีวัด — ซึ่งเป็นเหตุผลว่าทำไมคอร์ส #13 (evals) ถึงจำเป็น
สรุปบทนี้
หัวข้อที่มีชื่อว่า “สรุปบทนี้”ความจำสั้นคือประวัติในหน้าต่าง — ใช้งานได้ทันทีแต่กินงบและหายเมื่อ reset ความจำยาวคือโน้ตที่ agent เขียนเองไว้นอกหน้าต่างแล้วอ่านกลับข้าม session สิ่งที่ทำให้มันเป็น memory ไม่ใช่โครงสร้างที่เก็บ (จะเป็น JSON แบน หรือ vector store ตัวเดียวกับ retrieval ก็ได้) แต่คือ agent เป็นผู้เขียนและตัดสินใจเองว่าจะจำอะไร — ต่างจาก retrieval ที่ค้นจากคลังของคนอื่น คุณสร้างมาแล้วสองเกรดและเห็นว่ามันซื้องบ token คืนมาได้ แต่ต้องจับคู่กับ compaction เพื่อรักษาสิ่งที่ชี้ขาดไว้ให้รอด — ซึ่งคือเรื่องของสองบทถัดไป: บทที่ 4 วัดงบ token จริง แล้วบทที่ 5 ลงมือทำ compaction
บทนี้อิงต้นทางที่ลงวันที่กำกับ อ่านต่อได้โดยตรง:
- Anthropic, “Effective context engineering for AI agents” (2025-09-29) — structured note-taking / agentic memory, การอ่านโน้ตกลับหลัง context reset และการจับคู่ memory กับ compaction สำหรับ agent ที่รันยาว
- Anthropic, “Memory tool” (tool id
memory_20250818, เข้าถึง 2026-07-19) — memory tool ที่เก็บ/เรียกข้อมูลข้าม conversation, การทำงานแบบ just-in-time, ความเป็นเจ้าของฝั่ง client และ protocol “view memory before doing anything / assume interruption” - Anthropic, “Context management” (Sept 2025) — ตัวเลข 84% token / 39% performance จาก eval 100 เทิร์น (ผลการวัดครั้งเดียว ลงวันที่กำกับ)
- Chip Huyen, “AI Engineering” — chapter summaries (2025) — โครง taxonomy สามชั้นของความจำ (ความรู้ภายใน / สั้น / ยาว — ใช้เป็นโครง เรียบเรียง ไม่ใช่คำต่อคำ)
- Chip Huyen, “Agents” (2025-01-07) — ระบบความจำที่เสริม context ของ model ช่วยยกความสามารถของ agent เมื่อข้อมูลเกินเพดาน context
- Packer et al., “MemGPT: Towards LLMs as Operating Systems” (2023-10) — อุปมา virtual memory ของ OS, การแบ่ง main context กับ external context และการที่ agent แก้/ค้นความจำของตัวเองแบบ self-directed
เช็กความเข้าใจ — บทที่ 3
ข้อ 1 / 3อะไรคือความต่างที่ 'ตัดสิน' ว่าสิ่งหนึ่งเป็น memory ไม่ใช่ retrieval?