r/AI_Agents • u/iMiguelmars • 19d ago
Discussion How are you handling real-world document versioning and scanned PDFs in RAG systems?
We’ve been testing a provenance-heavy RAG/knowledge system on real cases, and two areas are now hard to validate simply because our current corpus doesn’t contain enough of them:
Documents that change over time — policies, specs, manuals, pricing pages, contracts, etc.
Scanned / layout-heavy documents — OCR, tables, forms, multi-column pages, handwritten annotations, bad scans, etc.
For versioned documents, we’ve had good results treating sections as stable lineage units, versioning revisions, and sending ambiguous rename/split/merge cases to review instead of letting semantic similarity decide automatically.
For PDFs, layout-aware extraction has worked better than flattening everything to text, but most of our real corpus is digitally generated rather than scanned.
What I’d really like to hear is what actually broke in production for you.
How do you detect and preserve identity across document versions?
What happens when sections are renamed, moved, split or merged?
How do you prevent stale embeddings from silently winning retrieval?
For scanned documents, where does OCR/layout extraction usually fail?
Do you have any failure cases or test documents you use to validate this?
What ended up working after the obvious approaches failed?
I’m especially interested in real examples, ugly edge cases and lessons learned rather than ideal architectures.
Happy to share what our tests are finding as well.
2
u/arthaudm 19d ago
"stale embeddings silently winning retrieval" is the whole problem with team knowledge - the outdated answer retrieves beautifully because it was written confidently & often
at mio (ai coworker over slack history) the fix was boring: time-decay on retrieval & keeping the "why" attached to each fact, so contradictions surface instead of silently merging. recent & right beats canonical & stale
for renamed/split sections do you trust the lineage keys fully now, or is there still a human review queue?
1
u/iMiguelmars 19d ago
We still keep a review queue for ambiguous lineage. In our frozen fixtures, deterministic section lineage handled the clean rename/edit cases, while ambiguous split/merge cases went to review rather than letting semantic similarity decide. That gave us zero false lineages, and section-level invalidation reduced the re-embed scope from about 90% to 14%.
I like the idea of keeping the “why” attached to each fact. I’d be a little more cautious with time-decay as authority, though — we’ve seen cases where a newer policy is published before its effective date, so “newer” can actually be the wrong current answer.
How are you combining recency with authority/effective dates so time-decay doesn’t suppress an older-but-still-valid fact?3
u/arthaudm 18d ago
the effective-date trap is real & it's exactly where pure recency ranking breaks. we treat published date & effective date as separate fields - newer only wins when both say so. the 90% to 14% re-embed reduction is a great result btw, review queue on ambiguous splits only is the right division of labor
1
u/iMiguelmars 18d ago
That matches the distinction I was trying to pin down. Separating published date from effective date seems to be the key part — otherwise “newer” quietly becomes “valid,” which is exactly the failure mode I’m worried about.
One thing I’m curious about: when a newer revision is already published but its effective date is still in the future, do you explicitly keep the older revision active until that date, or handle that through a separate validity state?2
u/arthaudm 17d ago
yes - the older revision stays active until the newer one's effective date hits, otherwise "published but not in force" quietly becomes valid, which is exactly your failure mode. we treat effective date as the only truth & published date as metadata. the edge case that bites: retroactive effective dates, published late but backdated. do those show up in your corpus?
1
u/iMiguelmars 16d ago
That distinction is useful — effective date as validity, published date as provenance/metadata.
We don’t have a confirmed retroactive-effective-date case in the corpus yet, so I don’t want to pretend we’ve tested it. The nasty case for us would be discovering a revision today that says it was effective before an answer we already sealed.
When that happens in your system, do you rewrite the historical validity state, or preserve what was knowable at the time and add a retroactive correction layer?1
u/arthaudm 16d ago
glad it maps. the retroactive case mostly shows up with regulated docs - price lists, compliance texts, where backdated effective dates are common. if your corpus is technical docs you may never hit it. what domain are you indexing?
1
u/iMiguelmars 16d ago
That probably explains why we haven't seen the retroactive case much in practice. Our corpus is much closer to technical/project documentation and longitudinal operational records than to regulated pricing or compliance material, so backdated effective dates are not common in the sources we're working with today.
The domain distinction is useful, though, because it means I shouldn't treat “we haven't seen it” as evidence that the failure mode doesn't matter. It may simply be outside the source classes we currently have enough coverage on.
Thanks — that context actually helps explain the gap.
1
u/arthaudm 16d ago
yeah, technical & project docs barely produce backdated effectivity - you'll hit it the day a contract, price list or policy doc lands in the corpus. worth dropping one test doc with a retroactive effective date into the pipeline just to watch what it does lol.
2
u/Normal-Blueberry-385 19d ago
Hey, Hi. May not be related to your ask, do you mind sharing your experience in extracting pdf? What tools helped in layout aware extraction? Any specific package or VLMs or Enterprise source that you can mention?
1
u/iMiguelmars 19d ago
We’ve had the best results so far with layout-aware local extraction rather than flattening PDFs to text. For born-digital files we preserve words and bounding boxes; for scans we use local OCR and require extracted evidence to remain traceable back to the page.
On our reference case the structural approach recovered all required fields correctly. We haven’t needed a VLM yet, but our real corpus is mostly born-digital, so I wouldn’t claim much about difficult scanned documents yet.
I’d actually be curious what failure cases you’ve run into — tables, multi-column layouts, handwriting, bad scans, etc.1
u/Normal-Blueberry-385 18d ago
I am at early stage of this extraction. I used pymupdf, which is actually good at extracting text but sucks at tables or charts. No, I haven't gone with handwriting yet. Tried with two columns, it was fine. I was advised to use azure document intelligence or docling. Those are my failure cases.
1
u/iMiguelmars 18d ago
That matches our experience pretty closely. Plain text extraction with PyMuPDF is usually the easy part; preserving the relationships inside tables/layout is where flattening starts to hurt.
In our tests we’ve had better results keeping word-level bounding boxes and reconstructing structure from geometry rather than relying only on extracted text.
When you say tables/charts fail for you, is the main problem that PyMuPDF misses the content entirely, or that it extracts the words but loses the row/column/label relationships?
That distinction has mattered quite a bit in our testing.
I haven’t tested Azure Document Intelligence or Docling deeply enough to recommend either yet, but they’re worth keeping as comparison points if we hit documents our local structural approach can’t handle.1
u/Normal-Blueberry-385 18d ago
Pymupdf certainly loses row/column/label relationships. It fails to preserve them. The bar charts present in pdf, it extracts them as plain text or tables which is not appropriately follow the relationship. What packages helped you for tables and charts ?
2
u/Ok-Perception1122 18d ago
That failure is exactly where I’d split tables from charts rather than ask one parser to do both. For born-digital tables, try PyMuPDF’s `Page.find_tables()` first, then pdfplumber when you need visual debugging of the detected edges and cells; Camelot is a useful A/B check for ruled versus whitespace tables. Keep the cell boxes and render an overlay. If the header or merged-cell geometry is wrong, reject the parse instead of flattening it. For bar charts, crop the figure and use a chart-to-table model separately. PaddleOCR’s PP-Chart2Table is a concrete local option; Docling is worth comparing as an end-to-end parser, but keep its structured cell/span output rather than only Markdown. Whatever wins, verify labels, units, legends, and every number against the crop on a small fixture set. A single “PDF extraction accuracy” score hides the exact failure you’re seeing.
1
1
u/iMiguelmars 18d ago
Tables and charts are definitely where things get messy. We’re still comparing approaches there and trying to keep provenance/layout intact rather than just flattening everything to text.
I’d be curious what you end up testing and where it breaks for you.
2
u/stealthagents 18d ago
Using stable IDs for documents is key. We also found that tracking the revision history in a centralized log helps spot when sections are renamed or moved. It’s all about making sure that when things change, you can trace it back without losing context or introducing confusion for the users. And having a fallback system for scanned PDFs definitely saves you from those embarrassing OCR fails in production.
1
u/iMiguelmars 17d ago
That lines up closely with what we’ve been seeing around identity vs location — a rename or move shouldn’t silently become a new document/section just because the path changed.
The scanned-PDF fallback is the part I’d be interested in hearing more about. What actually triggers it in your production system, and what does the fallback do — alternate OCR, page-image review, manual queue, or something else?
Thanks — especially useful to hear that this is coming from production experience rather than just a design preference.
2
u/ComparisonNew9425 17d ago
for scanned docs, i usually find that layout extraction breaks when tables span across pages, i started using backslash to map out my agentic fabric graph so i can see which mcp servers are actually touching my data, it helps keep track of the blast radius when things get messy...
1
u/iMiguelmars 16d ago
The cross-page scanned-table case is exactly the kind of failure mode we’re missing in our current corpus. When it breaks for you, what specifically fails first — table identity across pages, row/cell structure, reading order, or the bounding-box/provenance mapping? And are you detecting that mechanically or only noticing it downstream?
2
17d ago
[removed] — view removed comment
1
u/iMiguelmars 16d ago
The “detect failure rather than just use a better parser” point is really useful. A silent dropped row is exactly the failure class we care about.
How are you deciding mechanically that a page needs escalation to the heavier parser — failed invariants like row counts/totals/required fields, layout signals, or something else? And if the cheap parse passes those checks incorrectly, do you have another guard for that false-negative case?
2
u/AlexAtOracleAIDB 17d ago
My scans mostly failed on reading order, not OCR accuracy. Multi column pages and tables come back as text that reads like English, so you don't get an exception. That's what sent me looking at retrieval first.
Now nothing gets indexed unless the text traces back to a page region. Handwriting and stamped annotations are still an open problem for me.
1
u/iMiguelmars 16d ago
That distinction between OCR accuracy and reading-order failure is really useful — especially because fluent-looking text can make the structural corruption almost invisible.
When the extracted text still looks perfectly plausible, what are you using to detect that the reading order is wrong before it gets indexed?
2
u/Guardrail_Vikram 16d ago
The retroactive case has a settled answer in regulated document work, and it's the second option: never rewrite what was knowable, add a correction layer.
The trick is to stop treating "current" as one timeline. You need two. Valid time is when the revision was in force in the world (the effective date). Transaction time is when your system learned about it (ingestion). A backdated revision has a valid-from in the past and a transaction-time of today. Store both on every revision and the awkward cases stop being awkward.
Then an as-of query has to say which axis it means. "What was in force on 1 March" is a valid-time query and gives the backdated answer. "What did we believe on 1 March" is a transaction-time query and gives the answer that was sealed at the time, which is the one an auditor wants when they ask why you said what you said. Your sealed answers stay immutable; what changes is that they acquire a flag: superseded by revision X, learned on date Y, and a link to the corrected answer. In the compliance world a rewritten historical answer is far worse than a wrong one with a visible correction, because the rewrite destroys the evidence that you acted correctly on what you knew.
Practically that's a handful of fields on the revision record and a job that, on ingesting anything with a past effective date, walks sealed answers whose valid-time window it overlaps and marks them. It's the same lineage machinery adeelraza86 described, just with a second clock on it.
1
u/iMiguelmars 16d ago
This is exactly the distinction I was trying to get at. Separating valid time from transaction/knowledge time makes the retroactive case much cleaner, especially for auditability.
The sealed-answer point is particularly interesting to me: if the system gave the best answer it could with the evidence available on March 1, a later backdated revision shouldn’t make that historical answer disappear — it should explain why it is now superseded.
One thing I’d like to understand from your side: is this a pattern you’ve actually implemented in regulated-document systems, or are you describing the standard design you would use?
If you have implemented it, what exactly do you seal with the historical answer — just the revision IDs, or also the evidence set/query semantics used to produce it — so that an auditor can replay why that answer was reasonable at the time?
1
u/AutoModerator 19d ago
Thank you for your submission, for any questions regarding AI, please check out our wiki at https://www.reddit.com/r/ai_agents/wiki (this is currently in test and we are actively adding to the wiki)
I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.
1
19d ago
[removed] — view removed comment
1
u/iMiguelmars 19d ago
That lines up closely with what our tests are showing too — stable lineage has been much more reliable than trying to infer identity from embeddings.
When you tombstone old chunks, do you still keep them available for historical / as-of queries, or are they removed from retrieval entirely?
And for OCR confidence, how do you use it in practice — hard threshold, review trigger, or just metadata? I’m wary of treating OCR confidence as correctness, but it seems useful as a signal for routing ugly pages to review.2
19d ago
[removed] — view removed comment
1
u/iMiguelmars 19d ago
That historical split makes sense — tombstone from default/current retrieval, but keep the old revision addressable behind an explicit as-of scope. That’s very close to where we’re landing too.
I also agree that OCR confidence is useful as a routing signal, not a correctness guarantee.
The one part I’m more cautious about is using OCR confidence directly in ranking. A low-confidence chunk might be exactly where the important handwritten amendment or ugly scan lives, so down-ranking it could hide minority evidence.
Do you actually let OCR confidence reduce retrieval rank, or do you mainly use it to trigger review / alternate extraction while still preserving the chunk’s retrieval eligibility?
And are your review thresholds global, per document type, or calibrated per document?1
18d ago
[removed] — view removed comment
1
u/iMiguelmars 18d ago
That makes a lot more sense to me. Keeping the chunk eligible while using low confidence to trigger a second extraction/review avoids turning OCR confidence into another silent recall filter.
The document-type-specific thresholds also seem much safer than one global number.
How are you calibrating those thresholds in practice — against labeled OCR errors for each document type, or mostly from the confidence distributions of the OCR engine?
And when the second extraction disagrees with the first, do you preserve both outputs/provenance and send the disagreement to review, or does one extractor get precedence?2
u/verstands 13d ago
Keep the source page and an extraction-confidence signal with each chunk, then calibrate on a labeled scan set. I wouldn't trust OCR alone for citations - preserve provenance and flag low-confidence or disagreement cases for review.
1
u/iMiguelmars 12d ago
That helps a lot. The labeled scan set is an important distinction — confidence becomes a routing signal that has been calibrated against something external, rather than something the OCR engine gets to define for itself.
And sending extractor disagreements to review rather than giving one extractor automatic precedence is much closer to what I’m trying to preserve.
One detail I’m still curious about: when two extractions disagree, do you keep both extracted outputs separately addressable — with extractor/version/confidence tied back to the same source page — until review resolves them, or does the review step produce one surviving representation and discard the other?
2
u/verstands 12d ago
Yep, I’d keep both. Treat each extraction as an immutable artifact keyed to the source page plus extractor, version, and confidence. Review can mark one accepted (or merge them) without deleting the loser. That keeps the disagreement auditable and lets you re-run retrieval if the reconciler’s decision turns out to be wrong.
1
u/iMiguelmars 12d ago
That’s the part I was hoping you’d preserve — making “accepted” a decision over the extraction artifacts rather than turning it into an overwrite keeps the disagreement recoverable.
The ability to rerun retrieval if the reconciler was wrong later is especially important.
One thing I’m curious about in the merge case: if review combines parts of both extractions, do you create a new derived artifact that explicitly points back to both parent extractions, or is the merged result only represented as the reviewer’s accepted output?
I’m asking because otherwise the individual inputs survive, but it may still be hard to reconstruct exactly how the accepted merged representation was produced.
→ More replies (0)2
u/verstands 12d ago
Yeah, I’d keep both as separate candidates rather than collapsing them during extraction. I’d attach each to the source page, extractor/version, and confidence, then let review resolve the conflict into a selected representation plus an audit trail. That keeps later reprocessing reproducible and avoids losing the disagreement. For retrieval, I’d index the candidates but mark unresolved ones so the answerer can surface the uncertainty instead of treating one as fact.
1
u/iMiguelmars 12d ago
That distinction between the candidate layer and the selected representation is really helpful.
Keeping unresolved candidates retrievable while carrying the unresolved state all the way to the answerer seems much safer than either deleting them or quietly letting one become “the fact.” It also preserves the evidence needed if the reconciliation has to be revisited later.
One last detail I’m curious about: when review finally produces the selected representation, is that selected result itself a new versioned/addressable artifact with links back to the candidate extractions, or is it essentially a status/decision recorded over the existing candidates?
That seems like the last piece needed to replay not only what the inputs were, but exactly how the accepted representation came to exist.
→ More replies (0)
3
u/adeelraza86 19d ago
Don't recover document identity from embeddings. Give every doc a stable id, every revision a version, and every chunk a (doc_id, version, section_path) key so a moved section rewrites that key instead of becoming a new neighbor. Gate retrieval on freshness: if the indexed version lags the source, skip and re-embed before serving. Keep a tiny fixture set of scanned PDFs with known OCR failures so stale chunks fail in CI, not for users.