בניית pipeline לחיפוש וקטורי
שלב 1: חלוקת מסמכים לקטעים (Chunking)
לפני יצירת embeddings, מפצלים מסמכים ארוכים לקטעים (chunks) בגודל סביר — כמה משפטים עד פסקה. זו החלטת עיצוב מעשית ולא רק פרט טכני: קטעים גדולים מדי מייצרים embedding "מטושטש" שמנסה לייצג יותר מדי רעיונות שונים בוקטור אחד (הבעיה שהוזכרה בפרק העוסק ב-embeddings), וקטעים קטנים מדי מאבדים הקשר סביבתי חשוב שקיים במסמך המקורי.
פתרון נפוץ לאיזון בין השניים הוא חפיפה (overlap) בין קטעים סמוכים — כל קטע חדש מתחיל קצת לפני שהקטע הקודם הסתיים, כך שמידע שנמצא בדיוק על הגבול בין שני קטעים לא הולך לאיבוד לגמרי מאף אחד מהם.

שלב 2: יצירת Embeddings לכל קטע
כל קטע עובר במודל embedding ומקבל וקטור. בשלב הזה בונים בדרך כלל תור עבודה שמריץ את כל הקטעים במודל (לרוב באמצעות API חיצוני), ואוסף את הוקטורים שמתקבלים לצד מזהה הקטע המקורי.
async function embed(text: string): Promise<number[]> {
const response = await embeddingClient.create({
model: "text-embedding-model",
input: text,
});
return response.embedding;
}
const chunks = chunkDocument(document, { size: 500, overlap: 50 });
const embeddedChunks = await Promise.all(
chunks.map(async (chunk) => ({
id: chunk.id,
vector: await embed(chunk.text),
metadata: { source: document.id, ...chunk.metadata },
}))
);שלב 3: שמירה באינדקס וקטורי (Upsert)
הוקטורים, יחד עם מטא-דאטה רלוונטי (מזהה מקור, תאריך, קטגוריה וכד'), נשמרים באינדקס הוקטורי — בין אם זה מסד נתונים וקטורי ייעודי, תוסף וקטורי למסד קיים, או ספרייה מוטמעת (כפי שנסקר בפרק הקודם). פעולת השמירה נקראת בדרך כלל upsert — הוספה אם המזהה חדש, עדכון אם הוא כבר קיים.
await vectorIndex.upsert(
embeddedChunks.map((chunk) => ({
id: chunk.id,
values: chunk.vector,
metadata: chunk.metadata,
}))
);שלב 4: שאילתה — Embedding וחיפוש Top-k
בזמן שאילתה, טקסט החיפוש עצמו עובר באותו מודל embedding בדיוק (חשוב: אותו מודל שיצר את הוקטורים המאונדקסים — לא ניתן לערבב מודלים שונים, כי המרחבים הווקטוריים שלהם אינם ניתנים להשוואה זה לזה), ומתקבל וקטור שאילתה. הוקטור הזה מוזן לאינדקס, שמחזיר את k הפריטים הקרובים ביותר.
const queryVector = await embed(userQuestion);
const results = await vectorIndex.query({
vector: queryVector,
topK: 5,
});שלב 5: שילוב סינון לפי מטא-דאטה
רוב מערכות הפרודקשן לא מסתפקות בחיפוש וקטורי "נקי" — הן משלבות אותו עם סינון קשיח לפי שדות מבניים, בהתאם לצורך שהוזכר בפרק הקודם ("פריטים דומים, אבל רק כאלה שבמלאי"). הסינון מצטרף לשאילתה עצמה, ומצמצם את מרחב החיפוש עוד לפני או תוך כדי חישוב הדמיון הוקטורי.
const filteredResults = await vectorIndex.query({
vector: queryVector,
topK: 5,
filter: { category: "docs", updatedAfter: "2026-01-01" },
});