Automatic source citation for Docling-parsed documents — EU AI Act compliant provenance in one line.
Most RAG pipelines retrieve a chunk and hope the model cites the right source. docling-cite makes citation deterministic: every retrieved chunk is matched back to its origin node in the Docling document tree and returned with section, page, node_id, and confidence.
from docling_cite import CitationIndex
index = CitationIndex.from_dict(docling_doc)
citation = index.cite("Ibuprofen: interaction type CONTRAINDICATED.")
print(citation.section) # "Drug Interactions"
print(citation.page) # 14
print(citation.node_id) # "item_011"
print(citation.confidence) # 1.0
print(citation.format()) # "[Drug Interactions, p. 14, item_011 - warfarin_monograph.pdf]"The EU AI Act (enforced August 2026) classifies AI systems in healthcare, finance, and legal as high-risk. High-risk systems must be able to explain why an answer came from which source. "The embedding was close" does not satisfy this requirement. docling-cite makes every retrieval auditable without changing your pipeline.
Article 15 of the Verifiable AI Architecture series shows why vector search cannot produce an audit trail and what the compliance gap looks like in practice. This package is the mechanical layer that closes it.
pip install docling-citeZero hard dependencies. The core matching uses pure stdlib (difflib, re).
Optional extras:
pip install docling-cite[docling] # Use with live DoclingDocument objects
pip install docling-cite[langchain] # CitedRetriever wrapper
pip install docling-cite[llamaindex] # CitationPostprocessor
pip install docling-cite[all] # EverythingDocling-parsed document (dict or DoclingDocument)
│
▼
CitationIndex.from_dict(doc)
─────────────────────────────
Walk items list.
Track current section heading.
Assign node_id to every element.
Extract page + bounding box from provenance metadata.
│
▼
index.cite("retrieved chunk text")
─────────────────────────────────
Tier 1: Exact substring match → confidence 1.0
Tier 2: Token Jaccard overlap → confidence = overlap ratio
Tier 3: SequenceMatcher ratio → character-level similarity
│
▼
Citation(section, page, node_id, element_type, confidence, ...)
The three-tier strategy means paraphrased chunks still get attributed correctly — useful when the LLM reformulates retrieved text before passing it back to your logging layer.
from docling_cite import CitationIndex
# Build the index once per document
index = CitationIndex.from_dict(docling_doc)
# Cite a single chunk
citation = index.cite("Revenue growth was driven by APAC expansion.")
print(citation.format()) # "[Financial Highlights, p. 3, item_004 - report.pdf]"
# Cite a list of chunks (e.g. all retrieved docs)
citations = index.cite_all(retrieved_chunks)from docling.document_converter import DocumentConverter
from docling_cite import CitationIndex
result = DocumentConverter().convert("annual_report.pdf")
index = CitationIndex.from_docling_document(result.document)
citation = index.cite(retrieved_chunk)from docling_cite import CitationIndex
from docling_cite.integrations.langchain import CitedRetriever
index = CitationIndex.from_dict(docling_doc)
retriever = CitedRetriever(vectorstore.as_retriever(), index)
docs = retriever.invoke("What drove revenue growth?")
for doc in docs:
print(doc.metadata["citation"])
# {"section": "MD&A", "page": 12, "node_id": "item_031", "confidence": 0.94, ...}from docling_cite import CitationIndex
from docling_cite.integrations.llamaindex import CitationPostprocessor
index = CitationIndex.from_dict(docling_doc)
postprocessor = CitationPostprocessor(index)
query_engine = vector_index.as_query_engine(
node_postprocessors=[postprocessor]
)
response = query_engine.query("What are the renal dosing adjustments?")
for node in response.source_nodes:
print(node.metadata["citation"])citation = index.cite(chunk)
data = citation.to_dict() # plain dict, JSON-serialisable
import json
print(json.dumps(data, indent=2))| Field | Type | Description |
|---|---|---|
text |
str |
The retrieved chunk text |
document |
str |
Source document name |
node_id |
str |
Position in the document tree, e.g. item_031 |
element_type |
str |
paragraph, table, heading, etc. |
section |
str | None |
Nearest ancestor heading |
page |
int | None |
Page number (when available from Docling provenance) |
confidence |
float |
Match confidence 0.0–1.0. 0.0 = no match found |
bounding_box |
dict | None |
{l, t, r, b} when available from Docling |
bool(citation) is False when confidence == 0.0 (no match).
citation.format() returns an inline citation string: [Section, p. N, item_XXX — document.pdf]
git clone https://github.com/elyas-karbouch/docling-cite
cd docling-cite
python example.pyNo Docling installation required — the example uses a synthetic drug monograph dict.
pip install -e ".[dev]"
pytestThis package is the implementation layer for the citation and provenance concepts covered in the VAA series — specifically:
- Article 6: Why vector search destroys document structure at ingestion
- Article 15: Framework decision guide — when PageIndex beats vector search
- Article 17: EU AI Act compliance requirements for high-risk AI systems
MIT — see LICENSE.