--- title: "From documents to a RAG corpus in R" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{From documents to a RAG corpus in R} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} # Code in this vignette talks to the Docling Python backend and downloads # deep-learning models on first use, so chunks are shown but not evaluated at # build time. Run them in an interactive session after install_docling(). knitr::opts_chunk$set( collapse = TRUE, comment = "#>", eval = FALSE ) ``` ## Overview **doclingr** turns messy documents — PDF, DOCX, PPTX, HTML, images — into structured, AI-ready data. It wraps the [Docling](https://github.com/docling-project/docling) Python library through [reticulate](https://rstudio.github.io/reticulate/), giving you layout-aware parsing, table extraction and retrieval-ready chunking with a small, tidy R API. This vignette walks the full path: **document → structure → tables → chunks → embeddings**, i.e. everything you need to stand up a retrieval-augmented generation (RAG) corpus from R. ## One-time setup doclingr needs the Docling Python package. Install it once into a managed environment, then restart R: ```{r} library(doclingr) install_docling() # creates an "r-docling" Python environment # ...restart R... docling_available() # TRUE once the backend is ready ``` ## Converting a document `docling_convert()` runs Docling's understanding pipeline over a file path or URL and returns a lightweight handle: ```{r} doc <- docling_convert("https://arxiv.org/pdf/2408.09869") doc #> #> source: https://arxiv.org/pdf/2408.09869 #> pages: 9 #> tables: 5 #> figures: 3 ``` Tune the pipeline when you need to. OCR and the accurate table model cost time; turn them down for born-digital documents or large batches: ```{r} doc <- docling_convert( "report.pdf", ocr = FALSE, # skip OCR for born-digital PDFs table_mode = "fast", # "accurate" (default) or "fast" device = "mps" # "auto", "cpu", "cuda", "mps" ) # Convert many sources in one batch docs <- docling_convert(c("a.pdf", "b.docx", "c.html")) ``` ## Exporting structure Render the understood document into the format your downstream tools expect: ```{r} as_markdown(doc) # layout-aware Markdown as_text(doc) # plain text as_html(doc) # HTML as_json(doc) # structured DoclingDocument as a nested R list as_doctags(doc) # Docling's DocTags representation ``` ## Tables as tibbles Every detected table comes back as a tibble, in document order: ```{r} tables <- docling_tables(doc) length(tables) tables[[1]] #> # A tibble: 12 x 4 #> Method Recall Precision F1 #> #> 1 Baseline 0.81 0.78 0.79 #> ... ``` ## Figures Pull figure captions and pages, and optionally save the images (requires `images = TRUE` at conversion time): ```{r} doc <- docling_convert("paper.pdf", images = TRUE) figs <- docling_figures(doc, image_dir = "figures") figs #> # A tibble: 3 x 4 #> figure_id caption page image_path #> #> 1 1 "Figure 1: pipeline ..." 2 figures/figure-001.png #> ... ``` ## Chunking for retrieval `docling_chunk()` splits the document into context-rich chunks. The default hybrid chunker is token-aware: match its tokenizer to your embedding model and set a budget so chunks fit your model's context. ```{r} chunks <- docling_chunk( doc, tokenizer = "BAAI/bge-small-en-v1.5", max_tokens = 512 ) chunks #> # A tibble: 84 x 7 #> chunk_id text raw_text n_chars headings pages n_doc_items #> #> 1 1 "Docling: ..." "Docling..." 412 3 #> ... ``` Each chunk's `text` is *contextualized* — enriched with its heading path and table context — which is the form you typically embed. The unmodified text is kept in `raw_text`. ## From chunks to embeddings doclingr is deliberately provider-agnostic about embeddings: you supply a function that maps a character vector to vectors, and `docling_embed()` handles batching and tidy assembly. Here is a sketch against an OpenAI-style API: ```{r} embed_api <- function(texts) { # Call your embedding endpoint; return a matrix with one row per text. # e.g. httr2 -> a list of vectors, or a matrix. } corpus <- doc |> docling_chunk(tokenizer = "BAAI/bge-small-en-v1.5", max_tokens = 512) |> docling_embed(embed_api, batch_size = 64) corpus #> # ... your chunks plus `embedding` (list-column) and `n_dim` ``` At this point `corpus` is a tidy table of chunks with their headings, pages and embeddings — ready to write to a vector store, a database, or an in-memory nearest-neighbor index for RAG. ## Where to go next - Use `as_json(doc)` when you need the full structural detail Docling captured. - Persist `corpus` (for example with `arrow::write_parquet()`) to avoid re-converting and re-embedding. - See the [Docling documentation](https://docling-project.github.io/docling/) for the breadth of supported formats and pipeline options.