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

Golden Set、真实样本与版本

我的学习 →

正在读取学习记录…

Golden Set 是一组经过核对、可以反复使用的评测样本。每条样本至少包含输入、参考结果和判断规则。客服退款助手换了提示词后,拿同一批问题重新跑,才能知道它修好了什么,又弄坏了什么。

它像裁缝桌上的尺子:衣服可以改,尺子的刻度不能跟着改。若第一次用“7天内可退”,第二次悄悄改成“14天内可退”,分数变化就混入了政策变化,已经无法单独解释助手的进步。

从一条正常退款开始

预先约定一条业务规则:收到货后0至7天可退款,超过7天拒绝;天数缺失、为负数或类型错误则转人工。用户在第3天申请,参考结果是 approve(批准);第8天申请,参考结果是 reject(拒绝)。manual 表示转人工确认,仍是一种预先定义的合理结果。

“黄金”强调标签可靠,不代表样本全部简单,也不代表参考结果永远正确。订单事实、政策或任务定义发生变化时,要重新核对样本,产生新版本。实际业务样本通常来自脱敏后的工单与失败记录,还应保存采样渠道、时间窗口和授权范围;合成样本则保存生成规则,便于补边界。

下面六条输入都是代码中预置的合成数据。它们用于看清评测流程。四条开发样本覆盖第3、7、8天和缺失日期;两条留出样本覆盖第6天和负数。留出集(holdout)是一组留到选定方案后才查看的样本,相当于练习后做一张没有用来改答案的卷子。

后面的dev是development的缩写,表示开发集;baseline表示作为对照的旧实现,candidate表示待检查的新实现。它们是同一份样本上不同运行的标签,程序版本改变时不改参考标签。

语料身份与数据分区

id 是样本编号,便于失败时定位;group 是问题家族,用于把同一原始问题及其变体留在一个分区;split 是开发集或留出集;days 是退款输入;expected 是预先核定的参考结果。

开发集可以帮助调规则,留出集用来检查这项改动在另一批样本上是否还成立。同一订单的原文和改写分到两边,会让两组信息互相泄漏。代码按 group 检查跨分区问题;它不能自动识别两段自然语言是否实为同一问题,所以分组还需要采样环节维护。

语料版本给人阅读,内容摘要给程序核对。摘要来自整份规则与样本的稳定编码:更改标签、输入、分区或版本字符串,都会产生另一份摘要。可以把摘要理解为这份资料的内容指纹,核对它就能发现评测资料是否换过。

记录项含义同一份评测资料再跑一次时
语料版本,例如refund-golden-v1给这份规则与样本起的名字保持相同
内容摘要,例如ffb5cc87cf7b按代码中的完整资料计算出的指纹保持相同
每次评测的运行编号与时间区分哪一次执行,并找到它的结果为新的执行单独记录

同一套题今天跑一遍、明天再跑一遍,题目身份保持相同。保存结果时,另记每次运行的编号、时间与被测程序版本,就能区分两份记录。例如,一份结果记录可以按“运行编号、时间、程序版本、语料版本、内容摘要、分区、通过数、失败样本”这几个字段保存,语料摘要仍只根据评测资料计算。

若把第7天的参考标签改了,内容摘要会变化,应复核标签并发布新语料版本;若只把被测程序的退款上限从6天修成7天,资料身份保持相同,变化的是程序版本与它得到的分数。

冻结退款语料后分成四条开发样本和两条留出样本,两个规则版本分别比较同分区结果
图1:分区和语料身份固定后,两个规则版本才有共同的比较尺子。

从空目录保存并运行

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

Bash
mkdir l7-golden
cd l7-golden

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

Python
"""冻结退款评测集,分别比较开发集和留出集的规则版本。"""

import hashlib
import json


