Thế hệ tăng cường truy xuất (RAG)

Firebase Genkit cung cấp các thành phần trừu tượng giúp bạn xây dựng tính năng tạo tăng cường truy xuất (RAG), cũng như các trình bổ trợ giúp tích hợp với các công cụ liên quan.

RAG là gì?

Thế hệ tăng cường truy xuất là một kỹ thuật dùng để kết hợp các dữ liệu bên ngoài nguồn thông tin vào phản hồi của một mô hình ngôn ngữ lớn (LLM). Điều quan trọng là bạn có thể thực hiện bởi vì mặc dù các LLM thường được huấn luyện dựa trên nhiều việc sử dụng các LLM trong thực tế thường đòi hỏi kiến thức về miền cụ thể (cho Ví dụ: bạn nên sử dụng một LLM (mô hình ngôn ngữ lớn) để trả lời câu hỏi câu hỏi về sản phẩm của công ty).

Một giải pháp là tinh chỉnh mô hình đó bằng dữ liệu cụ thể hơn. Tuy nhiên, việc này có thể tốn kém cả về chi phí tính toán lẫn công sức bỏ ra chuẩn bị đầy đủ dữ liệu huấn luyện.

Ngược lại, RAG hoạt động bằng cách kết hợp các nguồn dữ liệu bên ngoài vào câu lệnh tại thời gian được truyền cho mô hình. Ví dụ: bạn có thể hình dung câu lệnh, "Bart có mối quan hệ gì với Lisa?" có thể được mở rộng ("tăng cường") bằng đặt trước một số thông tin có liên quan, dẫn đến lời nhắc "Homer và Các con của Marge là Bart, Lisa và Maggie. Mối quan hệ của Bart là gì cho Lisa?"

Phương pháp này có một số ưu điểm:

  • Cách này có thể tiết kiệm chi phí hơn vì bạn không phải đào tạo lại mô hình.
  • Bạn có thể liên tục cập nhật nguồn dữ liệu của mình và LLM ngay lập tức có thể đặt việc sử dụng thông tin đã cập nhật.
  • Giờ đây, bạn có thể trích dẫn tệp đối chiếu trong câu trả lời của LLM của mình.

Mặt khác, việc sử dụng RAG tự nhiên có nghĩa là các câu lệnh dài hơn, và một số API LLM phí dịch vụ cho mỗi mã thông báo đầu vào mà bạn gửi. Cuối cùng, bạn phải đánh giá chi phí đánh đổi cho ứng dụng của bạn.

RAG là một lĩnh vực rất rộng và có nhiều kỹ thuật khác nhau được dùng để đạt được RAG có chất lượng tốt nhất. Khung Genkit cốt lõi cung cấp hai khái niệm trừu tượng chính để giúp bạn thực hiện RAG:

  • Người lập chỉ mục: thêm tài liệu vào một "chỉ mục".
  • Trình nhúng: biến đổi tài liệu thành biểu diễn vectơ
  • Trình truy xuất: truy xuất tài liệu từ một "chỉ mục", dựa trên một truy vấn.

Những định nghĩa này mang tính chủ định rộng vì Genkit không có định nghĩa rõ ràng về "chỉ mục" là gì hoặc cách tài liệu được truy xuất từ đó. Chỉ Genkit cung cấp định dạng Document và mọi nội dung khác do trình truy xuất hoặc nhà cung cấp dịch vụ lập chỉ mục.

Trình lập chỉ mục

Chỉ mục chịu trách nhiệm theo dõi các tài liệu của bạn sao cho bạn có thể nhanh chóng truy xuất tài liệu có liên quan dựa trên một truy vấn cụ thể. Đây là thường được thực hiện bằng cơ sở dữ liệu vectơ, giúp lập chỉ mục các tài liệu của bạn bằng cách sử dụng các vectơ đa chiều được gọi là các vectơ nhúng. Nhúng văn bản (không rõ ràng) thể hiện các khái niệm được thể hiện qua một đoạn văn bản; các nhãn này được tạo bằng các mô hình học máy chuyên biệt. Bằng cách lập chỉ mục văn bản bằng cách dùng nhúng, vectơ có thể nhóm văn bản có liên quan về mặt khái niệm và truy xuất tài liệu liên quan đến một chuỗi văn bản mới (truy vấn).

