חיפוש פוסטים דומים עם Embeddings

18/01/2026

אין ספק שהשימוש הכי נפוץ היום במודל שפה גדול הוא ממשק השיחה בו אנחנו שואלים את הבינה המלאכותית שאלות ומקבלים תשובות. היום אני רוצה לדבר על שימוש נוסף שנקרא Embedding. ל Embedding יש מודלים משלו וצורת עבודה משלו שבנויים על ארכיטקטורה דומה למודלי השיחה אבל משמשים למטרה אחרת. בעוד שבמודל שיחה המטרה היא לנחש את המשך השיחה, במודל Embedding המטרה היא לזהות קשר סמנטי בין קלטים שונים. בואו נראה מה זה Embedding, איך לחשב את זה ודוגמה פרקטית למציאת פוסטים קשורים פה בבלוג.

1. איך מחשבים Embedding

מודל שפה גדול מייצג שפה באמצעות אוסף של וקטורים. בממשק שיחה המודל חוזה את המשך השיחה באמצעות מיפוי:

  1. הקלט (הפרומפט) ממופה לוקטור בתוך המודל.

  2. התוכנה מחפשת את הוקטור הכי קרוב, כי קרבה בעולם של מודל שיחה מייצגת המשך הגיוני לשיחה.

  3. מוסיפים את הוקטור שמצאנו לפרומפט וחוזרים בלולאה.

במודל שפה שמתאים ל Embedding אנחנו לא רוצים לגלות את המשך השיחה אלא קשר סמנטי בין קלטים. בהנתן מודל שפה גדול שמתאים להטמעה נוכל למפות כל קלט לוקטור במודל ובסוף לחפש איזה קלטים "קרובים" אחד לשני כדי למצוא קשר סמנטי בין נושאים.

בשביל הדוגמה התקנתי על המחשב ollama והפעלתי מודל שנקרא nomic-embed-text:v1.5. עכשיו אני יכול משורת הפקודה לחשב וקטורים של הטמעה לכל קלט שארצה לדוגמה:

