A production-grade AI system that helps HR teams parse, score and rank candidates against a job description β explainably, transparently, and with bias mitigation built in.
Modern recruiting teams receive hundreds to thousands of resumes per role. Manual screening is slow, inconsistent, and prone to unconscious bias.
AI HR Agent is a full-stack platform that automates the front of the recruiting funnel:
- Parses Job Descriptions and PDF / DOCX resumes into structured JSON.
- Masks bias-prone signals (name, gender, age, address, photo) before the AI ever sees them.
- Scores candidates against an explainable 5-dimension rubric with deterministic weighting.
- Generates a recruiter-friendly summary, a confidence score and a recommendation.
- Lets HR override any decision β with full audit trail.
- Exports every evaluation as JSON, HTML and PDF.
The goal is not just to "rank candidates" β it is to give HR a decision-support tool they can actually trust, with the math visible at every step.
| Pain point | Impact | What this project does |
|---|---|---|
| Recruiters spend ~23 hrs per hire on screening alone (SHRM) | Slow time-to-hire | Cuts initial screen to ~5 seconds per resume |
Keyword-matching ATS misses semantically similar skills (Postgres β PostgreSQL) |
Strong candidates rejected | Embedding-based semantic match |
| Bias from names / photos / addresses leaks into LLM scoring | Legal + ethical risk | PII masked before any LLM call |
| Black-box AI scoring | HR can't justify decisions | Every number is traceable to a concrete signal |
| No human override path | AI replaces HR (bad) instead of augmenting them | Built-in HITL with full audit trail |
- β JD Parser (regex + GPT-4o-mini structured extraction)
- β Resume Parser for PDF, DOCX, TXT
- β
Semantic Matching Engine β
sentence-transformers/all-MiniLM-L6-v2+ cosine similarity - β Explainable 5-dimension Rubric β Skills 30%, Experience 25%, Education 15%, Projects 20%, Communication 10%
- β Recruiter Summary β concise, evidence-based, identity-blind
- β Confidence Score β measures signal strength independent of total
- β Recommendation Bands β Strong Hire / Shortlist / Consider / Reject
- β Bias Mitigation Layer masking name, gender, age, address, photo, marital, nationality
- β Human-in-the-Loop Override β with reason logging and DB audit trail
- β Multi-format Reports β JSON / HTML / PDF (ReportLab β no native deps)
- β Premium Dashboard β dark theme, Plotly radar + bar + gauge charts
- β
REST API β full FastAPI surface, OpenAPI docs at
/docs - β Offline-capable β works without an OpenAI key (heuristic mode)
- π LinkedIn ingestion adapter
- π ChromaDB / FAISS retrieval for candidate-pool search
- π RAG-powered recruiter chatbot
- π Multi-agent debate for borderline candidates
Full diagrams live in
architecture/architecture.md. Highlights:
flowchart LR
HR((HR Reviewer)) --> UI[Streamlit Dashboard]
UI --> API[FastAPI]
API --> AGENT[HR Screening Agent]
AGENT --> P1[JD Parser]
AGENT --> P2[Resume Parser]
AGENT --> BM[Bias Masking]
BM --> SM[Semantic Matcher]
SM --> RB[Explainable Rubric]
RB --> EX[LLM Recruiter Summary]
AGENT --> DB[(SQLite)]
AGENT --> RPT[(JSON / HTML / PDF)]
flowchart TD
A[Upload PDF/DOCX] --> B[Extract Text]
B --> C[Parse β structured JSON]
C --> D[π‘ Bias Masking]
D --> E[Semantic Matcher]
E --> F[Rubric Scorer<br/>deterministic]
F --> G[LLM Recruiter Summary<br/>grounded in rubric]
G --> H[Persist + Reports]
ai-hr-agent/
βββ app.py β single launcher (Streamlit / API / demo)
βββ agent.py β orchestration faΓ§ade
βββ config.py β env-driven settings
β
βββ parser/ β JD + Resume parsers (regex + LLM)
βββ utils/ β embeddings, cleaner, bias_masking, llm_client
βββ scoring/ β matcher, rubric, explainability
βββ reports/ β JSON / HTML / PDF generation + Jinja template
βββ database/ β SQLite layer
βββ api/ β FastAPI service
βββ frontend/ β Streamlit dashboard + Plotly + dark theme
β
βββ data/ β sample JD + sample resumes for the `demo` command
βββ architecture/ β Mermaid diagrams
βββ requirements.txt
| Layer | Tool | Reason |
|---|---|---|
| Frontend | Streamlit + custom CSS + Plotly | Premium dashboard with no JS build step |
| Backend | FastAPI + Uvicorn | Auto-OpenAPI, async, typed |
| LLM | OpenAI GPT-4o-mini | Cost-efficient structured extraction + summaries |
| Embeddings | sentence-transformers/all-MiniLM-L6-v2 |
Strong + small, runs on CPU |
| Resume parsing | pdfplumber, python-docx | Robust, no native deps |
| Reports | Jinja2 + ReportLab | HTML + PDF without WeasyPrint |
| Database | SQLite | Zero-config; easy upgrade path to Postgres |
| Optional | ChromaDB, FAISS | For candidate-pool similarity search |
The rubric is fully deterministic. The LLM is never used to compute the score β it is only used to:
- Extract structure from JDs / resumes (validated against a strict JSON schema), and
- Write the recruiter summary grounded in the already-computed rubric (the prompt forbids inventing skills).
This means every digit in every report is traceable:
total = Ξ£ ( dimension.score / 10 ) Β· dimension.weight
For example:
{
"skills_match": {
"score": 8.0,
"weight": 30,
"weighted": 24.0,
"reason": "5/6 required skills matched, 2 preferred bonus.",
"matched": ["Python", "FastAPI", "PostgreSQL", "Docker", "AWS"],
"missing": ["Kubernetes"]
}
}A reviewer can see why a candidate scored 24 / 30 on Skills β and override it if they disagree.
Personal-identity signals are masked before any text reaches the LLM.
| Category | Examples masked |
|---|---|
| Name | "John Doe", "Name: John Doe", inline mentions |
| Gender | he / she / her / Mr. / Mrs. / man / woman / β¦ |
| Age & DOB | "Age: 27", "DOB: 12/06/1996", "27 years old" |
| Address | "Address: β¦", ZIP / pincode |
| Photo refs | "photograph", "headshot", "profile picture" |
| Marital / Religion / Nationality | structured statements |
| Contact | email + phone |
The masker emits an audit report (categories + counts) that the dashboard surfaces under "π‘ Bias masking audit trail" β so HR can verify what was redacted.
report = mask_pii(resume_text, candidate_name="Aisha Rao")
# β MaskingReport(masked_text=..., items_removed={'name_mentions': 3, 'gender_terms': 6, ...})We compute several signals, all in [0, 1]:
| Signal | How |
|---|---|
jd_similarity |
Cosine sim between JD's "search corpus" and resume's |
skills_overlap |
Average best-match cosine sim of required skills against candidate's skills+tools |
tools_overlap |
Same, for tools / tech stack |
experience_relevance |
Mean cosine sim per role description vs. JD, then tenure-boosted by min_experience_years |
project_relevance |
Mean cosine sim per project description vs. JD |
education_match |
Cosine sim between JD's education list and candidate's degrees |
certification_match |
Set-overlap between required certifications and candidate's |
Embedding model: all-MiniLM-L6-v2 (384-d, ~80 MB, CPU-friendly).
Embeddings are cached by content-hash so repeated JD calls are free.
- Python 3.10+
- ~500 MB disk (for the embedding model on first run)
git clone <this-repo>
cd ai-hr-agent
# create env
python -m venv .venv
source .venv/bin/activate # (Windows: .venv\Scripts\activate)
# install
pip install -r requirements.txt
# copy env template
cp .env.example .env
# edit .env and set OPENAI_API_KEY (optional β system runs without it)π‘ No OpenAI key? No problem β the system falls back to heuristic JD/resume parsing and a template-based recruiter summary. The deterministic scoring rubric works either way.
streamlit run app.pyOpen http://localhost:8501. Workflow:
- Paste / load a JD on the Upload page.
- Drop one or many PDF / DOCX resumes.
- Click π Analyse candidates β animated progress.
- Review the Leaderboard, drill into Candidate detail, override scores, and download JSON / HTML / PDF reports.
python app.py api
# or: uvicorn api.main:app --reload --port 8000OpenAPI docs at http://localhost:8000/docs.
# 1. register a JD
curl -X POST http://localhost:8000/jobs \
-H "Content-Type: application/json" \
-d '{"text": "We need a Python ML engineer with FastAPI and AWS..."}'
# β {"job_id": 1, "structured": {...}}
# 2. evaluate a resume
curl -X POST http://localhost:8000/jobs/1/evaluate \
-F "files=@resume.pdf"
# 3. get the leaderboard
curl http://localhost:8000/jobs/1/ranking
# 4. download a PDF report
curl -o report.pdf http://localhost:8000/evaluations/1/report.pdf
# 5. HR override
curl -X POST http://localhost:8000/evaluations/1/override \
-H "Content-Type: application/json" \
-d '{"new_score": 78, "new_recommendation": "Shortlist", "reason": "Promoted based on portfolio quality"}'python app.py demoPrints a leaderboard for the bundled JD + 4 sample resumes β useful for smoke-testing without uploading anything.
β Registered job #1 β AI/ML Engineer (Backend-Focused)
β’ Aisha Rao 87.3/100 (Strong Hire, conf 89%)
β’ Marcus Chen 72.4/100 (Shortlist , conf 78%)
β’ Priya Iyer 68.1/100 (Shortlist , conf 71%)
β’ Derek Olsen 36.5/100 (Reject , conf 44%)
Leaderboard:
#1 Aisha Rao 87.3 Strong Hire
#2 Marcus Chen 72.4 Shortlist
#3 Priya Iyer 68.1 Shortlist
#4 Derek Olsen 36.5 Reject
A full evaluation JSON looks like:
{
"candidate": { "name": "Aisha Rao", "experience_years": 3.0, ... },
"job": { "title": "AI/ML Engineer (Backend-Focused)", ... },
"evaluation": {
"total_score": 87.3,
"recommendation": "Strong Hire",
"confidence_score": 89.0,
"dimensions": {
"skills_match": { "score": 9.0, "weight": 30, "weighted": 27.0, "reason": "...", "matched": [...], "missing": [...] },
"experience_relevance": { "score": 8.6, "weight": 25, "weighted": 21.5, ... },
"education_certifications": { "score": 9.0, "weight": 15, "weighted": 13.5, ... },
"projects_portfolio": { "score": 8.4, "weight": 20, "weighted": 16.8, ... },
"communication_quality": { "score": 8.5, "weight": 10, "weighted": 8.5, ... }
},
"strengths": ["Skills Match (9.0/10)", "Education & Certifications (9.0/10)"],
"gaps": []
},
"recruiter_summary": "Candidate shows strong-hire-level alignment with AI/ML Engineer (...).",
"bias_masking": { "enabled": true, "summary": "Masked β name_mentions: 3, gender_terms: 4, ..." }
}Sample reports (JSON / HTML / PDF) are auto-written to
outputs/on first run.
streamlit run app.py- Click Paste sample JD on the Upload tab
- Upload the 4 sample resumes from
data/sample_resumes/ - Click π Analyse candidates
- Open Leaderboard β see ranking + score distribution
- Open Candidate detail β radar + bar + gauge charts, recruiter summary, dimension cards
- Expand HR override & downloads β adjust score, save, then download PDF
- Open the PDF β it includes the same rubric, narrative, strengths/gaps and a coloured recommendation badge
- π LinkedIn ingestion β pull additional context from public profiles
- π RAG over candidate pool β let HR ask "who in our pipeline has FAISS + Postgres?"
- π€ Multi-agent debate β 2 LLM agents argue the borderline candidates and a referee picks
- π Recruiter analytics β funnel + diversity metrics over time
- π i18n β multilingual JD + resume support
- π SSO + role-based access β recruiter / hiring-manager / admin
If this project was useful or you have ideas, open an issue β happy to chat.