תמלול משפטי בעברית: העורך הוא המוצר

פורסם במקור כפוסט בלינקדאין, והורחב כאן.

הקוד ב־GitHubOmerLapidot/tamlil

בניתי פייפליין תמלול בעברית על המודלים של ivrit.ai, כולל זיהוי דוברים, בערך בשני פרומפטים ל-Claude Code — והחלק הקשה לא היה התמלול אלא העורך שבו בן אדם מאזין, מתקן ומאשר כל שורה מול האודיו. מסמך משפטי לא יוצא מפלט ASR שאף אחד לא בדק.

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

הפייפליין הוא החצי הקל

השרשרת פשוטה: ffmpeg ממיר את הקובץ שהועלה (m4a, mp3, wav, ogg או opus) ל-wav מונו ב-16 kHz, faster-whisper מתמלל, pyannote מזהה דוברים, ופונקציית merge מחברת בין השניים. המודלים הם של ivrit.ai — whisper-large-v3-turbo-ct2 לתמלול ו-pyannote-speaker-diarization-3.1 לדיאריזציה.

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

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

הדבר היחיד שהוא לא טריוויאלי שם הוא מדד הביטחון. faster-whisper מחזיר avg_logprob לכל קטע, ואני ממיר אותו ל-0 עד 1 עם exp. זה בדיוק המספר שמסמן אחר כך בעורך אילו שורות חשודות.

ועוד פרט קטן שאפשר לבזבז עליו ערב שלם אם לא יודעים: פלט whisper.cpp מכיל תווי בקרה של כיווניות — סימני RTL/LTR בלתי נראים — והפרסר שממיר אותו לדאטת הפיתוח מנקה אותם. בלי זה הם דולפים בשקט למסמך הסופי.

העורך הוא המוצר

הפייפליין הוא כמה מאות שורות. העורך הוא כל השאר, וזה מה שהופך את זה למשהו שעורך דין יכול להשתמש בו:

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

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

הפורמט התקני הוא בעיה בפני עצמה

הייצוא הוא docx דרך python-docx או PDF דרך WeasyPrint, עם תיבות סימון לחותמות זמן ולשמות דוברים. קטעים רצופים של אותו דובר מתאחדים לפסקה אחת, כי אף אחד לא רוצה לקרוא מסמך שבו כל משפט הוא שורה נפרדת.

עברית בוורד היא כאב ראש נפרד לגמרי. הכללים שהתכנסתי אליהם: w:bidi על הפסקה, w:rtl רק על מקטעי הטקסט (runs) שהם באמת עברית, w:cs ו-w:szCs על כל run, פונט David, ולעולם לא לעשות pre-shaping לטקסט עם python-bidi. הטקסט מפוצל ל-runs לפי סקריפט לפני שהוא נכנס למסמך, כך שמספר או מונח באנגלית באמצע משפט בעברית לא מתהפך.

מה זה עולה להריץ

חוץ מה-GPU, כלום. ה-backend הוא Lambda שמתאפס כשאין שימוש, וה-S3 וה-CloudFront עולים גרושים בנפחים האלה. ה-worker הוא SageMaker async endpoint על ml.g5.xlarge שמוגדר לרדת לאפס מכונות, כך שמשלמים רק בזמן שיש ג'וב באוויר — במחיר של cold start של כמה דקות בג'וב הראשון.

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

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

מה שהריפו לא כולל

הריפו לא מכיל שום קובץ אודיו לדוגמה ושום תמלול, וה-.gitignore אומר את זה במפורש. כל הפיתוח והדמו רצו על מוק-דאטה, לא על ריאיונות אמיתיים — אין כאן חשש לזליגת פרטיות. הגרסה החיה יושבת מאחורי HTTP Basic Auth ב-edge של CloudFront, עם סיסמה נפרדת לכל משתמש וקידומת S3 פרטית לכל אחד; זו לא מערכת משתמשים אמיתית, וזו החלטה מודעת.

הרעיון שנשאר פתוח

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

אז מה אם ivrit.ai ייתנו את זה למשרד המשפטים במחיר עלות, ויקבלו בתמורה את התיקונים כדאטה לאימון? אנוטטורים בחינם, מעבודה שנעשית ממילא.

מה שלקחתי מזה

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

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

שאלות שחוזרות

האם אפשר לתמלל ריאיונות בעברית עם AI ולהגיש את זה כמסמך משפטי?

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

אילו מודלים מתמללים עברית הכי טוב?

בפרויקט הזה השתמשתי במודלים של ivrit.ai — whisper-large-v3-turbo-ct2 לתמלול ו-pyannote-speaker-diarization-3.1 לזיהוי דוברים. שניהם רצים דרך faster-whisper ו-pyannote על אותו קובץ wav מונו ב-16 kHz.

כמה עולה להריץ שירות תמלול בענן?

ה-backend הוא Lambda שמתאפס כשאין שימוש, וה-S3 וה-CloudFront עולים גרושים בנפחים האלה. הסעיף היקר הוא ה-GPU: ה-worker רץ על SageMaker async endpoint עם ml.g5.xlarge שמוגדר לרדת לאפס מכונות, כך שמשלמים רק בזמן שהג'וב באוויר — במחיר של cold start של כמה דקות בג'וב הראשון.

למה תיקון תמלול הוא בעצם אנוטציה לאימון מודלים?

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