返回所有文章
2361 字9 分钟阅读访问统计加载中

Jev 把「下午三点开会」判成清朝诗词:分类选项必须留一个兜底项

postmortemAI

用 Jev 做分类时,给它一段和任务完全无关的文字,它仍然会从预设选项里挑一个,并给出很高的置信度。一句「今天下午三点,我们在会议室开个会」被判成清朝诗词,置信度 0.97。

原因是它的概率只在请求里给出的选项集合上归一化,没有「以上都不是」的出口。官方宣称的零幻觉只保证返回值一定落在 schema 内,不保证判断在语义上成立。处理方式是在选项里显式加一个兜底项;加上之后,同一批越界输入全部被正确识别,真实样本的判断不受影响。

问题详情

Jev 是 TypeSafe AI 在 2026 年 9 月 15 日发布的模型,只输出三种结构化结果:从给定列表里选一项(Choice)、在给定档位上打分(Score)、给出一个 0 到 1 的是非概率(Noul)。它没有自由文本生成接口。官方文档写明结构化输出错误率为 0%。

为了判断它能不能承担生产分类任务,先用 159 条带标准答案的用例测了 8 类任务,全部通过 POST https://api.typesafe.ai/v1/systemone 调用 jev-latest:

任务用例准确率
代码安全漏洞分类26100%
检索结果相关性判断12100%
工单路由2596.0%
古诗分朝代2495.8%
Agent 工具路由1894.4%
内容审核1687.5%
提交信息分类2684.6%
日志告警分级1266.7%
合计15991.8%

代码安全那一项里有 6 段刻意写成「看着危险实际安全」的代码,包括参数化查询、做过白名单校验的路径拼接、从环境变量读取的密钥,全部正确判为无漏洞。

随后换成超出选项范围的输入,问题暴露出来。问题固定为「判断这段文字属于哪个朝代的中国古典诗词」,选项为唐、宋、元、明、清:

输入返回置信度
一段 Python 函数定义唐0.67
莎士比亚十四行诗首句唐0.45
随机乱码 asdkj23 !!! @@ zxcv 9182唐0.76
「今天下午三点,我们在会议室开个会」清0.97

四条全部落在合法选项内,没有一条触发错误或超时。

排查与原因

概率只在请求给出的选项上归一化

Jev 的输出层覆盖范围由请求里的 criteria 决定。给五个朝代,概率分布就在这五项上归一化,总和为 1。模型没有表达「这个问题的前提不成立」的位置,只能把概率质量分给现有选项。

「一段 Python 代码属于哪个朝代」这个问题本身不成立,但请求里没有任何地方允许它这么回答。

这也解释了官方那个零幻觉的实际含义:返回值永远是合法的类型和合法的枚举值,永远不会出现拼错的选项名或者多余字段。它约束的是输出格式,与判断本身对不对无关。

置信度平时可信,这让越界失败更难发现

159 条用例的置信度校准情况:

置信区间样本平均置信实际准确率
0.95-1.001040.9999.0%
0.85-0.95200.90100%
0.70-0.85100.80100%
0.50-0.70170.6264.7%
0.00-0.5080.4325.0%

加权平均偏差(ECE)为 0.037。它报 0.99 的那批,实际 99.0% 是对的,这个数字可以直接拿来卡阈值。

正因为置信度在正常输入上足够诚实,「会议室开个会」返回的 0.97 才危险。那个 0.97 是在「这段文字是某个朝代的诗词」这一前提成立的条件下算出来的。前提本身不成立时,置信度不会下降,下游按阈值放行的逻辑也就不会触发。

高置信区间还偏保守:它说 0.80 的实际全对。所以线上通常会把阈值设得比直觉更低,这进一步放大了越界输入被直接采信的概率。

解决办法

在选项里显式加兜底项

CRITERIA = {
    "唐": "唐朝", "宋": "宋朝", "元": "元朝", "明": "明朝", "清": "清朝",
    # 关键的一项
    "都不是": "这段文字根本不是中国古典诗词,或者无法判断朝代",
}

body = {
    "state": text,
    "model": "jev-latest",
    "questions": {
        "dynasty": {
            "type": "choice",
            "instructions": "判断这段文字属于哪个朝代的中国古典诗词",
            "criteria": CRITERIA,
        }
    },
}

