Skip to main content

groundrag

A production-grade advanced RAG system in Python, usable from a CLI, an HTTP API or as an MCP server (protocol revision 2026-07-28): multi-format ingestion, structure-aware chunking, hybrid dense + BM25 retrieval on LanceDB, query rewriting / multi-query / HyDE, cross-encoder reranking, cited generation with an explicit "not found" fallback, and an evaluation gate (recall@k, MRR, nDCG, faithfulness, answer relevance, abstention).

Docs: usage guide · configuration reference · design and default rationale.

Validation status. The embedding model (FastEmbed), the cross-encoder, tiktoken, Docling and MarkItDown (parsing, LLM image description, RapidOCR fallback) were exercised against real models with the opt-in suite (make test-real). The LLM paths (cited generation, query rewrite, multi-query, LLM judge) were exercised against a real OpenAI-compatible endpoint with a free reasoning model (also used for the translation evaluation, docs/DESIGN.md §6); with such models the LLM judge can return invalid JSON (faithfulness 0), so prefer eval.judge=offline or a non-reasoning model. HyDE and the OAuth / HTTP MCP paths have only been tested with stand-ins. See docs/USAGE.md §11.

Installation

pip install groundrag
# with extras (combine as needed):
pip install "groundrag[parsers,api,mcp,tiktoken,translation]"

The GitHub repository stays private until December 2026: until then the git form below and the repository links in this README work only for collaborators.

Or straight from GitHub:

pip install "git+https://github.com/andrea9293/groundrag.git"
pip install "groundrag[parsers,api,mcp,tiktoken,translation] @ git+https://github.com/andrea9293/groundrag.git"
Extra Enables
[parsers] layout-aware parsing and image description (Docling + MarkItDown, ingest.parser: hybrid)
[api] HTTP API (groundrag serve)
[mcp] MCP server (groundrag mcp)
[tiktoken] exact token counts (tokens.counter: tiktoken)
[translation] translation to English (lingua, CTranslate2, transformers, sentencepiece, huggingface-hub); switches on translation.enabled: auto. The first model conversion also needs torch

Development install:

git clone https://github.com/andrea9293/groundrag.git && cd groundrag
make venv install          # python3 -m venv .venv && pip install -e ".[dev]"

The repository's config.yaml is picked up from the current directory. To keep personal settings (endpoint, model, paths) out of git, copy it to a gitignored config.local.yaml and run GROUNDRAG_CONFIG=config.local.yaml groundrag ... (or --config config.local.yaml).

Quickstart

The make targets below need a clone of the repository (they use data/sample/, config.yaml and the Makefile). If you installed from PyPI, skip to the groundrag ... commands in the next block.

make ingest                # index data/sample (16 file types) into collection "default"
make query Q="What is the warranty period of the RX-200?"
make demo                  # ingest + questions answered from pdf, docx, xlsx, rst (+ a "not found")
make test                  # pytest (offline, deterministic stand-ins)
make test-real             # opt-in: real embedding model, cross-encoder, LLM (network)
make eval                  # gold set → metrics → reports/eval_latest.json; non-zero exit on regression
make serve                 # HTTP API (pip install -e ".[api]")
make mcp                   # MCP server, stdio (pip install -e ".[mcp]") — see docs/USAGE.md §8

For layout-aware parsing and image description (hybrid Docling + MarkItDown): pip install -e ".[parsers]" (see docs/USAGE.md §5).

With an OpenAI-compatible LLM: export OPENAI_API_KEY=... (or set llm.base_url for Ollama / vLLM). Without one, the pipeline runs fully offline (see Limitations).

Quick start commands (your own documents; these work with pip install groundrag):

# 1. index your documents into a collection (incremental: unchanged files are skipped)
groundrag ingest docs/ --collection handbook

# 2. ask a question, offline: no LLM, the answer quotes the best sentences
groundrag --set llm.backend=none query "How many vacation days do I get?" --collection handbook

# 3. ask with an LLM (cited answer, refuses when the documents don't say)
#    the key comes from the environment (never commit it); --set llm.base_url=... / llm.model=... pick another endpoint
export OPENAI_API_KEY=...
groundrag query "How many vacation days do I get?" --collection handbook

