← חזרה למרכז הידע
מרכז ידע

בית תוכנה ו־RAG: איך מחברים מודל שפה למידע ארגוני?

16.08.2026coreva

תכנון RAG למידע ארגוני: ingestion אמין, חיפוש היברידי, ACLs, ניהול embedding generations, ‏pgvector, אבטחה, recovery ו־rollback.

ארכיטקטורת RAG למידע ארגוני הכוללת ingestion, חיפוש היברידי, הרשאות, בניית context ומודל שפה

ארכיטקטורת RAG נבחנת ברגע שבו release נכשל, לא כאשר הדמו הראשון מחזיר תשובה. נניח שגרסת production כוללת chunker מעודכן, embedding model חדש ושינוי במבנה ה־metadata. ה־workers כבר כתבו מחצית מהמסמכים ל־index generation החדש, שירות ה־query התחיל לקרוא ממנו, ואז מתגלה regression שמחייב rollback.

החזרת ה־container image לבדה אינה משחזרת את המצב הקודם. ה־vectors החדשים אינם נמצאים בהכרח באותו embedding space, ה־schema migration עשויה להיות חד־כיוונית, והקוד הישן לא בהכרח יודע לפרש את ה־metadata החדש. אם מסלול הקריאה כבר ערבב בין generations, גם rollback מוצלח ברמת היישום אינו מבטיח שהתוצאות חזרו למצב עקבי.

זהו המבחן המעשי של RAG למידע ארגוני. לא מדובר ב״חיבור LLM למסד וקטורי״, אלא במערכת מבוזרת שמתחזקת נגזרת ניתנת לחיפוש של מידע ארגוני. עליה לשמר identity, הרשאות, provenance, ordering, freshness ויכולת recovery לאורך שני מסלולים בעלי failure semantics שונים: מסלול ה־ingestion ומסלול ה־query.

ה־LLM הוא שלב אחד בלבד ב־query path. רוב הסיכונים ההנדסיים נמצאים סביבו: parsing של קלט לא אמין, עדכונים כפולים או מחוץ לסדר, ACLs שמתיישנים, חיפוש approximate תחת filters, retries לשירותים חיצוניים, שינויי מודלים, token budget ו־prompt injection עקיף מתוך המסמכים עצמם.

ה־index הוא state נגזר, לא מקור האמת

מאמר ה־RAG המקורי הגדיר שילוב בין הזיכרון הפרמטרי של המודל לבין זיכרון חיצוני לא־פרמטרי שנגיש באמצעות retriever. הוא אינו מגדיר contracts עבור ingestion, authorization, replay, deletion או disaster recovery. במימוש ארגוני, ה־context שמגיע למודל הוא פלט של שרשרת טרנספורמציות, ולכל שלב יש owner ו־persistence semantics משלו.

State Owner מומלץ משמעות תפעולית
תוכן המקור DMS, Git, CRM, object store או מערכת מקור אחרת ה־source of truth; תיקון התוכן נעשה במקור
Sync cursor Connector metadata DB Checkpoint durable בעל ordering semantics מוגדרים
Raw ingest event Durable log או event table מאפשר replay, audit ואבחון של כפילויות ואירועים מחוץ לסדר
Canonical document Object store או relational DB פלט parser יציב שאינו תלוי ב־chunker או ב־embedding model
Chunks Versioned derivative store ניתנים לשחזור מתוך canonical version ו־chunker version
Embeddings Versioned derivative store קשורים למודל, revision, dimensions ו־normalization מפורשים
ANN index Search subsystem אינדקס rebuildable; אינו source of truth
ACL snapshot מקור הרשאות או materialized derivative מחייב freshness contract ומדיניות revocation
Prompt ו־context זיכרון זמני או audit store מוגן עשויים להכיל מידע רגיש ואינם מתאימים ל־logging כברירת מחדל
Conversation state האפליקציה אין להניח שספק ה־LLM מנהל isolation, retention או deletion כנדרש

ה־invariant המרכזי הוא שכל chunk ניתן לשחזור מתוך canonical document, גרסת chunker, כללי normalization ו־embedding revision. כאשר הטקסט וה־vector נשמרים רק ב־vector store, פעולות recovery, migration, audit ומחיקה רגולטורית נעשות תלויות במערכת שאמורה הייתה להיות נגזרת בלבד.

אותו עיקרון חל על provenance. תשובה צריכה להצביע לפחות על tenant_id, ‏source_system, ‏source_id, ‏source_version, ‏chunk_id ו־index_generation. קישור ל־URL נוכחי אינו citation מספק: המסמך עשוי להשתנות בין יצירת התשובה לבין בדיקתה. הדרישה הזו חשובה במיוחד במהלך פיתוח מערכת CRM, שבה רשומות ארגוניות מתעדכנות באופן תדיר.

Baseline טכנולוגי והצמדת גרסאות

נכון לתאריך חתך המחקר, 16 באוגוסט 2026, הדוגמאות במאמר מניחות PostgreSQL 18.4, ‏pgvector 0.8.6, ‏Kubernetes v1.36 ו־OpenTelemetry Semantic Conventions 1.43.0. PostgreSQL 19 עדיין נמצא ב־Beta 2 ולכן אינו baseline לדוגמת production. ב־pgvector, גרסה 0.8.7 מסומנת unreleased, ואין להצמיד image או extension ל־latest.

ה־patch level של pgvector אינו פרט שולי. לפי ה־changelog של pgvector, גרסאות 0.8.3–0.8.4 כללו בין היתר תיקונים הקשורים ל־HNSW corruption ול־vacuum. לפני rollout יש לבדוק release notes, לבנות index חדש אם נדרש, ולוודא שה־extension version זהה בכל replicas ו־restore environments.

ב־OpenTelemetry, גרסה 1.43.0 מתייחסת ל־core semantic conventions. conventions עבור GenAI הועברו למאגר נפרד, וחלק מה־attributes הישנים ברישום המרכזי מסומנים deprecated. לכן יש להצמיד גם את גרסת חבילת ה־GenAI שבה משתמשים, ולא להסיק שמספר גרסת ה־core לבדו מקבע את שמות כל ה־spans וה־attributes.

Embedding migration היא data migration

שינוי ב־embedding model, ב־normalization, ב־input prefix או ב־chunk boundaries הוא data migration, גם אם אף טבלת SQL לא השתנתה. Query embedding ו־document embeddings חייבים להיווצר באמצעות model revision, dimensions, distance metric ו־input mode תואמים. ערבוב vectors ממרחבים שונים עלול להחזיר תוצאות תקינות מבחינה תחבירית אך חסרות משמעות סמנטית.

