Firebase Genkit מספק הפשטות שיעזרו לכם ליצור תהליכי יצירת נתונים שמבוססים על אחזור (RAG), וגם פלאגינים שמספקים שילובים עם כלים קשורים.
מהו RAG?
יצירת תשובות באמצעות אחזור משופר היא טכניקה שמשמשת לשילוב מקורות מידע חיצוניים בתשובות של LLM. חשוב שתוכלו לעשות זאת כי בדרך כלל מודלים של LLM מאומנים על כמות גדולה של חומר, אבל לרוב נדרש ידע ספציפי בתחום כדי להשתמש בהם באופן מעשי (לדוגמה, יכול להיות שתרצו להשתמש ב-LLM כדי לענות על שאלות של לקוחות לגבי המוצרים של החברה).
פתרון אחד הוא לשפר את המודל באמצעות נתונים ספציפיים יותר. עם זאת, הדבר יכול להיות יקר גם מבחינת עלות המחשוב וגם מבחינת המאמץ הנדרש כדי להכין נתוני אימון מתאימים.
לעומת זאת, RAG פועל על ידי שילוב של מקורות נתונים חיצוניים בהנחיה בזמן שהיא מועברת למודל. לדוגמה, אפשר לדמיין שההנחיה "מה הקשר בין בארט לליזה?" עשויה להתרחב ('להשתפר') על ידי הוספת מידע רלוונטי, וכתוצאה מכך ההנחיה תהיה "הילדים של הומר ומרג' הם בארט, ליזה ומגי. What is Bart's relationship to Lisa?
לגישה הזו יש כמה יתרונות:
- השיטה הזו יכולה להיות חסכונית יותר כי לא צריך לאמן מחדש את המודל.
- אתם יכולים לעדכן את מקור הנתונים באופן שוטף, ו-LLM יכול להשתמש במידע המעודכן באופן מיידי.
- עכשיו יש לכם אפשרות לצטט מקורות בתשובות של ה-LLM.
מצד שני, השימוש ב-RAG כרוך בהנחיות ארוכות יותר, וחלק משירותי ה-API של LLM מחייבים על כל אסימון קלט שאתם שולחים. בסופו של דבר, עליכם להעריך את העלויות של האפליקציות שלכם.
ניתוח RAG הוא תחום רחב מאוד, ויש הרבה שיטות שונות להשגת ניתוח RAG באיכות הטובה ביותר. מסגרת הליבה של Genkit מציעה שלוש הפשטות ראשיות שיעזרו לכם לבצע RAG:
- מוסדי אינדקס: הוספת מסמכים לאינדקס.
- הטמעה: המרת מסמכים לייצוג בווקטור
- שירותי אחזור: אחזור מסמכים מ'אינדקס', על סמך שאילתה.
ההגדרות האלה רחבות בכוונה, כי ל-Genkit אין דעה מוצקה לגבי מהו 'אינדקס' או איך בדיוק מתבצעת אחזור המסמכים ממנו. Genkit מספק רק פורמט Document
, וכל השאר מוגדר על ידי ספק ההטמעה של האחזור או של מנוע האינדקס.
מנועי חיפוש
האינדקס אחראי על מעקב אחר המסמכים שלכם באופן שמאפשר לאחזר במהירות מסמכים רלוונטיים לפי שאילתה ספציפית. לרוב, הדרך לעשות זאת היא באמצעות מסד נתונים של וקטורים, שמוסיף את המסמכים לאינדקס באמצעות וקטורים מרובת-מימדים שנקראים הטמעות (embeddings). הטמעת טקסט מייצגת (באופן מעורפל) את המושגים שמתבטאים בקטע טקסט. הטמעות כאלה נוצרות באמצעות מודלים של למידת מכונה למטרות מיוחדות. הוספת טקסט לאינדקס באמצעות הטמעתו מאפשרת למסד נתונים וקטורי לקבץ טקסט שקשור מבחינה מושגית ולשלוף מסמכים שקשורים למחרוזת טקסט חדשה (השאילתה).
כדי לאחזר מסמכים לצורך יצירה, צריך להטמיע אותם במדד המסמכים. תהליך הטמעה אופייני כולל את הפעולות הבאות:
פיצול מסמכים גדולים למסמכים קטנים יותר, כך שרק החלקים הרלוונטיים ישמשו להוספת תוכן להנחיות – 'חלוקה לקטעים'. הצורך הזה נובע מכך שלמודלים רבים של שפה גדולה יש חלון הקשר מוגבל, ולכן לא מעשי לכלול בהנחיה מסמכים שלמים.
Genkit לא מספק ספריות מובנות לחלוקה לקטעים, אבל יש ספריות קוד פתוח שתואמות ל-Genkit.
יצירת הטמעות (embeddings) לכל מקטע. בהתאם למסד הנתונים שבו אתם משתמשים, תוכלו לעשות זאת באופן מפורש באמצעות מודל ליצירת הטמעה, או להשתמש במחולל הטמעה שסופק על ידי מסד הנתונים.
מוסיפים את מקטע הטקסט ואת האינדקס שלו למסד הנתונים.
אם אתם עובדים עם מקור נתונים יציב, יכול להיות שתפעילו את תהליך הטמעת הנתונים לעיתים רחוקות או רק פעם אחת. לעומת זאת, אם אתם עובדים עם נתונים שמשתנים לעיתים קרובות, כדאי להפעיל את תהליך הטמעת הנתונים באופן רציף (לדוגמה, בטריגר של Cloud Firestore, בכל פעם שמסמך מתעדכן).
גורמים שמטמיעים
הטמעה היא פונקציה שמקבלת תוכן (טקסט, תמונות, אודיו וכו') ויוצרת וקטור מספרי שמקודד את המשמעות הסמנטית של התוכן המקורי. כפי שצוין למעלה, נעשה שימוש ב-embedders כחלק מתהליך ההוספה לאינדקס, אבל אפשר להשתמש בהם גם באופן עצמאי כדי ליצור הטמעות (embeddings) בלי אינדקס.
אחזור נתונים
אחזור הוא מושג שמכיל לוגיקה שקשורה לכל סוג של אחזור מסמכים. רוב המקרים הנפוצים של אחזור כוללים אחזור מחנויות וקטורים, אבל ב-Genkit, פונקציית אחזור יכולה להיות כל פונקציה שמחזירה נתונים.
כדי ליצור אובייקט אחזור, אפשר להשתמש באחת מההטמעות שסופקו או ליצור אובייקט משלכם.
שירותי הוספה לאינדקס, אחזור והטמעה נתמכים
Genkit מספק תמיכה במסד הנתונים המנוהל ובאחזור באמצעות מערכת הפלאגינים שלו. הפלאגינים הבאים נתמכים באופן רשמי:
- מאגר וקטורים ב-Cloud Firestore
- Vertex AI Vector Search
- מסד נתונים של וקטורים ב-Chroma DB
- מסד נתונים של וקטורים בענן של Pinecone
בנוסף, Genkit תומך במאגרי הווקטורים הבאים באמצעות תבניות קוד מוגדרות מראש, שאפשר להתאים אישית בהתאם להגדרות ולסכימה של מסד הנתונים:
- PostgreSQL עם
pgvector
התמיכה בהטמעת מודלים ניתנת באמצעות הפלאגינים הבאים:
פלאגין | דגמים |
---|---|
Google Generative 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
, שמספקת כלי פשוט לפיצול טקסט כדי לפצל מסמכים לקטעים שאפשר להפוך לווקטורים.
ההגדרה הבאה מגדירה את פונקציית הפילוח כך שתבטיח קטע מסמך באורך של 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 ai.run('extract-text', () =>
extractTextFromPdf(filePath)
);
// Divide the pdf text into segments.
const chunks = await ai.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 עם שלבי אחזור.
הגדרת תהליך עם אחזור
בדוגמה הבאה מוסבר איך משתמשים ב-retriever בתהליך RAG. בדומה לדוגמה של ה-indexer, בדוגמה הזו נעשה שימוש באחזור וקטורים מבוסס-קובץ של Genkit, שאסור להשתמש בו בסביבת ייצור.
import { devLocalRetrieverRef } from '@genkit-ai/dev-local-vectorstore';
import { gemini } from '@genkit-ai/vertexai';
// 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({
model: gemini('gemini-1.5-flash'),
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 flow:run menuQA '"Recommend a dessert from the menu while avoiding dairy and nuts"'
הפלט של הפקודה הזו אמור להכיל תגובה מהמודל, שמבוססת על קובץ menu.pdf
שנוסף לאינדקס.
כתיבת מפתחות ומאגרי אחזור משלכם
אפשר גם ליצור מאגר משלכם. האפשרות הזו שימושית אם המסמכים שלכם מנוהלים במאגר מסמכים שלא נתמך ב-Genkit (למשל: MySQL, Google Drive וכו'). ב-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
הם דברים שתצטרכו להטמיע בעצמכם, הם לא מסופקים על ידי המסגרת)
ואז אפשר פשוט להחליף את ה-retriever:
const docs = await ai.retrieve({
retriever: advancedRetriever,
query: input,
options: { preRerankK: 7, k: 3 },
});
מודלים לשינוי דירוג וחיפוש בשני שלבים
מודל דירוג מחדש – שנקרא גם מקודד חוצה – הוא סוג של מודל שמקבל שאילתה ומסמך ומפיק ציון דמיון. אנחנו משתמשים בציון הזה כדי לשנות את הסדר של המסמכים לפי הרלוונטיות שלהם לשאילתה שלנו. ממשקי 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,
}));
}
);
הכלי הזה לשינוי הדירוג משתמש בתוסף genkit של Vertex AI עם 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, וכך תהיה לכם גמישות להטמיע אסטרטגיות מתקדמות למיון מחדש.