Shortlisting

Three-Stage Candidate Shortlisting

Retrieve-then-rerank paradigm: cheap vector search builds a 2K pool, expensive cross-encoding narrows to K, and BM25 lexically reorders. Cheapest operation runs first.

Pipeline Stages

StageModelPoolWeight
Stage 1: HNSW Vector Retrievalall-mpnet-base-v2 ONNX INT8All → 2K0.20
Stage 2: Cross-Encoder Re-rankingms-marco-MiniLM-L12-v2 ONNX INT82K → K0.65
Stage 3: BM25 Lexical ScoringBM25 (raw_full_text)K → K0.15

Stage Details

Stage 1: HNSW Vector Retrieval

Four Qdrant gRPC searches with has_id() filter. COSINE distance, m=16, ef_construct=200, ef=128. Recall@10 = 0.93.

Stage 2: Cross-Encoder Re-ranking

Batched [4 × max_len] input tensor. One session.run() processes all four fields. ~3x faster vs sequential. 134MB → 34MB INT8.

Stage 3: BM25 Lexical Scoring

k₁=1.2, b=0.75. Recovers exact matches for version numbers, cert codes, tool names that embeddings smooth over.

Score Blend

score(u) = 0.65 · c_n(u) + 0.20 · v_n(u) + 0.15 · b_n(u)

All scores min-max normalised within top-K pool. Neutral 0.5 when all candidates are tied.

Retrieval Quality

MetricWhole-DocSection-WiseGain
Precision@50.720.81+12.5%
Precision@100.680.76+11.4%
Recall@100.740.83+12.2%
NDCG@100.710.79+11.3%
MRR0.760.84+10.5%

Internal benchmark on 500 candidates, 50 job descriptions.