Firebase Genkit מספק הפשטות שיעזרו לכם ליצור תהליכי יצירת נתונים שמבוססים על אחזור (RAG), וגם פלאגינים שמספקים שילובים עם כלים קשורים.
מהו RAG?
יצירת תוכן באמצעות אחזור משופר היא טכניקה שמשמשת לשילוב מקורות מידע חיצוניים בתשובות של LLM. חשוב שתוכלו לעשות זאת כי בדרך כלל מודלים של LLM מאומנים על כמות גדולה של חומר, אבל לרוב נדרש ידע ספציפי בתחום כדי להשתמש בהם באופן מעשי (לדוגמה, יכול להיות שתרצו להשתמש ב-LLM כדי לענות על שאלות של לקוחות לגבי המוצרים של החברה שלכם).
פתרון אחד הוא לשפר את המודל באמצעות נתונים ספציפיים יותר. עם זאת, הדבר יכול להיות יקר גם מבחינת עלות המחשוב וגם מבחינת המאמץ הנדרש כדי להכין נתוני אימון מתאימים.
לעומת זאת, RAG פועל על ידי שילוב של מקורות נתונים חיצוניים בהנחיה בזמן שהיא מועברת למודל. לדוגמה, אפשר לדמיין שההנחיה "מה הקשר בין בארט לליזה?" עשויה להתרחב ('להשתפר') על ידי הוספת מידע רלוונטי, וכתוצאה מכך ההנחיה תהיה "הילדים של הומר ומרג' הם בארט, ליזה ומגי. What is Bart's relationship to Lisa?
לגישה הזו יש כמה יתרונות:
- השיטה הזו יכולה להיות חסכונית יותר כי לא צריך לאמן מחדש את המודל.
- אתם יכולים לעדכן את מקור הנתונים באופן שוטף, ו-LLM יכול להשתמש במידע המעודכן באופן מיידי.
- עכשיו יש לכם אפשרות לצטט מקורות בתשובות של ה-LLM.
מצד שני, השימוש ב-RAG כרוך בהנחיות ארוכות יותר, וחלק משירותי ה-LLM API מחייבים על כל אסימון קלט שאתם שולחים. בסופו של דבר, עליכם להעריך את העלויות של האפליקציות שלכם.
ניתוח RAG הוא תחום רחב מאוד, ויש הרבה שיטות שונות להשגת ניתוח RAG באיכות הטובה ביותר. מסגרת הליבה של Genkit מציעה שלוש הפשטות ראשיות שיעזרו לכם לבצע RAG:
- מוסדי אינדקס: הוספת מסמכים לאינדקס.
- הטמעה (embed): המרת מסמכים לייצוג בווקטור
- שירותי אחזור: אחזור מסמכים מ'אינדקס', על סמך שאילתה.
ההגדרות האלה רחבות בכוונה, כי ל-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 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 עם שלבי אחזור.
הגדרת תהליך עם אחזור
בדוגמה הבאה מוסבר איך משתמשים ב-retriever בתהליך RAG. בדומה לדוגמה של ה-indexer, בדוגמה הזו נעשה שימוש ב-Genkit's file-based vector retriever, שאסור להשתמש בו בסביבת הייצור.
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 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, וכך תהיה לכם גמישות להטמיע אסטרטגיות מתקדמות למיון מחדש.