การสร้างแบบเสริมการดึงข้อมูล (RAG)

Firebase Genkit มีข้อมูลทั่วไปที่จะช่วยคุณสร้างขั้นตอนการสร้างที่เพิ่มการดึงข้อมูล (RAG) รวมถึงปลั๊กอินที่ให้การผสานรวมกับเครื่องมือที่เกี่ยวข้อง

RAG คืออะไร

การสร้างที่เพิ่มการดึงข้อมูลเป็นเทคนิคที่ใช้รวมแหล่งข้อมูลภายนอกไว้ในคำตอบของ LLM คุณควรทำเช่นนั้นเนื่องจากโดยทั่วไป LLM ได้รับการฝึกอบรมเกี่ยวกับเนื้อหาที่หลากหลาย แต่การใช้งานจริงของ LLM มักต้องใช้ความรู้เฉพาะด้าน (เช่น คุณอาจต้องการใช้ LLM เพื่อตอบคําถามของลูกค้าเกี่ยวกับผลิตภัณฑ์ของบริษัท)

โซลูชันหนึ่งคือการปรับแต่งโมเดลโดยใช้ข้อมูลที่เฉพาะเจาะจงมากขึ้น อย่างไรก็ตาม วิธีนี้อาจมีค่าใช้จ่ายสูงทั้งในแง่ของต้นทุนการประมวลผลและในแง่ของความพยายามที่ต้องใช้ในการเตรียมข้อมูลการฝึกที่เพียงพอ

ในทางตรงกันข้าม RAG จะทํางานโดยการรวมแหล่งข้อมูลภายนอกไว้ในพรอมต์เมื่อส่งไปยังโมเดล ตัวอย่างเช่น คุณอาจจินตนาการได้ว่าพรอมต์ "Bart มีความสัมพันธ์อย่างไรกับ Lisa" อาจขยาย ("เพิ่ม") โดยใส่ข้อมูลที่เกี่ยวข้องไว้ข้างหน้า ส่งผลให้พรอมต์กลายเป็น "บุตรหลานของ Homer และ Marge มีชื่อว่า Bart, Lisa และ Maggie Bart มีความสัมพันธ์อย่างไรกับ Lisa"

วิธีการนี้มีข้อดีหลายประการดังนี้

  • ซึ่งอาจประหยัดค่าใช้จ่ายได้มากกว่าเนื่องจากคุณไม่จําเป็นต้องฝึกโมเดลใหม่
  • คุณสามารถอัปเดตแหล่งข้อมูลได้อย่างต่อเนื่อง และ LLM จะใช้ข้อมูลที่อัปเดตได้ทันที
  • ตอนนี้คุณอ้างอิงแหล่งข้อมูลในการตอบกลับของ LLM ได้แล้ว

ในทางกลับกัน การใช้ RAG หมายความว่าพรอมต์จะยาวขึ้น และบริการ LLM API บางรายการจะเรียกเก็บเงินสำหรับโทเค็นอินพุตแต่ละรายการที่คุณส่ง ท้ายที่สุด คุณต้องประเมินการแลกเปลี่ยนค่าใช้จ่ายสำหรับแอปพลิเคชัน

RAG เป็นพื้นที่ที่กว้างมากและมีเทคนิคต่างๆ มากมายที่ใช้เพื่อให้ได้ RAG คุณภาพดีที่สุด เฟรมเวิร์ก Genkit หลักมีนามธรรมหลัก 3 รายการเพื่อช่วยคุณทำ RAG ดังนี้

  • ตัวจัดทําดัชนี: เพิ่มเอกสารลงใน "ดัชนี"
  • เครื่องมือฝัง: เปลี่ยนเอกสารให้เป็นตัวแทนเวกเตอร์
  • เครื่องมือดึงข้อมูล: ดึงข้อมูลเอกสารจาก "ดัชนี" ตามการค้นหา

คําจํากัดความเหล่านี้มีความกว้างขวางโดยมีเจตนา เนื่องจาก Genkit ไม่ได้แสดงความคิดเห็นเกี่ยวกับ "ดัชนี" หรือวิธีดึงข้อมูลเอกสารจากดัชนี Genkit มีเฉพาะรูปแบบ Document ส่วนทุกอย่างอื่นๆ จะกำหนดโดยผู้ให้บริการการดึงข้อมูลหรือผู้ให้บริการติดตั้งใช้งานเครื่องมือจัดทำดัชนี

