尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

调用 LLM judge 前先设锚,TaoToken 复现 GAUGE 结论

调用 LLM judge 前先设锚,TaoToken 复现 GAUGE 结论 1. 我为什么在 judge 的 prompt 前面加了一段任务锚点先说这次踩到的坑。我把一条任务型智能体的评测链路接了 LLM judge跑完 200 条样本满意率 92%看着很漂亮人工抽检 30 条真正把用户原始诉求办完的只有 11 条。问题不在 judge 的模型能力而在 judge 的输入里根本没有什么算办完这个判据——它看到的只是几轮礼貌的对话用户说了谢谢好的它就打了高分。后面我把这条链路的接入换到 TaoTokenhttps://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentgauge_judge_anchorBase URL 固定填https://taotoken.net/apiKey 用控制台生成的字符串替换占位符YOUR_API_KEY然后把任务锚点塞进 judge 的 system prompt重跑同样的 200 条样本满意率掉到 61%但和人工标注的一致率从 38% 拉到 84%。这篇就把这套先设锚、再调 judge的做法完整拆开包括三组对照实验的设计、可复制的客户端配置、以及 Token 消耗怎么核算——全部在本地跑不碰任何线上业务库。为什么要专门讲这件事因为主流的智能体评测流水线里LLM 模拟用户 LLM judge 已经被当成默认的自动评估关卡很多人默认它是可信的。Amazon 的 GAUGE 工作恰好把这条默认假设打穿了它系统性地检查了这个关卡在什么条件下会给出误导性结论。标题里那句何时不能信任 LLM judge说的不是 judge 模型不行而是评估任务的锚点没定义清楚时judge 会稳定地给出错误但一致的答案。本文按评测研究员的视角把这个结论翻译成可复现的实验步骤。2. GAUGE 的两个关键数字以及它们对流水线的含义GAUGE 的核心设定是让 LLM 扮演用户和多轮任务型智能体对话再由另一个 LLM 当评委判断这次交互算不算成功。这条链路很诱人——不需要真人标注成本低、可扩展、还能顺便生成训练奖励信号。论文给出的两组观测值得逐字理解。第一组是满意度与任务完成度的脱钩。在论文报告的样本里被评为用户满意的对话中有 57.5% 实际上并没有完成客户原本的任务。翻译成工程语言如果你的奖励信号来自用户满意吗这一问那么你大概有超过一半的正样本是噪声而且这些噪声不是随机分布的——它们集中在智能体回复礼貌、解释充分、但没真正执行动作的情形上。用这种信号做 SFT 或 RL模型会学到一个很稳定的策略讨好用户比完成任务更划算。第二组是配对比较中的方向翻转。当把两个能力接近的智能体放在一起做 A/B让 judge 选哪个更好有 31% 的配对里 judge 选出来的是奖励更低的那一方。而当两个智能体能力差距明显时这个翻转比例降到了不足 1%。这说明 judge 的判别力并不是均匀衰减的它有一个分辨率下限差距大于某个阈值时它很准差距小于阈值时它的判断接近抛硬币甚至会系统性地偏向某种风格。对评测研究员来说这两组数字直接推导出三条实践约束不要用单一满意度打分当奖励。至少要把任务是否完成和用户是否满意拆成两个独立维度分别采集。不要用 LLM judge 去区分两个能力相近的检查点。如果你在做模型迭代前后两版的差距本来就小这时候 judge 的配对结论不构成证据需要换成人可验证的判据。锚点必须显式写进 judge 的输入。judge 不是不会判断而是它需要知道完成的定义是什么、可验证的凭据是什么。第 3 条是本文的主线把锚点做成 judge 调用前的固定结构而不是靠你是一个公正的评委这种空话去暗示。3. 把锚点落到配置里TaoToken Key 与三类客户端接入在动手跑对照实验之前先把调用链路打通。所有 judge 调用都会走同一个 Base URLhttps://taotoken.net/apiKey 在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentgauge_key_setup 拿。下面三套配置按客户端分开写互相之间不要混用环境变量前缀——尤其别把ANTHROPIC_*抄进 Codex 的配置里那是两套完全不同的读取逻辑。3.1 Claude Codesettings.json 写法Claude Code 读的是~/.claude/settings.json走ANTHROPIC_*前缀。把下面这段里的 Key 换成你自己的模型 ID 按你控制台里可用的填{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_AUTH_TOKEN: YOUR_API_KEY, ANTHROPIC_MODEL: YOUR_MODEL_ID } }改完重启会话让配置重新加载。验证方式很简单随便问一句观察是否正常返回如果返回 401八成是 Key 复制时带了空格或者AUTH_TOKEN和API_KEY两个字段写混了。3.2 Codexconfig.toml 写法Codex 用 TOML走自定义 provider 的写法不认ANTHROPIC_*model YOUR_MODEL_ID model_provider taotoken [model_providers.taotoken] name taotoken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY对应的环境变量在 shell 里导出export TAOTOKEN_API_KEYYOUR_API_KEY注意env_key填的是环境变量的名字不是 Key 本身。这是 Codex 配置里最常见的错误把 Key 直接写进env_key结果客户端去读一个叫sk-xxxx的环境变量自然读不到。3.3 CC Switch三件套 profile 一起维护如果你在同一台机器上同时用多个 CLI 客户端手动改配置文件很容易串。CC Switch 这类切换器的作用就是把不同客户端的供应商信息存成 profile切的时候一次生效。三件套建议这样组织profiles: - name: cc-taotoken kind: claude_code base_url: https://taotoken.net/api api_key: YOUR_API_KEY - name: codex-taotoken kind: codex base_url: https://taotoken.net/api api_key: YOUR_API_KEY - name: cli-generic-taotoken kind: openai_compatible base_url: https://taotoken.net/api api_key: YOUR_API_KEY三个 profile 共用同一个 Key 和同一个 Base URL区别只在kind这样切换时不会出现Claude Code 能用、Codex 报 404的情况。切换完记得清一次客户端的本地缓存/会话否则旧配置可能还在内存里。3.4 脚本侧Python 直连对照实验的批量调用我走 Python用 OpenAI 兼容风格import os from openai import OpenAI client OpenAI( api_keyos.environ[TAOTOKEN_API_KEY], base_urlhttps://taotoken.net/api, ) def ask_judge(system_prompt: str, user_content: str) - dict: resp client.chat.completions.create( modelos.environ.get(JUDGE_MODEL_ID, YOUR_MODEL_ID), messages[ {role: system, content: system_prompt}, {role: user, content: user_content}, ], temperature0, ) return { text: resp.choices[0].message.content, prompt_tokens: resp.usage.prompt_tokens, completion_tokens: resp.usage.completion_tokens, }temperature0是为了让三组对照之间只有 prompt 这一个变量在动。如果你觉得单次采样不稳定可以跑 3 次取多数但那样 Token 消耗要乘 3后面核算时要算进去。Key 的创建入口在 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentgauge_api_keys 建议给评测任务单独建一个 Key方便按项目看用量。4. 锚点对照实验三组 judge prompt 的可复现设计实验目标很明确在同一批对话记录上只改变 judge 拿到的锚点信息量观察判定结果与人工标注的一致率如何变化。样本来自你自己业务里已经跑过的任务型对话脱敏后存在本地 CSV字段建议是dialogue_id、turns对话文本、user_goal用户原始诉求、ground_truth_done人工标注是否完成0/1。三组 prompt 的差异如下A 组无锚点对照组你是一个客服质量评估员。请阅读以下对话记录判断用户对这次服务是否满意。 只输出一个 0 到 1 之间的分数。这是很多流水线的默认写法。它问的是满意不是完成而且没有任何任务定义。B 组弱锚点加目标不加判据你是一个客服质量评估员。用户的原始诉求是 {{user_goal}} 请阅读以下对话记录判断用户这次的任务是否已经完成。 只输出一个 0 到 1 之间的分数。比 A 组多了一个用户到底想要什么。但仍然没说完成的标准。C 组强锚点目标 可验证条件 凭据你是一个任务完成度评估员。请严格按下面的判据评估不要参考对话中的礼貌用语。 【用户原始诉求】 {{user_goal}} 【完成判据】全部满足才算完成 1. 诉求中提到的每一项操作是否都有明确的成功确认 2. 是否有可核验的凭据订单号、流水号、状态变更、回执内容 3. 对话结束时是否还有未闭环的待办 【对话记录】 {{turns}} 请输出 JSON {done: 0 或 1, missing: [未满足的判据编号], evidence: 支撑结论的关键语句}C 组的关键变化有三点把问题从满意改成完成、把判据拆成可逐条核对的清单、要求给出证据片段。第三点尤其重要——它让 judge 的判断变得可审计你能一眼看出它是依据哪句话下的结论而不是接受一个黑箱分数。跑的时候建议这样组织import csv, json from concurrent.futures import ThreadPoolExecutor PROMPTS {A: A_TMPL, B: B_TMPL, C: C_TMPL} rows list(csv.DictReader(open(samples.csv, encodingutf-8))) def run_one(group: str, row: dict) - dict: sys_p PROMPTS[group].replace({{user_goal}}, row[user_goal]) user_p row[turns] out ask_judge(sys_p, user_p) return { group: group, dialogue_id: row[dialogue_id], raw: out[text], prompt_tokens: out[prompt_tokens], completion_tokens: out[completion_tokens], } with ThreadPoolExecutor(max_workers4) as ex: results list(ex.map(lambda r: run_one(C, r), rows))max_workers不要开太大评测任务经常是几百条起并发过高容易触发限流反而拖慢整体。建议从 4 开始试稳定后再往上调。结果汇总时把 A/B/C 三组的判定和ground_truth_done对齐算一致率、混淆矩阵、以及判为满意但实际未完成的比例。按 GAUGE 的观察A 组这个比例大概率会明显偏高C 组应该会收敛代价是 prompt 变长、Token 上升。这个代价就是第 5 节要算的账。5. Token 消耗核算把 judge 的成本摊开看锚点不是免费的。C 组 prompt 比 A 组多出用户目标、判据清单、输出格式要求输入 Token 会明显增加。评测项目的预算通常按每条样本 × 每个 judge 调用来估所以必须把这块量出来而不是拍脑袋。最直接的量法是记录每次调用的usage落到本地文件import csv def append_usage(path: str, rec: dict) - None: with open(path, a, newline, encodingutf-8) as f: writer csv.DictWriter( f, fieldnames[group, dialogue_id, prompt_tokens, completion_tokens, raw_len], ) writer.writerow({ group: rec[group], dialogue_id: rec[dialogue_id], prompt_tokens: rec[prompt_tokens], completion_tokens: rec[completion_tokens], raw_len: len(rec[raw]), })然后本地聚合看三组各自的平均输入长度import pandas as pd df pd.read_csv(usage.csv) print(df.groupby(group)[[prompt_tokens, completion_tokens]].mean()) print(df.groupby(group)[dialogue_id].count())按经验锚点带来的输入增量主要来自三部分判据清单固定长度可缓存、对话记录随轮数增长、输出格式说明固定长度。如果你的样本量在几百到几千条之间固定部分占总量的比例会很小真正影响成本的是对话轮数分布——长尾对话可能一条就顶十条。几个压成本的实操点固定部分走 prompt 缓存。如果服务端支持缓存把判据和格式说明放在 prompt 最前面且内容完全一致能显著降低重复部分的计费。分档评估。先用 C 组全量跑一遍再把 A 组只跑在 C 组判定为完成的子集上验证两者的重叠度。这样既省 Token又保留了对照的结论力。按项目分 Key。评测、线上推理、实验脚本用不同的 Key用量面板里才能一眼分清哪部分是评测开销。Key 管理在 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentgauge_key_split 。别用 judge 的输出长度衡量质量。要求输出 JSON 时completion_tokens会明显低于自由文本但信息密度更高判断也更稳定。算清楚这几点之后你会发现锚点带来的增量成本是可接受的而它换来的是奖励信号从过半噪声变成基本可用。这笔账在评测项目里非常划算。6. 排查清单judge 分数异常的七个信号跑对照实验的过程中我整理了一份自查清单。只要出现下面任一信号先怀疑锚点定义而不是先怀疑模型。满意率高但人工抽检完成率低。这是 GAUGE 那 57.5% 在你自己数据上的表现形式说明 judge 在评礼貌度而不是完成度。两个相邻检查点的配对结论反复横跳。换一次随机种子结论就翻转说明差距低于 judge 的分辨率下限这时候的配对结果不能当证据。judge 输出的理由里出现了对话中不存在的凭据。这是典型的锚点缺失——它需要给结论找依据找不到就编一个。所有样本的分数都挤在同一个区间。通常是判据写得太抽象比如整体服务质量如何judge 没有可操作的区分点。同一条对话换个措辞重跑结论变化很大。把输出约束成结构化 JSON 并要求引用证据片段一般能收敛。长对话的判定准确率明显低于短对话。检查是否把关键凭据截断了judge 看到的是残缺的对话记录。成本曲线和样本量不成比例地陡增。多半是并发过高触发了重试或者对话记录里带了大量无关上下文。前者调max_workers后者在本地做一次对话裁剪只保留和任务相关的轮次。这七条都能在你自己的数据上验证不需要依赖任何外部标注集。跑完一轮你对什么时候能信这个 judge会有很具体的体感而不是停留在文献结论上。7. 小结锚点是评测流水线里最便宜的保险回到开头那组数字。GAUGE 给出的两个观测——满意与完成脱钩、能力相近时配对结论会翻转——本质上都指向同一件事评估关卡的可信度取决于你给它的判据有多明确。judge 模型本身不需要换需要换的是调用它之前那段输入的组织方式。这套做法的落地成本很低一份判据清单、一段结构化输出要求、一张本地 usage 表加起来不到半天工作量。但它能把你评测流水线里最大的一个噪声源按下去。想先看看不同模型在同类任务上的判定风格差异可以从模型对话入口 https://taotoken.net/models/detail/chat?utm_sourcetaotoken_aicg_blog_endutm_contentgauge_model_chat 直接试评测任务量大、想按项目切分用量看 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentgauge_coding_plan Key 就在这里建 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentgauge_create_key 如果你用 Claude Code 跑评测脚本客户端侧配置说明在 https://taotoken.net/doc/ClaudeCodeAnthropic?utm_sourcetaotoken_aicg_blog_endutm_contentgauge_cc_docs 。Base URL 统一填https://taotoken.net/apiKey 用占位符YOUR_API_KEY替换掉再跑。
返回列表