Firebase Genkit 提供抽象概念,可協助您建構檢索增強生成 (RAG) 流程,以及可與相關工具整合的外掛程式。
什麼是 RAG?
檢索增強生成是一種技術,可將外部資訊來源整合至 LLM 回覆。這點很重要,因為雖然 LLM 通常會針對廣泛的內容進行訓練,但實際使用 LLM 時,通常需要具備特定的領域知識 (例如,您可能想使用 LLM 來回答客戶對貴公司產品的疑問)。
解決方法之一是使用更具體的資料微調模型。不過,無論是運算成本,還是準備足夠訓練資料所需的努力,都可能會讓您付出高昂的代價。
相較之下,RAG 會在將外部資料來源傳遞至模型時,將其納入提示中。舉例來說,假設提示訊息是「Bart 和 Lisa 的關係為何?」,您可以透過在前面加上一些相關資訊來擴充 (「擴增」) 提示訊息,變成「Homer 和 Marge 的孩子分別是 Bart、Lisa 和 Maggie。What is Bart's relationship to Lisa?
這種方法有幾個優點:
- 您不必重新訓練模型,因此成本效益可能更高。
- 您可以持續更新資料來源,而 LLM 可以立即使用更新後的資訊。
- 你現在可以在 LLM 的回覆中引用參考資料。
另一方面,使用 RAG 自然會導致提示內容變長,且部分 LLM API 服務會針對您傳送的每個輸入符號收費。最終,您必須評估應用程式的成本取捨。
RAG 涵蓋的範圍非常廣泛,因此有許多不同的技術可用於取得最佳品質的 RAG。Genkit 核心架構提供三個主要抽象概念,協助您執行 RAG:
- 索引器:將文件新增至「索引」。
- 嵌入者:將文件轉換為向量表示法
- 檢索器:在指定查詢時,從「索引」中擷取文件。
這些定義的範圍很廣,因為 Genkit 不認為「索引」是什麼,也不知道如何從中擷取文件。Genkit 只提供 Document
格式,其他所有內容都由擷取器或索引器實作供應器定義。
索引器
索引負責追蹤您的文件,讓您在執行特定查詢時,能快速擷取相關文件。這通常是透過向量資料庫完成,這種資料庫會使用稱為嵌入項目的多維向量為文件建立索引。文字嵌入 (不透明) 代表文字段落所表達的概念,這些概念是使用專用機器學習模型產生。透過使用嵌入內容為文字建立索引,向量資料庫就能將概念上相關的文字分組,並擷取與新文字串 (查詢) 相關的文件。
您必須先將文件匯入文件索引,才能擷取文件以供產生。一般攝入流程會執行以下操作:
將大型文件分割成較小的文件,以便只使用相關部分來擴充提示 (稱為「分割」)。這是必要的做法,因為許多 LLM 的內容窗格有限,因此無法在提示中加入整份文件。
Genkit 不提供內建的分割程式庫,但有可與 Genkit 相容的開放原始碼程式庫。
為每個區塊產生嵌入。視您使用的資料庫而定,您可以使用嵌入生成模型明確執行這項操作,也可以使用資料庫提供的嵌入產生器。
將文字區塊及其索引新增至資料庫。
如果您使用的是穩定的資料來源,可能不常執行擷取流程,甚至只執行一次。另一方面,如果您要處理經常變更的資料,可以持續執行攝入流程 (例如在 Cloud Firestore 觸發事件中,每當文件更新時)。
嵌入者
嵌入器是一種函式,可接收內容 (文字、圖片、音訊等),並建立數值向量,用於編碼原始內容的語意意義。如上所述,嵌入器是索引程序的一部分,但也可以單獨使用,在沒有索引的情況下建立嵌入內容。
獵犬
擷取器是一種概念,用於封裝與任何類型文件擷取相關的邏輯。最常見的擷取案例通常包括從向量儲存空間擷取資料,不過在 Genkit 中,擷取器可以是任何可傳回資料的函式。
如要建立擷取器,您可以使用提供的其中一個實作項目,也可以自行建立。
支援的索引器、擷取器和嵌入器
Genkit 透過外掛程式系統提供索引器和擷取器支援。系統正式支援下列外掛程式:
- Cloud Firestore 向量儲存空間
- Vertex AI Vector Search
- Chroma DB 向量資料庫
- Pinecone 雲端向量資料庫
此外,Genkit 透過預先定義的程式碼範本支援下列向量儲存庫,您可以根據資料庫設定和結構定義自訂這些儲存庫:
- 搭配
pgvector
的 PostgreSQL
嵌入模型支援功能透過下列外掛程式提供:
外掛程式 | 模型 |
---|---|
Google 生成式 AI | 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
程式庫,該程式庫提供簡單的文字分割器,可將文件分割為可向量化的區段。
下列定義會設定分割函式,確保文件片段長度介於 1000 到 2000 個半形字元,並在句子結尾處分割,且每個片段之間重疊 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 技術 (例如重新排名或提示擴充功能)。
簡易擷取器
您可以使用簡易擷取器,輕鬆將現有程式碼轉換為擷取器:
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 },
});
重新排序器和兩階段擷取
重新排序模型 (也稱為交叉編碼器) 是一種模型,在給定查詢和文件時,會輸出相似度分數。我們會根據這項分數,依據與查詢的相關性重新排序文件。Reranker API 會取得文件清單 (例如擷取器的輸出內容),並根據文件與查詢的關聯性重新排序。這個步驟可用於微調結果,並確保在提供給生成模型的提示中,使用最相關的資訊。
重新排序器範例
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,
}));
}
);
這個重新排序器會使用 Vertex AI genkit 外掛程式搭配 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);
}
);
定義完成後,這個自訂重新排序工具的用法就和 RAG 流程中的其他重新排序工具一樣,讓您能靈活導入進階重新排序策略。