검색 보강 (RAG)

검색 증강 생성을 빌드하는 데 도움이 되는 추상화를 제공하는 Firebase Genkit (RAG) 플로우뿐만 아니라 관련 툴과의 통합을 제공하는 플러그인도 지원합니다.

RAG란 무엇인가요?

검색 증강 생성은 외부 IP 주소를 LLM의 대답에 넣는 방법을 학습합니다. 중요한 것은 왜냐하면 일반적으로 LLM은 다양한 종류의 LLM을 실용적으로 사용하려면 종종 예를 들어 LLM을 사용하여 고객의 질문이 사용합니다.

한 가지 해결책은 보다 구체적인 데이터를 사용하여 모델을 미세 조정하는 것입니다. 그러나 컴퓨팅 비용과 필요한 노력 측면에서 모두 비용이 많이 들 수 있습니다. 적절한 학습 데이터를 준비할 수 있습니다

반면 RAG는 외부 데이터 소스를 프롬프트에 통합하는 방식으로 작동합니다. 모델에 전달되는 시간을 나타냅니다. 예를 들어, 프롬프트를 떠올려 보세요 "민지와 준서는 어떤 관계인가요?" 는 앞에 몇 가지 관련 정보를 추가하여 "호머와 인간이 마지의 자녀의 이름은 바트, 리사, 매기입니다. 바트의 관계는 무엇인가 어떻게 해야 할까요?"

이 방식의 장점은 다음과 같습니다.

  • 모델을 다시 학습시킬 필요가 없기 때문에 비용 효율적일 수 있습니다.
  • 데이터 소스를 지속적으로 업데이트할 수 있고 LLM은 사용할 수 없습니다.
  • 이제 LLM의 대답에서 참고문헌을 인용할 수 있습니다.

반면에 RAG를 사용하면 당연히 프롬프트가 더 길어지고 일부 LLM API는 서비스는 전송하는 각 입력 토큰에 대한 요금이 부과됩니다 궁극적으로 비용을 절감할 수 있습니다

RAG는 매우 광범위한 분야이며, 이를 달성하기 위해 최상의 품질의 RAG를 개발합니다. 핵심 Genkit 프레임워크는 애플리케이션 아키텍처의 두 가지 주요 추상화로 RAG를 할 수 있도록 도와주세요.

  • 색인 생성기: '색인'에 문서를 추가합니다.
  • 임베딩: 문서를 벡터 표현으로 변환
  • Retrievers: 지정된 쿼리를 통해 '색인'에서 문서를 검색합니다.

Genkit는 편견이 없다는 점에서 의도적으로 이러한 정의는 광범위합니다. '색인'이란 무엇일까요? 정확히 어떻게 문서를 가져올 수 있는지 알 수 있습니다. Genkit만 해당 Document 형식을 제공하고 나머지는 모두 리트리버 또는 색인 생성기 구현 제공자입니다.

색인 생성기

색인은 특정 쿼리가 주어진 경우 관련 문서를 빠르게 검색할 수 있습니다. 이것은 가장 일반적으로 벡터 데이터베이스를 사용하여 수행됩니다. 벡터 데이터베이스는 임베딩이라고 하는 다차원 벡터가 있습니다. 텍스트 임베딩 (불투명) 텍스트 구절로 표현된 개념을 나타냅니다. 이러한 포드가 ML 모델을 빌드하는 법을 알아보았습니다 벡터는 임베딩을 사용하여 텍스트의 색인을 생성함으로써 데이터베이스는 개념적으로 관련된 텍스트를 클러스터링하고 로 검색됩니다.

생성 목적으로 문서를 검색하려면 먼저 다음 작업을 수행해야 합니다. 문서 색인으로 수집할 수 있습니다. 일반적인 수집 흐름은 있습니다.

  1. 큰 문서만 작은 문서로 분할하여 관련성 높은 문서만 부분은 프롬프트('청킹')를 보강하는 데 사용됩니다. 이는 필수 항목입니다. 왜냐하면 많은 LLM이 컨텍스트 윈도우가 제한되어 있기 때문에 프롬프트를 통해 전체 문서를 포함할 수 있습니다

    Genkit는 내장된 청킹 라이브러리를 제공하지 않습니다. Google Cloud Platform이라는 소스 라이브러리를 제공합니다.

  2. 각 청크의 임베딩을 생성합니다. 사용하는 데이터베이스에 따라 임베딩 생성 모델을 사용하여 명시적으로 이 작업을 수행하거나 데이터베이스에서 제공하는 임베딩 생성기를 사용할 수도 있습니다

  3. 텍스트 청크와 색인을 데이터베이스에 추가합니다.

수집 흐름은 자주 실행하지 않거나 작업 중인 경우 한 번만 실행할 수 있습니다. 도움이 될 수 있습니다 반면에 데이터를 사용하여 작업하는 경우 자주 변경되는 경우 수집 흐름을 지속적으로 실행할 수 있습니다( Cloud Firestore 트리거에서 문서가 업데이트될 때마다).

임베딩

임베딩은 콘텐츠 (텍스트, 이미지, 오디오 등)를 가져와 원본 콘텐츠의 의미론적 의미를 인코딩하는 숫자 벡터를 만드는 함수입니다. 위에서 언급했듯이 임베딩은 색인 생성 프로세스의 일부로 활용되지만 색인 없이 임베딩을 만드는 데 독립적으로 사용할 수도 있습니다.

검색자

검색기는 모든 종류의 문서와 관련된 로직을 캡슐화하는 개념 가져올 수 있습니다. 가장 많이 사용되는 검색 사례로는 벡터 저장소에서 리트리버는 데이터를 반환하는 모든 함수가 될 수 있습니다.

검색기를 만들려면 제공된 구현 중 하나를 사용하거나 직접 만들어 보세요.

지원되는 색인 생성기, 검색기, 삽입기

Genkit는 플러그인 시스템을 통해 색인 생성기와 검색기 지원을 제공합니다. 이 공식적으로 지원되는 플러그인은 다음과 같습니다.

  • Pinecone 클라우드 벡터 데이터베이스

또한 Genkit는 사전 정의된 코드 템플릿을 통해 데이터베이스 구성 및 스키마:

임베딩 모델 지원은 다음 플러그인을 통해 제공됩니다.

플러그인 모델
Google 생성형 AI 도마뱀 텍스트 임베딩
Google Vertex AI 도마뱀 텍스트 임베딩

RAG 흐름 정의

다음 예는 식당 메뉴 PDF 문서 컬렉션을 수집하는 방법을 보여줍니다. 벡터 데이터베이스에 저장하고 어떤 음식 항목을 사용할 수 있는지 결정하는 흐름에서 사용할 수 있도록 검색합니다.

종속 항목 설치

이 예에서는 langchaingotextsplitter 라이브러리를 사용합니다. ledongthuc/pdf PDF 파싱 라이브러리:

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

색인 생성기 정의

다음 예시에서는 색인 생성기를 만들어 PDF 문서 컬렉션을 수집하는 방법을 보여줍니다. 로컬 벡터 데이터베이스에 저장할 수 있습니다.

로컬 파일 기반 벡터 유사성 검색기 사용 간단한 테스트와 프로토타입 제작을 위해 즉시 사용 가능한 단일 서비스를 제공하는 프로덕션에서 사용)

색인 생성기 만들기

// 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자(영문 기준)로 하고, 20자(영문 기준)의 청크 간에 중복됩니다.

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 Drive 등). 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
    },
)