跳至主要内容
返回博客

大模型当裁判又慢又贵?用决策模型给 Agent 评估做第一道关

查看示例

下文引用的两篇论文研究的都是 TypeSafe 的 Jev。它们的结论不能直接套到其他决策模型上。system-one.dev 提供 Jev(typesafe/jev)、Clef Flash(cloudflare/clef-flash)和 Clef(cloudflare/clef)模型服务;本站没有在托管端复现论文实验,文中数字是作者实验条件下的结果。

还不熟悉决策模型和 Choice / Score / Noul 三种原语的话,可以先读 决策模型是什么?。

LLM 裁判的三个问题:慢、贵、不稳定

用大模型当裁判(LLM-as-a-Judge)已经是评估开放式输出的常见做法,但开发者的抱怨也很集中:

  • 慢:推理模型每做一次判断都要额外花算力。卡内基梅隆大学的论文在其计时实验中测得,GPT-6 的中位延迟为 1.89 秒,Jev 为 0.15 秒(作者实验条件下)。
  • 贵:同一篇论文估算,GPT-6 以低推理强度做裁判,大约每 1000 次判断 12 美元(作者实验条件下)。每换一个模型版本或一批数据,这笔费用就要重来一次。
  • 不稳定:中文开发者的原话是:“用大模型当裁判又慢又贵,还可能同一份答案打出不同分。”(原帖)

但要注意,“稳定”不等于“对”。宾夕法尼亚大学的论文发现,在有序量表上,各个裁判彼此之间的一致程度高于它们与人工标注的一致程度,而且大多都比人工打出更低的档位(作者实验条件下)。结果一致只说明大家判得一样,不说明判得对。

决策模型做裁判:两篇论文说了什么

结论一:能从文本读出判决时,级联可行

论文《JEV-as-a-Judge: Accept When Confident, Escalate When Unsure》(arXiv 2609.26550,卡内基梅隆大学)把 Jev 和 16 个生成式裁判、奖励模型做了对比,并用盲审的人工复核来检验结论。摘要中的主要结论(均为作者实验条件下):

  • 在判决可以直接从文本读出的场景,Jev 与 GPT-6 的差距在 3 个百分点以内;费用约为 GPT-6 的 0.36%,中位延迟 0.15 秒。
  • 在判决需要推导的场景,比如数学、代码、逻辑,Jev 明显落后。正文给出的差距是 7 到 28 个百分点。
  • 先冻结阈值,置信度高的直接采纳、其余升级给 GPT-6:在 1610 个留出样本对上,准确率比单用 GPT-6 高 0.9 个百分点,费用为 GPT-6 的 41%。在两个预先设计好的新任务上做实时测试,级联的准确率与 GPT-6 完全持平。
  • 两种场景下置信度路由会失效:一是“风格误导”的样本对(写得更花哨的那个答案反而是错的),二是没有参考答案的文风评估。在后者上,所有被测裁判都接近随机水平,却都很“自信”。

结论二:但它们会“错在同一处”

论文《Jev vs. LLMs as Rubric Judges: Cheaper, Faster, and Wrong in the Same Places》(arXiv 2609.29769,宾夕法尼亚大学)把 Jev 和三个 flash 档的 LLM 裁判放在 7 个基准、9 组带人工标注的评分面板上比较。摘要中的主要结论(均为作者实验条件下):

  • 三个 LLM 裁判的费用是 Jev 的 16 到 325 倍,耗时是 28 到 350 倍。
  • 无论哪种设置,Jev 和它们的准确率在 27 组配对比较中最多只有 8 组差异显著。Jev 领先的大多是二元清单项,落后的只出现在有序量表上。
  • 在 Jev 最自信的那批错误上,约 96% 的 LLM 判决重复了同样的错误答案;如果错误彼此独立,这个比例应该只有一半左右。
  • 因此,以 Jev 为第一道关的级联主要是省钱,准确率提升有限:即使用事后挑选的最优阈值,也没有一个级联比最好的单个裁判高出 2.7 个百分点以上。

两个结论怎么一起理解

