דלג לתוכן הראשי
AI Engineering Multimodal AI
רמה: מתקדם עודכן: אוגוסט 2026

Multimodal AI

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

מה באמת השתנה

המשפט הרגיל הוא "המודלים יודעים לראות". זה נכון וזה מפספס את מה שמשנה בפועל.

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

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

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

למה זה חזק לעסקים

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

כשל שקט במקום כשל רועש

זו הנקודה שקובעת איך בונים מערכת סביב זה, והיא הפוכה לגמרי ממה שהתרגלנו אליו.

OCR נכשל בקול. הוא מחזיר 1O0.5O במקום 100.50, ג׳יבריש, או כלום. אתה רואה את זה מיד, וגם הקוד שלך רואה — המחרוזת לא נפרסת למספר.

מודל ראייה נכשל בשקט. הוא יחזיר 100.50 — מספר תקין, מפורמט נכון, במקום הנכון ב-JSON. פשוט לא הסכום הנכון. לקח את הסכום לפני מע"מ במקום את הסה"כ, או את הסכום מחשבונית קודמת שמופיעה בפינה.

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

הבסיס: לשלוח תמונה

המנגנון פשוט — תמונה נכנסת לתוך ההודעה לצד הטקסט, בין כקישור ובין כ-base64:

from openai import OpenAI
client = OpenAI()

# מזהי מודלים משתנים — קח את הנוכחי מתיעוד הספק
MODEL = "gpt-..."

resp = client.chat.completions.create(
    model=MODEL,
    messages=[{"role": "user", "content": [
        {"type": "text",
         "text": "מה מוצג בתמונה? תיאור קצר בעברית."},
        {"type": "image_url",
         "image_url": {"url": "https://example.com/photo.jpg"}},
    ]}],
)
print(resp.choices[0].message.content)

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

לשאול על תמונה שונה מלשאול על טקסט

יש הבדל אחד שגורם לרוב התוצאות הגרועות, והוא לא בניסוח אלא בכיוון השאלה.

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

מה שעובד הוא להפוך את הסדר: קודם לשאול מה יש, ורק אחר כך לחלץ.

# ✗ שאלה מובילה
"מה הסכום הכולל?"

# ✓ קודם לזהות, אחר כך לחלץ
"""
1. איזה סוג מסמך זה? (חשבונית / קבלה /
   הצעת מחיר / תעודת משלוח / אחר / לא ברור)
2. האם המסמך שלם וקריא? אם חלקים חסרים
   או מטושטשים — ציין אילו.
3. רק אם זו חשבונית קריאה — חלץ לפי הסכימה.
   אחרת החזר {"document_type": ..., "extract": null}
"""

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

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

מסמכים: המקרה שמחזיר הכי הרבה

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

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

SCHEMA = """
החזר JSON בלבד, בדיוק במבנה הזה:
{
  "supplier_name":  string | null,
  "supplier_id":    string | null,
  "invoice_number": string | null,
  "date":           "YYYY-MM-DD" | null,
  "subtotal":       number | null,
  "vat":            number | null,
  "total":          number | null,
  "currency":       "ILS" | "USD" | "EUR" | null
}

חוקים:
- total הוא הסכום הסופי לתשלום, כולל מע"מ.
- אם שדה לא מופיע במסמך — null. אל תנחש.
- אל תמציא ואל תחשב שדה שלא כתוב.
"""

שלוש השורות בסוף עושות את רוב העבודה. השורה על total קיימת כי חשבונית ישראלית טיפוסית מכילה שלושה סכומים — לפני מע"מ, המע"מ עצמו, והסה"כ — ובלי הגדרה מפורשת המודל בוחר לפי מיקום, ולעיתים קרובות לא נכון. וההוראה על null קיימת כי ברירת המחדל של מודל היא למלא, לא להודות שאין.

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

האימות הוא לא אופציונלי

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

def validate(inv):
    problems = []

    # 1. האם החשבון מסתדר עם עצמו
    if None not in (inv["subtotal"], inv["vat"], inv["total"]):
        if abs(inv["subtotal"] + inv["vat"] - inv["total"]) > 0.05:
            problems.append("סכומים לא מסתדרים")

    # 2. האם המע"מ בשיעור סביר
    if inv["subtotal"] and inv["vat"] is not None:
        rate = inv["vat"] / inv["subtotal"]
        if not (0 <= rate <= 0.25):
            problems.append("שיעור מע\"מ חריג: %.3f" % rate)

    # 3. האם התאריך בכלל אפשרי
    if inv["date"] and not is_plausible_date(inv["date"]):
        problems.append("תאריך לא סביר")

    # 4. שדות חובה
    for f in ("supplier_name", "total"):
        if not inv[f]:
            problems.append("חסר: " + f)

    return problems

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

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

איך יודעים שזה מוכן

"בדקתי על כמה מסמכים וזה עבד" הוא לא מדד. וההערכה לא צריכה להיות פרויקט — היא צריכה להיות שלושים מסמכים.

מה שבונים פעם אחת:

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

והנקודה שהופכת את זה לשימושי: למדוד לפי שדה, לא לפי מסמך. "84% הצלחה" לא אומר כלום. מה שאומר משהו הוא שהתאריך נכון ב-97% מהמקרים, הסכום ב-99%, ושם הספק ב-71% — כי עכשיו אתה יודע בדיוק על מה לעבוד, ואולי אפילו שהשדה הבעייתי הוא כזה שאפשר לוותר עליו.

fields = ["supplier_name", "invoice_number",
          "date", "total", "vat"]

for f in fields:
    ok = sum(1 for d in docs
             if extracted[d.id][f] == truth[d.id][f])
    print("%-16s %d/%d" % (f, ok, len(docs)))

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

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

עברית ומסמכים ישראליים

ארבעה דברים שספציפיים למסמך מכאן, ואף מדריך זר לא יזכיר:

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

התור האנושי: איך מתכננים אותו

כל מערכת כזאת מייצרת מסמכים שנפלו באימות, וההצלחה שלה נמדדת לא במה שעבר אלא בכמה מהיר וזול לטפל במה שלא.

מה שהופך תור לשמיש:

ושאלה שכדאי להחליט עליה מראש: מה עדיף — לפספס או לטעות? אימות מחמיר שולח יותר מסמכים לבדיקה ידנית ומבטיח שמעט מאוד טעויות עוברות; אימות מקל חוסך עבודה ומעביר יותר שגיאות הלאה. בחשבונאות התשובה כמעט תמיד מחמיר, ובמיון תמונות לקטלוג — מקל. זו החלטה עסקית ולא טכנית, וכדאי שהיא תיאמר בקול לפני שהיא נקבעת במקרה.

רזולוציה, טוקנים ועלות

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

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

ואיך זה מתחבר לשאר המערכת

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

שלוש נקודות שמתגלות רק כשמחברים:

אודיו: מה "מולטימודלי" אומר כאן

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

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

ומה עם צילום מסך במקום מסמך

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

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

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

וידאו: פריימים, לא סרט

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

מה שנגזר מזה מעשית:

איפה זה באמת משתלם

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

מה אתה שולח, ולאן

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

שלוש שאלות לפני שמריצים על מידע אמיתי:

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

מתי לא

טעויות שחוזרות

הצעד הבא

בנה אוטומציית מסמכים אמיתית עם תבנית מוכנה, או חזק את הפלט המובנה.