Khi doanh nghiệp muốn AI assistant trả lời chính xác dựa trên tài liệu nội bộ — hợp đồng, quy trình, chính sách sản phẩm — câu trả lời là RAG (Retrieval-Augmented Generation). Bài viết này giải thích cách chúng tôi xây dựng pipeline RAG trong Assistant Core, từ ingest đến query production.
Vấn đề với LLM thông thường
LLM được huấn luyện trên dữ liệu công khai — chúng không biết sản phẩm của bạn, quy trình nội bộ, hay trạng thái đơn hàng mới nhất. Khi hỏi trực tiếp về dữ liệu không có trong training, LLM sẽ "hallucinate" — tạo ra câu trả lời trông hợp lý nhưng hoàn toàn sai.
Fine-tuning là một lựa chọn nhưng tốn kém, chậm cập nhật và không phù hợp với dữ liệu thay đổi thường xuyên. RAG giải quyết vấn đề này theo cách khác: tìm kiếm trước, trả lời sau.
Kiến trúc 2 giai đoạn của RAG
RAG tách làm 2 pipeline độc lập:
- Ingest Pipeline — chạy offline khi có tài liệu mới: Tài liệu → Parse → Chunk → Embed → Lưu vào pgvector
- Query Pipeline — chạy real-time mỗi request: Câu hỏi → Embed → Cosine Search → Top-K chunks → Inject vào prompt → LLM trả lời
Ingest Pipeline — chi tiết implementation
Chúng tôi hỗ trợ 7 định dạng đầu vào: PDF, DOCX, XLSX, CSV, TXT, URL web (qua Firecrawl), và plain text. Mỗi loại có parser riêng để extract text thuần trước khi chunking.
# Chunking strategy trong Assistant Core
CHUNK_SIZE = 4000 # characters
CHUNK_OVERLAP = 500 # overlap để không mất context
# Embedding
EMBEDDING_MODEL = "text-embedding-3-large"
EMBEDDING_DIMENSIONS = 2048 # reduced từ 3072
async def ingest_document(file_path: str, kb_id: str):
# 1. Parse
text = await parse_document(file_path)
# 2. Chunk with overlap
chunks = chunk_text(text, CHUNK_SIZE, CHUNK_OVERLAP)
# 3. Embed batch (128 chunks/request)
embeddings = await embed_batch(chunks)
# 4. Store in pgvector
await store_chunks(chunks, embeddings, kb_id)Batch embedding quan trọng để tiết kiệm chi phí API. Với 128 chunks/request, một tài liệu 200 trang chỉ cần ~20 API calls thay vì ~500.
pgvector — vector search ngay trong PostgreSQL
Thay vì dùng Pinecone hay Weaviate, chúng tôi chọn pgvector — extension PostgreSQL cho vector search. Ưu điểm lớn: không cần quản lý thêm một service riêng, transaction ACID vẫn đảm bảo, và metadata filtering dùng SQL thuần.
-- Schema cho document chunks
CREATE TABLE document_chunks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
kb_id UUID NOT NULL REFERENCES knowledge_bases(id),
content TEXT NOT NULL,
embedding VECTOR(2048),
metadata JSONB DEFAULT '{}'
);
-- IVFFlat index cho tốc độ query
CREATE INDEX ON document_chunks
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
-- Query: semantic search với metadata filter
SELECT content, 1 - (embedding <=> $query_vec) AS score
FROM document_chunks
WHERE kb_id = $kb_id
AND (metadata->>'doc_type') = 'policy' -- filter
ORDER BY embedding <=> $query_vec
LIMIT 5;Query Pipeline — real-time flow
Khi người dùng gửi câu hỏi, pipeline thực hiện 3 bước trong vòng ~150ms:
- Embed query — dùng cùng model với ingest (
text-embedding-3-large) - Cosine search — tìm 5–8 chunks gần nhất trong pgvector
- Inject context — chèn chunks vào system prompt, LLM trả lời với grounding
# Query pipeline
async def query_knowledge_base(question: str, kb_id: str) -> str:
# Embed question
q_embedding = await embed_text(question)
# Semantic search
chunks = await pgvector_search(
embedding=q_embedding,
kb_id=kb_id,
top_k=5,
min_score=0.75
)
# Build grounded prompt
context = "\n---\n".join([c.content for c in chunks])
system_prompt = f"""Trả lời dựa trên context sau:
{context}
Nếu thông tin không có trong context, hãy nói rõ."""
return await llm.chat(system_prompt, question)Production tips từ thực tế
- Chunk size 3000–4000 ký tự hoạt động tốt nhất cho tài liệu doanh nghiệp. Quá nhỏ mất context, quá lớn gây noise.
- Overlap 400–600 ký tự đảm bảo không bị mất thông tin ở ranh giới chunks.
- Minimum score threshold 0.70–0.75 để loại bỏ chunks không liên quan.
- Cache embeddings của các câu hỏi phổ biến trong Redis (TTL 1 giờ) giảm ~40% latency.
- Metadata tagging khi ingest cho phép filter theo loại tài liệu, phòng ban, ngày cập nhật.
Kết quả đo được
Với pipeline này, assistant trả lời chính xác dựa trên knowledge base nội bộ, trích dẫn nguồn cụ thể, và hầu như không hallucinate. Trong thử nghiệm với 500 câu hỏi về chính sách sản phẩm, độ chính xác đạt 91% so với 34% khi dùng LLM thuần không có RAG.