Add rag-ingestion module: PDF, Word and HTML readers and chunking strategies compared by retrieval hit rate; metadata in the embedding, content-hash idempotent ingestion, re-embedding cost of an edit
Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_0146opDKBYtADXvkc39zdiyw
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
# What each reader returns for the same 36-paragraph runbook
|
||||
|
||||
corpus: 10665 characters of body text (title, 12 headings, 36 paragraphs)
|
||||
|
||||
--- PagePdfDocumentReader, default config ---
|
||||
Documents returned: 4 (one per PDF page)
|
||||
metadata of page 1: {file_name=runbook.pdf, page_number=1}
|
||||
pages whose text contains the running footer: 4 of 4
|
||||
characters as read: 30358, after collapsing runs of whitespace: 10812 (the corpus itself is 10665)
|
||||
page 1 ends with: " Helios Operations Runbook - Confidential - Page 1"
|
||||
page 2 starts with: "they receive every page but are not expected to act, and they write up what"
|
||||
paragraphs that no single page contains whole, because a page break cuts them: 2 of 36
|
||||
|
||||
--- PagePdfDocumentReader with pageBottomMargin ---
|
||||
pageBottomMargin= 30: footer on 4 of 4 pages, answer fragments still in the text: 37 of 37
|
||||
pageBottomMargin= 60: footer on 0 of 4 pages, answer fragments still in the text: 37 of 37
|
||||
pageBottomMargin=100: footer on 0 of 4 pages, answer fragments still in the text: 36 of 37
|
||||
no margin: footer on 4 of 4 pages, answer fragments still in the text: 37 of 37
|
||||
|
||||
--- TikaDocumentReader on the .docx ---
|
||||
Documents returned: 1
|
||||
metadata: {source=runbook.docx}
|
||||
characters: 10657, contains the footer text: true
|
||||
starts with: "Helios Cloud Platform Operations Runbook\n1. Backups and Restore\nEvery production databas"
|
||||
blank-line-separated blocks in the text: 1, newline-separated lines: 50
|
||||
answer fragments in the text: 37 of 37
|
||||
|
||||
--- TikaDocumentReader on the same PDF and HTML ---
|
||||
.pdf -> 1 Document, 10861 characters, metadata keys [source]
|
||||
.html -> 1 Document, 10764 characters, contains "Cookie settings": true, contains tracking script text: false
|
||||
|
||||
--- JsoupDocumentReader on the .html ---
|
||||
default config: 1 Document, 10712 characters, contains nav link text "Status": true, contains "Cookie settings": true
|
||||
default metadata: {description=How Helios runs production., title=Helios Cloud Platform Operations Runbook}
|
||||
selector=article: 1 Document, 10615 characters, nav text: false, footer text: false
|
||||
selector=section, groupByElement=true: 12 Documents
|
||||
first section Document: 1188 characters, starts "1. Backups and Restore Every production database is snapshot"
|
||||
with metadataTag("description") and additionalMetadata: {description=How Helios runs production., format=html, title=Helios Cloud Platform Operations Runbook}
|
||||
@@ -0,0 +1,23 @@
|
||||
# TokenTextSplitter chunk size against retrieval hit rate (all-MiniLM-L6-v2, 37 questions)
|
||||
|
||||
source: runbook.docx read by TikaDocumentReader, 2172 cl100k tokens, 37 questions, embedding model all-MiniLM-L6-v2 (max 256 WordPiece tokens)
|
||||
hit@k: the question's answer is inside one of the k best chunks. cut: answers that no chunk contains whole.
|
||||
top3Toks: tokens in the three best chunks, the context a prompt would carry per question.
|
||||
|
||||
--- defaults except chunkSize (minChunkSizeChars=350) ---
|
||||
chunkSize chunks meanToks hit@1 hit@3 hit@5 cut top3Toks
|
||||
32 68 31.9 22/37 29/37 30/37 3 96
|
||||
64 34 63.9 27/37 33/37 33/37 2 191
|
||||
128 19 114.1 31/37 35/37 36/37 0 335
|
||||
256 9 241.1 24/37 31/37 36/37 0 720
|
||||
512 5 434.0 21/37 33/37 37/37 0 1278
|
||||
800 3 723.3 17/37 37/37 37/37 0 2170
|
||||
|
||||
--- same sizes with minChunkSizeChars=10 ---
|
||||
chunkSize chunks meanToks hit@1 hit@3 hit@5 cut top3Toks
|
||||
32 88 24.6 31/37 32/37 32/37 0 76
|
||||
64 42 51.7 27/37 32/37 34/37 1 160
|
||||
128 19 114.1 31/37 35/37 36/37 0 335
|
||||
256 9 241.1 24/37 31/37 36/37 0 720
|
||||
512 5 434.0 21/37 33/37 37/37 0 1278
|
||||
800 3 723.3 17/37 37/37 37/37 0 2170
|
||||
@@ -0,0 +1,18 @@
|
||||
# Chunking strategies on the same runbook (all-MiniLM-L6-v2, 37 questions)
|
||||
|
||||
strategy chunks meanToks hit@1 hit@3 hit@5 cut top3Toks
|
||||
TokenTextSplitter 128 tokens 19 114.1 31/37 35/37 36/37 0 335
|
||||
TokenTextSplitter 64 tokens, minChars=10 42 51.7 27/37 32/37 34/37 1 160
|
||||
SentenceSplitter 128 tokens, no overlap 20 108.5 24/37 33/37 34/37 0 341
|
||||
SentenceSplitter 128 tokens, 1 sentence overlap 24 113.5 30/37 33/37 36/37 0 337
|
||||
SentenceSplitter 64 tokens, no overlap 41 52.8 30/37 34/37 34/37 0 161
|
||||
one chunk per paragraph 37 56.4 34/37 36/37 36/37 0 176
|
||||
one chunk per paragraph + section heading 37 62.7 32/37 35/37 36/37 0 194
|
||||
one chunk per section (JsoupDocumentReader) 12 178.7 31/37 37/37 37/37 0 529
|
||||
|
||||
--- 37 chunks from 36 paragraphs ---
|
||||
last paragraph chunk: section=12. Decommissioning, text="Helios Operations Runbook - Confidential"
|
||||
|
||||
--- what a paragraph chunk with its heading looks like ---
|
||||
metadata: {chunk_index=2, section=1. Backups and Restore, source=runbook.docx}
|
||||
text: 1. Backups and Restore\nPoint-in-time recovery is available for the last 7 days. To request a recovery, the service owne
|
||||
@@ -0,0 +1,14 @@
|
||||
# Metadata and the text that gets embedded
|
||||
|
||||
chunk metadata: {chunk_index=0, content_hash=86f5d5577cede265, format=docx, section=1. Backups and Restore, source=runbook.docx}
|
||||
|
||||
--- the text each model would embed for the same chunk ---
|
||||
MetadataMode.NONE : \n\nEvery production database is snapshotted once a day at 01:30 UTC. Daily snapshots are kept for 35 days, af
|
||||
MetadataMode.EMBED: chunk_index: 0\nformat: docx\nsection: 1. Backups and Restore\nsource: runbook.docx\ncontent_hash: 86f5d5577ce
|
||||
|
||||
--- hit rate, one chunk per paragraph, 37 questions ---
|
||||
configuration chunks meanToks hit@1 hit@3 hit@5 cut top3Toks
|
||||
MetadataMode.NONE 37 56.4 34/37 36/37 36/37 0 176
|
||||
MetadataMode.EMBED, every key embedded 37 56.4 29/37 36/37 36/37 0 175
|
||||
MetadataMode.EMBED, four keys in excludedEmbedMetadataKeys 37 56.4 31/37 35/37 36/37 0 174
|
||||
embedded text with the four keys excluded: section: 1. Backups and Restore\n\nEvery production database is snapshotted once a day at 01:30 UTC. Daily sna
|
||||
@@ -0,0 +1,15 @@
|
||||
# One runbook, three formats, TokenTextSplitter(128), 37 questions
|
||||
|
||||
reader and configuration chunks meanToks hit@1 hit@3 hit@5 cut top3Toks
|
||||
PDF PagePdfDocumentReader, each page split alone 36 106.6 24/37 32/37 33/37 1 333
|
||||
PDF pages joined first, footer still there 34 112.9 26/37 33/37 34/37 1 338
|
||||
PDF pages joined, pageBottomMargin=60 34 110.5 27/37 33/37 34/37 1 330
|
||||
PDF pages joined, margin 60, whitespace collapsed 19 117.7 27/37 36/37 37/37 0 348
|
||||
DOCX TikaDocumentReader 19 114.1 31/37 35/37 36/37 0 335
|
||||
HTML JsoupDocumentReader, default selector (body) 19 114.8 30/37 34/37 36/37 0 342
|
||||
HTML JsoupDocumentReader, selector=article 19 113.8 29/37 34/37 36/37 0 342
|
||||
|
||||
--- tokens the splitter had to cut, by reader ---
|
||||
PDF pages as read : 30358 characters, 3933 tokens
|
||||
PDF whitespace collapsed: 10655 characters, 2251 tokens
|
||||
DOCX : 10657 characters, 2172 tokens
|
||||
@@ -0,0 +1,18 @@
|
||||
# Re-ingesting runbook.docx into pgvector 0.8.7 (all-MiniLM-L6-v2, 384 dims)
|
||||
|
||||
|
||||
--- a naive loop: read, split, add, with random chunk ids ---
|
||||
run 1: rows in table = 19, texts embedded so far = 19
|
||||
run 2: rows in table = 38, texts embedded so far = 38
|
||||
run 3: rows in table = 57, texts embedded so far = 57
|
||||
rows saying "kept for 35 days": 3
|
||||
|
||||
--- TokenTextSplitter(128): windows of tokens ---
|
||||
1. first ingest of v1 chunks=19 embedded=19 unchanged= 0 deleted= 0 | rows=19, texts embedded in total=19
|
||||
2. same file again chunks=19 embedded= 0 unchanged=19 deleted= 0 | rows=19, texts embedded in total=19
|
||||
3. same file a third time chunks=19 embedded= 0 unchanged=19 deleted= 0 | rows=19, texts embedded in total=19
|
||||
4. v2: one paragraph edited chunks=19 embedded= 1 unchanged=18 deleted= 1 | rows=19, texts embedded in total=20
|
||||
rows saying "kept for 35 days": 0, rows saying "kept for 45 days": 1
|
||||
best match for the snapshot question now contains "45 days": true
|
||||
5. v3: section 12 deleted from the file chunks=17 embedded= 1 unchanged=16 deleted= 3 | rows=17, texts embedded in total=22
|
||||
rows saying "180 days in advance" (a fact only section 12 holds): 0
|
||||
@@ -0,0 +1,12 @@
|
||||
# Same sequence with one chunk per paragraph
|
||||
|
||||
|
||||
--- HeadingAwareChunker: one chunk per paragraph ---
|
||||
1. first ingest of v1 chunks=37 embedded=37 unchanged= 0 deleted= 0 | rows=37, texts embedded in total=37
|
||||
2. same file again chunks=37 embedded= 0 unchanged=37 deleted= 0 | rows=37, texts embedded in total=37
|
||||
3. same file a third time chunks=37 embedded= 0 unchanged=37 deleted= 0 | rows=37, texts embedded in total=37
|
||||
4. v2: one paragraph edited chunks=37 embedded= 1 unchanged=36 deleted= 1 | rows=37, texts embedded in total=38
|
||||
rows saying "kept for 35 days": 0, rows saying "kept for 45 days": 1
|
||||
best match for the snapshot question now contains "45 days": true
|
||||
5. v3: section 12 deleted from the file chunks=34 embedded= 0 unchanged=34 deleted= 3 | rows=34, texts embedded in total=39
|
||||
rows saying "180 days in advance" (a fact only section 12 holds): 0
|
||||
@@ -0,0 +1,9 @@
|
||||
# Chunks to embed again after appending one sentence to one paragraph (all 36 paragraphs tried)
|
||||
|
||||
splitter chunks mean median max
|
||||
TokenTextSplitter 128 tokens 19 5.0 2 17
|
||||
TokenTextSplitter 256 tokens 9 2.5 2 10
|
||||
SentenceSplitter 128, no overlap 19 2.4 2 8
|
||||
SentenceSplitter 128, 1 sentence overlap 24 3.1 2 13
|
||||
one chunk per paragraph 36 1.0 1 1
|
||||
columns: chunks in the unedited file; mean, median and max chunks re-embedded over the 36 edits
|
||||
@@ -0,0 +1,29 @@
|
||||
# Filtering retrieval by the metadata written at ingestion
|
||||
|
||||
rows in table: 73 (runbook.docx and runbook.html, one chunk per paragraph)
|
||||
JsoupDocumentReader selector=article: 1 line(s) of text -> 0 chunks from the paragraph chunker
|
||||
selector="article h1, article h2, article p" with separator "\n": 49 lines -> 36 chunks
|
||||
rows per format: [docx=37, html=36]
|
||||
question: "How long is data kept?"
|
||||
|
||||
--- no filter, top 5 ---
|
||||
0.640 html 12. Decommissioning After a customer contract ends, the customer's data is del
|
||||
0.640 docx 12. Decommissioning After a customer contract ends, the customer's data is del
|
||||
0.588 html 1. Backups and Restore Every production database is snapshotted once a day at 01:
|
||||
0.588 docx 1. Backups and Restore Every production database is snapshotted once a day at 01:
|
||||
0.569 html 9. Logging and Monitoring Metrics are stored at 10 second resolution for 7 days, the
|
||||
(5 results)
|
||||
|
||||
--- filter: section == '9. Logging and Monitoring' ---
|
||||
0.569 docx 9. Logging and Monitoring Metrics are stored at 10 second resolution for 7 days, the
|
||||
0.569 html 9. Logging and Monitoring Metrics are stored at 10 second resolution for 7 days, the
|
||||
0.514 docx 9. Logging and Monitoring Application logs are searchable for 90 days in the hot tie
|
||||
0.514 html 9. Logging and Monitoring Application logs are searchable for 90 days in the hot tie
|
||||
0.188 docx 9. Logging and Monitoring To keep paging humane, each on-call shift has a noise budg
|
||||
(5 results)
|
||||
|
||||
--- filter: format == 'html' && section == '1. Backups and Restore' ---
|
||||
0.588 html 1. Backups and Restore Every production database is snapshotted once a day at 01:
|
||||
0.468 html 1. Backups and Restore Point-in-time recovery is available for the last 7 days. T
|
||||
0.254 html 1. Backups and Restore A backup that has never been restored is only a hope. On t
|
||||
(3 results)
|
||||
Reference in New Issue
Block a user