Files
legal-ai/docs/spec/03-retrieval.md
2026-05-30 14:57:11 +00:00

16 KiB
Raw Permalink Blame History

03 — אחזור (Retrieval: Corpora · Hybrid/RRF · Attribution · Eval)

קובץ-תחום זה כפוף ל-חוקת המערכת ומגדיר את שכבת-האחזור הקנונית (TARGET) — שלושת הקורפוסים, כלי-החיפוש המכוונים לכל אחד, מנגנון ה-hybrid (dense + lexical) ומיזוג ה-RRF, עקיבוּת-המקור והרמוניית-המדידה. הוא אוכף את G4 (חוזה-שלמות לפני "ניתן-לחיפוש"), G5 (הפרדת-קורפוס בכל query), G6 (re-index), G7 (מיזוג RRF), G8 (eval) ו- G9 (עקיבוּת-מקור).

TARGET, לא תיאור-מצב. כל מקום שבו הקוד בפועל סוטה מהיעד מתועד כ-audit-finding (§5), תסמין לתיקון — לא התנהגות תקינה. כל טענה על הקוד מצוטטת file:line.

כשל-השורש שהקובץ מייבש: 3 קורפוסים שחולקים תשתית-אחזור אחת, אך הפרדת-הקורפוס נאכפת רק על חלק ממסלולי-ה-query — כך שפריט מקורפוס אחד דולף לתוצאה של חיפוש בקורפוס אחר (cross-corpus leak).


1. שלושת הקורפוסים וכלי-החיפוש

קורפוס טבלת-אחסון source_kind כלי-MCP מכוון אימות file:line
מסמכי-תיק + קורפוס-סגנון דפנה document_chunks — (מובחן ב-case_id/practice_area) search_decisions · search_case_documents · find_similar_cases search.py:15,91,145hybrid_search.py:41 (search_documents_hybrid) → db.search_similar (hybrid_search.py:56)
פסיקה חיצונית סמכותית case_law + precedent_chunks/halachot external_upload search_precedent_library search.pyprecedent_library.py:235search_libraryhybrid_search.py:89,101 (source_kind="external_upload")
החלטות ועדות-ערר (פנימי) case_law + precedent_chunks/halachot internal_committee search_internal_decisions search.py:228internal_decisions.py:395,411-418 (source_kind="internal_committee") → hybrid_search.py:89

הבחנת-שם קריטית (לא קורפוס רביעי): precedent_search_library (server.py:160) הוא כלי שונה — מחפש בציטוטים שהיו"ר צירפה ידנית לתיקים (case_precedents), לא בקורפוס הפסיקה הסמכותית. search_precedent_library (server.py:280) הוא הכלי לקורפוס החיצוני. אל תבלבל ביניהם.

הקורפוס החיצוני והפנימי חולקים טבלה אחת (case_law), מובחנים ב-source_kind בלבד (02-data-model §2א). שניהם רצים דרך אותן פונקציות-DB (search_precedent_library_semantic/_lexical) — לכן הפרדת-הקורפוס היא תנאי-סינון בתוך אותה שאילתה, ושם נולדת ההפרה ב-§5.


2. עיצוב ה-hybrid retrieval

לכל קורפוס שני retrievers הטרוגניים המאוחים ב-RRF, ולא בסכום-ציונים — ראה INV-RET3:

  1. Dense (semantic) — דמיון-קוסינוס מול embedding vector(1024) (voyage). פסיקה: search_precedent_library_semantic (db.py:3143); מסמכי-תיק: db.search_similar.
  2. Lexical (BM25-style)ts_rank_cd מול content_tsv/rule_tsv/meta_tsv (Postgres FTS). פסיקה: search_precedent_library_lexical (db.py:3366). מופעל כש-BM25_HYBRID_ENABLED (hybrid_search.py:139).
  3. מיזוג sem+lex_merge_sem_lex (hybrid_search.py:240-308), נוסחת rrf_score = 1/(k+sem_rank) + 1/(k+lex_rank) (hybrid_search.py:256).
  4. שכבת-multimodal (אופציונלית) — כש-MULTIMODAL_ENABLED, עמודי-תמונה (voyage-multimodal-3) מאוחים לטקסט ב-RRF נפרד: _merge (hybrid_search.py:311-389), text_weight/(k+rank) + img_weight/(k+rank) (hybrid_search.py:356-357).
  5. Diversity cap (MMR-style)_diversify_by_case_law (hybrid_search.py:196-225): לכל היותר max_per_case_law hits לכל case_law_id, כדי שפסק-דין יחיד לא ישתלט על הרשימה.

למה RRF ולא סכום משוקלל: קוסינוס (~0.40.7) ו-ts_rank_cd (~0.0010.5, תלוי-אורך-שאילתה) חיים בסקיילים שונים — סכום משוקלל היה נותן לצד אחד להשתלט במקרה. RRF מאחד לפי דירוג, ולכן עמיד להבדלי-סקייל (hybrid_search.py:248-252,319-323). תואם feedback קיים (RRF, לא weighted-sum).


