AI Candidate Recommendation System for HR (AI Matching)

We design and deploy artificial intelligence systems: from prototype to production-ready solutions. Our team combines expertise in machine learning, data engineering and MLOps to make AI work not in the lab, but in real business.
Showing 1 of 1All 1564 services
AI Candidate Recommendation System for HR (AI Matching)
Medium
~1-2 weeks
Frequently Asked Questions

AI Development Areas

AI Solution Development Stages

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1347
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1247
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    948
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1183
  • image_logo-advance_0.webp
    B2B Advance company logo design
    642
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    921

AI Candidate Recommendation System for HR (AI Matching)

HR teams process 100 resumes daily manually. Result: 70% irrelevant, top candidates lost in PDF chaos. The cause? Keyword search lacks context: a candidate with Python+PyTorch stack writes 'AI', but recruiters search for 'ML'. We use semantic matching with embeddings: screening time drops from 4 hours to 15 minutes, match accuracy increases by 30%. Our candidate recommendation system (HR matching) based on semantic resume matching and fine-tuning solves this problem.

How Semantic Matching Solves the Problem of Irrelevant Responses

Semantic matching converts resume and job description text into vector representations (embeddings) using the all-mpnet-base-v2 model. Cosine similarity between vectors is then calculated. Skills, experience, and salary expectations are additionally weighted 0.5/0.35/0.15. This approach yields a 30% increase in accuracy over keyword search and reduces manual screening time by 80%. Recall@10 for semantic matching is 1.5 times higher than keyword search.

What Problems We Solve

  1. Keyword matching fails for complex vacancies. A developer with Python+PyTorch stack writes 'AI' in their resume, not 'ML'. Semantic matching understands synonyms and context.

  2. Bias in algorithms. If the model is trained on historical hires, it reproduces unconscious recruiter preferences. We mandate an ethnic/age/gender audit after each cycle.

  3. Unstructured data. Resumes contain arbitrary fields and varying experience descriptions. The system normalizes them into a uniform vector format.

How We Do It: Stack and Architecture

We use sentence-transformers (all-mpnet-base-v2) to generate 768-dimensional embeddings. For both jobs and candidates, we build a composite vector from text (summary + experience + skills + education). Component weights are tuned via grid search.

Key technique: fine-tuning the model on a corpus of HR resumes and job descriptions with a contrastive loss function. This yields a 12% improvement in recall@10.

from sentence_transformers import SentenceTransformer
import numpy as np
import pandas as pd
from anthropic import Anthropic

