รุ่น Augmented Reality (RAG)

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

RAG คืออะไร

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

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

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

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

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

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

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

  • ตัวจัดทำดัชนี: เพิ่มเอกสารไปยัง "ดัชนี"
  • เครื่องมือฝัง: เปลี่ยนเอกสารเป็นภาพแทนเวกเตอร์
  • Retrievers: เรียกเอกสารจาก "index" ตามคำค้นหา

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

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

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

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

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

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

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

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

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

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

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

รีทรีฟเวอร์

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

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

โปรแกรมจัดทำดัชนี รีทรีฟเวอร์ และเครื่องมือฝังที่รองรับ

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

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

การรองรับการฝังโมเดลจะทำผ่านปลั๊กอินต่อไปนี้

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

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

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

ติดตั้งทรัพยากร Dependency เพื่อประมวลผล 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 แล้วใช้เทคนิค 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 },
});