$ ollama run nomic-embed-text:v1.5 "hello"
[0.017934207,-0.005861857,-0.17534052,-0.013759711,...

$ ollama run nomic-embed-text:v1.5 "bye bye"
[0.059325065,-0.0042824764,-0.13401125,-0.009296248,0.010595661,0.062132772,0.008114117,0.049777295,-0.022414487 ...

כל וקטור במודל nomic-embed-text הוא בגודל 768 ערכים.

2. מה עושים עם הוקטור

תוכניות משתמשות ב Embedding בכל מקום שאנחנו רוצים להיעזר במודל השפה כדי למצוא קשר סמנטי בין קלטים שונים, לדוגמה:

  1. סוכן קוד יכול להשתמש ב Embedding כדי להוסיף קטעי קוד רלוונטים בצורה אוטומטית ל AI לפני שליחת הפרומפט.

  2. סוכן שיחה יכול להשתמש ב Embedding כדי להוסיף תיעוד רלוונטי לפרומפט לפני שליחת ההודעה ל AI. לדוגמה אני יכול ליצור סוכן שעונה מתוך מפרט טכני עצום של מוצר, אבל במקום לצרף את כל המפרט הטכני לכל בקשה להשתמש ב Embedding כדי להדביק רק קטעים שהם רלוונטים לפרומפט.

  3. בלוג יכול להציג "פוסטים קשורים" כשקוראים פוסט.

בכל המקרים שיטת העבודה עם Embedding דומה - המערכת מחשבת embedding עבור המידע שהיא שומרת ושומרת את ה embedding לצד המידע ובדרך כלל גם אינדקס או אינדקסים שיעזרו למצוא מידע קשור. כשצריך למצוא מידע קשור מספיק להריץ שאילתה על עמודת ה embedding שתחזיר וקטורים הקרובים לוקטור אותו אני מחפש.

את הוקטור של ה embedding אנחנו שומרים בבסיס נתונים מיוחד שנקרא בסיס נתונים וקטורי. אני אוהב לעבוד עם pgvector שהוא הרחבה של פוסטגרס שמוסיף אפשרות לשמור וקטורים בתוך בסיס הנתונים הרלציוני הרגיל שלי. הרבה אנשים אוהבים את כרומה שהוא בסיס נתונים וקטורי בקוד פתוח ואז יהיה לנו בסיס נתונים אחד ראשי של המערכת ובסיס נתונים וקטורי עבור הסרביסים של ה AI. בלינק הזה אפשר למצוא רשימה של 9 בסיסי נתונים וקטורים פופולריים אם אתם מרגישים הרפתקנים.

קיימות מספר שיטות לחישוב מרחק בין וקטורים בבסיס נתונים וקטורי:

  1. L2 distance
  2. inner product
  3. cosine distance
  4. L1 distance
  5. Hamming distance
  6. Jaccard distance

נתתי ל ChatGPT ליצור הסבר מלא בעברית עם דוגמאות על כל שיטה מוזמנים להעיף מבט:

https://chatgpt.com/share/696b6177-acdc-8009-bf4a-e33a2b416a8b

אני עד עכשיו עבדתי עם cosine distance והתוצאות היו טובות.

3. דוגמה: מציאת פוסטים קשורים באמצעות Embedding

נמשיך ונראה איך היינו מוסיפים תיבת "פוסטים קשורים" לבלוג בעזרת Embedding. תחילה נשים לב שבניגוד למודלי שיחה, מודלי Embedding מוגבלים לפרומפטים הרבה יותר קצרים לדוגמה מודל nomic‑embed‑text שדיברנו עליו מוגבל ל 8192 טוקנים. רוב הזמן יהיו לנו קלטים ארוכים בהרבה ולכן נצטרך לשבור את הקלטים ל Chunk-ים. בדוגמת חיפוש פוסטים קשורים אני אבצע:

  1. פיצול כל פוסט לקטעים קטנים אותם אפשר להעביר למודל ה embedding.

  2. שמירת ה embedding של כל קטע בבסיס נתונים וקטורי.

  3. חישוב ממוצע ה embedding של כל הקטעים ומזה אקבל וקטור אחד שמייצג את ה embedding של הפוסט. שמירת וקטור זה גם כן בבסיס הנתונים.

אחרי שכל המידע שמור אוכל להריץ שאילתה בבסיס הנתונים. התחביר לכל בסיס נתונים שונה וב pgvector זה יהיה:

SELECT * FROM posts WHERE id != 1 ORDER BY embedding <=> (SELECT embedding FROM posts WHERE id = 1) LIMIT 5;

או בעברית "בחר את כל הפוסטים שה id שלהם שונה מאחד וסדר אותם לפי מרחק קוסינוס מהפוסט שהמזהה שלו הוא 1. החזר את ה-5 הראשונים. אלה בדיוק הפוסטים הקשורים לפוסט 1.

יצרתי דוגמת קוד מלאה להמחשה בקישור הזה בגיטהאב:

https://github.com/ynonp/embedding-demo

להלן עיקרי הקוד מתוך הדוגמה:

  1. פיצול פוסט לקטעים, אני בחרתי אורך קטע קצר של 1024 תווים שלא יהיו בעיות:
def create_chunks
  self.chunks.destroy_all

  token_counter = ->(text) { text.length }
  text_chunks = Semchunk.chunk(text, chunk_size: 1024, token_counter: token_counter)
  text_chunks.each_with_index do |chunk_text, chunk_number|
    Chunk.create!(chunk_text:, chunk_number:, post: self)
  end
end
  1. חישוב embedding מכל קטע ושמירתם בבסיס הנתונים הוקטורי pgvector. נשים לב שהמודל מגיע כפרמטר. ברירת המחדל היא אותו nomic-embed גרסה 1.5 אבל האמת שגרסה 1.5 לא עובדת טוב עם עברית וקיבלתי תוצאות טובות יותר עם גרסה 2 מרובת השפות:
def calc_embed!(model: 'nomic-embed-text:v1.5')
  return unless chunk_text.present?
  embed = RubyLLM.embed(chunk_text, provider: :ollama, model:)
  self.embedding = embed.vectors

  save!
end
  1. חישוב ממוצע ה embedding של כל פוסט ושמירתו בבסיס הנתונים בטבלת הפוסטים:
def calc_embed!(model: 'nomic-embed-text:v1.5')
  chunks.where("embedding is null and chunk_text is not null").each do |chunk|
    chunk.calc_embed!(model:)
  end
  self.embedding = chunks.average(:embedding)
  save!
end
  1. מציאת פוסטים קשורים לפוסט מסוים:
Post
    .find_by(slug: '2026-01-react-use-effect-event')
    .nearest_neighbors(:embedding, distance: :cosine)
    .first(5)
    .map(&:slug)

התוצאה לא רעה בכלל. בדוגמה שהדבקתי לקחתי פוסט על ריאקט וקיבלתי 5 פוסטים רובם קשורים לריאקט:

 => ["2025-10-09-nextjs-ui", "2025-10-21-voice-agent", "2025-10-react-activity", "2025-08-learn-react-with-ai", "2025-08-ai-biases"]

4. לאן ממשיכים

ביום חמישי בבוקר אעביר וובינר על Embedding עם דוגמת חישוב הפוסטים הקשורים. בוובינר נסתכל על הוקטורים, איך הם נשמרים בבסיס הנתונים, ננסה שיטות חישוב מרחק שונות ומודלים שונים וכמובן יהיה המון מקום לשאלות שלכם. הוובינר בחינם, לא מתוכננת הקלטה ונרשמים כאן: https://www.tocode.co.il/talking_ai

לגבי האתר אני מודה שעדיין לא בטוח אם ואיך להפוך את זה לקוד פרודקשן פה בבלוג. שמירת ה embedding של כל הפוסטים עלולה להכביד על בסיס הנתונים וחישוב embed של כל פוסט ב ollama דורש בערך 20 ג'יגה זכרון RAM מה שאומר שעדיף לעשות את זה באופליין לא על שרת הפרודקשן. לא בטוח שהערך המוסף של פוסטים קשורים מצדיק את כל התקורה. אם יש לכם דעה חזקה בעניין בואו לוובינר ושכנעו אותי.

נתראה בחמישי, ינון