Rollout בטוח מפריד בין בניית הנגזרת לבין הפעלתה. Generation registry צריך לנהל state machine מפורש:

BUILDING → CATCHING_UP → VALIDATED → ACTIVE → DRAINING → RETIRED ↘ FAILED
  1. Expand: מוסיפים schema ו־API contracts שמאפשרים ל־readers ול־writers לעבוד עם ה־generation הפעיל ועם החדש.
  2. Snapshot: מגדירים source revision או watermark שממנו מתחיל ה־backfill.
  3. Build: יוצרים canonical chunks, ‏ACL snapshots ו־embeddings ב־generation שאינו חשוף למסלול הקריאה.
  4. Catch up: מבצעים replay לכל השינויים שהתרחשו אחרי ה־watermark. Dual write הוא חלופה אפשרית, אך הוא מוסיף coordination failures ולכן עדיין דורש reconciliation.
  5. Validate: בודקים completeness, ‏source versions, ‏ACL coverage, ‏dimension consistency, recall מול exact search ומדגם שאלות מתויג.
  6. Cut over: מעדכנים active-generation pointer בפעולה אטומית וקצרת טווח.
  7. Observe: משווים latency, ‏empty-result rate, retrieval quality, authorization failures ועלות tokens.
  8. Contract: מוחקים את ה־generation הישן רק לאחר שכל ה־readers הישנים יצאו משירות וחלונות ה־rollback, ה־audit וה־retention הסתיימו.

ה־pointer הוא state מנוהל, בדרך כלל ברמת tenant, corpus או security domain. יש לאכוף constraint של generation פעיל אחד לכל scope. Environment variable שנקרא רק בזמן עליית התהליך אינו מנגנון cutover אמין: instances שונים עלולים להמשיך לעבוד עם ערכים שונים. כל query צריך לפתור ולקבע את ה־generation בתחילת הבקשה. בקשה שהחלה מול generation ישן יכולה להסתיים מולו, ולכן אין למחוק אותו מיד לאחר המעבר.

כאשר המקור ממשיך להשתנות בזמן backfill, ספירת מסמכים אינה מוכיחה completeness. נדרש manifest שכולל watermark, revisions אחרונים, tombstones, מספר chunks ו־hashes לפי partition. רק לאחר שה־replay השלים את כל האירועים שמעבר ל־watermark וה־lag נמצא בגבול שהוגדר, ניתן להפעיל את ה־generation החדש.

מסלול ה־ingestion: אירועים מייצגים גרסאות

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

{ "event_id": "019...", "tenant_id": "...", "source_system": "dms", "source_id": "document-123", "source_version": "opaque-source-revision", "source_sequence": 1842, "ordering_domain": "document-123", "operation": "UPSERT", "content_locator": "...", "acl_revision": "...", "observed_at": "2026-08-16T10:00:00Z"
}

source_version עשוי להיות מזהה opaque, ולכן אין להניח שאפשר להשוות אותו לקסיקוגרפית. גם observed_at אינו שעון correctness: ‏clock skew ו־delivery delay עלולים לגרום לאירוע ישן להיראות חדש. כאשר מערכת המקור מספקת sequencer מונוטוני, יש לשמור אותו ב־source_sequence ולהחיל conditional publication. אם אין sequence בר־השוואה, ה־connector צריך להחזיק cursor בעל semantics ידועים או לבצע read-back מול המקור לפני publication.

מערכות event רבות מספקות at-least-once ללא ordering גלובלי. S3 Event Notifications, לדוגמה, עשויות להגיע בכפילויות ומחוץ לסדר, ו־AWS ממליצה להשתמש ב־sequencer בעת תחזוקת נגזרות. לכן נדרשים invariants ברמת (tenant_id, source_system, source_id):

  • Sequence ישן אינו דורס sequence שכבר פורסם.
  • אותו revision יכול להגיע יותר מפעם אחת ולהפיק אותה תוצאה לוגית.
  • DELETE נשמר כ־tombstone שמשתתף באותו סדר גרסאות.
  • שינוי ACL עשוי לחייב publication גם כאשר תוכן המסמך לא השתנה.
  • Worker ישן אינו רשאי לפרסם אחרי ש־worker חדש יותר כבר קיבל lease או fencing token מתקדם.
  • אירוע מסומן כהצלחה רק כאשר הנגזרות queryable, או כאשר generation מלא מוכן ל־cutover.
  • Reconciliation תקופתי משווה בין קטלוג המקור לקטלוג האינדקס ומתקן אירועים שאבדו.

כדי למנוע stale writer, רשומת העבודה צריכה לכלול attempt_id או fencing token. שלב publication מבצע compare-and-swap מול ה־sequence וה־token הנוכחיים. Worker שאיבד lease יכול להשלים parsing או embedding יקר, אך אינו רשאי להפוך את הפלט שלו לגרסה הפעילה.

Exactly-once אינו חוצה queue, ‏DB ו־embedding provider

Kafka producer idempotence מונע כפילויות שמקורן ב־producer retries באותה session. הוא אינו מבטל application-level resends ואינו הופך קריאת HTTP לשירות embedding, כתיבה ל־PostgreSQL ו־offset commit לעסקה אטומית אחת. גם Kafka transaction אינה transaction מבוזרת מול DB חיצוני.

החוזה המעשי הוא at-least-once delivery עם processing דטרמיניסטי, version comparison, ‏unique constraints ו־idempotent upserts. כאשר תוצאת העיבוד וה־checkpoint נשמרים באותו PostgreSQL, אפשר לעדכן אותם באותה transaction. לעומת זאת, אין להחזיק transaction פתוחה בזמן קריאה לספק embedding: פעולה כזו מאריכה את חיי ה־locks, צורכת connections ומרחיבה את ה־failure window.

כאשר מנוע החיפוש חיצוני ל־PostgreSQL, נדרש publication protocol מפורש. תבנית אפשרית היא transactional outbox: transaction אחת שומרת את ה־canonical state, את סטטוס העיבוד ואת אירוע ה־publication. Publisher נפרד כותב ל־search store באמצעות idempotency key, מאמת שהגרסה queryable ורק אז מסמן את ה־outbox כ־published. Reconciliation מאתר metadata ללא vectors, vectors יתומים וגרסאות שהופיעו רק בצד אחד.

