Thế hệ tăng cường truy xuất (RAG)

Firebase Genkit cung cấp các tính năng trừu tượng giúp bạn tạo luồng tạo (RAG) tăng cường truy xuất, cũng như các trình bổ trợ cung cấp khả năng tích hợp với các công cụ liên quan.

RAG là gì?

Tạo nội dung tăng cường truy xuất là một kỹ thuật dùng để kết hợp các nguồn thông tin bên ngoài vào câu trả lời của LLM. Điều quan trọng là bạn phải có thể làm được điều này vì mặc dù LLM thường được huấn luyện về một lượng lớn tài liệu, nhưng việc sử dụng LLM trong thực tế thường đòi hỏi kiến thức chuyên môn cụ thể (ví dụ: bạn có thể muốn sử dụng LLM để trả lời câu hỏi của khách hàng về sản phẩm của công ty).

Một giải pháp là tinh chỉnh mô hình bằng cách sử dụng dữ liệu cụ thể hơn. Tuy nhiên, việc này có thể tốn kém cả về chi phí tính toán và về nỗ lực cần thiết để chuẩn bị đủ dữ liệu huấn luyện.

Ngược lại, RAG hoạt động bằng cách kết hợp các nguồn dữ liệu bên ngoài vào một lời nhắc tại thời điểm lời nhắc đó được truyền đến mô hình. Ví dụ: bạn có thể tưởng tượng câu lệnh "Bart có mối quan hệ gì với Lisa?" có thể được mở rộng ("mở rộng") bằng cách thêm vào một số thông tin liên quan, dẫn đến câu lệnh "Con cái của Homer và Marge tên là Bart, Lisa và Maggie. Bart có mối quan hệ gì với Lisa?"

Phương pháp này có một số ưu điểm:

  • Cách này có thể tiết kiệm chi phí hơn vì bạn không phải huấn luyện lại mô hình.
  • Bạn có thể liên tục cập nhật nguồn dữ liệu và LLM có thể sử dụng ngay thông tin đã cập nhật.
  • Giờ đây, bạn có thể trích dẫn tài liệu tham khảo trong câu trả lời của LLM.

Mặt khác, việc sử dụng RAG sẽ dẫn đến các lời nhắc dài hơn và một số dịch vụ API LLM sẽ tính phí cho mỗi mã thông báo đầu vào mà bạn gửi. Cuối cùng, bạn phải đánh giá các chi phí đánh đổi cho ứng dụng của mình.

RAG là một lĩnh vực rất rộng và có nhiều kỹ thuật khác nhau được dùng để đạt được RAG chất lượng cao nhất. Khung Genkit cốt lõi cung cấp ba khái niệm trừu tượng chính để giúp bạn thực hiện RAG:

  • Trình lập chỉ mục: thêm tài liệu vào "chỉ mục".
  • Trình nhúng: chuyển đổi tài liệu thành một vectơ đại diện
  • Trình truy xuất: truy xuất tài liệu từ một "chỉ mục", dựa trên một truy vấn.

Các định nghĩa này được mở rộng một cách có chủ ý vì Genkit không có ý kiến về "chỉ mục" là gì hoặc cách truy xuất chính xác tài liệu từ chỉ mục đó. Genkit chỉ cung cấp định dạng Document và mọi thứ khác do trình truy xuất hoặc nhà cung cấp triển khai chỉ mục xác định.

Trình lập chỉ mục

Chỉ mục có trách nhiệm theo dõi các tài liệu của bạn theo cách mà bạn có thể nhanh chóng truy xuất các tài liệu có liên quan khi có một truy vấn cụ thể. Việc này thường được thực hiện bằng cách sử dụng cơ sở dữ liệu vectơ. Cơ sở dữ liệu này lập chỉ mục tài liệu của bạn bằng các vectơ đa chiều được gọi là nội dung nhúng. Một văn bản nhúng (mờ) đại diện cho các khái niệm được thể hiện bằng một đoạn văn bản; các khái niệm này được tạo bằng các mô hình học máy có mục đích đặc biệt. Bằng cách lập chỉ mục văn bản bằng cách nhúng, cơ sở dữ liệu vectơ có thể nhóm các văn bản có liên quan về mặt khái niệm và truy xuất các tài liệu liên quan đến một chuỗi văn bản mới (cụm từ tìm kiếm).