# 4. create a gold set with an LLM, review it, then set the collection's threshold
groundrag gold-generate --collection handbook --out data/eval/gold_handbook.jsonl
# dry run (nothing written), then --save
groundrag calibrate --collection handbook --gold data/eval/gold_handbook.jsonl
groundrag calibrate --collection handbook --gold data/eval/gold_handbook.jsonl --save

# 5. measure quality, inspect, clean up
groundrag eval --gold data/eval/gold_handbook.jsonl --collection handbook --no-ingest
groundrag stats                                    # collections, rows, calibration state
groundrag drop --collection handbook               # delete a collection (asks to confirm)

More CLI examples:

# these queries use the LLM: run the export from step 3 first
groundrag query "vacation days" --filter format=docx --filter date_from=2026-01-01
groundrag query "salary band?" --groups hr-admins
groundrag query "and the RX-400?" --history chat.json --json
groundrag --set rerank.top_k=8 query "..."                   # override any config key
groundrag ingest docs/ --collection handbook --prune         # incremental; --rebuild to reset

Architecture

ingest ─► clean/dedup ─► translate to English (when active; keeps original) ─► chunk (recursive|semantic|parent-child, metadata) ─► embed (normalised)
       ─► LanceDB: vectors (flat→HNSW) + BM25 FTS (+ BM25 on original_text) + scalar/ACL columns + manifest
query ─► cleanup ─► translate to English (when active) ─► standalone rewrite ─► multi-query / HyDE ─► routing
      ─► recall: dense + BM25 per variant, pre-filtered ─► RRF (k=60) ─► top-50
      ─► rerank: cross-encoder ─► top-5 ─► score < min_score ? "not found"
      ─► context "[n] source — page N" (token budget) ─► generate (T=0.1, cited, 1 retry)
eval  ─► gold.jsonl ─► retrieval + answer metrics ─► JSON report + RAGAS JSONL ─► gate
Module Path
Config / logging / models src/groundrag/{config,logging_utils,models,tokens,text}.py
Ingest (pdf, docx, pptx, xlsx, csv, tsv, md, html, txt, rst, eml, epub, json, xml) src/groundrag/ingest/
Chunking src/groundrag/chunking/
Embeddings (FastEmbed bge-small-en-v1.5, hashing fallback) src/groundrag/embeddings/
Index (LanceDB, safe filters, ACL) src/groundrag/index/
Translation (language detection, NMT tiers, placeholders, cache) src/groundrag/translation/
Query transforms src/groundrag/query/
Hybrid retrieval + metrics src/groundrag/retrieval/
Rerankers src/groundrag/rerank/
LLM client, context, generator src/groundrag/generation/
Façade src/groundrag/pipeline.py
Evaluation src/groundrag/eval/, data/eval/gold.jsonl
CLI / API / MCP server src/groundrag/cli.py, src/groundrag/api.py, src/groundrag/mcp_server.py

Every log line is JSON with trace_id and per-stage timings_ms.

Agent skills

The repository ships Agent Skills in skills/ that teach a coding agent how to use groundrag: groundrag-cli, groundrag-python-integration, groundrag-ingest-parsers, groundrag-tuning and groundrag-evaluation. Install them with:

npx skills add andrea9293/groundrag                       # pick the skills interactively
npx skills add andrea9293/groundrag --skill groundrag-cli    # or a single one

Configuration

Full per-key reference (allowed values, defaults, effects): docs/CONFIG.md. Most important keys (code defaults; config.yaml has the same values except where noted):

