:summary: על מה מחפש כל סוג בתיבת החיפוש הגלובלי בעמוד הקבצים, איך הוא מתאים ומדרג, מה קורה כשהמנוע לא מוצא כלום ועובר לגיבוי במונגו, ובמה זה שונה מהחיפוש לפי שם באותו עמוד ומ-/search בבוט. ובנוסף: איך נבנה אינדקס החיפוש בתיעוד, ועמוד האדמין שלו. .. _webapp-global-search: חיפוש גלובלי — סוגי החיפוש =========================== איפה זה בממשק ------------- תיבת החיפוש הגלובלי יושבת בראש עמוד הקבצים (``/files``). ליד התיבה יש רשימה נפתחת (``#searchType`` ב-``webapp/templates/files.html``) שבה בוחרים את סוג החיפוש, וברירת המחדל היא **תוכן**. הדפדפן (``performGlobalSearch`` ב-``webapp/static/js/global_search.js``) שולח את החיפוש ל-``POST /api/search/global`` (``api_search_global``). שני סוגים נשלחים ל-endpoint נפרד: **חיפוש חכם** (:ref:`global-search-smart`) ו\ **תיעוד** (:ref:`global-search-docs`). .. _global-search-types: סוגי החיפוש ----------- .. list-table:: :header-rows: 1 :widths: 14 14 32 40 * - תווית בממשק - ערך - על מה מחפש - איך מתאים * - תוכן - ``content`` - תוכן הקוד בלבד - מחרוזת רצופה, כמו שהוקלדה * - חיפוש חכם - ``semantic`` - קטעי קוד לפי משמעות - חיפוש היברידי: וקטורים + טקסט * - טקסט - ``text`` - מילים מתוך תוכן הקוד - כל מילה בנפרד, גם כחלק ממילה ארוכה יותר * - Regex - ``regex`` - תוכן הקוד בלבד - ביטוי רגולרי * - מטושטש - ``fuzzy`` - שם הקובץ, תוכן הקוד והתגיות - דמיון משוער, גם עם שגיאות הקלדה * - פונקציות - ``function`` - שמות פונקציות שחולצו מהקוד - השאילתה מופיעה בתוך שם הפונקציה * - תיעוד - ``docs`` - סעיפים באתר התיעוד (לאדמין בלבד) - לפי משמעות: חיפוש וקטורי באינדקס התיעוד "תוכן", "טקסט", "Regex", "מטושטש" ו"פונקציות" לא מבחינים בין אותיות גדולות לקטנות. .. important:: **"תוכן", "טקסט", "Regex" ו"פונקציות" לא מחפשים בשם הקובץ** — הם מסתכלים על הקוד בלבד. שם הקובץ נכנס רק ב"מטושטש", ובחיפוש החכם (כחלק מהטקסט שממנו נבנה הווקטור של כל קטע). מי שמחפש קובץ לפי השם שלו צריך לבחור "מטושטש", או להשתמש בחיפוש לפי שם שבאותו עמוד (:ref:`global-search-not-to-confuse`). יש חריג אחד: כשהסוג שבחרתם לא מוצא כלום, החיפוש עובר לגיבוי שכן מסתכל גם על שמות — ראו :ref:`global-search-fallback`. .. _global-search-content: תוכן ~~~~ המימוש הוא ``_content_search`` ב-``search_engine.py``. הוא עובר על כל הקבצים של המשתמש במנות (``SEARCH_PAGE_SIZE``), ובכל קובץ סופר כמה פעמים השאילתה מופיעה **כמחרוזת רצופה** בתוך הקוד. אין פירוק למילים: ``get user`` נמצא רק איפה שכתוב בדיוק ``get user``, עם הרווח. הדירוג לפי **צפיפות**: כמה הופעות יש ביחס לאורך הקובץ. קובץ קצר עם שתי הופעות יעלה מעל קובץ ארוך עם אותן שתי הופעות. הסוג הזה לא משתמש באינדקס בזיכרון ולא באינדקס הטקסט של מונגו — הוא קורא את הקוד עצמו. .. _global-search-text: טקסט ~~~~ המימוש הוא ``_text_search``. השאילתה מתפרקת למילים, ומילות עצירה (``stop_words``) ומילים של תו אחד נזרקות. כל מילה שנשארת מחפשת באינדקס המילים שבזיכרון (``SearchIndex.word_index``), שנבנה מתוך תוכן הקוד בלבד. כל מילה מתאימה בשתי דרכים: - **התאמה מדויקת** — המילה קיימת בקוד כמו שהיא. מקבלת ניקוד גבוה יותר. - **התאמה חלקית** — המילה היא תחילית של מילה בקוד, או מופיעה בתוכה. למשל ``user`` מתאים גם ל-``username`` וגם ל-``get_user_id``. הניקוד של קובץ הוא סכום הניקוד של כל המילים שמצאו אותו. סדר המילים בשאילתה לא משנה, ואין דרישה שכל המילים יופיעו: קובץ שמתאים למילה אחת בלבד עדיין נכנס לתוצאות. .. _global-search-regex: Regex ~~~~~ המימוש הוא ``_regex_search``. התחביר הוא של מודול ``re`` בפייתון, בלי הבחנה בין אותיות גדולות לקטנות, ו-``^`` ו-``$`` מתייחסים לתחילת שורה ולסוף שורה ולא לקובץ כולו. הדירוג לפי מספר ההתאמות בקובץ. לפני שהחיפוש רץ, ``_is_regex_safe`` (בתוך ``api_search_global``) דוחה תבניות שעלולות לתקוע את השרת: תבנית ארוכה מ-200 תווים, ``.*`` שמופיע פעמיים, כמתים מקוננים כמו ``(a+)+``, כמתים מספריים גדולים, ותבנית שלא מתקמפלת. תבנית שנדחתה מחזירה שגיאה ולא רצה. .. _global-search-fuzzy: מטושטש ~~~~~~ המימוש הוא ``_fuzzy_search``, עם ``fuzz.partial_ratio`` מ-``rapidfuzz``. לכל קובץ מחושב ציון דמיון בנפרד לשם הקובץ, לתוכן הקוד ולתגיות (מחוברות ברווח), והקובץ מקבל את **הציון הגבוה** מבין השלושה. קובץ נכנס לתוצאות רק אם הציון שלו הוא לפחות 60 מתוך 100. הסף הזה כתוב ישירות בתוך ``_fuzzy_search``. ``partial_ratio`` מחפש את הקטע הכי דומה בתוך הטקסט ולא משווה את כל הטקסט. לכן שאילתה קצרה מוצאת קובץ ארוך שמכיל משהו דומה לה. .. _global-search-function: פונקציות ~~~~~~~~ המימוש הוא ``_function_search``. הוא מחפש באינדקס הפונקציות שבזיכרון (``SearchIndex.function_index``), שנבנה משמות הפונקציות ש-``code_processor.extract_functions`` מחלץ מכל קובץ. פונקציה מתאימה אם השאילתה מופיעה **בתוך** השם שלה. כל פונקציה מתאימה מוסיפה לניקוד של הקובץ שלה, לפי כמה השם שלה קרוב לשאילתה כולה: בשאילתה ``save``, פונקציה בשם ``save`` מוסיפה יותר מ-``save_all_files_to_disk``. לכן קובץ עם כמה פונקציות מתאימות יכול לעלות מעל קובץ עם התאמה מדויקת אחת. מה שהחילוץ לא מזהה כפונקציה לא יימצא כאן. במקרה כזה אפשר לחפש את השם עם "תוכן". .. _global-search-smart: חיפוש חכם ~~~~~~~~~ "חיפוש חכם" לא עובר דרך ``/api/search/global``. הדפדפן שולח אותו ל-``POST /api/search/semantic`` (``api_semantic_search``), שמריץ את הפונקציה ``semantic_search`` ב-``search_engine.py``. זה חיפוש היברידי: חיפוש וקטורי על קטעי קוד (``snippet_chunks``) יחד עם חיפוש טקסט של Atlas, והתוצאות של שניהם מאוחדות. הפרטים הפנימיים, כולל שני רפי הציון, נמצאים ב-:doc:`/workflows/search-flow`. מסנן השפה: בחיפוש חכם נשלחת **רק השפה הראשונה** מבין השפות שסימנתם. יש לו גיבוי גם בשרת וגם בדפדפן, וחשוב להכיר אותם, כי בשניהם תקבלו תוצאות אבל לא של חיפוש לפי משמעות: - **בשרת** — כש-``SEMANTIC_SEARCH_ENABLED`` כבוי, כששירות ה-embeddings לא זמין, כשיצירת ה-embedding לשאילתה נכשלה, או כשהאוסף ``snippet_chunks`` לא זמין, ``semantic_search`` מחזיר במקום זה תוצאות של **מטושטש** (``_fallback_text_search``). - **בדפדפן** — כשהבקשה ל-``/api/search/semantic`` נכשלת, ``performGlobalSearch`` שולח את אותה שאילתה ל-``/api/search/global`` עם ``semantic`` כסוג. ``api_search_global`` לא מכיר את הערך הזה וממפה אותו ל-**תוכן**. האפשרות "חיפוש חכם" מופיעה ברשימה תמיד, גם כשהחיפוש הסמנטי כבוי. .. _global-search-docs: תיעוד ~~~~~ האפשרות "תיעוד" מופיעה ברשימה רק לאדמין, וגם לו לא במצב צפייה כמשתמש (``user_is_admin`` בתבנית). היא מחפשת בסעיפים של אתר התיעוד, ולא בקבצים. הדפדפן שולח אותה ל-``POST /api/search/docs`` (``api_search_docs``), ואין מעבר לחיפוש אחר כשמשהו נכשל: ``/api/search/global`` היה ממפה סוג שהוא לא מכיר ל"תוכן", ותוצאות של קבצים היו נראות כמו תשובה מהתיעוד. במקום זה מוצגת הודעה עם הסיבה. החיפוש עצמו הוא ``search_docs`` ב-``services/docs_search_service.py``: - **השאלה מוטמעת במודל שהאינדקס נבנה בו**, ולא במודל שבהגדרות כרגע. אחרי החלפת מודל, ועד שמעבר של האינדקס מסיים להטמיע מחדש, השאלה והנתחים עדיין באותו מרחב וקטורי. - **האינדקס הווקטורי נבדק לפני ההטמעה.** כשהוא חסר, במצב שגיאה, עוד לא מוכן לחיפוש או במימד אחר, התשובה היא ``index_unavailable`` עם הסיבה (``reason``), ולא "לא נמצאו תוצאות". - ``$vectorSearch`` מסנן כבר בתוך האינדקס לנתחים של אותו מודל. נתחים של אותו סעיף מתאחדים לציון הגבוה שבהם, והמארקדאון נשלף רק לסעיפים שמוחזרים. - **סעיף ארוך לא דוחק החוצה את השאר.** הסבב הראשון מבקש ``CHUNKS_PER_RESULT`` נתחים לכל סעיף שמוחזר. כשהנתחים נופלים בפחות סעיפים ממה שהתבקש והחיפוש מילא את כל המכסה — כי סעיף ארוך תפס את רובה — המכסה מוכפלת: עד שיש מספיק סעיפים, עד שהחיפוש מחזיר פחות נתחים ממה שביקשנו, או עד התקרה של Atlas (``MAX_NUM_CANDIDATES``). כשהסבב הראשון מוצא מספיק סעיפים, אין סבב נוסף. - לכל שלב יש תקרה משלו: ``QUERY_EMBED_DEADLINE_SECONDS`` להטמעת השאלה, ו-``SEARCH_DB_TIMEOUT_SECONDS`` לכל אחד משני החלקים מול המסד. ההמתנה של הדפדפן (``DOCS_SEARCH_DEADLINE_MS`` ב-``global_search.js``) ארוכה מסכומן, ו-``tests/test_docs_search_files_page.py`` משווה ביניהן. - שאלה ארוכה מ-``QUERY_MAX_BYTES`` בתים של UTF-8, אותו תקציב כמו נתח של התיעוד, נדחית ב-``query_too_long`` ולא נחתכת. - אין לה מגבלת קצב משלה: אדמינים פטורים מהמגבלות של Flask-Limiter (``_is_rate_limit_exempt_user`` ב-``webapp/app.py``). הקצב מול Gemini נשמר בשער של התהליך, כמו במעבר של האינדקס. כל סעיף מוצג בכרטיס, מימין לשמאל, כמו בתצוגת ה-Markdown של דפדפן הקוד: אותו רינדור (``MarkdownLiveRenderer``), אותו עיצוב (``markdown-preview.css``) ואותו משטח (:ref:`code-browser-markdown-preview`). מעל הסעיף מופיעים קובץ המקור שלו בריפו (``source_path`` בתשובה, למשל ``docs/webapp/global-search.rst``), שביל הכותרות והציון, ומתחתיו "פתח באתר התיעוד" (בלשונית חדשה, עם ``rel="noopener"``) ו"העתק כמארקדאון". קובץ המקור מוצג כטקסט, ולא כקישור. ברשומה של עדכון ביומן עדכונים (:ref:`docs-index`) הכותרת היא הפתיח של העדכון, ו"פתח באתר התיעוד" קופץ אליו ולא לראש היום: אחרי העוגן של היום, הכתובת נושאת את הפתיח (השדה ``link_text`` של הרשומה, דרך ``text_fragment`` ב-``services/docs_search_contract.py``). את הקפיצה עושה הדפדפן, לפי התקן של URL Fragment Text Directives (https://wicg.github.io/scroll-to-text-fragment/), והוא מחפש את הפתיח בלי הבדל בין אותיות גדולות לקטנות ובלי ניקוד. ב-Chromium 141, על האתר עצמו (11.10.2026), הקפיצה מביאה את העדכון למסך. כשהפתיח כבר אינו בעמוד, למשל כי העדכון נערך אחרי שהאינדקס נבנה, הדפדפן מגיע לראש היום, כמו בקישור בלי קפיצה. דפדפן שאינו תומך בתחביר הזה לא קופץ לעדכון, ולאן הוא מגיע לא נמדד. סעיף ארוך מוצג בהתחלה רק בחלקו העליון, דוהה בתחתית, עם "הצג את כל הסעיף". כשהסעיף פתוח הכפתור מציע לכווץ אותו: הטקסט, האייקון ו-``aria-expanded`` נגזרים יחד מהמצב של הסעיף (``syncDocsExpandButton`` ב-``global_search.js``). הכפתור יושב בתחתית הסעיף הפתוח, ולכן מגיעים אליו בגלילה. אחרי הכיווץ, כשראש הכרטיס מעל המסך, המסך חוזר לראש הכרטיס, גם כשהכרטיס המכווץ גבוה מהמסך. כרטיס שראשו כבר על המסך לא זז. מזהים בתוך כרטיס מקבלים קידומת של הכרטיס, כדי ששני סעיפים עם אותה כותרת לא ייתנו אותו ``id`` בעמוד. שורת המצב מעל הכרטיסים אומרת לאיזה קומיט של האתר האינדקס נכון, ומקשרת לעמוד האדמין של האינדקס (:ref:`docs-index`). כשהאינדקס לא מעודכן לפריסה האחרונה, או שנבנה בכללים קודמים (``index_complete`` שקר, ראו "שלם או חלקי" ב-:ref:`docs-index`), השורה אומרת שייתכן שחסרים בו סעיפים. מספר התוצאות הוא ברירת המחדל של ה-API (``DEFAULT_LIMIT``). מיון, מספר תוצאות בעמוד וסינון לפי שפה לא חלים כאן, ולכן הפקדים שלהם מושבתים כשבוחרים "תיעוד". חיפוש שהתחיל אחרון הוא שמוצג. תשובה של חיפוש קודם, שמגיעה אחרי שחיפוש חדש כבר התחיל, לא מוצגת. הכלל הזה חל על כל סוגי החיפוש בתיבה, כי Enter, הצעה ומעבר עמוד מתחילים חיפוש חדש גם כשהכפתור מושבת. .. _global-search-fallback: כשהסוג שבחרתם לא מוצא כלום -------------------------- ``api_search_global`` לא קורא למנוע ישירות, אלא דרך ``_safe_search`` ב-``webapp/app.py``. אם המנוע מחזיר **רשימה ריקה**, או זורק חריגה, ``_safe_search`` מריץ חיפוש גיבוי ישירות במונגו: - **ב-Regex** — ``$regex`` על תוכן הקוד בלבד. - **בכל שאר הסוגים** — ``$text`` על אינדקס הטקסט ``search_text_idx``. האינדקס הזה (מוגדר ב-``database/manager.py``) כולל את **שם הקובץ, התיאור, התגיות והקוד**. כלומר בגיבוי חיפוש "תוכן" או "טקסט" **כן** יכול למצוא קובץ לפי השם שלו. הקוד לא מעביר ל-``$text`` שפה או רגישות לאותיות, ולכן חלות ברירות המחדל של מונגו. לפי `התיעוד של MongoDB על $text `_: - הוא מתאים **מילה שלמה** (אחרי stemming) ולא חלק ממילה: ``blue`` לא מוצא ``blueberry``. - הוא לא מבחין בין אותיות גדולות לקטנות. - כמה מילים בשאילתה מחוברות ב-**או**: ``get user`` מחזיר כל קובץ שיש בו ``get`` או ``user``. מה שונה בתוצאות של הגיבוי: - לכל התוצאות אותו ציון, כך שהמיון לפי רלוונטיות לא אומר כלום. - הסדר הוא תמיד מהקובץ שעודכן לאחרונה, בלי קשר למיון שבחרתם. - רק מסנני השפה והתגיות מוחלים. .. _global-search-limits: מגבלות ------ .. list-table:: :header-rows: 1 :widths: 30 35 35 * - מגבלה - ערך - איפה נאכפת * - אורך שאילתה - 500 תווים - ``api_search_global`` ו-``api_semantic_search`` * - תבנית Regex - ראו :ref:`global-search-regex` - ``_is_regex_safe`` * - קצב בקשות - הכלל ``"30 per minute"`` על כל אחד משני ה-endpoints, לפי המשתמש המחובר. אדמינים ומשתמשים ברשימת ההחרגה לא מוגבלים - ``_search_limiter_decorator``; המפתח ``_client_rate_key``, וההחרגה ``_is_rate_limit_exempt_user`` * - תוצאות בעמוד - בין 1 ל-100 (בממשק: 5, 10, 20 או 50) - ``api_search_global``; הרשימה ``#resultsPerPage`` ב-``files.html`` .. _global-search-index: האינדקס בזיכרון --------------- "טקסט" ו"פונקציות" קוראים מאינדקס שנבנה בזיכרון של התהליך (``SearchIndex``). הבנייה היא סריקה של כל קבצי המשתמש, כולל הקוד. היא קורית בחיפוש הראשון מהסוגים האלה, וחוזרת כשהאינדקס מתיישן (``SearchIndex.should_rebuild``). "תוכן", "Regex" ו"מטושטש" לא קוראים מהאינדקס, ולכן גם לא בונים אותו — אלא אם ``SEARCH_MEMORY_INDEX_EAGER_BUILD`` דלוק. משתני הסביבה ששולטים בזה (את ברירות המחדל שלהם מתעד :doc:`/environment-variables`): - ``SEARCH_MEMORY_INDEX_ENABLED`` — כשהוא כבוי, האינדקס נשאר ריק. "טקסט" ו"פונקציות" לא ימצאו כלום במנוע, ולכן תמיד יעברו לגיבוי (:ref:`global-search-fallback`). בגיבוי ההתאמה החלקית של "טקסט" נעלמת, ו"פונקציות" כבר לא מחפש בשמות פונקציות. - ``SEARCH_MEMORY_INDEX_EAGER_BUILD`` — כשהוא דלוק, האינדקס נבנה גם בחיפושי "תוכן", "Regex" ו"מטושטש". הסיבה היחידה להדליק אותו היא ההשלמה האוטומטית (:ref:`global-search-suggestions`), שקוראת מהאינדקס רק אם הוא כבר קיים. - ``SEARCH_PAGE_SIZE`` — כמה קבצים נקראים בכל מנה, בסוגים שסורקים את הקבצים עצמם. .. _global-search-cache: קאש --- ``api_search_global`` שומר את התוצאות בקאש דרך ``cache.set_dynamic`` עם סוג התוכן ``search_results``. מפתח הקאש נבנה מהמשתמש, מהשאילתה, מהסוג, מהמסננים, מהמיון, מהעמוד ומכמות התוצאות בעמוד. כלומר אותו חיפוש שחוזר על עצמו מגיע מהקאש, וכל שינוי באחד מהם הוא חיפוש חדש. זמן התפוגה נקבע ב-``DynamicTTL.calculate_ttl`` לפי סוג התוכן ולפי ההקשר. ההסבר על המנגנון נמצא ב-:doc:`/webapp/advanced-caching`. .. _global-search-suggestions: השלמה אוטומטית -------------- כשמקלידים בתיבה, הדפדפן מבקש הצעות מ-``GET /api/search/suggestions`` (``api_search_suggestions``), שקורא ל-``suggest_completions``. ההצעות מגיעות מהמקורות האלה: - שמות קבצים, תגיות (בצורת ``#tag``) ושפות, מ-``autocomplete_manager``. - חיפוש ``$text`` במונגו, רק כשהשאילתה כבר מכילה מילה שלמה. - מילים ושמות פונקציות מהאינדקס שבזיכרון, **רק אם הוא כבר נבנה**. ההשלמה לא בונה אותו בעצמה. לחיצה על הצעה מכניסה אותה לתיבה ומריצה חיפוש מהסוג שבחור ברשימה. .. _global-search-not-to-confuse: לא לבלבל עם ----------- - **החיפוש לפי שם בעמוד הקבצים.** זו תיבה אחרת באותו עמוד (שדה ``q``, והטקסט "חיפוש לפי שם, תיאור או תגית..."). היא שולחת את הדף מחדש עם ``?q=``, ומחפשת בשם, בתיאור ובתגיות של הגרסה האחרונה של כל קובץ — **לא בקוד**: כל מילה בשאילתה צריכה להופיע באחד מהשלושה, גם כחלק ממילה, ובלי הבדל בין אותיות גדולות לקטנות. הכלל ב-``_files_search_file_match`` ב-``webapp/app.py``, והוא חל בכל התצוגות של העמוד. אין לה סוגי חיפוש, והיא לא משתמשת ב-``search_text_idx``. עד אוקטובר 2026 היא הריצה ``$text`` על האינדקס הזה, ולכן, בניגוד לטקסט שבה, מצאה גם קבצים שהמילה רק בקוד שלהם. - **``/search`` בבוט.** הבוט לא משתמש בסוגי החיפוש שבעמוד הזה. יש שני מימושים של ``search_command``, ואף אחד מהם לא קורא למנוע: זה שב-``main.py`` מזהה תגית (``#tag``) או שם של שפה, ובכל מקרה אחר קורא ל-``db.search_code``. זה שב-``bot_handlers.py`` רק מפרק את השאילתה ומחזיר תצוגת הדגמה. .. _docs-index: אינדקס התיעוד ------------- החיפוש בתיעוד (הסוג "תיעוד" בתיבה, :ref:`global-search-docs`) נשען על אינדקס של הסעיפים באתר התיעוד. האינדקס נבנה בוובאפ מתוך קובץ הייצוא של האתר (``_export/sections.json``, ראו :doc:`/doc-authoring`), ולא ממקור ה-rst, ומתעדכן אחרי כל פריסה של האתר. המימוש הוא ``services/docs_index_service.py``. השמות שהכותב והחיפוש חייבים להסכים עליהם — האוספים, השדות, האינדקס הווקטורי וכתובת האתר — יושבים ב-``services/docs_search_contract.py``. מה נכנס לאינדקס ~~~~~~~~~~~~~~~ - **סעיף** (האוסף ``docs_sections``) — מה שכרטיס בתוצאות יציג: העמוד, העוגן, שביל הכותרות והמארקדאון של הסעיף. - **נתח** (האוסף ``docs_chunks``) — הטקסט שנשלח להטמעה והווקטור שלו: שביל הכותרות, שורה ריקה, והגוף. סעיף ארוך מ-``CHUNK_MAX_BYTES`` (``services/chunking_service.py``) מתחלק ב-``split_code_to_chunks``, ושביל הכותרות נספר בתוך התקציב. - **עמוד** (האוסף ``docs_pages``) — עמוד rst שלם, למסך התצוגה המרונדרת בדפדפן הקוד (:ref:`code-browser-rst-preview`): כל הסעיפים של העמוד לפי הסדר בקובץ, כל אחד עם הכותרת שלו ברמה שלו (``page_markdown`` ו-``build_pages``). בעמוד נכנסים כל הסעיפים, גם אלה שהחיפוש מדלג עליהם, ויומן עדכונים נשמר בו כימים ולא כעדכונים. עם העמוד נשמרים הקבצים האחרים שהוא נבנה מהם (``dependencies`` בקובץ הייצוא), שדפדפן הקוד משווה כדי לדעת אם העמוד ישן מהקוד שהוא מציג. המפתח הוא קובץ המקור. עמוד לא מוטמע, ולכן הוא אינו נספר בעלות ובאישור. - סעיף בלי טקסט, או שכולו קישורים ("ראו גם"), לא נכנס כסעיף ולא כנתח (``is_links_only``). בעמוד הוא נשאר. - **יומן עדכונים** (``UPDATE_LOG_SOURCES``, היום :doc:`/whats-new`) — שם רשומה היא עדכון אחד ולא יום שלם: כל פריט ברשימה של יום נכנס כרשומה משלו. הכותרת שלה היא הפתיח המודגש של העדכון, והשביל הוא שביל היום ועוד הפתיח, כך שגם כל נתח של עדכון ארוך פותח בו. הגוף הוא שאר העדכון, כמו שסעיף נשמר בלי הכותרת שלו. עדכון בלי פתיח מודגש נכנס כרשומה בשם של היום, עם העדכון כולו. כך גם עדכון שהפתיח שלו מופיע בעמוד יותר מפעם אחת, בהשוואה של הדפדפן — בלי הבדל בין אותיות גדולות לקטנות, ובלי ניקוד ואקסנטים: אז הוא תווית, כמו "מגבלה ידועה:", ולא שם של עדכון. וכך גם עדכון שהפתיח שלו ארוך מהתקרה של כותרת בקובץ הייצוא (``MAX_TITLE_BYTES``), או שהשביל איתו ארוך מהתקרה של שביל (``MAX_BREADCRUMB_BYTES``): הפתיח הארוך לא נחתך, ורק עליו נרשמת אזהרה בלוג. מה שביום מחוץ לרשימה, כמו פסקה אחרי הבולטים, נכנס כרשומה אחת בשם של היום. איך כותבים עדכון שמתפרק כך: :ref:`doc-authoring-whats-new`. - המפתח של סעיף הוא קובץ המקור, שביל הכותרות ומספר סידורי לשביל שחוזר באותו עמוד — **בלי העוגן**, כי העוגן של כותרת בלי תווית זז. עוגן שזז מעדכן את הסעיף בלי הטמעה מחדש. נתח מוטמע מחדש רק כשה-``sha256`` של הטקסט שלו השתנה, או כשהמודל בהגדרות השתנה. ברשומה של עדכון השביל כולל את הפתיח, ולכן עדכון שנוסף ליומן לא משנה את המפתח של עדכון אחר עם פתיח, כל עוד הפתיח שלו נשאר ייחודי בעמוד. המעבר ~~~~~ מעבר (``run_pass``) משווה את מה שבקובץ למה שבמסד, ומיישר: מטמיע רק נתחים חדשים או שהשתנו, ומוחק מה שהקובץ כבר לא מכיר. מעבר אחד רץ בכל פעם, ב-thread ברקע, תחת lease במסמך המצב (האוסף ``docs_index_state``). בקשה שמגיעה בזמן שמעבר רץ ממתינה ורצה אחריו; כמה בקשות שממתינות מתאחדות לאחרונה שבהן, כי כל מעבר קורא ממילא את הקובץ העדכני. מה מפעיל מעבר: - **פריסה של האתר** — ה-webhook של GitHub, באירוע ``deployment_status``, כשפריסה של ``github-pages`` בריפו המקור הסתיימה בהצלחה (``handle_deployment_status_event`` ב-``webapp/routes/webhooks.py``). המעבר קורא את העותק של הקובץ לפי הקומיט של הפריסה, כי הקובץ הראשי עלול להגיע ממטמון האתר. - **רשת ביטחון** — ה-webhook של ``push`` לענף הראשי של ריפו המקור (``handle_push_event`` ב-``webapp/routes/webhooks.py``), לצד הסנכרון של המראה ובלי תלות בו: היא קוראת את הקובץ הראשי של האתר עם ``If-None-Match``, ובדרך כלל חוזרת ב-304 בלי לעשות כלום. היא תופסת פריסה שה-webhook שלה לא הגיע — אבל רק ב-push שאחריה, כי בזמן ה-push הבנייה שלו עוד לא נפרסה. - **"בדוק עכשיו"** בעמוד האדמין — כמו רשת הביטחון. השומרים: - **עלות.** מעבר אוטומטי שיש לו יותר מ-``APPROVAL_THRESHOLD_CHUNKS`` נתחים להטמעה — וכל מילוי ראשון או החלפת מודל — לא שולח כלום ועוצר ב"מחכה לאישור", עם התוכנית וטביעת אצבע שלה. האישור בעמוד האדמין מריץ אותה רק אם התוכנית שהמעבר מחשב מחדש זהה לזו שאושרה; אם היא השתנתה לתוכנית שאינה דורשת אישור, היא רצה כמו מעבר אוטומטי. כשהאינדקס *נכנס* להמתנה נשלחת התראה לבוט, עם מספר הנתחים, התקרה והקומיטים, ושורת Quick Fix לעמוד האדמין (``ALERT_APPROVAL_NEEDED``, ראו :ref:`docs-index-events`). מעבר שעוצר שוב בזמן שהאינדקס כבר מחכה — למשל מיזוג שמחשב מחדש את אותו פער מצטבר — אינו שולח עוד אחת. ההמתנה נגמרת כשמעבר מתחיל לכתוב, ולכן אחריו — בין שהצליח ובין שנעצר באמצע, למשל ב-``paused_quota`` — הכניסה הבאה להמתנה שולחת שוב: זו כבר בקשה חדשה לאישור. - **מחיקה.** מוחקים רק כשהקובץ מכיר לפחות סעיף, נתח או עמוד אחד מאלה שבמסד. קובץ שלא מכיר אף אחד מהם לא מוחק כלום, והמעבר רושם כמה מחיקות נחסכו. - **לא אחורה.** קובץ שנבנה לפני הקובץ שכבר הוחל לא מוחל (הקוד ``older_export``): עותק ישן ממטמון האתר, או webhook של פריסה קודמת שהגיע באיחור, לא מחזירים את האינדקס אחורה. - **שלם או חלקי.** הקומיט שהאינדקס נכון אליו (``indexed_source_commit``) נמחק לפני הכתיבה הראשונה של מעבר, ונכתב רק אחרי קריאה חוזרת שמוצאת כל סעיף, נתח ועמוד רצויים, עם ה-sha (ובנתחים גם המודל) הנכונים. בין שני הרגעים העמוד מציג את האינדקס כ"חלקי". לצד הקומיט נשמרת גרסת הכללים שבנו את הרשומות (``RECORDS_VERSION``). קוד שהופך את אותו קובץ לרשומות אחרות מעלה אותה, ועד שמעבר בונה את האינדקס בכללים החדשים ``is_index_complete`` מחזיר שקר: רשת הביטחון ו"בדוק עכשיו" לא נעצרים על 304, אלא קוראים את הקובץ בלי ``If-None-Match`` ובונים את התוכנית מחדש — גם כשקובץ הייצוא לא השתנה — ושורת המצב בחיפוש אומרת שייתכן שחסרים סעיפים. עמוד האדמין קובע "שלם" לפי הקומיט בלבד (``admin_docs_index.html``), ולכן אינו מסמן את המצב הזה. - **עצירה ודדליין.** לפני כל אצווה שכותבת — הטמעה, כתיבת סעיפים או מחיקה — נבדקות בקשת עצירה ו-``PASS_DEADLINE_SECONDS``, וה-lease מתחדש. עצירה היא גם בלם: מעבר שמוחק או כותב בלי הטמעות נעצר באותה נקודה, והספירה של מה שכבר נמחק נשמרת ב-``run.deleted``. מכסה של Gemini (429) עוצרת את המעבר במצב ``paused_quota``; המעבר הבא ממשיך מאיפה שהקודם עצר, ושולח רק את מה שחסר. עמוד האדמין ~~~~~~~~~~~ ``/admin/docs-index``, וכרטיס שמוביל אליו בהגדרות, בקבוצה "כלי אדמין". בעמוד: המעבר האחרון ומצבו, הקומיט שהאינדקס נכון אליו מול הקובץ שנקרא לאחרונה ומול ה-HEAD של המראה (כדי שפיגור ייראה), תוכנית "יבשה" מהקובץ הראשי, אישור של תוכנית, "בדוק עכשיו" ועצירה. ה-API של העמוד (``/api/docs-index`` והנתיבים שתחתיו) פתוח לאדמינים בלבד, וחסום גם במצב צפייה כמשתמש (``admin_api_required``). פעולה שמשנה מצב היא ``POST`` עם גוף JSON. הגדרה חד-פעמית ~~~~~~~~~~~~~~ 1. **ה-webhook.** ב-webhook של הריפו ב-GitHub — אותו webhook שמפעיל את הסנכרון (:doc:`/webapp/code-browser`) — בוחרים **Let me select individual events** ומסמנים, לצד ``push``, גם את ``deployment_status``. בלי זה רק רשת הביטחון והכפתור מפעילים מעבר. ה-webhook מוחרג מהמגבלה הגורפת של קצב הבקשות בוובאפ, כי GitHub שולח מכמה כתובות קבועות, ולכל פריסה יש כמה סטטוסים. 2. **האינדקס הווקטורי.** ב-Atlas יוצרים אינדקס מסוג Vector Search בשם ``docs_vector_index`` על האוסף ``docs_chunks``, עם ההגדרה שעמוד האדמין מציג (``vector_index_definition``): ``cosine`` על ``chunkEmbedding`` במימד של ההגדרות הפעילות, ופילטר על ``embeddingModelKey``. אחרי החלפת מודל במימד אחר צריך ליצור אותו מחדש, והעמוד אומר אם המימד תואם. בשרת שאינו Atlas העמוד מציג את מצב האינדקס כ"לא ידוע". 3. **מילוי ראשון.** "בדוק עכשיו" — המעבר עוצר לאישור ומציג כמה נתחים יישלחו — ואז "אשר והתחל". האירועים של המעבר ושל העמוד מתועדים ב-:ref:`docs-index-events`. ראו גם ------ - :doc:`/workflows/search-flow` — הפרטים הפנימיים של מנוע החיפוש: ``SearchIndex``, מסננים, מיון, והחיפוש הסמנטי. - :doc:`/webapp/commands-catalog` — כרטיסי "קיצורי הדרך" שמופיעים באותה תיבת חיפוש. - :doc:`/environment-variables` — המשתנים ``SEARCH_*`` ו-``SEMANTIC_*``. - :ref:`docs-index-events` — האירועים של אינדקס התיעוד.