Gloat作为内部人才市场的代表性平台,其核心价值在于让员工的技能画像与组织内的岗位、项目、任务实现自动匹配。但不少企业上线后发现一个尴尬的现象:系统推出来的机会要么和员工实际能力相去甚远,要么把明显合适的人漏掉了。匹配效果差的锅,往往不该由算法背,真正的问题通常出在技能图谱的搭建方式和岗位描述的结构化程度上。这篇文章就来拆解这个对齐问题,并给出一套可落地的改造方案。

匹配差的根因:技能数据源头失真
先看一个典型场景。某公司员工在Gloat的技能档案里填了Java后端、精通、3年,同时另一个岗位需求写的是需要后端开发经验。系统会怎么匹配?如果技能图谱里Java后端和后端开发是两个互不相关的节点,这一对人岗组合的相似度就接近于零,匹配自然失败。这就是最常见的第一个根因:同义词和近义词没有归一,技能词汇各自为政。
第二个根因更隐蔽,就是岗位描述颗粒度不统一。有的岗位写需要具备前端能力,有的写需要熟悉React 18、TypeScript和Webpack构建,前者宽泛到无法计算,后者细致到排斥了只会Vue的人。颗粒度不一致时,相似度计算的分数根本没有可比性,宽泛需求会匹配到所有人,细致需求几乎匹配不到任何人,排序结果自然一团糟。
第三个根因是技能权重缺失。岗位要求里通常有硬性项和加分项,员工画像里有核心技能和边缘技能,如果所有技能一视同仁地参与计算,匹配分数就会呈现一种虚假的平均主义,推荐结果看起来什么都沾点边,实际上什么都不精准。
构建可对齐的技能图谱本体
技能图谱的本质是一个带关系的技能本体(Ontology),核心要解决三个问题:归一、分层、关联。归一指把同义表述映射到同一个规范节点,比如Java后端开发、后端工程师、Backend Developer都指向同一个技能ID。分层指建立领域、技能族、技能点、技能版本四级结构,例如软件研发属于领域,后端开发属于技能族,Java、Go属于技能点,Spring Boot 3属于具体版本。关联指建立相似关系和前置关系,比如React和Vue在技能族内是相似关系,掌握Java是掌握Spring Boot的前置关系。
实践中推荐用自底向上的方式初始化图谱:先从企业内部的JD文本、简历、学习平台课程目录里抽取高频技能词,再做人工清洗和归并,最后补充行业标准技能库(如ESCO、O*NET的技能分类)作为骨架。这样可以保证图谱既贴合企业实际用语,又有行业通用性。下面是一个简化的图谱数据结构示例:
# 技能节点与关系的最小示例
skills = {
"S001": {"name": "Java后端开发", "level": "skill", "parent": "F001"},
"S002": {"name": "后端开发", "level": "family", "parent": "D001"},
"S003": {"name": "Spring Boot", "level": "skill", "parent": "S001"},
}
relations = [
{"from": "S001", "to": "S002", "type": "is_a"},
{"from": "S003", "to": "S001", "type": "is_a"},
{"from": "S001", "to": "S002", "type": "same_as", "alias": "后端工程师"},
]
# 归一化查找:任意输入词映射到规范节点
alias_index = {
"Java后端": "S001",
"Backend Developer": "S001",
"后端开发经验": "S002",
}
def normalize(term):
return alias_index.get(term, term)
归一化索引建立后,员工侧和岗位侧的技能表述都会先经过这一层清洗,再进入匹配计算,同义词造成的漏匹配问题可以从根源上消除。需要提醒的是,图谱不是建完就结束的,建议每季度跑一次新词发现流程,把新出现的技术栈和内部黑话及时纳入,否则图谱会慢慢和实际用语脱节,匹配质量会随时间衰减。
岗位描述的结构化改造
有了图谱,岗位描述也要跟着改造。自由文本的JD无法直接参与计算,必须拆解成结构化的技能需求单元,每个单元包含技能ID、熟练度要求、权重和是否硬性四个字段。熟练度建议采用统一的五级量表:了解、熟悉、掌握、精通、专家,并且给每个级别写清楚行为化定义,比如掌握指能独立完成该技能相关的典型任务,避免各业务部门理解不一致。
硬性项和加分项的区分尤为关键。硬性项参与过滤逻辑,不满足直接出局;加分项参与排序逻辑,满足越多分数越高。很多团队一开始把所有要求都设成硬性项,结果候选池被切得太小,系统只能降级推荐勉强合格的人。更合理的做法是硬性项控制在三到五条以内,其余全部作为加权项。结构化后的岗位需求数据示例如下:
{
"job_id": "J2048",
"title": "交易平台后端工程师",
"requirements": [
{"skill_id": "S001", "proficiency": 4, "weight": 0.4, "mandatory": true},
{"skill_id": "S003", "proficiency": 3, "weight": 0.3, "mandatory": false},
{"skill_id": "S010", "proficiency": 3, "weight": 0.3, "mandatory": false,
"note": "S010为消息队列技能,可接受Kafka或RocketMQ任一"}
]
}
注意示例中的note字段,它体现的是图谱层面的替代关系:消息队列这个技能点上,Kafka和RocketMQ满足其一即可。这类或逻辑必须在技能图谱里定义成技能组,而不是在JD里靠文字描述,否则匹配引擎读不到。改造初期建议由HRBP和技术负责人共同完成存量岗位的拆解,虽然耗时,但这是后续所有匹配质量的基础投入。
匹配算法与效果评估
数据对齐之后,匹配计算反而变得简单。基础方案是加权技能覆盖度:把员工画像中每个技能的熟练度和年限折算成分值,对照岗位需求的权重求加权和,硬性项单独做门槛校验。进阶方案可以在图谱上做语义扩展,员工没写Spring Boot但写了Java,且历史上大量Java掌握者同时具备Spring Boot,就给一个基于共现概率的软性加分。下面是基础匹配的参考实现:
def match_score(employee, job, graph):
# 硬性项校验
for req in job["requirements"]:
if req["mandatory"]:
emp_level = employee.skills.get(req["skill_id"], 0)
if emp_level < req["proficiency"]:
return 0.0
# 加权得分
score = 0.0
for req in job["requirements"]:
emp_level = employee.skills.get(req["skill_id"], 0)
covered = min(emp_level / req["proficiency"], 1.0)
score += covered * req["weight"]
return round(score, 3)
算法上线后必须有评估闭环。建议从三个指标持续观察:推荐接受率(员工看到推荐后实际申请的比例)、用人方满意度(负责人对候选人列表的评分)和匹配漏检率(人工判断合适但系统未推荐的比例)。前两个指标提升说明对齐有效,第三个指标下降则说明图谱归一还有遗漏,需要回到数据源头补同义词。评估数据每两周回流一次到图谱维护流程,形成数据、算法、反馈的闭环,匹配质量才能稳定爬坡而不是一次性脉冲。
最后说一个容易踩的坑:不要试图用算法掩盖数据问题。Gloat这类平台自带的能力再强,也建立在输入数据质量之上。技能图谱和岗位描述对齐是慢功夫,但它决定了内部人才市场是变成一个真正的人才流动引擎,还是又一个没人用的填报系统。