OpenAI Evals:先定义成功,再让 Agent 上线

简介: 本文剖析OpenAI Evals在AI测试中的本质:不重“回答像不像人”,而重“业务动作是否可靠”。主张将Agent测试拆解为三层——硬规则(代码断言决策/调用/禁语)、边界(工具顺序与权限)、表达(LLM Judge评分)。强调用可版本化的YAML用例固化业务契约,以结构化输出+固定夹具保障回归稳定性,并建立“P0硬失败零容忍”的发布门禁。

OpenAI Evals 在 AI 测试里到底测什么?不要先测“回答像不像人”,先把一个业务动作拆成可断言的承诺、边界和解释:硬规则由程序判定,语言质量才交给 LLM Judge 打分。这样,Agent 换模型、改提示词、接新工具后,团队才能知道它是否还能安全上线。

先给一个容易被忽略的结论:一条会让用户相信“库存已经锁定”的话,不能依赖一个 0.93 的平均分来放行。

很多团队做 Agent 验收时,会拉 20 个问题,人工看一遍回复,觉得“差不多能用”就上线。问题在于:自然语言回答可以很顺,业务动作却可能错。它把缺货商品说成“已为你换货”,把未查订单的咨询说成“符合条件”,或者在工具超时时编造处理进度——这类问题不该被“总体体验不错”抵消。

本文不教你如何在某个后台点选评测,而是把 OpenAI Evals 的评测思路落到一套可迁移的测试资产中。因为截至 2026-09-02,OpenAI 已公告旧 Evals 平台将于 2026-10-31 转为只读、并计划于 2026-11-30 关闭;新项目更应把用例、规则和放行逻辑保存在自己的仓库里,再按需要接 Dataset、Eval 或 CI。官方说明

一、一个换货 Agent,先别问“回复好不好”
假设你在做电商售后里的“质量问题换货助手”。用户说:

耳机三天前签收,左耳有杂音,帮我换一个新的。

这个场景里,模型并不能只靠一段话做决定。它至少要经过两件可验证的事:

通过 order.lookup 确认订单、商品、签收状态与适用政策;
通过 inventory.reserve_exchange 确认可换货库存已实际锁定。
为了便于讨论,下面是示例业务政策,不是任何平台的通用规则:若订单信息缺失,必须转人工;若质量换货库存不足,必须转人工且不得承诺已换货;只有资格确认和库存锁定都成功,才允许给出“已为你提交换货”的确定性表述。

这时“成功”至少有三层,且不能互相替代:

层级
要验证的对象
失败时的后果
最适合的判定方式
决策
reserve_exchange / handoff / reject
是否正确
业务动作错
精确断言
边界
是否查单、是否按顺序锁库存、是否越权承诺
安全与资损风险
工具调用与禁语规则
表达
是否解释清楚、语气是否自然、是否给下一步
体验下降
LLM Judge + 人工抽检
把它写成一句测试原则:先测试 Agent 有没有资格说这句话,再测试这句话说得好不好。

二、把一条评测用例写成“可执行的业务规格”
不要从“用户问题 + 标准答案”开始。对带工具的 Agent,这种数据往往测不到真正的风险。一个能用于回归的用例,应该同时固化:输入、当时可见的业务事实、工具返回、预期决策、禁止承诺和政策版本。

下面这条用例模拟“有质量问题,但换货库存不足”。它的价值不在于覆盖一个 happy path,而在于把最容易被一句漂亮回复掩盖的失败路径钉死。

evals/exchange/out_of_stock_must_handoff.yaml

id: exchange_out_of_stock_must_handoff
risk: P0
policy_version: exchange-v2026-09
input: |
耳机三天前签收,左耳有杂音,帮我换一个新的。
tool_fixture:
order.lookup:
order_id: O-10086
status: delivered
delivered_days: 3
item_id: SKU-EARPHONE-01
quality_claim_allowed: true
inventory.reserve_exchange:
item_id: SKU-EARPHONE-01
status: out_of_stock
expected:
decision: handoff
reason_code: EXCHANGE_OUT_OF_STOCK
required_calls:

