Tech

Agent Harness (Memory Part)

27 ก.ค. 2569 13:48 น.
By snui1s
Agent Harness (Memory Part)

การทำ Agent Harness หรือ Chatbot เราจะขาดตัว Memory ไปไม่ได้ ซึ่งบางทีถ้าทำแบบผิดๆ จะทำให้ Memory มันเติบโตแบบไร้ทิศทางจนไม่สามารถควบคุมได้ หรือว่าเกิน Cost หรือไร้ประสิทธิภาพ

การทำ Agent Harness หรือ Chatbot เราจะขาดตัว Memory ไปไม่ได้ ซึ่งบางทีถ้าทำแบบผิดๆ จะทำให้ Memory มันเติบโตแบบไร้ทิศทางจนไม่สามารถควบคุมได้ หรือว่าเกิน Cost หรือไร้ประสิทธิภาพ บทความนี้จะพามาดูแนวทางการทำ Long-Term Memory Management ที่ผมทำขึ้นมาเผื่อเป็นประโยชน์ครับ

ปัญหาของระบบ Memory แบบเดิมๆ

โดยทั่วไป เวลาเราสร้าง AI Agent ให้จำข้อมูลผู้ใช้ข้าม Session เช่น ชื่อ, ความชอบ, Tech Stack ระบบจะมี Flow การทำงานแบบนี้

ฟังดูเหมือนจะดีแต่พอใช้งานจริงไปสัก 1-2 เดือน ข้อมูล Facts จะสะสมจาก 5 ข้อ กลายเป็น 50 ข้อ, 200 ข้อ, หรือ 500 ข้อ

สิ่งที่เกิดขึ้นตามมาคือ:

  1. ทุกๆ Turn ที่คุยกับ AI เราต้องแบกเอา Facts ทั้งหมด 500 ข้อ ใส่ลงไปใน System Prompt ทุกลูป

  2. ค่า API เพิ่มขึ้นเรื่อยๆตาม O(n)

  3. LLM ต้องประมวลผล Context ที่ยาวโดยไม่จำเป็น

  4. มีข้อมูลที่ขัดแย้งกันเอง เช่น Fact #02: "User prefers React for frontend", Fact #84: "User switched from React to Vue", Fact #102: "User uses Vue with TypeScript" ตัว LLM จะเริ่มสับสนว่าสรุปผู้ใช้ใช้ตัวไหนกันแน่

การแก้ปัญหาด้วย Dual-Tier Memory System

เพื่อแก้ปัญหานี้ ผมได้ออกแบบสถาปัตยกรรมแบบใหม่ที่แยกบทบาทการทำงานระหว่าง Short-Term Compaction, Hot-Memory Retrieval (LRU) และ Background Semantic Consolidation ออกจากกัน

กระบวนการทำงานคือ เมื่อคุยกับ AI ไปเรื่อยๆ จนบทสนทนายาวเกินเพดาน (เช่น 25 ข้อความ)

ระบบจะย่อประวัติสนทนาให้สั้นลงและ สกัด Fact สำคัญเกี่ยวกับผู้ใช้ (เช่น "User ชอบใช้ Vue") ส่งไปบันทึกลงใน SQLite Database

ต่อไปคือ คุมขนาด Token ไม่ให้บวม (Tier 1: Hard Cap + LRU Eviction) โดยเวลา AI จะตอบคำถาม มันจะไม่ดึง Facts ทั้งหมดใน DB มาใช้ แต่จะเลือกดึงแค่ 30 Facts ล่าสุด (ORDER BY last_used_at DESC LIMIT 30)

ทุกครั้งที่ 30 Facts นี้ถูกดึงขึ้นมาใส่ System Prompt ระบบจะแอบไปอัปเดตเวลา last_used_at = now() ให้พวกมันทันที เพื่อต่ออายุให้พวกมันยังติด Top 30 ต่อไป ทำให้ System Prompt มีขนาดคงที่เสมอหรือ Token Cost เป็น O(1) ไม่ว่าจะใช้งานมานานกี่ปีก็ตาม

ต่อไปคือ ย่อยข้อมูลขยะเบื้องหลัง (Tier 2: LLM Consolidation) เมื่อ Facts สะสมใน DB จนล้นเกณฑ์ เช่น เกิน 40 ข้อ ระบบจะเรียก LLM มาทำความสะอาดความจำ 1 ครั้ง โดยที่ตัว LLM ทำหน้าที่ รวมประโยคเรื่องเดียวกัน -> เอาข้อมูลใหม่ไปทับข้อมูลเก่าที่ขัดแย้งกัน -> ลบประโยคซ้ำ -> พอ LLM ย่อยเสร็จ ระบบจะสั่งลบของเก่าทั้งหมดและสลับใส่ Facts ชุดใหม่ ลง SQLite DB แบบ รวดเดียวจบในก้าวเดียว เพื่อป้องกันข้อมูลค้างหรือหายกลางคัน

ตัวอย่างการทำงาน

โปรแกรมสมมติ

=== 1. Adding initial facts ===

Saved new fact: 'User's name is Nell'

Saved new fact: 'User likes React'

Saved new fact: 'User works as DevOps'

Saved new fact: 'User uses TypeScript'

Saved new fact: 'User likes jazz music'

Saved new fact: 'User switched from React to Vue'

Saved new fact: 'User uses uv for Python'

=== 2. Loading Memory for System Prompt (Capped at 5) ===

Facts injected to System Prompt (5 items):

- User uses uv for Python

- User switched from React to Vue

- User likes jazz music

- User uses TypeScript

- User works as DevOps

=== 3. Adding more facts to trigger consolidation (> 8) ===