DISCOVERED → FETCHED → PARSED → CHUNKED → EMBEDDED → STAGED → PUBLISHED ↘ QUARANTINED ↘ RETRYABLE

כל transition מקבל idempotency key ותנאי מעבר. Worker שמקבל אירוע מחדש בודק אם artifact תואם כבר קיים; הוא אינו מסתמך על כך שהודעת queue סומנה בעבר כהצלחה. גם batch embedding אינו בהכרח atomic. אם הספק מחזיר הצלחה חלקית, יש לשמור תוצאה לכל item ולנסות שוב רק את החסרים.

embedding_key = hash( normalized_chunk_text, provider, model_id, model_revision, dimensions, normalization_mode, input_type
)

Cache לפי hash של הטקסט בלבד אינו בטוח. שינוי ב־model revision, ב־dimensions, ב־normalization או ב־prefix ייעודי כמו query: ו־passage: עשוי לשנות את ה־vector. Timeout גם אינו מוכיח שהספק לא השלים את הבקשה. מפתח פנימי מונע publication כפול, אך אינו מבטיח שספק חיצוני לא יבצע חיוב כפול אם ה־API שלו אינו תומך ב־idempotency keys.

Publication של מסמך צריך להיות אטומי מבחינת הקוראים

עבור עדכון של מסמך יחיד ב־generation פעיל, רצוי לבצע parsing, ‏chunking ו־embedding מחוץ ל־transaction, לכתוב את התוצאות ל־staging, ורק אז להחליף את כל ה־chunks של המסמך ב־transaction קצרה. תחת MVCC, קורא יראה את הגרסה הישנה או את החדשה, ולא אוסף חלקי של שתיהן.

PostgreSQL INSERT ... ON CONFLICT DO UPDATE מספק INSERT-or-UPDATE אטומי עבור ה־statement וה־conflict target. הוא אינו הופך אוטומטית החלפה של מסמך, כל ה־chunks שלו, ה־ACL וה־checkpoint ליחידה אטומית. את הגבול הזה צריך להגדיר באמצעות transaction או generation cutover.

מסמך גדול במיוחד עלול להפוך גם transaction קצרה ליקרה מבחינת WAL, ‏locks ו־index churn. במקרה כזה אפשר לפרסם document generation פנימי באמצעות pointer, או לעבור ל־corpus-level generation cutover. המחיר הוא יותר storage ומורכבות של garbage collection; היתרון הוא failure envelope מצומצם וברור.

Parsing ו־canonicalization הם trust boundary

PDF, ‏Office, ‏HTML, ‏XML וארכיונים הם input לא אמין, גם כאשר הגיעו ממערכת ארגונית. לפי הנחיות OWASP להעלאת קבצים, parser צריך לרוץ ב־sandbox עם filesystem זמני, ללא credentials וללא network egress כברירת מחדל. יש להגביל raw size, ‏uncompressed size, מספר entries, ‏compression ratio, ‏recursion depth, מספר עמודים או nodes, זמן CPU, ‏wall-clock, זיכרון ומספר subprocesses.

XML parsing דורש ביטול מפורש של external entities, ‏DTD resolution ו־XInclude בהתאם לספרייה ול־runtime. Container לבדו אינו sandbox מספק: container בעל service account, ‏volume mount או network access עדיין יכול להרחיב את ה־blast radius. ב־Kubernetes נדרשים security context מצומצם, read-only root filesystem, ‏seccomp מתאים, resource limits ו־NetworkPolicy. האכיפה בפועל תלויה בגרסת ה־cluster, ב־runtime וב־CNI.

Canonicalization צריכה להיות יציבה ומוגדרת: character decoding, ‏Unicode normalization, ‏line endings, ‏whitespace, ‏extraction של טבלאות והיררכיית כותרות. שינוי normalization משנה hashes ועלול לגרום ל־re-embedding רחב. לכן canonical document נשמר לפני chunking ומקבל schema version עצמאי.

Chunk הוא רשומת provenance, לא מחרוזת

chunk_id
tenant_id
source_system
source_id / source_version
source_sequence
chunk_ordinal
structural_path
canonical offsets
content_hash
chunker_name / chunker_version
embedding model / revision / dimensions
distance_metric
acl_revision
index_generation
valid_from / deleted_at

Offsets חייבים להתייחס לגרסת canonical document מוגדרת. אחרת citation highlighting נשבר לאחר עדכון המסמך. אין להשוות vectors שנוצרו ממודלים או מתהליכי normalization שונים כאילו הם נמצאים באותו space, גם כאשר מספר ה־dimensions זהה.

אין chunk size אוניברסלי. קטע קצר עשוי לשפר precision אך לאבד dependent context. קטע ארוך מצמצם את מספר ה־vectors, אך מגדיל semantic noise, ‏token cost ואת ההסתברות להכנסת מידע לא רלוונטי. Code, טבלאות, סעיפי חוזה ומסמכים נרטיביים דורשים boundaries שונים, ולכן chunking נמדד מול מסמכים ושאלות מהדומיין ולא מול כלל אצבע גלובלי.

  • אותו canonical input ואותה גרסת chunker מפיקים אותם boundaries ו־IDs.
  • שינוי מקומי אינו מזיז ללא צורך את כל ה־ordinals; ‏content-addressed IDs יכולים לצמצם churn.
  • טבלאות, code blocks וכותרות נשמרים כיחידות מבניות כאשר פיצול יפגע במשמעות.
  • Parent-child relation מאפשר retrieval של קטע ממוקד והרחבת context מבוקרת.
  • מחיקת source version מוציאה את כל הנגזרות הפעילות שלו, ולא רק את הרשומה הראשית.

Query path: ‏security context, ‏retrieval ו־deadline

מסלול ה־query צריך לקבע security context ו־generation לפני הפעלת retrieval:

  1. מאמתים את המשתמש או השירות.
  2. בונים security context immutable שכולל tenant, ‏principal, ‏groups, ‏scopes ו־policy revision.
  3. פותרים active generation ומקבעים אותו לאורך הבקשה.
  4. מנרמלים את השאלה ומפיקים query embedding שתואם ל־generation.
  5. מריצים dense ו־lexical retrieval במקביל כאשר ה־workload מצדיק זאת.
  6. מחילים ACL לפני שהתוכן יוצא מגבול ה־search subsystem.
  7. מבצעים candidate fusion, ‏deduplication ו־reranking.
  8. בונים context במסגרת token budget ו־request deadline.
  9. קוראים ל־LLM עם הזמן שנותר, ולא עם timeout מלא חדש.
  10. מאמתים output schema, ‏citations ו־source identifiers.