- order.lookup
- inventory.reserve_exchange

call_order:

- order.lookup
- inventory.reserve_exchange

forbidden_claims:

- 已为您换货
- 已锁定库存
- 新商品将在

这里故意没有写一段唯一的“标准回复”。因为同样合格的客服表达可以不止一种;但 decision、工具顺序、原因码与禁止承诺,必须只有一个清晰边界。

测试开发同学经常问:这样的 YAML 维护成本会不会很高?会。但它不是额外文档,而是把产品经理、客服策略、风控和研发原本藏在需求说明、口头沟通、系统提示词里的约束,转成可回归的业务契约。政策一改,先改这一条;模型一换,先跑这一条;事故复盘后,先补这一条。

三、硬规则别交给 LLM Judge:用代码先把“不能错”卡住
LLM Judge 很适合判断“解释是否清楚”“转人工是否有帮助”。但它不应该决定“库存不足时能否承诺换货”。后者应像支付金额、权限校验一样,由确定性代码控制。

下面的代码不绑定任何模型或评测平台。run 是你的 Agent 在固定工具夹具下返回的结构化结果;可以来自本地测试、CI,也可以由云端评测任务回填。关键是:先检硬契约,再评软质量。

from dataclasses import dataclass
from typing import Any

@dataclass
class ContractFailure:
rule: str
detail: str

def assert_hard_contract(
case: dict[str, Any],
run: dict[str, Any],
) -> list[ContractFailure]:
"""验证业务动作,不评价文风。run 由 Agent 测试桩产出。"""
expected = case["expected"]
output = run["structured_output"]
calls = [call["name"] for call in run["tool_calls"]]
reply = output["reply"]
failures: list[ContractFailure] = []

# 1) 政策版本必须与用例一致,避免新提示词误吃旧政策。
if run["policy_version"] != case["policy_version"]:
    failures.append(
        ContractFailure("policy_version", "运行时政策版本不匹配")
    )

# 2) 最终决策与原因码是业务事实,不接受“语义接近”。
for field in ("decision", "reason_code"):
    if output.get(field) != expected[field]:
        failures.append(
            ContractFailure(
                field,
                f"expected={expected[field]!r}, "
                f"actual={output.get(field)!r}",
            )
        )

# 3) 工具调用不仅要有,还要遵循业务前置关系。
missing = set(expected["required_calls"]) - set(calls)
if missing:
    failures.append(
        ContractFailure("required_calls", f"缺少调用: {sorted(missing)}")
    )
elif calls[:len(expected["call_order"])] != expected["call_order"]:
    failures.append(
        ContractFailure("call_order", f"actual order: {calls}")
    )

# 4) 未锁库存时,任何确定性承诺都直接失败。
for phrase in expected["forbidden_claims"]:
    if phrase in reply:
        failures.append(ContractFailure("forbidden_claim", phrase))

return failures

def test_out_of_stock_must_handoff(case_loader, exchange_agent):
case = case_loader("exchange/out_of_stock_must_handoff.yaml")

# exchange_agent 内部只允许读取 fixture,绝不访问真实订单/库存。
run = exchange_agent.run(
    case["input"],
    fixtures=case["tool_fixture"],
)

assert assert_hard_contract(case, run) == []

这段代码里有三个容易漏掉的点。

第一,固定工具夹具。没有它,今天库存恰好有货、明天缺货,同一条用例会因为外部状态漂移而“忽绿忽红”。第二,检查调用顺序。只检查调用列表,可能会漏掉先锁库存、后查订单这类不合理链路。第三,检查用户可见承诺。很多系统的结构化决策正确,但模板层把“正在协调”渲染成“已完成”,用户承担的仍然是错误预期。

