Know your documents before they reach an LLM.
An open-source, offline audit for documents before they're indexed.
Works with
LangChain ·
LlamaIndex ·
Unstructured ·
Docling ·
LlamaParse ·
Azure Document Intelligence
A PDF can say one thing to a person and another to your model. White text, tiny type, an IBAN behind a black bar: it's all still in the text layer, and most loaders pass it straight through. complydoc shows you what your loaders actually extracted, page by page, flags the hidden text and the personal and financial identifiers before they're embedded, and can fail a CI check when a document breaks your policy. It also traces every ingestion step with its time and cost, and nothing leaves your machine unless you ask.
Try it in 30 seconds
A one-page contract with a clause in white text:
uv tool install complydoc
curl -LO https://raw.githubusercontent.com/complydoc/complydoc/main/examples/hidden_clause.pdf
complydoc audit hidden_clause.pdf
complydoc ui
Quickstart
uv tool install complydoc # or: pip install complydoc
Observe an ingestion pipeline as it runs, every loader, splitter, embedding call and vector store a step:
import complydoc as cd
with cd.observe("contracts-ingest"):
documents = PyMuPDF4LLMLoader("contract.pdf").load()
chunks = splitter.split_documents(documents)
vector_store.add_documents(chunks)
Open the run with complydoc ui. Langfuse and Phoenix
is how an LLM trace points at the same run.
Compare loaders on your own documents, page by page:
from langchain_community.document_loaders import PyPDFLoader
from langchain_pymupdf4llm import PyMuPDF4LLMLoader
report = cd.compare_loaders(
{"PyPDFLoader": PyPDFLoader, "PyMuPDF4LLMLoader": PyMuPDF4LLMLoader},
paths="./contracts",
page_images=True,
)
cd.write_json(report, ".complydoc/loaders.json", detail="full")
Or audit a folder from the command line:
complydoc audit ./contracts
Then open it all in the viewer, served from your machine:
complydoc ui
The playground has all of this ready to run.
Demo
examples/hidden_clause.pdf is one page: two columns, and a sentence in white.
examples/hidden_clause.py writes it.
complydoc audit examples/hidden_clause.pdf --compare-extractor pypdf
complydoc check examples/hidden_clause.pdf --policy examples/hidden_clause.yaml
complydoc ui
The audit keeps both readings and flags the white sentence. The check fails on it.
complydoc ui opens the report.
Six pages of the same kind, and what each loader returns from them, are in What PDF loaders let through.
What it shows
- Pipeline traces: each LangChain or LlamaIndex loader, splitter and embedding call as a step, with its settings, time, tokens, cost and what it passed on.
- Loader diffs: every loader's reading of a document side by side, line by line, with the page it came from.
- Identifiers: personal and financial identifiers from Europe, the Americas, India and Australia, checksum-validated where a checksum exists, and masked until you choose to see them.
- Hidden content: text a reader does not see and a model does, and passages written as instructions to a model.
- Chunks: splitters side by side, drawn over the text, with the sentences and tables they cut.
- Cost: text and vision tokens per page, priced across models and extraction paths.
In CI
complydoc check holds a folder to rules written in YAML and fails when they break. On
GitHub, the repository is also an action:
- uses: complydoc/complydoc@v0.7.6
with:
path: documents
policy: policy.yaml
policy can be a file, or a pack that ships with the tool: finance, legal, hr.
See GitHub Action and Policy files.
For a team
One place where everyone opens the same reports, inside your own infrastructure:
complydoc ui ./reports --host 0.0.0.0 # served to your team, read-only
docker run -p 8500:8500 -v "$PWD/reports:/reports:ro" ghcr.io/complydoc/complydoc
Reports can live in a shared folder or a bucket (--out s3://bucket/folder), each run
records the repository, branch and commit it came from, and a Compose file puts the viewer
behind the sign-in your organisation already has. See
Run it for your team and
Run it in a container.
Privacy
Outbound connections and DNS lookups are blocked for the whole run, and every report records
that they were. Identifiers are masked in every output, the page text included. The only
things that send data anywhere are ones you add yourself: a hosted parser or the optional
typesafe and assistant extras, which run only with allow_network=True, and a bucket you
name as the place to keep reports, which receives the report and never the documents. See
Network isolation.
Optional extras
Reading scans and finding names are optional, because they are large. Every report says when they were missing.
| Extra | Size | Adds |
|---|---|---|
ocr |
~80 MB | Reads scans and images |
multilingual-names |
~2 GB | Finds people and companies in European languages |
ner |
~50 MB | Finds names with spaCy's small English model |
s3 |
~90 MB | Writes reports to a bucket and serves them from one |
uv tool install "complydoc[ocr,multilingual-names]"
complydoc doctor # what is installed, and the command for anything missing
The name models are downloaded once, before a scan, never during one: complydoc doctor
prints the command. Detection accuracy
publishes how often each finds and misses names.
Resources
- Documentation
- Playground: runnable examples, with a CI workflow
- Langfuse and Phoenix: the ingestion report beside an LLM trace
- Command line and Python API references
- Changelog
- Contributing
License
MIT
Metadata
Release files for complydoc 0.7.6
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| complydoc-0.7.6.tar.gz | 6.4 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| complydoc-0.7.6-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 10.6 MB
Release files / complydoc-0.7.6.tar.gz
| Download URL | complydoc-0.7.6.tar.gz |
|---|---|
| Size | 6.4 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c35ac75ed581308fdf21edc8e9638e60753a906e8819eb70fb0bea72331388ea
|
|
BLAKE2b-256 checksum How to use checksums |
4eef4bed51c94d99b74f6741cdf964d034553f78ccd684bd94553f7c4c234c77
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logRelease files / complydoc-0.7.6-py3-none-any.whl
| Download URL | complydoc-0.7.6-py3-none-any.whl |
|---|---|
| Size | 4.1 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fd37cd5d1d5781f4dd4a1dd21bf266b117bdc5fde1ec39fe73dbfd52ac52bd9c
|
|
BLAKE2b-256 checksum How to use checksums |
4735f278fcc02222f06701f870ecf6a0d3cf0d25c474a554ae1f5b11ce5f41f6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency log