raggit
A plug-and-play production-grade RAG system that connects to local and remote object storage, automatically indexes documents, and answers questions using hybrid retrieval with reranking and LLM augmentation.
What raggit does
raggit is built for teams that need reliable retrieval-augmented generation without managing a patchwork of vector databases, parsers, and rerankers. It handles the full document lifecycle: watching storage, parsing common formats, chunking intelligently, embedding, searching, and generating cited answers.
Documents live in storage you already control. Point raggit at a local directory, S3 bucket, GCS bucket, or Azure container and it keeps an index in sync automatically. Query it through the CLI and receive an answer grounded in your documents, with citations.
raggit is in early development. The core ingestion, retrieval, and CLI flows are functional and tested. API server mode and analytics are on the roadmap.
Features
Ingestion
- Automatic watching via
raggit serve. Local filesystem changes are detected instantly through OS-native events (fsevents/inotify). Cloud storage is polled and diffed. - Format-aware parsing for PDF, DOCX, HTML, Markdown, and plain text. PDF page numbers are preserved through chunking.
- Smart chunking respects Markdown headers, code boundaries, and PDF pages, with recursive character splitting as a fallback.
- Deduplication removes exact and near-duplicate chunks using content hashes and Jaccard word-set similarity.
- Cleaning & normalization fixes Unicode, hyphenation, and whitespace before embedding.
- Optional safety steps: PII redaction and prompt-injection hardening.
- Model-scoped collections in Qdrant so changing embedding models never corrupts an existing index.
Retrieval
- Hybrid search combines BM25 keyword search with dense semantic search.
- Weighted Reciprocal Rank Fusion merges heterogeneous rank lists.
- Query rewriting with multi-query expansion and HyDE.
- Optional cross-encoder reranking for better top-N ordering.
- Dynamic top-k scales with corpus size while staying bounded.
- Parent-window expansion provides surrounding context for each hit.
- Structured citations include source URI, filename, page, section, offsets, score, and excerpt.
Operations
- Multi-tenancy & filtering by tenant, tags, source prefix, filename prefix, document IDs, and date range.
- Structured audit logs persisted to PostgreSQL for every ingestion, query, and answer.
- Document lifecycle status tracks pending, indexing, parsed, chunked, embedded, completed, failed, and deleted states.
- Async-first architecture using asyncpg, async Qdrant client, and async cloud SDKs.
Quick Start
You need Docker Desktop (or Docker Engine + Compose) and uv installed.
-
Start PostgreSQL and Qdrant
docker compose up -d postgres qdrantPostgreSQL is mapped to host port
5433to avoid conflicts with a local postgres on5432. -
Install dependencies
uv sync -
Run database migrations
uv run alembic upgrade head -
Configure raggit
uv run raggit setup \ --database-url postgresql+asyncpg://raggit:raggit@localhost:5433/raggit \ --qdrant-url http://localhost:6333 \ --storage-source-type local \ --storage-uri ./data/documents \ --llm-provider openai \ --llm-model gpt-4o-mini \ --llm-api-key $OPENAI_API_KEYraggit setupwrites~/.config/raggit/raggit.envwith0600permissions and bootstraps the system by checking Postgres, running migrations, checking Qdrant, and creating the local document directory. -
Add documents
mkdir -p data/documents cp my-docs/*.pdf data/documents/ -
Run the service for continuous indexing
uv run raggit serveraggit will perform an initial sync and then watch for new, modified, and deleted files. Local changes are reflected almost instantly.
-
Ask questions
uv run raggit query "What is raggit?"
See the storage backends section for S3, GCS, and Azure setup. The legacy raggit watch command is still available if you prefer live event indicators in the terminal.
Architecture
raggit is organized into clear layers: storage, ingestion, data, retrieval, LLM, audit, and interface. All I/O is async, and every significant input and output is logged to PostgreSQL.
flowchart TB
subgraph Storage
LS[Local Filesystem]
RS[S3 / GCS / Azure Blob]
end
subgraph Ingestion
W[Watcher Service]
I[Indexing Service]
P[Parser Registry]
FA[Format-Aware Chunker]
DED[Dedup]
CL[Chunk Cleaner]
PI[PII Redaction]
IH[Injection Hardening]
E[Embedder]
end
subgraph PostgreSQL
D[documents]
CH[chunks]
EC[embedding_collections]
LG[logs]
end
VS[Qdrant Vector Store]
LS --> W
RS --> W
W --> I
I --> P
P --> FA
FA --> DED
DED --> CL
CL --> PI
PI --> IH
IH --> E
E --> VS
I --> D
IH --> CH
E --> CH
I --> EC
IH --> LG
E --> LG
subgraph Retrieval
Q[User Query]
QR[Query Rewriter]
MQ[Multi-Query]
HY[HyDE]
BM25[Postgres FTS / BM25]
SEM[Qdrant Semantic Search]
RRF[Weighted RRF]
RR[Cross-Encoder Reranker]
TH[Score Threshold]
PW[Parent-Window Expansion]
AUG[Augmenter]
LLM[LLM Provider]
GRD[Groundedness Check]
OUT[Answer + Citations]
end
Q --> QR
QR --> BM25
QR --> SEM
QR --> MQ
QR --> HY
BM25 --> RRF
SEM --> RRF
RRF --> RR
RR --> TH
TH --> PW
PW --> AUG
AUG --> LLM
LLM --> GRD
GRD --> OUT
RRF --> LG
AUG --> LG
LLM --> LG
Component responsibilities
- Storage layer abstracts local directories and cloud object stores behind a common interface. Local backends emit OS-native events; cloud backends poll and diff.
- WatcherService starts storage watching, runs an initial sync, and dispatches file events to the indexer with lightweight debouncing.
- Indexer orchestrates hash checks, parsing, chunking, cleaning, embedding, and persistence.
- Parser registry selects the right parser by file extension and extracts text plus structural hints like page numbers.
- Chunker produces format-aware pieces and links sequential siblings for relevance-chain retrieval.
- Embedder generates dense vectors using sentence-transformers or an OpenAI-compatible API.
- Vector store manages Qdrant collections scoped to the embedding model and version.
- Retrieval engine sanitizes queries, optionally rewrites them, runs BM25 and semantic search in parallel, fuses results, reranks, thresholds, and expands parent windows.
- LLM layer augments prompts with isolated untrusted context and generates cited answers.
- Audit logger persists structured events to PostgreSQL.
Design goals
- Accuracy through hybrid retrieval, cross-encoder reranking, query rewriting, and parent-document expansion.
- Safety by isolating untrusted document text from system instructions and optionally redacting PII.
- Observability with lifecycle statuses, model-scoped collections, and structured audit logs.
- Multi-tenancy via
tenant_idand tags applied consistently across BM25 and vector search. - Evolvability via model-scoped Qdrant collections so changing embedding models does not corrupt existing indexes.
Data Model
PostgreSQL is the source of truth for metadata, chunks, audit logs, and collection tracking. Qdrant holds the dense vectors with filterable payloads.
documents
| Field | Type | Description |
|---|---|---|
id | UUID | Primary key. |
source_type | enum | local, s3, gcs, azure_blob. |
source_uri | string | Stable URI such as a local path or s3://bucket/key. |
filename | string | Relative filename. |
content_hash | string | Hash of file contents used to skip unchanged files. |
file_size | integer | Cached file size for fast sync decisions. |
file_modified_at | timestamptz | Cached modification time for fast sync decisions. |
status | enum | pending, indexing, parsed, chunked, embedded, completed, failed, deleted. |
error_message | text | Failure reason when status is failed. |
tenant_id | string | Optional tenant identifier. |
tags | array | Optional filter tags. |
created_at | timestamptz | Row creation time. |
updated_at | timestamptz | Row update time. |
deleted_at | timestamptz | Soft-deletion timestamp. |
chunks
| Field | Type | Description |
|---|---|---|
id | UUID | Primary key. |
document_id | UUID | Foreign key to documents. |
chunk_index | integer | Sequential index within the document. |
raw_content | text | Original chunk text. |
cleaned_content | text | Normalized chunk text used for embedding and BM25. |
word_count | integer | Word count for diagnostics. |
embedding_model | string | Model that produced the embedding. |
vector_id | UUID | Qdrant point ID. |
fts_vector | tsvector | PostgreSQL full-text search vector. |
parent_chunk_index | integer | Parent chunk for hierarchical chunking. |
prev_chunk_id | UUID | Previous sibling chunk. |
next_chunk_id | UUID | Next sibling chunk. |
section_title | string | Section or heading, if detected. |
page_number | integer | PDF page number, if detected. |
start_offset | integer | Character offset in source text. |
end_offset | integer | Character offset in source text. |
content_hash | string | Hash of chunk content. |
embedding_collections
Tracks which Qdrant collection is active for the current embedding model. Changing models creates a new collection and activates it automatically.
logs
Structured audit log with level, component, message, and JSON extra field. Captures ingestion inputs/outputs, queries, retrieval results, and generated answers.
Ingestion Pipeline
When a file is added or changed, the watcher triggers the indexer, which runs the document through parsing, format-aware chunking, cleaning, optional safety steps, embedding, and persistence.
tiktoken cl100k_base when available, with configurable overlap.logs table.Retrieval Pipeline
Queries are sanitized, optionally rewritten, run through BM25 and semantic search in parallel, fused, reranked, expanded, and then passed to the LLM with citations.
multi_query generates alternative phrasings; hyde generates a hypothetical answer passage to embed.tsvector GIN index.BAAI/bge-reranker-base.min_score and refuse empty or low-score retrieval.parent_window > 0.logs table.Watcher & Continuous Indexing
raggit keeps your index in sync with storage automatically. The recommended way to run it is raggit serve, a lightweight long-running process that starts a WatcherService.
raggit serve
raggit serve is the default long-running mode. It:
- Loads configuration from environment variables and
~/.config/raggit/raggit.env. - Runs an initial full sync of all existing files.
- Starts the storage-specific watcher.
- Dispatches add/modify/delete events to the indexer with per-path debouncing.
- Handles SIGINT and SIGTERM gracefully.
uv run raggit serve
uv run raggit serve ./data/documents
uv run raggit serve --log-level DEBUG --tenant acme --tag finance
Event-driven local watching
For local directories, raggit uses watchdog to receive OS-native filesystem events (FSEvents on macOS, inotify on Linux). This means:
- New or modified files are detected almost instantly.
- There is no periodic polling loop waking the CPU.
- Idle resource usage is negligible.
Git-like stat snapshot
On startup and during sync, raggit stores a cheap stat snapshot (file_size and file_modified_at) for each document. When a file is seen again, the indexer first compares this metadata. Only when size or modification time differs does it read the file and compute a content hash. This makes initial sync fast when most files have not changed.
Cloud storage watching
S3, GCS, and Azure backends do not have a universal push-notification mechanism, so they use lightweight polling and snapshot diffing. The interval is configurable with STORAGE_POLL_INTERVAL_SECONDS (default 30 seconds). You can make cloud watching near-instant by routing provider events (S3 Event Notifications, GCS Pub/Sub, Azure Event Grid) into a queue that a future extension can consume.
raggit watch (legacy mode)
raggit watch is preserved for users who want an explicit command with live +/- event indicators in the terminal. It uses the same WatcherService internally.
The included docker-compose.yml runs raggit serve by default, so the container starts watching and indexing as soon as it comes up.
CLI Reference
The raggit CLI is the primary interface for setup, ingestion, watching, and querying.
| Command | Description |
|---|---|
raggit setup |
Interactive configuration for local, S3, GCS, and Azure backends. |
raggit serve [path] |
Run the long-running service. Watches storage and indexes changes automatically. |
raggit ingest <path> |
One-time ingestion with a progress bar. Path is optional for cloud storage. |
raggit watch <path> |
Continuously watch and index with live event indicators. |
raggit query "<question>" |
Ask a question; shows status spinners, chunk table, answer panel, and citation tree. |
raggit status |
Show indexed document status and active embedding collections. |
setup
Writes ~/.config/raggit/raggit.env and bootstraps the system. Exposes every configuration parameter as a CLI option.
uv run raggit setup --help
serve
uv run raggit serve [PATH] [OPTIONS]
Options: --log-level, --tenant, --tag (repeatable).
ingest
uv run raggit ingest [PATH] [OPTIONS]
Options: --chunk-size, --chunk-overlap, --preserve-sections/--split-sections, --embedding-provider, --embedding-model, --log-level, --tenant, --tag.
watch
uv run raggit watch [PATH] [OPTIONS]
Options: --poll-interval, --log-level, --tenant, --tag.
query
uv run raggit query "<question>" [OPTIONS]
Options include --top-k, --min-top-k, --max-top-k, --top-k-ratio, --rrf-k, --source-prefix, --filename-prefix, --tenant, --tag, --document-id, --created-after, --created-before, --min-score, --rewrite, --multi-query-count, --parent-window, --reranker/--no-reranker, --reranker-model, --reranker-top-n, --refuse-on-empty/--no-refuse-on-empty, --refuse-on-low-score/--no-refuse-on-low-score, --min-answer-score, --groundedness-check/--no-groundedness-check, --pii-redaction/--no-pii-redaction, --prompt-injection-hardening/--no-prompt-injection-hardening, and --no-llm.
status
uv run raggit status
Shows a table of indexed documents and active embedding collections.
Storage Backends
raggit supports local filesystem, S3, Google Cloud Storage, and Azure Blob Storage. Remote backends are installed as optional extras to keep the base install small.
Local filesystem
The default backend. Set STORAGE_SOURCE_TYPE=local and STORAGE_URI to a directory path. The directory is created automatically if it does not exist. Changes are detected instantly via OS-native events.
AWS S3
Install the S3 extra:
uv pip install 'raggit[s3]'
Then run setup with S3 options:
uv run raggit setup \
--database-url postgresql+asyncpg://raggit:raggit@localhost:5433/raggit \
--qdrant-url http://localhost:6333 \
--storage-source-type s3 \
--storage-uri s3://my-bucket/documents \
--storage-bucket my-bucket \
--storage-prefix documents \
--storage-region us-east-1 \
--aws-access-key-id $AWS_ACCESS_KEY_ID \
--aws-secret-access-key $AWS_SECRET_ACCESS_KEY \
--llm-provider openai \
--llm-model gpt-4o-mini \
--llm-api-key $OPENAI_API_KEY
Google Cloud Storage
Install the GCS extra:
uv pip install 'raggit[gcs]'
uv run raggit setup \
--database-url postgresql+asyncpg://raggit:raggit@localhost:5433/raggit \
--qdrant-url http://localhost:6333 \
--storage-source-type gcs \
--storage-uri gs://my-bucket/documents \
--storage-bucket my-bucket \
--storage-prefix documents \
--gcs-service-account-path /path/to/service-account.json \
--llm-provider openai \
--llm-model gpt-4o-mini \
--llm-api-key $OPENAI_API_KEY
Azure Blob Storage
Install the Azure extra:
uv pip install 'raggit[azure]'
uv run raggit setup \
--database-url postgresql+asyncpg://raggit:raggit@localhost:5433/raggit \
--qdrant-url http://localhost:6333 \
--storage-source-type azure_blob \
--storage-uri azure://my-container/documents \
--storage-container my-container \
--storage-prefix documents \
--azure-connection-string $AZURE_STORAGE_CONNECTION_STRING \
--llm-provider openai \
--llm-model gpt-4o-mini \
--llm-api-key $OPENAI_API_KEY
Cloud storage watchers use periodic polling and snapshot diffing instead of native push notifications. This keeps the implementation portable across providers without requiring SQS, Pub/Sub, or Event Grid.
Configuration
Configuration is loaded from environment variables and a ~/.config/raggit/raggit.env file generated by raggit setup. The setup command writes the file with 0600 permissions.
Environment variables
| Variable | Default | Description |
|---|---|---|
DATABASE_URL | postgresql+asyncpg://raggit:raggit@localhost:5433/raggit | PostgreSQL connection string. |
QDRANT_URL | http://localhost:6333 | Qdrant URL. |
QDRANT_COLLECTION | raggit_chunks | Base Qdrant collection name. Actual collection is model-scoped. |
QDRANT_API_KEY | None | Qdrant API key, if required. |
LOG_LEVEL | INFO | Log level for console output. |
CHUNK_SIZE | 1024 | Target chunk size in tokens. |
CHUNK_OVERLAP | 0 | Overlap between chunks in tokens. |
CHUNKING_DEDUP_ENABLED | true | Remove near-duplicate chunks. |
CHUNKING_DEDUP_SIMILARITY | 0.92 | Jaccard similarity threshold for dedup. |
CHUNKING_FORMAT_AWARE | true | Use format-aware chunk boundaries. |
CHUNKING_PRESERVE_SECTIONS | true | Keep detected sections whole when possible. |
MIN_TOP_K | 5 | Minimum retrieved chunks. |
MAX_TOP_K | 50 | Maximum retrieved chunks. |
TOP_K_RATIO | 0.01 | Fraction of total chunks used to scale top-k. |
RRF_K | 60 | Reciprocal rank fusion constant. |
RETRIEVAL_PARENT_WINDOW | 0 | Expand hits by +/- N sibling chunks. |
RETRIEVAL_MIN_SCORE | None | Drop chunks below this score. |
RETRIEVAL_QUERY_REWRITE | none | Query rewrite: none, multi_query, hyde. |
RETRIEVAL_MULTI_QUERY_COUNT | 3 | Variants for multi_query. |
RETRIEVAL_TRAVERSAL_ENABLED | true | Relevance-chain traversal. |
RETRIEVAL_TRAVERSAL_MAX_STEPS | 10 | Max traversal steps. |
RETRIEVAL_TRAVERSAL_MIN_SCORE | 0.01 | Min score to continue traversal. |
RETRIEVAL_TRAVERSAL_DROP_RATIO | 0.5 | Score ratio that stops traversal. |
RERANKER_ENABLED | false | Cross-encoder reranking. |
RERANKER_MODEL | BAAI/bge-reranker-base | Reranker model name. |
RERANKER_TOP_N | 20 | Candidates to rerank. |
EMBEDDING_PROVIDER | sentence-transformers | Embedding provider. |
EMBEDDING_MODEL | BAAI/bge-small-en-v1.5 | Embedding model name. |
EMBEDDING_API_KEY | None | API key for remote embedding provider. |
EMBEDDING_BASE_URL | None | OpenAI-compatible embedding base URL. |
EMBEDDING_BATCH_SIZE | 32 | Texts per embedding batch. |
LLM_PROVIDER | openai | LLM provider: openai or ollama. |
LLM_MODEL | gpt-4o-mini | Model name. |
LLM_BASE_URL | None | OpenAI-compatible LLM base URL. |
LLM_API_KEY | None | LLM API key. |
LLM_TEMPERATURE | 0.1 | Sampling temperature. |
LLM_MAX_TOKENS | 2048 | Max response tokens. |
STORAGE_SOURCE_TYPE | local | Storage backend: local, s3, gcs, azure_blob. |
STORAGE_URI | ./data/documents | Storage URI or local path. |
STORAGE_BUCKET | None | S3/GCS bucket name. |
STORAGE_CONTAINER | None | Azure container name. |
STORAGE_PREFIX | None | Object prefix or folder. |
STORAGE_REGION | None | S3 region. |
STORAGE_AWS_ACCESS_KEY_ID | None | AWS access key ID. |
STORAGE_AWS_SECRET_ACCESS_KEY | None | AWS secret access key. |
STORAGE_GCS_SERVICE_ACCOUNT_PATH | None | GCS service account JSON path. |
STORAGE_AZURE_CONNECTION_STRING | None | Azure Blob connection string. |
STORAGE_POLL_INTERVAL_SECONDS | 30 | Poll interval for cloud watchers. |
SAFETY_REFUSE_ON_EMPTY | true | Refuse when no chunks retrieved. |
SAFETY_REFUSE_ON_LOW_SCORE | true | Refuse when scores are low. |
SAFETY_MIN_ANSWER_SCORE | 0.01 | Minimum answer score. |
SAFETY_GROUNDEDNESS_CHECK | true | Groundedness check. |
SAFETY_PII_REDACTION | false | Redact PII before embedding. |
SAFETY_PROMPT_INJECTION_HARDENING | true | Harden chunks against prompt injection. |
DEFAULT_TENANT_ID | None | Default tenant id. |
Docker Deployment
Build and run the entire stack with one command:
docker compose up -d
This starts:
raggit-postgreson port5433raggit-qdranton ports6333and6334raggit-apprunningraggit serve(watcher enabled by default)
Mount your documents into ./data/documents. The container entrypoint applies Alembic migrations before starting the service.
Useful commands
# View logs
docker compose logs -f raggit
# Run one-time ingestion inside the container
docker compose exec raggit raggit ingest
# Run a query
docker compose exec raggit raggit query "What is raggit?"
# Stop everything
docker compose down
Development
raggit uses uv for dependency management and pytest for testing. The default dependency group includes dev tools and all cloud SDKs.
# Install dependencies
uv sync
# Run linting
uv run ruff check .
# Run type checking
uv run mypy raggit
# Run tests
uv run pytest
Project structure
raggit/
api/ # Pydantic models and public API types
cli/ # Typer CLI commands
core/ # Configuration, logging, audit, watcher service
db/ # SQLAlchemy models, repositories, sessions, vector store
ingestion/ # Parsing, chunking, cleaning, embedding, indexer
llm/ # LLM providers and answer augmentation
retrieval/ # Search, RRF, reranker, safety
storage/ # Storage backends and factory
Adding a storage backend
- Subclass
raggit.storage.base.Storage. - Implement
list_files,read_file,file_exists,compute_hash,watch, andclose. - Register the backend in
raggit.storage.factory.create_storage. - Add corresponding fields to
raggit.api.models.StorageConfigif needed. - Add tests mirroring
tests/test_storage_s3.py.
Troubleshooting
Watcher is not detecting local file changes
- Verify you are running
raggit serveorraggit watch. - Ensure the watched path is the exact directory where files are created.
- Network filesystems (NFS, some Docker volumes) may not emit reliable filesystem events. Use host paths when possible.
Initial sync is slow
- The first sync must read, parse, chunk, and embed every file. Subsequent syncs use the stat snapshot and skip unchanged files.
- Enable the sentence-transformers cache and ensure you have enough CPU/GPU for embedding.
Queries return no results
- Check
raggit statusto confirm documents arecompleted. - Verify the active embedding collection in
embedding_collectionsmatches the model you queried with. - Try lowering
--min-scoreor disabling--refuse-on-emptytemporarily.
Cloud watcher misses events
- Cloud watchers poll by default. Decrease
STORAGE_POLL_INTERVAL_SECONDSif you need faster detection. - For true real-time cloud watching, route provider events to a message queue and extend the watcher to consume it.
Permission denied on config file
raggit setupwrites~/.config/raggit/raggit.envwith0600permissions. If you created it manually, ensure it is readable by your user.
License
raggit is released under the MIT License.