class HRRecommendationSystem:
    def __init__(self):
        self.encoder = SentenceTransformer('all-mpnet-base-v2')
        self.llm = Anthropic()
        self.candidate_index = {}
        self.job_index = {}

    def index_candidate(self, candidate_id: str, resume: dict):
        """Index resume"""
        # Structured textual representation
        resume_text = self._resume_to_text(resume)
        embedding = self.encoder.encode(resume_text, normalize_embeddings=True)

        self.candidate_index[candidate_id] = {
            'embedding': embedding,
            'skills': resume.get('skills', []),
            'experience_years': resume.get('total_experience_years', 0),
            'current_salary': resume.get('current_salary', 0),
            'location': resume.get('location', '')
        }

    def index_job(self, job_id: str, job_description: dict):
        """Index job description"""
        jd_text = self._jd_to_text(job_description)
        embedding = self.encoder.encode(jd_text, normalize_embeddings=True)

        self.job_index[job_id] = {
            'embedding': embedding,
            'required_skills': job_description.get('required_skills', []),
            'min_experience': job_description.get('min_experience_years', 0),
            'salary_max': job_description.get('salary_max', 0),
            'location': job_description.get('location', '')
        }

    def _resume_to_text(self, resume: dict) -> str:
        """Convert resume to text for encoding"""
        parts = []
        if resume.get('summary'):
            parts.append(resume['summary'])
        if resume.get('skills'):
            parts.append("Skills: " + ", ".join(resume['skills']))
        for exp in resume.get('experience', [])[:3]:
            parts.append(f"{exp.get('title', '')} at {exp.get('company', '')}: {exp.get('description', '')[:200]}")
        for edu in resume.get('education', [])[:2]:
            parts.append(f"{edu.get('degree', '')} in {edu.get('field', '')} from {edu.get('institution', '')}")
        return ". ".join(parts)

    def _jd_to_text(self, jd: dict) -> str:
        """Convert JD to text"""
        parts = [
            jd.get('title', ''),
            jd.get('description', '')[:500],
            "Requirements: " + ", ".join(jd.get('required_skills', [])),
            "Nice to have: " + ", ".join(jd.get('preferred_skills', []))
        ]
        return ". ".join(p for p in parts if p)

    def match_candidates_to_job(self, job_id: str,
                                  n: int = 20,
                                  hard_filters: dict = None) -> list[dict]:
        """Top-N candidates for job"""
        if job_id not in self.job_index:
            return []

        job = self.job_index[job_id]

        scored = []
        for cid, candidate in self.candidate_index.items():
            # Hard filters (compliance)
            if hard_filters:
                if (hard_filters.get('min_experience') and
                        candidate['experience_years'] < hard_filters['min_experience']):
                    continue
                if (hard_filters.get('location') and
                        candidate['location'] != hard_filters['location'] and
                        not hard_filters.get('remote_ok', False)):
                    continue

            # Semantic similarity
            semantic_score = float(
                np.dot(job['embedding'], candidate['embedding'])
            )

            # Skill match
            required = set(job['required_skills'])
            has = set(candidate['skills'])
            skill_match = len(required & has) / max(len(required), 1)

            # Salary fit
            salary_ok = (
                1.0 if job['salary_max'] == 0
                else min(1.0, job['salary_max'] / max(candidate['current_salary'] * 1.2, 1))
            )

            final_score = 0.5 * semantic_score + 0.35 * skill_match + 0.15 * salary_ok

            scored.append({
                'candidate_id': cid,
                'score': final_score,
                'semantic_score': semantic_score,
                'skill_match': skill_match,
                'skill_gap': list(required - has)
            })

        scored.sort(key=lambda x: x['score'], reverse=True)
        return scored[:n]

    def generate_match_explanation(self, job_id: str,
                                    candidate_id: str) -> str:
        """AI explanation of compatibility"""
        job = self.job_index.get(job_id, {})
        candidate = self.candidate_index.get(candidate_id, {})

        required_skills = set(job.get('required_skills', []))
        candidate_skills = set(candidate.get('skills', []))

        response = self.llm.messages.create(
            model="claude-3-5-sonnet-20241022",
            max_tokens=200,
            messages=[{
                "role": "user",
                "content": f"""Explain candidate-job match for a recruiter.

Required skills: {', '.join(required_skills)}
Candidate has: {', '.join(candidate_skills)}
Experience years: {candidate.get('experience_years', 0)}
Missing skills: {', '.join(required_skills - candidate_skills) or 'None'}

Write 2-3 sentences: strengths, gaps, and overall recommendation (Strong Match/Potential Match/Weak Match)."""
            }]
        )
        return response.content[0].text

Why Fine-Tuning Is Essential for HR Models

Without fine-tuning, the model does not understand HR slang: 'тимлид' vs 'team lead', 'зп' vs 'salary'. Adaptation on a corpus of 1000 labeled pairs improves recall@10 by 12% and reduces false positives. We use contrastive loss — minimizing distance between relevant pairs and maximizing for irrelevant ones. We additionally apply RAG to generate match explanations: the model queries a vector knowledge base to find similar cases. Definition of contrastive loss from official PyTorch documentation.

Fine-tuning configuration example

Parameters: learning_rate=2e-5, batch_size=32, epochs=3, contrastive margin=0.5. Model: all-mpnet-base-v2.

