Bỏ qua đến nội dung chính

A multi-search RAG pipeline

📖 Nội dung bài học

Họ phông chữ

Tóm tắt

Khi bạn có cả tìm kiếm ngữ nghĩa (vector embeddings) và tìm kiếm từ vựng (BM25) hoạt động độc lập, bước tiếp theo là kết hợp chúng thành một pipeline tìm kiếm thống nhất. Cách tiếp cận kết hợp này tận dụng thế mạnh của cả hai phương pháp để mang lại kết quả chính xác hơn.

Xây dựng một giao diện thống nhất

Cả hai cách triển khai tìm kiếm đều chia sẻ API gần như giống hệt nhau - cả hai đều có các phương thức add_document()search(). Sự nhất quán này giúp dễ dàng gói chúng trong một lớp Retriever duy nhất, lớp này điều phối giữa hai phương pháp.

Retriever hoạt động như một người điều phối, người:

  • Nhận câu hỏi của người dùng
  • Chuyển tiếp nó đến cả VectorIndex và BM25Index
  • Thu thập kết quả từ cả hai hệ thống
  • Hợp nhất các kết quả bằng thuật toán xếp hạng

Reciprocal Rank Fusion

Thách thức nằm ở việc hợp nhất kết quả từ các phương pháp tìm kiếm khác nhau. Mỗi hệ thống trả về kết quả với các cơ chế tính điểm khác nhau, vì vậy bạn không thể chỉ kết hợp điểm trực tiếp. Thay vào đó, chúng ta dùng kỹ thuật gọi là Reciprocal Rank Fusion (RRF).

Đây là cách RRF hoạt động với một ví dụ thực tế. Giả sử VectorIndex của bạn trả về kết quả được xếp hạng là: Phần 2, Phần 7, Phần 6. Trong khi đó, BM25Index của bạn trả về: Phần 6, Phần 2, Phần 7.

Để hợp nhất các kết quả này, bạn tạo một bảng kết hợp hiển thị thứ hạng của từng đoạn văn bản từ cả hai hệ thống:

Công thức RRF tính điểm cho mỗi tài liệu:

RRF_score(d) = Σ(1 / (k + rank_i(d)))

Trong đó k là một hằng số (thường là 60, mặc dù 1 hoạt động tốt để có kết quả rõ ràng hơn) và rank_i(d) là thứ hạng của tài liệu d trong hệ thống xếp hạng thứ i.

Đối với mỗi đoạn văn bản, bạn tính toán:

  • Phần 2: 1.0/(1+1) + 1.0/(1+2) = 0.833
  • Phần 7: 1.0/(1+2) + 1.0/(1+3) = 0.583
  • Phần 6: 1.0/(1+3) + 1.0/(1+1) = 0.75

Sau khi sắp xếp theo điểm, thứ hạng cuối cùng trở thành: Phần 2 (thứ nhất), Phần 6 (thứ hai), Phần 7 (thứ ba).

Triển khai

Việc triển khai lớp Retriever rất đơn giản:

class Retriever:
    def __init__(self, *indexes):
        self._indexes = list(indexes)
    
    def add_document(self, document):
        for index in self._indexes:
            index.add_document(document)
    
    def search(self, query_text, k=1, k_rrf=60):
        # Get results from all indexes
        all_results = []
        for idx, results in enumerate(all_results):
            for rank, (doc, _) in enumerate(results):
                # Track document ranks across systems
                # Apply RRF formula
                # Return merged, sorted results

Điểm mấu chốt là thuật toán RRF tạo ra một thứ hạng thống nhất bằng cách xem xét mức độ hoạt động của từng tài liệu trên tất cả các hệ thống tìm kiếm, thay vì dựa vào bất kỳ phương pháp tính điểm nào.

Kiểm tra cách tiếp cận kết hợp

Khi thử nghiệm với một truy vấn như "what happened with INC-2023-Q4-011?", cách tiếp cận kết hợp mang lại kết quả tốt hơn đáng kể so với bất kỳ phương pháp nào. Thay vì nhận được kết quả không mong muốn từ tìm kiếm vector thuần túy, giờ đây bạn nhận được báo cáo sự cố an ninh mạng có liên quan nhất trước, sau đó là nội dung kỹ thuật phần mềm có liên quan.

Khả năng mở rộng

Vẻ đẹp của thiết kế này là tính mô-đun của nó. Vì mỗi chỉ mục tìm kiếm triển khai cùng một giao diện (add_document()search()), bạn có thể dễ dàng thêm các phương pháp tìm kiếm mới vào hệ thống. Cho dù đó là một mô hình embedding khác, tìm kiếm miền chuyên biệt hay bất kỳ kỹ thuật truy xuất nào khác, miễn là nó tuân theo API đã thiết lập, nó sẽ tích hợp liền mạch vào pipeline kết hợp.

Cách tiếp cận tìm kiếm kết hợp này thể hiện một cải tiến đáng kể về độ chính xác truy xuất bằng cách kết hợp sự hiểu biết ngữ nghĩa của tìm kiếm vector với sự khớp từ khóa chính xác của tìm kiếm từ vựng, tất cả được thống nhất thông qua thuật toán xếp hạng RRF có cơ sở toán học.

Tải xuống

🔁 Bài học liên quan

📚 Nguồn & ghi nhận

Bài học có hữu ích không?

Góp ý / Báo lỗiPhát hiện sai sót hoặc có ý tưởng cải thiện?