两篇论文并不矛盾,只是回答了不同的问题。第一篇问的是:用决策模型做第一道关,能不能在保住强模型准确率的同时省钱?在它测试的场景里,能。第二篇问的是:级联能不能超过最好的单个裁判?它的回答是很难,因为第二道关会重复第一道关的错误。

对开发者来说,实用的结论是:把决策模型当成省钱的第一道关,不要指望它让评估变得更准。级联能否有效,取决于第二道关是否会在不同的地方出错。

另外,Microsoft 在 Microsoft-Decision-1 的发布博客中,也把“基于评分标准给 AI 回答和 Agent 动作打分”列为该模型的用途之一(厂商说法)。

哪些评估交给 Score / Noul,哪些不要

下文的“适用边界表”整理自两篇论文的结论。每一行都是作者在特定数据集上的实验结果,只能当作判断的起点,不能当作部署保证。概括起来:

  • 适合做第一道关:普通偏好比较、回答是否有给定材料支持、二元清单项是否满足。
  • 先在本地验证:最终答案判定、多选一、有参考的摘要,以及多档的有序量表(第二篇发现各个裁判普遍比人工打分偏低)。
  • 直接升级给强模型或人工:需要推导的数学、代码、逻辑、专业知识题,以及错误答案写得更像样的“风格误导”样本。
  • 任何自动裁判都不要用:没有参考答案的文风、散文质量评估。第一篇的结论是,所有被测裁判在这类任务上都接近随机。

把 rubric 写成 Score 层级

Score 的意思是:你给出一组从低到高排列的档位描述,模型为每一档返回一个概率,再按概率加权得出一个分数。这个分数可以落在两档之间。写 rubric 时有几条要点:

  • 层级数量:System One 文档规定最多 10 个 Score 层级。开发团队示例的注释写明,托管端 Score 支持 2 到 10 个有序层级,并且只有在同一份 rubric、同一个模型、同一套说明下得出的分数才能互相比较。
  • 描述情境,不要描述程度:TypeSafe 文档建议,每一档写成一个可以对照的情境,比如“功能损坏但有替代方案”,而不是“中等严重”。每一档是被单独评估的,模型看不到档位编号和相邻档位,所以“比上一档更差”这样的写法没有意义。
  • 一个问题只量一个维度:一个问题里混了好几个维度,置信度就会下降。应该拆成几个 Score,再在代码里加权合并。
  • 同样的分数可能代表不同的分布:分数 1.0 可能是全部概率都在第 1 档,也可能是第 0 档和第 2 档各一半。做决定时要同时看 probabilities 和 confidence。
  • 偏好对比时两种顺序各问一次:第一篇论文的做法是把两个候选答案按两种顺序各评一次,再取平均。
  • 分数更适合排序和设阈值,不适合当绝对评分:Microsoft-Decision-1 文档建议,Score 优先用于相对排序和阈值判断,不要当作绝对、已校准的评分,并且要在自己的标注样本上验证。

阈值:先在本地样本上选,再在留出集上复核

第一篇论文最后给了一套可以照做的流程(作者建议):

  1. 准备本地标注:从你自己的评估任务里抽大约 100 个样本,人工标注正确答案,同时跑决策模型和你的强裁判。
  2. 用置信下界选阈值:在一组候选阈值上模拟级联,找出“准确率损失不超过两个百分点”的最宽松阈值。判断时要看损失的置信下界,不要只看点估计。论文报告,这样做可以把“实际损失超过两个百分点”的风险从约 45% 降到约 5%(作者模拟条件下)。
  3. 每类任务单独选阈值:不同评估任务的最佳阈值差别很大,混在一起选很容易出问题。
  4. 用留出数据复核:确认阈值在没参与选择的样本上依然成立。

根据第二篇论文“错在同一处”的结论,还可以多做一项检查(这是本稿的建议,论文没有给出这样的操作步骤):在选阈值用的标注样本上,统计第二道关重复第一道关错误的比例,并和“错误彼此独立”时约一半的基线比较。如果重复率远高于基线,级联就只能省钱,不能提升准确率。