RFC 9700 / BCP 240 הוא baseline עדכני לאבטחת OAuth flows ו־tokens, אך token תקף אינו החלטת authorization. האפליקציה עדיין צריכה למפות claims למדיניות גישה למשאבים. יש להגדיר issuer, ‏audience, ‏token lifetime ודרך טיפול ב־revocation; ‏claims ממקור לא מאומת אינם נכנסים ל־security context. כאשר ממשק הקצה נבנה כחלק מפרויקט פיתוח אפליקציה לאייפון, גם הלקוח הנייד אינו גבול אמון ואין להסתמך עליו לאכיפת ההרשאות.

Dense ו־lexical retrieval מכסים failure modes שונים

Dense retrieval מתאים לפרפרזות ולדמיון סמנטי. Lexical retrieval נדרש עבור error codes, ‏class names, ‏SKUs, מספרי חוזים, symbols וציטוטים מדויקים. ממצאים מ־BEIR ומ־MTEB מחזקים את המסקנה שאין embedding model יחיד ששולט בכל סוגי המשימות, השפות וה־corpora. במאגרים ארגוניים שמכילים code, ‏tickets ומונחים פנימיים, vector-only מרחיב את ה־failure envelope ללא צורך.

אין לחבר ישירות ציוני BM25 ומרחק cosine, משום שהם אינם מכוילים לאותה סקאלה. Reciprocal Rank Fusion הוא baseline שימושי שמחבר מיקומים במקום scores:

fusion_score(document) = Σ 1 / (k + rank_in_result_set)

לאחר fusion, ‏reranker יכול לשפר את הסדר עבור קבוצת מועמדים מצומצמת. השיפור מגיע עם latency, ‏compute ושליחת תוכן לרכיב נוסף. לכן ACL חייב להיאכף לפני reranking, ולרכיב עצמו נדרשים timeout ו־fallback. אם reranker נכשל, אפשר לחזור ל־fused ranking, אך ה־degradation צריך להופיע ב־telemetry.

Context assembler מנהל evidence, לא רק tokens

Long context אינו תחליף ל־retrieval. מחקר Lost in the Middle הראה שמידע רלוונטי עלול לקבל פחות משקל כאשר הוא ממוקם באמצע context ארוך. מעבר לכך, irrelevant context מגדיל עלות ועלול לטשטש את ההבחנה בין הוראות מערכת לבין תוכן מסמכים.

available_context = model_context_limit - system_prompt_tokens - conversation_tokens - reserved_output_tokens - safety_margin

ה־assembler צריך להסיר near-duplicates, לשמור diversity בין מקורות, להרחיב neighbor chunks רק כאשר הדבר תורם ולהשאיר מקום ל־output. אם הראיות אינן נכנסות, אפשר לצמצם candidates או לבצע summarization היררכי. Summarization היא transformation לא דטרמיניסטית שעלולה לאבד qualifiers, ולכן במערכות audit-sensitive שומרים citations לקטעי המקור ולא מציגים summary כ־source.

Structured output חייב לעבור schema validation. Citation validation יכולה לוודא שכל source identifier הגיע מה־retrieval set המורשה, אך היא אינה מוכיחה שהטענה נתמכת במקור. בדיקה חזקה יותר דורשת claim-to-evidence evaluation, וגם היא אינה guarantee מוחלט.

Authorization ו־tenant isolation

Chunk שה־principal אינו רשאי לקרוא אסור שייכנס ל־prompt, ל־reranker, ל־cache, ל־trace או להודעת שגיאה.

סינון לאחר generation מאוחר מדי. עצם שליחת התוכן ל־LLM או ל־reranker היא disclosure. ה־retrieval subsystem צריך לקבל security context שנגזר מזהות מאומתת ולהחזיר רק candidates מורשים.

אסטרטגיה יתרון Failure envelope
Query-time ACL Policy עדכנית יחסית ופחות שכפול Latency, ‏joins מורכבים ופגיעה אפשרית במספר מועמדי ANN
Materialized ACL Lookup מהיר ופשוט Stale grants ו־revocation lag
Security partitions Blast radius קטן ובידוד חזק יותר Index proliferation, ‏migrations ו־capacity fragmentation
Authorization במערכת המקור הכרעה קרובה ל־source of truth תלות בזמינות וב־latency של המקור במסלול הקריטי

כאשר ACL משוכפל לאינדקס, יש להגדיר freshness budget. אם ה־snapshot ישן מהתקציב המותר, המערכת צריכה לבצע query-time check או להיכשל במצב deny. אירועי revocation צריכים לקבל קדימות תפעולית גבוהה מאירועי תוכן רגילים, אך קדימות אינה מחליפה בדיקת freshness.

גם cache דורש isolation. מפתח cache לתוצאות retrieval צריך לכלול לפחות tenant, ‏generation, ‏query normalization, ‏embedding revision וייצוג של policy revision או authorization scope. Cache משותף לפי query text בלבד עלול להחזיר למשתמש אחד context שנשלף עבור משתמש אחר.

PostgreSQL RLS הוא defense in depth

כאשר RLS פעיל ואין policy מתאימה, PostgreSQL משתמש ב־default deny. עם זאת, superuser, ‏role בעל BYPASSRLS ובדרך כלל table owner יכולים לעקוף RLS. שירות האפליקציה אינו צריך להתחבר כ־owner, ויש להשתמש ב־FORCE ROW LEVEL SECURITY כאשר נדרשת החלה גם על owner.

ALTER TABLE rag_chunk_gen_42 ENABLE ROW LEVEL SECURITY;
ALTER TABLE rag_chunk_gen_42 FORCE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation ON rag_chunk_gen_42
USING ( tenant_id = NULLIF( current_setting('app.tenant_id', true), '' )::uuid
);

את app.tenant_id יש לקבוע באמצעות set_config(..., true) בתוך transaction, כדי ש־connection pool לא ידליף context לבקשה הבאה. עם זאת, custom GUC אינו secret ואינו boundary מול application role עוין שיכול לשנות אותו. הדפוס מגן בעיקר מפני שאילתה ששכחה tenant predicate. לבידוד מול שירות compromised נדרשים roles נפרדים, policy lookup שאינו נשלט בידי הלקוח, partitions או databases נפרדים.