Trước khi có thể truy xuất tài liệu để tạo, bạn cần phải nhập chúng vào chỉ mục tài liệu của bạn. Một quy trình nhập thông thường sẽ sau:

  1. Chia nhỏ các tài liệu lớn thành các tài liệu nhỏ hơn để chỉ những tài liệu phù hợp được dùng để tăng cường câu lệnh của bạn – "cụm từ". Điều này là cần thiết vì nhiều LLM có cửa sổ ngữ cảnh hạn chế, khiến việc này không thực tế đưa vào toàn bộ tài liệu bằng lời nhắc.

    Genkit không cung cấp các thư viện phân đoạn tích hợp sẵn; tuy nhiên, vẫn có các thư viện nguồn tương thích với Genkit.

  2. Tạo các mục nhúng cho từng đoạn. Tuỳ thuộc vào cơ sở dữ liệu mà bạn đang sử dụng, bạn có thể làm điều này một cách rõ ràng bằng mô hình tạo nhúng hoặc bạn có thể sử dụng trình tạo nhúng do cơ sở dữ liệu cung cấp.

  3. Thêm đoạn văn bản và chỉ mục của đoạn văn bản đó vào cơ sở dữ liệu.

Bạn có thể chạy quy trình nhập không thường xuyên hoặc chỉ chạy một lần nếu đang làm việc với một nguồn dữ liệu ổn định. Ngược lại, nếu bạn làm việc với dữ liệu thường xuyên thay đổi, bạn có thể liên tục chạy quy trình nhập (cho ví dụ: trong điều kiện kích hoạt Cloud Firestore, mỗi khi một tài liệu được cập nhật).

Trình nhúng

Nhúng là một hàm nhận nội dung (văn bản, hình ảnh, âm thanh, v.v.) và tạo một vectơ số mã hoá ý nghĩa ngữ nghĩa của nội dung gốc. Như đã đề cập ở trên, trình nhúng được tận dụng như một phần trong quá trình lập chỉ mục. Tuy nhiên, trình nhúng cũng có thể được sử dụng một cách độc lập để tạo các mục nhúng mà không cần chỉ mục.

Chó tha mồi

Trình truy xuất là một khái niệm đóng gói logic liên quan đến bất kỳ loại tài liệu nào truy xuất. Các trường hợp truy xuất phổ biến nhất thường bao gồm truy xuất từ Tuy nhiên, trong Genkit, tệp truy xuất có thể là bất kỳ hàm nào trả về dữ liệu.

Để tạo chó săn, bạn có thể sử dụng một trong những cách triển khai được cung cấp hoặc tạo của riêng bạn.

Trình lập chỉ mục, trình truy xuất dữ liệu và trình nhúng được hỗ trợ

Genkit cung cấp tính năng hỗ trợ trình lập chỉ mục và trình truy xuất thông qua hệ thống trình bổ trợ. Chiến lược phát hành đĩa đơn các plugin sau được hỗ trợ chính thức:

  • Cơ sở dữ liệu vectơ trên đám mây Pinecone

Ngoài ra, Genkit hỗ trợ các kho lưu trữ vectơ sau đây thông qua các kho lưu trữ vectơ được xác định trước mã mẫu mà bạn có thể tuỳ chỉnh cho cấu hình cơ sở dữ liệu và giản đồ:

Chúng tôi cung cấp tính năng hỗ trợ cho mô hình nhúng thông qua các trình bổ trợ sau:

Trình bổ trợ Mô hình
AI tạo sinh của Google Nhúng văn bản Gecko
Vertex AI của Google Nhúng văn bản Gecko

Xác định Luồng RAG

Các ví dụ sau đây cho thấy cách bạn có thể nhập một bộ sưu tập tài liệu PDF về thực đơn nhà hàng vào cơ sở dữ liệu vectơ rồi truy xuất để sử dụng trong quy trình xác định những mặt hàng thực phẩm có sẵn.

Cài đặt phần phụ thuộc

Trong ví dụ này, chúng ta sẽ sử dụng thư viện textsplitter từ langchaingo và Thư viện phân tích cú pháp PDF ledongthuc/pdf:

go get github.com/tmc/langchaingo/textsplitter
go get github.com/ledongthuc/pdf

Xác định Trình lập chỉ mục

Ví dụ sau đây cho biết cách tạo trình lập chỉ mục để nhập một tập hợp các tài liệu PDF và lưu trữ chúng trong cơ sở dữ liệu vectơ cục bộ.

Phương thức này sử dụng trình truy xuất vectơ tương tự dựa trên tệp cục bộ mà Genkit cung cấp ngay từ đầu để thử nghiệm và tạo nguyên mẫu đơn giản (không sử dụng trong phiên bản chính thức)

Tạo trình lập chỉ mục

// Import Genkit's file-based vector retriever, (Don't use in production.)
import "github.com/firebase/genkit/go/plugins/localvec"

// Vertex AI provides the text-embedding-004 embedder model.
import "github.com/firebase/genkit/go/plugins/vertexai"
ctx := context.Background()

err := vertexai.Init(ctx, &vertexai.Config{})
if err != nil {
    log.Fatal(err)
}
err = localvec.Init()
if err != nil {
    log.Fatal(err)
}

menuPDFIndexer, _, err := localvec.DefineIndexerAndRetriever(
    "menuQA",
    localvec.Config{
        Embedder: vertexai.Embedder("text-embedding-004"),
    },
)
if err != nil {
    log.Fatal(err)
}

Tạo cấu hình phân đoạn

Ví dụ này sử dụng thư viện textsplitter cung cấp một văn bản đơn giản để chia nhỏ tài liệu thành các phân đoạn có thể vectơ hoá được.

Định nghĩa sau đây định cấu hình hàm phân đoạn để trả về tài liệu các phân đoạn 200 ký tự, với sự chồng chéo giữa các phân đoạn 20 ký tự.

splitter := textsplitter.NewRecursiveCharacter(
    textsplitter.WithChunkSize(200),
    textsplitter.WithChunkOverlap(20),
)

Bạn có thể xem các tuỳ chọn phân đoạn khác cho thư viện này trong Tài liệu langchaingo.

Xác định luồng cho trình lập chỉ mục

genkit.DefineFlow(
    "indexMenu",
    func(ctx context.Context, path string) (any, error) {
        // Extract plain text from the PDF. Wrap the logic in Run so it
        // appears as a step in your traces.
        pdfText, err := genkit.Run(ctx, "extract", func() (string, error) {
            return readPDF(path)
        })
        if err != nil {
            return nil, err
        }

        // Split the text into chunks. Wrap the logic in Run so it
        // appears as a step in your traces.
        docs, err := genkit.Run(ctx, "chunk", func() ([]*ai.Document, error) {
            chunks, err := splitter.SplitText(pdfText)
            if err != nil {
                return nil, err
            }

            var docs []*ai.Document
            for _, chunk := range chunks {
                docs = append(docs, ai.DocumentFromText(chunk, nil))
            }
            return docs, nil
        })
        if err != nil {
            return nil, err
        }

        // Add chunks to the index.
        err = menuPDFIndexer.Index(ctx, &ai.IndexerRequest{Documents: docs})
        return nil, err
    },
)
// Helper function to extract plain text from a PDF. Excerpted from
// https://github.com/ledongthuc/pdf
func readPDF(path string) (string, error) {
    f, r, err := pdf.Open(path)
    if f != nil {
        defer f.Close()
    }
    if err != nil {
        return "", err
    }

    reader, err := r.GetPlainText()
    if err != nil {
        return "", err
    }

    bytes, err := io.ReadAll(reader)
    if err != nil {
        return "", err
    }
    return string(bytes), nil
}

Chạy quy trình của trình lập chỉ mục

genkit flow:run indexMenu "'menu.pdf'"

Sau khi chạy luồng indexMenu, cơ sở dữ liệu vectơ sẽ được khởi đầu bằng tài liệu và sẵn sàng để sử dụng trong quy trình Genkit với các bước truy xuất.

Xác định luồng bằng tính năng truy xuất