还有一点:换了模型或模型版本之后,阈值必须重新验证。不同模型的 confidence 算法不同,详见 Jev 开源替代有哪些?中的说明。

TypeScript 示例:先用 Score 预筛,再交给你自己的裁判

在线试一下:AI 回答谁更好(示例结果,登录后可跑真实数据)

下面这段代码来自开发团队提供的示例(examples/decision-articles/03-score-eval-prefilter.ts)。它先给每个候选回答打一个 Score,分数达到阈值的才交给应用自己提供的裁判回调函数(例如你的 LLM 裁判),低于阈值的直接跳过。

ts/**
 * Article 3 — Use Score as a coarse eval pre-filter instead of an LLM-as-judge.
 *
 * System One exposes Score (ordered rubric → weighted number). It does not
 * expose an LLM-as-judge, eval harness, or dataset API. Those stay in your
 * application. This file scores each candidate first; only items at or above
 * your threshold are passed to an application-supplied judge callback.
 *
 * Hosted Score limits: 2–10 ordered levels. Compare scores only under the
 * same rubric, model, and instructions.
 */
import { createSystemOne, score } from '@system-one-ai/core';
import type { EvaluationClient } from '@system-one-ai/core';
import { systemOneAdapter } from '@system-one-ai/adapter-system-one';
import { createFetchTransport } from '@system-one-ai/transport-fetch';

export const helpfulness = score('How clear and actionable is this answer for the question?', [
  'Incorrect or unhelpful',
  'Slightly relevant',
  'Useful but incomplete',
  'Clear and actionable',
]);

/** Inclusive Score threshold on the 0..3 rubric above. Tune on your own labeled set. */
export const PREFILTER_MIN_SCORE = 2;

export interface EvalCandidate {
  id: string;
  question: string;
  answer: string;
}

export interface JudgeInput extends EvalCandidate {
  prefilterScore: number;
}

export function createHostedClient() {
  const apiKey = process.env.SYSTEM_ONE_API_KEY;
  const baseURL = process.env.SYSTEM_ONE_BASE_URL;
  const model = process.env.SYSTEM_ONE_MODEL;
  if (!apiKey || !baseURL || !model) {
    throw new Error('Set SYSTEM_ONE_API_KEY, SYSTEM_ONE_BASE_URL (…/v1), and SYSTEM_ONE_MODEL from GET /v1/models.');
  }
  return createSystemOne({
    apiKey, baseURL, model, adapter: systemOneAdapter,
    transport: createFetchTransport(), timeoutMs: 30_000, maxRetries: 0,
  });
}

export async function scoreCandidate(client: EvaluationClient, candidate: EvalCandidate) {
  const result = await client.evaluate({
    state: { question: candidate.question, answer: candidate.answer },
    questions: { helpfulness },
  });
  return result.answers.helpfulness.score;
}

/**
 * @param judge Application-owned expensive check. Not a System One method.
 *              Omit it to only run the Score pre-filter.
 */
export async function prefilterThenJudge(
  client: EvaluationClient,
  candidates: readonly EvalCandidate[],
  judge?: (input: JudgeInput) => Promise<unknown>,
) {
  const rows = [];
  for (const candidate of candidates) {
    const prefilterScore = await scoreCandidate(client, candidate);
    if (prefilterScore < PREFILTER_MIN_SCORE) {
      rows.push({ id: candidate.id, prefilterScore, status: 'skipped_low_score' as const });
      continue;
    }
    const judged = judge ? await judge({ ...candidate, prefilterScore }) : undefined;
    rows.push({ id: candidate.id, prefilterScore, status: 'sent_to_application_judge' as const, judged });
  }
  return rows;
}

async function main() {
  const candidates: EvalCandidate[] = [
    {
      id: 'reset-password',
      question: 'How can I reset my password?',
      answer: 'Select Forgot password on the sign-in page and follow the email link.',
    },
    {
      id: 'unrelated',
      question: 'How can I reset my password?',
      answer: 'Our office is open on weekdays.',
    },
  ];
  // No System One judge endpoint exists. Live runs stop after Score unless you pass your own judge.
  const rows = await prefilterThenJudge(createHostedClient(), candidates);
  console.log(JSON.stringify(rows, null, 2));
}