兜底项的描述要写清楚什么情况算「都不是」。只写选项名而把描述留空时,模型缺少判断依据,兜底项等同于一个没有语义的第六个朝代。

0.5 以下的结果当作「不知道」

上表里 0.00-0.50 区间的实际准确率只有 25.0%,低于该区间的平均置信 0.43。这一段不能当作弱信号使用,按「模型没有结论」处理,转人工或走降级逻辑。

多分类比单一是非判断灵敏

同一批 45 条输入(25 条攻击样本、20 条正常样本)分别用两种问法测:

问法漏判误判达到零漏判所需阈值
Choice 多分类(含兜底项)00—
Noul 单一是非判断200.3

Noul 在 0.5 阈值下漏掉两条:伪造会话边界的输入拿到 0.30,双人格越狱拿到 0.31。同样的输入交给多分类,全部正确归类。需要零漏判时用多分类,或者把 Noul 的阈值下调到 0.3。

验证结果

加上兜底项后重跑同一批越界输入,并保留一条真实唐诗作对照:

输入加兜底前加兜底后置信度
一段 Python 函数定义唐都不是1.00
莎士比亚十四行诗首句唐都不是1.00
随机乱码唐都不是1.00
「今天下午三点,我们在会议室开个会」清都不是1.00
现代流行歌词宋都不是0.62
《静夜思》(对照)唐唐1.00

6/6 正确。对照样本没有被兜底项抢走,说明加这一项不会让模型在正常输入上变得犹豫。

延迟构成决定了它适合放在哪里

把网络和推理拆开测(同一条已预热的长连接,对比参数不合法被拒的请求和完整推理请求):

阶段中位耗时
纯网络往返(不跑推理)340ms
完整一次决策402ms
状态放大到约 2500 token395ms

推理本身约 63ms,网络往返占 84%。状态从 20 token 放大到 3000 token,耗时基本不变,说明算力不是瓶颈。

这个构成有两个直接后果。一是跨区域调用时,实测单次 402ms,单个串行循环只能跑到 2.7 次/秒;同区域部署按 20ms 往返估算,单次约 83ms,对应约 12 次/秒。二是一帧要决策多件事时必须合并成一次调用——同一状态下问 4 个问题,合并写法消耗 513 个输入 token,拆成 4 次调用消耗 1560 个,10 帧的耗时从 24.88 秒降到 4.33 秒。

已验证适用的场景

用同一套方法测过两类应用,都加了兜底项:

输入侧安全拦截。45 条样本(指令覆盖、角色扮演越狱、情感操纵、伪造权限标记、间接注入、违禁内容各若干,外加 20 条正常输入)达到零漏判零误判。20 条正常输入里有 15 条是刻意构造的高风险误判样本,例如安全从业者询问 SQL 注入原理、开发者调试自己的提示词、「忽略我刚才说的那个方案」,全部正确放行。另外测了 7 种在输入末尾夹带伪造审核结论的写法,风险分维持在 0.90 到 0.97,没有被压低。

界面操作选择。把可交互元素列成带编号的受限菜单,给出目标让它选操作哪一个,6 条用例全部正确,平均 402ms,置信度 0.99 以上。

有一类结论目前无法给出:编码混淆的检出不稳定。5 条恶意内容做 base64 后直接送检漏掉 1 条(制爆类拿到 0.33),先解码再送检则全部检出。正常内容的 base64 只拿到 0.06 到 0.29,说明它确实能部分解码,可靠性不足以依赖。输入侧需要自行完成归一化和解码。

判断依据只在自有数据里时不适用

构造一组用户行为记录,活跃度拉平,只让工单数变化,设定一条与常识相反的规律:工单提得多代表深度使用不会流失,零工单代表已放弃会流失。

不给规律时准确率 5/10,等同随机;它给 10 个用户全部打了 0.16 到 0.25 的低流失概率。把规律写进 instructions 后准确率升到 9/10。

官方文档写明不用客户数据做微调或 LoRA 适配,所有账号共用同一套权重。规律能用文字描述清楚时可以现场交给它;规律只能从历史数据统计得出时,这条路走不通。

参考资料

喜欢这篇文章?

如果这篇文章帮到了你,可以请我喝杯咖啡,支持我继续写下去。

评论