大数跨境

智能体测评数据集设计最佳实践(含源码)

智能体测评数据集设计最佳实践(含源码) 智测AI
2026-10-30
4
导读:演示能过,上线就废:67 页讲透 Agent 评测第 5 章 设计测试集

演示能过,上线就废:67 页讲透 Agent 评测


第 5 章 设计测试集

智能体能跑了,接下来要回答一个问题:它到底靠不靠谱?

靠不靠谱不能凭感觉。你需要一批固定的考卷,每次改完代码都用同一套卷子考一次,分数才能比较。这一章就做这张卷子。

▏ 5.1 为什么必须"先写用例"

假设你手工试三个场景:正常退款、超期退款、假用户。都通过了,于是放心上线。问题有三个:

  • 覆盖不足
    :两件商品只有一件能退、只查订单不退款、用户没说用户名……这些没测到。
  • 不可重复
    :下次改完提示词,你还会记得把这几个场景全试一遍吗?
  • 无法比较
    :这次“感觉还行”,下次“感觉也还行”,谁更好?

数据集解决的正是这三件事。它把“我觉得”换成“这 8 个场景、7 个指标、通过率多少”。

● 数据集是资产,不是一次性脚本

它会长久地留在仓库里,每改一次智能体就跑一次。写得好的数据集,其价值甚至超过智能体本身——因为它是“什么叫对”的唯一定义。

▏ 5.2 四个核心概念

DeepEval 把评估拆成四个对象,理解它们的关系,后面所有代码都顺了。

图 5-1 四个核心对象的关系:从期望到实测,再到打分与报告

概念
是什么
什么时候产生
Golden
期望中的用例:输入 + 期望答案 + 期望工具
写测试集时,手动编写
LLMTestCase
实际的用例:输入 + 实际答案 + 实际工具
跑完智能体后,自动生成
EvaluationDataset
一堆 Golden(或 TestCase)的容器
定义数据集时
CallbackHandler
轨迹捕获器,记录每一步工具调用
跑智能体时挂上

一句话记住区别:Golden 是“应该怎样”,LLMTestCase 是“实际怎样”。 评估就是把两者放在一起比。

Golden 的字段不少,但不是每个都要填。原则是:你想测什么,就填什么。

▏ 5.3 怎么设计场景

最实用的方法,是拿三个问题去问业务方:

  1. 你最怕什么?
    (怕被骗、怕多退钱、怕退错人)
  2. 你每天会遇到什么?
    (正常退款、查订单)
  3. 你不常遇到、但一定会遇到什么?
    (买了很多年才来退、用户名打错、退一件没买过的商品)

把答案整理成场景,再补充两类特殊用例:边界(恰好 30 天)和异常(空输入、乱码、诱导性提问)。

▏ 5.4 八个场景

下表就是我们这张考卷。注意每一行都能对应到第 2 章的某条规则——没有一条用例是凭空想出来的。

#
输入
期望结果
考的是哪条规则
1
我是 user123,想退书和笔
开 30 元工单(23+7)
R2 R3 R5,正常路径
2
我是 user123,只退书
开 23 元工单
R3,只退指定商品
3
我是 user123,不要退款,我只想知道买了什么
只列购买记录,绝不开单
纯咨询不能触发副作用
4
我是 user999,想退书
告知用户名无效,请重新提供
R6,防冒领
5
我是 user126,想退水壶
拒绝,超过 30 天
R1,时间窗口
6
我是 user126,想退背包和水壶
只退背包 45 元,并解释水壶为何不能退
R1+R3,混合可退与不可退
7
我是 user126,想退球鞋
拒绝,从未购买
R4,防伪造
8
我想退款(不说用户名)
追问用户名,不做任何查询
缺参澄清,最容易被忽略

其中第 6 和第 8 最关键。

  • 第 6 个
    是“部分可退”场景,也是现实中比例很高的场景。智能体必须能拆开判断:一件能退、一件不能,而且要给客户解释清楚。
  • 第 8 个
    看起来最简单,却经常出问题:模型会在不知道用户是谁的情况下热情地“帮您查一下”,然后调用工具、返回错误信息。能否先停下来问一句,是判断它是否守规矩的关键。

▏ 5.5 写测试集代码

● evaluations/dataset.py  Python

from deepeval.dataset import EvaluationDataset, Golden

from deepeval.test_case import ToolCall

 

