尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

用LLM judge评估招聘搜索排序:离线评测流程与避坑指南

用LLM judge评估招聘搜索排序:离线评测流程与避坑指南 招聘搜索的排序评估过去基本靠点击率、投递率这类线上指标再补一部分人工标注。现在很多团队开始尝试另一种思路让大语言模型当评审直接对职位搜索结果打分或对比排序这就是常说的 LLM judge。我最近在搭建一个招聘垂直场景的离线评测流程把数据构造、prompt 设计、批量调用、指标计算和偏差排查完整跑了一遍下面把能直接复用的经验拆出来。这篇文章适合做搜索排序、推荐系统或招聘平台算法迭代的人尤其是需要快速回归排序质量但又缺人工标注资源的团队。先说结论LLM judge 不是要替代线上实验而是补上“离线快速评估”这一环。它能稳定、低成本地给出带理由的相关性判断但必须先用人工标注小样本校准才能判断它到底可不可信。1. 招聘搜索排序评估LLM judge 最值得关注的价值是什么1.1 传统评估的三条路各自卡在哪做招聘搜索排序团队通常会从三个方向评估效果评估方式能反映什么主要问题线上行为指标点击率、投递率、转化率业务结果最直接有滞后受位置、公司知名度、职位新鲜度干扰离线指标NDCG、MRR、Recall 等适合快速比较算法版本依赖人工标注的相关性标签标注成本高人工逐条评估判断质量最可靠能给出原因速度慢、成本高人数少时一致性难以保证招聘搜索还有一个特殊之处相关性不只是文本词面匹配。两个人搜同一个“Java 后端”一个人期望北京、三年经验、薪资 30K另一个人期望远程、五年经验、薪资 50K结果应该完全不同。传统离线指标很难把这个“候选人画像”完整吃进去人工标注又扛不住大规模查询。1.2 LLM judge 的定位是补充不是替代LLM judge 的做法是把原本由人完成的评估任务交给大模型给它一段候选人信息、一个查询条件、一组职位结果让它按规则打分或对比最后输出一个结构化结论。这套方案的好处有三个快。几百条样本十分钟到半小时就能跑完人工一整天都未必能完成。便宜。相比雇佣标注团队API 调用费用通常在可接受范围。可解释。让模型输出理由能反查它为什么给某个职位高分或低分。但也要明确边界LLM judge 输出的是“代理评估结果”不是真实用户行为。它只能回答“这个排序看起来合不合理”不能回答“用户会不会真的投递”。到了确认上线阶段还是要回到线上实验看最终指标。1.3 适用场景和不适合的场景实际落地时我建议优先把 LLM judge 用在这么几类任务上算法版本回归每次排序模型迭代后跑一批固定评估集看排序质量有没有退化。长尾查询热门查询可能有人工标注长尾查询没有可以让 LLM judge 先粗筛一遍。多方案对比两个 ranker 在同一个查询上的表现差异用两两对比方式让模型判胜负。人工标注前的预标注先让模型给一个初判再让人工复核分歧大的样本。不适合的场景也要说清楚。如果业务对职位推荐有很强的合规要求或者判定结果会直接进入自动化决策流程不能只靠模型输出。LLM judge 适合做辅助不适合做最终裁决。2. 开始跑之前先把输入数据和评分规则定清楚2.1 三段输入查询、候选人画像、职位列表LLM judge 的输入不是简单的“关键词 文档列表”而是三条信息查询条件用户搜索时填的关键词、筛选条件比如“Java 后端 北京 三年以上”。候选人画像期望城市、期望薪资、工作年限、技能栈、目标职位类型、求职状态。这部分在招聘搜索里尤其重要没有它模型只能用文本相关性猜测。职位结果职位标题、公司、地点、薪资范围、经验要求、技能要求、职位描述必要时带上发布时间。我建议把候选人画像放在 prompt 靠前的位置并且明确告诉模型“相关性是针对这个人不是泛指某个职位”。否则模型很容易把它当成普通的文本检索题甚至只凭职位标题长度做判断。2.2 评判维度把“相关”拆成可打分的规则“相关”这个词太模糊必须拆成可操作维度。招聘场景里我常用的维度如下维度判定内容容易踩的误区职位相关性职位职责和目标岗位是否匹配只看标题忽略职责描述技能匹配候选人的主要技能是否在 JD 里出现把加分项当成硬性要求薪资匹配期望薪资和职位薪资区间是否有重叠职位没写薪资就随意给高分地点匹配期望城市、通勤、远程要求是否满足忽略候选人明确写的远程偏好经验与职级年限、职级是否在要求范围附近过于严格的精确匹配评分规则要写成模型能执行的形式。例如0 分表示“明显不匹配”1 分表示“部分匹配但存在明显问题”2 分表示“基本匹配”3 分表示“完全匹配”。不要用含糊的“好、中、差”。2.3 人工标注一小批标准答案这一步很多人会跳过但恰恰是让整个方案可信的关键。在开始大量调用 LLM judge 之前先人工标注 100 到 300 个样本样本可以是“候选人 查询 职位对”也可以是“候选人 查询 两版排序结果”。这批数据有三个作用用于调 prompt看模型输出和人的判断有没有明显冲突。用于计算 LLM judge 与人工标注的一致性确定它是否靠谱。用于发现模型偏好比如是不是总给描述长的职位高分。标注样本时要故意放一些困难样本候选人要求远程、职位明显在别的城市候选人技能是 Java职位标题写“Python 后端”但职责里要求 Java职位薪资范围明显低于期望。困难样本才能逼出模型的真实判断能力。2.4 模型选择与运行条件模型选择会影响效果和成本。如果只是验证流程先用一个市面上主流的中小模型或性价比模型就够了如果要正式回归再考虑换更强模型。运行条件方面LLM 不一定要跟搜索服务部署在同一台机器上。接受 OpenAI 兼容接口的远程服务、局域网内服务都能接入如果本地有显卡也可以部署开源模型做测试。本地部署时显存和内存需求取决于参数量通常至少几十 GB 内存显存 16 GB 以上才能跑得舒服一点。如果机器配置偏低建议先用 API 方式跑通流程再考虑本地化。3. 评测流程搭建prompt、调用和 JSON 解析3.1 选点对点打分还是两两对比LLM judge 有两种常见形态点对点打分pointwise给每个职位输出 1 到 5 分。两两对比pairwise给两个职位或两版排序直接判断谁更好。在排序评估里我更推荐两两对比。原因很直接模型在绝对分数上容易漂移这次给 4 分下次给 3 分但在“A 和 B 谁更靠前”这种相对比较上稳定性好很多。如果要对比两个排序算法就给模型同一份候选人信息和查询条件把两个算法产出的职位顺序分别标为方案 A、方案 B让模型判断哪个排序更合理。3.2 推荐 prompt 结构一份能稳定工作的 judge prompt至少包含四部分角色定义说明它是招聘搜索排序评估专家。输入信息候选人画像、查询条件、职位列表或两个排序方案。评分规则包含维度、分值和约束条件。输出格式要求输出结构化 JSON包含结果、理由和得分明细。下面是一段可参考的结构实际字段按你自己的业务调整系统你是一名招聘搜索排序评估专家负责判断职位和候选人的匹配程度。 用户 候选人信息 {候选人的城市、薪资期望、技能、年限、目标职位} 查询条件{用户搜索词} 职位 A {职位 JSON} 职位 B {职位 JSON} 请根据以下维度判断 1. 职位职责和候选人目标岗位是否一致。 2. 候选人主要技能是否满足 JD 要求。 3. 薪资期望与职位薪资区间是否匹配。 4. 地点、远程、通勤要求是否匹配。 5. 经验年限和职级是否合适。 只依据上面提供的信息判断不能编造职位未写明的福利或要求。 输出 JSON 格式字段为 { winner: A 或 B 或 tie, reason: 简要理由, score_a: 0-3, score_b: 0-3 }这里有一个关键点必须明确要求模型“只依据输入信息判断”。否则模型很容易用训练知识脑补出某家公司的情况或者默认“写得多就是好职位”。3.3 Python 调用示例和 JSON 解析接口调用没有太复杂的技术关键是封装好输入和输出。下面是一个通用示例客户端和参数以你实际使用的服务商为准import json from openai import OpenAI client OpenAI( api_keyyour_api_key, base_urlyour_service_base_url # 按服务商文档填写 ) def judge_pair(query, candidate, job_a, job_b, modelyour_model_name): system_prompt ( 你是一名招聘搜索排序评估专家 负责判断职位和候选人的匹配程度。 ) user_prompt f 候选人信息 {json.dumps(candidate, ensure_asciiFalse)} 查询条件{query} 职位 A {json.dumps(job_a, ensure_asciiFalse)} 职位 B {json.dumps(job_b, ensure_asciiFalse)} 请判断职位 A 和职位 B 哪个对这位候选人更合适。 只输出 JSON不要输出其他内容。 resp client.chat.completions.create( modelmodel, temperature0, max_tokens800, messages[ {role: system, content: system_prompt}, {role: user, content: user_prompt}, ], ) return resp.choices[0].message.content # 调用示例 result_text judge_pair( Java 后端 北京 三年以上, {city: 北京, salary: 30K-40K, skills: [Java, Spring Boot], years: 4}, {title: Java 开发工程师, salary: 25K-35K, city: 北京, desc: ...}, {title: Python 后端工程师, salary: 20K-30K, city: 上海, desc: ...}, ) try: result json.loads(result_text) except json.JSONDecodeError: # 部分模型会输出 Markdown 代码块需要先清理 cleaned result_text.strip().removeprefix(json).removesuffix() result json.loads(cleaned) print(result)解析 JSON 时经常踩两个坑模型在 JSON 前后追加解释文字或者把 JSON 包在代码块里。稳妥做法是先把返回内容清理掉代码块标记再解析如果解析失败记录原始文本不要直接丢弃。3.4 参数设置温度、max_tokens、seedLLM judge 要尽量可复现参数要收敛。temperature 设 0 或接近 0。temperature 越高判分越不稳定同一个样本跑两次可能结论不同。max_tokens 给够。判断理由和 JSON 输出需要一定长度建议 500 到 1000太少会被截断。如果服务商支持参数 seed 或 response_format 设为 json_object可以一并开启。response_format 能减少解析失败比例。每改一次 prompt都要把 prompt 版本号记录下来。后续发现结果异常时能回查到是哪一版 prompt、哪个模型、哪批数据产生的。4. 结果怎么算才不算白跑4.1 胜率和同序率最常用的指标是两两对比的胜率。假如要对比排序算法 A 和 B每个样本都让 judge 从 A 结果和 B 结果中选一个更合理的排序最后统计胜率 A 获胜的样本数 / 总样本数平局样本要单独统计。如果平局率超过 20%说明两个算法差异不明显或者 judge 区分力不足。不要简单把平局算到某一方头上这会掩盖问题。除了胜率还要看“同序率”也就是 judge 的判断和某个基准排序一致的样本比例。这个基准可以是人工标注也可以是线上点击数据。4.2 与人工标注的一致性判断 LLM judge 可不可信不能只看它自己说的要对齐到人工标注上。先用前面标注好的 100 到 300 个样本跑一遍 judge计算“模型和人工都判断正确”的样本比例。更严谨一点可以算 Cohens kappa一致性超过 0.6结果基本可用。0.4 到 0.6可以用于粗筛但需要人工复核分歧部分。低于 0.4说明 prompt 或维度定义有问题不要急着批量跑。如果一致性不达标优先检查评分规则是否含糊而不是加更多解释。很多 prompt 问题不是模型能力不够而是规则让模型没法稳定输出。4.3 judge 自身稳定性检查LLM judge 还会出现“自己和自己不一致”的情况。同一份输入连续跑 20 次如果结果在不同方案之间反复横跳说明稳定性不够。我一般这样做取 30 条有代表性的样本每个样本跑 3 次取多数票作为最终结果同时记录“三次结果不一致”的样本比例。这个比例最好不要超过 10%。超过的话要么降低温度要么给每个样本多投几次票要么换更强模型。4.4 耗时和成本估算正式跑批量之前先估算 token 消耗。每条样本的 token 主要花在候选人画像和职位描述上。职位描述越长成本越高。可以先用几条样本估算单次调用 token再乘上总样本数。如果预算紧张可以考虑把职位描述截断到前 500 字左右。大多数场景下头部信息已经足够判断相关性。先让便宜模型过滤掉明显不相关的职位再让更强模型做精细对比。控制每次调用只对比两个职位不要一次塞 10 个职位否则容易超出上下文窗口结果也不稳定。5. 最容易翻车的四个坑5.1 位置偏差换个顺序结论就变LLM judge 非常容易受输入顺序影响。同一个职位 A 放在前面和放在后面胜率可能差出一大截。规避方法是随机化顺序。每次调用都随机交换 A、B 的位置记录时再映射回真实方案。如果交换顺序后有一批样本的结论跟着翻转说明 judge 的位置偏好很强。位置偏差严重时不要直接用单次结果要多次投票。5.2 描述长度偏差越长越容易高分模型倾向给描述更长的职位打高分因为它看起来“更正规”。这在招聘场景里很常见某些公司 JD 写得非常长但候选人的核心技能其实只在里面出现一次。处理办法有两个在 prompt 里明确“职位描述长度不影响分数”。对职位描述做截断让两个选项的文本长度接近。更彻底的做法是把职位文本转成结构化字段标题、地点、薪资、技能要求、经验要求、职责摘要再让模型基于结构判断而不是让它读一整段原文。5.3 模型自偏好和多票不一致如果你要评估的是“由某个模型生成的排序理由”或“由某个模型生成的职位摘要”judge 很容易偏心同一家模型产出的内容。招聘场景里比较少见但只要排序结果附带生成文本就要提防。规避方法评估时把生成内容的来源标成 A、B 匿名方案不要暴露模型名。用不同公司或不同系列的大模型当 judge。比如生成侧用甲模型判断侧用乙模型。对分歧样本做人工复核不要自动采信。5.4 prompt 改一个词结果差一截judge 评估里最大的不稳定因素是 prompt 本身。可能只是把“匹配”改成“合适”结论就会变。所以每次调 prompt 都要在固定人工标注集上做对比不能只靠感觉。我的做法是先定义“这一版 prompt 相比上一版在哪些维度上有改进”再在同一批样本上跑完对比看一致性和胜率变化。没有数据支撑的 prompt 改动建议直接排除。6. 从小样本到批量评估建议按这个顺序推进6.1 第一批20 条只看日志和格式不要一上来就跑几百条。先取 20 条样本目的只有一个确认整个链路能通。这一批要检查的内容包括API 调用是否成功鉴权是否正常。返回内容能否稳定解析成 JSON。输出字段是否完整winner、reason、score 都不缺。每条样本的耗时和 token 消耗是否有明显异常。这一阶段发现问题不要急着调 prompt先解决输入格式、路径、编码、接口参数这些基础设施问题。很多批量任务卡住不是模型问题而是文件路径写错、输入字段缺失、JSON 里混进了非法字符。6.2 第二批200 条看指标和偏差链路跑通后扩大到 200 条左右。这时重点看三类结果胜率和同序率是否符合预期。judge 与人工标注的一致性是否达标。有没有明显的位置偏差、长文本偏差、薪资缺失导致的误判。这一批结果决定要不要继续跑到全量。如果一致性不达标停下来先调 prompt 和输入构造。拿 200 条结果反推 prompt成本低结论也足够明显。建议把每个样本的原始返回都存下来不要只存解析后的字段。后面排查问题时原始文本能告诉我们模型到底给了什么理由解析失败也能看到是哪一步出的错。6.3 批量跑JSONL、重试、并发、命名全量评估时推荐使用 JSONL 作为输入输出格式。每一行是一个独立样本便于断点续跑也便于按行检查失败原因。输入文件示例{sample_id: sample_001, query: Java 后端 北京, candidate: {...}, job_a: {...}, job_b: {...}} {sample_id: sample_002, query: 产品经理 远程, candidate: {...}, job_a: {...}, job_b: {...}}处理时建议按 sample_id 把结果写回新文件遇到解析失败或接口报错的样本单独记录到一个错误列表重跑时只补这些样本。批量任务还要注意几点并发不要拉满。先用 4 到 8 个线程试跑观察服务商限流情况。必须有重试机制。推荐指数退避例如失败后等 1 秒、5 秒、15 秒再试。输出文件要包含 prompt_version、模型名、temperature、seed保证可追踪。控制单个样本的 token避免因上下文过长导致输出质量下降或直接报错。# 批量处理伪代码实际以你的文件路径为准 import json with open(eval_samples.jsonl, r, encodingutf-8) as fin: lines [json.loads(line) for line in fin if line.strip()] results [] failed [] for item in lines: try: text judge_pair( item[query], item[candidate], item[job_a], item[job_b] ) results.append({sample_id: item[sample_id], raw: text}) except Exception as exc: failed.append({sample_id: item[sample_id], error: str(exc)}) with open(judge_results.jsonl, w, encodingutf-8) as fout: for res in results: fout.write(json.dumps(res, ensure_asciiFalse) \n) with open(judge_failed.jsonl, w, encodingutf-8) as ferr: for item in failed: ferr.write(json.dumps(item, ensure_asciiFalse) \n)失败样本不要直接丢弃先看失败原因。大部分失败集中在几种情况超时、限流、JSON 解析失败、输入字段缺失。按原因分批处理比抓着一个样本反复重试有效。7. 落到生产环境前别忘了这三件事7.1 先建立可追踪的评估集LLM judge 方案能不能长期用取决于评估集是否稳定。我建议把样本集、prompt 版本、模型版本、结果文件都放到同一个目录或仓库里每次跑都生成一个记录。一个月后再跑才能知道算法是变好了还是变差了。如果不记录版本第一次跑出 60% 胜率一个月后跑出 55%你根本不知道是算法退化了还是 judge 变了还是样本集被改动了。7.2 定期和人工校准别让误差越积越大LLM judge 不能永远只和自己比较。每过一段时间抽一部分新样本让真人标注和 judge 结果做一次对齐。模型换代、业务重点变化、职位数据结构变化都可能影响 judge 的表现。校准频率不需要太高半个月到一个月一次就够。校准的目的不是彻底替代模型而是确认误差还在可控范围。7.3 最终判断还是要看线上真实行为离线评估做得再好也只是“看起来靠谱”。用户是不是真的愿意点击、投递、接受推荐只有线上实验能给出答案。我建议把 LLM judge 当成一个快速筛选器它负责在离线阶段过滤掉大概率退化的方案把有潜力的方案留到线上小流量验证。这样既节省了重复上线的时间又不会因为模型误判而错过真正有效的改进。踩过几次之后我发现很多排序评估问题不是工具能力不够而是前置数据、评分规则和版本记录没有处理干净。把这几点做扎实LLM judge 能成为招聘搜索排序迭代里相当顺手的一件工具。
返回列表