3. Invariants של התחום

INV-RET1: הפרדת-קורפוס נאכפת ב-100% ממסלולי-ה-query

כלל: הפרדת 3 הקורפוסים נאכפת בכל מסלול-אחזור — גם בסינון ה-chunks וגם בסינון ההלכות. אין פריט מקורפוס אחד שמופיע בתוצאת חיפוש שכוון לקורפוס אחר. כל ענף-SQL (semantic/lexical, chunks/halachot) נושא את אותו תנאי-source_kind. מקורות: Pinecone — Implement multitenancy (metadata-filter isolation per tenant) · RAG attribution (Lewis et al., 2020, NeurIPS — pinned non-leaking provenance) · ISO 8000 (Data quality / conformance) | סטטוס: verified אכיפה: תנאי-source_kind בכל ענף-SQL בשכבת-החיפוש; בדיקת-בריאות שמריצה שאילתת-ביקורת (חיפוש מכוון-קורפוס שמחזיר פריט בעל source_kind זר = כשל). אוכף את G5. הפרה ידועה: משימה #56 — halacha_filters אינם כוללים cl.source_kind ב- search_precedent_library_semantic (db.py:3168, ענף ה-halacha; לעומת chunk_filters שכן — db.py:3169) וב-search_precedent_library_lexical (db.py:3401 מול db.py:3402). שני ה-halacha_sql עושים JOIN case_law cl בלי לסנן source_kind (db.py:3236-3238, db.py:3475-3477) → הלכות מהקורפוס הפנימי דולפות לתוצאות החיפוש בקורפוס החיצוני ולהפך → ממצא ל-audit.

INV-RET2: אין החזרה/אינדוקס בלי metadata מלא + locator פתיר

כלל: פריט אינו מוחזר מ-search (ואינו נחשף לאחזור) אלא אם שדות-החובה שלו מולאו (G4) ובידו locator פתיר למקור (case_law_id/document_id + מזהה-עמוד/chunk). רשומה ללא metadata לא-ריק או ללא chunk עם embedding מסומנת searchable=false ולא מוחזרת (02-data-model INV-DM1). מקורות: Pinecone (metadata filtering — completeness לפני שליפה) · RAG attribution (Lewis et al., 2020) · ISO 8000 (completeness) | סטטוס: verified אכיפה: חוזה-שלמות בנקודת-הקליטה (02-data-model §2)

  • סינון בשכבת-החיפוש (embedding IS NOT NULL, db.py:3239,3271; length(trim(content))>=50, db.py:3274) + בדיקת-בריאות שחושפת backlog. אוכף את G5. הפרה ידועה: ערן סופר 8046/24 — נקלטה בלי metadata (headnote/summary/tags ריקים), היעדר תיזמון חילוץ-מטא-דאטה במסלול הפנימי (01-ingest INV-ING3), אך ללא דגל-searchable מפורש שימנע את חשיפתה לאחזור → ממצא ל-audit.

INV-RET3: מיזוג retrievers הטרוגניים ב-RRF בלבד

כלל: מיזוג תוצאות בין retrievers שונים (semantic↔lexical, text↔image) נעשה אך ורק לפי דירוג (Reciprocal Rank Fusion) — לעולם לא סכום/ממוצע ציונים גולמיים, שכן ציונים בסקיילים שונים אינם בני-השוואה ישירה. מקורות: Elastic — Reciprocal Rank Fusion · Weaviate — Hybrid Search Explained · Manning, Raghavan & Schütze, Introduction to Information Retrieval (CUP, 2008) | סטטוס: verified אכיפה: מיזוג sem+lex ב-_merge_sem_lex (hybrid_search.py:240-308, נוסחה ב-:256) ומיזוג text+image ב-_merge (hybrid_search.py:311-389, נוסחה ב-:356-357), שניהם עם k = MULTIMODAL_RRF_K. אוכף את G7. מצב: כבר ממומש (codify, לא gap) — הקוד הקיים מיישם RRF נכון בשני המיזוגים. ה-invariant מקבע את ההתנהגות הקיימת כחוזה. הפרה ידועה:

INV-RET4: איכות-אחזור נמדדת ב-eval harness עומד (precision + recall)

