検索拡張生成(RAG)

Firebase Genkit は、検索拡張生成の構築に役立つ抽象化機能を提供します (RAG)フローと、関連ツールとの統合を可能にするプラグインが含まれます。

RAG とは

検索拡張生成は、外部のソフトウェア ツールを組み込むために使用される手法 LLM のレスポンスに変換しています。データサイエンティストや LLM は通常、幅広い種類のテキストでトレーニングされるのに対し、 LLM を実際に使用するには、多くの場合、特定の分野の知識( たとえば LLM を使用して顧客の質問に確認しましょう。 。

1 つの解決策は、より具体的なデータを使用してモデルをファインチューニングすることです。ただし、 コンピューティング費用の点でも、必要な労力の点でも、 トレーニング データを準備します。

対照的に、RAG は外部データソースをプロンプトに組み込み、 渡される時刻を表します。たとえば、プロンプトは、 「バートとリサの関係は?」拡張(拡張)される場合もあります。 関連する情報が先頭に追加され、「Homer and マージの子供たちはバート、リサ、マギーという名前です。バートの関係とは どうしますか?」

このアプローチにはいくつかのメリットがあります。

  • モデルを再トレーニングする必要がないため、費用対効果を高めることができます。
  • データソースは継続的に更新でき、LLM はすぐに 更新された情報の使用についてのみです。
  • LLM の回答に参照を引用できるようになりました。

一方、RAG を使用する場合は当然、プロンプトが長くなり、LLM API も サービスの料金は、送信した入力トークンごとに発生します。最終的には アプリケーションに最適な コストトレードオフがあります

RAG の分野は非常に広範であり、目的達成のためにさまざまな手法が用いられる 実現しますGenkit のコア フレームワークには主に 2 つの抽象化があります。 RAG の作成に役立ちます。

  • インデクサ: ドキュメントを「インデックス」に追加します。
  • エンべディング: ドキュメントをベクトル表現に変換します。
  • リトリーバー: クエリに応じて「インデックス」からドキュメントを取得します。

Genkit はこうした定義が目的上広範であるため、Genkit は 「インデックス」とはドキュメントの取得方法などを説明しますGenkit のみ Document 形式を提供し、それ以外はすべて Retriever または インデクサ実装プロバイダ。

インデクサ

インデックスは、次のような方法でドキュメントを追跡します。 特定のクエリに対して関連するドキュメントをすばやく取得できます。これは最も 通常はベクトル データベースを使用します。ベクトル データベースでは、 エンベディングと呼ばれる多次元ベクトルです。テキスト エンベディング(不透明) 文章によって表現される概念を表します。これらは 専用の ML モデルを使用しますエンべディングを使用してテキストをインデックス化することにより、 概念的に関連するテキストをクラスタ化してドキュメントを取得できる 新しいテキスト文字列(クエリ)に関連する検索です。

生成のためにドキュメントを取得する前に、以下を行う必要があります。 ドキュメントのインデックスに取り込みます一般的な取り込みフローでは、 次のとおりです。

  1. 大きなドキュメントを小さなドキュメントに分割して、関連性があるものだけにする 部分はプロンプトを補強する目的で使用されます。必須項目です なぜなら、多くの LLM はコンテキスト ウィンドウが限られており、 プロンプトにドキュメント全体を含めます

    Genkit には組み込みのチャンク ライブラリがありません。ただしオープンテストでは Genkit と互換性のあるソース ライブラリを使用できます。

  2. チャンクごとにエンベディングを生成する。使用しているデータベースに応じて エンベディング生成モデルを使って明示的に行うか、 データベースが提供するエンベディング生成ツールを使用できます。

  3. テキスト チャンクとそのインデックスをデータベースに追加します。

取り込みフローを実行する頻度が低いか、作業中の場合は 1 回だけ 必要があります一方、データを扱う場合は 取り込みフローを継続的に実行して、 Cloud Firestore トリガーで、ドキュメントが更新されるたびにトリガーされます)。

埋め込み

エンべディングとは、コンテンツ(テキスト、画像、音声など)を受け取り、元のコンテンツのセマンティックな意味をエンコードする数値ベクトルを作成する関数です。前述のように、エンベディングはインデックス付けのプロセスの一部として利用されますが、インデックスなしでエンべディングを作成するために個別に使用することもできます。

リトリーバー

リトリーバーは、あらゆる種類のドキュメントに関連するロジックをカプセル化した概念です。 使用します。最も一般的な取得ケースでは、通常、データソースから ベクトルストアがありますが、Genkit ではデータを返す任意の関数をリトリーバーとすることができます。

リトリーバーを作成するには、提供されている実装のいずれかを使用するか、 独自に作成できます。

サポートされているインデクサ、リトリーバー、埋め込みツール

Genkit は、プラグイン システムを通じてインデクサとリトリーバーをサポートしています。「 次のプラグインが正式にサポートされています。

  • Pinecone クラウド ベクトル データベース

さらに、Genkit は事前定義されたベクトル ストアと コード テンプレート。データベース構成やユースケースに応じて schema:

エンベディング モデルのサポートは、次のプラグインによって提供されます。

プラグイン モデル
Google の生成 AI Gecko テキスト エンベディング
Google Vertex AI Gecko テキスト エンベディング

RAG フローの定義

次の例は、レストランのメニューの PDF ドキュメント コレクションを取り込む方法を示しています。 ベクトル データベースに格納し、取得してフローで使用することで、どのような食品が入手可能か判断できます。

依存関係のインストール

この例では、langchaingotextsplitter ライブラリを使用し、 ledongthuc/pdf PDF 解析ライブラリを使用します。

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

インデクサを定義する

次の例は、インデクサを作成して PDF ドキュメントのコレクションを取り込む方法を示しています。 ローカルのベクトル データベースに保存します。

ローカル ファイルベースのベクトル類似度 Retriever を使用 Genkit には、シンプルなテストとプロトタイピングにすぐに使える機能が備わっています( 使用)

インデクサを作成する

// 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)
}

チャンク構成の作成

この例では、単純なテキストを提供する textsplitter ライブラリを使用します。 スプリッターを使用して、ドキュメントをベクトル化可能なセグメントに分割します。

次の定義では、ドキュメントを返すようにチャンク関数を構成します。 200 文字のチャンク間に重複します。

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

このライブラリのその他のチャンク オプションは、このモジュールの langchaingo ドキュメントをご覧ください。

インデクサ フローを定義する

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
}

インデクサ フローを実行する

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

indexMenu フローを実行すると、ベクトル データベースに次のデータがシードされます。 取得ステップを含む Genkit フローで使用できます。

取得を含むフローを定義する

次の例は、RAG フローでリトリーバーを使用する方法を示しています。高評価 インデクサの例では、Genkit のファイルベースのベクトル リトリーバーを使用します。 本番環境では使用すべきでない要素です

    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()
        })

独自のインデクサとレトリバーを作成する

独自のレトリーバーを作成することもできます。この方法は、 ドキュメントが Genkit でサポートされていないドキュメント ストア( MySQL、Google ドライブなど)。Genkit SDK には柔軟なメソッドが備わっており、 ドキュメントを取得するためのカスタムコードを提供します

また、既存のレトリバーを基にしたカスタム レトリバーを定義することもできます。 高度な RAG 手法(再ランキングやプロンプト設計など)を 。

たとえば、使用したいカスタムの再ランキング関数があるとします。「 次の例では、関数を 前に定義したメニュー レトリバー:

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
    },
)