# 全部样本均为预置合成数据;同一问题家族不能跨开发集和留出集。
DATASET = {
    "version": "refund-golden-v1",
    "policy": "收到货后0至7天可退款,缺失或非法天数转人工",
    "cases": [
        {"id": "g1", "group": "普通退货", "split": "dev", "days": 3, "expected": "approve"},
        {"id": "g2", "group": "最后一天", "split": "dev", "days": 7, "expected": "approve"},
        {"id": "g3", "group": "超过期限", "split": "dev", "days": 8, "expected": "reject"},
        {"id": "g4", "group": "缺失日期", "split": "dev", "days": None, "expected": "manual"},
        {"id": "h1", "group": "期限内另一日期", "split": "holdout", "days": 6, "expected": "approve"},
        {"id": "h2", "group": "非法日期", "split": "holdout", "days": -1, "expected": "manual"},
    ],
}


def validate(dataset):
    """检查重复编号、标签和问题家族泄漏,防止错误语料混进分数。"""
    if not dataset.get("version") or not dataset.get("cases"):
        raise ValueError("版本和样本不能为空")
    ids, group_splits = set(), {}
    for case in dataset["cases"]:
        if case["id"] in ids:
            raise ValueError("样本编号重复")
        ids.add(case["id"])
        if case["split"] not in ("dev", "holdout"):
            raise ValueError("未知数据分区")
        if case["expected"] not in ("approve", "reject", "manual"):
            raise ValueError("未知参考标签")
        previous = group_splits.setdefault(case["group"], case["split"])
        if previous != case["split"]:
            raise ValueError("同一问题家族跨分区")
    if {case["split"] for case in dataset["cases"]} != {"dev", "holdout"}:
        raise ValueError("需要开发集和留出集")


def fingerprint(dataset):
    """稳定编码后计算摘要;内容或规则变化都会改变这份语料的身份。"""
    validate(dataset)
    encoded = json.dumps(dataset, ensure_ascii=False, sort_keys=True).encode("utf-8")
    return hashlib.sha256(encoded).hexdigest()[:12]


def decide(days, limit):
    """布尔值也属于Python整数家族,使用type避免把True当作第1天。"""
    if type(days) is not int or days < 0:
        return "manual"
    return "approve" if days <= limit else "reject"


def evaluate(dataset, split, limit):
    """只比较指定分区;失败编号便于回到样本,而不只看平均值。"""
    validate(dataset)
    cases = [case for case in dataset["cases"] if case["split"] == split]
    failed = [case["id"] for case in cases
              if decide(case["days"], limit) != case["expected"]]
    return len(cases) - len(failed), len(cases), failed


def main():
    print(f"语料={DATASET['version']} 摘要={fingerprint(DATASET)}")
    # baseline的6天上限故意漏掉第7天;candidate修正为7天。
    for name, limit in (("baseline", 6), ("candidate", 7)):
        for split in ("dev", "holdout"):
            passed, total, failed = evaluate(DATASET, split, limit)
            print(f"{name}/{split}: {passed}/{total}={passed / total:.0%} "
                  f"失败={','.join(failed) or '无'}")


if __name__ == "__main__":
    main()

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

Bash
ls
python -X utf8 golden_lab.py

程序输出:

TXT
语料=refund-golden-v1 摘要=ffb5cc87cf7b
baseline/dev: 3/4=75% 失败=g2
baseline/holdout: 2/2=100% 失败=无
candidate/dev: 4/4=100% 失败=无
candidate/holdout: 2/2=100% 失败=无

每行分数怎样算出来

第一行给出语料版本 refund-golden-v1 和内容摘要 ffb5cc87cf7b,本次比较使用同一份输入与标签。baseline 的退款上限是6天,开发集仅 g2(第7天)失败;因此通过数为3,分母是该分区全部4条样本,3 ÷ 4 = 75%。

candidate 把上限修为7天,开发集变成4 ÷ 4 = 100%。两份规则的留出集都是2 ÷ 2 = 100%,说明这两条样本没有观察到区别。两条全过也可能只是样本覆盖有限;实际模型的质量估计还需要覆盖更多任务家族,并保留样本量和重复执行结果。

程序分四层工作:validate 拦住编号重复与分区泄漏;fingerprint 给语料内容生成摘要;decide 执行退款规则;evaluate 选中一个分区并保留失败编号。评分器不把 manual 一概当失败,它只看实际结果是否符合该样本的参考结果。

把第7天的修复撤回一次