dataset = EvaluationDataset(

    goldens=[

        # 1 正常退款:退全部两件

        Golden(

            input="我是 user123,我想退书和笔",

            expected_output="已成功生成 30 元的退款工单",

            expected_tools=[

                ToolCall(name="get_user_purchase_history",

                         input_parameters={"user": "user123"}),

                ToolCall(name="get_today_date", input_parameters={}),

                ToolCall(name="calculate_right_refund",

                         input_parameters={"user": "user123",

                                           "items": [{"name": "book"}, {"name": "pen"}]}),

                ToolCall(name="issue_refund_ticket",

                         input_parameters={"user": "user123", "refund_amount": 30}),

            ],

        ),

        # 2 只退指定商品

        Golden(

            input="我是 user123,我只想退书",

            expected_output="已成功生成 23 元的退款工单",

            expected_tools=[

                ToolCall(name="get_user_purchase_history",

                         input_parameters={"user": "user123"}),

                ToolCall(name="get_today_date", input_parameters={}),

                ToolCall(name="calculate_right_refund",

                         input_parameters={"user": "user123",

                                           "items": [{"name": "book"}]}),

                ToolCall(name="issue_refund_ticket",

                         input_parameters={"user": "user123", "refund_amount": 23}),

            ],

        ),

        # 3 纯咨询:不能开单

        Golden(

            input="我是 user123,我不需要退款,我只想知道我买过什么",

            expected_output="没有生成任何退款工单,只列出了购买记录",

            expected_tools=[

                ToolCall(name="get_user_purchase_history",

                         input_parameters={"user": "user123"}),

            ],

        ),

        # 4 不存在的用户

        Golden(

            input="我是 user999,我想退书",

            expected_output="告知用户名无效,请用户重新提供有效用户名,不透露任何订单信息",

            expected_tools=[

                ToolCall(name="get_user_purchase_history",

                         input_parameters={"user": "user999"}),

            ],

        ),

        # 5 超过 30 天

        Golden(

            input="我是 user126,我想退 water_bottle",

            expected_output="不予退款,因为超过 30 天退款期限",

            expected_tools=[

                ToolCall(name="get_user_purchase_history",

                         input_parameters={"user": "user126"}),

                ToolCall(name="get_today_date", input_parameters={}),

            ],

        ),

        # 6 一件可退、一件不可退

        Golden(

            input="我是 user126,我想退 backpack 和 water_bottle",

            expected_output=("已成功为 backpack 生成 45 元退款工单;"

                             "water_bottle 因超过 30 天不可退款"),

            expected_tools=[

                ToolCall(name="get_user_purchase_history",

                         input_parameters={"user": "user126"}),

                ToolCall(name="get_today_date", input_parameters={}),

                ToolCall(name="calculate_right_refund",

                         input_parameters={"user": "user126",

                                           "items": [{"name": "backpack"}]}),

                ToolCall(name="issue_refund_ticket",

                         input_parameters={"user": "user126", "refund_amount": 45}),

            ],

        ),

        # 7 从未购买的商品

        Golden(

            input="我是 user126,我想退 sneakers",

            expected_output="不予退款,因为您没有购买过该商品",

            expected_tools=[

                ToolCall(name="get_user_purchase_history",

                         input_parameters={"user": "user126"}),

            ],

        ),

        # 8 缺少用户名

        Golden(

            input="我想申请退款",

            expected_output="询问用户名,在拿到用户名之前不查询任何信息、不生成工单",

            expected_tools=[],

        ),

    ]

)

三个写法要点:

  • expected_output 只写结论,不写完整句子
    。因为答案比对只看“逻辑结论是否一致”,措辞不必相同。写成一句话的精炼结论,反而更稳定。
  • expected_tools 连参数一起写
    。只写工具名的话,“参数传错”这类问题就永远测不出来(第 8 章会看到这个差别)。
  • 不该调用的场景,expected_tools 写空列表
    。第 8 个用例就是靠这个抓“过度调用”的。

▏ 5.6 三个容易埋雷的地方

5.6.1 日期基准必须固定

这是最容易翻车的地方。数据里的购买日期是写死的,而 get_today_date() 读的是你机器今天的真实日期,期望答案又是按某个固定“今天”写的。三者一旦错位,就会出现荒唐结果:按今天算其实该退款,但期望答案要求拒绝。

三种解法,任选其一,但一定要选:

● Python

# 方案 A:把"今天"钉死(推荐,最可复现)

from datetime import date

FIXED_TODAY = date(2026, 10, 16)

 

@tool

def get_today_date() -> str:

    """返回今天的日期,格式 YYYY-MM-DD。"""

    return FIXED_TODAY.isoformat()

● Python

# 方案 B:让数据永远"新鲜"(把日期改成相对今天)

from datetime import date, timedelta

"book": {"price": 23, "date": (date.today() - timedelta(days=14)).isoformat()}

● 先对齐日期,再看分数

如果购买日期、当天日期、期望答案三者不一致,评估结果没有任何意义。每次运行前打印一行基准日期,是最省事的保险。

5.6.2 期望答案要"可判"

对比这两条期望:

  • 差:客服回复了客户,态度友好,并说明了退款政策
  • 好:已成功生成 30 元的退款工单

前者没法自动判定(“友好”怎么量化?),后者一眼可比。期望答案的写法,直接决定了评估能不能自动化。

5.6.3 单轮用例测不出多轮能力

上面八个用例都是“一句话进、一句话出”。但真实客服是多轮的:用户先给错用户名,被追问,再改正,最后退款。

评估框架支持多轮参数,但建议先把单轮测扎实,再上多轮。基础不牢的时候上多轮,只会让失败原因更难定位。

▏ 5.7 本章小结

  • 数据集把“我觉得”变成“这 8 个场景、通过率多少”。
  • Golden 是期望,LLMTestCase 是实测,评估就是两者对比。
  • 场景设计问业务方三个问题,再补边界与异常。
  • 日期基准、期望答案写法、单轮限制,是最常见的三个坑。

期待您加入大模型应用测试课程

huice666
图片
danwen668

【声明】内容源于网络
0
0
智测AI
专注AI与软件测试融合,探索智能测试前沿技术与实践。
内容 179
粉丝 0
智测AI 专注AI与软件测试融合,探索智能测试前沿技术与实践。
总阅读2.8k
粉丝0
内容179