RLS אינה מכסה caches, ‏traces, ‏rerankers או LLM endpoints. היא גם אינה מחליפה resource-level ACL: ‏policy שמסננת רק לפי tenant מונעת cross-tenant access, אך אינה מכריעה אילו מסמכים בתוך tenant מותרים למשתמש.

PostgreSQL ו־pgvector כ־reference implementation

PostgreSQL מתאים כאשר metadata, ‏ACLs, ‏full-text search ו־vector search צריכים ACID, ‏joins ו־point-in-time recovery באותו operational boundary. הוא אינו בחירה אוטומטית לכל scale: ‏HNSW build, ‏vacuum, ‏WAL והעומס הטרנזקציוני חולקים CPU, זיכרון ו־I/O. השיקולים האלה חשובים במיוחד במהלך פיתוח מערכת SaaS, שבה tenants רבים עשויים לחלוק את אותה תשתית.

הדוגמה הבאה משתמשת בטבלה פיזית נפרדת ל־generation שמפיק vectors בעלי 1,536 dimensions. המספר הוא assumption של הדוגמה, לא המלצה על מודל מסוים. acl_tokens מייצג materialized allow-list פשוטה; מערכות עם deny rules, ‏inheritance או ABAC צריכות policy model עשיר יותר.

CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE rag_chunk_gen_42 ( tenant_id uuid NOT NULL, source_system text NOT NULL, source_id text NOT NULL, source_version text NOT NULL, source_sequence bigint, chunk_ordinal integer NOT NULL CHECK (chunk_ordinal >= 0), chunker_version text NOT NULL, embedding_model text NOT NULL, embedding_revision text NOT NULL, acl_revision text NOT NULL, acl_tokens text[] NOT NULL, content_hash bytea NOT NULL, content text NOT NULL, content_tsv tsvector GENERATED ALWAYS AS ( to_tsvector('simple', coalesce(content, '')) ) STORED, embedding vector(1536) NOT NULL, deleted_at timestamptz, PRIMARY KEY ( tenant_id, source_system, source_id, chunk_ordinal )
);

ב־generation חדש שאינו משרת traffic אפשר בדרך כלל לבנות indexes רגילים, שהם מהירים ופשוטים יותר מ־CREATE INDEX CONCURRENTLY. בנייה concurrent נדרשת כאשר הטבלה כבר משרתת writes או reads שאסור לחסום; היא גם אינה יכולה לרוץ בתוך transaction block.

CREATE INDEX rag_chunk_gen_42_tsv_idx ON rag_chunk_gen_42 USING gin (content_tsv);

CREATE INDEX rag_chunk_gen_42_acl_idx ON rag_chunk_gen_42 USING gin (acl_tokens);

CREATE INDEX rag_chunk_gen_42_hnsw_idx ON rag_chunk_gen_42 USING hnsw (embedding vector_cosine_ops);

CREATE INDEX rag_chunk_gen_42_scope_idx ON rag_chunk_gen_42 ( tenant_id, embedding_model, embedding_revision );

simple הוא baseline שמרני למידע רב־לשוני, ולא analyzer עברי איכותי. יש למדוד tokenization, ‏morphology, טקסט מעורב בעברית ובאנגלית ומונחים טכניים על ה־corpus בפועל. בחלק מהמערכות יידרש analyzer מותאם או search engine ייעודי.

ב־pgvector 0.8.6 ברירות המחדל של HNSW כוללות m=16, ‏ef_construction=64 ו־hnsw.ef_search=40. הגדלת ef_construction מעלה את עלות הבנייה ויכולה לשפר recall. הגדלת ef_search מרחיבה את החיפוש במחיר latency וזיכרון. ערכים שעבדו על corpus אחד אינם עוברים אוטומטית ל־workload אחר.

BEGIN;

SELECT set_config('app.tenant_id', $2::text, true);
SET LOCAL hnsw.ef_search = 100;
SET LOCAL hnsw.iterative_scan = strict_order;

SELECT source_id, source_version, chunk_ordinal, content, embedding <=> $1 AS distance
FROM rag_chunk_gen_42
WHERE tenant_id = $2::uuid AND embedding_model = $3 AND embedding_revision = $4 AND acl_tokens && $5::text[] AND deleted_at IS NULL
ORDER BY embedding <=> $1
LIMIT $6;

COMMIT;

ה־ACL tokens חייבים להיגזר בשרת מזהות מאומתת; אין לקבל אותם ישירות מהלקוח. בנוסף, GIN על acl_tokens אינו מבטיח ש־PostgreSQL ישלב אותו עם HNSW באותו access path. בחיפוש approximate, אינדקס ה־ANN מפיק candidates וה־filters מסירים את מי שאינו מתאים.

ה־WHERE אינו נעקף, אך ייתכן שיוחזרו פחות תוצאות מה־LIMIT משום שלא נסרקו מספיק candidates מורשים. Iterative scans, הזמינים מ־pgvector 0.8.0, יכולים להמשיך לסרוק עד למציאת תוצאות נוספות או עד limits מוגדרים. זהו trade-off של recall מול latency וזיכרון, ולא תיקון ללא עלות.

אין להכניס vectors ממודלים שונים לאותו HNSW index רק מפני שמספר ה־dimensions זהה. גם אם filter מסיר לבסוף את ה־generation הלא נכון, מבנה ה־ANN עצמו נבנה על מרחקים בין vectors שאינם בהכרח בני־השוואה. טבלה, partition או index נפרדים לכל embedding space הם boundary בטוח יותר.

Shared ANN index יוצר noisy-neighbor גם בלי החזרת שורות אסורות: צפיפות vectors של tenant אחד יכולה להשפיע על recall ועל זמן החיפוש של tenant אחר. בנוסף לבידוד משאבים, מערכות בעלות דרישות סודיות מחמירות צריכות לבחון גם timing side channels. Partitioning, טבלאות נפרדות או database לכל security domain מספקים בידוד חזק יותר, במחיר schema management, ‏connection pressure ו־capacity fragmentation.

Runtime resilience מתחיל ב־backpressure

Pipeline שמבצע parsing ו־embedding חייב להיות demand-bounded. אם embedding provider מאט, consumers שממשיכים לקרוא queue ללא הגבלה ממירים latency ל־memory pressure, ‏connection exhaustion ו־retry storm. יש להגביל in-flight requests, ‏batch size ו־per-tenant concurrency, ולהקטין prefetch או לעצור consumption כאשר downstream saturation עולה.

