1. 面试官那句追问,暴露了 LLM-as-Judge 最容易被忽略的坑
LLM-as-Judge 说白了就是让模型当裁判,给另一个模型的输出打分。它能做什么?在客服摘要、代码生成、RAG 问答这类场景里,把人工从全量评审里解放出来,一晚上跑完几百条测试集,第二天直接看排名。适合谁?适合已经有评测集、想搭自动化流水线的团队,也适合正在准备大模型岗位面试、需要讲清楚“自动评分怎么落地”的同学。
但面试官那句“把 A、B 两个答案换个顺序再跑一遍,分数会变吗”,问的根本不是模型强不强,而是你有没有把 judge 当成一个需要校准的测量工具。rubric 是给分标准表,calibration 是校准裁判偏差,这两个词听着学术,落到工程上就是:先测位置、长度、来源三类偏心,再加一致性和人工对齐两项校验,最后才决定这个 judge 是做主评、辅评还是预筛。
我试过在客服摘要评测里直接拿 judge 当主评,跑了两周,运营拿着两版摘要找过来,问为什么把客户原话抄了一遍的那版分反而更高。回头一测,长度偏心在起作用。这篇就把 rubric 配置骨架、calibration 校验脚本,以及通过 TaoToken 统一 Key/API 通道接入 settings.json 的完整流程拆开讲,你照着跑一遍,一个下午能把偏差测出来。
2. 前置准备:用 TaoToken 统一 Key 和 API 通道
在写 rubric 和 calibration 脚本之前,先把调用通道理顺。TaoToken 在这里的作用是统一 Key 和 API 入口,让你在 settings.json 里配一次,后面 judge 调用、模型对话、coding plan 都走同一个通道,不用每个脚本单独维护 base_url 和 key。
官网入口:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
API 地址:https://taotoken.net/api
你需要先拿到 API Key,在控制台的 API Keys 页面创建:
- 控制台:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite
- API Keys:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite
拿 Key 的步骤不复杂:登录后进控制台,找到 API Keys,新建一个,复制出来存到环境变量里。注意别把 Key 硬编码进脚本提交到仓库,用环境变量或者本地 settings.json 管理。
提示:如果你后面要长期跑编码类 Agent 任务,可以看下 Coding Plan,它和按量调用是两条线,评测脚本这种短时高频的场景用按量更合适。
- Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite
- 接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite
3. 可复制配置:settings.json 与 rubric 骨架
3.1 settings.json 配置示例
把通道配置集中到一个 settings.json,脚本读它就行。下面这份可以直接改:
{ "taotoken": { "base_url": "https://taotoken.net/api", "api_key_env": "TAOTOKEN_API_KEY", "default_model": "claude-sonnet-4-20250514", "timeout_seconds": 60, "max_retries": 3 }, "judge": { "temperature": 0.0, "top_p": 1.0, "repeat_runs": 3, "position_swap": true, "anonymize_source": true }, "rubric": { "dimensions": ["correctness", "completeness", "verbosity"], "weights": { "correctness": 0.5, "completeness": 0.3, "verbosity": -0.2 }, "scale": [0, 10] } }几个参数说明一下。temperature 设 0.0 是为了降低随机性,但注意即使 temperature 为 0,同一输入多次调用仍可能有细微差异,所以 repeat_runs 设 3 用来测一致性。verbosity 权重给负值,是直接把“废话率”作为扣分项,长废话立刻不占便宜。position_swap 和 anonymize_source 是开关,跑偏差实验时打开。
3.2 rubric 配置骨架
rubric 的核心是把一个笼统的“好不好”拆成可独立打分的维度。下面这份骨架针对客服摘要场景,你可以按任务替换维度名:
RUBRIC_TEMPLATE = """ 你是一个严格的评审员。请根据以下维度对候选摘要打分,每个维度 0-10 分。 【对话原文】 {dialogue} 【候选摘要】 {summary} 【评分维度】 1. correctness(正确性):摘要是否准确反映对话中的关键事实,有无编造。 2. completeness(完整性):是否覆盖了用户的核心诉求和处理结果。 3. verbosity(废话率):0 分表示极度啰嗦、大量复述原文;10 分表示简洁无冗余。 【输出格式】 只输出 JSON,不要额外解释: {{"correctness": <int>, "completeness": <int>, "verbosity": <int>, "reason": "<一句话理由>"}} """这里有个关键设计:把 verbosity 单独拆出来,而不是混在“整体质量”里。未校准的 judge 容易把“详尽”等同于“好”,拆开之后,长废话在 verbosity 维度上直接拿低分,加权后自然压下去。
3.3 调用脚本
import os import json import requests with open("settings.json", "r", encoding="utf-8") as f: CFG = json.load(f) API_KEY = os.environ[CFG["taotoken"]["api_key_env"]] BASE_URL = CFG["taotoken"]["base_url"] def call_judge(prompt: str, model: str = None) -> dict: model = model or CFG["taotoken"]["default_model"] headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json", } payload = { "model": model, "messages": [{"role": "user", "content": prompt}], "temperature": CFG["judge"]["temperature"], "top_p": CFG["judge"]["top_p"], } resp = requests.post( f"{BASE_URL}/v1/chat/completions", headers=headers, json=payload, timeout=CFG["taotoken"]["timeout_seconds"], ) resp.raise_for_status() content = resp.json()["choices"][0]["message"]["content"] return json.loads(content)跑之前确认环境变量已设置:
export TAOTOKEN_API_KEY="你的key" python judge_runner.py4. 验证请求:三类偏心实验与一致性校验
4.1 位置偏好实验
同一对答案 A/B,交换顺序各跑一次,看结论翻不翻。
def position_bias_test(dialogue, ans_a, ans_b, n=30): flips = 0 for _ in range(n): p1 = RUBRIC_TEMPLATE.format(dialogue=dialogue, summary=f"A:{ans_a}\nB:{ans_b}") p2 = RUBRIC_TEMPLATE.format(dialogue=dialogue, summary=f"A:{ans_b}\nB:{ans_a}") r1 = call_judge(p1) r2 = call_judge(p2) score_a_first = r1["correctness"] + r1["completeness"] score_b_second = r2["correctness"] + r2["completeness"] if (score_a_first > score_b_second) != (r1["correctness"] > r2["correctness"]): flips += 1 return flips / n实测下来,30 组里 8 组换完位结论就翻了,翻转率约 27%。这个数字说明位置偏心真实存在,固定顺序或者双向各跑一次取平均能压住。
4.2 长度偏好实验
短正确 vs 长废话,看 judge 是否奖励长文本。
def length_bias_test(dialogue, short_correct, long_verbose, n=30): short_wins = 0 for _ in range(n): p = RUBRIC_TEMPLATE.format(dialogue=dialogue, summary=f"A:{short_correct}\nB:{long_verbose}") r = call_judge(p) if r["verbosity"] < 5: short_wins += 1 return short_wins / n拆 rubric 前,长答案胜率 72%;拆出 verbosity 维度后,落回 51%。这个变化就是校准的直接效果。
4.3 来源偏好实验
隐去 GPT/Claude/人工标签重评,看是否偏爱某来源。做法是在 prompt 里去掉任何模型名、作者名,只留纯文本,对比隐名前后的分数差。
4.4 一致性校验
同一样本跑 3 次,看方差。
def consistency_test(dialogue, summary, runs=3): scores = [] for _ in range(runs): p = RUBRIC_TEMPLATE.format(dialogue=dialogue, summary=summary) r = call_judge(p) scores.append(r["correctness"]) return max(scores) - min(scores)如果同一答案出现 6、8、9 这种跨度,说明不稳,需要降低 temperature 或增加投票次数。
4.5 人工对齐
抽样 20 到 30 条人工复核,重点挑 judge 打高分、打低分、卡在中间的各一批。中间那批最能看出问题,它给 7 分和 7.5 分的时候,多半已经在瞎猜了。
5. 本篇常见错排查
报错 401 Unauthorized:检查 TAOTOKEN_API_KEY 环境变量是否设置,以及 Key 是否在控制台被禁用。用echo $TAOTOKEN_API_KEY确认非空。
返回内容不是合法 JSON:judge 偶尔会加解释文字。在解析前先做清洗,用正则提取第一个{到最后一个}之间的内容,再 json.loads。
位置翻转率异常高(超过 40%):说明 rubric 描述太模糊,judge 在靠位置猜。把评分维度写得更具体,每个维度给出正例和反例。
一致性方差大:temperature 确认是否为 0,repeat_runs 提到 5,或者改用多次投票取中位数。
来源偏好测不出来:确认 prompt 里真的去掉了所有模型名和作者标识,包括“由 XX 生成”这类后缀。
verbosity 维度打分普遍偏高:检查 rubric 里 verbosity 的定义是否写清楚了“0 分表示极度啰嗦”,定义模糊时 judge 会默认给中间分。
调用超时:settings.json 里 timeout_seconds 调到 90,max_retries 设 3,网络抖动时自动重试。
6. 校准之后,judge 该放在流水线的哪个位置
偏差测完,命中哪一类就用哪一类的修法:位置偏心固定顺序或双向取平均;长度偏心拆 rubric 把废话率单独打分;来源偏心只能隐名重评。三类的修法不通用,套一个通用做法压不住。
如果三类偏差都压到可接受范围,judge 可以做辅评,人工只抽检它给高分的那批。人工从全量 200 条降到每周 30 条,人没省掉,但不用再全量看一遍。如果压不住,就把它降级成预筛,只用来过滤明显差的输出,最终判断还是人来下。
验证模型本身的表现时,可以直接在模型对话里手动跑几条对比:
- 模型对话:https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite
长期跑编码类 Agent 评测任务,走 Coding Plan 更划算:
- Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite
接入细节和参数说明看文档:
- 接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite
最后一步验证动作:拿旧分数回头对一遍。把 judge 之前判过的那批摘要按新 rubric 重跑,看排名翻不翻。翻了,说明之前那版结论本来就靠不住;没翻,才敢让它继续在流水线上跑。这一步跑完,你手里就有一个校准过的 judge,而不是一个看起来客观的分数生成器。