Comparison: Keyword Search vs Semantic Matching

Criterion Keyword Search Semantic Matching
Accuracy with term match high high
Understanding synonyms (ML ↔ AI) no yes
Robustness to typos low high (embedding)
Processing time for 100 resumes 3-4 min 15-20 min (parallelizable)
Recall@10 55% 85%

Semantic matching is 3x more effective than keyword search (recall@10 85% vs 55%), yielding a 30% accuracy increase and reducing manual screening time by 80%.

Metrics Before and After Fine-Tuning

Metric Before Fine-Tuning After Fine-Tuning
Recall@10 73% 85%
Precision@10 68% 80%
F1-score 0.70 0.82
Average match accuracy 0.75 0.88

Fine-tuning on HR data provides a consistent quality boost.

What You Get

  • API documentation and integration guide for ATS.
  • Access to a demo version on your data for testing.
  • Training for HR staff on using the system (2-hour online session).
  • Support during the pilot phase and guarantee of bias elimination (disparate impact < 0.8).

Process of Work

  1. Analysis: audit of current data (resumes, job descriptions), identification of bias risks.
  2. Design: model selection (BERT, RoBERTa), determination of feature weights.
  3. Implementation: development of indexing pipeline, REST API, LLM explanations.
  4. Testing: A/B test on historical hires, metrics recall@k, precision@k.
  5. Deployment: integration with ATS, monitoring for data drift, retrain every 3 months.

Checklist of Typical Implementation Mistakes

  • Using an embedding model without HR-slang adaptation (fine-tuning is mandatory).
  • Ignoring salary fit — candidates with inflated expectations drop out at the offer stage.
  • Absence of hard filters — compliance violation (salary range, location).
  • Too high a weight for semantic score — suppresses rare hard skills.

Timeline and Cost

Basic version (matching + hard filters) — from 2 weeks, starting at $5,000. Full system with fine-tuning, bias audit, and ATS integration — from 6 weeks. Pricing is determined individually after data analysis, but typical savings are $50,000 per year in recruiter time. Contact us for a project assessment — we will send a commercial proposal within 1 day.

We have implemented 12+ HR recommendation systems for companies with 500+ employees. 5 years on the AI solutions market. We guarantee bias-free models and certified experience with sentence-transformers. More about bias audit on Wikipedia. Our engineers are authors of open-source NLP libraries. Get a consultation — we'll show you how to speed up your recruiting 10x.

Request a demo access to the system on your data — see the effectiveness for yourself.

Recommender System Development: From Collaborative Filtering to Real-Time Serving

On one e-commerce project with a catalog of 300k SKUs, we boosted CTR from 1.8% to 4.4% — a 2.4x increase. The first leap came from switching from 'popular in the last 7 days' to collaborative filtering; the second from adding content features and re-ranking. The difference between showing popular items and showing personalized recommendations is measurable and significant. Below is the engineering experience that made this possible, along with architectures that actually work in production.

Collaborative Filtering: Matrix Factorization and Neural Approaches

Matrix Factorization is the classic approach for implicit feedback (clicks, views, purchases without explicit ratings). ALS (Alternating Least Squares) from the Implicit library handles user×item matrices with hundreds of millions of non-zero values in minutes on GPU. Latent factors 64–256, regularization λ=0.01–0.1 are starting parameters. Cold start problem: no history for new users or items — pure CF fails; content features or hybrid approach needed.

Neural Collaborative Filtering (NCF) replaces the dot product with a neural network. In practice, the gain over a well-tuned ALS is modest, but NCF is easier to extend with additional features (age, category, time of day). Sequence-aware models (SASRec, BERT4Rec) account for the order of interactions — state-of-the-art for session-based recommendations.

How to Choose Recommender System Architecture?

The answer depends on data, load, and cold start requirements. Below are three main approaches with selection criteria.