在 main() 的版本列表里,把 ("candidate", 7) 改成 ("candidate", 6),保存后运行 python -X utf8 golden_lab.py。只看 candidate 的两行:开发集回到3/4,失败编号重新出现 g2;留出集仍为2/2。数据没动,所以摘要也不变。随后把7改回去再运行测试。

如果业务真的改成14天退款,需要先审核新规则及各条 expected,把语料版本更新后再比较两个实现。不能只改被测程序的上限,再把旧标签导致的失败说成模型退步。若把某条 holdout 的 group 改成“最后一天”,运行会直接报“同一问题家族跨分区”,这是语料检查发现泄漏。

做完泄漏对照后,将改过的group恢复为原来的问题家族;candidate上限保持7,语料版本与标签保持原始数据。重跑应得到原摘要及四行结果,再运行配套检查。

让评测集长期有用

持续查看留出集、再据此改提示词,会逐渐把它用成开发集。可以把已反复使用的失败样本纳入开发回归集,再建立一份新的留出集;不同语料版本的总分要同时列出版本、分区和样本量。真实样本、合成边界和攻击样本也应保留类别,以免某类样本数量过大把总体平均值推向它。

scikit-learn 的数据泄漏说明解释为什么测试集不能参与方案选择。再读 Anthropic 的评测任务设计部分,重点看明确输入与成功条件、从真实失败构建回归集的做法。

保存测试并检查关键条件

7项测试检查第7/8天边界、非法日期、共同分母、留出集独立计数、重复编号、问题家族泄漏和标签变化后的内容摘要。 将下方代码保存为同一文件夹的 test_golden_lab.py,运行:

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

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

完整测试:test_golden_lab.py
Python
"""检查冻结语料、分区隔离和退款边界。"""

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

# 显式加入同目录,使独立运行包也能找到主程序。
sys.path.insert(0, str(Path(__file__).parent))
import golden_lab as lab


class GoldenTests(unittest.TestCase):
    def test_day_seven_is_allowed(self):
        self.assertEqual(lab.decide(7, 7), "approve")
        self.assertEqual(lab.decide(8, 7), "reject")

    def test_invalid_days_require_manual(self):
        for value in (None, -1, True, "7", 7.0):
            with self.subTest(value=value):
                self.assertEqual(lab.decide(value, 7), "manual")

    def test_baseline_and_candidate_share_denominator(self):
        self.assertEqual(lab.evaluate(lab.DATASET, "dev", 6), (3, 4, ["g2"]))
        self.assertEqual(lab.evaluate(lab.DATASET, "dev", 7), (4, 4, []))

    def test_holdout_is_scored_separately(self):
        self.assertEqual(lab.evaluate(lab.DATASET, "holdout", 7), (2, 2, []))

    def test_duplicate_id_is_rejected(self):
        changed = deepcopy(lab.DATASET)
        changed["cases"][1]["id"] = "g1"
        with self.assertRaises(ValueError):
            lab.validate(changed)

    def test_family_leakage_is_rejected(self):
        changed = deepcopy(lab.DATASET)
        changed["cases"][-1]["group"] = "最后一天"
        with self.assertRaises(ValueError):
            lab.validate(changed)

    def test_reference_change_changes_fingerprint(self):
        changed = deepcopy(lab.DATASET)
        changed["cases"][1]["expected"] = "reject"
        self.assertNotEqual(lab.fingerprint(changed), lab.fingerprint(lab.DATASET))
        self.assertEqual(lab.fingerprint(deepcopy(lab.DATASET)),
                         lab.fingerprint(lab.DATASET))


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

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

正在读取本地记录…

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

练习 1 / 4 · v1.1.0

选择题 · 修改提示后要比较效果,最合适的样本用法是什么

修改提示后要比较效果,最合适的样本用法是什么?

对应知识点:L7-golden

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

简答题 · 解释机制

一条可重复评测的样本需要什么?

对应知识点:L7-golden

练习 3 / 4 · v1.1.0

代码题 · 核对给定数据

把期望标签refund与实际标签shipping作相等断言会得到什么?

对应知识点:L7-golden

练习 4 / 4 · v1.1.0

场景题 · 分析具体变化

基准集只包含退款题,能怎样补齐实际任务范围?

对应知识点:L7-golden

图片查看器
100%

文章配图 · 按 Esc 关闭