Recruitment Data is Fuzzy. Hiring Constraints Aren't.
Power job search, candidate matching, and recommendation systems for scaled talent platforms.
result = client.query_points(
collection_name="jobs",
prefetch=[
models.Prefetch(
query=dense_emb, using="dense",
),
models.Prefetch(
query=sparse_vec, using="sparse",
),
],
query=models.FusionQuery(
fusion=models.Fusion.RRF),
query_filter=models.Filter(must=[
models.FieldCondition(
key="location",
match=models.MatchAny(any=["london", "remote"]),
),
models.FieldCondition(
key="salary_max",
range=models.Range(gte=60000),
),
]),
limit=20,
)
Step 1
Embed - Parse + Embed Resume / JD
Step 2
Search - Semantic Search + Strict Filter
Step 3
Rank - Rank + Rerank (Optional)
Step 4
Result - Evidence-based Match
“Qdrant is the last thing I worry about breaking.”
Elvis Moraa
Engineering Lead, Pariti
2.4x
increase in hiring fill rate
70%
reduction in candidate vetting time
Why Teams Choose Qdrant
Recruitment Search is Broken at Scale
Many HR tech teams don't start with vector search. They start with Elasticsearch, PGVector, or a managed API, and hit a wall when the product needs compound filters at scale, multi-language support, or sub-second latency on millions of vectors. These are the engineering problems that surface, and the pain we hear from teams switching.
Inconsistent Taxonomies
"Senior Software Engineer," "Staff Dev," and "Lead SWE" might be the same role. Keyword search misses these. Semantic search doesn't. Embeddings catch them.
Fuzzy Data with Constraints
Skills and titles are inconsistent across resumes, but location, work authorization, and certifications are hard constraints. You need semantic similarity AND hard filters on the same query.
Evaluating Migration?
Our solutions engineers do technical deep-dives with HR tech teams weekly.
What you can build with Qdrant
Modernized Job Search
Layer semantic relevance on top of existing keyword/Boolean patterns with hybrid search. Apply strict metadata filters alongside vector similarity, no post-filter penalty.
Similar Jobs & Candidates
Layer semantic relevance on top of existing keyword/Boolean patterns with hybrid search. Apply strict metadata filters alongside vector similarity, no post-filter penalty.
How It Works Under the Hood
Architecture patterns with API examples for the problems above.
Semantic Match with Hard Hiring Constraints
Embed the job description or resume, then apply must-match payload filters for non-negotiable constraints. Filters applied during HNSW traversal, not after, so recall doesn't degrade.
Pre-filter during graph traversal
Compound filters on location, salary, category don't cause latency spikes.
Hybrid Search
Combine dense + sparse vectors with Reciprocal Rank Fusion in a single query.
Native Inferencing
Use Qdrant Cloud inference to simplify your data pipeline.
# Pattern: hybrid semantic + keyword with filters
# → see docs for complete example
result = client.query_points(
collection_name="jobs",
prefetch=[
models.Prefetch(
query=dense_emb, using="dense",
limit=100),
models.Prefetch(
query=sparse_vec, using="sparse",
limit=100),
],
query=models.FusionQuery(
fusion=models.Fusion.RRF),
query_filter=models.Filter(must=[
models.FieldCondition(
key="location",
match=models.MatchAny(any=["london", "remote"]),
),
models.FieldCondition(
key="salary_max",
range=models.Range(gte=60000),
),
]),
limit=20,
)
Quantization for 10M+ Vectors
Scalar and binary quantization compress vectors 4-32x in memory. At 10M+ candidate or job vectors.
Scalar Quantization
4x memory reduction with minimal recall loss. One config flag.
Predictable Cost
fixed infra cost for vector search means retrieval across multiple pipeline stages without margin erosion.
# Pattern: quantized collection for scale
# → see docs for complete example
client.create_collection(
collection_name="candidates_quantized",
vectors_config={
"dense": models.VectorParams(
size=1536,
distance=models.Distance.COSINE,
),
},
sparse_vectors_config={
"sparse": models.SparseVectorParams(
modifier=models.Modifier.IDF,
),
},
quantization_config=models.ScalarQuantization(
scalar=models.ScalarQuantizationConfig(
type=models.ScalarType.INT8,
quantile=0.99,
always_ram=True,
)
),
)
"Similar Jobs" and Candidate Discovery
Recommendation-style retrieval using existing points as query inputs. Pass positive and negative examples, no embedding step needed on the query side.
Recommend by Point ID
No re-embedding required. Just pass the job or candidate ID.
Positive + Negative Examples
"More like these 3, less like that one": refine without retraining.
# Pattern: recommend by point ID
# → see docs for complete example
result = client.query_points(
collection_name="jobs",
query=models.RecommendQuery(
recommend=models.RecommendInput(
positive=[0], # job_id_0
negative=[1], # job_id_1
)
),
using="dense",
query_filter=models.Filter(
must=[
models.FieldCondition(
key="category",
match=models.MatchValue(value="engineering"),
),
models.FieldCondition(
key="remote",
match=models.MatchValue(value=True),
),
]
),
limit=20,
)