Trước khi có thể truy xuất tài liệu cho mục đích tạo, bạn cần nhập tài liệu vào chỉ mục tài liệu. Quy trình truyền dẫn thông thường thực hiện những việc sau:

  1. Chia các tài liệu lớn thành các tài liệu nhỏ hơn để chỉ sử dụng các phần liên quan để tăng cường câu lệnh của bạn – "chia thành phần". Điều này là cần thiết vì nhiều LLM có cửa sổ ngữ cảnh bị hạn chế, khiến việc đưa toàn bộ tài liệu vào một câu lệnh trở nên không thực tế.

    Genkit không cung cấp thư viện phân đoạn tích hợp sẵn; tuy nhiên, có các thư viện nguồn mở tương thích với Genkit.

  2. Tạo các phần nhúng cho mỗi đoạn. Tuỳ thuộc vào cơ sở dữ liệu bạn đang sử dụng, bạn có thể thực hiện việc này một cách rõ ràng bằng mô hình tạo nội dung nhúng hoặc sử dụng trình tạo nội dung nhúng do cơ sở dữ liệu cung cấp.

  3. Thêm đoạn văn bản và chỉ mục của đoạn văn bản đó vào cơ sở dữ liệu.

Bạn có thể chạy quy trình truyền dẫn không thường xuyên hoặc chỉ một lần nếu đang làm việc với một nguồn dữ liệu ổn định. Mặt khác, nếu đang làm việc với dữ liệu thường xuyên thay đổi, bạn có thể liên tục chạy quy trình truyền dẫn (ví dụ: trong điều kiện kích hoạt Cloud Firestore, bất cứ khi nào một tài liệu được cập nhật).

Trình nhúng

Trình nhúng là một hàm lấy nội dung (văn bản, hình ảnh, âm thanh, v.v.) và tạo một vectơ số để mã hoá ý nghĩa ngữ nghĩa của nội dung ban đầu. Như đã đề cập ở trên, trình nhúng được tận dụng trong quá trình lập chỉ mục, tuy nhiên, bạn cũng có thể sử dụng trình nhúng độc lập để tạo nội dung nhúng mà không cần chỉ mục.

Chó săn mồi

Trình truy xuất là một khái niệm bao gồm logic liên quan đến mọi loại truy xuất tài liệu. Các trường hợp truy xuất phổ biến nhất thường bao gồm truy xuất từ các cửa hàng vectơ, tuy nhiên, trong Genkit, trình truy xuất có thể là bất kỳ hàm nào trả về dữ liệu.

Để tạo trình truy xuất, bạn có thể sử dụng một trong các phương thức triển khai được cung cấp hoặc tạo phương thức triển khai của riêng mình.

Các trình lập chỉ mục, trình truy xuất và trình nhúng được hỗ trợ

Genkit cung cấp dịch vụ hỗ trợ trình lập chỉ mục và trình truy xuất thông qua hệ thống trình bổ trợ. Các trình bổ trợ sau đây được hỗ trợ chính thức:

Ngoài ra, Genkit hỗ trợ các kho vectơ sau đây thông qua các mẫu mã được xác định trước mà bạn có thể tuỳ chỉnh cho cấu hình và giản đồ cơ sở dữ liệu của mình:

Hỗ trợ mô hình nhúng được cung cấp thông qua các trình bổ trợ sau:

Trình bổ trợ Mô hình
AI tạo sinh của Google Nhúng văn bản Gecko
Google Vertex AI Nhúng văn bản Gecko

Xác định quy trình RAG

Các ví dụ sau đây cho thấy cách bạn có thể nhập một tập hợp tài liệu PDF về thực đơn của nhà hàng vào cơ sở dữ liệu vectơ và truy xuất các tài liệu đó để sử dụng trong một luồng xác định các món ăn có sẵn.

Cài đặt các phần phụ thuộc để xử lý tệp PDF

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

Thêm kho vectơ cục bộ vào cấu hình

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

Xác định Trình lập chỉ mục

Ví dụ sau đây cho biết cách tạo một trình lập chỉ mục để nhập một tập hợp tài liệu PDF và lưu trữ các tài liệu đó trong cơ sở dữ liệu vectơ cục bộ.

Công cụ này sử dụng trình truy xuất độ tương đồng vectơ dựa trên tệp cục bộ mà Genkit cung cấp sẵn để kiểm thử và tạo nguyên mẫu đơn giản (không sử dụng trong quá trình sản xuất)

Tạo trình lập chỉ mục

export const menuPdfIndexer = devLocalIndexerRef('menuQA');

Tạo cấu hình phân đoạn

Ví dụ này sử dụng thư viện llm-chunk cung cấp một trình phân tách văn bản đơn giản để chia tài liệu thành các phân đoạn có thể được vectơ hoá.