ผู้จัดทำดัชนี

ดัชนีมีหน้าที่ติดตามเอกสารของคุณในลักษณะที่ช่วยให้คุณสามารถเรียกดูเอกสารที่เกี่ยวข้องได้อย่างรวดเร็วเมื่อมีการค้นหาที่เฉพาะเจาะจง ซึ่งมักทำโดยใช้ฐานข้อมูลเวกเตอร์ ซึ่งจะจัดทำดัชนีเอกสารโดยใช้เวกเตอร์หลายมิติที่เรียกว่าการฝัง การฝังข้อความ (แบบทึบ) จะแสดงถึงแนวคิดที่สื่อผ่านข้อความ ซึ่งสร้างขึ้นโดยใช้โมเดล ML เฉพาะทาง การจัดทําดัชนีข้อความโดยใช้การฝังช่วยให้ฐานข้อมูลเวกเตอร์จัดกลุ่มข้อความที่เกี่ยวข้องตามแนวคิดและดึงข้อมูลเอกสารที่เกี่ยวข้องกับสตริงข้อความใหม่ (ข้อความค้นหา) ได้

คุณต้องส่งผ่านเอกสารลงในดัชนีเอกสารก่อนจึงจะเรียกข้อมูลเอกสารเพื่อวัตถุประสงค์ในการสร้างได้ ขั้นตอนการส่งผ่านข้อมูลทั่วไปจะทําสิ่งต่อไปนี้

  1. แบ่งเอกสารขนาดใหญ่ออกเป็นเอกสารขนาดเล็กเพื่อให้ใช้เฉพาะส่วนที่เกี่ยวข้องเพื่อเสริมพรอมต์เท่านั้น ซึ่งเรียกว่า "การแบ่งกลุ่ม" ซึ่งจำเป็นเนื่องจาก LLM จำนวนมากมีกรอบบริบทที่จำกัด ทำให้การใส่เอกสารทั้งฉบับในพรอมต์ไม่สามารถทำได้

    Genkit ไม่มีไลบรารีการแบ่งออกเป็นส่วนๆ ในตัว แต่มีไลบรารีโอเพนซอร์สที่ใช้ร่วมกับ Genkit ได้

  2. สร้างการฝังสําหรับแต่ละกลุ่ม คุณอาจทําเช่นนี้อย่างชัดเจนด้วยรูปแบบการสร้างการฝัง หรืออาจใช้เครื่องมือสร้างการฝังที่ฐานข้อมูลมีให้ ทั้งนี้ขึ้นอยู่กับฐานข้อมูลที่ใช้งาน

  3. เพิ่มข้อมูลโค้ดข้อความและดัชนีลงในฐานข้อมูล

คุณอาจเรียกใช้ขั้นตอนการส่งผ่านข้อมูลเป็นครั้งคราวหรือเพียงครั้งเดียวหากทํางานกับแหล่งข้อมูลที่เสถียร ในทางกลับกัน หากคุณทํางานกับข้อมูลที่เปลี่ยนแปลงบ่อย คุณอาจเรียกใช้ขั้นตอนการนำเข้าอย่างต่อเนื่อง (เช่น ในทริกเกอร์ Cloud Firestore เมื่อใดก็ตามที่มีการอัปเดตเอกสาร)

ผู้ฝัง

เครื่องมือฝังคือฟังก์ชันที่ใช้เนื้อหา (ข้อความ รูปภาพ เสียง ฯลฯ) และสร้างเวกเตอร์ตัวเลขที่เข้ารหัสความหมายเชิงอรรถศาสตร์ของเนื้อหาต้นฉบับ ดังที่ได้กล่าวไว้ข้างต้น ระบบจะใช้โปรแกรมฝังเป็นส่วนหนึ่งของกระบวนการจัดทำดัชนี แต่ก็สามารถนำไปใช้แยกต่างหากเพื่อสร้างการฝังโดยไม่มีดัชนีได้เช่นกัน

รีทรีฟเวอร์