Retries מותרים רק לשגיאות שסווגו transient, עם exponential backoff, ‏jitter ו־deadline כולל. שגיאת validation, ‏dimension mismatch או קובץ בלתי נתמך צריכות להגיע ל־quarantine או ל־DLQ עם reason code. Retry אינסופי אינו recovery strategy, ו־DLQ ללא owner, ‏alert ו־replay procedure הוא רק storage לשגיאות שלא טופלו.

ב־query path יש לחלק את ה־deadline בין השלבים. אם תקציב הבקשה הוא שתי שניות, שלושה dependencies עם timeout של שתי שניות כל אחד מפרים את החוזה. Cancellation צריך להתפשט ל־DB query, ל־reranker ול־LLM call, כדי לא לצרוך compute לאחר שהלקוח ניתק.

  • כשל query embedding עשוי לאפשר lexical-only retrieval, אם המוצר מסמן שהמסלול degraded.
  • כשל reranker יכול לחזור ל־fusion ranking.
  • כשל authorization source אינו הופך ל־allow; ברירת המחדל היא deny, או שימוש ב־snapshot רק אם freshness policy מתירה זאת.
  • כשל LLM יכול להחזיר evidence בלבד או שגיאה מוגדרת, ולא תשובה שאינה מבוססת.
  • עומס של tenant אחד צריך להיעצר באמצעות quotas ו־fair scheduling לפני שהוא מרעיב tenants אחרים.

Observability ו־evaluation של מערכת לא דטרמיניסטית

Telemetry צריכה להפריד בין latency של authentication, ‏query embedding, ‏dense retrieval, ‏lexical retrieval, ‏policy filtering, ‏reranking, ‏context assembly ו־generation. מדדי p50 מסתירים queueing ו־tail latency; נדרשים גם p95 ו־p99, ‏queue lag, מספר candidates לפני ואחרי ACL, ‏empty-result rate, ‏tokens, ‏cache hit ratio ו־generation distribution.

במסלול ה־ingestion יש למדוד source-to-index freshness, ‏revisions ישנים שנדחו, stale-writer rejections, parser quarantine rate, ‏embedding retries, ‏publication lag, ‏ACL staleness ו־reconciliation drift. פילוח לפי tenant ו־source system נדרש לצורך diagnosis, אך יש להגביל cardinality במערכת ה־metrics.

אין לרשום prompts, ‏chunks או תשובות מלאות כברירת מחדל. Traces עלולים להפוך למאגר מידע רגיש שעוקף ACL ו־retention. עדיף לשמור IDs, ‏hashes, ‏sizes, ‏model revision, ‏policy revision ו־timings, עם sampling ו־access controls. בשימוש ב־OpenTelemetry יש להצמיד את גרסאות ה־core וה־GenAI conventions ולתכנן migration כאשר attributes משתנים או מסומנים deprecated.

Retrieval evaluation קודמת ל־answer evaluation

Evaluation צריכה להפריד בין שלושה סוגי כשל:

  • Retrieval failure: הראיה הנכונה לא הוחזרה.
  • Context failure: הראיה הוחזרה, אך הוסרה, נחתכה או מוקמה בצורה לא יעילה.
  • Generation failure: הראיה הגיעה למודל, אך התשובה אינה נתמכת בה.

ל־retrieval יש למדוד Recall@k, ‏MRR או nDCG לפי סוג המשימה, ולהשוות approximate search ל־exact baseline על corpus מייצג. סט הבדיקה צריך לכלול עברית, אנגלית, טקסט מעורב, identifiers, שאלות שאין עליהן תשובה ותרחישי ACL. ממוצע גלובלי עלול להסתיר regression חמור בשפה, ב־source system או ב־security domain מסוימים.

ל־generation יש לבדוק citation correctness, ‏unsupported claims, ‏schema validity ויכולת להימנע מתשובה כאשר אין evidence מספיק. Model revision, ‏prompt revision, ‏temperature ו־retrieval generation חייבים להישמר עם תוצאת הניסוי כדי לאפשר השוואה בין releases.

Shadow traffic יכול לזהות regressions לפני cutover, אך הוא כפוף לאותם ACLs ולמדיניות data processing. אין לשלוח context מורשה לספק נוסף רק משום שהפלט אינו מוצג למשתמש.

Prompt injection אינו נפתר באמצעות delimiters

מסמך שנשלף הוא untrusted data גם אם הוא נמצא ב־wiki פנימי. הוא עשוי להכיל הוראות שמנסות לשנות את התנהגות המודל, לחשוף context אחר או להפעיל tools. Delimiters והוראת מערכת להתעלם מהוראות במסמכים עשויים להפחית סיכון, אך אינם security boundary.

אם למודל יש tools, כל tool call חייב לעבור authorization עצמאי, schema validation, ‏allowlist ו־rate limit. Retrieved content אינו מעניק permission. יש להפריד בין ה־data plane, שבו נמצאים המסמכים, לבין ה־control plane, שמחליט אילו פעולות מותרות.

גם provider boundary צריך להיות מפורש. שליחת chunk לשירות embedding, ‏reranking או generation היא העברת מידע לצד נוסף. Data residency, ‏retention, ‏training policy, ‏encryption ו־deletion semantics הם חלק מחוזה המערכת, ולא הנחה שנגזרת משם המוצר.

Recovery ומחיקה חוצים את כל שכבות המערכת

העובדה ש־ANN index הוא derived state אינה אומרת שאין צורך לגבות אותו. אם rebuild מלא נמשך מעבר ל־RTO, ‏snapshot של האינדקס או replica חם עשויים להיות מוצדקים. ההחלטה תלויה בזמן ה־backfill, בקצב ה־embedding, במגבלות הספק ובעלות האחסון.

  1. משחזרים source catalog, ‏canonical documents, ‏sync state ו־generation registry.
  2. מיישרים queue offsets או מבצעים replay מנקודת watermark ידועה.
  3. משחזרים index snapshot או בונים את הנגזרת מחדש.
  4. מריצים reconciliation מול המקור.
  5. מפעילים generation רק לאחר validation של completeness ו־ACL freshness.

Point-in-time recovery של PostgreSQL אינו מבטיח התאמה אוטומטית ל־offset של queue חיצוני. יש לשמור checkpoint שניתן לקשור ל־DB state או להסתמך על replay idempotent. אחרת restore עלול ליצור חור בעיבוד או כפילויות שאינן מזוהות.

