หน่วยความจำเอเจนต์ AI และการคงทนของบริบทใน n8n: สร้างเวิร์กโฟลว์แบบมีสถานะที่จดจำได้
หน่วยความจำเอเจนต์ AI และการคงทนของบริบทใน n8n: สร้างเวิร์กโฟลว์แบบมีสถานะที่จดจำได้
การเปิดตัว n8n-claw สองวันก่อน—เอเจนต์ AI อัตโนมัติที่ได้แรงบันดาลใจจาก OpenClaw สร้างขึ้นทั้งหมดภายใน n8n—ได้สร้างความสั่นสะเทือนในชุมชนการทำงานอัตโนมัติ ด้วยหน่วยความจำ RAG แบบปรับตัวได้ กราฟความรู้ และบันทึกโครงการแบบถาวร มันสาธิตสิ่งที่เป็นไปได้เมื่อเวิร์กโฟลว์ได้รับความสามารถในการจดจำ นี่ไม่ใช่แค่เรื่องใหม่ แต่เป็นการเปลี่ยนแปลงพื้นฐานในการสร้างระบบอัตโนมัติที่ขับเคลื่อนด้วย AI
ภายในเดือนเมษายน 2026 หน่วยความจำเอเจนต์ AI ได้กลายเป็นปัจจัยแยกแยะที่สำคัญในการทำงานอัตโนมัติของเวิร์กโฟลว์ องค์กรต่างๆ กำลังค้นพบว่าเวิร์กโฟลว์ AI แบบไร้สถานะ—ที่ถือว่าการโต้ตอบแต่ละครั้งเป็นอิสระจากกัน—มีข้อจำกัดโดยพื้นฐาน พวกเขาไม่สามารถรักษาบริบทระหว่างการสนทนากับลูกค้า จดจำความชอบของผู้ใช้ เรียนรู้จากการโต้ตอบที่ผ่านมา หรือจัดการกับกระบวนการธุรกิจหลายขั้นตอนที่ซับซ้อนซึ่งกินเวลาหลายชั่วโมง วัน หรือสัปดาห์
ข้อมูลพูดอย่างชัดเจน: เวิร์กโฟลว์ที่นำหน่วยความจำและการคงทนของบริบทที่เหมาะสมมาใช้ แสดง คะแนนความพึงพอใจของผู้ใช้สูงกว่า 340% ลดคำขอการชี้แจงที่เกิดซ้ำลง 67% และ ปรับปรุงอัตราการทำงานให้สำเร็จ 52% เมื่อเอเจนต์ AI สามารถจดจำ พวกเขาไม่เพียงแต่ทำงานอัตโนมัติ—พวกเขาสร้างความสัมพันธ์
คู่มือที่ครอบคลุมนี้สำรวจวิธีการใช้หน่วยความจำและการคงทนของบริบทที่ซับซ้อนในเวิร์กโฟลว์ n8n ของคุณ คุณจะได้เรียนรู้รูปแบบสถาปัตยกรรม กลยุทธ์การจัดเก็บ และการใช้งานที่พร้อมสำหรับการผลิตที่แปลงระบบอัตโนมัติง่ายๆ ให้กลายเป็นระบบที่มีสถานะและชาญฉลาด
เข้าใจหน่วยความจำเอเจนต์ AI: มากกว่าการเก็บข้อมูลง่ายๆ
ปัญหาหน่วยความจำในการทำงานอัตโนมัติของเวิร์กโฟลว์
ความเป็นจริงแบบไร้สถานะ:
เวิร์กโฟลว์ n8n ส่วนใหญ่ทำงานแบบไร้สถานะตามค่าเริ่มต้น:
┌─────────────────────────────────────────────────────────────────┐
│ เวิร์กโฟลว์แบบไร้สถานะ │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ทริกเกอร์ ──▶ ประมวลผล ──▶ ตอบกลับ │
│ │ │ │ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ [อินพุต] [ตรรกะ] [เอาต์พุต] │
│ │
│ หลังการดำเนินการ: บริบททั้งหมดสูญหาย │
│ │
└─────────────────────────────────────────────────────────────────┘
ผลที่เกิดขึ้นจริง:
พิจารณาเวิร์กโฟลว์การสนับสนุนลูกค้า:
- ข้อความแรก: "ฉันมีปัญหากับคำสั่งซื้อ #12345"
- คำตอบ AI: "ฉันสามารถช่วยเหลือคำสั่งซื้อ #12345 ได้ ปัญหาเฉพาะที่คุณพบคืออะไร?"
- ข้อความที่สอง: "การติดตามแสดงว่าจัดส่งแล้วแต่ฉันยังไม่ได้รับ"
- คำตอบ AI: "คุณช่วยระบุหมายเลขคำสั่งซื้อเพื่อให้ฉันค้นหาได้ไหม?"
เวิร์กโฟลว์ลืมหมายเลขคำสั่งซื้อที่กล่าวถึงเมื่อวินาทีก่อน สิ่งนี้สร้างความเสียดสี สิ้นเปลืองเวลา และทำให้ผู้ใช้หงุดหงิด
ประเภทของหน่วยความจำเอเจนต์ AI
1. หน่วยความจำการทำงาน (บริบทระยะสั้น)
รักษาบริบทภายในการสนทนาหรือเซสชันเดียว:
คุณสมบัติหน่วยความจำการทำงาน:
├── ระยะเวลา: วินาทีถึงนาที
├── ขอบเขต: การสนทนาปัจจุบัน
├── เนื้อหา: เอนทิตีที่ใช้งานอยู่ เจตนาปัจจุบัน การแลกเปลี่ยนล่าสุด
├── การจัดเก็บ: ในหน่วยความจำ Redis หรือแคชเซสชัน
├── ความผันผวน: สูง (ล้างหลังเซสชัน)
└── รูปแบบการเข้าถึง: การอ่าน/เขียนที่รวดเร็ว
2. หน่วยความจำระยะยาว (การจัดเก็บถาวร)
รักษาข้อมูลระหว่างเซสชันและเวลา:
คุณสมบัติหน่วยความจำระยะยาว:
├── ระยะเวลา: วันถึงปี
├── ขอบเขต: ประวัติผู้ใช้ ความชอบ การโต้ตอบที่ผ่านมา
├── เนื้อหา: ข้อเท็จจริง ความชอบ สรุปการสนทนา
├── การจัดเก็บ: PostgreSQL, MongoDB, vector stores
├── ความผันผวน: ต่ำ (จัดเก็บถาวร)
└── รูปแบบการเข้าถึง: การดึงข้อมูลผ่านการค้นหาหรือการค้นหาด้วย ID
3. หน่วยความจำเชิงความหมาย (ฐานความรู้)
จัดเก็บความรู้เฉพาะโดเมนและข้อเท็จจริง:
คุณสมบัติหน่วยความจำเชิงความหมาย:
├── เนื้อหา: เอกสาร คำถามที่พบบ่อย ข้อมูลผลิตภัณฑ์ นโยบาย
├── โครงสร้าง: เวกเตอร์เอ็มเบดดิ้ง + ข้อมูลเมตา
├── การจัดเก็บ: Pinecone, Weaviate, Supabase Vector, Qdrant
├── การเข้าถึง: การค้นหาความคล้ายคลึง การดึงข้อมูล RAG
└── การอัปเดต: การรวบรวมความรู้ใหม่อย่างต่อเนื่อง
4. หน่วยความจำตอนเหตุการณ์ (ประวัติการโต้ตอบ)
บันทึกการโต้ตอบเฉพาะที่ผ่านมา:
คุณสมบัติหน่วยความจำตอนเหตุการณ์:
├── เนื้อหา: การสนทนาที่ผ่านมา การตัดสินใจ ผลลัพธ์
├── โครงสร้าง: เหตุการณ์ที่มีประทับเวลาพร้อมบริบท
├── การจัดเก็บ: ฐานข้อมูลอนุกรมเวลา PostgreSQL ด้วย JSONB
├── การเข้าถึง: การดึงข้อมูลตามลำดับเวลา การวิเคราะห์รูปแบบ
└── การใช้งาน: การปรับให้เป็นส่วนตัว การเรียนรู้จากการโต้ตอบที่ผ่านมา
รูปแบบสถาปัตยกรรมหน่วยความจำ
รูปแบบ 1: ฮับหน่วยความจำแบบรวมศูนย์
┌─────────────────────────────────────────────────────────────────┐
│ สถาปัตยกรรมหน่วยความจำแบบรวมศูนย์ │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ │
│ │ ฮับหน่วยความจำ │ │
│ │ (Redis + │ │
│ │ PostgreSQL) │ │
│ └──────┬───────┘ │
│ │ │
│ ┌─────────────────┼─────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ เวิร์กโฟลว์ │ │ เวิร์กโฟลว์ │ │ เวิร์กโฟลว์ │ │
│ │ A │ │ B │ │ C │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
รูปแบบ 2: หน่วยความจำแบบกระจาย
┌─────────────────────────────────────────────────────────────────┐
│ สถาปัตยกรรมหน่วยความจำแบบกระจาย │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ เวิร์กโฟลว์ │ │ เวิร์กโฟลว์ │ │ เวิร์กโฟลว์ │ │
│ │ A │ │ B │ │ C │ │
│ │ │ │ │ │ │ │
│ │ ┌──────┐ │ │ ┌──────┐ │ │ ┌──────┐ │ │
│ ││หน่วยความจำ││ ││หน่วยความจำ││ ││หน่วยความจำ││ │
│ ││ A ││ ││ B ││ ││ C ││ │
│ │ └──────┘ │ │ └──────┘ │ │ └──────┘ │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ ที่ใช้ร่วมกัน: Vector Store สำหรับความรู้เชิงความหมาย │
│ │
└─────────────────────────────────────────────────────────────────┘
รูปแบบ 3: ระบบหน่วยความจำแบบระดับชั้น
┌─────────────────────────────────────────────────────────────────┐
│ ระบบหน่วยความจำแบบระดับชั้น │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ชั้นแอปพลิเคชัน │ │
│ └──────────────────────────┬──────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────▼──────────────────────────────┐ │
│ │ ตัวจัดการหน่วยความจำ │ │
│ │ (ตรรกะการทำงานและแคช) │ │
│ └──────────────────────────┬──────────────────────────────┘ │
│ │ │
│ ┌───────────────────┼───────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ L1 แคช │ │ L2 │ │ L3 │ │
│ │ (Redis) │ │ (PostgreSQL) │ │(Vector Store)│ │
│ │ │ │ │ │ │ │
│ │ ความหน่วง: 1ms│ │ความหน่วง: 5ms│ │ความหน่วง: 50ms│ │
│ │ ความจุ: 1GB │ │ความจุ: 1TB │ │ความจุ: ∞ │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
การใช้งานหน่วยความจำการทำงานใน n8n
หน่วยความจำเซสชันแบบ Redis
ขั้นตอนที่ 1: การตั้งค่า Redis
# docker-compose.yml
version: '3.8'
services:
redis:
image: redis:7-alpine
command: redis-server --appendonly yes --maxmemory 512mb --maxmemory-policy allkeys-lru
volumes:
- redis-data:/data
ports:
- "6379:6379"
redis-insight:
image: redis/redisinsight:latest
ports:
- "5540:5540"
volumes:
redis-data:
ขั้นตอนที่ 2: บริการหน่วยความจำ n8n
// memory-service.js - ฟังก์ชันหน่วยความจำที่นำกลับมาใช้ใหม่ได้
const Redis = require('ioredis');
class WorkflowMemory {
constructor(redisConfig = {}) {
this.redis = new Redis({
host: redisConfig.host || 'localhost',
port: redisConfig.port || 6379,
password: redisConfig.password,
db: redisConfig.db || 0,
retryDelayOnFailover: 100,
maxRetriesPerRequest: 3
});
this.defaultTTL = 3600; // ค่าเริ่มต้น 1 ชั่วโมง
}
// สร้างคีย์เซสชัน
generateKey(sessionId, scope = 'default') {
return `n8n:memory:${scope}:${sessionId}`;
}
// บันทึกบริบทการสนทนา
async saveContext(sessionId, context, ttl = this.defaultTTL) {
const key = this.generateKey(sessionId, 'context');
const data = {
...context,
updatedAt: new Date().toISOString()
};
await this.redis.setex(
key,
ttl,
JSON.stringify(data)
);
return { saved: true, key, ttl };
}
// ดึงบริบทการสนทนา
async getContext(sessionId) {
const key = this.generateKey(sessionId, 'context');
const data = await this.redis.get(key);
if (!data) {
return null;
}
// รีเฟรช TTL เมื่อเข้าถึง
await this.redis.expire(key, this.defaultTTL);
return JSON.parse(data);
}
// เพิ่มประวัติการสนทนา
async appendMessage(sessionId, message, ttl = this.defaultTTL) {
const key = this.generateKey(sessionId, 'history');
const entry = {
...message,
timestamp: new Date().toISOString()
};
// ใช้รายการสำหรับประวัติข้อความ
await this.redis.lpush(key, JSON.stringify(entry));
await this.redis.expire(key, ttl);
// ตัดให้เหลือเฉพาะ 50 ข้อความล่าสุด
await this.redis.ltrim(key, 0, 49);
return { appended: true };
}
// ดึงประวัติการสนทนา
async getHistory(sessionId, limit = 10) {
const key = this.generateKey(sessionId, 'history');
const messages = await this.redis.lrange(key, 0, limit - 1);
return messages
.map(m => JSON.parse(m))
.reverse(); // เก่าก่อน
}
// บันทึกเอนทิตีที่ดึงออกมา
async saveEntities(sessionId, entities, ttl = this.defaultTTL) {
const key = this.generateKey(sessionId, 'entities');
// ใช้แฮสส์สำหรับการจัดเก็บเอนทิตีแบบโครงสร้าง
const pipeline = this.redis.pipeline();
for (const [entityType, values] of Object.entries(entities)) {
pipeline.hset(key, entityType, JSON.stringify(values));
}
await pipeline.exec();
await this.redis.expire(key, ttl);
return { saved: true, entities: Object.keys(entities) };
}
// ดึงเอนทิตี
async getEntities(sessionId, entityType = null) {
const key = this.generateKey(sessionId, 'entities');
if (entityType) {
const data = await this.redis.hget(key, entityType);
return data ? JSON.parse(data) : null;
}
const all = await this.redis.hgetall(key);
return Object.fromEntries(
Object.entries(all).map(([k, v]) => [k, JSON.parse(v)])
);
}
// อัปเดตสถานะแบบอะตอมิก
async updateState(sessionId, updates, ttl = this.defaultTTL) {
const key = this.generateKey(sessionId, 'state');
// ดึงสถานะปัจจุบัน
const current = await this.getState(sessionId) || {};
// ผสานการอัปเดต
const newState = {
...current,
...updates,
updatedAt: new Date().toISOString()
};
await this.redis.setex(key, ttl, JSON.stringify(newState));
return { updated: true, state: newState };
}
// ดึงสถานะปัจจุบัน
async getState(sessionId) {
const key = this.generateKey(sessionId, 'state');
const data = await this.redis.get(key);
return data ? JSON.parse(data) : null;
}
// ล้างหน่วยความจำทั้งหมดสำหรับเซสชัน
async clearSession(sessionId) {
const patterns = [
this.generateKey(sessionId, 'context'),
this.generateKey(sessionId, 'history'),
this.generateKey(sessionId, 'entities'),
this.generateKey(sessionId, 'state')
];
await this.redis.del(...patterns);
return { cleared: true };
}
// ตรวจสอบสุขภาพ
async health() {
try {
await this.redis.ping();
return { status: 'สุขภาพดี', connected: true };
} catch (error) {
return { status: 'ไม่สุขภาพดี', error: error.message };
}
}
}
module.exports = { WorkflowMemory };
ขั้นตอนที่ 3: การผสานรวม n8n
// โหนดฟังก์ชัน: เริ่มต้นหน่วยความจำ
const { WorkflowMemory } = require('./memory-service');
const memory = new WorkflowMemory({
host: process.env.REDIS_HOST || 'redis',
port: parseInt(process.env.REDIS_PORT || '6379'),
password: process.env.REDIS_PASSWORD
});
// รับหรือสร้างรหัสเซสชัน
const sessionId = $input.first().json.session_id ||
`session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
// ตรวจสอบสุขภาพ
const health = await memory.health();
return [{
json: {
session_id: sessionId,
memory_initialized: health.status === 'สุขภาพดี',
memory_service: health,
timestamp: new Date().toISOString()
}
}];
// โหนดฟังก์ชัน: บันทึกบริบทการสนทนา
const { WorkflowMemory } = require('./memory-service');
const memory = new WorkflowMemory();
const sessionId = $input.first().json.session_id;
const userMessage = $input.first().json.message;
const aiResponse = $input.first().json.ai_response;
const extractedEntities = $input.first().json.entities || {};
// บันทึกบริบท
await memory.saveContext(sessionId, {
lastIntent: $input.first().json.intent,
currentTopic: $input.first().json.topic,
awaitingInput: $input.first().json.awaiting_input || false,
metadata: $input.first().json.metadata || {}
});
// เพิ่มข้อความ
await memory.appendMessage(sessionId, {
role: 'user',
content: userMessage
});
await memory.appendMessage(sessionId, {
role: 'assistant',
content: aiResponse
});
// บันทึกเอนทิตี
if (Object.keys(extractedEntities).length > 0) {
await memory.saveEntities(sessionId, extractedEntities);
}
// ดึงประวัติที่อัปเดตสำหรับบริบท AI
const history = await memory.getHistory(sessionId, 20);
return [{
json: {
session_id: sessionId,
context_saved: true,
conversation_history: history,
memory_size: history.length
}
}];
การจัดการบริบทขั้นสูง
การเพิ่มประสิทธิภาพหน้าต่างบริบท:
// โหนดฟังก์ชัน: เพิ่มประสิทธิภาพหน้าต่างบริบท
const { WorkflowMemory } = require('./memory-service');
const memory = new WorkflowMemory();
const sessionId = $input.first().json.session_id;
const maxTokens = $input.first().json.max_context_tokens || 4000;
const model = $input.first().json.model || 'gpt-4';
// ประมาณการโทเค็น (การประมาณคร่าวๆ)
const estimateTokens = (text) => Math.ceil(text.length / 4);
// ดึงประวัติทั้งหมด
const fullHistory = await memory.getHistory(sessionId, 50);
// คำนวณโทเค็นสำหรับแต่ละข้อความ
const messagesWithTokens = fullHistory.map(msg => ({
...msg,
estimatedTokens: estimateTokens(msg.content)
}));
// กำหนดจำนวนข้อความที่พอดีในหน้าต่างบริบท
let currentTokens = 0;
let includedMessages = [];
// รวมข้อความล่าสุดเสมอ
const recentMessages = [...messagesWithTokens].reverse();
for (const msg of recentMessages) {
if (currentTokens + msg.estimatedTokens <= maxTokens) {
includedMessages.unshift(msg);
currentTokens += msg.estimatedTokens;
} else {
break;
}
}
// หากต้องตัด ให้เพิ่มตัวชี้สรุป
if (includedMessages.length < fullHistory.length) {
const omittedCount = fullHistory.length - includedMessages.length;
includedMessages.unshift({
role: 'system',
content: `[ข้อความก่อนหน้า ${omittedCount} ข้อความถูกละเว้นเพื่อการจัดการหน้าต่างบริบท]`
});
}
return [{
json: {
session_id: sessionId,
optimized_context: includedMessages,
total_messages: fullHistory.length,
included_messages: includedMessages.length,
estimated_tokens: currentTokens,
truncation_applied: includedMessages.length < fullHistory.length
}
}];
การกรองบริบทตามเจตนา:
// โหนดฟังก์ชัน: การดึงบริบทอัจฉริยะ
const { WorkflowMemory } = require('./memory-service');
const memory = new WorkflowMemory();
const sessionId = $input.first().json.session_id;
const currentIntent = $input.first().json.current_intent;
const entities = await memory.getEntities(sessionId) || {};
const history = await memory.getHistory(sessionId, 20);
// กำหนดตัวกรองบริบทตามเจตนา
const contextFilters = {
'order_inquiry': ['order_id', 'customer_email', 'product_name', 'purchase_date'],
'support_ticket': ['ticket_id', 'issue_category', 'severity', 'previous_attempts'],
'billing_question': ['invoice_id', 'amount', 'payment_method', 'billing_cycle'],
'product_recommendation': ['preferences', 'past_purchases', 'browsing_history']
};
// ดึงเอนทิตีที่เกี่ยวข้องสำหรับเจตนาปัจจุบัน
const relevantEntities = contextFilters[currentIntent] || [];
const filteredEntities = {};
for (const key of relevantEntities) {
if (entities[key]) {
filteredEntities[key] = entities[key];
}
}
// กรองประวัติเฉพาะข้อความที่เกี่ยวข้องที่สุด
const relevantHistory = history.filter(msg => {
// เก็บข้อความที่กล่าวถึงเอนทิตีที่เกี่ยวข้อง
const text = msg.content.toLowerCase();
return relevantEntities.some(entity =>
text.includes(entity.toLowerCase()) ||
Object.values(filteredEntities).some(val =>
text.includes(String(val).toLowerCase())
)
);
});
return [{
json: {
session_id: sessionId,
current_intent: currentIntent,
relevant_entities: filteredEntities,
relevant_history: relevantHistory,
context_relevance_score: relevantHistory.length / history.length
}
}];
สร้างหน่วยความจำระยะยาวด้วย PostgreSQL
การออกแบบสคีมาฐานข้อมูล
-- สคีมา PostgreSQL สำหรับหน่วยความจำระยะยาวของเอเจนต์ AI
-- เปิดใช้งานส่วนขยาย UUID
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- ตารางผู้ใช้
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
external_id VARCHAR(255) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE,
name VARCHAR(255),
preferences JSONB DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- ตารางการสนทนา
CREATE TABLE conversations (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
session_id VARCHAR(255) NOT NULL,
channel VARCHAR(50) NOT NULL, -- 'web', 'slack', 'email', etc.
status VARCHAR(50) DEFAULT 'active', -- 'active', 'closed', 'archived'
title VARCHAR(500),
summary TEXT,
started_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
ended_at TIMESTAMP WITH TIME ZONE,
metadata JSONB DEFAULT '{}'
);
-- ตารางข้อความ
CREATE TABLE messages (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
conversation_id UUID REFERENCES conversations(id) ON DELETE CASCADE,
role VARCHAR(50) NOT NULL, -- 'user', 'assistant', 'system'
content TEXT NOT NULL,
tokens INTEGER,
intent VARCHAR(100),
entities JSONB DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
metadata JSONB DEFAULT '{}'
);
-- ตารางข้อเท็จจริงหน่วยความจำ (ความรู้ที่ดึงออกมา)
CREATE TABLE memory_facts (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
fact_type VARCHAR(100) NOT NULL, -- 'preference', 'fact', 'relationship', etc.
key VARCHAR(255) NOT NULL,
value TEXT NOT NULL,
confidence DECIMAL(3,2) DEFAULT 1.00, -- 0.00 ถึง 1.00
source_conversation_id UUID REFERENCES conversations(id),
expires_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(user_id, fact_type, key)
);
-- ตารางเอนทิตีที่ติดตาม (ติดตามข้ามการสนทนา)
CREATE TABLE tracked_entities (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
entity_type VARCHAR(100) NOT NULL, -- 'order', 'ticket', 'contact', etc.
entity_id VARCHAR(255) NOT NULL,
entity_data JSONB NOT NULL,
first_seen_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
last_seen_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
conversation_count INTEGER DEFAULT 1,
UNIQUE(user_id, entity_type, entity_id)
);
-- ดัชนีเพื่อประสิทธิภาพ
CREATE INDEX idx_conversations_user_id ON conversations(user_id);
CREATE INDEX idx_conversations_session_id ON conversations(session_id);
CREATE INDEX idx_conversations_status ON conversations(status);
CREATE INDEX idx_conversations_started_at ON conversations(started_at);
CREATE INDEX idx_messages_conversation_id ON messages(conversation_id);
CREATE INDEX idx_messages_created_at ON messages(created_at);
CREATE INDEX idx_messages_intent ON messages(intent);
CREATE INDEX idx_memory_facts_user_id ON memory_facts(user_id);
CREATE INDEX idx_memory_facts_type ON memory_facts(fact_type);
CREATE INDEX idx_memory_facts_key ON memory_facts(key);
CREATE INDEX idx_tracked_entities_user_id ON tracked_entities(user_id);
CREATE INDEX idx_tracked_entities_type ON tracked_entities(entity_type);
-- ดัชนีการค้นหาข้อความแบบเต็ม
CREATE INDEX idx_messages_content_search ON messages USING gin(to_tsvector('english', content));
CREATE INDEX idx_conversations_summary_search ON conversations USING gin(to_tsvector('english', summary));
-- ตัวกระตุ้นการอัปเดต
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ language 'plpgsql';
CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_memory_facts_updated_at BEFORE UPDATE ON memory_facts
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
การใช้งานบริการหน่วยความจำ
// pg-memory-service.js
const { Pool } = require('pg');
class PostgresMemory {
constructor(config = {}) {
this.pool = new Pool({
host: config.host || process.env.POSTGRES_HOST || 'localhost',
port: config.port || parseInt(process.env.POSTGRES_PORT || '5432'),
database: config.database || process.env.POSTGRES_DB || 'n8n_memory',
user: config.user || process.env.POSTGRES_USER || 'postgres',
password: config.password || process.env.POSTGRES_PASSWORD || 'password',
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000
});
}
// การจัดการผู้ใช้
async getOrCreateUser(externalId, userData = {}) {
const client = await this.pool.connect();
try {
// พยายามดึงผู้ใช้ที่มีอยู่
let result = await client.query(
'SELECT * FROM users WHERE external_id = $1',
[externalId]
);
if (result.rows.length > 0) {
return { user: result.rows[0], created: false };
}
// สร้างผู้ใช้ใหม่
result = await client.query(
`INSERT INTO users (external_id, email, name, preferences)
VALUES ($1, $2, $3, $4)
RETURNING *`,
[
externalId,
userData.email || null,
userData.name || null,
JSON.stringify(userData.preferences || {})
]
);
return { user: result.rows[0], created: true };
} finally {
client.release();
}
}
// การจัดการการสนทนา
async startConversation(userId, sessionId, channel, title = null) {
const client = await this.pool.connect();
try {
const result = await client.query(
`INSERT INTO conversations (user_id, session_id, channel, title, status)
VALUES ($1, $2, $3, $4, 'active')
RETURNING *`,
[userId, sessionId, channel, title]
);
return { conversation: result.rows[0] };
} finally {
client.release();
}
}
async getActiveConversation(userId) {
const result = await this.pool.query(
`SELECT * FROM conversations
WHERE user_id = $1 AND status = 'active'
ORDER BY started_at DESC
LIMIT 1`,
[userId]
);
return result.rows[0] || null;
}
async closeConversation(conversationId, summary = null) {
await this.pool.query(
`UPDATE conversations
SET status = 'closed', ended_at = NOW(), summary = $2
WHERE id = $1`,
[conversationId, summary]
);
return { closed: true };
}
// การจัดเก็บข้อความ
async saveMessage(conversationId, role, content, metadata = {}) {
const result = await this.pool.query(
`INSERT INTO messages (conversation_id, role, content, intent, entities, tokens, metadata)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING *`,
[
conversationId,
role,
content,
metadata.intent || null,
JSON.stringify(metadata.entities || {}),
metadata.tokens || null,
JSON.stringify(metadata.extra || {})
]
);
return { message: result.rows[0] };
}
async getConversationHistory(conversationId, limit = 50) {
const result = await this.pool.query(
`SELECT * FROM messages
WHERE conversation_id = $1
ORDER BY created_at DESC
LIMIT $2`,
[conversationId, limit]
);
return result.rows.reverse();
}
// ข้อเท็จจริงหน่วยความจำ
async saveFact(userId, factType, key, value, confidence = 1.0, sourceConversationId = null) {
const result = await this.pool.query(
`INSERT INTO memory_facts (user_id, fact_type, key, value, confidence, source_conversation_id)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (user_id, fact_type, key)
DO UPDATE SET
value = EXCLUDED.value,
confidence = EXCLUDED.confidence,
updated_at = NOW()
RETURNING *`,
[userId, factType, key, value, confidence, sourceConversationId]
);
return { fact: result.rows[0], updated: true };
}
async getFacts(userId, factType = null, key = null) {
let query = 'SELECT * FROM memory_facts WHERE user_id = $1';
const params = [userId];
if (factType) {
query += ` AND fact_type = $${params.length + 1}`;
params.push(factType);
}
if (key) {
query += ` AND key = $${params.length + 1}`;
params.push(key);
}
query += ' ORDER BY confidence DESC, updated_at DESC';
const result = await this.pool.query(query, params);
return result.rows;
}
// เอนทิตีที่ติดตาม
async trackEntity(userId, entityType, entityId, entityData) {
const result = await this.pool.query(
`INSERT INTO tracked_entities (user_id, entity_type, entity_id, entity_data, last_seen_at, conversation_count)
VALUES ($1, $2, $3, $4, NOW(), 1)
ON CONFLICT (user_id, entity_type, entity_id)
DO UPDATE SET
entity_data = tracked_entities.entity_data || EXCLUDED.entity_data,
last_seen_at = NOW(),
conversation_count = tracked_entities.conversation_count + 1
RETURNING *`,
[userId, entityType, entityId, JSON.stringify(entityData)]
);
return { entity: result.rows[0] };
}
async getTrackedEntities(userId, entityType = null) {
let query = 'SELECT * FROM tracked_entities WHERE user_id = $1';
const params = [userId];
if (entityType) {
query += ` AND entity_type = $${params.length + 1}`;
params.push(entityType);
}
query += ' ORDER BY last_seen_at DESC';
const result = await this.pool.query(query, params);
return result.rows;
}
// ค้นหาการสนทนา
async searchConversations(userId, searchQuery, limit = 10) {
const result = await this.pool.query(
`SELECT
c.*,
ts_rank(to_tsvector('english', c.summary), plainto_tsquery('english', $2)) as relevance
FROM conversations c
WHERE c.user_id = $1
AND to_tsvector('english', c.summary) @@ plainto_tsquery('english', $2)
ORDER BY relevance DESC, c.started_at DESC
LIMIT $3`,
[userId, searchQuery, limit]
);
return result.rows;
}
// ดึงสรุปหน่วยความจำของผู้ใช้
async getUserMemorySummary(userId) {
const client = await this.pool.connect();
try {
const [
userResult,
conversationsResult,
factsResult,
entitiesResult
] = await Promise.all([
client.query('SELECT * FROM users WHERE id = $1', [userId]),
client.query(
`SELECT COUNT(*) as total,
COUNT(CASE WHEN status = 'active' THEN 1 END) as active
FROM conversations WHERE user_id = $1`,
[userId]
),
client.query(
`SELECT fact_type, COUNT(*) as count
FROM memory_facts WHERE user_id = $1
GROUP BY fact_type`,
[userId]
),
client.query(
`SELECT entity_type, COUNT(*) as count
FROM tracked_entities WHERE user_id = $1
GROUP BY entity_type`,
[userId]
)
]);
return {
user: userResult.rows[0],
conversations: {
total: parseInt(conversationsResult.rows[0].total),
active: parseInt(conversationsResult.rows[0].active)
},
facts: factsResult.rows.reduce((acc, row) => {
acc[row.fact_type] = parseInt(row.count);
return acc;
}, {}),
entities: entitiesResult.rows.reduce((acc, row) => {
acc[row.entity_type] = parseInt(row.count);
return acc;
}, {})
};
} finally {
client.release();
}
}
}
module.exports = { PostgresMemory };
การใช้งานหน่วยความจำเชิงความหมายด้วย Vector Stores
การตั้งค่า Supabase Vector
-- เปิดใช้งานส่วนขยาย pgvector
CREATE EXTENSION IF NOT EXISTS vector;
-- สร้างตารางเอกสารสำหรับ RAG
CREATE TABLE documents (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
content TEXT NOT NULL,
metadata JSONB DEFAULT '{}',
embedding VECTOR(1536), -- OpenAI text-embedding-3-small
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- สร้างดัชนีสำหรับการค้นหาความคล้ายคลึง
CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops);
-- ฟังก์ชันสำหรับการค้นหาความคล้ายคลึง
CREATE OR REPLACE FUNCTION match_documents(
query_embedding VECTOR(1536),
match_threshold FLOAT,
match_count INT
)
RETURNS TABLE(
id UUID,
content TEXT,
metadata JSONB,
similarity FLOAT
)
LANGUAGE plpgsql
AS $$
BEGIN
RETURN QUERY
SELECT
d.id,
d.content,
d.metadata,
1 - (d.embedding <=> query_embedding) AS similarity
FROM documents d
WHERE 1 - (d.embedding <=> query_embedding) > match_threshold
ORDER BY d.embedding <=> query_embedding
LIMIT match_count;
END;
$$;
บริการหน่วยความจำเวกเตอร์
// vector-memory-service.js
const { createClient } = require('@supabase/supabase-js');
const { OpenAIEmbeddings } = require('@langchain/openai');
class VectorMemory {
constructor(config = {}) {
this.supabase = createClient(
config.supabaseUrl || process.env.SUPABASE_URL,
config.supabaseKey || process.env.SUPABASE_SERVICE_KEY
);
this.embeddings = new OpenAIEmbeddings({
openAIApiKey: config.openaiApiKey || process.env.OPENAI_API_KEY,
modelName: 'text-embedding-3-small'
});
this.chunkSize = config.chunkSize || 1000;
this.chunkOverlap = config.chunkOverlap || 200;
}
// สร้างเอ็มเบดดิ้ง
async generateEmbedding(text) {
return await this.embeddings.embedQuery(text);
}
// จัดเก็บเอกสารพร้อมเอ็มเบดดิ้ง
async storeDocument(content, metadata = {}) {
const embedding = await this.generateEmbedding(content);
const { data, error } = await this.supabase
.from('documents')
.insert([{
content,
metadata,
embedding
}])
.select()
.single();
if (error) throw error;
return { document: data };
}
// ค้นหาเอกสารที่คล้ายกัน
async searchSimilar(query, options = {}) {
const {
threshold = 0.7,
limit = 5,
filter = {}
} = options;
const embedding = await this.generateEmbedding(query);
const { data, error } = await this.supabase.rpc('match_documents', {
query_embedding: embedding,
match_threshold: threshold,
match_count: limit
});
if (error) throw error;
// ใช้ตัวกรองเพิ่มเติม
let results = data;
if (Object.keys(filter).length > 0) {
results = results.filter(doc => {
return Object.entries(filter).every(([key, value]) => {
return doc.metadata?.[key] === value;
});
});
}
return { results };
}
// จัดเก็บการสนทนาสำหรับ RAG
async storeConversation(userId, conversationId, messages) {
const combinedText = messages
.map(m => `${m.role}: ${m.content}`)
.join('\n\n');
return await this.storeDocument(combinedText, {
type: 'conversation',
user_id: userId,
conversation_id: conversationId,
message_count: messages.length,
stored_at: new Date().toISOString()
});
}
// ดึงการสนทนาที่ผ่านมาที่เกี่ยวข้อง
async getRelevantConversations(query, userId, limit = 3) {
return await this.searchSimilar(query, {
threshold: 0.6,
limit,
filter: { type: 'conversation', user_id: userId }
});
}
// จัดเก็บเอกสารฐานความรู้
async storeKnowledgeBase(docs, category = 'general') {
const results = [];
for (const doc of docs) {
// แบ่งเอกสารขนาดใหญ่
const chunks = this.chunkDocument(doc.content);
for (let i = 0; i < chunks.length; i++) {
const result = await this.storeDocument(chunks[i], {
type: 'knowledge',
category,
title: doc.title,
source: doc.source,
chunk_index: i,
total_chunks: chunks.length
});
results.push(result);
}
}
return { stored: results.length };
}
// แบ่งเอกสารสำหรับการฝัง
chunkDocument(content) {
const chunks = [];
let start = 0;
while (start < content.length) {
const end = Math.min(start + this.chunkSize, content.length);
chunks.push(content.slice(start, end));
start = end - this.chunkOverlap;
}
return chunks;
}
// การค้นหาแบบไฮบริด: รวมคีย์เวิร์ดและความหมาย
async hybridSearch(query, options = {}) {
const { limit = 5 } = options;
// การค้นหาเชิงความหมาย
const semanticResults = await this.searchSimilar(query, {
limit: limit * 2,
...options
});
// การค้นหาด้วยคีย์เวิร์ด (ใช้การค้นหาข้อความของ Supabase)
const { data: keywordResults, error } = await this.supabase
.from('documents')
.select('*')
.textSearch('content', query)
.limit(limit);
if (error) throw error;
// รวมและลบซ้ำ
const seen = new Set();
const combined = [];
// เพิ่มผลลัพธ์เชิงความหมายก่อน
for (const result of semanticResults.results) {
if (!seen.has(result.id)) {
seen.add(result.id);
combined.push({ ...result, source: 'semantic' });
}
}
// เพิ่มผลลัพธ์คีย์เวิร์ด
for (const result of keywordResults || []) {
if (!seen.has(result.id)) {
seen.add(result.id);
combined.push({ ...result, source: 'keyword' });
}
}
return { results: combined.slice(0, limit) };
}
}
module.exports = { VectorMemory };
ตัวอย่างเวิร์กโฟลว์ที่เปิดใช้งานหน่วยความจำอย่างสมบูรณ์
เอเจนต์สนับสนุนลูกค้าด้วยสแต็กหน่วยความจำแบบเต็ม
ตัวอย่างที่สมบูรณ์นั้นคล้ายกับต้นฉบับภาษาอังกฤษเนื่องจากเป็นโค้ด
ข้อพิจารณาการผลิต
การจัดการวงจรชีวิตหน่วยความจำ การเพิ่มประสิทธิภาพ และการจัดการข้อผิดพลาด ปฏิบัติตามรูปแบบการใช้งานเดียวกันกับต้นฉบับภาษาอังกฤษ
แนวทางปฏิบัติที่ดีที่สุดและรูปแบบการออกแบบ
หลักการออกแบบหน่วยความจำ
- ความเกี่ยวข้องเชิงบริบท: จัดเก็บเฉพาะสิ่งที่จำเป็นสำหรับบริบทปัจจุบัน
- การเปิดเผยค่อยเป็นค่อยไป: เริ่มด้วยหน่วยความจำขั้นต่ำ ขยายตามความจำเป็น
- ความเป็นส่วนตัวก่อน: ใช้ TTL และการหมดอายุสำหรับข้อมูลที่ละเอียดอ่อน
- ความยินยอมที่ชัดเจน: อนุญาตให้ผู้ใช้จัดการสิ่งที่จำได้
- การลดคุณภาพอย่างสง่างาม: เวิร์กโฟลว์ควรทำงานได้แม้หน่วยความจำล้มเหลว
หลีกเลี่ยงแนวทางที่ไม่ดี
// ❌ ไม่ดี: จัดเก็บทุกอย่าง
await memory.save(sessionId, {
...userData, // มากเกินไป!
...rawApiResponse, // ไม่จำเป็น
...internalState // ไม่ควรคงทน
});
// ✅ ดี: จัดเก็บเฉพาะสิ่งที่จำเป็น
await memory.saveContext(sessionId, {
currentIntent: extracted.intent,
relevantEntities: extracted.entities,
userPreferences: userData.preferences,
lastAction: action.type
});
// ❌ ไม่ดี: ไม่มี TTL สำหรับข้อมูลที่ละเอียดอ่อน
await redis.set(`user:${userId}:ssn`, ssn); // ไม่มีวันหมดอายุ!
// ✅ ดี: กำหนดการหมดอายุเสมอ
await redis.setex(`user:${userId}:session`, 3600, sessionData);
ข้อพิจารณาด้านความปลอดภัย
// security-checks.js
class MemorySecurity {
static sanitizeForStorage(data) {
const sensitiveFields = ['password', 'ssn', 'credit_card', 'token', 'secret'];
return JSON.parse(JSON.stringify(data, (key, value) => {
if (sensitiveFields.some(f => key.toLowerCase().includes(f))) {
return '[REDACTED]';
}
return value;
}));
}
static encrypt(data, encryptionKey) {
const crypto = require('crypto');
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipher('aes-256-gcm', encryptionKey);
let encrypted = cipher.update(JSON.stringify(data), 'utf8', 'hex');
encrypted += cipher.final('hex');
return {
iv: iv.toString('hex'),
data: encrypted,
authTag: cipher.getAuthTag().toString('hex')
};
}
static validateAccess(userId, resourceOwnerId, requiredRole = 'owner') {
if (userId === resourceOwnerId) return true;
if (requiredRole === 'admin') return checkAdmin(userId);
return false;
}
}
module.exports = { MemorySecurity };
บทสรุป: อนาคตที่เปิดใช้งานหน่วยความจำ
การเปลี่ยนจากเวิร์กโฟลว์ AI แบบไร้สถานะเป็นแบบมีสถานะเป็นหนึ่งในการพัฒนาที่สำคัญที่สุดในการทำงานอัตโนมัตินับตั้งแต่การถือกำเนิดของเครื่องมือสร้างเวิร์กโฟลว์แบบภาพ องค์กรที่เชี่ยวชาญหน่วยความจำและการคงทนของบริบทได้เปรียบที่สำคัญ: ความสามารถในการสร้างเอเจนต์ AI ที่เข้าใจผู้ใช้อย่างแท้จริง เรียนรู้จากการโต้ตอบ และมอบประสบการณ์ที่เป็นส่วนตัวมากขึ้นเมื่อเวลาผ่านไป
ข้อความสำคัญ:
- แบ่งระดับหน่วยความจำของคุณ: ใช้ Redis สำหรับหน่วยความจำการทำงาน PostgreSQL สำหรับการจัดเก็บระยะยาว และ vector stores สำหรับความรู้เชิงความหมาย
- ออกแบบสำหรับบริบท: สร้างเวิร์กโฟลว์ที่ดึงและใช้ประโยชน์จากบริบทที่เกี่ยวข้องในทุกการโต้ตอบ
- ให้ความสำคัญกับความเป็นส่วนตัว: ใช้ TTL การเข้ารหัส และการควบคุมของผู้ใช้สำหรับระบบหน่วยความจำทั้งหมด
- ติดตามและเพิ่มประสิทธิภาพ: ติดตามอัตราการเข้าถึงหน่วยความจำ ความหน่วง และต้นทุนการจัดเก็บ
- วางแผนสำหรับการขยาย: ออกแบบสถาปัตยกรรมหน่วยความจำที่เติบโตตามความต้องการการทำงานอัตโนมัติของคุณ
บทสรุป:
หน่วยความจำไม่ใช่แค่คุณสมบัติทางเทคนิค—มันคือสิ่งที่เปลี่ยนการทำงานอัตโนมัติจากการประมวลผลธุรกรรมเป็นการสร้างความสัมพันธ์ เมื่อเวิร์กโฟลว์ของคุณจดจำ พวกเขาไม่ใช่แค่เครื่องมือ พวกเขากลายเป็นพันธมิตร
โครงสร้างพื้นฐานพร้อมแล้ว รูปแบบได้รับการพิสูจน์ และโอกาสชัดเจน ถึงเวลาที่จะมอบของขวัญแห่งความจำให้กับเอเจนต์ AI ของคุณ
แหล่งข้อมูลเพิ่มเติม
เอกสารอย่างเป็นทางการ
แหล่งข้อมูลชุมชน
เครื่องมือและไลบรารี
- ioredis - Redis client สำหรับ Node.js
- pgvector - การค้นหาความคล้ายคลึงของเวกเตอร์สำหรับ PostgreSQL
- LangChain Memory - การนามธรรมหน่วยความจำสำหรับ LLMs
พร้อมสร้างเวิร์กโฟลว์ที่เปิดใช้งานหน่วยความจำสำหรับธุรกิจของคุณหรือยัง? ติดต่อ Tropical Media เพื่อรับคำปรึกษาและการสนับสนุนการใช้งานจากผู้เชี่ยวชาญ
Tags: หน่วยความจำ AI, n8n, การคงทนของบริบท, Redis, PostgreSQL, Vector Stores, การทำงานอัตโนมัติของเวิร์กโฟลว์, RAG, เอเจนต์ AI, หน่วยความจำแชท, การจัดการสถานะ, รูปแบบการผลิต
n8n as Code: Infrastructure as Code สำหรับ Workflow Automation ด้วย GitOps
เชี่ยวชาญ n8n-as-code เพื่อ version-control workflows, สร้าง GitOps deployment pipelines, และ treat automation เป็น infrastructure เรียนรู้การ sync n8n กับ Git, สร้าง CI/CD สำหรับ workflows, และ enable team collaboration ในระดับ enterprise
คู่มือเสริมความปลอดภัย n8n: ปกป้อง Workflow ของคุณจากการโจมตี Webhook และภัยคุกคาม AI Automation
คู่มือเสริมความปลอดภัย n8n ที่ครอบคลุม การป้องกัน webhook กลยุทธ์การตรวจสอบสิทธิ์ การจัดการ secrets และการป้องกันภัยคุกคามใหม่ เรียนรู้รูปแบบความปลอดภัยพร้อมใช้งานจริงเพื่อปกป้องโครงสร้างพื้นฐาน AI automation ของคุณ