Before Full Agentic RAG: Parsing Decisions You Should Control
A rule-based document parsing dispatcher gives enterprise RAG pipelines explicit, inspectable control over which methods run and why.
There is a lot of discussion about agentic AI, and the principle is simple: let the model decide.
- For a general-purpose assistant, that is fine: let the agent try, watch what it does.
- For an enterprise RAG process, it is dangerous. The answers feed real decisions, so we have to know every step and control the flow between the steps.
This article applies that position to the step where “let the model decide” is most tempting: picking the right parsing method for each document. We build that choice as a dispatcher we control. It reads the PDF’s nature, plans the methods that fit, executes them in order, and synthesizes every output into one enriched corpus for retrieval, generation and evaluation. Each decision is explicit and logged, so the plan can be read and checked before anything runs.
This article extends the document parsing brick of Enterprise Document Intelligence, the series that builds an enterprise RAG system from four bricks. It closes that brick by composing the methods the series built one at a time: fitz for the text layer, Azure Document Intelligence and Docling for tables, a vision LLM for charts and diagrams, EasyOCR for pages with no text layer, image captioning for what the pipeline would otherwise skip, and two ways of recovering a table of contents, from the printed sommaire or from body typography alone.
🧭 New to the series? Start with the map: Prompt, Context, Loop sets out the three engineering layers every RAG system is built on — the prompt (the call itself), the context (what fills the model’s window), the loop (when the next call fires and when it stops) — and walks the whole series through that lens, article by article. It is the shortest way to see what is covered and where this one sits.
Where this article sits: it closes brick 1 by composing every parsing method the previous articles introduced – Image by author
📓 The runnable notebook runs parse_pdf_agentic() on the attention paper (data/paper/1706.03762v7.pdf), prints the detected nature, the four-step plan the dispatcher produced, and the merged corpus dict with a 15-row native toc_df and a 1048-row line_df: doc-intel/notebooks-vol1.
1. Why the scare quotes on “agentic”
Every RAG vendor now labels their document-parsing loop agentic. Open the code and it is almost always the same thing: a rule-based dispatcher that reads a few file signals, picks an ordered plan of methods, runs each in sequence, and folds the outputs. The LLMs live inside individual leaves (a heading validation loop, a vision reader on figures, an OCR post-processor). No LLM at the dispatch layer decides what to run next. No feedback loop where an agent watches an output and re-plans.
That is exactly what the dispatcher in this article does. So calling it agentic is a stretch, and calling it agentic without quotes would be selling the same buzzword-inflation the rest of the market sells. The quotes stay.
The honest picture, function by function:
detect_document_nature(pdf_path): six deterministic flags read fromline_df/span_df.is_scanned,has_native_outline,has_sommaire,is_composite,has_rich_figures,has_tables_signal. Docstring says it plainly: “deterministic; no LLM”.plan_parsing_methods(nature): pure Pythonif / elifon the nature label. Every branch returns a hard-coded ordered list ofMethodStep. No LLM.parse_pdf_agentic(pdf_path, llm_parse=…): loops over the plan and calls one adapter per method. Thellm_parsekwarg is forwarded to methods that use one (the body-structure loop, the future vision reader). The dispatcher itself makes zero LLM calls.synthesize_parsing_outputs(step_outputs): DataFrame merges plus a_pick_richerheuristic. No LLM.
What true agentic parsing would add: an LLM that reads each method’s output, decides whether the corpus is done or a method is worth re-running with different parameters, and adds or drops methods from the plan on the fly. Tool use in the ReAct sense. Feedback loops. That belongs to Volume 3 (Agentic Bricks) of the series, where each brick gets an agent wrapper that observes, plans, and acts. This article stops one step short.
So this article builds the honest version of the pattern that everyone calls agentic today: rule-based routing plus LLM leaves. It is enough to close the parsing brick with a single parse_pdf_agentic(path) call that returns an enriched corpus. Volume 3 will add the real agent on top.
2. The document we want to use to its full extent
Think of the documents where a single parsing method is never enough: a 200-page contract with rate tables, a quarterly report full of charts and footnotes, a grant application, a patent, a dense NIPS paper with equations and results tables. Each one defeats a different parser. Fitz gets the text but misses the table cells. Docling gets the tables but the outline is only two levels deep. Azure Layout is strong on both but has no font size. Mistral OCR returns markdown for free but only when you run it against a scanned page. The team needs each of these tools on different documents, sometimes on different pages of the same document.
The reflex the series has been building for eight articles is one method per problem. That reflex is correct at the individual level and it does not scale to the document. A production caller does not want to write a switch statement over parsers. They want to call one function and get back a corpus dict filled to the level the document deserves. That is what this article closes with.
Two regimes coexist and it is worth naming them before the code lands. The first is what this article builds: the “agentic” one above. Read the document once, pick a plan, execute every step, fold the outputs. The document gets everything it deserves in one pass, even if that pass costs several LLM calls and one OCR run. The second is adaptive parsing (a later article in the series): the caller does not enrich anything up front; instead, the retrieval brick asks for the pages it needs and parsing runs on demand. The first is ex ante, the second is lazy. Both belong in the pipeline. This article is only about the first.
3. Nature, plan, execute, synthesize
The loop has four stages. Each is deterministic, cheap, and inspectable; the choice of what to run is not an LLM decision. The LLMs enter each individual method at the level of its own contract (fixed schema, injected callable, cached JSON), never at the dispatcher layer.
Nature, plan, execute, synthesize; the choice of what to run is deterministic, never an LLM decision – Image by author
In: a PDF path. Optionally a pre-computed nature or plan. Optionally an
llm_parsecallable for methods that use one. Out: an enriched corpus dict withline_df,span_df,toc_df,image_df,reference_df,table_df, plus thenatureandplanused to produce it, so the run is fully reproducible.
3.1 Nature: a coarse read of the document
The first pass reads the file’s nature: a small Pydantic model with categorical flags. Cheap signals only. File probe plus a single line_df and span_df build. No LLM.
The six flags are read from the following signals:
is_scanned:line_dfis empty or its row count sits well belowpage_count. No extractable text layer, OCR is required.has_native_outline:doc.get_toc()returns a non-empty list.has_sommaire: an early page carries five or more dot-leader lines (Title ....... 12), the signal Article 5septies (TOC reconstruction from a sommaire) reads.is_composite:detect_document_boundaries(from the body-structure module of Article 5octies, TOC reconstruction from body typography) fires on a numbering re-init, style rupture or cover page.has_rich_figures: image density above the median for a prose doc.has_tables_signal: a cheap grid detector finds rows of three or more short whitespace-separated fields.
3.2 Plan: nature to an ordered list of methods
plan_parsing_methods reads the nature and returns a list of MethodStep values. Each step names one parsing method, gives a one-line rationale, and carries an optional flag saying whether the dispatcher may skip it on error.
fitz_native always first, image_pipeline always last, the middle changes with the nature – Image by author
Two rules that keep the plan honest:
- Every plan starts with
fitz_native. Line and span frames feed every downstream method; there is no case where skipping them saves time. - Optional flags are used sparingly.
image_pipelineandvision_llm_figuresare opt-out because they call out to a heavy tool; the TOC and layout methods are required because they are the load-bearing outputs.
3.3 Identity cards for the parsing methods
The open-source parsing landscape is wide and it keeps growing, so treat this as a catalog, not a fixed list. Each method in the dispatcher has a defined role, a clear input contract, and a predictable output schema — which is precisely what makes rule-based routing over them reliable and auditable in a production pipeline.