כלל: איכות-האחזור נמדדת אמפירית — precision ו-recall — מול סט-שאילתות מתויג קבוע (labeled query set) ב-eval harness עומד. כל שינוי בשכבת-האחזור (משקלי-RRF, k, סף-chunk, embedder) מלווה במדידה לפני/אחרי; אין כוונון "לפי תחושה". מקורות: Manning, Raghavan & Schütze, Introduction to Information Retrieval (CUP, 2008 — fixed relevance judgments, precision/recall) · RAG evaluation literature (Lewis et al., 2020 ואחריו) · Elastic — relevance evaluation guidance | סטטוס: verified אכיפה: eval harness עם gold-set מתויג + בדיקת-בריאות תקופתית; שער-CI על שינוי שכבת-האחזור. אוכף את G8. הפרה ידועה (GAP): אין כיום eval harness ולא gold-set — קיים רק telemetry.log_search_bg (search.py:62,118,190,271; precedent_library.py:280) שמתעד שאילתות בפועל, אך אינו מודד precision/recall מול תיוג (תצפית, לא הערכה). היעד: harness שמריץ סט קבוע ומחזיר metrics → ממצא ל-audit.

INV-RET5: כל span מוחזר עקיב למקורו

כלל: כל קטע מוחזר נושא עקיבוּת-מקור מלאה — מזהה-מסמך/פסק-דין (case_law_id/document_id/ case_number) ו-locator בתוכו (page_number / chunk_id / supporting_quote להלכה). פלט ללא ייחוס פתיר אינו תקין; היו"ר חייבת לאמת כל ציטוט מול מקורו. מקורות: Council of Europe / CEPEJ — European Ethical Charter on AI in judicial systems (2018, traceability) · RAG attribution (Lewis et al., 2020) · ISO 15489-1:2016 (records authenticity/integrity) | סטטוס: verified אכיפה: כל פורמטר-תוצאה כולל מזהה + locator: search.py:77-86 (case_number/page/section), _format_internal_row (search.py:322-343: case_number/case_name/court + content/page או rule/quote להלכה). עקיבוּת מלאה מפורטת ב-X5-audit-provenance.md. אוכף את G9. הפרה ידועה:


4. re-index ושינוי-תוכן (G6)

האחזור מסתמך על embeddings מסונכרנים מול התוכן. ה-tsvectors (content_tsv/rule_tsv/meta_tsv) הם GENERATED ALWAYS … STORED (db.py:778,782,1086) ולכן מתעדכנים אוטומטית; אך ה-embedding vector(1024) אינו generated — הוא תלוי-טריגר-חיצוני, נקודת-drift אפשרית (02-data-model INV-DM3). שינוי-תוכן חייב להפעיל re-embed; בדיקת-בריאות מגלה embeddings מיושנים. אוכף את G6.


5. מצב קיים מול יעד — audit-findings

ההבדלים בין הקוד בפועל ל-TARGET. אלו תסמינים, לא התנהגויות תקינות. כל פריט אומת מול הקוד.

  • דליפת-הלכות חוצת-קורפוס (משימה #56). halacha_filters נפתחים רק עם review_status (db.py:3168, db.py:3401) ואינם מוסיפים cl.source_kind, בעוד chunk_filters כן (db.py:3169, db.py:3402). שני ה-halacha_sql עושים JOIN case_law בלי סינון (db.py:3236-3242, db.py:3463-3482). תסמין: חיפוש בקורפוס החיצוני (search_precedent_library, source_kind="external_upload") יכול להחזיר הלכה שמקורה בהחלטת-ועדה פנימית — ולהפך עבור search_internal_decisions (source_kind="internal_committee", internal_decisions.py:418). יעד: halacha_filters יתחילו ב-cl.source_kind = '{source_kind}' בדיוק כמו chunk_filters (INV-RET1).
  • אין eval harness — מדידת-איכות לא קיימת. רק telemetry.log_search_bg מתעד שאילתות (search.py:62,118,190,271); אין gold-set מתויג ואין precision/recall. יעד: harness עומד (INV-RET4).
  • search_decisions מתעד אזהרה כשאין practice_area אך לא חוסם. ללא פילטר-תחום החיפוש עלול לערבב תחומים משפטיים (search.py:45-49,172-176logger.warning, ממשיך). יעד: הפרדה לפי תחום נאכפת, לא מומלצת בלבד — תואם את עקרון ההפרדה ב-G5.
  • embedding אינו GENERATED (בניגוד ל-tsvector). נקודת-drift אפשרית בין תוכן ל-embedding אחרי עדכון (§4; תואם 02-data-model). יעד: טריגר re-embed מובטח + health-check.

6. הפניות-אחיות

  • 00-constitution.md — invariants גלובליים (G4G9) + כללי-הנדסה.
  • 01-ingest.md — חוזה-הקליטה שמייצר את ה-chunks/embeddings שהאחזור שולף.
  • 02-data-model.md — חוזה-השלמות (searchable) + re-index שהאחזור מסנן לפיהם.
  • 05-qa-review.md — שער-הלכה הידני (review_status) שמגדיר אילו הלכות searchable.
  • X5-audit-provenance.md — עקיבוּת-מקור מלאה של כל span מוחזר (בסיס ל-INV-RET5).