Recruiting Agent — AI-Powered ATS

Multi-step LLM pipeline for resume verification, role-fit scoring, and HR follow-up

AI Agent Project
  • FastAPI + React SPA on a single port
  • ChromaDB semantic JD matching
  • Observable 9-step agent pipeline
  • Optional Groq acceleration (local embeddings)

Recruiting Agent is an AI-powered Applicant Tracking System built for HR teams. It ingests job descriptions and resumes (including scanned PDFs), runs a multi-stage LLM pipeline for structured verification and role-fit scoring, flags authenticity concerns, and surfaces categorized HR follow-up questions — with a live Agent Activity timeline during processing. Part of the Agents monorepo — designed as the first of multiple AI agent projects.

What the system solves

Manual resume screening is slow, inconsistent, and weak on fraud detection for technical roles. Recruiting Agent provides an end-to-end ATS with semantic JD matching, structured verification, evidence-calibrated scoring, and an HR feedback loop — not a chatbot, but an observable multi-step agent pipeline.

9-step pipeline OCR for scanned PDFs Top-K JD matching HR reassessment loop
FastAPI React 19 TypeScript SQLite ChromaDB Ollama Groq TanStack Query Recharts

Core features

FeatureDescription
JD ManagerCreate/update roles; paste raw JD text → AI extracts skills, seniority, must-haves
Vector searchJDs chunked and embedded with Ollama → ChromaDB semantic matching
Resume uploadPDF, DOCX, TXT, JPG/PNG with OCR for scanned documents
VerificationStructured extract (education, experience, projects) + rule-based anomaly detection
ScoringTechnical / HR / overall fit with evidence-based strengths and gaps
Suspicion + HR QsFraud flags and categorized verification questions with resume excerpts
Agent ActivityLive pipeline timeline during analysis with similarity bars and skill chips
Dashboard chartsScore distribution, role averages, top candidates, verification gaps

Monorepo context

Agents/ ├── README.md ├── recruiting-agent/ # ATS / resume scoring agent (this project) │ ├── app/ # FastAPI backend + agent services │ ├── frontend/ # React SPA (Vite + TanStack Router) │ ├── deploy/ # systemd install scripts │ └── scripts/ # e2e, benchmark, reimport └── (future agents)/

System architecture

Recruiting Agent full stack architecture

React SPA → FastAPI → agent services → SQLite/ChromaDB/uploads → Ollama (local) + Groq (optional)

Component summary

LayerRole
React SPADashboard, JD manager, upload, results, HR questions, settings — TanStack Router + Query
FastAPIJWT auth, async job endpoints, SPA static serving on port 8512
job_serviceBackground threads, step timeline, job recovery, cancellation
scoring_agentOrchestrates parse → verify → embed → search → score → suspicion → save
vector_storeChromaDB HNSW index — JD chunks + skills, cosine similarity
llm_clientRoutes chat to Groq when key set, else Ollama; embeddings always local
SQLiteCandidates, JDs, analysis results, processing jobs, HR questions

Data model

  • job_descriptions + jd_versions — role definitions with versioned content
  • candidates — raw text, structured/verification JSON, parse method
  • analysis_results — per candidate × JD scores, suspicion, similarity
  • hr_questions — categorized questions with resume excerpts and HR answers
  • processing_jobs — step timeline + viz_json for live Agent Activity UI
  • user_settings — per-user Groq API key (optional)

LLM routing

Middleware resolves Groq API key per request (header, saved settings, or local file). Chat inference uses Groq when a key is available, otherwise Ollama. Embeddings always run on local Ollama (nomic-embed-text) — even when Groq accelerates scoring.

Resume analysis pipeline

Resume analysis 9-step pipeline

Upload → parse → normalize → verify → embed → search → score → suspicion → save → HR questions

Pipeline steps

StepLabelWhat happens
receiveReceive fileStore upload, create processing job record
parseParse resumeExtract text from PDF/DOCX/image; OCR for low-text scans
normalizeStructure textLLM normalization when unstructured
verifyVerify education & experienceStructured LLM extract + rule-based anomaly checks
embedEmbed vectorsOllama embeddings for resume text
searchSearch matching rolesChromaDB top-K JD similarity (default K=3)
scoreLLM role scoringTechnical / HR / overall fit with evidence
suspicionSuspicion analysis0–100 authenticity score + HR questions
saveSave resultsPersist candidate, scores, questions to SQLite

Other async job types