if (process.argv[1] && import.meta.url.endsWith(process.argv[1].split(/[\\/]/).pop() ?? '')) {
  main().catch((error: unknown) => {
    console.error(error instanceof Error ? error.message : error);
    process.exitCode = 1;
  });
}

需要说明几点(均出自示例注释):

  • System One 不提供 LLM 裁判、评估框架或数据集 API。 示例里的 judge 是应用自己的回调函数,不是 System One 的方法;不传 judge 时,示例只运行 Score 预筛。
  • 阈值归应用所有:PREFILTER_MIN_SCORE = 2(对应 0 到 3 的量表,含等号)要在你自己的标注集上调。
  • 托管模型:SYSTEM_ONE_MODEL 要从 GET /v1/models 获取。system-one.dev 提供 Jev(typesafe/jev)、Clef Flash(cloudflare/clef-flash)和 Clef(cloudflare/clef)模型服务。

这段示例和上面论文里的级联不是一回事。 示例按分数高低决定是否送去复核,是“低分直接淘汰、高分再细审”的预筛;论文里的级联按置信度决定是否升级,是“高置信采纳、低置信升级”。后者见下面的第二个示例。

TypeScript 示例:高置信采纳,低置信升级给大模型

下面这段代码同样来自开发团队提供的示例,文件为 examples/decision-articles/06-confident-or-escalate.ts。它用一个 Choice 判断请求该怎么处理:置信度存在且不低于阈值时,直接采用决策模型的结果;置信度偏低或缺失时,把请求、模型的初步判断和置信度一起交给应用自己的大模型处理。

ts/**
 * Article 3 — Confident: use the decision-model result.
 * Uncertain: escalate to a large LLM in the application.
 *
 * Choice/Score answers may include `confidence`. That value is an optional
 * provider statistic, not necessarily P(selected option), and not a product
 * default threshold. Missing confidence stays unknown — this file treats
 * unknown as uncertain.
 *
 * System One has no chat/completions URL. Escalation is an application
 * callback you already own. Packages: @system-one-ai/core,
 * adapter-system-one, transport-fetch. Endpoint: POST /v1/systemone.
 */
import { createSystemOne, choice } from '@system-one-ai/core';
import type { ChoiceAnswer, EvaluationClient } from '@system-one-ai/core';
import { systemOneAdapter } from '@system-one-ai/adapter-system-one';
import { createFetchTransport } from '@system-one-ai/transport-fetch';

export const routeQuestions = {
  handling: choice('Choose how this request should be handled.', {
    billing: 'Payments, invoices, and refunds',
    support: 'Technical issues with the product',
    review: 'Needs a person; the request is incomplete or consequential',
  }),
};

/** Application-owned. Tune on your own labeled cases; not a hosted default. */
export const MIN_DIRECT_CONFIDENCE = 0.6;

export function isConfident(answer: ChoiceAnswer<string>, minimum = MIN_DIRECT_CONFIDENCE) {
  if (answer.confidence === undefined) return false;
  return answer.confidence >= minimum;
}

export interface EscalationInput {
  message: string;
  proposed: string;
  confidence: number | undefined;
}

export function createHostedClient() {
  const apiKey = process.env.SYSTEM_ONE_API_KEY;
  const baseURL = process.env.SYSTEM_ONE_BASE_URL;
  const model = process.env.SYSTEM_ONE_MODEL;
  if (!apiKey || !baseURL || !model) {
    throw new Error('Set SYSTEM_ONE_API_KEY, SYSTEM_ONE_BASE_URL (…/v1), and SYSTEM_ONE_MODEL from GET /v1/models.');
  }
  return createSystemOne({
    apiKey, baseURL, model, adapter: systemOneAdapter,
    transport: createFetchTransport(), timeoutMs: 30_000, maxRetries: 0,
  });
}