מחיקה צריכה לחצות canonical store, ‏chunks, ‏embeddings, ‏caches, ‏traces ו־provider stores בהתאם למדיניות retention. מחיקה מה־index בלבד אינה מספקת. במקביל, דרישות audit עשויות לחייב שמירת tombstone או metadata מינימלי; יש להפריד בין הוכחת מחיקה לבין שמירת התוכן שנמחק.

תרחישי כשל שצריכים להופיע ב־design review

Trigger Propagation Detection Mitigation
UPSERT ישן מגיע אחרי DELETE ה־tombstone נדרס והמסמך חוזר ל־index ולתוצאות מונה stale revisions, השוואת sequence ו־reconciliation מול המקור Source-specific ordering, ‏tombstone sequence ו־conditional publication
Worker ישן מסיים אחרי worker חדש Artifact ישן מתפרסם מעל גרסה עדכנית Fencing-token rejection ו־audit של publication attempts Lease מוגבל בזמן ו־compare-and-swap בשלב publication
ACL בוטל אך ה־snapshot לא עודכן Chunk ממשיך לעבור retrieval, ‏reranking ו־generation מדד ACL age, השוואת policy revision ובדיקות revocation סינתטיות Freshness budget, ‏query-time check או fail closed
Query vector חדש מופעל מול index ישן ה־ANN מחזיר ranking שגוי ללא exception אימות generation tuple ו־regression ב־Recall@k Generation binding ואימות model revision, ‏dimensions ו־metric
ANN עם filter סורק מעט candidates תוצאת false no-answer למרות שקיים מידע מורשה פער מול exact baseline, יחס candidates לפני ואחרי ACL ו־short-result rate Iterative scan, ‏tuning, ‏partitioning או הרחבת candidate budget
ZIP bomb או parser exploit CPU, זיכרון או filesystem מתמלאים ו־workers מפסיקים להתקדם Sandbox termination, resource-limit events ו־quarantine reason codes Sandbox, ‏quotas, ‏network isolation ו־content validation
Prompt injection בתוך מסמך המודל מתייחס ל־data כהוראה או מנסה להפעיל tool Tool-call audit, policy violations ומבחני adversarial corpus הפרדת data מהוראות, tool allowlist ו־authorization עצמאי
Provider timeout תחת עומס Retries מגדילים עומס, ממלאים pools ומאריכים queue lag In-flight saturation, retry rate, breaker state ו־tail latency Backpressure, ‏circuit breaker, ‏jitter ו־bounded concurrency
Cutover לפני השלמת catch-up ה־generation הפעיל חסר revisions או tombstones Watermark lag, completeness manifest ו־cross-generation diff Validation gate ו־atomic activation רק לאחר replay מלא
Connection pool משמר tenant context בקשה חדשה משתמשת ב־security context של בקשה קודמת Integration tests, audit של tenant mismatch ו־transaction-bound assertions Transaction-local context, ‏reset discipline ו־roles מצומצמים
מחיקה מתבצעת מה־index בלבד המידע נשאר ב־cache, ב־traces, ב־canonical store או אצל provider Deletion manifest ובדיקת completion בכל storage domain Deletion workflow חוצה שכבות עם retention policy ו־audit

גם semantics של no-answer צריכים להיות מוגדרים. אפס candidates, ‏candidates שנחסמו ב־ACL, ‏timeout ב־retrieval ו־evidence חלש הם מצבים שונים. אין לחשוף למשתמש שמסמך חסוי קיים, אך telemetry פנימית צריכה להבחין בין הסיבות. הודעה חיצונית כללית יכולה להתקיים לצד reason code מוגן לצורכי diagnosis.

Engineering Note: Cutover אטומי בין דורות אינדקס

Design constraint: שינוי chunker או embedding model הוא migration של נתונים, ולכן אסור לערבב באותה שאילתה vectors מדורות שונים. ה־invariant הוא שכל בקשה מקבעת בתחילתה generation יחיד, הכולל מודל, dimensions, metric, metadata ו־ACL snapshot תואמים. ב־production יש לאמת completeness מול watermark ו־tombstones, למדוד recall, lag ושגיאות הרשאה, ורק אז להחליף pointer אטומי. את הדור הקודם מוחקים רק לאחר חלון rollback וניקוז כל ה־readers הישנים.

החלטות ארכיטקטוניות ו־trade-offs

החלטה ארכיטקטונית ב־RAG אינה בחירת מוצר בלבד. כל החלטה צריכה לתעד את ה־invariant שהיא מגינה עליו, consistency boundary, failure propagation, מנגנון detection, מסלול rollback והעלות התפעולית. בלי הרכיבים האלה, אמירה כמו ״נשתמש ב־vector database ייעודי״ אינה מגדירה מה קורה כאשר ה־metadata נכתב אך ה־vector לא פורסם, כאשר ACL בוטל או כאשר index generation חדש נכשל באמצע cutover.

החלטה תועלת עלות Boundary מרכזי
PostgreSQL עם pgvector Metadata, ‏ACL, lexical ו־vector search באותו transactional boundary HNSW, ‏vacuum, ‏WAL והעומס הטרנזקציוני מתחרים על משאבים ה־atomicity אינה כוללת queue, object store או ספק embedding חיצוני
Vector service ייעודי Sharding, scale ויכולות ANN ייעודיות עוד מערכת מבוזרת, replication ו־on-call surface נדרש protocol לסנכרון vectors, metadata ו־ACL
Shared ANN index פחות indexes וניצול capacity טוב Noisy-neighbor, filter-sensitive recall ו־blast radius רחב Tenant predicate מונע החזרת שורה אסורה, אך אינו מבטל resource contention
Partition או database לכל domain Isolation, residency, backup ו־restore נקודתיים Index proliferation, connections ו־capacity fragmentation מצמצם blast radius אך מגדיל את מורכבות provisioning ו־migrations
Materialized ACL Read latency נמוך ופחות dependencies שכפול policy, reindex והרשאות stale גבול האבטחה תלוי ב־freshness budget
Query-time authorization הכרעה קרובה למדיניות העדכנית Latency, dependency availability ועלות לכל query כשל בשירות ההרשאות חייב להסתיים ב־deny
Publication לכל מסמך Freshness גבוהה ושינוי מצומצם בכל transaction יותר index churn ויותר transitions מתאים כאשר אפשר להחליף את כל נגזרות המסמך אטומית
Corpus-level generation Cutover ו־rollback ברורים ללא ערבוב embedding spaces אחסון כפול, backfill ארוך ו־garbage collection כשל build אינו משפיע על readers כל עוד ה־pointer לא הוחלף
Cross-encoder reranker שיפור ranking לפני צריכת context יקר Compute, latency ושליחת תוכן לרכיב נוסף ACL נאכף לפני הרכיב; timeout חוזר ל־fused ranking
Long context שומר תלות פנימית במספר קטן של מסמכים Token cost, ‏noise, latency ו־lost-in-the-middle אינו מחליף retrieval, authorization או evidence selection

