HireFlow indexes PDF resumes using hybrid search (BM25 + Pinecone vectors), fuses results with Reciprocal Rank Fusion, applies post-search filters, and optionally re-ranks candidates with a Gemini LLM evaluator.
git clone https://github.com/dhakksinesh/hireflow.git
cd HireflowMac/Linux:
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtWindows:
python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txtCreate a .env file at the project root:
PINECONE_API_KEY=your_pinecone_key
PINECONE_INDEX_NAME=hireflow
PINECONE_DIMENSION=384
PINECONE_METRIC=cosine
GOOGLE_API_KEY=your_google_api_key
GOOGLE_MODEL=gemini-2.5-flash
MAX_TEXT_LENGTH=4000
Place your PDF resumes in data/resumes/.
Follow these steps in order. Each one builds on the previous.
Verify everything works before touching any external APIs:
pytest tests/ -vAll tests are self-contained (Pinecone and Gemini are mocked). You should see
tests passing for: test_filters, test_hybrid_indexer, test_re_ranker, test_ingestion.
python start_backend.pyThis starts the API at http://localhost:8000. Open http://localhost:8000/docs to see the Swagger UI.
Check system status:
curl http://localhost:8000/statusWindows users: Use the Swagger UI at
http://localhost:8000/docsto test endpoints instead of curl.
Response: {"resumes_ready": false, "vector_store_ready": true, "hybrid_ready": false, "pinecone_vector_count": 0}
curl -X POST http://localhost:8000/indexThis loads all PDFs from data/resumes/, embeds them, and upserts into Pinecone + builds BM25 index. Response:
{"indexed": 50, "message": "Successfully indexed 50 resumes"}Verify with /status again β pinecone_vector_count should now be > 0.
curl -X POST http://localhost:8000/search \
-H "Content-Type: application/json" \
-d '{"query": "Senior Accountant with QuickBooks", "top_k": 5}'Response includes three separate scores for each candidate:
{
"results": [
{
"candidate_id": "c_alice_smith",
"name": "Alice Smith",
"bm25_score": 0.8721,
"vector_score": 0.7534,
"combined_score": 0.0323,
"skills": ["QuickBooks", "Excel", "Accounting"],
"location": "New York, USA",
"experience": 5
}
],
"total": 5
}- bm25_score β normalized keyword match (0-1)
- vector_score β cosine similarity from Pinecone (0-1)
- combined_score β Reciprocal Rank Fusion across both lists
Open a second terminal:
streamlit run streamlit/app.pySmart startup: On launch, the app checks if Pinecone already has vectors. If yes, it only rebuilds the in-memory BM25 index (fast). If not, it does a full index. No redundant embedding work on restarts.
In the Streamlit app:
- Use the Add Resumes panel on the left
- Upload one or more PDF files
- Click Process & Index Resumes
Each resume is parsed with Gemini to extract name, skills, location, and experience, then indexed into both BM25 and Pinecone.
In the Search Candidates panel:
- Enter a Job Title (e.g. "Senior Accountant")
- Enter a Job Description (e.g. "Looking for experienced accountant with tax expertise")
- Enter Required Skills (one per line)
- Optionally set a Location Filter and Min. Experience
- Click Find Candidates
Post-search filters (core/filters.py) are applied after the hybrid search to narrow down by skills, location, and experience. Results show:
- BM25 Score, Vector Score, and Combined (RRF) as separate metrics
- Skills, experience, and location for each candidate
- Resume preview
For the top 5 search results, the ReRanker (core/re_ranker.py) calls Gemini to produce:
- Fit score (0-100)
- Strengths β what makes this candidate a good match
- Gaps β where they fall short
- Summary β one-line assessment
These appear inside each candidate's expandable card. If Gemini is unavailable, a rule-based fallback scores based on skill overlap.
In the Streamlit sidebar, click Memory & Evaluation:
- Search Memory β see total interactions, searches, and candidate views
- Recent Search History β last 5 queries
- RAG Quality Evaluation β enter a query and expected skills, then measure search quality using RAGAS metrics (answer relevancy, context precision, faithfulness, answer correctness)
If you add new resume PDFs to data/resumes/ and want to refresh the index:
- Sidebar > click Force Re-index Resumes
This triggers a full BM25 + Pinecone rebuild.
Hireflow/
βββ api/
β βββ main.py # FastAPI backend (POST /index, /search, GET /status)
βββ core/
β βββ hybrid_indexer.py # BM25 + Pinecone vector search with RRF fusion
β βββ vector_store.py # Pinecone client wrapper
β βββ ingestion.py # PDF loading -> LangChain Documents
β βββ parsing.py # Gemini-based resume field extraction
β βββ re_ranker.py # LLM candidate evaluation (fit score 0-100)
β βββ filters.py # Post-search filtering (skills/location/experience)
β βββ memory_rag.py # Search history and interaction tracking
β βββ search_router.py # Shallow vs deep search routing
β βββ evaluator.py # RAGAS quality metrics
βββ utils/
β βββ schemas.py # SearchQuery, Resume, CandidateEvaluation
β βββ config.py # .env configuration loader
β βββ embeddings.py # HuggingFace all-MiniLM-L6-v2
β βββ utils.py # Text processing, PDF loading, logging
β βββ multi_query.py # LLM query expansion
βββ streamlit/
β βββ app.py # Web interface
βββ tests/
β βββ test_filters.py # Filter function tests
β βββ test_hybrid_indexer.py # RRF fusion and indexing tests
β βββ test_re_ranker.py # Evaluation and section parsing tests
β βββ test_ingestion.py # PDF loading tests
βββ data/
β βββ resumes/ # Place PDF resumes here
βββ main.py # CLI interface
βββ start_backend.py # uvicorn launcher
βββ requirements.txt # All dependencies
βββ HireFlow_Architecture.md # Detailed architecture docs
| Component | Technology |
|---|---|
| LLM | Google Gemini (gemini-2.5-flash) |
| Vector DB | Pinecone (serverless, cosine, 384 dims) |
| Embeddings | HuggingFace all-MiniLM-L6-v2 |
| Keyword Search | rank_bm25 (BM25Okapi) |
| Score Fusion | Reciprocal Rank Fusion (k=60) |
| Orchestration | LangChain |
| Backend | FastAPI + uvicorn |
| Frontend | Streamlit |
| RAG Evaluation | RAGAS |
| Testing | pytest |