/**
 * @param escalateToApplicationLlm Your existing large-LLM path.
 *        Not a System One method and not a hosted completion endpoint.
 */
export async function routeOrEscalate(
  client: EvaluationClient,
  message: string,
  escalateToApplicationLlm: (input: EscalationInput) => Promise<string>,
) {
  const result = await client.evaluate({
    state: { message },
    questions: routeQuestions,
  });
  const answer = result.answers.handling;
  if (isConfident(answer)) {
    return {
      action: 'use_decision' as const,
      handling: answer.choice,
      confidence: answer.confidence,
    };
  }
  const escalated = await escalateToApplicationLlm({
    message,
    proposed: answer.choice,
    confidence: answer.confidence,
  });
  return {
    action: 'escalate_llm' as const,
    handling: answer.choice,
    confidence: answer.confidence,
    reason: answer.confidence === undefined ? 'confidence_unknown' as const : 'confidence_below_threshold' as const,
    escalated,
  };
}

async function main() {
  const message = process.env.EXAMPLE_MESSAGE ?? 'The invoice PDF will not download.';
  const decision = await routeOrEscalate(createHostedClient(), message, async () => {
    throw new Error('No System One LLM-completion endpoint. Wire your existing large-LLM call here.');
  });
  console.log(JSON.stringify(decision, null, 2));
}

if (process.argv[1] && import.meta.url.endsWith(process.argv[1].split(/[\\/]/).pop() ?? '')) {
  main().catch((error: unknown) => {
    console.error(error instanceof Error ? error.message : error);
    process.exitCode = 1;
  });
}

需要说明几点(均出自示例代码和注释):

  • 示例场景是请求分流,不是给回答打分。 示例用 Choice 在 billing / support / review 之间做选择,演示的是“置信度门控 + 升级”的结构。把它用在评估上时,要换成你自己的判决问题(例如偏好比较的 Choice),阈值也要按上文的方法在本地标注样本上重新选。
  • 置信度是可选字段。 SDK 的 ChoiceAnswer 中 confidence 可能不存在。示例注释写明,它是服务端提供的统计量,不一定等于“选中项的概率”,也不是产品默认阈值;缺失时示例一律按“不确定”处理并升级(confidence_unknown)。System One 决策原语文档也写明,Choice/Score 统计在规范化答案中是可选的。
  • 阈值是示例值。 MIN_DIRECT_CONFIDENCE = 0.6 只是示例,示例注释写明“要在自己的标注案例上调,不是托管端默认值”。
  • 升级是应用自己的回调。 System One 没有 chat/completions 接口,escalateToApplicationLlm 是你已有的大模型调用,不是 System One 的方法。
  • 它不做论文里的“两种顺序取平均”和“用置信下界选阈值”。 这些步骤需要你在离线评估中自己实现。

表中数字均来自论文作者的实验,研究对象是 TypeSafe 的 Jev,不代表其他决策模型的表现。对照模型按论文表 2 的说明填写:凡是跑过 GPT-6 的样本都与 GPT-6 对比,自然散文类样本则与 GPT-5.4 对比。本站没有在托管端复现论文实验,文中数字是作者实验条件下的结果。

适用边界表(作者实验条件下,核验日期 2026-10-11)