Retriever คือแนวคิดที่รวมตรรกะที่เกี่ยวข้องกับการดึงข้อมูลเอกสารทุกประเภท กรณีการดึงข้อมูลที่นิยมมากที่สุดมักจะเป็นการดึงข้อมูลจากที่เก็บเวกเตอร์ แต่ในกรณีของ Genkit ตัวดึงข้อมูลอาจเป็นฟังก์ชันใดก็ได้ที่แสดงผลข้อมูล

หากต้องการสร้างเครื่องมือดึงข้อมูล คุณสามารถใช้การติดตั้งใช้งานที่มีให้หรือจะสร้างเองก็ได้

ตัวจัดทําดัชนี ตัวดึงข้อมูล และผู้ฝังที่รองรับ

Genkit รองรับเครื่องมือจัดทำดัชนีและเครื่องมือดึงข้อมูลผ่านระบบปลั๊กอิน ระบบรองรับปลั๊กอินต่อไปนี้อย่างเป็นทางการ

นอกจากนี้ Genkit ยังรองรับที่เก็บเวกเตอร์ต่อไปนี้ผ่านเทมเพลตโค้ดที่กําหนดไว้ล่วงหน้า ซึ่งคุณสามารถปรับแต่งสําหรับการกําหนดค่าฐานข้อมูลและสคีมา

การรองรับการฝังโมเดลมีให้ผ่านปลั๊กอินต่อไปนี้

ปลั๊กอิน โมเดล
Generative AI ของ 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 ไดรฟ์ ฯลฯ) Genkit SDK มีวิธีการที่ยืดหยุ่นซึ่งช่วยให้คุณระบุโค้ดที่กำหนดเองสำหรับการดึงข้อมูลเอกสารได้ นอกจากนี้ คุณยังกําหนดเครื่องมือดึงข้อมูลที่กำหนดเองซึ่งสร้างขึ้นจากเครื่องมือดึงข้อมูลที่มีอยู่ใน 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 },
});

การจัดอันดับใหม่และการดึงข้อมูลแบบ 2 ขั้นตอน

โมเดลการจัดอันดับใหม่หรือที่เรียกว่าตัวเข้ารหัสข้ามเป็นโมเดลประเภทหนึ่งที่จะให้คะแนนความคล้ายคลึงกันเมื่อได้รับคำค้นหาและเอกสาร เราใช้คะแนนนี้เพื่อเรียงลำดับเอกสารใหม่ตามความเกี่ยวข้องกับคำค้นหา Reranker API จะนํารายการเอกสาร (เช่น เอาต์พุตของเครื่องมือดึงข้อมูล) และจัดเรียงเอกสารใหม่ตามความเกี่ยวข้องกับคําค้นหา ขั้นตอนนี้มีประโยชน์ในการปรับแต่งผลลัพธ์และตรวจสอบว่าใช้ข้อมูลที่ตรงที่สุดในพรอมต์ที่ส่งไปยังโมเดล Generative

ตัวอย่างตัวจัดอันดับใหม่

เครื่องมือจัดอันดับใหม่ใน 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 เพื่อประเมินและจัดอันดับเอกสาร ยิ่งคะแนนสูง เอกสารก็ยิ่งมีความเกี่ยวข้องกับข้อความค้นหามากเท่านั้น

เครื่องมือจัดอันดับใหม่ที่กำหนดเอง

นอกจากนี้ คุณยังกําหนดตัวจัดอันดับใหม่ที่กำหนดเองให้เหมาะกับ Use Case เฉพาะของคุณได้ด้วย ซึ่งจะมีประโยชน์เมื่อคุณต้องการจัดอันดับเอกสารใหม่โดยใช้ตรรกะที่กำหนดเองหรือโมเดลที่กำหนดเอง ต่อไปนี้เป็นตัวอย่างง่ายๆ ในการกําหนดตัวจัดอันดับใหม่ที่กำหนดเอง

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

เมื่อกําหนดแล้ว คุณจะใช้ตัวจัดอันดับใหม่ที่กำหนดเองนี้ได้เช่นเดียวกับตัวจัดอันดับใหม่อื่นๆ ในขั้นตอน RAG ซึ่งช่วยให้คุณใช้กลยุทธ์การจัดอันดับใหม่ขั้นสูงได้อย่างยืดหยุ่น