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

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

RAG คืออะไร

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

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

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

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

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

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

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

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

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

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

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

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

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

    Genkit ไม่มีไลบรารีที่แบ่งเป็นกลุ่มๆ ในตัว แต่ก็ยังมีแอตทริบิวต์ ไลบรารีแหล่งที่มาที่พร้อมใช้งานซึ่งเข้ากันได้กับ Genkit

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

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

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

เครื่องมือฝัง

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

รีทรีฟเวอร์

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

หากต้องการสร้างรีทรีฟเวอร์ คุณสามารถใช้การติดตั้งใช้งานที่ระบุไว้หรือ ก็สร้างได้

ตัวจัดทำดัชนี รีทรีฟเวอร์ และการฝังที่รองรับ

Genkit ให้การสนับสนุนเกี่ยวกับตัวจัดทำดัชนีและรีทรีฟเวอร์ผ่านระบบปลั๊กอิน ปลั๊กอินต่อไปนี้ได้รับการสนับสนุนอย่างเป็นทางการแล้ว:

นอกจากนี้ Genkit ยังรองรับเวกเตอร์ Store ต่างๆ ผ่านที่กำหนดไว้ล่วงหน้า ที่คุณสามารถปรับแต่งสำหรับการกำหนดค่าฐานข้อมูลและ สคีมา:

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

ปลั๊กอิน โมเดล
Generative AI ของ Google การฝังข้อความตุ๊กแก
AI ของ Google Vertex การฝังข้อความตุ๊กแก

การกำหนดโฟลว์ RAG

ตัวอย่างต่อไปนี้แสดงวิธีส่งผ่านคอลเล็กชันเอกสารเมนูอาหารในรูปแบบ PDF ลงในฐานข้อมูลเวกเตอร์และเรียกข้อมูลเพื่อใช้ในโฟลว์กำหนดรายการอาหารที่ใช้ได้

ติดตั้งการอ้างอิงสำหรับการประมวลผล PDF

npm install llm-chunk pdf-parse
npm i -D --save @types/pdf-parse

เพิ่มที่เก็บเวกเตอร์ในเครื่องลงในการกำหนดค่า

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

configureGenkit({
  plugins: [
    // vertexAI provides the textEmbeddingGecko embedder
    vertexAI(),

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

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

ตัวอย่างต่อไปนี้แสดงวิธีสร้างเครื่องมือจัดทำดัชนีเพื่อนำเข้าคอลเล็กชันเอกสาร PDF และจัดเก็บไว้ในฐานข้อมูลเวกเตอร์ในเครื่อง

จะใช้ตัวดึงข้อมูลความคล้ายคลึงกันของเวกเตอร์ที่อิงตามไฟล์ในเครื่อง Genkit มีให้ใช้งานในการทดสอบและสร้างต้นแบบ (อย่า ใช้ในเวอร์ชันที่ใช้งานจริง)

สร้างตัวจัดทำดัชนี

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

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 { index } from '@genkit-ai/ai';
import { Document } from '@genkit-ai/ai/retriever';
import { defineFlow, run } from '@genkit-ai/flow';
import { readFile } from 'fs/promises';
import { chunk } from 'llm-chunk';
import path from 'path';
import pdf from 'pdf-parse';
import * as z from 'zod';

export const indexMenu = 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 index({
      indexer: menuPdfIndexer,
      documents,
    });
  }
);

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

เรียกใช้ขั้นตอนของเครื่องมือจัดทำดัชนี

genkit flow:run indexMenu "'../pdfs'"

หลังจากเรียกใช้โฟลว์ indexMenu ระบบจะฝังฐานข้อมูลเวกเตอร์ไว้กับเอกสารและพร้อมใช้งานในขั้นตอน Genkit ที่มีขั้นตอนการดึงข้อมูล

กำหนดโฟลว์ที่มีการดึงข้อมูล

ตัวอย่างต่อไปนี้แสดงวิธีที่คุณอาจใช้รีทรีฟเวอร์ในขั้นตอน RAG ชอบ ตัวอย่างตัวจัดทำดัชนี ตัวอย่างนี้ใช้เวกเตอร์รีทรีฟเวอร์ตามไฟล์ของ Genkit ที่คุณไม่ควรใช้ในเวอร์ชันที่ใช้งานจริง

import { generate } from '@genkit-ai/ai';
import { retrieve } from '@genkit-ai/ai/retriever';
import { devLocalRetrieverRef } from '@genkit-ai/dev-local-vectorstore';
import { defineFlow } from '@genkit-ai/flow';
import { geminiPro } from '@genkit-ai/vertexai';
import * as z from 'zod';

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

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

    // generate a response
    const llmResponse = await generate({
      model: geminiPro,
      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}
    `,
      context: docs,
    });

    const output = llmResponse.text();
    return output;
  }
);

เขียนตัวจัดทำดัชนีและรีทรีฟเวอร์ของคุณเอง

คุณยังสร้างรีทรีฟเวอร์ของคุณเองได้ด้วย วิธีนี้มีประโยชน์หาก เอกสารได้รับการจัดการในที่เก็บเอกสารที่ไม่รองรับใน Genkit (เช่น MySQL, Google ไดรฟ์ ฯลฯ) Genkit SDK มีวิธีการใช้งานที่ยืดหยุ่นซึ่งช่วยให้ คุณจะระบุรหัสที่กำหนดเองสำหรับเรียกเอกสาร นอกจากนี้ คุณยังสามารถระบุ รีทรีฟเวอร์ที่สร้างต่อยอดจากรีทรีฟเวอร์ที่มีอยู่ใน Genkit และใช้ Advanced เทคนิค RAG (เช่น การจัดอันดับใหม่หรือส่วนขยายข้อความแจ้ง) ที่ด้านบน

รีทรีฟเวอร์อย่างง่าย

รีทรีฟเวอร์แบบง่ายช่วยให้คุณแปลงโค้ดที่มีอยู่เป็นรีทรีฟเวอร์ได้อย่างง่ายดาย ดังนี้

import {
  defineSimpleRetriever,
  retrieve
} from '@genkit-ai/ai/retriever';
import { searchEmails } from './db';
import { z } from 'zod';

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,
  defineRetriever,
  retrieve,
} from '@genkit-ai/ai/retriever';
import * as z from 'zod';

export const menuRetriever = devLocalRetrieverRef('menuQA');

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

const advancedMenuRetriever = defineRetriever(
  {
    name: `custom/advancedMenuRetriever`,
    configSchema: advancedMenuRetrieverOptionsSchema,
  },
  async (input, options) => {
    const extendedPrompt = await extendPrompt(input);
    const docs = await 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 retrieve({
  retriever: advancedRetriever,
  query: input,
  options: { preRerankK: 7, k: 3 },
});