94 条 UI 报错交给 Jev,三档判对 85.1%,开单闸门定在 0.6
Jev 最近太火了,总有测试小伙伴问有哪些可以用到测试上面的,我手上正堆着一批界面报错。94 条,全是从我自己那套被测系统上真实点出来的记录。
人一条条看,一天过不了几十条。看久了还会累。那能不能让模型替我先过一遍?我把这 94 条交给了 Jev。每条都只围绕一件事问,这条观察算不算被测产品的缺陷。围着这一个问题,它一次回几道题,是非题回一个 0 到 1 的数,三档选择题回它属于哪一档。它不回话,也不解释。真正让我敢把它接进流程的不是这些数,是三档选择题附带的那个把握度。按把握度不低于 0.5 的 68 条让它自己出结论,判对 67 条。剩下 26 条交回给人,这一档它只判对 13 条,跟抛硬币差不多。
要把闸门收紧到自动开缺陷单,得放到 0.6,开了 57 张单,一张误报都没有,代价是 14 条真缺陷也进了人看档。
这篇把整条路写下来。Jev 是什么,哪些测试活能交给它,94 条素材怎么变成 94 道题,跑出来的数是什么样,它稳定错在哪两处。数字全部来自真跑,两批读数和跑批脚本都留着,谁想核都能核。
Jev 到底是什么
它跟聊天模型不是一类东西。你给它一段材料,再给一组带类型的题目,它一次回完,直接给代码能用的结构化答案。题目之间并行,互不干扰,一道题的回答不会变成另一道题的上下文,加几道题几乎不增加响应时间。它也不生成文字,要理由、要报告、要用例步骤,它一概不给。
官方只给了三个原语。
| 类型 | 你怎么问 | 它回什么 |
|---|---|---|
| Noul | 一个是非题 | 0 到 1 的一个数,表示「是」的概率 |
| Choice | 从一组固定选项里选一个 | 概率最高的那一项,加上全部选项的概率和 confidence |
| Score | 沿有顺序的档位打分,最少 2 档最多 10 档 | 一个分数,加上全部档位的概率和 confidence |
Choice 和 Score 的答案都带一个把握度,官方叫 confidence。不过是非题不带。这不是它偷懒,是非题的输出本身就是全部信息,阈值要你自己在代码里定。把握度表示概率分布的集中程度。分布越尖,它就越高。分布平的时候,从几档里挑一档都会显得勉强,所以它能提前给你一个提醒,这条别当真,交给人看。
计费只算输入。官方现在按每百万输入 token 收 0.042 美元。输出不计费。一次请求的上下文上限 64k,材料和最长的那道题加起来不超过 32k。
官方自己列了九类薄弱处,跟我们这次直接相关的有三条。它照字面读,题目写什么它就答什么。数值和计数不能信,别让它数数,也别拿分数去反推精确数值。同一个意思换个问法,答案对不上;是非题上调出来的阈值也不能搬到选择题上。官方还写明英文最准,中文要自己拿样本标定,而我这次的素材全是中文。
测试里哪些活能交给它
判断标准只有一条,就看这件事有没有唯一答案。
有唯一答案的交给代码。一条评审记录里有没有写文件名和行号,正则扫一遍就有结果,交给 Jev,等于给一件本来就准的事留个看走眼的口子,还白搭一次调用。没有唯一答案的才轮到它。一条失败日志到底算产品缺陷还是脚本自己的问题,同一条日志里常常既报了断言失败又报了连接断开,换个问法答案就变,这种只能靠读懂意思来判。
我拿这条标准把自己那套工具链上的 16 个候选位置过了一遍,划出来三档。能直接交的,是那些没有确定性真值、只能靠读懂意思才能判的活。要先改造才敢交的,是问法本身就歪的。举个例,让它判响应里哪个字段该对库里的哪一列,这可以;比对上不上,得交回代码断言。还有一档该直接交给代码判死,这一档有 4 个位置,正则和字段存在性一查就判死,连一次调用都不用花。
同一类东西,问法差一点,该不该交就翻过来。用例的预期能不能判定失败,要读懂那句预期写了什么,这种能交;草稿里缺不缺复现步骤和实际结果,只是查两个字段在不在,这种就不该交给它。
还有一个位置要单独拎出来说。用例失败之后,允不允许改测试脚本。这处的兜底方向跟别的题反着来,必须是「不许改」,它没有十足把握,就停在不许改。要是反过来默认放行,它判成能改,就去把断言改了,本该判失败的用例变成通过,中间发生了什么没人知道。
我手上正好有一套被测系统
我自己写了一套 AI 评测平台,用来给模型和 Agent 打分,团队里也在用。这套平台的前端页面改得勤,每改一次,总得有人把菜单和按钮挨个点一遍。
9 月 18 号那天我用真实浏览器把它全菜单全按钮点了一遍,逐元素枚举之后再逐个点击,同时收控制台和网络请求。那份报告记了 71 条问题。
报告里另开了 5 节「已排除的误报」,记了 23 条看着像问题,其实出在测试脚本、采集脚本、拦截器或等待时机上。71 加 23,就是这次的 94 条。
94 条素材怎么变成 94 道题
这 94 条的真值是报告结构里现成的,没有另外找人标。
| 来源 | 条数 | 真值 |
|---|---|---|
| 问题清单,问题 1 到问题 71 | 71 | 产品缺陷 |
| 5 节「已排除的误报」 | 23 | 测试侧问题。脚本、采集脚本、拦截器、等待时机造成的假象 |
送进去只留三样,页面、操作步骤、现象,现象里的原始证据留着。标题、原因、建议解决方案、严重度都砍掉。标题和原因句里基本写着结论,留着等于把答案抄给它。字段里的文字一个字没改,删的只是字段。页面里的本机端口去掉,只留路径。那 23 条误报只记了现象,页面和步骤填「(未标注页面)」「(未标注步骤)」,不猜也不编。
每条单独发一次请求,材料各自独立,模拟一条失败记录进来判一次。一次请求里并行三道题。第一道是非题,问这条观察是不是被测产品的缺陷,回一个 0 到 1 的概率。第二道把同一句反过来问,用来看两个概率加起来是不是 1。第三道是三档选择题,产品缺陷、测试侧问题、证据不足。判据附在每道题后面。同样的输入当天连续跑两遍,八个线程并发,中途没有改参数,也没有请求失败。94 条乘 2 遍是 188 次请求,每次 3 道题。单遍 0.003631 美元,两遍合计 0.007262 美元,约五分钱人民币,账户额度 5 美元这次基本没动。
只根据材料里写的内容判断,不要引用材料以外的假设。被测产品指网站的前端页面与后端接口。
测试脚本自己的定位器写法、采集脚本、测试用拦截器、等待时机、测量窗口造成的假象,不算产品缺陷。
请求体长这样,key 用你自己的。
payload = {
"model": "typesafe/jev-1.13",
"state": {
"页面": page,
"操作步骤": steps,
"现象": evidence,
},
"questions": {
"是产品缺陷": {"type": "noul", "instructions": "题目: 这条观察是被测产品的缺陷吗?" + CRITERIA},
"不是产品缺陷": {"type": "noul", "instructions": "题目: 这条观察不是被测产品的缺陷吗?" + CRITERIA},
"三档": {
"type": "choice",
"instructions": "题目: 这条观察属于哪一档?只选一个。" + CRITERIA,
"criteria": {
"产品缺陷": "被测产品的行为、数据或文案错了,开发要改",
"测试侧问题": "是测试脚本、采集脚本、测试拦截器、等待时机造成的假象,产品没错",
"证据不足": "材料里的证据不足以归到上面任一类,需要人再查",
},
},
},
}
挑两条看全过程。
第一条是问题 18,素材三个字段齐全。
页面: /memory-eval
操作步骤: 观察列表表格「评测类型」列,并与 `GET /api/v1/memory-eval/tasks` 响应体逐字段对照
现象: 表格「评测类型」列显示 `-`;接口返回的该条记录中类型字段名为 `eval_type`(值为 `forgetting`),不存在 `type` 字段
三个字段装进 state,题面照上面那份,发出去。现场重跑那次它回的是这样。
三档: 产品缺陷 把握度: 0.90
概率: 产品缺陷 0.93 / 测试侧问题 0.03 / 证据不足 0.04
判对了。材料里前后两个字段名摆着,前端取的字段名和接口返回的对不上,这是实打实的一条缺陷。
第二条是 R1-2,出自「已排除的误报」。报告只记了一句现象,页面和步骤本来就是空的,填进去的是「(未标注页面)」「(未标注步骤)」。
现象: 选择「评测类型」后,容器文本里出现 `safety`;该文本仅存在于 `aria-live="polite"` 的 0×0 隐藏 span 中。
它选了三档里的「产品缺陷」,这条判错了。那句 safety 只活在隐藏节点里,不是页面给用户看的东西,本该归测试侧问题。但这次把握度只有 0.26。
三档: 产品缺陷 把握度: 0.26
概率: 产品缺陷 0.50 / 证据不足 0.44 / 测试侧问题 0.06
这两条的结论一共跑了四次,一次没变。四个数对应早间批两遍、现场重跑两遍。问题 18 的把握度是 0.92、0.92、0.92、0.90,R1-2 是 0.28、0.31、0.27、0.26。材料里摆着字段名这种硬证据,它判得又准又笃定;材料里只剩一句技术细节,它会顺着字面读偏,但把握度会掉到 0.3 以下,等于自己举手说这条没底。
跑完我另写了一个只读脚本,把两批读数逐条重算一遍,不改任何记录。总数、分桶、闸门、正反加总,每个数都能从这两份文件里重算出来。响应里带着模型版本 typesafe/jev-1.13-20260917。把它钉死,以后换版本先比选项有没有翻。
跑出来是什么样
重跑那一批的结果。
| 真值 | 条数 | 判对 | 判对率 |
|---|---|---|---|
| 产品缺陷 | 71 | 66 | 93.0% |
| 测试侧问题 | 23 | 14 | 60.9% |
| 合计 | 94 | 80 | 85.1% |
同一天早上 07:43 我还跑过一批,两批摆在一起,最能看清它的不稳定落在哪。
| 项 | 早间 | 现场重跑 |
|---|---|---|
| 三档总判对 | 80/94 | 80/94 |
| 产品缺陷判对 | 65/71 | 66/71 |
| 测试侧判对 | 15/23 | 14/23 |
| 批内两遍三档一致 | 91/94 | 94/94 |
| 批内两遍概率完全相同 | 24/94 | 20/94 |
两批的三档总判对都是 80 条,一条不差。会抖的是分桶明细和那个概率值,同一份材料在批内跑两遍,概率值只有两成能完全对上。所以要钉的是结论和阈值,概率末位那一两位不用管。
把握度才是能当闸门用的数
| 把握度 | 条数 | 判对 |
|---|---|---|
| 0.7 以上 | 58 | 58 |
| 0.5 到 0.7 | 10 | 9 |
| 0.5 以下 | 26 | 13 |
0.7 以上全对。0.5 以下跟抛硬币差不多,这一档我整档交回给人。中间那一档只有 10 条,让它自己出结果,对了 9 条。
但自动开单这种动作代价高,线得再紧一档,下面把三个候选闸门摆在一起比。有一行容易看错,把握度够 0.5 的有 68 条,可自动开单只算判「产品缺陷」的那些,判成测试侧问题或者证据不足的都不开单,所以闸门取 0.5 时是 61 条。
| 闸门 | 自动开单 | 其中误报 | 真缺陷没开出来 |
|---|---|---|---|
| 0.5 | 61 | 1 | 11 |
| 0.6 | 57 | 0 | 14 |
| 0.7 | 52 | 0 | 19 |
0.6 是一条分界。闸门往下放到 0.5 就会出现误报,往上收到 0.7 漏的真缺陷明显变多。这条线是从这批数据里跑出来的,换一套系统要重新标定,不能直接搬。
它稳定错在两处
第一处出在送进去的材料。现象里要是带着测试侧的字眼,一条真缺陷就会被判成测试侧问题,而漏掉一条缺陷比多报一条贵。有一条现象写着「拦截器捕获到该请求」,这明明是条真缺陷,它看见「拦截器」三个字,就归成了测试侧。另一条的现象段里混进了测试者自己写的一句结论,「也就是说,请求里没有带套件 ID,而当前系统恰好 0 条运行记录,所以『空』是正常结果」,它顺着这句话判了下去。
第二处是反证已经写在材料里,它还是不改判。有一条,现象的前半截是下拉里的文本读出来一片重复,看着像重复渲染;后半截就写着 DOM 里其实是 8 个下拉项,位置各不相同。后半截已经推翻了前半截的猜测,它没当回事,还是判成产品缺陷。
对着这三条的原始材料看,它就是照着字面在读。测试侧的字眼和测试者自己写的结论还留在现象里,反证又跟现象挤在同一段,它没有第二道题把这个判断翻过来。送进去之前先做两件事,把现象里测试侧的痕迹和测试者自己写的结论句剥掉,再把反证单独做成一道题,别跟现象塞在同一道题面里。这批数据上,我打算先改送进去的材料,换模型的事先放一放。
正反两问加起来不是 1
同一份材料我正着问了一遍,又反着问了一遍,本来想确认两个数加起来是不是接近 1。1 的上下各留 0.05 当余量,出了这个范围就算明显对不上。94 条里均值是 1.259,最大的一条到了 1.87,超过 1.05 的占了七成。
按真值拆开之后两组差得很多,真缺陷组的均值 1.430,测试侧组只有 0.730。判得不准的那些条,它两边都不肯否,正着问和反着问会一起偏高,加总反而更大。这个加总不能拿来当把握度,是非题上调出来的阈值搬到选择题上也不成立。
这些数字的边界
真值是从那份报告自己的结构里读出来的,问题清单对上已排除的误报,没有另外找人标过一遍。
71 条里有同根重复,同一个空态按钮被记了三次,这种重复会把整体判对率抬高一点。
测试侧那 23 条,是报告里已经写明排除、证据也最清楚的那些。更模糊的测试侧假象没进这批样本,60.9% 这个判对率偏乐观,真实的判对率应该更低。
样本只有一个系统、一轮点击,素材又全是中文。0.6 这条线换一个系统不能直接用,英文素材也套不上这批中文标定出来的数。中文要先拿 200 条已经有结论的样本打一遍,看把握度怎么分段再定线,200 条中等长度的素材打一遍大约 0.02 美元。
带走什么
- 先问这件事有没有唯一答案。有答案的交给代码,别交给概率模型。
- 把握度低于 0.5 的一律交给人判;要自动开缺陷单,闸门放到 0.6,这条线换系统得重新标。
- 送进去之前,剥掉测试工具的痕迹,剥掉测试者自己写的结论句。
- 一条题面只做一类判断,有反证单独出题。
脚本一起给你。