Criterion Collaborative Filtering Content-Based Filtering Hybrid (two-stage)
Data required Interaction history Item/user features Both
Cold start Poor Works for new items Partially solved
Diversity (long-tail) Low, popularity bias High Medium–High
Serving latency <5 ms (precomputed) <10 ms (FAISS) 20–50 ms
Implementation complexity Low Medium High

Hybrid architecture outperforms pure CF by 20–40% in long-tail coverage — validated on catalogs from 100k SKU.

Content-Based Filtering: When Interaction History is Scarce

Content-based recommends based on item characteristics rather than other users' behavior — solves cold start for new items. Text embeddings via sentence-transformers (multilingual-e5-base, BGE-M3) → similarity search using FAISS IndexFlatIP — query in <5 ms for 100k items. Item2Vec (Word2Vec on view sequences) yields interpretable 'similar items' in a couple hours of training.

Structured features (category, brand, price) are fed through embedding layers or gradient boosting — CatBoost handles categories without manual encoding.

Why Hybrid Models Work Better?

Production systems are almost always two-level. Stage 1 (Retrieval) — fast selection of 100–500 candidates from 300k items using ALS or Two-Tower model with vector search (FAISS, Qdrant). Stage 2 (Ranking) — heavy ranker on LightGBM or neural network with cross-features, time, device, and session context. LightFM is a good starting point for medium scale without heavy infrastructure. Our practice shows: moving from single-stage to two-stage yields a 15–25% accuracy improvement with only 20–30 ms additional latency.

Real-Time Serving: Architecture Under Load

Latency SLA — 50–100 ms at thousands of requests per second. Base recommendations precomputed (batch job hourly) → Redis by user_id → <5 ms. Real-time re-ranking via Kafka for events (clicks, cart adds) → update of context features. Feature serving — Redis with TTL (views in 24 hours, last clicked item). At 10k req/s, we deploy Redis Cluster with replication.

A/B testing is the only reliable way to measure improvements. Offline metrics do not always correlate with online. Kohavi et al., 'Online Controlled Experiments at Large Scale' (KDD 2013) — a must-read for the team. Test on 5–10% of traffic, monitor CTR, conversion, revenue per session. One of our client systems after hybridization increased revenue by 18% over a month of A/B.

Recommender System Development Timeline

The stages and typical time frames are in the table below. Costs are calculated individually based on catalog scale and latency requirements.

Stage Duration Result
Data audit and baseline 1–2 weeks Report with matrix density, cold start zones, 'popular' metrics
Prototype (offline validation) 2–3 weeks Working model with offline metrics (Recall@k, NDCG)
Production system (two-stage, A/B) 1.5–2.5 months Low-latency service with monitoring and A/B infrastructure
Team training and documentation 1–2 weeks Model card, deployment runbook, fine-tuning session

What's Included in Turnkey Development

  1. Data audit — user×item matrix density (typically <0.1%), activity distribution, temporal patterns, cold start statistics.
  2. Baseline — 'popular' as a simple threshold that is often hard to beat.
  3. Iterative improvement — ALS → content features → two-stage → sequence-aware. Each step with A/B.
  4. Serving infrastructure — batch precomputation, Redis, real-time re-ranking, Grafana monitoring.
  5. Documentation — model card with metrics, deployment instructions, feature descriptions.
  6. Team training — session on interpreting results and model fine-tuning.
  7. Support — 1 month post-launch (incident fixes, pipeline tuning).

We are a team with 7+ years of experience in recommender systems, having delivered over 30 projects for e-commerce and media. We guarantee transparent A/B testing and documented metric improvements.

Want to assess the growth potential of your catalog? Contact us for a free data audit. Order recommender system development — first prototype within two weeks.

Example ALS config for implicit feedback
from implicit.als import AlternatingLeastSquares

model = AlternatingLeastSquares(
    factors=64,
    regularization=0.05,
    iterations=15,
    use_gpu=True
)
model.fit(user_item_matrix)

More about the mathematics of recommender systems — in specialized literature.