评估类型研究中的表现建议
普通偏好比较(RewardBench)来源: arXiv 2609.26550 · 核验日期 2026-10-11Jev 92.5%,GPT-6 92.5%(1500 对)来源: arXiv 2609.26550 表 2 · 核验日期 2026-10-11可以作为低成本的第一道关
有依据的事实性(HaluEval QA)来源: arXiv 2609.26550 · 核验日期 2026-10-11Jev 87.3%,GPT-6 88.4%(3000 条)来源: arXiv 2609.26550 表 2 · 核验日期 2026-10-11可以作为低成本的第一道关
二元清单项(RiceChem、HealthBench)来源: arXiv 2609.29769 · 核验日期 2026-10-11Jev 领先的比较大多出现在二元清单面板上来源: arXiv 2609.29769 摘要 · 核验日期 2026-10-11可以作为第一道关,先在本地验证
最终答案判定来源: arXiv 2609.26550 · 核验日期 2026-10-11Jev 94.0%,GPT-6 96.7%(150 条,标注者信息未公开)来源: arXiv 2609.26550 表 2 · 核验日期 2026-10-11先在本地验证
有参考的摘要来源: arXiv 2609.26550 · 核验日期 2026-10-11Jev 69.8%,GPT-5.4 69.3%(400 条;按论文表 2 的说明,自然散文类样本的对照模型是 GPT-5.4)来源: arXiv 2609.26550 表 2 · 核验日期 2026-10-11先在本地验证
多档有序量表来源: arXiv 2609.29769 · 核验日期 2026-10-11Jev 落后的比较只出现在有序量表上;各裁判彼此更一致,但普遍比人工打分偏低来源: arXiv 2609.29769 摘要 · 核验日期 2026-10-11先用本地标注校准,不要直接当绝对分数用
困难正确性:数学、代码、逻辑(JudgeBench)来源: arXiv 2609.26550 · 核验日期 2026-10-11Jev 78.6%,GPT-6 93.1%(350 对)来源: arXiv 2609.26550 表 2 · 核验日期 2026-10-11升级给强模型或人工
风格误导的样本对(RM-Bench 困难组)来源: arXiv 2609.26550 · 核验日期 2026-10-11Jev 76.6%,GPT-6 90.1%(3000 次判断)来源: arXiv 2609.26550 表 2 · 核验日期 2026-10-11升级给强模型或人工
无参考答案的文风、散文质量来源: arXiv 2609.26550 · 核验日期 2026-10-11Jev 53.5%,GPT-5.4 56.0%(200 条;按论文表 2 的说明,自然散文类样本的对照模型是 GPT-5.4);所有被测裁判都接近随机来源: arXiv 2609.26550 表 2 · 核验日期 2026-10-11不要交给任何自动裁判

常见问题

决策模型能完全替代 LLM 裁判吗?
不能完全替代。在判决可以直接从文本读出的评估上,研究中的 Jev 与强模型差距不大,成本却低得多;但在数学、代码、逻辑这类需要推导的评估上明显落后,在没有参考答案的文风评估上,所有被测裁判都接近随机(作者实验条件下)。比较稳妥的用法是让它做第一道关。
“高置信采纳、低置信升级”怎么做?
先让决策模型给出判决和置信度。置信度达到阈值的直接采纳,没达到的交给强模型或人工复核。阈值要用你自己带人工标注的样本来选(论文建议约 100 个),每类任务单独选,并用留出数据复核。开发团队提供了两个示例:一个按分数预筛,一个按置信度升级(见上文)。
评分稳定是不是就说明准确?
不是。研究发现,各个裁判在有序量表上彼此之间的一致程度高于它们与人工标注的一致程度;在没有参考答案的文风评估上,被测裁判都接近随机,却都很自信(作者实验条件下)。判断准不准,只能拿人工标注来对照。
级联能省多少钱?
论文给出的数字都依赖具体的任务和模型。第一篇报告,在其 1610 个留出样本对上,级联费用为 GPT-6 的 41%;在 Jev 较弱的新任务上,节省的幅度明显变小(作者实验条件下)。你的实际节省取决于升级比例和第二道关的价格。System One 托管 API 使用预付决策积分,价格见[定价页](https://system-one.dev/zh-CN/pricing):10,000 积分 $10 起。
为什么级联没有让评估更准?
第二篇论文的解释是:级联只有在第二道关错在别处时才能超过它。实验中,LLM 裁判重复了 Jev 大多数最自信的错误,所以级联主要是省钱(作者实验条件下)。
在 TypeScript 里怎么写?
用 `@system-one-ai/core` 的 `score()` 定义 rubric,对每个候选回答调用 `client.evaluate()`,再在应用代码里按阈值决定是否交给你自己的裁判;或者用 `choice()` 拿到置信度,置信度不够时升级给你自己的大模型。两种写法的完整代码见上文示例。System One 不提供裁判或评估框架 API。