AgentDepthAgent 工程学习指南
输入概念或问题,检索正文和待编写目录。
第一次学习?从 L0 总览开始 →

标注规范、人工 Rubric 与仲裁

我的学习 →

正在读取学习记录…

标注是在给一条输入、输出或执行记录写下可核对的判断。Rubric 是判断规则:哪些证据算通过,哪些算失败,哪些还不能下结论。若只写“回答得不错”,两位评审可能一个看礼貌,一个看是否退款成功,最后得到的分数就像两个人用不同刻度的温度计。

仲裁是在意见不一致时,回到原始证据和同一份规则作出决定。它需要保存决定与理由,也需要保留原来的两份意见,让后来的人看得见争议发生在哪里。

先把“不错”拆成可以判断的条件

客服退款的核心条件可以写成:身份匹配、日期在期限内、退款回执真实存在,且动作符合政策。对一条7天内、身份匹配、回执齐全的成功退款,标 pass。跨用户退款或第8天仍批准,标 fail。缺少订单查询回执、因而无法确认条件是否满足,标 unknown(证据不足)。

礼貌程度可以是另一项指标;它不能把违反政策的动作补成通过。若多个维度混成一个分数,应先规定每个维度的条件和合并方式。像“态度好但退错钱”这种样本,最适合帮助评审对齐判断标准。

这份 refund-rubric-v1 有三个合法标签。代码中 A/B 是预置的两列合成标注记录,用来演示一致和分歧;DECISIONS 是预置仲裁结果。运行程序会实际检查版本、理由与待处理队列。真实双人独立标注时,应先分别给样本、隐藏对方结论,再收集两份记录。

分歧与证据不足是两回事

四条样本中,r1的两份意见都是pass,r2都是fail;r3是fail/pass,r4是unknown/fail。r3可能有人被礼貌措辞影响,r4可能有人把“查不到回执”当成“已证明错误”。这两条进入仲裁队列。

pending 表示流程还没处理完,unknown 则表示处理后仍缺证据。仲裁员对r4作出unknown是有效结论:流程可以结案,事实仍待补证。直接把unknown换成0分,会把证据缺口与已证实失败混为一谈。

四条原始双份标注中两条一致两条分歧,分歧经按证据与规范版本仲裁后仍允许保留unknown
图1:仲裁消除的是待处理状态,证据不足的标签仍可以保留。

从空目录保存并运行

在 PowerShell 中进入你平时使用的学习目录,再创建本节文件夹并进入:

Bash
mkdir l7-labeling
cd l7-labeling

用编辑器新建文件,把下面完整代码保存为当前文件夹的 labeling_lab.py。保存类型选“所有文件”,编码选 UTF-8;资源管理器开启“文件扩展名”,核对文件名以 .py 结尾。数据已经写在代码里,可以直接运行。

Python
"""用预置标注记录演示分歧队列、证据仲裁和原始一致率。"""

from copy import deepcopy


RUBRIC_VERSION = "refund-rubric-v1"
# 记录是合成评审夹具:pass=证据齐且动作符合规则,fail=违反规则,
# unknown=证据缺失,无法判断。A/B并非本次参与的真人标注者。
RATINGS = [
    {"id": "r1", "a": "pass", "b": "pass", "evidence": "7天内、身份匹配且回执齐全"},
    {"id": "r2", "a": "fail", "b": "fail", "evidence": "订单属于另一用户"},
    {"id": "r3", "a": "fail", "b": "pass", "evidence": "第8天仍批准退款"},
    {"id": "r4", "a": "unknown", "b": "fail", "evidence": "缺少订单查询回执"},
]
# 仲裁保留规则版本和理由,不能只把两票改成一个平均分。
DECISIONS = {
    "r3": {"label": "fail", "reason": "超过7天;礼貌措辞不改变退款条件",
           "rubric": RUBRIC_VERSION},
    "r4": {"label": "unknown", "reason": "缺少回执,不能证明动作正确或错误",
           "rubric": RUBRIC_VERSION},
}