Job typeStepsTrigger
jd_parsereceive → llm → donePOST /api/v1/jds/parse-async
jd_indexchunk → embed → index → donePOST /api/v1/jds/{id}/index-async
hr_reassesscollect → llm_reassess → update → donePOST /api/v1/candidates/{id}/reassess-suspicion

HR verification loop

HR verification loop flow

Suspicion flags → categorized HR questions → recruiter answers → LLM reassessment → updated fit summary

Live Agent Activity

Live agent activity timeline

Frontend polls GET /api/v1/jobs every 1.5s via TanStack Query — step timeline updates with similarity bars and skill chips from viz_snapshots

Dashboard

Recruiting Agent dashboard

Pipeline health, score distribution, and top candidates at a glance

Job descriptions

Job descriptions manager

Create roles, paste JD text, and index skills for vector search

JD parsing with LLM

LLM extracts structured fields — skills, seniority, must-haves — from raw JD text

Resume upload

Resume upload for analysis

Bulk upload with optional target role; supports PDF, DOCX, scans, and images

Analysis results

Analysis results per candidate

Per-candidate scores, role suitability comparison, verification gaps, and ranking

Verification gaps and suspicion report

Detailed verification gaps and suspicion evidence tied to resume excerpts

HR questions

HR verification questions

Categorized verification questions with resume excerpts for recruiter follow-up

Settings

Groq API key settings

Optional Groq API key for faster LLM scoring — embeddings stay on local Ollama

Key services

ServiceResponsibility
scoring_agent.pyEnd-to-end resume analysis orchestration and result persistence
resume_parser.pyMulti-format parsing, OCR for scans, LLM normalization
candidate_verification.pyStructured extract + timeline gap / missing section rules
suspicion_detector.pyLLM authenticity scoring with evidence-tied HR questions
suspicion_reassessment.pyRe-evaluate suspicion after HR answers
vector_store.pyChromaDB chunking, embedding, and top-K JD search
jd_parser.pyLLM extraction of skills and requirements from raw JD text
llm_client.pyGroq/Ollama routing; local embeddings via Ollama
viz_snapshots.pyStep visualization data for Agent Activity panel

API endpoints

EndpointMethodDescription
/api/v1/auth/loginPOSTJWT login (bcrypt password)
/api/v1/jdsGET/POSTList and create job descriptions
/api/v1/jds/parse-asyncPOSTAsync LLM JD field extraction
/api/v1/jds/{id}/index-asyncPOSTChunk, embed, and index JD in ChromaDB
/api/v1/candidates/upload-asyncPOSTUpload resume → returns job_id for polling
/api/v1/jobsGETPoll job status and step timeline
/api/v1/candidates/{id}/reassess-suspicionPOSTHR answer loop reassessment
/api/v1/dashboard/statsGETAnalytics aggregates for charts
/api/v1/settings/groq-keyGET/PUTPer-user Groq API key management

Concurrency & resilience

  • Resume jobs serialized via threading.Semaphore(1) — one resume at a time
  • Background daemon threads for all async jobs
  • Stale job recovery on server restart (recover_stale_jobs)
  • Job cancellation support
  • SQLite WAL mode + commit retry on lock
  • Email-based deduplication — re-upload replaces prior analyses

Scoring prompt design

Scoring prompts enforce full 0–100 range, role-specific weighting (e.g. GenAI/ML roles), and penalize keyword stuffing without project proof. Suspicion prompts require evidence-tied HR questions with calibrated 0–100 authenticity scores.

Quick start

git clone https://github.com/Harish-nika/Agents.git cd Agents/recruiting-agent # Prerequisites: Python 3.10+, Node 20+, Ollama ollama pull llama3.1:8b ollama pull nomic-embed-text cp .env.example .env # set APP_PASSWORD python3 -m venv venv && source venv/bin/activate pip install -r requirements.txt python -c "from app.db.models import init_db; init_db()" cd frontend && npm install && npm run build && cd .. export PYTHONPATH=$(pwd) uvicorn app.api.main:app --host 0.0.0.0 --port 8512 # Open http://localhost:8512 — login: hr / APP_PASSWORD

Production (systemd)

bash deploy/install.sh sudo systemctl enable --now recruiting-agent

Security

Privacy by default: Ollama handles all inference and embeddings locally. Groq is optional — when enabled, text is sent to Groq's API for faster scoring. Never commit .env, groq_key.txt, or resumes/. Change APP_PASSWORD before exposing on a network.

Testing

source venv/bin/activate && export PYTHONPATH=$(pwd) python scripts/e2e_test.py python scripts/reimport_resumes.py # bulk score resumes/ folder python scripts/benchmark_resumes.py # Groq vs Ollama latency
Back to Portfolio