Ta sẽ xây một bộ nhớ episodic hoàn chỉnh cho agent chăm sóc khách hàng bằng pgvector — lựa chọn tuyệt vời khi bạn đã có Postgres và muốn join memory với dữ liệu người dùng.
Bước 1 — Bật extension và tạo bảng
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE agent_memory (
id bigserial PRIMARY KEY,
user_id int NOT NULL,
content text NOT NULL, -- text gốc, BẮT BUỘC lưu
embedding vector(1536) NOT NULL, -- khớp model text-embedding-3-small
kind text DEFAULT 'episodic',
created_at timestamptz DEFAULT now()
);
Lưu ý: content chính là payload để đưa vào prompt sau này. Đừng chỉ lưu vector.
Bước 2 — Tạo index
-- Index vector cho tìm kiếm ngữ nghĩa
CREATE INDEX ON agent_memory
USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64);
-- Index phụ cho filter theo user (bài 6 sẽ nói kỹ)
CREATE INDEX ON agent_memory (user_id);
Bước 3 — Ghi ký ức (write path)
import psycopg2
def remember(user_id, text):
vec = embed(text) # list 1536 float
cur.execute(
"INSERT INTO agent_memory (user_id, content, embedding) "
"VALUES (%s, %s, %s)",
(user_id, text, vec) # psycopg2 + pgvector adapter
)
conn.commit()
remember(42, "Khách phàn nàn giao hàng trễ 3 ngày, đã hoàn 50k voucher")
Bước 4 — Truy hồi ký ức liên quan (read path)
Đây là trái tim: dùng toán tử <=> (cosine distance) của pgvector, kết hợp filter user_id.
SQL = (
"SELECT content, 1 - (embedding <=> %s) AS score "
"FROM agent_memory "
"WHERE user_id = %s "
"ORDER BY embedding <=> %s " # gần nhất trước
"LIMIT %s"
)
def recall(user_id, query, k=5):
qvec = embed(query)
cur.execute(SQL, (qvec, user_id, qvec, k))
return cur.fetchall()
hits = recall(42, "đơn hàng của tôi bị sao vậy")
# -> [("Khách phàn nàn giao hàng trễ...", 0.83), ...]
<=> là cosine distance (0 = trùng khớp). Ta đổi thành score = 1 - distance cho dễ đọc.
Bước 5 — Ghép vào prompt (augment)
def build_prompt(user_id, question):
memories = recall(user_id, question, k=5)
context = "\n".join(f"- {m[0]}" for m in memories)
return (
"Ký ức liên quan về khách hàng:\n" + context +
f"\n\nCâu hỏi hiện tại: {question}\n"
"Trả lời dựa trên ký ức trên, nếu không có thì nói chưa có thông tin."
)
Bước 6 — Ngưỡng và write-back
Đừng nhét mọi mẩu tìm được. Đặt ngưỡng score (ví dụ chỉ lấy score > 0.75) để tránh nhồi ký ức không liên quan. Sau mỗi lượt, lưu lại tóm tắt đáng nhớ:
memories = [m for m in recall(uid, q) if m[1] > 0.75]
# ... sau khi trả lời:
remember(uid, summarize(conversation)) # write-back
Checklist triển khai pgvector
- [ ] Extension
vectorđã bật. - [ ]
vector(1536)khớp đúng model đang dùng. - [ ] Có index HNSW cho embedding.
- [ ] Có index cho các field filter (
user_id). - [ ] Luôn lưu
content(text gốc). - [ ] Có ngưỡng score khi retrieve.
- [ ] Có cơ chế write-back.
Sai lầm thường gặp
- Quên
CREATE INDEXHNSW → mọi truy vấn là brute-force, ban đầu chạy được nhưng chậm dần khi bảng lớn. - Không filter
user_id→ agent trả về ký ức của khách khác (rò rỉ dữ liệu nghiêm trọng!). - Đưa vector vào SQL sai định dạng — cần adapter pgvector cho psycopg2 hoặc truyền dạng chuỗi
'[...]'. - Lấy top-k không ngưỡng → nhồi ký ức rác vào prompt khi không có mẩu nào thực sự liên quan.
- VACUUM/ANALYZE bị bỏ quên trên bảng ghi nhiều — Postgres cần thống kê cập nhật để chọn plan đúng.
Học xong bài này rồi?
Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu,
và học hết khóa thì có chứng chỉ.
Lưu tiến độ của tôi