Luồng ghi dữ liệu trong EzyRAG
Back to ezyragLuồng ghi dữ liệu trong EzyRAG biến dữ liệu nguồn thành các đoạn văn bản có thể tìm kiếm bằng vector. Mỗi đoạn được lưu song song ở cơ sở dữ liệu ứng dụng và vector database: cơ sở dữ liệu giữ nội dung đầy đủ, embedding và metadata; vector database giữ vector cùng thông tin định danh tối thiểu để phục vụ tìm kiếm.
Kiến trúc tổng quát
flowchart LR
A[Yêu cầu ghi dữ liệu] --> B[Chọn collection]
B --> C[Đọc dữ liệu nguồn]
C --> D[Làm sạch văn bản]
D --> E[Chia thành các chunk]
E --> F[Tính content hash]
F --> G[Lưu hoặc cập nhật chunk]
G --> H[Lưu metadata]
H --> I{Nội dung thay đổi?}
I -- Có --> J[Sinh embedding]
I -- Không --> K[Tái sử dụng embedding]
J --> L[Lưu embedding]
K --> M[Upsert vector point]
L --> M
M --> N[Xóa chunk dư trong DB]
Luồng gồm các thành phần chính:
- API quản trị tiếp nhận yêu cầu.
- Data loader chuyển từng loại nguồn thành dữ liệu đầu vào thống nhất.
- Text cleaner chuẩn hóa văn bản.
- Chunker chia văn bản thành các đoạn nhỏ.
- Embedding service chuyển từng đoạn thành vector.
- Cơ sở dữ liệu ứng dụng lưu nội dung, embedding và metadata.
- Vector database lưu vector point để tìm kiếm tương đồng.
Điều kiện trước khi ghi dữ liệu
Hệ thống cần được cấu hình đầy đủ:
- Một embedding service đang hoạt động.
- Một vector database service đang hoạt động.
- Một chiến lược chia đoạn đã được chọn.
- Vector collection đích đã tồn tại.
- Kích thước vector của collection tương thích với embedding model.
Khi tạo collection, EzyRag lưu cấu hình collection trong cơ sở dữ liệu ứng dụng, sau đó yêu cầu vector database tạo collection tương ứng nếu chưa tồn tại. Kích thước vector thực tế có thể được đọc lại từ vector database và cập nhật vào cấu hình nội bộ.
Nếu yêu cầu ghi không chỉ định collection, hệ thống tìm collection mặc định của vector database service tương ứng. Nếu không tìm thấy, quá trình dừng trước khi xử lý dữ liệu.
Các API quản trị dùng để ghi hoặc xóa dữ liệu yêu cầu người gọi đã được xác thực và feature RAG đang được bật.
Tiếp nhận dữ liệu nguồn
Một nguồn dữ liệu được nhận diện bằng hai trường:
-
sourceType: loại nguồn dữ liệu. -
sourceId: định danh bản ghi trong nguồn đó.
Ngoài ra, nguồn có thể mang theo dữ liệu trực tiếp và metadata bổ sung.
EzyRag chọn data loader dựa trên
sourceType. Nếu không có loader tương ứng, yêu cầu thất bại thay vì âm thầm bỏ qua dữ liệu.Các nhóm nguồn được hỗ trợ theo thiết kế hiện tại gồm:
- Văn bản nhập trực tiếp.
- Bài viết.
- Sản phẩm.
- Tệp media.
- Loader mở rộng do ứng dụng đăng ký thêm.
Một loader có thể sinh ra nhiều đầu vào. Ví dụ, một sản phẩm có thể tạo dữ liệu cho nội dung mặc định và các phiên bản ngôn ngữ khác nhau; một tệp có thể tạo một đầu vào từ tiêu đề/mô tả và nhiều đầu vào từ nội dung được trích xuất trong tệp.
Trường hợp văn bản nhập trực tiếp
Văn bản nhập trực tiếp được xử lý khác với các nguồn đã tồn tại.
Trước khi chạy pipeline RAG, hệ thống tạo một bản ghi nội dung dạng knowledge data trong kho bài viết. Bản ghi này được xuất bản và trở thành nguồn dữ liệu thực tế của các chunk.
Vì vậy, định danh nguồn ban đầu trong request không phải định danh cuối cùng dùng cho các chunk. Các chunk sẽ tham chiếu tới bản ghi knowledge data vừa được tạo.
Mỗi lần gửi lại văn bản trực tiếp hiện được xem như tạo một nguồn mới, không phải cập nhật nguồn văn bản đã ghi trước đó.
Đọc và chuẩn hóa dữ liệu
Data loader chuyển dữ liệu nguồn thành một hoặc nhiều đối tượng đầu vào thống nhất, mỗi đối tượng gồm:
- Nội dung cần xử lý.
- Kiểu nội dung.
- Metadata liên quan.
Một số ví dụ:
- Bài viết cung cấp tiêu đề, nội dung, slug và phần tóm tắt.
- Sản phẩm cung cấp tên, mã, mô tả, giá, đơn vị tiền tệ và slug.
- Media cung cấp tiêu đề, caption, mô tả, URL và nội dung đọc được từ tệp.
- Văn bản trực tiếp cung cấp chính chuỗi văn bản từ yêu cầu.
Nếu bản ghi nguồn không tồn tại, loader trả về tập dữ liệu rỗng. Pipeline không tạo chunk mới và sau đó có thể dọn các chunk cũ của cùng nguồn khỏi cơ sở dữ liệu ứng dụng.
Sau khi tải, văn bản đi qua chuỗi text cleaner. Bộ làm sạch mặc định:
- Chuẩn hóa Unicode về NFC.
- Chuyển các kiểu xuống dòng về
n. - Loại bỏ ký tự điều khiển không cần thiết.
- Thu gọn khoảng trắng và tab liên tiếp.
- Loại bỏ khoảng trắng quanh dấu xuống dòng.
- Thu gọn nhiều dòng trống liên tiếp.
- Xóa khoảng trắng ở đầu và cuối nội dung.
Kiến trúc cho phép đăng ký thêm nhiều cleaner. Chúng được áp dụng tuần tự, kết quả của cleaner trước là đầu vào của cleaner sau.
Chia dữ liệu thành chunk
Sau khi làm sạch, văn bản được đưa vào chunker đang cấu hình.
Chiến lược mặc định là chia đoạn theo thứ tự ưu tiên:
- Loại bỏ HTML nhưng giữ lại ranh giới có ý nghĩa giữa đoạn văn, tiêu đề, danh sách và dòng.
- Nếu toàn bộ nội dung không vượt quá giới hạn, giữ thành một chunk.
- Nếu nội dung dài, chia theo đoạn văn và ghép tuần tự trong giới hạn kích thước.
- Nếu một đoạn văn quá dài, tiếp tục chia theo câu.
- Nếu một câu vẫn quá dài, chia cứng theo số ký tự.
Chunk rỗng không được tạo. Các chunk giữ nguyên thứ tự xuất hiện trong nguồn và được đánh số bắt đầu từ
1.
Hiện tại giới hạn chunk được tính theo số ký tự, không phải số token của embedding model.
Tạo định danh và phát hiện thay đổi
Với mỗi chunk, EzyRag tính SHA-256 từ nội dung đã làm sạch và chia đoạn. Giá trị này được lưu dưới dạng
contentHash.Khóa logic dùng để đối chiếu một chunk cũ gồm:
sourceType + sourceId + chunkIndex
Cơ chế này giúp hệ thống ghi lại cùng một nguồn mà không phải tạo mới toàn bộ dữ liệu:
- Nếu chưa có chunk tại vị trí tương ứng, hệ thống tạo bản ghi mới.
- Nếu đã có, hệ thống cập nhật nội dung và hash trên bản ghi cũ.
- Nếu hash không đổi và embedding cũ còn tồn tại, embedding được tái sử dụng.
- Nếu hash thay đổi hoặc embedding chưa có, hệ thống gọi embedding service để sinh lại vector.
Việc đối chiếu dựa trên vị trí chunk, không dựa trên nội dung gần giống. Nếu một đoạn mới được chèn ở đầu tài liệu, chỉ số của nhiều chunk phía sau có thể thay đổi và khiến chúng phải được cập nhật hoặc tạo embedding lại.
Đối với nguồn văn bản trực tiếp, cơ chế tái sử dụng này không được áp dụng vì mỗi lần ghi tạo ra một nguồn knowledge data mới.
Lưu chunk vào cơ sở dữ liệu
Bản ghi chunk chứa các thông tin chính:
- ID duy nhất của chunk.
- Loại và ID của nguồn.
- ID của vector collection.
- Vị trí chunk trong nguồn.
- Nội dung đầy đủ.
- Hash nội dung.
- Tên embedding service.
- Vector embedding.
- Thời điểm tạo và cập nhật.
Thứ tự ghi cho một chunk là:
- Thêm mới hoặc cập nhật bản ghi nội dung.
- Lưu metadata.
- Sinh embedding nếu cần.
- Cập nhật embedding và tên embedding service.
- Upsert vector point sang vector database.
ID của bản ghi chunk trong cơ sở dữ liệu ứng dụng đồng thời được dùng làm ID của vector point. Nhờ đó, kết quả tìm kiếm vector có thể ánh xạ trực tiếp về nội dung đầy đủ mà không cần thêm một bảng ánh xạ.
Hợp nhất và lưu metadata
Metadata của mỗi chunk được hợp nhất từ ba lớp theo thứ tự:
metadata nguồn → metadata của đầu vào do loader tạo → metadata riêng của chunk
Nếu các lớp sử dụng cùng một key, giá trị ở lớp phía sau được ưu tiên.
Metadata có thể chứa các thông tin như:
- Tiêu đề.
- Slug hoặc URL.
- Mã sản phẩm.
- Giá và đơn vị tiền tệ.
- Tóm tắt.
- Thuộc tính do ứng dụng tích hợp cung cấp.
Metadata đầy đủ được lưu trong cơ sở dữ liệu ứng dụng, tách khỏi bản ghi chunk chính. Cơ chế lưu hỗ trợ cập nhật theo key để tránh tạo nhiều giá trị trùng cho cùng một chunk.
Metadata này không được gửi đầy đủ sang vector database trong luồng mặc định.
Sinh embedding
Khi cần tạo embedding, hệ thống gửi nội dung chunk đã chuẩn hóa tới embedding service cùng kích thước vector của collection.
Embedding service mặc định chỉ chấp nhận dữ liệu dạng chuỗi văn bản. Kết quả trả về phải có số chiều tương thích với collection.
Sau khi nhận vector, EzyRag cập nhật vào bản ghi chunk:
- Tên embedding service đã sử dụng.
- Mảng vector embedding.
Việc lưu embedding trong cơ sở dữ liệu ứng dụng cho phép hệ thống tái sử dụng vector khi nội dung không thay đổi, giảm số lần gọi dịch vụ embedding.
Tuy nhiên, quyết định tái sử dụng hiện chỉ dựa trên hash nội dung và việc vector có tồn tại. Nó chưa so sánh tên model, embedding service hoặc cấu hình số chiều. Khi đổi model hay đổi embedding service, dữ liệu cũ cần được tái lập chỉ mục nếu muốn bảo đảm toàn bộ vector đồng nhất.
Ghi vào vector database
Mỗi chunk được chuyển thành một vector point gồm:
-
id: ID của chunk trong cơ sở dữ liệu ứng dụng. -
vector: embedding của chunk. -
payload: thông tin định danh tối thiểu.
Payload mặc định chứa:
{
"sourceType": "<loại nguồn>",
"sourceId": 123,
"chunkIndex": 1
}
Vector point được ghi theo cơ chế upsert. Nếu point cùng ID đã tồn tại, vector và payload của nó được thay thế; nếu chưa tồn tại, point mới được tạo.
Pipeline hiện upsert từng chunk riêng lẻ, dù giao diện vector database hỗ trợ một danh sách point. Với tài liệu lớn, điều này đồng nghĩa có thể phát sinh nhiều request tuần tự.
Vector database service dùng trong bước ghi được lấy từ cấu hình dịch vụ đang hoạt động. Vì vậy collection được chọn và dịch vụ vector database đang cấu hình cần trỏ tới cùng backend.
Ghi lại một nguồn đã tồn tại
Khi xử lý lại một nguồn không phải văn bản trực tiếp, EzyRag duyệt các chunk mới theo thứ tự:
Chunk mới 1 → Chunk cũ 1 Chunk mới 2 → Chunk cũ 2 Chunk mới 3 → Chunk cũ 3
Với mỗi vị trí:
- Bản ghi cũ được giữ nguyên ID.
- Nội dung, hash, collection và thời gian cập nhật được làm mới.
- Metadata được ghi lại.
- Embedding chỉ được sinh lại khi cần.
- Vector point được upsert bằng cùng ID.
Sau khi xử lý xong, các chunk trong cơ sở dữ liệu ứng dụng có chỉ số lớn hơn tổng số chunk mới sẽ bị xóa.
Ví dụ, nguồn cũ có 5 chunk nhưng nội dung mới chỉ còn 3 chunk thì các chunk số 4 và 5 bị xóa khỏi cơ sở dữ liệu ứng dụng.
Xóa một chunk
Khi người quản trị xóa một chunk cụ thể, hệ thống thực hiện theo thứ tự:
- Đọc chunk để xác định collection.
- Xác định vector database service của collection.
- Xóa vector point khỏi vector database.
- Xóa bản ghi chunk khỏi cơ sở dữ liệu ứng dụng.
Thứ tự này tránh tình huống xóa bản ghi nội bộ trước rồi không còn đủ thông tin để xác định point cần xóa.
Nhất quán dữ liệu và xử lý lỗi
Luồng ghi trải qua nhiều hệ thống:
Cơ sở dữ liệu ứng dụng → Dịch vụ embedding → Vector database
Không có một transaction phân tán bao trùm toàn bộ pipeline. Vì vậy lỗi ở bước sau có thể để lại dữ liệu đã ghi ở bước trước.
Một số trạng thái trung gian có thể xảy ra:
- Chunk đã tồn tại nhưng chưa có embedding.
- Embedding đã được lưu nhưng vector point chưa được upsert.
- Một số chunk của nguồn đã cập nhật, các chunk phía sau chưa xử lý.
- Metadata đã thay đổi nhưng embedding hoặc vector point chưa cập nhật.
- Xóa vector point thành công nhưng xóa bản ghi nội bộ thất bại.
Luồng không thực hiện rollback tự động. Có thể chạy lại quá trình ingest cho cùng nguồn để sửa phần lớn trạng thái ghi dở dang, do các chunk hiện có được cập nhật và vector point được upsert lại.
Tóm tắt
Luồng ghi dữ liệu của EzyRag có thể rút gọn thành:
Chọn collection → tải dữ liệu theo loại nguồn → làm sạch → chia chunk → tính hash → thêm mới hoặc cập nhật chunk → lưu metadata → tái sử dụng hoặc sinh embedding → lưu embedding → upsert vector point → dọn chunk dư trong DB
Thiết kế dùng chung ID giữa chunk và vector point giúp việc truy xuất từ kết quả tìm kiếm về nội dung gốc đơn giản.
contentHash giúp tránh tạo embedding lại khi nội dung không đổi. Đổi lại, hệ thống cần cơ chế retry, đối soát và dọn vector mồ côi để bảo đảm nhất quán khi một pipeline ghi qua nhiều kho dữ liệu.