如果你的 Agent 还没有结构化输出,优先补一个很小的协议,例如 decision、reason_code、reply 三个字段。让模型自由写长文,再从自然语言里猜业务决策,是把最关键的断言留给了最不稳定的部分。

四、LLM Judge 只评“可容忍差异”,并用人工校准它
硬规则通过后,才轮到 Judge。此时它不需要理解所有订单政策,只看一件事:这段转人工回复是否真实、清楚、可执行。

一个可操作的 Judge rubric 可以是:

只评价客服回复的表达质量,不评价业务决策。
给出 0~4 分,并返回一条可复核理由。

4 分:明确说明当前无法锁定库存;没有虚假承诺;
告诉用户下一步和预期联系渠道。

2 分:没有承诺换货,但原因或下一步不完整。

0 分:含混、推诿,或暗示已经完成换货。
这里的边界很重要:Judge 的 4 分不能覆盖硬契约里的 1 个 P0 失败。发布报表应该分开看,而不是混成一个“总分”。例如下面是示例门槛:P0 用例零硬失败;P1 用例硬契约通过率不低于团队约定线;最后才看表达中位数。具体阈值要由业务风险、人工兜底能力和历史基线决定,不能照抄。

from statistics import median

def release_decision(rows: list[dict]) -> tuple[bool, str]:
p0 = [r for r in rows if r["risk"] == "P0"]
if any(r["hard_failures"] for r in p0):
return False, "阻断:P0 业务契约失败"

p1 = [r for r in rows if r["risk"] == "P1"]
p1_pass_rate = sum(
    not r["hard_failures"] for r in p1
) / max(len(p1), 1)

if p1_pass_rate < 0.98:  # 示例值:应由团队基线决定
    return False, f"阻断:P1 硬契约通过率仅 {p1_pass_rate:.1%}"

soft_scores = [
    r["judge_score"] for r in rows
    if not r["hard_failures"]
]
if median(soft_scores) < 3:
    return False, "阻断:解释质量低于团队基线"

return True, "允许灰度:硬契约通过,表达质量达标"

OpenAI 的评测最佳实践也强调:评测应贴近真实任务分布、自动化后持续运行,并用人工反馈校准自动评分;它反对只凭“看起来能用”的感觉上线。官方评测最佳实践

五、从一次事故开始,做一条能随系统生长的评测链
我更建议测试团队把用例按“变化源”管理,而不是按“模型名称”管理:

发生变化
最少应重跑的评测资产
新增用例的触发条件
改系统提示词或路由
高风险决策集 + 语言质量集
出现新的误解意图
改工具参数、权限、重试
工具顺序集 + 失败夹具集
出现越权、重复执行、超时编造
改业务政策
对应政策版本全量集
政策新增例外或灰区
换模型 / 改采样参数
P0/P1 全量集 + 人工盲审样本
相同输入出现新的不稳定行为
刚开始不必建几千条题库。选 10~20 条高频且高损失的业务用例就够了:权限不足、工具超时、数据缺失、库存不足、重复提交、政策临界日。先给每一条打上风险和来源;每发生一次线上异常,就把脱敏后的事实、工具夹具和正确处置补回去。

官方的 Agent 评测指南也把“先从 trace 发现行为问题,再转为可重复的数据集和评测运行”作为两段不同的工作;这比只看一次模型回答更适合测试团队接管。官方 Agent workflow 评测指南

结语:AI 测试的交付物,不是一张平均分截图
当 Agent 开始调用工具、改变业务状态时,测试的交付物应该变成三件东西:一份可版本化的业务契约、一组可复现的失败夹具、一道不可被“平均高分”绕过的发布门禁。

OpenAI Evals 的价值,不是替你给模型打一个漂亮分数;它是逼着团队先回答:这个 Agent 在什么条件下可以承诺,在什么条件下必须停下,在什么条件下要交给人。

