Spaces:
Running
Download README.md from HarryDataScience/documind-ai: direct link, hf CLI and curl.
- Browser
- Download file 8.98 kB
-
https://huggingface.co/spaces/HarryDataScience/documind-ai/resolve/main/README.md
- Command line
-
hf download hf://spaces/HarryDataScience/documind-ai/README.md
-
curl -L -o README.md https://huggingface.co/spaces/HarryDataScience/documind-ai/resolve/main/README.md
title: DocuMind AI
emoji: 🧠
colorFrom: blue
colorTo: purple
sdk: docker
app_port: 8000
pinned: false
DocuMind AI
Ask questions about your PDFs and get answers with page-level citations. FastAPI + PyMuPDF + sentence-transformers + FAISS + ChromaDB + LangGraph + Groq.
Quick start
You need Python 3.10, 3.11 or 3.12 (3.13/3.14 do not work: torch 2.5.1 has no wheels for them).
Get a free API key at https://console.groq.com.
Windows (PowerShell)
.\run.ps1
The script creates .venv, installs everything, creates .env (and opens it so you can paste
GROQ_API_KEY), then starts the app. Or do it manually:
py -3.12 -m venv .venv
.\.venv\Scripts\Activate.ps1 # if blocked: Set-ExecutionPolicy -Scope Process RemoteSigned
pip install -r requirements-dev.txt
Copy-Item .env.example .env # then edit .env and set GROQ_API_KEY
python main.py
Linux / macOS
./run.sh # first run creates .env; add GROQ_API_KEY, then run it again
Docker
cp .env.example .env # Windows: Copy-Item .env.example .env (then set GROQ_API_KEY)
docker-compose up --build
Open http://localhost:8000 (API docs at /docs). The first upload/question loads the
embedding model (~90 MB download when running outside Docker).
How it works
Upload: PDF -> PyMuPDF text (per page, reading order, OCR fallback) -> clean -> token-based
chunks (200 tokens, 30 overlap) -> MiniLM embeddings (384-d, normalised) -> ChromaDB (text + metadata) and FAISS (vectors)
Query: question -> (condense follow-up using chat history) -> embed -> top-K cosine search
-> weak evidence? -> "no evidence" answer
-> otherwise Groq LLM answers with [Source N] citations -> grounding check
- LangGraph nodes:
condense_question(only with history) ->retrieve-> gate ->generate->check_evidence. - Vector search: FAISS (
IndexFlatIPon L2-normalised vectors = exact cosine similarity) finds the nearest chunk ids; ChromaDB stores the chunk text, metadata and the document list (one collectiondocuments, filters ondoc_id/owner_id). Chroma is the source of truth: the FAISS index lives in memory and is rebuilt from Chroma at startup (about 0.5 s per 10,000 chunks) and automatically whenever the two disagree.VECTOR_BACKEND=chromaswitches to Chroma's own HNSW search; iffaiss-cpuis missing the app falls back to it with a warning. - Why FAISS here: exact search (100% recall) and faster than Chroma's approximate index at this size
(
python scripts/benchmark_search.py). A flat index scans every vector, so past roughly a million chunks switch to an approximate FAISS index (IVF/HNSW). confidence(HIGH/MEDIUM/LOW) comes from retrieval similarity;is_groundedis a lexical-overlap heuristic. Neither proves the answer is correct - always check the cited pages.- Only the top-K chunks are shown to the LLM, so questions about the whole document ("how many
sections are there?") can be wrong while still showing
is_grounded=true. - Reasoning models such as
openai/gpt-oss-*need a large answer budget (LLM_MAX_TOKENS, default 4096).
Configuration (.env)
| Variable | Default | Meaning |
|---|---|---|
GROQ_API_KEY |
- | Required for answers. Placeholder values are treated as "not set". |
LLM_MODEL |
openai/gpt-oss-20b |
Any chat model available to your Groq key (see Troubleshooting). |
LLM_MAX_TOKENS |
4096 |
Answer budget. Reasoning models (gpt-oss) need a large value. |
API_KEY |
empty | If set, clients must send X-API-Key; data is isolated per key. Empty = open (dev). |
ALLOWED_ORIGINS |
http://localhost:8000 |
Comma-separated CORS origins. |
CHUNK_SIZE / CHUNK_OVERLAP / CHUNK_UNIT |
200 / 30 / tokens |
Keep chunks <= 256 tokens: the embedding model ignores the rest. |
TOP_K_RESULTS |
5 |
Chunks sent to the LLM. |
RETRIEVAL_THRESHOLD |
0.20 |
Below this top similarity the LLM is not called. |
GROUNDING_OVERLAP_THRESHOLD |
0.30 |
Word-overlap needed for is_grounded=true. |
VECTOR_BACKEND |
faiss |
faiss or chroma - which engine runs the similarity search. |
HYBRID_SEARCH |
false |
Re-rank the top vector candidates with BM25 (RRF fusion). |
ENABLE_RERANK |
false |
Cross-encoder re-ranking (downloads another model). |
ENABLE_FAITHFULNESS_CHECK |
false |
Extra LLM call that judges whether the answer is supported. |
TORCH_THREADS |
1 |
CPU threads for embeddings. |
DEBUG |
false |
true enables auto-reload. |
API
| Method | Path | Notes |
|---|---|---|
| GET | /api/health |
Public. |
| POST | /api/upload |
PDF, max 50 MB. >= 10 MB is indexed in the background and returns a job_id. |
| GET | /api/jobs/{id} |
Background upload status (queued/processing/done/failed). |
| GET | /api/documents |
List indexed documents. |
| DELETE | /api/documents/{doc_id} |
Remove vectors and the stored PDF. |
| POST | /api/query |
{"question": "...", "top_k": 5, "doc_ids": [], "history": [{"role":"user","content":"..."}]} (history: max 10 turns, 4000 chars each). |
| POST | /api/query/stream |
Same input, Server-Sent Events: token events, then one final event. |
Query response:
{
"answer": "The report identifies three findings [Source 1].",
"sources": [{"doc_name": "report.pdf", "page_number": 4, "text": "Relevant excerpt", "similarity": 0.82}],
"query": "What are the findings?", "model_used": "openai/gpt-oss-20b",
"latency_ms": 1200.0, "chunks_retrieved": 1, "is_grounded": true, "confidence": "HIGH"
}
Errors: 400 bad upload, 401 bad API key, 404 nothing indexed / unknown id, 409 content owned by
another key, 422 unreadable or scanned PDF without OCR / invalid body, 429 rate limit
(10 uploads/min, 30 queries/min per IP), 500 generic message with a request_id.
Project structure
main.py app, CORS, rate limiting, request ids
backend/api/{routes,schemas}.py HTTP layer
backend/core/{config,logger}.py settings + logging
backend/services/
pdf_processor.py extraction, OCR fallback, chunking
embeddings.py sentence-transformers wrapper (lazy singleton)
vector_store.py ChromaDB storage + indexing/search/delete (+ optional BM25/rerank)
faiss_index.py FAISS exact-cosine index with delete and doc/owner filters
rag_graph.py LangGraph workflow, prompts, grounding check
rag_engine.py adapts graph output to the API response
frontend/ single-page UI (vanilla JS)
tests/ scripts/ pytest suite, retrieval evaluation script
Development
pip install -r requirements-dev.txt
pytest tests/ -v
ruff check .
python scripts/eval_retrieval.py scripts/eval_example.json # hit-rate@k / MRR on your own PDFs
python scripts/benchmark_search.py # FAISS vs Chroma search speed + recall
Troubleshooting
| Problem | Fix |
|---|---|
No matching distribution found for torch==2.5.1 |
You are on Python 3.13/3.14. Use Python 3.12 (py -3.12 -m venv .venv). |
running scripts is disabled (PowerShell) |
Set-ExecutionPolicy -Scope Process RemoteSigned |
Answers say "Answer generation failed" / model_not_found |
Your key cannot use that model. List the ones it can: $k=(Select-String -Path .env -Pattern '^GROQ_API_KEY=(.*)$').Matches.Groups[1].Value; (Invoke-RestMethod https://api.groq.com/openai/v1/models -Headers @{Authorization="Bearer $k"}).data.id and set LLM_MODEL to a chat model from that list. |
| Scanned PDF returns 422 | Install Tesseract and put it on PATH (Docker image includes it). |
VECTOR_BACKEND=faiss but faiss-cpu is not installed |
pip install -r requirements.txt again (the app keeps working on Chroma meanwhile). |
| Port 8000 busy | Set PORT=8001 in .env. |
Known limitations
confidence/is_groundedare heuristics, not guarantees of correctness.- Rate limits and background jobs live in process memory: run one worker (Chroma's local client is also single-process). Use a real queue / server database to scale out.
- "Hybrid" search re-ranks the top vector candidates with BM25; it is not a full-corpus keyword search.
- OCR needs Tesseract. Tables and multi-column layouts are extracted as plain text.
- English-focused embedding model (
all-MiniLM-L6-v2). - The FAISS index is in-memory (rebuilt from Chroma at startup); it is exact, not approximate, so very large corpora (millions of chunks) need an IVF/HNSW index instead.
- Data written by older versions (one Chroma collection per document) is not migrated: delete
data/vectorstoreand re-upload your PDFs. - Default mode has no authentication - set
API_KEYbefore exposing the service.