Định nghĩa sau đây định cấu hình hàm phân đoạn để đảm bảo một phân đoạn tài liệu có từ 1.000 đến 2.000 ký tự, bị ngắt ở cuối câu, với sự trùng lặp giữa các phân đoạn 100 ký tự.

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

Bạn có thể xem thêm các tuỳ chọn phân đoạn cho thư viện này trong tài liệu về llm-chunk.

Xác định luồng trình lập chỉ mục

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

Chạy quy trình của trình lập chỉ mục

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

Sau khi chạy luồng indexMenu, cơ sở dữ liệu vectơ sẽ được tạo bằng các tài liệu và sẵn sàng để sử dụng trong các luồng Genkit có các bước truy xuất.

Xác định luồng có chức năng truy xuất

Ví dụ sau đây cho thấy cách bạn có thể sử dụng trình truy xuất trong luồng RAG. Giống như ví dụ về trình lập chỉ mục, ví dụ này sử dụng trình truy xuất vectơ dựa trên tệp của Genkit mà bạn không nên sử dụng trong phiên bản chính thức.

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

Viết trình lập chỉ mục và trình truy xuất của riêng bạn

Bạn cũng có thể tạo trình truy xuất của riêng mình. Điều này rất hữu ích nếu tài liệu của bạn được quản lý trong một kho tài liệu không được hỗ trợ trong Genkit (ví dụ: MySQL, Google Drive, v.v.). SDK Genkit cung cấp các phương thức linh hoạt cho phép bạn cung cấp mã tuỳ chỉnh để tìm nạp tài liệu. Bạn cũng có thể xác định các trình truy xuất tuỳ chỉnh dựa trên các trình truy xuất hiện có trong Genkit và áp dụng các kỹ thuật RAG nâng cao (chẳng hạn như xếp hạng lại hoặc mở rộng lời nhắc) ở trên cùng.

Trình truy xuất đơn giản

Trình truy xuất đơn giản cho phép bạn dễ dàng chuyển đổi mã hiện có thành trình truy xuất:

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

Trình truy xuất tuỳ chỉnh

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

(extendPromptrerank là những thứ bạn phải tự triển khai, không phải do khung cung cấp)

Sau đó, bạn chỉ cần hoán đổi trình truy xuất:

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

Công cụ xếp hạng lại và Truy xuất hai giai đoạn

Mô hình xếp hạng lại (còn gọi là bộ mã hoá chéo) là một loại mô hình, khi được cung cấp một truy vấn và tài liệu, sẽ xuất ra một điểm tương đồng. Chúng tôi sử dụng điểm số này để sắp xếp lại các tài liệu theo mức độ liên quan đến truy vấn của chúng ta. API xếp hạng lại lấy danh sách tài liệu (ví dụ: kết quả của trình truy xuất) và sắp xếp lại các tài liệu dựa trên mức độ liên quan của chúng với truy vấn. Bước này có thể hữu ích để tinh chỉnh kết quả và đảm bảo rằng thông tin phù hợp nhất được sử dụng trong lời nhắc được cung cấp cho mô hình tạo sinh.

Ví dụ về công cụ xếp hạng lại

Trình xếp hạng lại trong Genkit được xác định theo cú pháp tương tự như trình truy xuất và trình lập chỉ mục. Sau đây là ví dụ về cách sử dụng công cụ xếp hạng lại trong Genkit. Quy trình này xếp hạng lại một tập hợp tài liệu dựa trên mức độ liên quan của tài liệu đó với truy vấn đã cung cấp bằng cách sử dụng trình xếp hạng lại Vertex AI được xác định trước.

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

Trình xếp hạng lại này sử dụng trình bổ trợ genkit Vertex AI với semantic-ranker-512 để tính điểm và xếp hạng tài liệu. Điểm số càng cao thì tài liệu càng liên quan đến truy vấn.

Trình xếp hạng lại tuỳ chỉnh

Bạn cũng có thể xác định các thuật toán xếp hạng lại tuỳ chỉnh cho phù hợp với trường hợp sử dụng cụ thể của mình. Điều này rất hữu ích khi bạn cần xếp hạng lại tài liệu bằng cách sử dụng logic tuỳ chỉnh của riêng mình hoặc mô hình tuỳ chỉnh. Dưới đây là ví dụ đơn giản về cách xác định một công cụ xếp hạng lại tuỳ chỉnh:

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

Sau khi xác định, bạn có thể sử dụng trình xếp hạng lại tuỳ chỉnh này giống như bất kỳ trình xếp hạng lại nào khác trong các luồng RAG, giúp bạn linh hoạt triển khai các chiến lược xếp hạng lại nâng cao.