Trade-off נוסף הוא granularity של consistency. עקביות חזקה לכל corpus דורשת generation cutover, אחסון כפול וזמן backfill. עקביות ברמת מסמך מאפשרת freshness גבוהה יותר ועלות migration נמוכה יותר, אך בזמן rollout הקוראים עלולים לפגוש מסמכים שנבנו בגרסאות pipeline שונות. אם נבחרת עקביות ברמת מסמך, ה־query חייב להגביל candidates ל־embedding space תואם; אם נדרשת השוואה עקבית על כל ה־corpus, generation מלא הוא boundary מתאים יותר.

גם זמינות מול סודיות היא החלטה מפורשת. שימוש ב־ACL snapshot בעת כשל של authorization service משפר availability, אך מותר רק כל עוד גיל ה־snapshot נמצא בתוך freshness budget. לאחר החריגה, המשך שירות מגדיל את חלון החשיפה ולכן המערכת צריכה להיכשל במצב deny. אפשר להחזיר רק מקורות ציבוריים או evidence שהרשאתו אינה תלויה ב־snapshot שנכשל, במקום לבטל את הבקרה.

הבחירה אינה מתחילה במוצר vector database. תחילה מגדירים corpus size וקצב שינוי, latency ב־p95 וב־p99, ‏freshness של תוכן והרשאות, RPO ו־RTO, גבולות tenant, ‏data residency, ‏retention, עלות reindex והיכולת לבצע exact evaluation.

מערכת RAG production-ready צריכה לענות באופן חד על השאלות הבאות: מי מחזיק כל state; כיצד revision ישן נדחה; מהו חלון ה־ACL staleness; איזה generation פעיל; כיצד מתבצע rollback ללא reindex; מה קורה כאשר provider איטי; אילו נתונים נכנסים ל־trace; וכיצד מוכיחים שתשובה נשענה על source version מסוים.

מכאן נגזר גם תפקידו של בית תוכנה בפרויקט RAG. בחירת חברת פיתוח תוכנה לפרויקט כזה צריכה להתבסס על יכולתה להגדיר וליישם את הגבולות ההנדסיים האלה. האחריות אינה מסתיימת בבחירת מודל או ספריית orchestration. היא כוללת הגדרת contracts בין מערכות המקור לנגזרות, בניית failure semantics, בידוד הרשאות, תכנון migrations ויצירת מסלול מדיד ל־recovery ול־rollback.

העיקרון המסכם הוא להפריד בין source of truth, נגזרות versioned ו־runtime orchestration. ה־LLM יכול להשתנות, embedding model יכול להתחלף וה־index יכול להיבנות מחדש. Identity, ‏authorization, ‏provenance ו־state transitions חייבים להישאר contracts מפורשים שניתנים לבדיקה, ל־replay, ל־recovery ול־rollback.

שאלות טכניות על RAG למידע ארגוני

האם אפשר להבטיח exactly-once לכל מסלול ה־ingestion?

בדרך כלל לא מקצה לקצה, כאשר queue, ‏PostgreSQL, search store ושירות embedding הם transactional domains נפרדים. החוזה המעשי הוא at-least-once עם event IDs, ‏source sequence, fencing tokens, עיבוד דטרמיניסטי, idempotent writes ו־reconciliation. אפשר לאחד result, outbox ו־checkpoint באותה DB transaction, אך אין להחזיק אותה פתוחה בזמן קריאה ל־provider חיצוני.

כיצד מחליפים embedding model ללא downtime?

בונים generation חדש שכולל chunks, ‏vectors ואינדקסים התואמים למודל החדש. מתחילים מ־source watermark, משלימים backfill ו־replay לשינויים מאוחרים, מאמתים completeness ואיכות retrieval, ומפעילים את ה־generation באמצעות pointer אטומי. כל בקשה מקבעת generation, והגרסה הקודמת נשמרת עד שכל הבקשות וה־readers הישנים יצאו מחלון ה־rollback.

מדוע ACL filter בשאילתת ANN עלול להחזיר פחות תוצאות מה־LIMIT?

Approximate index סורק קבוצת candidates מוגבלת, ולאחר מכן ה־filters מסירים מועמדים שאינם מורשים. אם רבים מהם נפסלים, התוצאה עלולה להיות חלקית אף שקיימים vectors מורשים נוספים. אפשר להשתמש ב־iterative scans, להרחיב את candidate budget או לבודד tenants ו־security domains באמצעות partitioning. כל שינוי דורש מדידת recall, latency וזיכרון.

האם PostgreSQL RLS מספיק לבידוד tenants במערכת RAG?

לא לבדו. RLS הוא defense in depth למסד, אך אינו מכסה caches, ‏rerankers, ‏traces או LLM endpoints. בנוסף, superusers, ‏BYPASSRLS ובדרך כלל table owners יכולים לעקוף policies. ‏Custom tenant GUC גם אינו boundary מול application role עוין, ולכן בידוד חזק עשוי לדרוש roles, ‏partitions, databases נפרדים ומדיניות authorization ברמת המשאב.

מתי retrieval היברידי עדיף על vector-only?

כאשר ה־corpus כולל identifiers, ‏error codes, ‏symbols, מספרי חוזים, מונחים פנימיים או טקסט רב־לשוני. Lexical retrieval שומר על exact matching, בעוד dense retrieval מטפל בפרפרזות. Fusion ו־reranking מוסיפים latency ועלות, ולכן יש להצדיק אותם באמצעות evaluation על שאלות מהדומיין ולהשוות מול lexical-only, dense-only ו־exact baselines.

כיצד מטפלים ב־prompt injection שמגיע ממסמך פנימי?

מתייחסים לכל retrieved content כאל untrusted data. מפרידים בין instructions ל־evidence, אינם מעניקים permissions לפי תוכן המסמך, ומעבירים כל tool call דרך authorization, ‏allowlist, rate limit ו־schema validation עצמאיים. ACL נאכף לפני reranking או generation. Delimiters והוראות מערכת עשויים להפחית סיכון, אך אינם security boundary.