Ví dụ sau đây cho thấy cách bạn có thể sử dụng tệp truy xuất dữ liệu trong quy trình RAG. Thích ví dụ về trình lập chỉ mục, ví dụ này sử dụng trình truy xuất vectơ dựa trên tệp của Genkit, mà bạn không nên dùng trong phiên bản chính thức.

    ctx := context.Background()

    err := vertexai.Init(ctx, &vertexai.Config{})
    if err != nil {
        log.Fatal(err)
    }
    err = localvec.Init()
    if err != nil {
        log.Fatal(err)
    }

    model := vertexai.Model("gemini-1.5-pro")

    _, menuPdfRetriever, err := localvec.DefineIndexerAndRetriever(
        "menuQA",
        localvec.Config{
            Embedder: vertexai.Embedder("text-embedding-004"),
        },
    )
    if err != nil {
        log.Fatal(err)
    }

    genkit.DefineFlow(
        "menuQA",
        func(ctx context.Context, question string) (string, error) {
            // Retrieve text relevant to the user's question.
            docs, err := menuPdfRetriever.Retrieve(ctx, &ai.RetrieverRequest{
                Document: ai.DocumentFromText(question, nil),
            })
            if err != nil {
                return "", err
            }

            // Construct a system message containing the menu excerpts you just
            // retrieved.
            menuInfo := ai.NewSystemTextMessage("Here's the menu context:")
            for _, doc := range docs.Documents {
                menuInfo.Content = append(menuInfo.Content, doc.Content...)
            }

            // Call Generate, including the menu information in your prompt.
            resp, err := model.Generate(ctx, &ai.GenerateRequest{
                Messages: []*ai.Message{
                    ai.NewSystemTextMessage(`
You are acting as a helpful AI assistant that can answer questions about the
food available on the menu at Genkit Grub Pub.
Use only the context provided to answer the question. If you don't know, do not
make up an answer. Do not add or change items on the menu.`),
                    menuInfo,
                    ai.NewUserTextMessage(question),
                },
            }, nil)
            if err != nil {
                return "", err
            }

            return resp.Text()
        })

Viết trình lập chỉ mục và trình truy xuất của riêng bạn

Bạn cũng có thể tự tạo chú chó săn mồi của riêng mình. Điều này rất hữu ích nếu tài liệu được quản lý trong một kho tài liệu không được hỗ trợ trong Genkit (ví dụ: MySQL, Google Drive, v.v.). SDK Genkit cung cấp các phương thức linh hoạt cho phép bạn cung cấp mã tuỳ chỉnh để tìm nạp tài liệu.

Bạn cũng có thể xác định chó tha mồi tuỳ chỉnh được xây dựng dựa trên những chó tha mồi hiện có trong Genkit và áp dụng các kỹ thuật RAG nâng cao (chẳng hạn như xếp hạng lại hoặc nhập câu lệnh tiện ích) ở trên cùng.

Ví dụ: giả sử bạn có một hàm xếp hạng lại tuỳ chỉnh mà bạn muốn sử dụng. Chiến lược phát hành đĩa đơn ví dụ sau đây xác định một trình truy xuất tuỳ chỉnh áp dụng hàm của bạn cho trình truy xuất trình đơn được xác định trước đó:

type CustomMenuRetrieverOptions struct {
    K          int
    PreRerankK int
}
advancedMenuRetriever := ai.DefineRetriever(
    "custom",
    "advancedMenuRetriever",
    func(ctx context.Context, req *ai.RetrieverRequest) (*ai.RetrieverResponse, error) {
        // Handle options passed using our custom type.
        opts, _ := req.Options.(CustomMenuRetrieverOptions)
        // Set fields to default values when either the field was undefined
        // or when req.Options is not a CustomMenuRetrieverOptions.
        if opts.K == 0 {
            opts.K = 3
        }
        if opts.PreRerankK == 0 {
            opts.PreRerankK = 10
        }

        // Call the retriever as in the simple case.
        response, err := menuPDFRetriever.Retrieve(ctx, &ai.RetrieverRequest{
            Document: req.Document,
            Options:  localvec.RetrieverOptions{K: opts.PreRerankK},
        })
        if err != nil {
            return nil, err
        }

        // Re-rank the returned documents using your custom function.
        rerankedDocs := rerank(response.Documents)
        response.Documents = rerankedDocs[:opts.K]

        return response, nil
    },
)