Reasoning-first document intelligence system
Project description
Querdex
Reasoning-first document intelligence system.
Querdex indexes any document into a hierarchical tree, then uses a two-tier LLM search to answer questions with cited sources. It works without an LLM (keyword heuristics), and optionally plugs in Anthropic or OpenAI for higher-quality results.
Table of Contents
- How it works
- Installation
- Quick Start (CLI)
- LLM Setup
- Structured Extraction
- CLI Reference
- Python API
- Supported File Types
- Environment Variables
How it works
Document
│
▼
Ingestion ──► parse into pages/sections (Section[])
│
▼
Indexing ───► build hierarchical tree (TreeNode) + entity map + knowledge graph
│
▼
Storage ────► persist to SQLite (sections, tree, entities, graph, query cache)
│
▼
Query
├─ Tier 1: LLM (or keyword) batch-prune of tree nodes
├─ Tier 2: LLM (or heuristic) per-node relevance scoring
├─ Retrieval: pull section text for selected nodes
└─ Answer: LLM synthesizes answer with source citations
│
▼
Adaptive ───► update node summaries based on query feedback (runs in background)
Three query routes are selected automatically:
- single_doc — standard hierarchical search on one document
- multi_doc — virtual super-tree across up to 3 documents
- graph — entity-seeded graph walk for relationship queries ("how does X relate to Y?")
Installation
Base install (no LLM, uses keyword heuristics):
pip install querdex
With Anthropic (Claude):
pip install querdex[anthropic]
With OpenAI (GPT):
pip install querdex[openai]
Development:
git clone <repo>
cd querdex
uv sync --extra dev
# or with an LLM provider:
uv sync --extra dev --extra anthropic
uv sync --extra dev --extra openai
Requirements: Python 3.11+
Quick Start (CLI)
1. Index a document
querdex index ./report.pdf --doc-id annual-report
Output:
Indexed doc_id=annual-report version=1
Nodes=12 max_depth=3
2. Query it
querdex query --doc-id annual-report --query "What was the Q3 revenue?"
Output:
Query ID: 3f8a1c...
Intent: single_doc | Cache hit: False
Q3 revenue was $1.2B, up 8% year-over-year (Revenue Analysis, pages 4-6).
3. Multi-turn conversation (session)
# First turn
querdex query --doc-id annual-report \
--query "What were the risk factors?" \
--session-id session_001
# Second turn — context from first turn is carried over
querdex query --doc-id annual-report \
--query "Which of those risks materialised?" \
--session-id session_001
4. Re-index an updated document
When the document changes, Querdex only rebuilds the affected parts:
querdex index ./report_v2.pdf --doc-id annual-report
5. Delete a document
querdex delete --doc-id annual-report
Custom database path
By default the database is stored at ./index_store/querdex.db. To change it:
querdex --db /path/to/my.db index ./report.pdf --doc-id demo
querdex --db /path/to/my.db query --doc-id demo --query "summary?"
LLM Setup
Without any LLM configured, Querdex falls back to keyword/heuristic matching — it always produces an answer, just less precise.
Anthropic (Claude)
export QUERDEX_LLM_PROVIDER=anthropic
export QUERDEX_LLM_API_KEY=sk-ant-...
# Optional: override model defaults
export QUERDEX_LLM_TIER1_MODEL=claude-haiku-4-5-20251001 # fast, cheap (batch prune)
export QUERDEX_LLM_TIER2_MODEL=claude-sonnet-4-6 # powerful (deep reasoning + answers)
OpenAI (GPT)
export QUERDEX_LLM_PROVIDER=openai
export QUERDEX_LLM_API_KEY=sk-...
# Optional: override model defaults
export QUERDEX_LLM_TIER1_MODEL=gpt-4o-mini # fast, cheap
export QUERDEX_LLM_TIER2_MODEL=gpt-4o # powerful
How the two tiers are used:
| Tier | Model | Purpose |
|---|---|---|
| Tier 1 | cheap/fast | Single batched call to prune all tree nodes to the relevant few |
| Tier 2 | powerful | Per-node deep reasoning to confirm relevance + score confidence |
| Answer | powerful | Synthesise a cited answer from the retrieved section text |
Structured Extraction
Pull structured facts out of any indexed document — with source grounding: every extraction carries the exact section, page, and character span it came from, so nothing is silently hallucinated.
The schema is defined by example, not by code. Describe what you want and (optionally) show one or two examples:
querdex extract --doc-id demo \
--prompt "Extract revenue figures and executive names" \
--examples examples.json \
--html review.html
examples.json:
[
{
"text": "Alice Chen reported revenue of $5M in Q1.",
"extractions": [
{"extraction_class": "metric", "extraction_text": "revenue of $5M", "attributes": {"period": "Q1"}},
{"extraction_class": "person", "extraction_text": "Alice Chen"}
]
}
]
The classes and attribute keys in your examples define the output schema. Every result is aligned back to the source text (exact → fuzzy matching); model output that cannot be located is kept but flagged unaligned so you can review it instead of trusting it.
--html writes a self-contained review page: the full document with color-coded highlights per extraction class, toggleable legend, attribute tooltips, and a click-to-jump list of all extractions.
Long documents are chunked and processed in parallel; use --passes 2 to trade extra LLM calls for higher recall. Without an LLM configured, extraction degrades to literal matching of your example texts.
Python API:
from querdex.extraction import ExtractionTask, ExtractionExample, ExampleExtraction
task = ExtractionTask(
description="Extract revenue figures and executive names",
examples=[...],
)
run = engine.extract_document("demo", task, passes=1)
for e in run.extractions:
print(e.extraction_class, repr(e.extraction_text), e.section_id, e.char_start, e.alignment)
CLI Reference
querdex [--db PATH] <command> [options]
| Command | Description |
|---|---|
index <file> |
Index a document. Auto-detects format from extension. |
query |
Query an indexed document. |
extract |
Run schema-by-example structured extraction over an indexed document. |
delete |
Remove a document and all its data from the store. |
index
querdex index <file_path> [--doc-id ID]
| Argument | Default | Description |
|---|---|---|
file_path |
required | Path to the document to index |
--doc-id |
auto-generated from filename+hash | Stable identifier for this document |
query
querdex query --doc-id ID --query TEXT [--session-id ID]
| Argument | Default | Description |
|---|---|---|
--doc-id |
required | Document to query |
--query |
required | Natural language question |
--session-id |
none | Enables multi-turn context (pass same ID across turns) |
extract
querdex extract --doc-id ID --prompt TEXT [--examples FILE] [--passes N] [--html FILE]
| Argument | Default | Description |
|---|---|---|
--doc-id |
required | Document to extract from |
--prompt |
required | Natural language description of what to extract |
--examples |
none | JSON file with few-shot examples (defines the output schema) |
--passes |
1 |
Extraction passes; more passes improve recall |
--html |
none | Write an interactive HTML review page to this path |
delete
querdex delete --doc-id ID
Python API
For integration into your own application:
import asyncio
from querdex.services import build_engine
# build_engine reads QUERDEX_LLM_* env vars automatically
engine = build_engine("./index_store/querdex.db")
# Index a document
doc = asyncio.run(engine.index_document("./report.pdf", doc_id="annual-report"))
print(f"Indexed: {doc.doc_id} | nodes={doc.stats.total_nodes}")
# Query
result = engine.query_document("annual-report", "What was Q3 revenue?")
print(result.answer)
print(f"Confidence: {result.confidence:.0%}")
for source in result.source_nodes:
print(f" Source: {source.title}, pages {source.pages}")
# Multi-turn query
result2 = engine.query_document(
"annual-report",
"What caused that increase?",
session_id="my-session-001",
)
# Re-index after the document changes
doc_v2 = asyncio.run(engine.reindex_document("./report_v2.pdf", doc_id="annual-report"))
# Delete
engine.store.delete_document("annual-report")
# Always close when done
engine.store.close()
Passing an LLM client directly
from querdex.llm.anthropic_client import AnthropicLLMClient
from querdex.services.engine import QuerdexEngine
from querdex.storage import SQLiteStore
llm = AnthropicLLMClient(
api_key="sk-ant-...",
tier1_model="claude-haiku-4-5-20251001",
tier2_model="claude-sonnet-4-6",
)
store = SQLiteStore("./querdex.db")
engine = QuerdexEngine(store, llm_client=llm)
Using the FakeLLMClient in tests
from querdex.llm.fake_client import FakeLLMClient
from querdex.query.answering import AnswerGenerator
fake = FakeLLMClient(
default='{"answer": "Revenue was $1.2B.", "confidence": 0.9}'
)
gen = AnswerGenerator(llm_client=fake)
answer, confidence, sources = gen.generate("What was revenue?", chunks)
Supported File Types
| Extension | Parser | Notes |
|---|---|---|
.txt |
TextParser | Plain text, split by paragraphs |
.md, .markdown |
MarkdownParser | Heading-aware section splitting |
.html, .htm |
HTMLParser | Strips tags, extracts text blocks |
.docx |
DOCXParser | Microsoft Word, paragraph-level |
.pdf |
PDFParser | Page-level; OCR optional (see below) |
.py |
PythonCodeParser | Function/class level chunking |
.js, .ts, .jsx, .tsx |
JSCodeParser | Function-level chunking |
.csv |
CSVParser | Row-batched sections |
.db, .sqlite |
SQLiteParser | Table-level sections |
.mp3, .wav, .m4a, .mp4, .mov |
AudioVideoParser | Transcript-based (requires Whisper or similar) |
.url |
URLParser | Fetches and parses the web page at that URL |
| URL string | URLParser | Pass a URL string directly as the file path |
PDF OCR
For scanned PDFs, enable OCR via environment variables:
# Tesseract (local)
export QUERDEX_OCR_ENABLED=true
export QUERDEX_OCR_PROVIDER=tesseract # default when OCR enabled
export QUERDEX_TESSERACT_CMD=tesseract # path to tesseract binary
# Cloud OCR (custom endpoint)
export QUERDEX_OCR_ENABLED=true
export QUERDEX_OCR_PROVIDER=cloud
export QUERDEX_OCR_ENDPOINT=https://your-ocr-api.com/v1/ocr
export QUERDEX_OCR_API_KEY=your-key
Environment Variables
| Variable | Default | Description |
|---|---|---|
QUERDEX_LLM_PROVIDER |
(none) | anthropic or openai. If unset, heuristic mode is used. |
QUERDEX_LLM_API_KEY |
(none) | API key for the selected provider |
QUERDEX_LLM_TIER1_MODEL |
claude-haiku-4-5-20251001 / gpt-4o-mini |
Fast model for batch node pruning |
QUERDEX_LLM_TIER2_MODEL |
claude-sonnet-4-6 / gpt-4o |
Powerful model for deep reasoning and answers |
QUERDEX_OCR_ENABLED |
false |
Enable OCR for scanned PDFs |
QUERDEX_OCR_PROVIDER |
tesseract |
tesseract or cloud |
QUERDEX_TESSERACT_CMD |
tesseract |
Path to Tesseract binary |
QUERDEX_OCR_ENDPOINT |
(none) | Endpoint URL for cloud OCR provider |
QUERDEX_OCR_API_KEY |
(none) | API key for cloud OCR provider |
License
MIT
Project details
Release history Release notifications | RSS feed
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 querdex-0.2.0.tar.gz.
File metadata
- Download URL: querdex-0.2.0.tar.gz
- Upload date:
- Size: 114.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fd9c10846cccea157355c34e5427fd1e47f3caa991ccc5c8f5bbb94a31d67bc6
|
|
| MD5 |
13c9fa567e3f053c457eb47f29a85076
|
|
| BLAKE2b-256 |
9c66758cfdd42f10f6935e6a98cab3eb14eece81685cc77fa264c8b896868455
|
Provenance
The following attestation bundles were made for querdex-0.2.0.tar.gz:
Publisher:
publish.yml on its-animay/querdex
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
querdex-0.2.0.tar.gz -
Subject digest:
fd9c10846cccea157355c34e5427fd1e47f3caa991ccc5c8f5bbb94a31d67bc6 - Sigstore transparency entry: 2087833025
- Sigstore integration time:
-
Permalink:
its-animay/querdex@90000516d6543db95a3498dd3ae9c72ea25a69d4 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/its-animay
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@90000516d6543db95a3498dd3ae9c72ea25a69d4 -
Trigger Event:
release
-
Statement type:
File details
Details for the file querdex-0.2.0-py3-none-any.whl.
File metadata
- Download URL: querdex-0.2.0-py3-none-any.whl
- Upload date:
- Size: 78.1 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 |
389380684daf1a8000a16228bd0e8d38e7981b57292b93f10212771f54d5b856
|
|
| MD5 |
99792c670262ea4ed080f31568eca12c
|
|
| BLAKE2b-256 |
6a631b55dd8e1d038742b3e4e8cb3c16eca3eecc1c3ea5820c3f81c05192a34b
|
Provenance
The following attestation bundles were made for querdex-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on its-animay/querdex
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
querdex-0.2.0-py3-none-any.whl -
Subject digest:
389380684daf1a8000a16228bd0e8d38e7981b57292b93f10212771f54d5b856 - Sigstore transparency entry: 2087833139
- Sigstore integration time:
-
Permalink:
its-animay/querdex@90000516d6543db95a3498dd3ae9c72ea25a69d4 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/its-animay
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@90000516d6543db95a3498dd3ae9c72ea25a69d4 -
Trigger Event:
release
-
Statement type: