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

يوفّر Firebase 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 دعمًا لبرنامج الفهرسة واسترجاع البيانات من خلال نظام المكوّنات الإضافية. المكوّنات الإضافية التالية متوافقة رسميًا:

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

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

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

تحديد مسار RAG

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

تثبيت التبعيات لمعالجة ملفات PDF

npm install llm-chunk pdf-parse @genkit-ai/dev-local-vectorstore
npm i -D --save @types/pdf-parse

إضافة متجر رسومات محلية إلى الإعدادات

import {
  devLocalIndexerRef,
  devLocalVectorstore,
} from '@genkit-ai/dev-local-vectorstore';
import { textEmbedding004, vertexAI } from '@genkit-ai/vertexai';
import { z, genkit } from 'genkit';

const ai = genkit({
  plugins: [
    // vertexAI provides the textEmbedding004 embedder
    vertexAI(),

    // the local vector store requires an embedder to translate from text to vector
    devLocalVectorstore([
      {
        indexName: 'menuQA',
        embedder: textEmbedding004,
      },
    ]),
  ],
});

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

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

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

إنشاء أداة الفهرسة

export const menuPdfIndexer = devLocalIndexerRef('menuQA');

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

يستخدم هذا المثال مكتبة llm-chunk التي توفّر أداة تقسيم نص بسيطة لتقسيم المستندات إلى أقسام يمكن تحويلها إلى أشكال هندسية.

يضبط التعريف التالي وظيفة تقسيم المحتوى لضمان أن يتراوح طول مقطع المحتوى بين 1, 000 و2, 000 حرف، ويتم تقسيمه في نهاية الجملة، مع تداخل بين أجزاء من 100 حرف.

const chunkingConfig = {
  minLength: 1000,
  maxLength: 2000,
  splitter: 'sentence',
  overlap: 100,
  delimiters: '',
} as any;

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

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

import { Document } from 'genkit/retriever';
import { chunk } from 'llm-chunk';
import { readFile } from 'fs/promises';
import path from 'path';
import pdf from 'pdf-parse';

async function extractTextFromPdf(filePath: string) {
  const pdfFile = path.resolve(filePath);
  const dataBuffer = await readFile(pdfFile);
  const data = await pdf(dataBuffer);
  return data.text;
}

export const indexMenu = ai.defineFlow(
  {
    name: 'indexMenu',
    inputSchema: z.string().describe('PDF file path'),
    outputSchema: z.void(),
  },
  async (filePath: string) => {
    filePath = path.resolve(filePath);

    // Read the pdf.
    const pdfTxt = await run('extract-text', () =>
      extractTextFromPdf(filePath)
    );

    // Divide the pdf text into segments.
    const chunks = await run('chunk-it', async () =>
      chunk(pdfTxt, chunkingConfig)
    );

    // Convert chunks of text into documents to store in the index.
    const documents = chunks.map((text) => {
      return Document.fromText(text, { filePath });
    });

    // Add documents to the index.
    await ai.index({
      indexer: menuPdfIndexer,
      documents,
    });
  }
);

تشغيل عملية الفهرسة

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

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

تحديد مسار مع استرداد

يوضّح المثال التالي كيفية استخدام أداة استرجاع في عملية RAG. مثل مثال الفهرس، يستخدم هذا المثال أداة استرداد المتجهات المستندة إلى الملفات من Genkit، والتي يجب عدم استخدامها في مرحلة الإنتاج.

import { devLocalRetrieverRef } from '@genkit-ai/dev-local-vectorstore';

// Define the retriever reference
export const menuRetriever = devLocalRetrieverRef('menuQA');

export const menuQAFlow = ai.defineFlow(
  { name: 'menuQA', inputSchema: z.string(), outputSchema: z.string() },
  async (input: string) => {
    // retrieve relevant documents
    const docs = await ai.retrieve({
      retriever: menuRetriever,
      query: input,
      options: { k: 3 },
    });

    // generate a response
   const { text } = await ai.generate({
      prompt: `
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.

Question: ${input}`,
      docs,
    });

    return text;
  }
);

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

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

Simple Retrievers

تتيح لك أدوات الاسترجاع البسيطة تحويل الرموز البرمجية الحالية إلى أدوات استرجاع بسهولة:

import { z } from "genkit";
import { searchEmails } from "./db";

