ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用LLM judge评估职位搜索排序:从方案设计到落地实践

用LLM judge评估职位搜索排序:从方案设计到落地实践 在实际职位搜索产品里排序结果的质量直接影响用户能不能快速找到合适工作也影响企业端的招聘效果。可是评估排序质量这件事往往比训练排序模型更繁琐人工标注需要领域经验点击日志存在位置偏差线上 A/B 测试周期又太长。LLM judge 提供了一种新的评估方式让大语言模型扮演评审员根据预先定义的标准对搜索返回的职位排序进行打分或比较从而快速得到可复现、可追踪的评估结果。这篇文章会以 Evaluating job search ranking with LLM judge 为主线索依次讲清楚评估方案怎么设计、最小评估流程怎么跑通、指标怎么聚合以及 judge 本身出问题时要如何排查。适合阅读这篇内容的读者主要有两类一类正在做搜索排序、推荐策略或内容质量评估的工程师需要为模型迭代找到更稳定的离线评估手段另一类是听说过 LLM judge但还不确定怎么落地到具体业务场景的人。职位搜索是一个很典型的文本匹配场景查询词、职位标题、职位描述和公司信息都可以交给大模型理解所以用它来做评估比较自然。下面先回答一个更基础的问题为什么不用传统方式非要引入 LLM judge。1. 为什么职位搜索排序需要 LLM judge 来评估排序评估不是一个新问题。招聘平台每天有大量用户搜索“Java 后端”“产品经理”“远程前端”等关键词搜索引擎会返回一页职位列表。这个列表排得好不好可以从用户点击、投递行为和最终转化率里看到结果但这些反馈都有延迟和偏差。LLM judge 的价值在于把“评估排序质量”从依赖人工和流量实验中部分解放出来变成一个可以批量执行的文本评估任务。1.1 传统排序评估方法的瓶颈职位搜索排序的评估目标通常不是“这个职位是否包含关键词”而是“这个职位对当前查询是否相关、是否值得展示、出现在这个位置是否合理”。传统评估方法各有优缺点。人工标注是最直接的方式。招聘领域的标注员需要理解职位描述里的技能要求、工作地点、薪资范围是否和查询匹配还要能区分“部分相关”和“高度相关”。这种标注质量高但成本也高而且招聘需求在不同城市、不同职级之间差异很大标注标准很难完全统一。一个中型评估集可能需要数千条 query每条 query 又有 10 到 20 个候选职位靠人工标注很难跟上排序策略的迭代节奏。点击日志评估则是另一种常见方案。用户点击了排序靠前的职位并不能说明这个职位真的最合适因为用户可能只是被标题吸引点进去发现并不匹配。点击行为天然包含位置偏差排在前面的结果更容易被看到也更容易被点击。如果不做位置校正直接用点击率评估排序质量排序模型会越来越倾向于把高点击率的老职位放在前面而不是把真正匹配的职位放在前面。线上 A/B 测试的结果最接近真实业务效果但需要足够流量、足够测试周期和完整的实验平台。对于排序策略的日常改动比如调一个特征权重、换一种相关性打分方式每次都要等一周甚至更久的数据反馈闭环太慢。所以离线评估仍然不可替代问题只在于怎样低成本地拿到可靠的离线标签。下表可以更直观地对比几种评估方式的差异。评估方式成本反馈周期主要偏差适合场景人工标注高天到周标注标准不一致小样本精标、模型阶段验收点击日志低天到周位置偏差、标题党线上效果监控、粗粒度验证线上 A/B中到高周到月流量分配、时间窗口最终决策、发布前验证LLM judge中分钟到小时模型偏好、幻觉快速迭代、回归测试、数据扩充LLM judge 并不是要完全替代人工标注或线上实验而是把高频、低成本的离线评估场景接住让工程师在每次排序策略改动后都能在几小时内得到一份相对稳定的评估报告。1.2 LLM judge 在排序评估里的定位LLM judge 的定义可以这样理解把一段评估任务描述成一个 prompt让大语言模型按照固定规则输出评分、理由或排序结果然后由程序解析并聚合这些输出形成质量评估指标。它和大语言模型作为排序模型本身是两个概念。排序模型的任务是“给定 query 和候选职位输出相关性分数并用分数排序”LLM judge 的任务是“给定 query 和一组已经排好的职位评价这份排序好不好”。两者虽然都调用 LLM但目标不同评估方式也不同。实际项目中常见的做法是用传统检索模型或轻量排序模型生成候选列表再用 LLM judge 对列表做质量评估评估结果反过来指导排序模型迭代。职位搜索场景非常适合 LLM judge因为职位描述和用户搜索词之间有大量语义关系。例如用户搜索“远程 Python 开发”一个职位标题是“Python 工程师远程办公”另一个是“Python 开发工程师驻场”传统关键词匹配很难区分两者但 LLM 可以根据职位描述中的工作方式字段理解“远程”与“驻场”的差异。1.3 适用场景和不适用场景LLM judge 不是银弹。适合用它做评估的场景有几个特征第一评估对象本身就是文本或者可以转换成文本第二评估标准可以通过自然语言描述清楚第三评估结果可以接受一定概率的错误和不稳定第四需要快速重复执行人工标注来不及。职位搜索排序恰好符合这些特征。查询词、职位标题、公司名称、工作地点、职责描述都可以放进 prompt。相关性、薪资透明度、地域匹配、职位质量这些维度也能用自然语言定义。不适合的场景也要提前识别。如果评估结果直接影响法律合规、安全审核或资金相关决策不能只依赖 LLM judge如果职位数据包含敏感个人信息不允许发送到外部模型服务就需要私有化部署或选择合规的本地模型如果评估任务需要精确执行 100 条硬性规则prompt 很容易遗漏最好还是用规则引擎来做。注意LLM judge 适合做“质量度量”和“策略回归”不适合单独做“最终决策”。把 judge 输出和历史人工标注同时记录下来才能知道它什么时候会偏。2. 评估方案设计先确定评估粒度、维度和数据开始写代码之前应该先把评估方案设计清楚。很多项目失败不是因为 prompt 写得不好而是评估粒度和评估维度没有提前定义导致 judge 输出无法聚合或者评估结果无法解释。2.1 三种评估粒度Pointwise、Pairwise、Listwise用 LLM 评估排序结果按照输入输出形式可以分成三种粒度。Pointwise 是逐项打分。把每个 query 和单个候选职位发给 LLM让模型输出一个分数例如 1 到 5 分。它的优点是逻辑简单、输出稳定、便于批量处理缺点是它不直接比较多个职位之间的关系可能出现两个职位都得到 4 分但实际其中一个明显更适合当前用户的情况。Pairwise 是两两比较。给 LLM 两个职位 A 和 B让它判断 A 比 B 好、B 比 A 好还是差不多。这种方式更贴近“排序”语义模型比较容易捕捉相对差异评估结果可以聚合成胜率或排序关系。缺点是调用次数多一个长度为 10 的候选列表朴素的 pair 比较会产生 45 次调用成本较高。Listwise 是整体评估。把整个候选列表放进 prompt让 LLM 对整体排序打分或者直接输出一个更合理的排列。它最直接但大模型对长列表的稳定性通常不如单项打分而且输出结果很难解析。实际使用中可以先让模型输出一个重排后的职位 id 序列再和原始排序计算一致性指标。下面用表格做一个对比。评估粒度输入输出优点缺点适合场景Pointwisequery 单个职位分数稳定、便宜、易解析无法感知排序位置先判断每个职位是否相关Pairwisequery 两个职位偏好/平手贴近排序语义调用次数多人工评价抽样、小规模精评Listwisequery 候选列表整体分或重排结果直接评估列表不稳定、token 成本高Top K 质量测试、策略回归职位搜索排序评估的常见组合是先做 Pointwise 过滤筛掉明显不相关的职位再对剩下职位做 Pairwise 或 Listwise。组合使用可以在成本和稳定性之间取得平衡。2.2 评估维度怎么定评估维度是 judge 打分时需要遵循的规则。如果维度太多模型会顾此失彼如果维度太少又无法解释分数为什么低。对职位搜索排序来说下面几个维度最常用。相关性查询和职位之间的语义匹配程度。这是最核心的维度判断职位是否满足了用户的搜索意图。比如搜索“Java 后端”返回“Java 开发工程师”应该算高度相关返回“Java 培训课程”就不一定符合招聘搜索意图。信息质量职位描述是否提供了足够的决策信息例如职责、要求、薪资范围、工作地点、团队规模。一个只有一句话“招开发”的职位即使标题相关信息质量也可能不足以让用户投递。地域匹配用户如果有城市或远程偏好职位地点是否匹配。这个维度有时比相关性更影响用户体验因为地点不符的职位用户多半不会投递。职位有效性职位是否已下线、是否已招满、是否重复发布。LLM judge 可以从描述中的语气或状态推断但它并不一定掌握实时职位状态所以这个维度最好结合线上状态字段一起判断。质量风险是否存在虚假宣传、薪资过于模糊、职责描述与职位名称严重不符等情况。这个维度适合作为布尔判断而不是打分。建议每次评估只聚焦一到三个维度。例如在排序策略回归阶段重点看“相关性和信息质量”在用户体验专项分析阶段再加入“地域匹配”。不同维度可以分开调用 judge也可以在同一个 prompt 里用结构化 JSON 输出多个字段。维度定义Pointwise 打分示例相关性职位与查询语义匹配程度1 无关3 部分相关5 高度相关信息质量描述是否包含职责、要求、薪资、地点1 信息极少3 信息中等5 信息完整地域匹配工作地点是否符合查询中的城市或远程要求0 不匹配1 匹配2 高度匹配质量风险是否存在虚假或严重歧义0 正常1 有风险2.3 评估数据集如何构造评估数据是 judge 效果的基石。构造数据集时需要同时考虑查询的覆盖面和候选职位的多样性。查询采样要从线上日志中抽取真实 query而不是只用手写例子。可以从几个维度分层采样按职位类别分层例如技术、产品、设计、运营按搜索模式分层例如“城市 职位”“职位 技能”“远程 职位”按热度分层例如热门高频词和长尾低频词都要有。线上 query 往往包含口语化表达和拼写变体比如“前端开发”“web 前端”“前端攻城狮”这些都应该出现在评估集里。候选职位的构造也需要设计。理想情况下每个 query 对应 5 到 20 个候选职位这些职位应该来自不同排序策略。常见做法是同时记录线上排序模型的输出和候选实验模型的输出。如果实验环境中已经有多个版本的排序结果就把它们都收集起来形成“同一 query 多份排序”的结构。这样 judge 后续可以直接比较策略 A 和策略 B 的排序质量而不只是给一个绝对分数。数据量上如果只是验证 judge 本身是否可靠建议先准备 50 到 100 个 query每个 query 带 5 到 10 个候选职位。进入正式回归测试后可以把规模提升到 500 到 2000 个 query。数据量太小无法发现 judge 的偏差数据量太大又会造成调用成本过高所以要从少到多逐步扩展。构造样本时每一条记录需要用 JSON 保存 query、用户上下文和候选职位列表。用户上下文可选的字段包括城市、期望职位、工作年限、是否接受远程。下面会给出一个具体的 JSONL 示例。2.4 评估指标怎么设计LLM judge 输出原始分数之后还需要设计聚合指标才能回答以下问题排序策略在整体上有没有变好新策略是不是只在一部分 query 上有效judge 本身是不是足够稳定Pointwise 场景常用指标包括平均分全部样本分数的平均值。分数分布各分数段占的比例观察是否集中在中间值。Top K 通过率候选列表前 5 名中分数大于等于 4 的比例。Pairwise 场景常用指标包括胜率策略 A 的职位在 pairwise 比较中胜过策略 B 的比例。Elo 评分当比较多个策略时用 Elo 公式把两两胜负关系累加成策略本身的得分。排序一致性如果 judge 直接输出偏好可以用 Kendalls tau 比较 judge 排序和人工标注排序。除了均值类指标还要计算 judge 稳定性指标。可以每次评估跑两遍计算两次输出之间的一致率。一致率越高说明评估结果越可靠。常见的做法还包括随机打乱候选职位顺序再让 judge 重新打分观察顺序变化对结果的影响。指标类别指标名称计算方式用途质量平均分所有样本评分求均值快速看整体趋势质量高分率分数达到 4 或 5 的比例看高质量职位占比策略对比胜率A B 的 pair 数 / 总 pair 数比较新旧策略排序一致Kendalls tau两排名中一致对占比减不一致对占比判断 judge 排序是否合理稳定性两次一致率相同输入两次打分完全一致的比例判断 judge 是否可靠这些指标不需要一开始全部实现。先实现平均分和胜率等 judge 跑通之后再加入一致性指标和稳定性指标。3. 环境准备和最小可运行评估流程在设计好方案之后可以开始搭建一个最小评估流程。以下示例以 Python 为主模型服务使用 OpenAI 兼容接口。如果你的项目使用的是本地模型或其他云厂商模型只需要替换 base_url 和 model 参数。3.1 环境依赖和配置建议使用 Python 3.9 或更高版本。如果是在公司内部项目中先确认 Python 版本和依赖仓库仍然维护。至少需要安装以下依赖。依赖库用途openai调用 OpenAI 兼容的 LLM APIpydantic定义输出结构和解析 JSONtenacity请求重试与超时控制pandas聚合与统计指标python-dotenv读取本地环境变量安装命令如下pip install openai pydantic tenacity pandas python-dotenv在项目根目录创建一个.env文件把 API Key 和模型名写进去。注意不要把.env提交到 Git 仓库。OPENAI_API_KEYsk-your-key EVAL_MODELgpt-4o-mini EVAL_BASE_URLhttps://api.openai.com/v1如果使用本地部署的 OpenAI 兼容服务EVAL_BASE_URL 可以改成http://localhost:8000/v1。不同模型的兼容程度不同落地前先确认接口格式。3.2 项目结构一个简洁的评估项目可以按照下面的目录组织。job_search_eval/ src/ __init__.py judge.py models.py aggregator.py data/ sample_eval.jsonl config.py run_eval.pysrc/models.py 定义输入输出数据结构src/judge.py 负责构造 prompt 和调用模型src/aggregator.py 负责聚合指标run_eval.py 是整个评估流程的入口。这样分层之后替换模型服务或新增评估维度时不需要改所有模块。3.3 构造一个评估样本示例评估数据使用 JSON Lines 格式每行是一个样本。下面示例包含一个 query、用户上下文和 6 个候选职位。实际项目中候选职位来自线上日志或排序模型输出。{query: 远程 Python 开发, context: {city: 不限, remote: true, years: 3-5}, candidates: [{id: job_001, title: Python 开发工程师远程, company: 某云服务公司, location: 远程, description: 负责后端服务开发使用 Python 和 Django支持远程办公。}, {id: job_002, title: Python 开发工程师, company: 某金融科技公司, location: 上海, description: 负责交易系统开发要求熟悉 Python、MySQL需驻场。}, {id: job_003, title: 后端工程师, company: 某社交产品公司, location: 北京, description: 技术栈包括 Java、GoPython 经历加分不要求远程。}, {id: job_004, title: Python 实习生, company: 某创业公司, location: 远程, description: 处理数据清洗任务要求每周到岗至少三天远程可谈。}, {id: job_005, title: 全栈工程师, company: 某外包公司, location: 深圳, description: 使用 Python 和 Vue 开发管理系统要求驻场。}, {id: job_006, title: AI 算法工程师, company: 某研究院, location: 杭州, description: 主要做 NLP 模型训练要求博士学历可远程。}]}为了让评估更有区分度候选职位中最好同时包含明显相关、部分相关和明显不相关的情况。如果候选职位全部是高质量结果judge 无法反映排序差异。3.4 用 LLM judge 对单个样本打分先定义评估输入输出的数据结构。使用 pydantic 的好处是可以校验返回的 JSON 字段是否完整。from pydantic import BaseModel, Field from typing import Optional class Candidate(BaseModel): id: str title: str company: str location: str description: str class JudgeScore(BaseModel): score: int Field(description1-5 integer relevance score) reason: str Field(descriptionbrief explanation) class JudgeResult(BaseModel): candidate_id: str score: int reason: str接着实现 judge 模块。以下代码调用 OpenAI 兼容接口并请求模型返回 JSON 对象。import os from openai import OpenAI from dotenv import load_dotenv load_dotenv() from .models import Candidate, JudgeScore client OpenAI( api_keyos.getenv(OPENAI_API_KEY), base_urlos.getenv(EVAL_BASE_URL), ) def build_prompt(query: str, context: dict, candidate: Candidate) - str: return f 你是职位搜索排序评估员。请根据用户搜索请求和候选职位信息判断该职位与用户需求的相关性。 用户搜索请求 {query} 用户背景 {context} 候选职位 职位ID{candidate.id} 标题{candidate.title} 公司{candidate.company} 地点{candidate.location} 描述{candidate.description} 评分规则 - 5 分高度相关完全符合用户查询意图和背景要求。 - 4 分相关主要要求满足但存在一些次要偏差。 - 3 分部分相关部分条件满足可能不是最佳选择。 - 2 分弱相关用户可能需要详细查看才能决定。 - 1 分不相关不建议展示。 请只输出 JSON不要输出其它内容格式如下 {{score: 分数, reason: 简要理由}} def judge_single(query: str, context: dict, candidate: Candidate) - JudgeScore: response client.chat.completions.create( modelos.getenv(EVAL_MODEL, gpt-4o-mini), temperature0, response_format{type: json_object}, messages[ {role: system, content: 你是一个严谨的职位搜索排序评估员。}, {role: user, content: build_prompt(query, context, candidate)}, ], ) content response.choices[0].message.content parsed JudgeScore.model_validate_json(content) return parsed这个函数把单个候选职位映射成一个 JudgeScore。调用前要确认模型服务支持response_format参数如果不支持可以去掉该参数然后在解析时增加容错逻辑。3.5 批量评估并输出报告批量评估需要逐条读取 JSONL对每个候选职位调用 judge并把结果追加写入一个输出 JSONL。为了避免模型限流可以先不加并发等样本量变多之后再引入线程池。import json import time from pathlib import Path from src.judge import judge_single from src.models import Candidate, JudgeResult def run_eval(input_file: str, output_file: str): results [] with open(input_file, r, encodingutf-8) as f: for line in f: if not line.strip(): continue item json.loads(line) candidates item[candidates] for cand in candidates: candidate_obj Candidate(**cand) try: score judge_single( item[query], item.get(context, {}), candidate_obj, ) except Exception as exc: print(ffailed on {cand[id]}: {exc}) continue results.append( JudgeResult( candidate_idcand[id], scorescore.score, reasonscore.reason, ) ) time.sleep(0.2) with open(output_file, w, encodingutf-8) as f: for r in results: f.write(r.model_dump_json() \n) print(fprocess {len(results)} candidates) if __name__ __main__: run_eval(data/sample_eval.jsonl, data/judge_output.jsonl)批量输出文件只保留了每个候选职位的结果没有关联回 query 和原始排序位置。实际工程里最好把 query、候选职位 id、原始排名、judge 分数一起保存后续做指标分析时才有足够上下文。注意批量调用前先跑两三个样本确认模型返回的 JSON 能正常解析。不要一次性调用几百次之后才发现输出格式对不上。4. 关键代码和参数拆解最小流程跑通之后要回头审视 prompt 设计、模型参数和容错逻辑。这些细节决定评估稳定性。4.1 Prompt 结构为什么这么写上面的 prompt 分成了角色设定、任务目标、输入数据、评分规则和输出格式五段。每一段都有作用。角色设定让模型进入评估人员状态避免模型把自己当作用户去“回答搜索问题”。任务目标告诉模型它要评估的不是职位本身好不好而是职位与搜索请求的匹配程度。输入数据字段越完整模型判断依据越充分。评分规则给出每个分值的语义尤其要定义“部分相关”和“弱相关”的差异否则模型会把所有结果都打 3 分。输出格式里要求 JSON是为了让程序能自动解析。如果不指定输出格式模型可能会在理由中夹杂额外内容。使用response_format{type: json_object}可以让大部分模型返回合法 JSON但实际输出中仍可能缺少字段所以还需要 pydantic 校验。可以改进的 prompt 技巧是加入评分锚点示例。也就是在 prompt 中给模型看一个“查询 职位 应该给几分 为什么”的例子。Few-shot 示例会增加 token 成本但对不稳定的评分标准帮助很大。通常先不加示例跑一批如果分数分布不合理再逐步加锚点。示例 查询深圳 Java 后端 用户背景期望5年以上经验驻场 职位Java 服务端开发工程师深圳负责订单系统开发要求熟悉 Spring Boot 评分5 分理由地点、岗位和技能要求都匹配。 请参考以上示例对下面的候选职位打分。示例中的分数维度要和正式评分规则一致。不要用不同的尺度否则模型会被带偏。4.2 调用 LLM 时的参数选择和注意事项评估任务的参数选择和生成式对话任务不同。核心目标是稳定不是创造性和多样性。temperature 应该设置成 0 或非常低的值。评估任务希望相同的输入尽量得到相同输出即使牺牲一些创造性。top_p 也可以调低但许多模型在使用 temperature0 时已经足够稳定不一定需要调两个参数。max_tokens 要足够容纳 reason 和 JSON 结构。如果设置太小模型输出会被截断导致 JSON 解析失败。建议根据理由长度预留空间例如 300 到 500 tokens。timeout 和重试次数影响任务成功率。调用外部模型服务时网络抖动和限流是常态。使用 tenacity 可以自动重试但要注意重试次数过多会导致任务时间不可控。参数默认值或建议值调小的影响调大的影响temperature0 或 0.1更稳定更保守输出更多样不稳定top_p0.9 或 1.0更聚焦更多样max_tokens300 到 500可能截断 JSON增加 token 成本timeout30 到 60 秒更容易超时单请求等待过长n1成本低但无法做投票可以多次采样取众数对于评估任务推荐先设 temperature0max_tokens500timeout60。如果单模型稳定性不能满足需求再使用 n3 或重复调用多次取分数众数作为最终输出。4.3 从非结构化输出中解析评分即使设置了response_format也不能保证所有模型都返回严格 JSON。更稳妥的解析函数需要兼容多种情况文本前面有解释、JSON 嵌套在代码块里、字段顺序不同等。可以先用正则找出第一个{和最后一个}再交给 pydantic 校验。如果失败再从文本中匹配score\s*[:]\s*(\d)提取分数。import json import re from .models import JudgeScore def parse_judge_response(content: str) - JudgeScore: content content.strip() try: return JudgeScore.model_validate_json(content) except Exception: pass # 尝试提取 JSON 块 json_match re.search(r\{.*\}, content, re.DOTALL) if json_match: try: data json.loads(json_match.group()) return JudgeScore(**data) except Exception: pass # 最后尝试只提取分数 score_match re.search(rscore[\s\]*[:]\s*(\d), content) if score_match: score int(score_match.group(1)) score max(1, min(5, score)) return JudgeScore(scorescore, reasonparsed from raw text) raise ValueError(fcannot parse judge response: {content})解析函数返回的分数范围要校验。如果模型输出 7 分或 -1 分应该截断到 1 到 5 分并把异常情况记录到日志里。分数越界往往是 prompt 中评分规则描述不清导致的需要回头检查规则定义。4.4 聚合和导出指标评估结果都写入 JSONL 之后可以用 pandas 做聚合计算。下面代码计算每个候选职位的平均分、分数分布以及候选列表前 5 名的高分率。import json import pandas as pd from pathlib import Path def load_results(path: str) - pd.DataFrame: rows [] with open(path, r, encodingutf-8) as f: for line in f: if not line.strip(): continue data json.loads(line) rows.append(data) return pd.DataFrame(rows) def summarize(df: pd.DataFrame) - dict: summary {} summary[sample_count] len(df) summary[mean_score] round(df[score].mean(), 3) summary[score_distribution] df[score].value_counts().sort_index().to_dict() summary[high_score_rate] round((df[score] 4).mean(), 3) return summary if __name__ __main__: df load_results(data/judge_output.jsonl) print(summarize(df))实际业务中还要把分子和分母拆到 query 级别。比如先按 query 分组计算每个 query 的前 5 名平均分再对所有 query 求平均值。这样可以避免长候选列表对整体指标的稀释。5. 如何验证 LLM judge 本身的可靠性LLM judge 的分数只有可信才能用来指导排序策略决策。可信度可以从内部一致性和外部一致性两个方向验证。5.1 自动化一致性检查内部一致性是指 judge 在相同输入下是否给出相同或相似的判断。检查方式主要有三种。第一种是重复测试。对同一批样本运行两次 judge比较两次分数的完全一致率和相关性。完全一致率越高越好。如果分数变化很大说明模型对 prompt 或参数太敏感。第二种是位置测试。把候选列表顺序随机打乱再让 judge 评估其中某一个职位。如果同一个 job 因为位置变化而得到不同分数说明存在位置偏差。例如 job_002 在列表第 1 位时是 4 分换到第 6 位变成 3 分那么分数就不够稳定。第三种是属性扰动测试。修改与相关性无关的信息比如公司名称从“某科技公司”改成“某外包公司”看分数是否发生明显变化。注意这种扰动要控制在“不改变描述实际含义”的前提之下只验证 judge 是否被无关特征干扰。def repeat_consistency(judge_fn, query, context, candidate, times3): scores [] for _ in range(times): scores.append(judge_fn(query, context, candidate).score) # 完全一致率 same_count sum(1 for i in range(1, times) if scores[i] scores[0]) return len(scores), same_count / (times - 1) def swap_consistency(judge_fn, query, context, candidates, k2): # 将候选顺序打乱再评估排在第一位的职位 shuffled candidates[::-1] return judge_fn(query, context, shuffled[0]).score如果一致性指标不达标优先调整评分规则描述增加 anchor examples或者降低 temperature。不要直接换更大的模型因为成本增加未必带来一致性提升。5.2 与人工评估对齐外部一致性是指 judge 的评估结果和人工评估结果是否接近。这是判断 judge 是否可信的最强证据。具体做法是从评估集中抽样 200 到 500 条候选职位让人工标注员按相同评分规则打分然后计算 Cohens kappa 或二次加权 kappaQWK。kappa 会考虑随机一致率比简单一致率更严格。对于 Pairwise 评估可以计算 judge 偏好排序和人工偏好排序之间的 Kendalls tau 系数。Kendalls tau 取值范围是 -1 到 11 表示完全一致。职位搜索场景中如果 judge 与人工在 0.6 以上已经可以用于策略回归如果在 0.8 以上基本可以替代高频人工质检。指标含义参考阈值注意事项Cohens kappa两份评分之间的一致性0.4 以上可用0.6 以上较好分数类别太多会偏低QWK加权一致性惩罚大偏差0.5 以上可接受适合有序分数Kendalls tau两个排序的一致性0.5 以上可参考0.7 以上较好对长列表更稳定简单一致率完全相同的比例0.6 以上参考不惩罚接近分数但不等的情况人工标注的样本分布也要覆盖高频和长尾 query不然 kappa 容易被热门样本主导。5.3 LLM judge 常见偏差来源使用 LLM judge 一段时间后会观察到一些固定偏差。提前知道这些偏差才能设计对应的对抗样本。位置偏差排在前面的结果更容易得到高分模型倾向于认为“输出顺序代表重要性”。通过随机打乱顺序可以检测。文本长度偏差描述更长的职位更容易得到高分即使信息密度不高。有两种应对方式一是 prompt 中要求只根据信息完整度打分不看长度二是设置最大输入长度截断超长描述。机构名称偏差知名公司或大公司更容易得到高分。这个偏差在职位搜索里影响很大因为用户搜索“Java 后端”时返回字节、腾讯可能默认相关。对抗样本应该刻意加入中小公司的高相关职位观察 judge 是否给出高分。模型自我偏好大语言模型可能偏向自己生成的答案或偏向训练数据中常见的表达方式。评估排序时这个偏差影响相对小但也要注意候选中如果包含模型生成的职位描述分数可能虚高。偏差类型现象检查方式应对手段位置偏差交换顺序后分数变化swap 测试随机打乱多次取平均长度偏差长描述分数更高截断描述后对比评分规则强调信息密度机构偏差知名公司分数高替换公司名后对比增加中小公司样本格式偏好特定语言或排版得分高构造双语样本统一输入格式6. 生产环境落地建议和排错手册从实验脚本变成稳定的评估服务中间还有不少差距。下面从工程化角度给出可落地的建议。6.1 从离线评估到线上监控学习环境里跑通一个run_eval.py很容易但生产环境需要把评估流程纳入持续集成或定时任务。建议先做离线回归再逐步扩展到线上抽样。离线回归适合在排序模型每次提交代码或更新特征时运行。数据量可以固定在几百个 query评估结果作为发布门禁的一部分。如果新策略的 judge 平均分低于线上策略需要人工确认后再合入。线上监控则是在用户请求日志中抽样把真实请求中的 query 和排序结果匿名化后送入 judge统计每天的平均分和高分率。这个分数可以和人工质检指标一起看也可以和用户留存、投递率做相关性分析。生产环境还需要额外的工程保障。配置要外置化不能把模型名、API Key 写死在代码里。日志要记录每个评估样本的输入、输出、耗时和模型版本。监控要覆盖 judge 成功率、超时率、token 消耗和异常分数占比。一旦 judge 崩溃应该有回滚机制切换到历史评估结果或人工抽样。环节学习环境生产环境数据量50 到 100 个 query每天数百到数千个 query模型调用手动运行脚本服务化或定时任务配置.env 文件配置中心或环境变量日志print 或本地 jsonl结构化日志、trace id监控无成功率、延迟、成本、质量指标风险控制不需要回滚、限流、降级6.2 常见报错和排查路径LLM judge 在工程化过程中会遇到各种问题。下面整理一张排查表按现象、原因、检查方式和处理建议的顺序排列。问题现象常见原因检查方式处理建议JSON 解析失败模型未严格遵循输出格式打印原始输出看是否有代码块或额外文本使用结构化输出改进解析函数分数全部为 3 分评分规则模糊缺少区分度查看各分数分布增加评分锚点示例细化“部分相关”请求频繁超时网络不稳定或模型限流查看重试日志和响应头增加超时时间和重试次数限制并发token 消耗过高候选列表太长或并发过高统计每个样本 token 数截断描述先过滤再评估控制并发模型返回不相关理由prompt 任务目标不清晰查看 reason 字段是否偏离重新设计 system prompt两次评估结果波动大temperature 太高或 prompt 不稳定跑三次相同输入temperature 设为 0检查候选顺序影响成本超预算单次评估调用量过大统计总请求次数和 token抽样评估或先用轻量模型预筛排查顺序建议按照“输入 - 输出解析 - 模型参数 - 网络服务 - 成本控制”进行。不要一开始就怀疑模型能力先检查 prompt 是否把规则表达清楚。6.3 可复用清单以下清单可以直接用于每次评估任务启动前和发布前。评估集构造检查清单是否覆盖热门 query 和长尾 query是否覆盖不同城市、不同职位类型候选职位是否同时包含高质量、低质量和无关样本是否保存了 query、上下文、候选职位和原始排名评估集是否固定版本方便前后对比Judge 上线前检查清单是否运行过重复测试完全一致率是否达到预期是否做过位置 swap 测试排序变化是否影响结果是否抽样与人工标注对比过 kappa 或 Kendalls tau是否检查过输出 JSON 解析失败率是否设置 temperature0 和合理的超时重试生产运行检查清单是否记录模型版本、prompt 版本和评估集版本是否有结构化日志和 trace id是否监控成功率、超时率、token 成本和异常分数是否有成本上限和并发限制是否有回滚到历史评估结果的方案6.4 后续扩展方向LLM judge 评估职位搜索排序的流程跑通之后还可以继续扩展。多位 judge 投票可以减少单模型偏差。例如同时使用两个或多个不同公司的大模型对同一候选职位打分只有多数一致的分数才进入最终统计。这样能在一定程度上缓解模型自我偏好和随机波动。judge 输出也可以变成训练数据。把评分高且理由明确的“查询 - 职位”对收集起来构造排序模型的训练样本。但要注意生成数据的质量需要先经过人工抽检不能直接当作 ground truth。还可以把 judge 评估和线上行为数据做交叉验证。比如计算 judge 高分职位在用户侧的真实投递率如果高薪相关职位投递率也高说明 judge 和业务目标一致如果出现偏离需要重新校准评分规则。最后如果职位搜索产品使用多语言可以用多语言模型或同一模型的不同语言 prompt 做评估并定期检查语言间的一致性。评估体系越稳定排序模型的每一次迭代就越有据可依。职位搜索排序的评估工作本质上是在“接近用户真实评价”和“快速规模化执行”之间找平衡。LLM judge 不完美但它让离线评估从周级缩短到小时级也让评估过程可以随时复现和审计。开始使用时不妨先拿 50 个 query 和一份简单评分规则跑通全流程再逐步增加样本、维度和一致性检查。这样既能控制成本也能更快发现 judge 在具体业务场景中的偏差。
返回列表