DEV Community

Alain Airom (Ayrom)
Alain Airom (Ayrom)

Posted on

Embedding Docling-NLP in Ad-Hoc UI Applications: A Lightweight Blueprint

Implementing a “Graph Language Model” with Docling-NLP

Image from Docling-AI

Image from Docling-AI

Building a local, privacy-focused Document AI pipeline often feels like balancing heavy models with sluggish performance. IBM’s open-source ecosystem solves this by splitting document parsing and entity extraction into two specialized tools: docling (Python-based document conversion) and docling-nlp (a C++ compiled core with Python bindings via pybind11).

This guide outlines a complete, practical framework for embedding docling-nlp into an ad-hoc Streamlit interface, based on the reference architecture designed by IBM Bob.


What is Docling NLP: Graph Language Model

Excerpt from Github;

Finding entities and relations via NLP on text and documents and creating Graphs from NLP entities and relations in document collections
To get easily started, simply install the docling-nlp package from PyPi. This can be done using the traditional pip install docling-nlp or via uv uv add docling-nlp.


High-Level Architecture Overview of the sample implementation

The system divides responsibilities across distinct runtime layers:

  • Streamlit GUI Layer (app.py): Provides interactive controls for raw text, file uploads, URLs, and real-time visualization.
  • Python Processing Layer (src/docling_processor.py): Interfaces with docling to extract reading-order Markdown, DataFrames, and plain text chunks.
  • C++ NLP Layer (src/nlp_processor.py): Uses pybind11 bindings to run native C++ entity and relation extraction at ~1.2M tokens/sec.

Dynamic Workflow & Sequence Dataflow

When a document or raw string is provided, execution moves synchronously across the document parsing pipeline into the pybind11-wrapped C++ core:


Core Implementation

Pre-processing Markdown Input

docling-nlp achieves high performance when operating on clean text spans. Passing raw Markdown syntax (headings, code blocks, links) introduces noise into tokenization boundaries. The utility layer strips markup before passing text down to C++:

def clean_markdown_for_nlp(text: str) -> str:
    """Strip Markdown syntax so C++ NLP receives clean prose."""
    if not text:
        return text
    cleaned = text
    for pattern, replacement in _MD_CLEAN_PATTERNS:
        cleaned = pattern.sub(replacement, cleaned)
    return re.sub(r"\n{3,}", "\n\n", cleaned).strip()
Enter fullscreen mode Exit fullscreen mode

Interfacing with the C++ NLP Core

The NLPProcessor wraps initializations, runs inference on text chunks, and parses zero-indexed array representations back into rich dataclass objects:

# src/nlp_processor.py
from docling_nlp.utils.load_pretrained_models import load_pretrained_nlp_models
from docling_nlp.nlp_utils import init_nlp_model

class NLPProcessor:
    def __init__(self):
        self._model = None

    def _get_model(self):
        if self._model is None:
            # Load pre-compiled weights and init pybind11 bindings
            load_pretrained_nlp_models(force=False, verbose=False)
            self._model = init_nlp_model()
        return self._model

    def process_text(self, text: str) -> NLPResult:
        result = NLPResult(source_text=text)
        nlp_input = clean_markdown_for_nlp(text) if is_markdown_text(text) else text

        # Invoke native C++ inference engine
        raw = self._get_model().apply_on_text(nlp_input)
        self._parse_raw_result(raw, result)
        return result
Enter fullscreen mode Exit fullscreen mode

Streamlit Caching & Singletons

Re-loading ONNX parsing models or C++ NLP weights on every UI interaction slows response times. Wrapping processor instantiations with @st.cache_resource ensures singletons persist across application re-runs:

# app.py
@st.cache_resource(show_spinner="Loading Docling processor…")
def get_docling_processor() -> DoclingProcessor:
    return DoclingProcessor()

@st.cache_resource(show_spinner="Loading C++ NLP model via pybind11…")
def get_nlp_processor() -> NLPProcessor:
    return NLPProcessor()
Enter fullscreen mode Exit fullscreen mode

Conclusion: What This Application Demonstrates

Beyond the underlying code, this ad-hoc application serves as a concrete proof-of-concept for lightweight local document intelligence:

  • Local-First Privacy & Zero Cloud Dependency: Documents, tables, and raw text are parsed entirely on the host machine without sending sensitive data to external API endpoints.
  • High-Speed Hybrid Execution: It shows how Python and native C++ can be bridged via pybind11 to deliver near-instantaneous token processing (~1.2M tokens/sec) directly inside a web interface.
  • Unified Document Extraction: Complex file formats (PDFs, DOCX, HTML, Images) are seamlessly transformed into reading-order Markdown, structured DataFrames, and entity-rich JSON outputs through a single workflow.
  • Zero-Friction Interface: By wrapping sophisticated machine learning pipelines into an intuitive Streamlit UI, non-technical users can interact with complex NLP pipelines without running shell scripts or managing code.

Thanks for reading 🎩

Links

Top comments (0)