How to split documents before embedding. Get this wrong and RAG is useless.
The default recipe
Chunk size: 500 tokens (target)
Chunk overlap: 100 tokens (20%)
Split on: heading boundaries first, then paragraphs, then sentences
Preserve: section titles as prefixes in each chunk
Minimum chunk: 100 tokens (smaller merges into neighbor)
Maximum chunk: 1000 tokens (larger splits at sentence boundary)
Variant strategies
Semantic chunking
Group sentences by embedding similarity, split when similarity drops below threshold. Slower to compute but better retrieval quality on prose-heavy corpora.
Sliding window
Fixed-size windows with heavy overlap (50%). Good for narrow question-answer patterns. Wasteful on storage.
Recursive splitting
Try to split on \n\n, then \n, then ., then space. Falls back gracefully.
Document-type-aware
Code files: split on function/class boundaries
Markdown: split on H2/H3 boundaries
PDFs: extract by page, then apply text splitting
Slides: one slide = one chunk (or a group of related slides)
Spreadsheets: rows or logical groups; consider text-serialization
Before embedding, strip / normalize:
- Boilerplate headers/footers
- Watermarks
- Extra whitespace
- Repeated navigational text
- Table of contents entries (dedupe against body)
- Signatures and email routing
Contextual retrieval
Anthropic-recommended pattern: prepend each chunk with a short LLM-generated context summary (50–100 tokens) that says "This chunk is from [doc] discussing [topic]. Prior context is: [summary]." Improves retrieval by 30-50% but adds cost.
Multi-representation
Store multiple representations of each chunk:
- Raw text (as-is)
- Cleaned text (whitespace normalized)
- Summary (LLM-generated 1-line summary — searched with a different embedding)
- Entities extracted (for filter-based retrieval)
Retrieve using summary embeddings, return raw text.
Verification
For any RAG deployment, verify chunking with:
Random inspection — sample 20 chunks. Are they self-contained? Do they include enough context?
Reconstruction test — retrieve the top 5 chunks for a real question. Does the LLM produce a correct answer?
Ground-truth eval — for known answers, does the retrieval find the right chunk in top-5?
If retrieval@5 is below 80%, chunking or embedding needs work.
Retrieval filters:
- Copilot only retrieves chunks the user is authorized to see (permissions_group)
- Only current (not archived) unless user asks for history
- Only classification appropriate to the current session tool
What NOT to put in the KB
Personal drafts
Ephemeral status updates
Duplicates (any information more than once is wrong)
Anything > 18 months old without explicit re-review
Unstructured chat logs
Deprecated processes without archived tag
Individual PII of employees or customers beyond what's business-critical
Content that could go stale within a week
Refresh rules
Every doc has next_review — after that date, either re-approve or archive
Doc owner gets a monthly digest of docs coming due
Any change to a policy triggers a KB update within 5 business days
Retired docs move to 99-archived/ with a redirect stub in the original location if links exist
Update ACLs on permissions change (within 1 hour target)
User identity
Every query includes:
- Authenticated user identity (via SSO)
- Their role(s) at query time (fresh, not cached)
- Any context restrictions (e.g., "this session is for external customer response — restrict to public and internal only")
Auditing
Log for every retrieval:
- Timestamp
- User identity
- Query (redacted for PII)
- Chunks retrieved
- Chunks filtered (permissions denied)
- Response generated (redacted)
Retain per policy (typically 90 days minimum for security review).
Testing
Every RAG deployment has these red-team tests:
Direct ask: User A queries for content in a doc User A can't see. Must return "not found" or refusal, not the content.
Indirect ask: User A queries for the topic of a restricted doc. Must not surface the restricted doc's information.
Permission change: After removing User A from a group, they can't retrieve previously-authorized content.
Injection: Adversarial input in a document tries to override permission checks.
Run these tests before every RAG deployment, and quarterly thereafter.