Tôi đã tự xây dựng RAG từ con số 0 bằng Python để hiểu rõ về nó
Tôi đã sử dụng LangChain trong môi trường production suốt sáu tháng. Nhưng tôi không thể giải thích được cách nó hoạt động. Tôi không biết tại sao mình lại chọn các chỉ số cụ thể hay làm thế nào văn bản trở thành các vector. Thư viện này đã che giấu đi phần logic cốt lõi.
Để khắc phục điều này, tôi đã gỡ bỏ framework đó. Tôi đã tự viết một pipeline RAG từ con số 0 chỉ với 500 dòng mã Python thuần túy.
Dưới đây là những gì tôi đã học được từ việc tự xây dựng hệ thống này một cách thủ công.
Vấn đề với các "Hộp đen" (Black Boxes)
Khi bạn sử dụng các thư viện cấp cao, bạn sẽ mất đi quyền kiểm soát. Tôi đã thấy các mô hình bị "ảo giác" (hallucinate) về các sự thật hoặc đưa ra các trích dẫn sai. Tôi không thể phân biệt được lỗi nằm ở bộ chia nhỏ văn bản (chunker), mô hình embedding, hay ở câu lệnh (prompt).
Khi bạn tự xây dựng, mọi lớp đều có thể kiểm tra được. Bạn có thể in ra chính xác các chunk được gửi đến LLM. Bạn có thể thấy chính xác nơi một câu bị ngắt.
Năm lớp của RAG
RAG không phải là một thuật toán đơn lẻ. Nó là năm quy trình khác nhau được xếp chồng lên nhau:
- Chunking: Quyết định cách chia nhỏ văn bản.
- Embedding: Chuyển đổi văn bản thành toán học.
- Retrieval: Tìm kiếm các phần phù hợp.
- Prompt Construction: Chỉ dẫn mô hình cách hành xử.
- Generation: Tạo ra câu trả lời cuối cùng.
Những bài học rút ra từ quá trình xây dựng
Chunking là bước quan trọng nhất Hầu hết các hướng dẫn đều bỏ qua bước này. Nếu bạn không sử dụng phần chồng lấp (overlap), bạn sẽ làm mất ngữ cảnh tại các điểm ranh giới. Tôi đã sử dụng kỹ thuật cửa sổ trượt (sliding window) với độ chồng lấp ở cấp độ ký tự. Điều này đảm bảo mô hình thấy được mối liên kết giữa hai chunk.
Các chỉ số khoảng cách (distance metrics) rất quan trọng Tôi đã mất hàng giờ để gỡ lỗi các kết quả tìm kiếm kém. Vấn đề không nằm ở dữ liệu, mà nằm ở chỉ số đo lường. ChromaDB mặc định sử dụng khoảng cách L2. Đối với tìm kiếm ngữ nghĩa (semantic search), bạn cần sử dụng độ tương đồng Cosine (Cosine similarity). Chỉ một dòng mã đã thay đổi tất cả.
Prompt cần có các ràng buộc Một LLM là một bộ máy hoàn thiện văn bản, không phải là một vị thần biết tuốt. Nếu bạn đặt một câu hỏi mơ hồ, nó sẽ tự bịa ra câu trả lời. Tôi đã học cách sử dụng một mẫu từ chối nghiêm ngặt. Tôi bảo mô hình rằng: "Nếu ngữ cảnh không chứa câu trả lời, hãy nói rằng bạn không biết." Điều này đã giúp giảm tỷ lệ ảo giác từ 40% xuống còn 5%.
Gửi yêu cầu theo lô (batch) Việc gửi mỗi chunk một yêu cầu HTTP riêng lẻ sẽ rất chậm. Gửi chúng theo lô sẽ nhanh hơn nhiều. Nó cho phép mô hình cục bộ thực hiện xử lý theo dạng đường ống (pipeline).
Kiểm thử từ dưới lên trên Đừng đợi đến cuối cùng mới viết test. Hãy kiểm thử bộ chia nhỏ (chunker) trước. Sau đó kiểm thử bộ embedding (embedder). Tiếp theo là kiểm thử kho lưu trữ (store). Nếu bạn kiểm thử sau cùng, bạn sẽ chỉ đang đi tìm lỗi thay vì kiểm tra logic.
Nếu bạn cảm thấy mình không thực sự hiểu stack AI của mình, hãy tự xây dựng nó. Mã nguồn không phải là mục tiêu. Tư duy mới là mục tiêu.
Cộng đồng học tập tùy chọn: https://t.me/GyaanSetuAi