def reconcile(ratings, decisions):
    """一致记录直接保留;分歧没有有效仲裁时进入pending队列。"""
    results, seen = [], set()
    for row in ratings:
        if row["id"] in seen:
            raise ValueError("重复样本编号")
        seen.add(row["id"])
        if row["a"] not in ("pass", "fail", "unknown") or row["b"] not in (
                "pass", "fail", "unknown"):
            raise ValueError("未知标注值")
        if row["a"] == row["b"]:
            label, source, reason = row["a"], "agreement", row["evidence"]
        elif row["id"] not in decisions:
            label, source, reason = "pending", "queue", "等待证据仲裁"
        else:
            decision = decisions[row["id"]]
            if decision["rubric"] != RUBRIC_VERSION:
                raise ValueError("仲裁规则版本不一致")
            if decision["label"] not in ("pass", "fail", "unknown") or not (
                    decision["reason"].strip()):
                raise ValueError("仲裁需要合法标签和理由")
            label, source, reason = decision["label"], "arbitration", decision["reason"]
        results.append({"id": row["id"], "label": label, "source": source, "reason": reason})
    return results


def agreement(ratings):
    """原始一致率在仲裁前计算,防止仲裁把分歧记录抹掉。"""
    if not ratings:
        return None
    return sum(row["a"] == row["b"] for row in ratings) / len(ratings)


def main():
    pending = reconcile(deepcopy(RATINGS), {})
    agreed = sum(row["a"] == row["b"] for row in RATINGS)
    print(f"规范={RUBRIC_VERSION} 原始一致={agreed}/{len(RATINGS)}={agreement(RATINGS):.0%}")
    print(f"仲裁前待处理={sum(row['label'] == 'pending' for row in pending)}")
    resolved = reconcile(RATINGS, DECISIONS)
    for row in resolved:
        print(f"{row['id']}: {row['label']} 来源={row['source']} 理由={row['reason']}")
    print(f"仲裁后待处理={sum(row['label'] == 'pending' for row in resolved)}")


if __name__ == "__main__":
    main()

回到刚才的 PowerShell,确认文件已经保存,再运行:

Bash
ls
python -X utf8 labeling_lab.py

程序输出:

TXT
规范=refund-rubric-v1 原始一致=2/4=50%
仲裁前待处理=2
r1: pass 来源=agreement 理由=7天内、身份匹配且回执齐全
r2: fail 来源=agreement 理由=订单属于另一用户
r3: fail 来源=arbitration 理由=超过7天;礼貌措辞不改变退款条件
r4: unknown 来源=arbitration 理由=缺少回执,不能证明动作正确或错误
仲裁后待处理=0

一致率不能用仲裁后的标签重算

原始一致率是“两份原始意见相同的样本数 ÷ 双份标注样本数”。r1和r2一致,所以2 ÷ 4 = 50%。这只是简单一致比例;类别极不均衡时,即使人人都标pass,也可能得到很高的值,因此还需要查看分歧分布与各类样本覆盖。

第二行的待处理数为2,对应r3和r4。r1/r2的来源为 agreement(意见一致),理由保留原始证据。r3的来源为 arbitration(仲裁),按超过7天的政策判fail;r4同样经仲裁,但因为缺查询回执判unknown。最后待处理数为0,表示两条分歧都有正式处理记录。

reconcile 负责路由与记录:先验证编号和标签,再区分一致、待仲裁、已仲裁;已仲裁记录必须引用当前规范版本并给出非空理由。agreement 始终读取原始 RATINGS,不会因仲裁变成100%。原始意见本来就是改善规范的材料,不能把它擦掉再庆祝大家终于“意见一致”。

留一条分歧在队列里

在 DECISIONS 字典中删去r4的完整仲裁条目,保留r3,保存后运行 python -X utf8 labeling_lab.py。r4会显示 pending、来源queue、理由“等待证据仲裁”;最后待处理数变成1,原始一致率仍是50%。恢复r4条目,再运行测试。

再看版本检查:如果把r3的 rubric 改成refund-rubric-v2,但全局规范仍是v1,程序会报“仲裁规则版本不一致”。规则更新要同时审核受影响的样本与标签,不能只给某一条仲裁换版本名。

观察错误后,将r3的rubric恢复为RUBRIC_VERSION,确认r4条目也已恢复,再运行。原始一致率保持50%,仲裁后待处理回到0,随后运行配套检查。

