الجيل المعزَّز بالاسترجاع (RAG)

توفّر حزمة Firebase Genkit تجريدًا لمساعدتك في بناء جيل استرجاعي (RAG)، بالإضافة إلى المكونات الإضافية التي توفر عمليات دمج مع الأدوات ذات الصلة.

ما هي طريقة RAG؟

استرجاع البيانات هو أسلوب يُستخدَم لدمج جهات خارجية مصادر المعلومات في ردود النموذج اللغوي الكبير. من المهم أن تكون قادرًا على القيام لأنّه على الرغم من أنّ النماذج اللغوية الكبيرة يتم تدريبها عادةً على مجموعة كبيرة من غالبًا ما يتطلب الاستخدام العملي للنماذج اللغوية الكبيرة معرفة بالمجال المحدد ( قد ترغب في استخدام النموذج اللغوي الكبير للرد على تعليقات العملاء الأسئلة المتعلقة منتجات الشركة).

يتمثل أحد الحلول في تحسين النموذج باستخدام بيانات أكثر تحديدًا. ومع ذلك، مكلفة من حيث تكلفة الحوسبة والجهد المطلوب لإعداد بيانات تدريب كافية.

في المقابل، تعمل RAG من خلال دمج مصادر البيانات الخارجية في مطالبة في وقت تمريره إلى النموذج. على سبيل المثال، يمكنك أن تتخيل المطالبة، "ما هي علاقة بارت وليزا؟" قد يتم توسيع ("زيادة") بنسبة بعض المعلومات ذات الصلة، مما ينتج عنه مطالبة "هومر يُسمّى أطفال مرجى بارت وليزا وماجي. ما هي علاقة بارت؟ إلى ليزا؟"

وهناك مزايا عديدة لهذه الطريقة:

  • وقد تكون أكثر فعالية من حيث التكلفة لأنك لن تحتاج إلى إعادة تدريب النموذج.
  • يمكنك تعديل مصدر بياناتك باستمرار ويمكن أن يُجري النموذج اللغوي الكبير على الفور استخدام المعلومات المحدثة.
  • يمكنك الآن الإشارة إلى مراجع في ردود النموذج اللغوي الكبير.

من ناحية أخرى، يعني استخدام طريقة RAG بشكل طبيعي الطلبات الأطول، واستخدام بعض واجهات برمجة التطبيقات LLM التي يتم تحصيل رسومها عن كل رمز إدخال ترسله ترسله. في النهاية، يجب تقييم ومفاضلات التكلفة لتطبيقاتك.

تُعد RAG مجالاً واسعًا للغاية وهناك العديد من التقنيات المختلفة المستخدمة لتحقيق أفضل RAG. يقدم إطار عمل Genkit الأساسي تجريدين رئيسيين تساعدك على تنفيذ RAG:

  • المفهرسون: إضافة المستندات إلى "الفهرس"
  • أدوات التضمين: تحويل المستندات إلى تمثيل متّجه
  • أدوات استرداد: استرداد المستندات من "فهرس"، حسب طلب بحث.

هذه التعريفات واسعة عن قصد لأن Genkit لا ترى رأيًا فيها ما هو "المؤشر" أو كيفية استرداد الوثائق منه بالضبط. Genkit فقط تنسيق Document ويتم تحديد أي شيء آخر بواسطة برنامج الاسترجاع أو أداة تنفيذ أداة الفهرسة.

المفهرسون

ويكون الفهرس مسؤولاً عن تتبع مستنداتك بطريقة يمكنك استرداد المستندات ذات الصلة بطلب بحث معين بسرعة. هذا هو الأكثر باستخدام قاعدة بيانات متجه، تفهرس مستنداتك باستخدام متجهات متعددة الأبعاد تسمى التضمينات. تضمين نص (بشكل غير شفاف) ويمثل المفاهيم التي يتم التعبير عنها من خلال فقرة نصية؛ يتم إنشاء هذه الإعلانات باستخدام نماذج تعلُّم الآلة ذات الأغراض الخاصّة عن طريق فهرسة النص باستخدام تضمينه، فإن المتجه قادرة على تجميع النصوص ذات الصلة من الناحية النظرية واسترداد المستندات يتعلق بسلسلة نصية جديدة (الاستعلام).

