TrifectaRAG
Local multi-modal RAG: HNSW (vectors) + BM25 (keywords) + a knowledge graph, fused with Reciprocal Rank Fusion and optionally reranked with MMR.
Install the Python SDK and CLI from PyPI, or run the optional browser tutor from this repo.
pip install trifectarag[pdf]
from trifecta import TrifectaClient, PDFIngestor
client = TrifectaClient(device="cpu")
PDFIngestor(client, mode="page").ingest_pdf("textbook.pdf", output_dir="extracted")
client.save_snapshot("my.index")
hits = client.get_results(client.query("Newton's method", top_k=5))
for row in hits:
print(row["global_id"], row["score"], row["metadata"].get("page"))
trifecta ingest textbook.pdf --index ./my.index
trifecta query "Newton's method" --index ./my.index --top-k 5
trifecta info --index ./my.index
Install
Requires Python 3.10+ and a C++17 toolchain only when building from source. PyPI wheels ship the compiled trifecta_py extension.
| Extra | What it adds |
|---|---|
| (none) | Core SDK + CLI (hash embeddings, no PyTorch) |
[pdf] |
PyMuPDF ingestion + vector-figure extraction |
[ml] |
CLIP embeddings via torch / transformers |
[mcp] |
Model Context Protocol server |
[agent] |
Ollama tutor agent helpers |
[all] |
Everything above |
pip install trifectarag # library + CLI
pip install trifectarag[pdf] # + PDF ingestion
pip install trifectarag[all] # + CLIP, MCP, agent
From a clone of this repository:
pip install -e ".[pdf,ml]"
Default index path is ./trifecta.index (or $TRIFECTA_INDEX). Snapshots are two files: <stem>.trifecta (C++ engine) and <stem>.meta.gz (Python metadata).
Python SDK
from trifecta import TrifectaClient, PDFIngestor
from trifecta import trifecta_py as tr
client = TrifectaClient(device="cpu")
gid = client.add_document(
"Lagrange interpolation uses basis polynomials L_i(x).",
metadata={"source": "notes", "page": 1},
)
client.add_image("figure.png", caption="Bisection interval", metadata={"source": "notes", "page": 2})
client.add_edge(gid, other_gid, tr.EdgeType.RELATES_TO) # or EXPLAINS / DEPICTS
# PDF: page mode = one chunk per page + cropped figures. HNSW + BM25 are filled together.
stats = PDFIngestor(client, mode="page").ingest_pdf("textbook.pdf", output_dir="extracted")
print(stats) # pages, text_chunks, images, kg_edges
client.save_snapshot("my.index")
client = TrifectaClient.from_snapshot("my.index", device="cpu")
mode="classical" uses overlapping word chunks instead of one page per node.
Query
hits = client.query(text="Lagrange interpolation", top_k=8)
for row in client.get_results(hits):
meta = row["metadata"]
print(row["global_id"], row["score"], meta.get("source"), meta.get("page"))
# Keyword only / vector only
client.query(text="bisection", top_k=5, use_hnsw=False, use_bm25=True)
client.query(text="bisection", top_k=5, use_hnsw=True, use_bm25=False)
# Image + text late fusion (CLIP, or hash embeddings if [ml] is not installed)
client.query(text="root finding figure", image="scan.png", top_k=5)
Page index
client.list_sources()
client.list_pages("Numerical_Analysis")
client.get_page_chunks("Numerical_Analysis", 36)
client.get_chunk_page(209) # -> ("Numerical_Analysis", 213)
Hybrid search the tutor uses (MMR + provenance)
from trifecta import hybrid_search, serialize_sources
hits = hybrid_search(client, "Lagrange interpolation", top_k=4)
print(serialize_sources(hits))
CLI
trifecta ingest FILE.pdf [FILE.pdf ...] [--mode page|classical] [--pages START:END]
trifecta query TEXT [--image FILE] [--top-k N] [--no-hnsw] [--no-bm25]
trifecta add-text TEXT [--source NAME] [--page N]
trifecta add-image FILE [--caption TEXT]
trifecta info | sources | pages [--source NAME] [--page N]
trifecta get GID
trifecta mcp [--ingest FILE.pdf]
Shared flags: --index PATH, --device cpu|cuda, --json.
trifecta ingest examples/data/Numerical_Analysis.pdf --index ./na.index --pages 11:444
trifecta query "trapezoidal rule" --index ./na.index --json
python -m trifecta info --index ./na.index
How retrieval works
On ingest, HNSW and BM25 are always built together. At query time:
- Optional HNSW (vector / embedding search)
- Optional BM25 (lexical search)
- Knowledge-graph 1-hop expansion from those seeds
- Reciprocal rank fusion, then optional MMR so near-duplicate pages are dropped
Each hit carries source, page, global_id, and RRF score.
Browser tutor (this repo)
The optional UI is not on PyPI. From a clone:
- Python 3.11+
- Node 18+
- Ollama with a tool-capable model (default
qwen2.5:7b)
pip install -e ".[all]"
pip install -r requirements.txt
ollama pull qwen2.5:7b
cd frontend
npm install
# terminal 1 — API (port 8001)
python api.py
# terminal 2 — Vite (port 5172)
cd frontend
npm run dev
Open http://localhost:5172/. On first start the API loads a textbook snapshot under examples/data/ if one exists (*_page_*.trifecta).
| Variable | Default | Meaning |
|---|---|---|
OLLAMA_URL |
http://127.0.0.1:11434 |
Ollama server |
OLLAMA_MODEL / TRIFECTA_OLLAMA_MODEL |
qwen2.5:7b |
Chat model |
OLLAMA_CHAT_TIMEOUT_SECONDS |
150 |
Per agent turn |
TRIFECTA_AGENT_ROUNDS |
6 |
Max tool-call rounds |
Index a file from Library, then ask exam / figure / web questions in the single chat. Retrieval toggles (HNSW / BM25) live in the sidebar.
HTTP API (what the UI calls)
Base URL with Vite: same origin. Direct: http://127.0.0.1:8001.
| Method | Path | Use |
|---|---|---|
| GET | /health |
Chunks, corpus, retriever label |
| POST | /chat |
Agent: { mode, messages, question?, attachments? } |
| POST | /upload |
Index PDF/DOCX/TXT/MD/CSV → { task_id } |
| GET | /ingest-status/{task_id} |
Poll until done / error |
| GET | /corpora |
Indexed + on-disk sources |
| GET/POST | /settings/retrieval |
{ use_hnsw, use_bm25 } |
Example scripts
From the repo root, after pip install -e ".[pdf,ml]":
python examples/00_basic_usage.py
python examples/01_ingest_textbook.py
python examples/02_query_textbook.py
python examples/05_page_index_demo.py
Put a PDF in examples/data/ or set TRIFECTA_TEXTBOOK_PDF.
Publishing
pip install build twine
python -m build
python -m twine check dist/*
python -m twine upload dist/*
GitHub Releases trigger .github/workflows/publish.yml (cibuildwheel + trusted PyPI publishing).
Metadata
Release files for trifectarag 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| trifectarag-0.1.0.tar.gz | 371.0 kB | Without provenance | Details for trifectarag-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| trifectarag-0.1.0-cp312-cp312-win_amd64.whl | CPython 3.12 | CPython 3.12 | Windows x86-64 | Details for trifectarag-0.1.0-cp312-cp312-win_amd64.whl |
Total release size: 1.6 MB
Release files / trifectarag-0.1.0.tar.gz
| Download URL | trifectarag-0.1.0.tar.gz |
|---|---|
| Size | 371.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
31bce412b21dd35fb513412b04cb8a58194e792858c4f381d8b2c2826d1c2115
|
|
BLAKE2b-256 checksum How to use checksums |
f73569c711659ab5849186fde5decf6b70469f3140d637ae6308553ec28fd2f5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.4
|
Release files / trifectarag-0.1.0-cp312-cp312-win_amd64.whl
| Download URL | trifectarag-0.1.0-cp312-cp312-win_amd64.whl |
|---|---|
| Size | 1.2 MB |
| Tags | CPython 3.12 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
69eb14540bdfe3f3d18d48dc810ebd9f58d0e0d7b1db563159899dc9fbb431f0
|
|
BLAKE2b-256 checksum How to use checksums |
4c17de45a615d6fd56e362b4d337daaa18fcd22c8cc7f9d428db31cce0c39487
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.4
|
Security for trifectarag 0.1.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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
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.