Self-hosted document ingestion and hybrid (vector + keyword) retrieval server backed by Postgres/pgvector and Google embeddings.
Project description
VectorRAG (pgrag)
A self-hosted document ingestion and hybrid retrieval server for Retrieval-Augmented Generation pipelines. Bring your own embedding key and database — no SaaS dependency.
VectorRAG ingests text documents, embeds them with Google gemini-embedding-001, stores the vectors in Postgres + pgvector, and serves hybrid (vector + full-text) retrieval over HTTP — with every retrieval knob (top-k, top-p, similarity threshold, MMR, hybrid fusion, metadata filters) adjustable per request. Async ingestion via an Arq worker, API-key auth, per-key rate limiting, Prometheus metrics, structured logs, and health probes are wired in by default.
The PyPI distribution name is
pgrag(the barevectorragname was claimed by an unrelated project). The Python package still imports asvectorragand the CLI isvectorrag. Sources, issue tracker, architecture diagram, and contributor guide live on GitHub.
Why VectorRAG
There are excellent hosted vector databases. There are also good ingestion frameworks. VectorRAG sits in the gap between them:
- It's a service, not a library. A real FastAPI server with auth, rate limiting, observability, and health probes — not a notebook helper.
- You self-host it. Your data stays in your Postgres. Your embeddings cost what you pay Google, not a markup. No outbound calls beyond the embedding API.
- The retrieval layer is configurable per request. Want pure semantic for one query and hybrid + MMR for another? Just change the request body — no redeploy, no separate index.
- It's built to be operated. API-key auth, per-key rate limits, Prometheus metrics with request id correlation,
/healthz+/readyzprobes, graceful worker drain, content-hash dedup, idempotent migrations.
Features
- Hybrid retrieval — HNSW vector ANN (pgvector
halfvec+halfvec_cosine_ops) fused with Postgres full-text search (ts_rank_cdovertsvector). RRF or weighted-α fusion. - Every knob adjustable per request —
top_k,candidate_k,ef_search,similarity_threshold,top_p(softmax nucleus),mmr+mmr_lambda,hybridtoggle,fusion/alpha/rrf_k, JSONB metadatafilter,includefield selection, and a reranker seam ready for a cross-encoder drop-in. - Embeddings via Google Gemini —
gemini-embedding-001at 1536 dims throughgoogle-genai, with task-aware embeddings (RETRIEVAL_DOCUMENTfor chunks,RETRIEVAL_QUERYfor queries) and a content-hash cache that never re-embeds the same chunk twice. - Resilient composition —
CachingEmbedder(RetryingEmbedder(GeminiEmbedder(...)))with exponential backoff on transient failures. - Async ingestion — Submit a document over HTTP, get a
job_idback, watch the Arq worker chunk → embed → bulk-insert with status visible via the API. Failed jobs mark the documentfailed, increment attempts, and re-raise for Arq retries. - Production hardening built in — API-key auth (SHA-256 hashed at rest, Bearer or
X-API-Key), per-key fixed-window rate limiting, JSON logs withX-Request-IDcorrelation, Prometheus metrics (http_requests_total,http_request_duration_seconds),/healthzand/readyz. - Operator tooling —
pg_dump/pg_restorescripts + runbook, recall@k evaluation harness,BatchingEmbedderfor cheap bulk loads. - Strict quality bar — 126 tests, 99% coverage, ruff clean, pyright strict 0 errors, CI runs on every push.
Requirements
- Python 3.12+
- Postgres 16+ with the
pgvectorextension (easiest:pgvector/pgvector:pg17Docker image) - Redis 7+ for the Arq queue
- A Google Gemini API key for embeddings
Installation
pip install pgrag
This installs the vectorrag console script (vectorrag migrate / serve / worker / create-key) and the importable package (import vectorrag).
Set the required environment variables, then bring up the service:
export GCP_API_KEY=...
export DATABASE_URL=postgresql://user:pass@localhost:5432/rag
export REDIS_URL=redis://localhost:6379/0
vectorrag --version # print the installed version
vectorrag migrate # apply schema
vectorrag create-key my-app # mint a server API key (prints the raw key once — capture it)
vectorrag serve & # HTTP API on :8000
vectorrag worker & # ingestion worker
For a complete docker-compose stack (Postgres + Redis + API + worker, all wired together), clone the repo:
git clone https://github.com/sagarhedaoo/VectorRAG.git
cd VectorRAG && cp .env.example .env # fill in GCP_API_KEY
docker compose up -d --build
docker compose exec api vectorrag migrate
docker compose exec api vectorrag create-key my-app
Usage
All examples assume BASE=http://localhost:8000 and KEY=<your-api-key>.
1. Create a collection
curl -s -X POST $BASE/collections \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"name":"my-docs"}'
Returns:
{
"id": "8c2a...",
"name": "my-docs",
"embedding_model": "gemini-embedding-001",
"embedding_dim": 1536,
"distance": "cosine"
}
2. Submit a document for ingestion
curl -s -X POST $BASE/collections/<collection_id>/documents \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{
"text": "VectorRAG stores embeddings in Postgres and serves hybrid retrieval...",
"title": "intro",
"metadata": {"source": "docs", "lang": "en"},
"chunk_size": 512,
"chunk_overlap": 64
}'
Returns 202 Accepted with { "document_id": ..., "job_id": ..., "deduplicated": false }. The worker picks the job up, chunks the text, embeds each chunk (using cached results when available), and bulk-inserts. Poll GET /documents/{id} until "status": "done".
Dedup is per-collection on sha256(text). Submitting the same text twice in the same collection returns deduplicated: true and no new job.
3. Query the collection
curl -s -X POST $BASE/collections/<collection_id>/search \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{
"query": "hybrid retrieval",
"top_k": 5,
"hybrid": true,
"fusion": "rrf",
"filter": {"lang": "en"},
"include": ["content", "metadata", "score"]
}'
Returns:
{
"results": [
{
"chunk_id": "...",
"score": 0.84,
"content": "VectorRAG stores embeddings in Postgres...",
"metadata": { "source": "docs", "lang": "en" }
}
]
}
Configuration
Set via environment variables (or a local .env):
| Var | Required | Default | Purpose |
|---|---|---|---|
GCP_API_KEY |
✓ | — | Google Gemini embedding API key |
DATABASE_URL |
✓ | — | Postgres connection string (must have pgvector) |
REDIS_URL |
✓ | — | Redis URL for the Arq queue |
EMBEDDING_MODEL |
gemini-embedding-001 |
Embedding model name | |
EMBEDDING_DIM |
1536 |
Embedding dimensionality (Matryoshka-truncated) | |
EMBED_BATCH_SIZE |
100 |
Documents per embedding call | |
HNSW_EF_SEARCH_DEFAULT |
80 |
Default HNSW recall/speed knob | |
API_KEYS_BOOTSTRAP |
"" |
If set, this raw key is inserted as an API key on first boot (idempotent). Convenient for local dev. |
Search parameters
Every request to POST /collections/{id}/search accepts the following:
| Parameter | Type | Default | Notes |
|---|---|---|---|
query |
string|null | — | Required unless query_vector is provided. |
query_vector |
float[]|null | — | Bypass embedding by sending a vector. Length should match embedding_dim. |
top_k |
int (1..1000) | 10 |
Final results returned. Must be ≤ candidate_k. |
candidate_k |
int (1..10000) | 100 |
Candidates pulled from each index before fusion. |
ef_search |
int|null (1..1000) | HNSW_EF_SEARCH_DEFAULT |
HNSW recall/speed dial. Set higher for better recall. |
similarity_threshold |
float|null (-1..1) | — | Drop candidates with vector_similarity below this. Keyword-only hits are preserved. |
top_p |
float|null (0..1] | — | Nucleus cutoff over softmaxed scores. |
mmr |
bool | false |
Apply Maximal Marginal Relevance after threshold/top_p. |
mmr_lambda |
float (0..1) | 0.5 |
0 = max diversity, 1 = max relevance. |
hybrid |
bool | true |
Combine vector ANN with full-text search. |
fusion |
"rrf"|"weighted" |
"rrf" |
Reciprocal Rank Fusion or min-max-normalized weighted blend. |
alpha |
float (0..1) | 0.5 |
Weighted fusion only — vector contribution weight. |
rrf_k |
int ≥1 | 60 |
RRF constant. |
filter |
object | {} |
JSONB containment filter on chunks.metadata (e.g. {"lang": "en"}). |
distance |
"cosine" |
"cosine" |
Only cosine supported in v1; others return 400. |
rerank |
bool | false |
Enable the reranker seam (no-op until a cross-encoder is wired). |
include |
string[] | ["content","metadata","score"] |
Subset of content, metadata, score, embedding. |
Two common recipes:
- Pure semantic:
{"query": "...", "top_k": 5, "hybrid": false} - Hybrid + diverse + filtered:
{"query": "...", "top_k": 5, "fusion": "rrf", "mmr": true, "filter": {"lang": "en"}}
API reference
All data endpoints require Authorization: Bearer <key> (or X-API-Key: <key>). Health and /metrics are open.
| Method | Path | Notes |
|---|---|---|
POST |
/collections |
Create a collection. 409 on duplicate name. |
GET |
/collections |
List (limit/offset query params). |
GET |
/collections/{id} |
Get one. 404 if missing. |
DELETE |
/collections/{id} |
Cascades to documents and chunks. 204 on success. |
POST |
/collections/{id}/documents |
Submit a document. 202 on accept; 404 on missing collection; 422 on empty text. |
GET |
/documents/{id} |
Document status + metadata. |
GET |
/jobs/{id} |
Job status, progress, attempts, error. |
POST |
/collections/{id}/search |
Hybrid search. 400 on non-cosine distance; 404 on missing collection. |
POST |
/embeddings |
Debug: returns {dim, embedding} for arbitrary text. |
GET |
/healthz |
Liveness — always 200 {"status":"ok"}. |
GET |
/readyz |
Readiness — 200 if DB reachable, 503 otherwise. |
GET |
/metrics |
Prometheus text format. |
Roadmap
- v1.1 — Production hardening for scale. Optional binary-quantization + rerank path for ≥10M-chunk deployments, partitioned
chunkstable, OpenTelemetry tracing. - v1.2 — Beyond the basics. Cross-encoder reranker implementation behind the existing seam, batch embedding API path (50% off via Gemini batch endpoint), HyDE / query expansion experiments.
For the full architecture diagram, project structure, contributor guide, and operator runbooks, see the GitHub repository.
License
Released under the Apache License 2.0.
Acknowledgements
VectorRAG stands on the shoulders of:
- pgvector — Postgres vector search.
- FastAPI + Pydantic — the HTTP and validation layer.
- psycopg3 — async Postgres driver.
- Arq — Redis-backed async task queue.
- Google AI Studio /
google-genai— embeddings. - Alembic — schema migrations.
- testcontainers-python — test infrastructure.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pgrag-1.1.0.tar.gz.
File metadata
- Download URL: pgrag-1.1.0.tar.gz
- Upload date:
- Size: 43.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fec21a0b56df0dd5471e056dda6a1eb7333714ad56f54c5104c65c9016c494fc
|
|
| MD5 |
e532737eff8bef6091f1996620b03bf5
|
|
| BLAKE2b-256 |
b84ec5c8f53ca0c3bdb144bbac80b146677ece57962eeaa5e678ea907c1dc360
|
Provenance
The following attestation bundles were made for pgrag-1.1.0.tar.gz:
Publisher:
release.yml on sagarhedaoo/VectorRAG
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pgrag-1.1.0.tar.gz -
Subject digest:
fec21a0b56df0dd5471e056dda6a1eb7333714ad56f54c5104c65c9016c494fc - Sigstore transparency entry: 1674026882
- Sigstore integration time:
-
Permalink:
sagarhedaoo/VectorRAG@409938981b9083d270e4e86fce84406117937870 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/sagarhedaoo
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@409938981b9083d270e4e86fce84406117937870 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pgrag-1.1.0-py3-none-any.whl.
File metadata
- Download URL: pgrag-1.1.0-py3-none-any.whl
- Upload date:
- Size: 46.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0eed1ad8cd879a66227f73117280800efbd0d034934ce715d3c17804f40b5a3b
|
|
| MD5 |
121203e962beb1094d01cc8aa95b5d90
|
|
| BLAKE2b-256 |
f5cd3b8210f43ffdecd636663480a4367be3e6938d98ec11844c27d104b31c14
|
Provenance
The following attestation bundles were made for pgrag-1.1.0-py3-none-any.whl:
Publisher:
release.yml on sagarhedaoo/VectorRAG
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pgrag-1.1.0-py3-none-any.whl -
Subject digest:
0eed1ad8cd879a66227f73117280800efbd0d034934ce715d3c17804f40b5a3b - Sigstore transparency entry: 1674026896
- Sigstore integration time:
-
Permalink:
sagarhedaoo/VectorRAG@409938981b9083d270e4e86fce84406117937870 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/sagarhedaoo
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@409938981b9083d270e4e86fce84406117937870 -
Trigger Event:
push
-
Statement type: