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

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

ما هو RAG؟

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

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

في المقابل، تعمل ميزة RAG من خلال دمج مصادر بيانات خارجية في طلب في الوقت الذي يتم فيه تمريره إلى النموذج. على سبيل المثال، يمكن أن تتضمّن الرسالة "ما هي علاقة "بارت" بـ "ليسا"؟" (بالإنجليزية: "What is Bart's relationship to Lisa?") معلومات إضافية (بالإنجليزية: "augmented") في مقدمتها، ما يؤدي إلى ظهور الرسالة "أسماء أطفال "هومر" و" مارج" هي "بارت" و"ليسا" و"ماجي". ما هي علاقة "بارت" بـ "ليزا"؟"

هناك عدة مزايا لهذا الأسلوب:

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

من ناحية أخرى، يؤدي استخدام نموذج RAG إلى ظهور طلبات أطول، وتفرض بعض خدمات واجهة برمجة التطبيقات LLM API رسومًا مقابل كل رمز مميّز لإدخال المعلومات ترسله. في النهاية، عليك تقييم مفاضلات التكلفة لطلباتك.

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

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

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

الفهرِّسون

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

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

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

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

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

  3. أضِف الجزء النصي وفهرسه إلى قاعدة البيانات.

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

أدوات التضمين

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

كلاب المستردّين

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

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

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

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

  • قاعدة بيانات Pinecone للأشكال الهندسية على السحابة الإلكترونية

بالإضافة إلى ذلك، يتيح Genkit استخدام متاجر المتجهات التالية من خلال نماذج رمز مُحدَّدة مسبقًا، ويمكنك تخصيصها لإعداد قاعدة البيانات و مخطّطها:

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

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

تحديد مسار تقييم حسب الحالة

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

تثبيت التبعيات

في هذا المثال، سنستخدم مكتبة textsplitter من langchaingo ومكتبة ledongthuc/pdf لتحليل ملفات PDF:

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

تحديد أداة فهرسة

يوضّح المثال التالي كيفية إنشاء أداة فهرسة لتحميل مجموعة من مستندات ملف PDF وتخزينها في قاعدة بيانات محلية للملفات المتجهة.

ويستخدم هذا الإجراء أداة استرداد التشابه بين المتجهات المستندة إلى الملفات المحلية التي يوفّرها 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()

g, err := genkit.Init(ctx, genkit.WithPlugins(&googlegenai.VertexAI{}))
if err != nil {
    log.Fatal(err)
}

if err = localvec.Init(); err != nil {
    log.Fatal(err)
}

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

إنشاء إعدادات تقسيم البيانات

يستخدم هذا المثال مكتبة textsplitter التي توفّر أداة تقسيم نص بسيطة لقسمة المستندات إلى أجزاء يمكن تحويلها إلى رسومات.

يضبط التعريف التالي وظيفة تقسيم المحتوى لعرض أقسام مستند تتألف من 200 حرف، مع تداخل بين الأقسام التي تتألف من 20 حرفًا.

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

يمكنك العثور على مزيد من خيارات تقسيم هذه المكتبة في ملف langchaingo.

تحديد مسار الفهرسة

genkit.DefineFlow(
    g, "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 = ai.Index(ctx, menuPDFIndexer, ai.WithDocs(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()

g, err := genkit.Init(ctx, genkit.WithPlugins(&googlegenai.VertexAI{}))
if err != nil {
    log.Fatal(err)
}

if err = localvec.Init(); err != nil {
    log.Fatal(err)
}

model := googlegenai.VertexAIModel(g, "gemini-1.5-flash")

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

genkit.DefineFlow(
  g, "menuQA",
  func(ctx context.Context, question string) (string, error) {
    // Retrieve text relevant to the user's question.
    resp, err := ai.Retrieve(ctx, menuPdfRetriever, ai.WithTextDocs(question))


    if err != nil {
        return "", err
    }

    // Call Generate, including the menu information in your prompt.
    return genkit.GenerateText(ctx, g,
        ai.WithModelName("googleai/gemini-2.0-flash"),
        ai.WithDocs(resp.Documents),
        ai.WithSystem(`
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.`)
        ai.WithPrompt(question),
  })

كتابة برامج الفهرسة والاسترجاع

من الممكن أيضًا إنشاء أداة استرجاع خاصة بك. يكون ذلك مفيدًا إذا كانت المستندات تُدار في متجر مستندات غير متوافق مع Genkit (مثل MySQL وGoogle Drive وما إلى ذلك). توفّر حزمة تطوير البرامج (SDK) Genkit طرقًا مرنة تتيح لك تقديم رمز مخصّص لجلب المستندات.

يمكنك أيضًا تحديد أدوات استرجاع مخصّصة تستند إلى أدوات استرجاع حالية في Genkit وتطبيق تقنيات RAG المتقدّمة (مثل إعادة الترتيب أو توسيع الطلب) عليها.

على سبيل المثال، لنفترض أنّ لديك دالة مخصّصة لإعادة الترتيب تريد استخدامها. يحدِّد المثال التالي أداة استرجاع مخصّصة تطبِّق دالتك على أداة استرجاع القائمة التي تم تحديدها سابقًا:

type CustomMenuRetrieverOptions struct {
    K          int
    PreRerankK int
}

advancedMenuRetriever := genkit.DefineRetriever(
    g, "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.
        resp, err := ai.Retrieve(ctx, menuPDFRetriever,
            ai.WithDocs(req.Query),
            ai.WithConfig(ocalvec.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
    },
)