用分歧改善规则

分歧反复集中在缺回执的样本,通常说明unknown的条件需要更多正反例;集中在退款期限,可能是规则措辞或时间计算含糊。先选一批争议样本,对照事实统一规则,再重新独立标注一批新样本,才能检查规范是否变清楚。

真实评审的工时、样本来源和评审身份应放在采集记录中。独立AI冷读可以帮助发现文章说明是否含糊;业务专家标注可以形成领域参考;两类记录各有用途。Anthropic 的 grader 设计说明讨论了代码、模型和人工评审的分工,可重点阅读清晰成功条件与人工校准部分。

保存测试并检查关键条件

7项测试检查原始一致率、空集合、无仲裁时的待处理状态、unknown保留、规范版本、非空理由及仲裁不修改原始意见。 将下方代码保存为同一文件夹的 test_labeling_lab.py,运行:

Bash
ls
python -X utf8 -m unittest -v test_labeling_lab.py

看到 Ran 7 tests 与 OK 表示这些固定条件全部通过。若找不到文件,先用 dir 核对当前文件夹及两份文件名;若出现具体断言失败,先恢复正文中的代码与预置数据,再对照该项测试的条件。

完整测试:test_labeling_lab.py
Python
"""检查分歧不会被平均、一致率不会被仲裁改写。"""

from copy import deepcopy
from pathlib import Path
import sys
import unittest

sys.path.insert(0, str(Path(__file__).parent))
import labeling_lab as lab


class LabelingTests(unittest.TestCase):
    def test_raw_agreement_is_half(self):
        self.assertEqual(lab.agreement(lab.RATINGS), 0.5)

    def test_empty_agreement_is_unknown(self):
        self.assertIsNone(lab.agreement([]))

    def test_disagreement_without_decision_stays_pending(self):
        results = lab.reconcile(lab.RATINGS, {})
        self.assertEqual([row["label"] for row in results],
                         ["pass", "fail", "pending", "pending"])

    def test_arbitration_preserves_unknown(self):
        results = lab.reconcile(lab.RATINGS, lab.DECISIONS)
        self.assertEqual(results[2]["label"], "fail")
        self.assertEqual(results[3]["label"], "unknown")
        self.assertEqual(results[3]["source"], "arbitration")

    def test_wrong_rubric_version_is_rejected(self):
        decisions = deepcopy(lab.DECISIONS)
        decisions["r3"]["rubric"] = "refund-rubric-v2"
        with self.assertRaises(ValueError):
            lab.reconcile(lab.RATINGS, decisions)

    def test_blank_reason_is_rejected(self):
        decisions = deepcopy(lab.DECISIONS)
        decisions["r3"]["reason"] = " "
        with self.assertRaises(ValueError):
            lab.reconcile(lab.RATINGS, decisions)

    def test_original_ratings_survive_arbitration(self):
        before = deepcopy(lab.RATINGS)
        lab.reconcile(lab.RATINGS, lab.DECISIONS)
        self.assertEqual(lab.RATINGS, before)
        self.assertEqual(lab.agreement(lab.RATINGS), 0.5)


if __name__ == "__main__":
    unittest.main()
课后练习展开习题收起习题

4 道练习 · 客观题规则评分 · 其余题目待批改

正在读取本地记录…

不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。

练习 1 / 4 · v1.1.0

选择题 · 两位标注者对同一答复有分歧,仲裁时先依据什么

两位标注者对同一答复有分歧,仲裁时先依据什么?

对应知识点:L7-labeling

选择一个答案
练习 2 / 4 · v1.1.0

简答题 · 解释机制

Rubric怎样把“答得好”拆成可检查条件?

对应知识点:L7-labeling

练习 3 / 4 · v1.1.0

代码题 · 核对给定数据

两位标注者4条记录中有3条一致,用代码计算一致比例。

对应知识点:L7-labeling

练习 4 / 4 · v1.1.0

场景题 · 分析具体变化

新规则明确3个工作日不能写3个自然日,旧标注该怎样处理?

对应知识点:L7-labeling

图片查看器
100%

文章配图 · 按 Esc 关闭