Saved new fact: 'User likes locked-room mystery novels'

Saved new fact: 'User works as Senior DevOps Engineer'

[Consolidation Triggered]: 9 facts exceed threshold (8)

Sending all facts to LLM for merging, deduplication, and superseding...

[Consolidation Complete]: Memory compressed from 9 -> 4 facts.

=== 4. Final Memory State after Consolidation ===

- User's name is Nell and works as a Senior DevOps Engineer

- User switched from React to Vue for frontend development

- User prefers TypeScript and uv for Python environment management

- User enjoys jazz music and locked-room mystery novels

ผลลัพธ์ที่ได้ก็จะเป็นแบบนี้เลยครับ โดยจะมีข้อสังเกตตามนี้

- ข้อมูลเก่าเรื่อง "User likes React" ถูกลบทิ้งและถูกแทนที่ด้วย "switched from React to Vue"

- ข้อมูล "User works as DevOps" + "Senior DevOps Engineer" ถูก Merge เข้าด้วยกันเป็นประโยคที่สมบูรณ์ขึ้น

- จำนวน Facts ลดลงจาก 9 เหลือ 4 ข้อที่มีคุณภาพสูงกว่าเดิมมาก

Benchmark

1. System Prompt Size

- ระบบ Memory ทั่วไป : เติบโตขึ้นเรื่อยๆ O(n)

- 2-Tier Memory : คงที่เสมอ O(1) (Cap ไว้ที่ 30 ข้อ)

2. Token Cost / Turn

- ระบบ Memory ทั่วไป : แพงขึ้นเรื่อยๆ ตามระยะเวลาที่ใช้งาน

- 2-Tier Memory : ประหยัดคงที่

3. ความถูกต้องของข้อมูล

- ระบบ Memory ทั่วไป : ข้อมูลซ้ำซ้อน และขัดแย้งกันเอง

- 2-Tier Memory : ข้อมูลถูก Update, Merge และ Supersede เสมอ

4. ความเร็ว (Response Time)

- ระบบ Memory ทั่วไป : ช้าลงเรื่อยๆ

- 2-Tier Memory : รวดเร็วคงที่

TL;DR

ปัญหาคือยิ่งคุยกับ AI นาน ข้อมูลยิ่งเยอะ พอขนข้อมูลทั้งหมดใส่ให้ AI อ่านทุกลูป AI ก็จะ ตอบช้า แพง Token บวม และเริ่มสับสนข้อมูลเก่า/ใหม่

วิธีแก้คือเราทำระบบ Dual-Tier Memory System โดย Tier 1 ดึงแค่ 30 ข้อล่าสุด มาใช้ ทิ้งอันเก่าไว้ใน DB แก้ปัญหา Token บวมหรือตอบช้า และ Tier 2 ใช้ AI สรุปรวบยอด และลบข้อมูลที่ขัดแย้งทิ้งเมื่อเกิน 40 ข้อ แก้ปัญหาข้อมูลสับสนหรือซ้ำซ้อน

Implement

  1. เตรียม Database สร้างตารางเก็บข้อมูลความจำ โดยเพิ่มฟิลด์ last_used_at เข้าไปด้วย

  2. ทำระบบ Tier 1 โดย เวลาจะดึงข้อมูลไปใช้ให้ Query ดึงมาเฉพาะ 30 ข้อล่าสุด ที่มีเวลาใช้งานล่าสุดเรียงจากใหม่ไปเก่าและพอดึงเสร็จให้กดอัปเดตเวลา ของ 30 ข้อนั้นทันทีใน last_used_at เพื่อให้มันคงสถานะเป็นข้อมูลชุดหลักต่อไป

  3. ทำระบบ Tier 2 โดยตั้งเงื่อนไขเช็กทุกครั้งที่มีการบันทึกความจำใหม่ ให้เช็กว่าข้อมูลใน DB เกิน 40 ข้อไหม ถ้าเกินให้ดึงข้อมูลทั้งหมด ส่งไปให้ LLM ช่วยสรุปตามกฎ (รวมเรื่องเดียวกัน, ลบของซ้ำ, ถ้าข้อมูลขัดแย้งกันให้เอาอันใหม่ล่าสุด) ให้เหลือไม่เกิน 20 ข้อและ ลบข้อมูลเก่าทั้งหมดใน DB ทิ้ง แล้วเอา 20 ข้อใหม่ที่ LLM สรุปมาบันทึกแทนที่ทันที

แต่ตัวเลขต่างๆสามารถแก้ไขได้ตามงานที่คุณกำลังทำเลย ตัวเลขที่ผมเขียนเป็นเพียงตัวอย่างเท่านั้น

Source Code สามารถกดได้ที่ลิงก์นี้เลย

https://github.com/snui1s/lru-consolidated-memory

Comments

ล็อกอินด้วย Google เพื่อร่วมเป็นส่วนหนึ่งของชุมชนนะจ๊ะ

เชื่อมต่อหัวใจผ่านตัวอักษร

เม้นแรกเป็นของคุณแล้ว

แนะนำให้อ่านต่อ

สำรวจเรื่องราวอื่นๆ ที่คุณอาจสนใจ

5 Centimeters per Second
Film

5 Centimeters per Second

อนิเมะ 1 ชม. ที่ทำให้หม่นไปสองวัน (Spoilers)

15 ก.พ. 2569
ทำไมต้อง Shiori?
Tech

ทำไมต้อง Shiori?

ที่คั่นหนังสือดิจิทัลสำหรับบันทึกโค้ด ความเหงา และข้าวแถวสีลม

4 ก.พ. 2569