Key Default Meaning
ingest.parser hybrid hybrid | docling | markitdown | native (extras [parsers])
ingest.extensions [] in code; 16 formats in config.yaml files that are ingested
chunking.strategy / chunk_size / chunk_overlap recursive / 400 / 60 (config.yaml: parent_child / 150 / 30) how text is split
embeddings.backend / model auto / BAAI/bge-small-en-v1.5 FastEmbed, hashing fallback
translation.enabled / tier / languages auto / fast / [] translate to English when the [translation] extra is installed; true | false to force
retrieval.dense_top_k / sparse_top_k / candidates_top_k 50 / 50 / 50 recall stage (dense + BM25 + RRF)
rerank.backend / model auto / Xenova/ms-marco-MiniLM-L-6-v2 cross-encoder, lexical fallback
rerank.top_k 5 chunks kept after reranking
rerank.min_score.cross_encoder / min_score_llm.cross_encoder 0.5 / 0.05 "not found" thresholds without / with an LLM
llm.backend / model / base_url / api_key_env auto / gpt-4o-mini / null / OPENAI_API_KEY any OpenAI-compatible endpoint; the key is never read from the file
query.multi_query.n / query.hyde.enabled 3 / false LLM query expansion
generation.temperature / max_tokens / max_context_tokens 0.1 / 2000 / 3000 answer building
eval.judge / eval.thresholds auto / {} in code; 8 gates in config.yaml eval judge and pass/fail gate
security.enforce_acl true ACL filtering
api.* / mcp.* 127.0.0.1:8000, /ingest off / stdio, ingest off HTTP API and MCP server

Justifications for the defaults: docs/DESIGN.md §2.

Limitations

  • Validation scope. HyDE and the OAuth / HTTP MCP paths have only been tested with stand-ins; the LLM judge can fail with reasoning models. See the validation status above.
  • Offline fallbacks. Without access to HuggingFace / an LLM API the auto backends use a hashing embedder, a lexical reranker and an extractive answerer (all logged as WARNING). They're deterministic and pass the eval gate on the sample set, but they don't understand synonyms (e.g. "EU" vs "European Union"). For production quality, use FastEmbed models and an LLM.
  • The token count is a regex estimate unless tokens.counter: tiktoken. The contextual header (title > section) sits on top of chunk_size.
  • Translation. Translation quality is that of the NMT model: tables, names and short queries can come out wrong, which cost a few answers on same-language Italian questions in the evaluation (one run, 13 questions). Only English is a target language. Routing keywords must be in English while translation is active. The first ingest after translation becomes active re-chunks the documents it sees; unseen ones need --rebuild or --prune.
  • The BM25 index is rebuilt after each ingest batch, which is fine up to roughly 10⁵ chunks. For larger corpora, batch your ingests.
  • The HTTP API has no authentication. Run it behind a gateway. The MCP server over HTTP requires OAuth (or an explicit opt-out for use behind a gateway).
  • The MCP Tasks extension is not yet supported by the Python SDK, so MCP ingest_documents is synchronous (and disabled by default).
  • The gold set is small (23 questions: 20 answerable and 3 unanswerable traps) and synthetic. Extend it with real questions before relying on it for tuning.
  • "Not found" thresholds. Without an LLM no threshold can reject questions that are on topic but have no answer in the documents (the cross-encoder scores them ~0.98); only an LLM does that. groundrag calibrate reports how many it would reject (trap_rejection) instead of hiding it. With few gold questions (< 30 answerable) the threshold is flagged few_data.
  • Generated gold sets (groundrag gold-generate) are easier than real questions and are marked needs_review. Use them as a starting point and recalibrate with real questions.

Metadata

Release files for groundrag 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for groundrag 0.2.0
File Size Uploaded Provenance
groundrag-0.2.0.tar.gz 148.3 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for groundrag 0.2.0
File Interpreter ABI Platform
groundrag-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 263.2 kB

Release files / groundrag-0.2.0.tar.gz

Download URL groundrag-0.2.0.tar.gz
Size 148.3 kB
Tags Source
SHA-256 checksum
How to use checksums
162d5abb321f3beb57006c3c14b60f6a87578a1cb418df7f04e7ef22d6e7e762
BLAKE2b-256 checksum
How to use checksums
921a335e056cedc619b0df92ebff1fb7e8d932e9ce97e88c96e316155ff1be1b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 22, 2026.

Transparency log

Release files / groundrag-0.2.0-py3-none-any.whl

Download URL groundrag-0.2.0-py3-none-any.whl
Size 114.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2c1e289fcb94d17c532539449ddcbe7b76cc306e724d57bc181955168f474fc4
BLAKE2b-256 checksum
How to use checksums
06a5e9d6e8d6d2b46dba5030ba9338b89f751f98df1a4da40d27bd0b1ddb19b3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 22, 2026.

Transparency log

Security for groundrag 0.2.0

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for groundrag-0.2.0.tar.gz

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 22, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page