قبل أن تتمكن من استرداد المستندات لغرض إنشائها، يجب إجراء ما يلي: ونقلها إلى فهرس المستندات. يُجرى تدفق النقل النموذجي التالي:

  1. قسِّم المستندات الكبيرة إلى مستندات أصغر بحيث لا يتم استخدام أجزاء منها لزيادة مطالباتك - "قطع". هذا الإجراء ضروري ولأنّ العديد من النماذج اللغوية الكبيرة محدودة السياق، من الصعب تضمين مستندات كاملة بمطالبة.

    لا توفّر Genkit مكتبات تقسيم مدمجة؛ ومع ذلك، تتوفر مكتبات المصادر المتاحة المتوافقة مع Genkit.

  2. إنشاء تضمينات لكل مقطع. اعتمادًا على قاعدة البيانات التي تستخدمها، يمكنك تنفيذ ذلك بشكل صريح باستخدام نموذج إنشاء التضمين، أو منشئ التضمين الذي توفره قاعدة البيانات.

  3. أضف مقطع النص والفهرس الخاص به إلى قاعدة البيانات.

قد يتم تنفيذ عملية النقل بشكل غير متكرر أو مرة واحدة فقط إذا كنت تعمل من خلال مصدر مستقر للبيانات. من ناحية أخرى، إذا كنت تعمل على البيانات يتغيّر بشكل متكرّر، يمكنك تشغيل عملية النقل باستمرار ( على سبيل المثال، في مشغل Cloud Firestore عندما يتم تحديث مستند).

التضمينات

أداة التضمين هي دالة تأخذ المحتوى (نص، صور، صوت، إلخ.) وتنشئ متجهًا رقميًا يشفّر المعنى الدلالي للمحتوى الأصلي. كما ذكرنا أعلاه، يتم الاستفادة من أدوات التضمين كجزء من عملية الفهرسة، ولكن يمكن استخدامها أيضًا بشكل مستقل لإنشاء تضمينات بدون فهرس.

ريتريفر

المسترد هو مفهوم يلخص المنطق المتعلق بأي نوع من المستندات استرداد البيانات. تشمل حالات الاسترجاع الأكثر شيوعًا عادةً الاسترجاع من في حين أن متاجر المتجه، في Genkit، يمكن أن يكون المسترد أي دالة تُرجع البيانات.

لإنشاء برنامج استرداد، يمكنك استخدام إحدى طرق التنفيذ المقدمة أو إنشاء مجموعتك الخاصة.

أدوات الفهرسة وبرامج استرداد البيانات وبرامج التضمين المتوافقة

توفِّر Genkit إمكانية استخدام أداة الفهرسة والاسترداد من خلال نظام المكوّنات الإضافية. تشير رسالة الأشكال البيانية تكون المكوّنات الإضافية التالية متوافقة رسميًا:

  • قاعدة بيانات متّجه السحابة الإلكترونية Pinecone

بالإضافة إلى ذلك، تدعم Genkit متاجر المتجهات التالية من خلال نماذج التعليمات البرمجية، والتي يمكنك تخصيصها لتهيئة قاعدة البيانات المخطط:

يتمّ توفير نموذج التضمين من خلال المكوّنات الإضافية التالية:

المكوّن الإضافي الطرُز
الذكاء الاصطناعي التوليدي من Google تضمين نص الوزغة
Google Vertex AI تضمين نص الوزغة

تحديد تدفق RAG

توضّح الأمثلة التالية كيفية نقل مجموعة من مستندات PDF الخاصة بقائمة المطاعم. في قاعدة بيانات متجه واستردادها للاستخدام في تدفق يحدد المواد الغذائية المتوفرة.

تثبيت الملحقات

في هذا المثال، سنستخدم مكتبة textsplitter من langchaingo مكتبة تحليل ملفات PDF في ledongthuc/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 طرقًا مرنة تتيح فإنك تقدّم رمزًا مخصّصًا لجلب المستندات.

يمكنكم أيضًا تحديد أدوات استرداد مخصصة تستند إلى برامج استرداد الأموال الحالية في Genkit وتطبيق أساليب 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
    },
)