ai.defineSimpleRetriever(
  {
    name: "myDatabase",
    configSchema: z
      .object({
        limit: z.number().optional(),
      })
      .optional(),
    // we'll extract "message" from the returned email item
    content: "message",
    // and several keys to use as metadata
    metadata: ["from", "to", "subject"],
  },
  async (query, config) => {
    const result = await searchEmails(query.text, { limit: config.limit });
    return result.data.emails;
  }
);

أدوات الاسترجاع المخصّصة

import {
  CommonRetrieverOptionsSchema,
} from 'genkit/retriever';
import { z } from 'genkit';

export const menuRetriever = devLocalRetrieverRef('menuQA');

const advancedMenuRetrieverOptionsSchema = CommonRetrieverOptionsSchema.extend({
  preRerankK: z.number().max(1000),
});

const advancedMenuRetriever = ai.defineRetriever(
  {
    name: `custom/advancedMenuRetriever`,
    configSchema: advancedMenuRetrieverOptionsSchema,
  },
  async (input, options) => {
    const extendedPrompt = await extendPrompt(input);
    const docs = await ai.retrieve({
      retriever: menuRetriever,
      query: extendedPrompt,
      options: { k: options.preRerankK || 10 },
    });
    const rerankedDocs = await rerank(docs);
    return rerankedDocs.slice(0, options.k || 3);
  }
);

(extendPrompt وrerank هما شيئان عليك تنفيذهما بنفسك، ولا يقدّمهما إطار العمل)

بعد ذلك، يمكنك استبدال أداة الاسترداد:

const docs = await ai.retrieve({
  retriever: advancedRetriever,
  query: input,
  options: { preRerankK: 7, k: 3 },
});

إعادة ترتيب النتائج واسترجاع البيانات على مرحلتَين

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

مثال على إعادة ترتيب النتائج

يتم تعريف إعادة ترتيب المحتوى في Genkit ببنية مشابهة لتلك المستخدَمة في أدوات الاسترجاع والفهرسة. في ما يلي مثال على استخدام أداة إعادة الترتيب في Genkit. تعيد هذه العملية ترتيب مجموعة من المستندات استنادًا إلى مدى صلة كل مستند بطلب البحث المقدَّم باستخدام أداة إعادة ترتيب مُحدَّدة مسبقًا في Vertex AI.

const FAKE_DOCUMENT_CONTENT = [
  'pythagorean theorem',
  'e=mc^2',
  'pi',
  'dinosaurs',
  'quantum mechanics',
  'pizza',
  'harry potter',
];

export const rerankFlow = ai.defineFlow(
  {
    name: 'rerankFlow',
    inputSchema: z.object({ query: z.string() }),
    outputSchema: z.array(
      z.object({
        text: z.string(),
        score: z.number(),
      })
    ),
  },
  async ({ query }) => {
    const documents = FAKE_DOCUMENT_CONTENT.map((text) =>
       ({ content: text })
    );

    const rerankedDocuments = await ai.rerank({
      reranker: 'vertexai/semantic-ranker-512',
      query:  ({ content: query }),
      documents,
    });

    return rerankedDocuments.map((doc) => ({
      text: doc.content,
      score: doc.metadata.score,
    }));
  }
);

يستخدم هذا الإجراء المُعاد للترتيب المكوّن الإضافي genkit في Vertex AI مع semantic-ranker-512 لتقييم المستندات وترتيبها. وكلما ارتفعت النتيجة، كان المستند أكثر صلةً بطلب البحث.

أدوات إعادة الترتيب المخصّصة

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

export const customReranker = ai.defineReranker(
  {
    name: 'custom/reranker',
    configSchema: z.object({
      k: z.number().optional(),
    }),
  },
  async (query, documents, options) => {
    // Your custom reranking logic here
    const rerankedDocs = documents.map((doc) => {
      const score = Math.random(); // Assign random scores for demonstration
      return {
        ...doc,
        metadata: { ...doc.metadata, score },
      };
    });

    return rerankedDocs.sort((a, b) => b.metadata.score - a.metadata.score).slice(0, options.k || 3);
  }
);

بعد تحديد هذا المُعاد ترتيبه المخصّص، يمكن استخدامه تمامًا مثل أيّ مُعاد ترتيبه آخر في عمليات إعادة الترتيب حسب الفئات، ما يمنحك المرونة في تنفيذ استراتيجية إعادة ترتيب متقدّمة.