otzaria_search_engine 0.7.2
otzaria_search_engine: ^0.7.2 copied to clipboard
Tantivy search engine for Otzaria
Changelog #
0.7.2 – 2026-08-06 – תיקון ספירת המילים בהדגשת הספר הפתוח #
Fixed #
-
מילים הודגשו בספר במרווח שבו החיפוש עצמו אינו מוצא אותן. תבנית ההדגשה (
generate_highlight_pattern) ספרה מילה מתווכת אחת כ-\S+— רצף בלי רווחים — בעוד האינדקס מפצל מילים גם על מקף, פסק וסוף-פסוק. בפסוקוַיֹּאמֶר לְאַבְרָם יָדֹעַ תֵּדַע כִּי־גֵר יִהְיֶה זַרְעֲךָ, השאילתה "תדע זרעך" הודגשה כבר במרווח 2 (כי־גרנספרה כמילה אחת) בעוד החיפוש דורש 3. באפליקציה זה נראה כך: הטקסט מודגש, וחלונית החיפוש שלידו מציגה "אין תוצאות".מחלקות התווים של התבנית נגזרות עכשיו מהטוקנייזר ולא משוכפלות בעבודת יד:
hebrew_tokenizer::continues_tokenהוא מקור האמת, ומעליו נבנות שלוש מחלקות זרות זו לזו — אות/ספרה (רק שם טוקן מתחיל ומסתיים), סימן רך (ניקוד, פיסוק שקוף, גרשיים — ממשיך טוקן ואינו פותח אותו), ושובר-טוקן. כךרמב״ם,פ.ב.י,3.14ו-יב[ע]רהם מילה אחת, ואילוכי־גר,א|ב,א/ב,א׃ב,כי⸗גר(U+2E17) ו-כי−גר(U+2212) הם שתיים.הזרות אינה קוסמטית: בצורה הקודמת מחלקת המילה ומחלקת המפריד חפפו, ולכן
SEP(?:WORD SEP){0,n}היה דו-משמעי ו-RegExpשל Dart נתקע בשורה שאינה מתאימה. נמדד ב-Dart על אותה שורה בת 90 תווים עם תגי HTML צפופים: 1,333ms במרווח 5 ו-4,350ms במרווח 10–30 לפני, 0ms אחרי.</>מטופלים עכשיו רק כתג שלם:תדע<b>זרעךאינו מודגש בשום מרווח, כיstrip_html_for_indexingמוחק את התג בלי רווח והאינדקס רואה שם טוקן אחד. -
המפריד של תבנית ההדגשה הליטרלית הורחב לאותה מחלקה.
[\s־׀|]+החטיא פיסוק דבוק:תדע, זרעךנמצא בחיפוש (הפיסוק אינו חלק מהטוקן) ולא הודגש בספר. עכשיו שני המסלולים חולקים את אותוWORD_SEPARATOR.שבעה טסטים אוכפים את השקילות: property test שעובר על כל תו בדומיין ומשווה את שלוש המחלקות לפרדיקטים של הטוקנייזר ומאמת את הזרות ביניהן, טסט טבלאי שמשווה את המרווח המזערי של התבנית למספר הטוקנים ש-
next_token_boundariesמוצא בפער (18 מקרים), טסט לצמידות דרך פיסוק שקוף, שני טסטים לתגי HTML, טסט לאורכי התבנית, וטסט שמריץ חיפוש אמיתי על אינדקס ומאמת שהחיפוש וההדגשה מסכימים באותו מרווח. בצד Dart נוספו טסטי התאמה על טקסט אמיתי (test/display_highlight_pattern_test.dart).
0.7.1 – 2026-08-02 – תיקון קישור ב-Apple #
Fixed #
-
בנייה ל-macOS/iOS נכשלה בשלב הקישור. 0.7.0 הביאה איתה את llama.cpp, ושתי תלויות נייטיביות שלו לא הוצהרו בשום מקום. cargokit בונה
staticlib, ולכן ההצהרותcargo:rustc-link-libשלllama-cpp-sys-2לא מגיעות ללינקר של Xcode כלל. שתי הבעיות התגלו רק בקישור של אפליקציה — ה-CI של הבינאריים המוכנים מייצר את הארכיון בלבד ואף פעם לא מלנקק אפליקציה מולו.- frameworks חסרים ב-podspec (
macos/,ios/): ggml משתמש ב-vDSP (Accelerate) ו-Metal, והקוד עצמו הוא C++. נוספוs.libraries = 'c++'ו-s.frameworks = 'Accelerate', 'Metal', 'MetalKit', 'Foundation'. בלעדיהם חסרו מאות סמלים מסוג_vDSP_*,_MTL*ו-___cxa_*. commonשל llama.cpp נבנה שלא לצורך: הסיידקר נעוץ מחדש ל-revision שמכבה אותו.llama-cpp-2הצהיר עלllama-cpp-sys-2בליdefault-features = false, וה-default של sys הוא["common"]— מה שגרר אתdownload.cppואתcpp-httplibשאף פעם לא מקושרת, ולכן נשארו 12 סמליhttplib::*בלתי פתורים. הקוד הזה לא היה נגיש מ-Rust מלכתחילה. זהות הווקטורים נשמרה (token ids זהים, worst cosine 0.9961), והארכיון ל-Apple קטן ב-~13MB.
- frameworks חסרים ב-podspec (
0.7.0 – 2026-07-31 – חיפוש סמנטי היברידי #
Added #
-
חיבור החיפוש הסמנטי ל-
SearchEngineדרך FFI – משטח API חדש ותוספתי לחלוטין; אף קריאה קיימת לא שונתה. הסיידקרotzaria-semantic-searchנקשר לאותה ספרייה נייטיבית של Tantivy, ונעוץ ל-revision מדויק ב-rust/Cargo.tomlו-rust/Cargo.lock— זהות המודל והאינדקס תלויה במימוש המדויק, ולכן ענף נייד אסור.- מחזור חיים:
configureSemantic/disableSemantic/semanticStatus. קריאה חוזרת ל-configureSemanticעם אותם קלטים היא no-op שמחזירה את הסטטוס; עם קלטים שונים היא נכשלת ומציינת איזה שדה השתנה, כי מאגר הווקטורים הוא בזיכרון ופתיחה מחדש הייתה מוחקת אותו בשקט. החלפת מודל או ספרייה היא מעשה מפורש:disableSemanticתחילה. - אינדוקס:
semanticIndexBooks/semanticIndexDiff/removeSemanticBooks/resetSemanticIndex. כולם מקבלים&selfבצד Rust, כך ש-flutter_rust_bridge אינו נועל את המנוע כולו — חיפוש לקסיקלי ו-polling של סטטוס נשארים רספונסיביים לאורך אינדוקס של ספרייה שלמה. - חיפוש:
searchSemanticעםSemanticRetrievalMode(hybrid/semanticOnly/lexicalOnly), נפרד מ-SemanticLexicalMode(exact/fuzzy) — פרשנות לקסיקלית ומצב אחזור הם שני צירים שונים. בקשתhybridנופלת ל-Tantivy עםfallbackReasonמפורש כשהסמנטי לא זמין; בקשתsemanticOnlyלעולם אינה מתחזה לתוצאה לקסיקלית.
- מחזור חיים:
-
חוזה תצוגה מפורש בתוצאה –
snippetHtmlתמיד מכיל טקסט להצגה, ולצידוisHighlightedשאומר אם הוא נצבע. תוצאה סמנטית שלא עמדה בביטוי הלקסיקלי מקבלת קטע טקסט נקי ו-isHighlighted == false, במקום להיצבע כאילו נמצאה לקסיקלית.SemanticResultSource(lexical/semantic/both) מוסיף לכל תוצאה את מקורה.
Changed #
-
minSdkVersionהועלה מ-21 ל-23 – שינוי שובר לצרכנים שתומכים ב-API 21 או 22.llama.cppקורא ל-posix_madvise, ש-bionic חושף רק מ-API 23. התואם ל-ANDROID_PLATFORMשבו נבנים הבינארים המוכנים. אפליקציות עלflutter.minSdkVersion(24) אינן מושפעות. -
הפיצ'ר
semanticפעיל בבניות הצרכן (rust/cargokit.yaml) – בדרך כלל שקוף, כי הבינארים המוכנים מורדים משוחררים וחתומים. בנייה מקומית שנופלת אחורה (crate hash ללא artifacts) מקמפלתllama.cppולכן דורשת cmake. -
ב-ARM 32-ביט (
armv7-linux-androideabi) אין embedding backend –llama-cpp-sys-2אינו נבנה ליעד הזה, ומודל 0.6B ב-Q4 אינו שמיש עליו ממילא. הבנייה מצליחה והחיפוש מתדרדר לבדו ללקסיקלי, אבלsemanticIndexBooksזורק שם. הדגל לבדיקה לפני אינדוקס הואavailable— לאenabled, ולא diff לא-ריק. ראו "Builds without a backend" ב-README.
0.6.9 – 2026-07-15 #
0.6.8 – 2026-07-14 – חיפוש מתקדם מורחב, facets ממדיים ושדרוג ביצועי אינדוקס #
Added #
-
התאמה חלקית של מילות השאילתה (
wordMatchMode) במסלול המתקדם – צמד פרמטרים אופציונליים חדש בכל משפחת ה-advanced (searchAdvanced, ה-streams,countAdvanced,countByBookAdvanced,getFacetCountsAdvancedוגרסאות ה-withStatusשלהם):wordMatchMode—all(ברירת המחדל, ההתנהגות הקיימת) /anyWord(די במילה אחת) /mostWords(רוב:n/2+1) /atLeast(לפחותwordMatchCountמילים, נחתך ל-[1, n]). בכל מצב שאינוallדרישת הסדר והמרחק בטלה:wordDistanceמתנהג כ"אותה פסקה" (BooleanQuery של Should עם מינימום נדרש — תוצאה עם יותר מילים מקבלת score גבוה יותר במיון רלוונטיות), ו-sameSectionדורש שהסעיף יכיל לפחות את מספר המילים הנדרש (ספירת מילים ייחודיות פר סעיף במקום חיתוך). מילה שחוזרת בשאילתה נספרת פעם אחת בסף; שאילתת השלילה נשארת תמיד "כל המילים"; חלופות ר"ת נשארות ביטוי שלם. בהדגשה: מסנן-הביטוי מנוטרל בהתאמה חלקית כך שגם מילה בודדת שנמצאה נצבעת. -
אפשרות "ארמית" פוצלה ל"קידומות ארמיות" ו"סיומות ארמיות" – שתי אפשרויות פר-מילה עצמאיות במקום מפתח
ארמיתהיחיד (שלא פורסם): "קידומות ארמיות" — קבוצת הקידומות הדקדוקית (ד/כד/אד/מד...) לפני המילה; "סיומות ארמיות" — שקילות אות סופית ה↔א (מלכה↔מלכא) ו-ם↔ן (חכמים↔חכמין). סימון שתיהן משחזר את ההתנהגות המקורית. בהדגשה: וריאנטי השקילות נצבעים תחת "סיומות ארמיות"; זכאות גבול-המילה נשברת רק תחת "קידומות ארמיות". -
אפשרות "ראשי תיבות" – פענוח ר"ת דו-כיווני בחיפוש המתקדם – מילון ראשי-תיבות (
Acronyms.jsonשל האפליקציה, נטען דרךset_acronyms_dictionary_path/has_acronyms_dictionary) מרחיב שאילתה שסומנה לה האפשרות: ר"ת בודד מוצא גם את פענוחיו המלאים (רמב"ם← "רבי משה בן מיימון"), וביטוי שהוא פענוח ידוע מוצא גם את הר"ת (רבי משה בן מיימון←רמב"ם). הפענוח רב-מילי ולכן נבנה כתת-שאילתות OR מלאות (slop 0, באותם facets/scope) ולא כחלופות חד-מילתיות; ההתאמה הדטרמיניסטיתרמבם↔רמב"םנשארת ברמת האינדקס (הטוקן-התאום). פענוח חד-מילי מדולג בטעינה (מכוסה ממילא ע"י האינדקס); תקרתMAX_ACRONYM_EXPANSIONS = 16פר כיוון. מצב מנוקד אינו נתמך בשלב זה. מפתח UI:ראשי תיבות. הדגשה: מילות החלופות מצטרפות לאיחוד ההדגשה השטוח, כך שמסמך שנמצא דרך הפענוח (או דרך הר"ת בכיוון ההפוך) נצבע — דרך נפילת מסנן-הביטוי לצביעה הרחבה. -
אפשרות "תרגום ארמי" – הרחבת מילה בתרגומיה – מילוני הארמית-עברית (
dictionary.jsonשל האפליקציה, נטען דרךset_translation_dictionary_path/has_translation_dictionary) מרחיבים מילה שסומנה לה האפשרות בתרגומיה בשני הכיוונים (ארמי↔עברי), כמילים חלופיות שזורמות בכל המסלולים הקיימים. כל המילונים שבקובץ ממוזגים (פשיטא, שיח ישראל, אונקלוס...) — הגרסה הראשונית לקחה את "המילון הראשון", ומפת serde_json ממוינת אלפביתית כך שנטען בפועל "מושגים ואישים" (ערך יחיד) וכל התרגומים מתו בשקט (הכא↔כאןלא עבד). רק תרגומים בני מילה אחת נכנסים למפה; תקרתMAX_TRANSLATION_EXPANSIONS = 16. מפתח UI:תרגום ארמי. -
אפשרות "התעלם מגרשיים" פר-מילה – גרש/גרשיים שהוקלדו במילה מוסרים לפני בניית התבנית (
רמב"םמחפשרמבם), שמותאמת באינדקס לשתי הצורות דרך הטוקן-התאום. מפתח UI:התעלם מגרשיים. -
טוקן-תאום נטול-גרשיים באינדוקס – במצב האינדוקס (
emit_quote_free), מילה עם גרש/גרשיים מטמיעה גם את צורתה הנקייה באותה עמדה ובאותם offsets — חיפושרמבםמוצארמב"ם(וההפך, דרך "התעלם מגרשיים") וההדגשה יורשת את טווח המילה המקורית. שינוי תוכן מילון הטרמים בלי שינוי סכימת tantivy — בדיקת התאימות לא תופסת זאת לבד, ולכן מכוסה בהעלאתINDEX_SCHEMA_VERSIONל-3 (שטרם פורסמה), שמחייבת בנייה מחדש של אינדקסים ישנים. -
SearchStreamUpdate.truncated– איתות "תוצאות חלקיות" ל-UI – כשמסלול המילה-היחידה הרחבה חורג מתקציב איסוף הטרמים (SINGLE_WORD_POSTINGS_BUDGET/max_expansions) הוא ממשיך להגיש את ההרחבות בעדיפות הגבוהה (degrade, לא שגיאה) — עד כה בשקט, עםwarn!ליומן בלבד. כעת דגל ה-truncation מחלחל מ-single_regex_term_query(כולל שמירה ב-term_cache) אל האירוע הראשון של ה-stream המשולב, כך שאוצריא מציגה באנר "ייתכן שהתוצאות חלקיות — צמצמו את החיפוש". המסלולים המדויק והמקורב נטולי-הסימנים אינם מתדרדרים כך ולכן תמידfalse; המסלולים המנוקדים שלהם כן (מילה מנוקדת מתממשת לסט טרמים כמו מילה מתקדמת) ולכן נושאים את הדגל. -
add_text_book_bytes– אינדוקס ספר טקסט מבייטים גולמיים (UTF-8) – האפליקציה קוראת תוכן מ-SQLite שמאוחסן UTF-8; העברתו כ-Vec<u8>(Uint8Listבצד Dart) חוסכת את סבב הקידוד UTF-8→UTF-16→UTF-8 שמחרוזת Dart עולה על הגשר (~180ms/MB שנמדדו). קלט UTF-8 לא-תקין מתוקן (lossy), לעולם לא שגיאה. זהות מלאה ל-add_text_book— אותם מסמכים ואותה טביעת אצבע (ה-FNV מחושב על אותם בייטים). -
נרמול מקבילי באינדוקס (rayon) –
add_text_bookו-add_pdf_bookמריצים את הנרמול (וסינון הזבל ב-PDF) על כל הליבות: מעבר זול סדרתי פותר את ה-reference trail (תלוי-סדר) לאינדקס-לשורה, ואז par_iter על השורות. בלוגים הנרמול היה ~85% מזמן ה-CPU של חוט ההזנה (55s מתוך 67s על 942 ספרים). -
add_pdf_book– אינדוקס PDF שלם בקריאת FFI אחת – מקבל את עמודי הספר (reference, טקסט גולמי, אינדקס עמוד), מנרמל כל שורה (normalize_pdf_text_for_indexing), מסנן שורות זבל (is_probably_garbage_pdf_text) ומאנדקס — אותה לוגיקת per-line שלnormalize_pdf_texts_for_indexing, בלי שהטקסט המחולץ יחצה את הגשר ארבע-חמש פעמים (isolate ← נרמול באצוות ← SendPort ← addDocumentsBatch). מחזיר את מספר המסמכים שנוספו; 0 ⇒ אין טקסט שמיש (סרוק) והקורא נופל ל-sidecar/סמן-ריק.segment= אינדקס העמוד, מזהי מסמכים מקודדים סדר קטלוגי כמוadd_text_book, contentHash=0. -
set_bulk_indexing– מצב בנייה מלאה ללא מיזוגי רקע – בזמן בניית ספרייה מלאהLogMergePolicyממזג סגמנטי-ביניים שוב ושוב — עבודה שנזרקת, כי הקורא מריץoptimize(מיזוג-הכול) פעם אחת בסוף. במצב bulk ה-writer (וגם writer שנפתח מחדש בעצלנות) מקבלNoMergePolicy; כבוי כברירת מחדל, ואינדוקס אינקרמנטלי ממשיך למזג כרגיל. -
לוגי תזמון לאבחון מהירות אינדוקס –
add_text_book/add_pdf_bookמדווחים ב-info!מסמכים/בייטים/משך בפירוק prepare (נרמול על חוט ההזנה) מול enqueue (לחץ-חוזר מחוטי האינדוקס של tantivy); commitמדווח משך commit ו-reader reload; optimizeמדווח סגמנטים לפני/אחרי ומשך. המנוע מתקין (פעם אחת,try_init) env_logger עם ברירת מחדלsearch_engine=info— הלוגים נראים בקונסולת האפליקציה בלי הגדרה בצד Dart, ו-RUST_LOGעדיין גובר. -
*_with_statusלמניית תוצאות – איתות truncation גם ל-count/facets – count,count_by_book,get_facet_counts(וגם ה-_advanced) זרקו את דגל ה-truncation של מסלול המילה-היחידה, כך שעץ סינון ה-facets היה מציג ספירות חלקיות בלי סימון. נוספו טיפוסיםCountResult,BookCountResult,FacetCountsResult(כל אחד עםtruncated) ומתודות*_with_statusמקבילות. המתודות הישנות נשמרו כתואמות לאחור (מחזירות את הערך הבודד ומשמיטות את הדגל) — ומתועדות כלא-מתאימות לתצוגת UI כשחשוב לדעת אם התוצאה חלקית. נוספו גם*_exact_with_statusו-*_fuzzy_with_status: המסלולים נטולי-הסימנים שלהם אמנם לעולם אינם מתדרדרים, אבל המסלולים המנוקדים כן — ועד כהcount_exact/count_fuzzyוהמקבילים זרקו את הדגל.
Changed #
-
חתימת ספר קנונית הכוללת metadata (
computeBookFingerprint) –add_text_book/add_text_book_bytesחותמים עתה ב-contentHashחתימה שכוללת, לצד הטקסט הגולמי, גם את הכותרת, נתיב הקטגוריה, הסדר הקטלוגי, סדר הדורות וממדי הסינון (ממוינים ומנוקי-כפילויות, בקידוד קידומת-אורך) — שינוי metadata בלבד (למשל תיקון דור או מחבר) מזוהה עתה כספר שדורש אינדוקס-מחדש, במקום להשאיר את האינדקס עם facets/מיון/כותרת ישנים. צד האפליקציה חייב לעבור מ-computeContentFingerprint(טקסט בלבד, נשאר קיים) ל-computeBookFingerprintבהשוואות מולgetBookFingerprints— אחרת כל ספר יזוהה כ"השתנה" בכל הפעלה. -
הרחבת רף הווריאציות (VARIATION_CEILING_RESEARCH.md) – ארבעה שינויים משלימים:
- מסלול מילה בודדת: degrade במקום שגיאה + תקציב postings – איסוף
הטרמים נעצר בהגעה לתקציב ומחזיר את הענפים בעדיפות גבוהה שנאספו (אין יותר
query exceeded max expansions). השומר האמיתי הוא עתה תקציב postings (סכום doc_freq פר-segment, 1M), הנקרא חינם מה-streamer; תקרת מספר הטרמים נותרה כשומר זיכרון בלבד והועלתה פי 10 (מורפולוגי: 20k–50k, typo: 500, ברירת מחדל: 100). סדר הענפים הוא חוזה: כל הצורות המדויקות (מילה + חלופות) לפני כל וריאנט typo, מקובע בטסט. - typo במילה בודדת דרך אוטומט לוינשטיין – כשהדגל היחיד הוא "שגיאות כתיב", ההרחבה רצה כסריקת FST אחת לכל טוקן (מרחק 1 + שיכול) במקום ≤128 סריקות וריאנטים ליטרליים: כל שכונת מרחק-עריכה-1 (על-קבוצה של הרשימה הקודמת) במחיר נמוך פי ~100, בכפוף לתקציבי האיסוף — typo בעדיפות הנמוכה ביותר, ומדולג אם הצורות המדויקות לבדן מיצו תקציב (מילה שכיחה במיוחד). typo בשילוב מורפולוגיה/כתיב נשאר במסלול הליטרלי (האוטומט לא מרכיב wildcards). ההדגשות (display + snippets) שומרות parity.
- תקרות פרַאזה הועלו –
max_expansionsלפרַאזות: 8,192 אחיד (היה 100–5,000; ברירת המחדל של tantivy היא 16,384), ותקציבי הענפים 64/48 ענפים ו-6,000 תווים (היו 48/20 ו-1,000) — בזכות ה-vendor הבא. - vendor של tantivy-fst 0.5.0 עם
STATE_LIMIT=8192(היה 1,000; שינוי יחיד, מנוהל ב-rust/vendor/tantivy-fstדרך[patch.crates-io]) – תבניות פרַאזה מורפולוגיות אמיתיות (48 ענפים / ~800 תווים) שקרסו על תקרת ה-DFA מתקמפלות עתה; העלות זיכרון חולף בבניית השאילתה (≈4KB ל-state). - תקרות ההדגשה יושרו פרופורציונלית:
MAX_HIGHLIGHT_TERMS512→2,048, MAX_DISPLAY_PATTERN_CHARS4,000→12,000. - כיול אמפירי של התקציבים (postings budget, תקרות פרַאזה) על אינדקס אמיתי
דרך
benchmark_cli— עדיין פתוח.
- מסלול מילה בודדת: degrade במקום שגיאה + תקציב postings – איסוף
הטרמים נעצר בהגעה לתקציב ומחזיר את הענפים בעדיפות גבוהה שנאספו (אין יותר
-
גרשיים וגרש נשמרים בתוך טוקנים – ראשי-תיבות (
רמב"ם,ז"ל) ומילים עם גרש פנימי (ג'ורג',ד'אש) מאונדקסים כטרם יחיד. ׳/״ עבריים מקופלים בטרם ל-'/" ASCII, וזוג גרשים בין אותיות (רמב''ם, מוסכמת קבצים ישנים) מאוחד לגרשיים — כל צורות הדפוס מתלכדות לטרם אחד.splitQueryWordsמשקף את אותם חוקים, וההדגשות תופסות את כל שלוש צורות הדפוס (",״,''). -
חיפוש מדויק רגיש-גרשיים (מחיר מתועד) – שאילתה
רמבםללא גרשיים לא תמצארמב"םבחיפוש מדויק ובמתקדם ללא דגלים, ולהפך. הגישור קיים במקורב (גם במרחק 0, דרך הזרקת הצורה הנקייה), בדגל typo (וריאנט-מחיקה) ובדגל כתיב מלא/חסר. כמו כן, מרכאות-כציטוט בתחילת מילה (ה"מגיד) הופכות לחלק מהטוקן — שאילתתמגידמדויקת תחטיא מופע כזה; "חלק ממילה"/מקורב מגשרים. -
תיקון מפתח ה-lookup הלקסיקלי –
normalize_hebrewמוחק עתה גם"/'ASCII, כך שטוקני-גרשיים ממשיכים לקבל הרחבות מ-lexical.dbולהיתפס ב-blacklist.
Fixed #
-
תקרת הקיבוץ שומרת את הקבוצות הטובות ביותר, גלובלית – בהגעה ל-50,000 קבוצות ה-collector השמיט כל קבוצה חדשה לפי סדר סריקת המסמכים והסגמנטים — קבוצה טובה (למשל id נמוך במיון קטלוגי, או ציון גבוה ברלוונטיות) שהגיעה מאוחר נזרקה בעוד גרועות ממנה נשמרו, והעמוד הראשון היה שגוי. כעת בתקרה הקבוצה הגרועה לפי סדר המיון מפנה את מקומה לקבוצה טובה ממנה (אינדקס BTreeSet לצד המפה) — כל עמוד בטווח התקרה מדויק. הצבירה עברה למפה משותפת אחת לכל החיפוש (הסגמנטים כותבים אליה דרך buffer של 4K מסמכים) — במקום עד 50k קבוצות לכל סגמנט שהצטברו לפני המיזוג למאות MB באינדקס לא-ממוזג; הזיכרון כעת מפה גלובלית חסומה ב-50k קבוצות בתוספת buffers חסומים פר-סגמנט, ללא תלות במספר הסגמנטים. שארית ה-degrade תחת
truncated: group_countנשאר תחתית, ומונה של קבוצה שפונתה וחזרה מאבד את חבריה המוקדמים. שימו לב: המסלולים שמחזירים רקList<SearchResult>משמיטים את הדגל כמו את שאר דגלי הסטטוס — לתצוגת קיבוץ השתמשו ב-searchAndCount*/stream_with_counts. -
תקציב לספירת הסעיפים בהתאמה חלקית בטווח "תחת אותה כותרת" – מפת ספירת המילים-פר-סעיף של
mostWords/atLeastגדלה כאיחוד סעיפי כל המילים — מילים נפוצות הגיעו למאות אלפי רשומות ללא תקרה. כעת תקציב קשיח (500k סעיפים): מעבר אליו סעיפים חדשים נשמטים עםtruncated, וסעיפים שכבר נספרים ממשיכים להצטבר. -
חתימת הדה-דופ (
lineHash) כוללת אלפאנומרי לא-ASCII – ספרות ערביות-הודיות (١٥/١٦), אותיות לטיניות עם סימנים ושאר אלפאנומרי יוניקודי נשמטו מהחתימה, כך ששורות שנבדלו רק בהם אוחדו בטעות במצב "טקסט זהה". כעת כלis_alphanumericמשתתף, בקיפול רישיות יוניקודי; סף 12 האותיות נותר עברי בלבד. -
SearchPageResult.truncated– איתות "תוצאות חלקיות" גם במסלול page/count – דגל ה-truncation שנוסף ל-stream המשולב נזרק ב-search_and_count_advancedוב-search_and_countהגנרי (regex), כך שצרכן של ה-API המעומד היה מציג תוצאות וספירה חלקיות בלי אזהרה. כעתSearchPageResultנושאtruncatedבאותה סמנטיקה שלSearchStreamUpdate.truncated; המסלולים המדויק/המקורב נטולי-הסימנים תמידfalse, המנוקדים נושאים את הדגל. -
שלילה בטווח "תחת אותה כותרת" פוסלת את כל הסעיף – שאילתת השלילה בטווח
SameSectionמחזירה רק את השורות שנושאות מילת שלילה בתוך סעיף חותך, כך שב-MustNotהיא פסלה רק אותן — תוצאה חיובית בשורה אחרת של אותו סעיף שרדה בטעות. כעת נאספים ה-sectionIdשהשלילה חותכת (SectionIdsCollector) וכל שורה בסעיף כזה נחסמת (SectionFilteredQueryעלAllQuery). -
בדיקת התאימות דורשת meta.json תקין של tantivy – sidecar תקין עם
schema_versionנכון החזיר "תואם" גם כשה-meta.json של tantivy עצמו חסר או פגום — מצב שבו פתיחת האינדקס נכשלת בכל מקרה. כעת כשל קריאה/פרסור/חוסר-סכימה ב-meta.json מחזירrebuild_requiredעם הסיבה, במקום ליפול בשקט ל-"compatible". -
בדיקת התאימות משווה גם את סכימת ה-tantivy בפועל – אינדקס שנבנה בגרסת-ביניים של אותה schema_version (למשל
textעם fast field, לפני ההסרה) עבר את בדיקת הקובץ הצדדי ("3=3, תואם") אבל הפיל את פתיחת המנוע על SchemaError — והאפליקציה נפלה בשקט לאינדקס זמני ב-Temp שנבנה מחדש בכל הפעלה. כעתcheck_index_compatibilityמשווה את הסכימה השמורה ב-meta.json מול סכימת המנוע (אותה השוואה שלIndex::open_or_create) ומחזירהrebuild_requiredעל סטייה, כך שזרימת הבנייה-מחדש הרגילה מטפלת בזה. -
הטקסט השמור באינדקס משמר פיסוק (Otzaria issue #446) – נרמול ה-ingestion (
normalizeTextForIndexing/normalizePdfTextForIndexing) כבר לא מוחק פסיקים, נקודתיים, סוגריים וכו', כך שתוצאות החיפוש מציגות "עא:" ולא "עא". שוויון מילון הטרמים מול צד השאילתה נשמר ע"י "תווים שקופים" ב-HebrewTokenizer: הפיסוק ש-sanitizeQueryמוחק אינו שובר טוקן ואינו נכלל בטקסטו ("א.ב" → "אב"), וההדגשות (SnippetGenerator) נופלות נכון על הטקסט המקורי דרך ה-offsets. -
פירוק Hebrew Presentation Forms (Otzaria issue #500) – תווים מורכבים בטווח U+FB1D–U+FB4F (כגון יִ שהוצגה כ"?") מפורקים לאות בסיס + סימן בזמן האינדוקס, והסימן מוסר עם שאר הניקוד. מילים שהכילו אותם נעשות ברות-חיפוש.
Notes #
- גרסת סכימת האינדקס עלתה ל-3 (טרמים חדשים + הסרת ה-fast field): אינדקסים
קיימים ידווחו
rebuild_requiredוייבנו מחדש פעם אחת בעדכון.
0.6.7 – 2026-06-30 #
Fixed #
- פרסום מחדש עם סופי-שורה LF – הגרסה שפורסמה ב-0.6.6 הכילה את סקריפטי
cargokit (
run_build_tool.sh,build_pod.sh) עם CRLF, מה ששבר את בניית Linux/Android/macOS אצל הצרכן (/usr/bin/env: 'bash\r'). אין שינוי קוד.
0.6.6 – MagicDictionary fuzzy search – 2026-06-29 #
New #
- שילוב MagicDictionary בחיפוש מקורב (fuzzy) – ניתן לטעון
lexical.dbבזמן ריצה, והחיפוש המקורב מרחיב מונחים לצורות מורפולוגיות קשורות בלי לשנות את התנהגות החיפוש כאשר המילון לא נטען.
Improvements #
- דירוג רלוונטיות בחיפוש מקורב – תוצאות
ResultsOrder::Relevanceמקבלות מדרוג ברור יותר: התאמה מדויקת, אחריה צורה מורפולוגית מהמילון, ואחריה התאמת fuzzy רגילה. - הדגשות בחיפוש מקורב עם מילון – ההדגשה משקפת גם את הצורות המורפולוגיות
שהוזרקו מה-
lexical.db, כולל תמיכה בשאילתות מרובות מילים.
0.6.5 – Fix Apple linking – 2026-06-12 #
Fixes #
- תיקון קישור (linking) ב-macOS/iOS – הוספת
module_name = 'search_engine'בגרסה 0.6.4 שינתה אתPRODUCT_NAMEשל ה-pod, כך ש-cargokit כתב אתlibsearch_engine.aל-$PODS_CONFIGURATION_BUILD_DIR/$PRODUCT_NAMEבעוד ש--force_loadעדיין חיפש ב-${BUILT_PRODUCTS_DIR}— שתי תיקיות שונות, והבנייה נפלה עםlibrary 'libsearch_engine.a' not found. תוקן ביישורoutput_filesו-OTHER_LDFLAGSל-${PODS_CONFIGURATION_BUILD_DIR}/${PRODUCT_NAME}.
0.6.3 – Line Endings Fix (Republish) – 2026-06-12 #
Fixes #
- תיקון סופי של סיומות השורה (CRLF) בארכיון שפורסם – למרות שגרסה 0.6.2 נועדה
לתקן את הבעיה, הארכיון שפורסם בפועל ל-pub.dev עדיין הכיל CRLF בסקריפטי
cargokit (
build_pod.sh,run_build_tool.sh), כי לא ניתן לפרסם מחדש גרסה קיימת ב-pub.dev. כתוצאה מכך הבנייה ב-macOS המשיכה ליפול עםset: - invalid option. גרסה זו נארזת מחדש ממכונת macOS עם LF בלבד. אין שינוי קוד.
0.6.2 – Line Endings Fix – 2026-06-12 #
Fixes #
- תיקון סיומות שורה (CRLF) בארכיון שפורסם – גרסה 0.6.1 פורסמה מ-Windows עם
core.autocrlf=true, וסקריפטי ה-shell של cargokit (build_pod.sh,run_build_tool.sh) נארזו עם CRLF — מה ששבר את הבנייה ב-macOS (set: - invalid option), Android ו-Linux (exit 127 בגלל shebang פגום). אין שינוי קוד; פרסום מחדש עם LF בלבד. נוסף.gitattributesשמונע הישנות.
0.6.1 – Fuzzy Highlight Fix – 2026-06-12 #
Fixes #
- הדגשות בחיפוש מקורב (fuzzy) – תוצאות
searchFuzzy/searchAndCountFuzzy/searchFuzzyStream/searchFuzzyTermsחזרו עד כה ללא הדגשה כלל, כיFuzzyTermQueryמבוסס-אוטומט ואינו חושף מונחים למחולל ה-snippets. כעת מונחי ההדגשה ממומשים ממילון האינדקס דרך אותו אוטומט לוינשטיין שהחיפוש משתמש בו (כמו במצב advanced), כך שגם וריאנטים במרחק עריכה — ולא רק המילה שהוקלדה — מודגשים בתוצאות. - אימות
max_distanceמראש – ערךmax_distanceמחוץ לטווח 0–2 ב-searchFuzzyגורם כעת לשגיאה מפורשת לפני בניית השאילתה, במקום כשל לא צפוי בהמשך.
0.6.0 – Mode-Specific Search & Hardening – 2026-06-11 #
Breaking Changes #
ReferenceSearchEngine הוסר
המנוע הייעודי לחיפוש הפניות (ReferenceSearchEngine, ReferenceSearchResult,
ReferenceDocumentInput) הוסר מה-API הציבורי. אפליקציות שהשתמשו בו צריכות
להסיר את הקריאות לפני עדכון התלות.
חבילה שונתה ל-otzaria_search_engine
החבילה, ה-export הראשי וה-podspecs של iOS/macOS שונו מ-tantivy_search_engine
ל-otzaria_search_engine (שם ה-crate הפנימי search_engine נשאר).
search() – חריגה מ-maxExpansions במונח בודד מחזירה שגיאה
עד כה התקרה נאכפה רק בשאילתות מרובות מונחים; מונח regex בודד רץ ללא הגבלה.
כעת חריגה מחזירה שגיאה בכל המקרים, בדומה להתנהגות של RegexPhraseQuery.
New APIs #
- חיפוש לפי מצב –
searchExact/searchFuzzy/searchAdvanced(+countExact/Fuzzy/Advanced,searchAndCountExact/Fuzzy/Advanced,searchExactStream/searchFuzzyStream/searchAdvancedStream). המצב המתקדם מקבלsearchOptions/alternativeWords/customSpacingומריץ את כל לוגיקת השאילתות העברית (קידומות, סיומות, כתיב מלא/חסר, סובלנות לשגיאות) ב-Rust (מודולhebrew_query). - הדגשות בתוצאות regex/advanced – מונחי ההדגשה ממומשים ממילון האינדקס דרך אותו אוטומט שהחיפוש משתמש בו, כך שכל וריאציה מורפולוגית שתאמה מודגשת.
checkIndexCompatibility(path)– בדיקת תאימות אינדקס (sidecarotzaria_index_meta.json+ נפילה חזרה להשוואת הסכמה המלאה של Tantivy).- קריאת תוכן האינדקס –
countDocumentsByFilePath()ו-getIndexedFilePaths()לשחזור מצב האינדוקס ישירות מהאינדקס. searchAndCount/searchStream– ספירה ותוצאות במעבר יחיד, והזרמת תוצאות בנתחים;searchקיבלoffset(חובה) ו-highlight(אופציונלי).
Fixes & Hardening #
- שאילתה ריקה (או סימני פיסוק בלבד) מחזירה אפס תוצאות בכל המצבים — ולא panic במצב advanced או כל המסמכים במצב fuzzy.
- רשימת facets ריקה כבר לא מאפסת תוצאות בנתיבי ה-regex/advanced; facet לא תקין מחזיר שגיאה במקום panic.
- שאילתות advanced מנורמלות כמו האינדקס (הסרת ניקוד + lowercase), כך שטקסט מנוקד שהודבק כבר לא מחזיר אפס תוצאות בשקט.
SearchEngine.newלא קורס כשנעילת ה-writer תפוסה — נפתח לקריאה והכתיבה הראשונה מנסה שוב; הודעות שגיאה ברורות לסכמה לא תואמת.optimize()שומר (commit) שינויים ממתינים במקום לזרוק אותם.- בדיקת התאימות לאינדקסים ישנים משווה את הסכמה המלאה ולא רק את שדה
id.
Performance #
- מתודות החיפוש הישנות עברו ל-
&self— חיפושים מקבילים לא מסתנכרנים יותר מאחורי נעילת כתיבה. SnippetGeneratorנוצר פעם אחת לכל stream (ולא לכל chunk); תקציב מונחי ההדגשה מתחלק שווה בין מילות השאילתה.
Packaging / CI #
url_prefixשל הבינארים המקומפלים מצביע על הריפו הקנוני (otzaria/otzaria_search_engine); סודות ה-CI מוגבלים ל-steps החותמים.
0.5.0 – Bridge Expansion (Tantivy 0.26 / FRB 2.12) – 2026-05-02 #
Breaking Changes #
Schema Change: id field is now INDEXED
Affects: All existing indices built with the previous version.
שדה id שודרג מ-STORED | FAST ל-STORED | FAST | INDEXED.
נדרש: rebuild מלא של כל האינדקסים הקיימים.
למה: בלי INDEXED, פעולות delete_term לא עובדות על השדה הזה, ולכן deleteDocumentById, upsertDocument ו-upsertDocumentsBatch לא היו אפשריות.
Tantivy 0.26: TopDocs no longer implements Collector directly
Affects: rust/src/api/search_engine.rs, rust/src/api/reference_search_engine.rs
ב-Tantivy 0.26, TopDocs הפסיק לממש את ה-trait Collector ישירות.
כדי לקבל collector לתוצאות ממוינות לפי ציון רלוונטיות, חובה לקרוא ל-.order_by_score().
// לפני (Tantivy < 0.26) – התקמפל אבל כעת שגוי:
let collector = TopDocs::with_limit(100);
searcher.search(&query, &collector)?;
// אחרי (Tantivy 0.26) – חובה:
let collector = TopDocs::with_limit(100).order_by_score();
searcher.search(&query, &collector)?;
תוקן בשני המנועים.
search() signature – נוספו פרמטרים
// לפני:
Future<List<SearchResult>> search({
required List<String> regexTerms,
required List<String> facets,
required int limit,
required int slop,
required int maxExpansions,
required ResultsOrder order,
});
// אחרי:
Future<List<SearchResult>> search({
required List<String> regexTerms,
required List<String> facets,
required int limit,
required int offset, // ← חדש (required, אין ברירת מחדל)
required int slop,
required int maxExpansions,
required ResultsOrder order,
HighlightConfig? highlight, // ← חדש (אופציונלי)
});
Migration: הוסף offset: 0 לכל קריאות search() קיימות.
createQuery / createSearchQuery הוסרו
פונקציות אלו חשפו BoxQuery ו-Index כ-opaque types בלי שום API ציבורי להפעלתן מ-Dart. הן היו dead-ends ממשיים.
Migration: אין תחליף ישיר – השתמש ב-search(), searchAndCount() או searchFuzzy().
New APIs – SearchEngine #
Write
| Method | תיאור |
|---|---|
deleteDocumentById(id) |
מחיקה מדויקת לפי מזהה. מחליף את removeDocumentsByTitle. |
upsertDocument(id, ...) |
מחיקת ישן + הוספת חדש בפעולה אחת. מניעת כפילויות. |
addDocumentsBatch(docs) |
הוספת רשימת מסמכים ב-FFI call אחד, ללא delete. מיועד לטעינה ראשונית. |
upsertDocumentsBatch(docs) |
כמו batch אבל עם delete-before-add לכל מסמך. מיועד לעדכונים. |
rollback() |
ביטול כל השינויים מאז ה-commit האחרון. |
Read
| Method | תיאור |
|---|---|
getDocumentById(id) |
שליפת מסמך יחיד לפי ID. מחזיר SearchResult? עם טקסט גולמי (ללא snippet). |
searchAndCount(...) |
חיפוש + ספירה כוללת ב-pass אחד דרך Tantivy (tuple collector). מחזיר SearchPageResult. |
getFacetCounts(regexTerms, facets, facetPrefix, ...) |
ספירת תוצאות לפי קטגוריה תחת prefix נתון. שימושי ל-drill-down בממשק. |
searchFuzzy(terms, facets, limit, offset, maxDistance, order, highlight?) |
חיפוש מקורב אמיתי (Levenshtein) על מילות טקסט רגילות. maxDistance: 0=מדויק, 1–2=מקורב. |
searchStream(regexTerms, ..., chunkSize) |
מחזיר Stream<List<SearchResult>> ב-Dart. שלב ה-TopDocs (דירוג) מסתיים לפני פליטת ה-chunk הראשון; שלב שליפת המסמכים ויצירת ה-snippets מתבצע באופן מוגדר. שימושי כשה-limit גדול ויצירת snippets היא צוואר הבקבוק. |
Operational
| Method | תיאור |
|---|---|
optimize() |
מיזוג כל הסגמנטים לאחד. להריץ ברקע לאחר הרבה עדכונים/מחיקות. |
getDocumentCount() |
סך כל המסמכים באינדקס. |
getSegmentCount() |
מספר הסגמנטים הנוכחי. גבוה = כדאי להריץ optimize(). |
Structs חדשים
class DocumentInput {
final BigInt id;
final String title;
final String reference;
final String topics;
final String text;
final BigInt segment;
final bool isPdf;
final String filePath;
}
class HighlightConfig {
final String highlightPrefix; // ברירת מחדל: "<font color=red>"
final String highlightPostfix; // ברירת מחדל: "</font>"
final int maxChars; // ברירת מחדל: 800
}
class SearchPageResult {
final int totalCount;
final List<SearchResult> results;
final bool truncated; // תוצאות/ספירה חלקיות (חריגה מתקציב הרחבת מילה יחידה)
}
class FacetCount {
final String path;
final int count;
}
New APIs – ReferenceSearchEngine #
| Method | תיאור |
|---|---|
deleteDocumentById(id) |
מחיקה לפי ID |
upsertDocument(id, ...) |
עדכון לפי ID |
addDocumentsBatch(docs) |
batch הוספה |
upsertDocumentsBatch(docs) |
batch עדכון |
rollback() |
ביטול שינויים |
Struct חדש:
class ReferenceDocumentInput {
final BigInt id;
final String title;
final String reference;
final String shortRef;
final BigInt segment;
final bool isPdf;
final String filePath;
}
Bug Fixes #
IndexReader נוצר מחדש בכל חיפוש (SearchEngine)
לפני: כל קריאה ל-search() / count() / countByBook() פתחה IndexReader חדש מהדיסק – פעולה יקרה מאוד.
// לפני (בעייתי – קורא metadata מהדיסק בכל חיפוש):
let searcher = index.reader()?.searcher();
אחרי: IndexReader נשמר ב-struct ומשתמשים בו לכל החיפושים. commit() מרענן אותו.
// אחרי (מהיר – reader כבר בזיכרון):
let searcher = self.index_reader.searcher();
השפעה: שיפור ביצועים משמעותי בחיפוש, במיוחד תחת עומס.
ReferenceSearchEngine התעלם מה-IndexReader הקיים
גם ב-ReferenceSearchEngine היה index_reader ב-struct אבל לא השתמשו בו בפועל. תוקן.
Notes #
removeDocumentsByTitleנשמר לתאימות אחורה אבל לא מומלץ לשימוש חדש. השתמש ב-deleteDocumentById.- fuzzy קיים לעומת חדש: הקוד הנוכחי באפליקציה משתמש ב-
slopעם מילים רגילות כ"חיפוש מקורב".searchFuzzy()מוסיף חיפוש מקורב אמיתי ברמת ה-Levenshtein – מוצא מסמכים גם כשיש שגיאות כתיב, כתיב מלא/חסר, וכו'. searchStreamvs pagination:searchStreamשולח תוצאות ב-chunks – שלב הדירוג (TopDocs) מסתיים לפני ה-chunk הראשון, אבל שליפת המסמכים ויצירת ה-snippets מתפצלים. ל-pagination רגילה,search()עםoffsetמספיקה לרוב המקרים.
0.0.1 #
- Initial release.