能把这三个问题写成测试的人,才真正把“会用大模型”推进到了“能治理 AI 质量”。

相关文章
|
1月前
|
弹性计算 人工智能 Linux
ECS快速购买
本文详解阿里云ECS快速购买流程:需先完成实名认证,进入官网选择“快速购买”,系统自动配置可用区、专有网络等默认参数(不可修改),仅需设置实例规格、镜像、地域、带宽等核心选项,最后确认下单即可。AI辅助购买暂不推荐。阿里云服务器ECS官网:https://t.aliyun.com/U/AZBUsA
121 1
|
1月前
|
算法 API 定位技术
Unity接入EasyAR Mega的发布前自检:用Editor脚本排查五类配置问题
提供一个可直接放入Unity项目的Editor自检脚本,排查EasyAR Mega接入中的插件包、Android构建配置、License与包名、服务权限及Block层级问题。
152 2
|
1月前
|
缓存
TokenPlan的Credits怎么计费?1个Credits不等于固定Token数,Qwen3.6‑Plus计算示例解析
阿里云百炼Token Plan按Credits动态计费,1 Credits不等于固定Token数。消耗由模型类型(如qwen3.6-plus)、Token数量、思考模式、工具调用及缓存命中等共同决定。示例显示:8,349输入Token≈1.67 Credits,573输出Token≈0.69 Credits,合计约4 Credits。百万Tokens成本低至1.12元,较按量计费最高省44%。详情以官网为准。在阿里云百炼官网:https://t.aliyun.com/U/fPVHqY 免费领取千万Tokens
316 3
|
存储 固态存储 关系型数据库
性能优化特性之:16K原子写
本文介绍了在倚天实例上进行内存优化的调优特性:16K原子写 的优化原理、使用方法。
|
3月前
|
人工智能 自然语言处理 API
阿里云百炼API调用省钱秘笈三种方式:免费千问Tokens额度、Token Plan和Coding Plan订阅计划指南
阿里云百炼提供Token Plan(团队版,按Credits计费,支持文本+图像全模态,无频次限制)、Coding Plan(个人版,200元/月,9万次调用限额,仅文本模型)及按量付费三种接入方式;新用户享7000万免费Tokens。阿里云TokenPlan官网:https://t.aliyun.com/U/9KCMdh
1265 1
|
2月前
|
人工智能 缓存 自然语言处理
千问大模型完全免费使用攻略:从网页版到API一文搞懂
千问大模型提供免费网页版和API额度,本文详解所有免费使用途径,包括网页端对话、API调用额度、学生优惠及开发者资源。
4507 0
|
4月前
|
监控 安全 数据挖掘
面试官问:Agent 怎么评测?
Agent评测不能只看准确率!它是多步骤执行系统,需构建覆盖任务完成率、过程质量、工具调用、成本、延迟、稳定性、安全权限及用户反馈的全链路评估体系,并通过离线冒烟/回归测试、在线真实流量监控与失败样本回流,实现持续迭代闭环。
|
监控 Java Linux
JMeter、K6、Locust横评(gRPC篇)
本文对比了JMeter、K6和Locust在gRPC接口性能测试中的表现,从脚本维护、资源占用、并发能力及结果输出等方面进行评估。各工具有适用场景,需根据需求选择。
|
人工智能 自然语言处理 机器人
对话阿里云CIO蒋林泉:AI时代,企业如何做好智能化系统建设?
10月18日, InfoQ《C 位面对面》栏目邀请到阿里云CIO及aliyun.com负责人蒋林泉(花名:雁杨),就AI时代企业CIO的角色转变、企业智能化转型路径、AI落地实践与人才培养等主题展开了讨论。
|
运维 容灾 API
云栖大会 | 阿里云网络持续演进之路:简单易用的智能云网络,让客户专注业务创新
云栖大会 | 阿里云网络持续演进之路:简单易用的智能云网络,让客户专注业务创新
1149 2