hari2809-art/redrob-candidate-ranking

GitHub: hari2809-art/redrob-candidate-ranking

一个采用两阶段检索重排架构的AI候选人排名系统,能在资源受限条件下对十万份简历进行语义匹配与陷阱检测,精准输出最优候选人列表。

Stars: 0 | Forks: 0

# Redrob 候选人排名系统 ### India Runs 黑客松 — 赛道 1(数据与 AI) ### 智能候选人发现与排名 — Redrob AI 资深 AI 工程师 **NovaMind AI 团队** | 角色 | 姓名 | |---|---| | 队长 | Gurram Harinath | | 成员 | Ponna Chaitanya | | 成员 | Boggula Hrudayananda Reddy | ## 概述 该系统对 100,000 份候选人档案进行排名,以找出最适合 Redrob AI(一家位于浦那/诺伊达的 A 轮 AI 原生招聘平台)资深 AI 工程师职位的前 100 名候选人。 职位描述 (JD) 刻意没有使用固定的清单来编写。它明确指出,“正确答案”**不是**“寻找技能部分包含最多 AI 关键词的候选人”——这是数据集中内置的一个陷阱。相反,这份简报要求建立一个能够理解职位描述字面意思与其实际含义之间差距的系统。一个在产品公司上线过推荐系统的候选人是真正合适的人选,即使他们的技能列表中从未出现过“RAG”或“Pinecone”。而一个头衔是“营销经理”的候选人则不合适,无论他们的档案中出现多少 AI 关键词。 该系统正是围绕这一原则构建的:使用多个基于规则的信号来理解职业轨迹和岗位匹配度,结合能够捕捉超越精确关键词含义的语义(基于 embedding)层,并针对职位描述中提到的每种陷阱模式配备了明确的检测器。 ## 架构:两阶段 Pipeline 考虑到这些限制条件——100,000 名候选人、仅使用 CPU、5 分钟时间限制、16 GB 内存——对每个候选人运行 transformer embedding 模型太慢了(早期未优化的尝试预计需要 4 个多小时)。因此,该系统采用了两阶段的“检索后重排”架构,这也是生产级搜索和推荐系统中使用的相同模式,而这正是职位描述中希望有人去构建的那种系统。 ``` ┌─────────────────────────────┐ │ 100,000 candidates │ │ (candidates.jsonl) │ └───────────────┬───────────────┘ │ ┌────────────────▼────────────────┐ │ STAGE 1: Rule-based scoring │ │ (ALL 100,000 candidates, ~15s) │ │ │ │ • Title & role fit │ │ • Skills match (synonym-aware) │ │ • Experience profile │ │ • Location fit │ │ • Education tier │ │ • Redrob platform signals │ │ • Honeypot detection │ │ • Red-flag detection (JD traps) │ └────────────────┬────────────────┘ │ Top 10,000 by Stage 1 score │ ┌────────────────▼────────────────┐ │ STAGE 2: Semantic reranking │ │ (Top 10,000 only, ~2.5–3 min) │ │ │ │ BGE-small-en-v1.5 embeddings: │ │ cosine similarity between JD │ │ and candidate profile text │ └────────────────┬────────────────┘ │ ┌────────────────▼────────────────┐ │ Combine: weighted final score │ │ × behavioral multiplier │ │ − honeypot/red-flag penalties │ └────────────────┬────────────────┘ │ Sort, take top 100 │ ┌────────────────▼────────────────┐ │ Generate per-candidate reasoning │ │ → output/submission.csv │ └────────────────────────────────┘ ``` **为什么是 10,000,而不是 100,000 或 1,000?** 阶段 1 已经为每位候选人捕获了主要的 JD 信号——职位、技能、经验、位置。在阶段 1 得分位于后 90% 的候选人(职位错误、没有相关技能、位置错误),无论语义相似度如何,都没有现实途径进入最终的前 100 名。这经过了经验验证:分别对 5,000、10,000 和 15,000 的候选池规模进行了端到端测试,这三者都产生了完全相同的最终前 100 名列表。最终选择了 10,000(数据集的 10%)作为在 5 分钟计算限制下具有最佳安全余量的配置。 ## 评分组件 | 组件 | 权重 | 捕捉的信息 | |---|---|---| | **语义相似度 (BGE)** | 0.30 | JD 与候选人的职位标题、摘要、头衔、技能和近期职业描述之间的 embedding 余弦相似度。捕捉超越精确关键词匹配的含义——例如,“从零开始构建语义搜索基础设施”即使在没有出现“embeddings”一词的情况下也能与 JD 匹配。 | | **头衔与角色匹配度** | 0.20 | 当前的头衔和职业轨迹是否反映了产品公司的 AI/ML 工程师职位,包含生产级 ML 信号检测,并对纯咨询背景的职业进行扣分。 | | **技能匹配** | 0.15 | 针对 JD 的必备技能组(embeddings、向量数据库、混合搜索、评估指标、Python)和加分技能组(LoRA/QLoRA、learning-to-rank、分布式系统)进行同义词感知匹配。 | | **经验画像** | 0.15 | 相对于 JD 要求的 5-9 年经验区间的年限,对拥有产品公司资历和生产部署信号者给予加分,对重度咨询或纯研究背景者进行扣分。 | | **位置匹配** | 0.08 | 浦那/诺伊达得分最高,其次是海得拉巴/孟买/德里 NCR,然后是其他印度城市(支持搬迁),最后是国际地点。 | | **教育背景** | 0.07 | 院校层级(如数据集中所提供)和研究领域的相关性。 | | **Redrob 平台信号** | 0.05 | 档案完整度、招聘人员回复率、面试完成率、招聘人员收藏、GitHub 活跃度。 | 在加权求和之后,应用了两个调整: - **行为乘数 (0.65×–1.0×)** — 根据平台活跃度、求职状态、招聘者回复率和离职通知期降低候选人的权重。这直接实现了 JD 自身的指示:*“一个在简历上完美的候选人,如果 6 个月没有登录,且招聘者回复率只有 5%,那么出于招聘目的,他实际上是不可用的。”* - **扣分项** — 蜜罐惩罚(最高 0.10)和红旗惩罚(最高 0.30)将直接从最终得分中扣除。 ## 陷阱检测 职位描述明确指出了内置在数据集中的模式,旨在过滤掉天真的排名方法。每一种模式都有专门的检测器。 ### 1. 关键词堆砌者 — `scorers/title_scorer.py` 如果一个候选人的技能列表中有“RAG, Pinecone, LLM”,但其头衔是“营销经理”或“HR经理”,那么无论其技能列表如何,其头衔得分都将接近于零。头衔/角色匹配度在基于规则的组件中权重最高(0.20),正是为了防止这种模式主导排名。 ### 2. 蜜罐档案 — `honeypot_detector.py` 包含内部逻辑不可能数据的档案:期望薪资的 `min > max`、总职业经历时长远远超过所填写的经验年限、列出多个持续时间为 0 个月的“专家”技能,或者单个角色的持续时间长于候选人的整个职业生涯。 **结果:最终前 100 名中有 0 名被标记为蜜罐的候选人**(提交规范的阈值为 ≤10)。 ### 3. 框架狂热者 — `jd_red_flags.py` JD 指出:*“如果你的‘AI 经验’主要包含最近(12 个月内)使用 LangChain 调用 OpenAI 的项目——除非你能展示出大量 LLM 出现之前的 ML 生产经验,否则我们可能不会推进。”* 该检测器检查三种独立的模式:(a) 最近的一段短期任职角色大量涉及 LangChain/OpenAI/prompt-engineering 相关描述,且没有更早的角色展示出 LLM 出现之前的生产级 ML 信号;(b) 技能列表主要由浅层掌握的框架名称组成,且没有实质性的 ML/检索技能作为弥补;(c) 当前头衔属于非工程类(例如项目经理、平面设计师、运营经理),同时在职位标题或摘要中包含“AI 爱好者”/“使用 LLM 构建”等描述,且没有真正的生产级 ML 背景。在诊断到数据集中该信号最强烈的表现存在于职位标题/摘要字段而非职业经历文本后,我们增加了模式 (c)。 **结果:全数据集共标记了 4,051 名候选人;0 人进入最终的前 100 名。** ### 4. 追求头衔者 — `jd_red_flags.py` JD 指出:*“如果你的职业轨迹显示你通过每 1.5 年跳槽一次来优化获取‘资深 (Senior)’ -> ‘骨干 (Staff)’ -> ‘主管 (Principal)’头衔,那么我们并不合适。”* 被检测为具有两次或更多短期任职(≤18 个月)且资历严格递增的角色。 **结果:全数据集共标记了 107 名候选人。** ### 5. 纯咨询职业,包含 JD 中明确的例外情况 JD 指出,纯咨询职业(TCS、Infosys、Wipro 等)属于不匹配,除非候选人目前在这类公司任职,但先前拥有产品公司经验。`scorers/experience_scorer.py` 中的检测器会检查是否**所有**职业经历都是咨询;只要有一个角色是在产品公司,就不会进行扣分——这与 JD 中声明的例外情况完全一致。 ## 推理生成 — `reasoning_generator.py` 前 100 名候选人中的每一位都会收到一个完全基于真实档案字段构建的推理字符串,没有任何捏造的声明。每个推理字符串: - 引用具体事实:工作年限、当前职位和公司、位置,以及候选人实际与 JD 相关的技能,优先考虑必备技能而非加分技能,然后再按熟练度和持续时间排序 - 明确与 JD 建立联系(例如,“符合 JD 首选的浦那/诺伊达中心要求”) - 在存在问题时提出一个诚恳的顾虑——检测到的红旗信号、技能重合度低、重度咨询背景、离职通知期长、招聘者回复率低、非求职状态、缺失生产环境信号、经验在 5-9 年区间之外,或非印度位置(JD 注明不为国际候选人提供签证担保) - 在不同的排名层级(1-10、11-40、41-75、76-100)中变换句子结构,以确保在进行抽样查看时推理内容不会显得模板化 - 对于具有出色技能但排名较低的候选人,将排名归因于可用性或行为因素,而不是将出色的技能错误地描述为薄弱,从而保持推理的基调与影响得分的实际驱动因素一致 ## 运行说明 ``` pip install -r requirements.txt python rank.py ``` 这将在标准 CPU 上大约 2.5-3 分钟内生成 `output/submission.csv`(包含 candidate_id、rank、score、reasoning)。 验证输出格式: ``` python data/validate_submission.py output/submission.csv ``` 运行完整性检查(蜜罐数量、分数分布、位置/职位多样性、推理变化): ``` python health_check.py ``` ## 结果摘要 - 最终前 100 名中有 **0** 名被标记为蜜罐的候选人(阈值:≤10) - 前 100 名中没有重复的推理字符串 **0** - 分数范围:大约 0.97–1.16,按排名平稳非递增 - 前 100 名涵盖了 19 个不同的职位和 25 个以上的不同城市,主要集中在 JD 首选的浦那/诺伊达/德里 NCR 中心,符合预期,同时仍包括了来自其他印度城市有资格 relocation 的候选人 - 全数据集共检测到 **4,158** 个红旗信号(4,051 名框架狂热者,107 名追求头衔者),其中 0 人进入最终的前 100 名 - 总运行时间:约 2.5-3 分钟,轻松满足 5 分钟的计算限制 ## 项目结构 ``` india-runs-ai-challenge/ ├── data/ │ ├── candidates.jsonl │ └── validate_submission.py ├── output/ │ └── submission.csv ├── scorers/ │ ├── title_scorer.py │ ├── skill_scorer.py │ ├── experience_scorer.py │ ├── location_scorer.py │ ├── education_scorer.py │ ├── redrob_signal_scorer.py │ ├── behavioral_multiplier.py │ └── semantic_scorer.py ├── honeypot_detector.py ├── jd_red_flags.py ├── reasoning_generator.py ├── health_check.py ├── rank.py ├── requirements.txt └── README.md ``` ## 设计决策与未来工作 - **手工调整的权重。** 组件权重反映了 JD 对各项要求的重视程度(生产级 embeddings/检索经验在“你绝对需要具备的条件”下位列第一且被强调得最重),而不是通过标注数据学习得来的。如果拥有真实的 相关性判断数据,可以使用 learning-to-rank 模型——例如,使用 NDCG 目标训练的 LightGBM——来替代手工调整的加权求和。由于本次挑战没有可用的标注相关性数据,因此未采用此方法。 - **语义候选池规模。** 在开发过程中已解决:分别对 5,000、10,000 和 15,000 的池规模进行了端到端测试,并产生了相同的前 100 名结果,证实了 10,000 是一个安全且充分的配置。 - **用于语义评分器的 JD 文本。** 在开发过程中已解决:embedding 文本从最初的释义更新为直接提取自实际职位描述的表述,刻意排除了“不想要”的描述语言,以避免使那些符合 JD 自身红旗描述的候选人的相似度得分虚高。 - **框架狂热者检测器。** 在开发过程中已解决:最初只检查职业经历描述的版本标记了 0 名候选人。诊断表明,该模式最强烈的信号存在于 `headline` 和 `summary` 字段中。检测器随后被扩展为包含三种独立的模式,目前在全数据集中共标记了 4,051 名候选人,并确认均未进入最终的前 100 名。
标签:人工智能, 人才筛选, 候选人排序, 排序算法, 数据科学, 智能招聘, 用户模式Hook绕过, 语义检索, 资源验证, 逆向工具