75 类行业分类:置信度不够就上报大类
💡 一句话总结:用 Jev 建立「不确定就自动降级」的文本分类流水线 —— 一道 Choice 分进 75 个行业组、confidence ≥ 0.9 报行业组、不够就纯代码上报所属大类。
每家在 SEC 提交年报(10-K)的公司,都会在 Item 1「业务描述」里说明自己是做什么的。官方 cookbook 拿这批真实材料出了一道经典的 Jev 文本分类题:把每份描述按标准行业分类(SIC)分进 75 个行业组——每份文件一道 Choice,整个类目表一次请求装得下。
多数文件好分:地区银行就是地区银行。难的是刚卖掉两个业务板块之一的公司、还有描述「打算进入」的行业的初创公司。模型无论如何都得硬选一个组,而难例的答案和易例长得一模一样。区分难易通常正是成本所在:第二个模型、额外调用、人工复核。
Jev 的 Choice 答案自带 confidence(classification confidence,分类置信度)——一个数就能把「能信的答案」和「不能信的答案」分开。而 SIC 标签天然有层级:行业组之上有 10 个更宽的大类(division)。官方的做法是:模型对行业组没把握时,就上报它所属的大类——大类由组号查表可得,零第二次调用。
官方拿 60 份年报实测:0.9 的 confidence 门槛把文件正好对半分开。有把握的一半,90% 正确;没把握的一半,硬报行业组只有 40% 对——上报一级大类后,可用率提到 70%。本课约 15 分钟,带你用官方公开数据完整复现这套门控(官方 cookbook 难度标为 Beginner)。
学完你能做什么
- 用一道 75 选项的 Choice 题给公司描述做 Jev 文本分类,拿到完整概率分布和 confidence
- 写出「confidence ≥ 0.9 报行业组、低于它上报所属大类」的门控代码——查表完成,零第二次 API 调用
- 用官方公开数据复现 SEC 年报分类实验:60 份年报,强制报行业组 39/60 正确 → 加门控 48/60 可用
- 把「置信度不够就升级上报」搬到你自己的类目树:AI 行业分类、电商类目、工单体系都适用
为什么用 Jev,而不是普通 LLM 文本分类?
| 能力 | 普通 LLM 分类 | Jev |
|---|---|---|
| 不确定度 | 问「你确定吗」得到一段散文;让它把概率写成文本,还多一个解析故障点 | 每个答案自带 confidence(0–1),由概率分布算出,直接设阈值 |
| 选项控制 | 自由发挥类别名,「贷款经纪」「放贷中介」词表天天漂 | 答案永远是 75 个选项之一,choice 即最高概率选项 |
| 大选项列表 | 75 个选项连同描述塞进 prompt,输出还要对表校验 | Choice 一题最多 255 个选项,约 240 个内可靠——75 远在安全区 |
| 概率校准 | 为生成文本优化,自报的概率没有校准保证 | RLCD 校准训练,概率对齐真实结果 |
| 难例兜底 | 硬报一个错标签,或再调一次模型问「确定吗」 | 大类由组号查表推出,上报是纯代码映射,零第二次调用 |
一句诚实话:confidence 门控降低的是「把错标签当真」的比例,不消灭错误——官方实测里,有把握的一半也有 3/30 是错的。校准的出处和公式细节见 Confidence 置信度。
你现在的困境
- 「让 LLM 把 200 家客户分进 75 个行业,它每条都答得斩钉截铁——分错的那几条和分对的长得一模一样,抽查全靠运气」
- 「想加人工复核,可 60 条全看一遍比分类本身还贵;想问模型『你确定吗』,它永远说确定」
- 「类目明明有层级,分不到最细时给个粗答案也比错答案强——可模型只会硬选一个,不会说『我只能到这一层』」
什么时候用这一招
当你需要:
- 标签本身有层级(行业分类、电商类目、组织架构),分不到细级时粗级也能用
- 每条数据都有去处——既不许「不知道」直接丢弃,也不想全部转人工
- 知道哪些答案能自动采信、哪些要复核,把复核预算花在刀刃上
而且不想:
- 为「模型确定吗」再发一次请求、再调一个模型
- 从模型吐的文本里解析类别名和概率
- 在模型没把握时硬报细级,下游把错误的细级标签当真
🎒 开始前的准备
- 完成了 Choice 选择题:知道
criteria是选项 map、probabilities是所有选项的分布、答案永远是选项之一 - 完成了 Confidence 置信度:知道 confidence 由概率分布算出、三档用法、阈值随风险缩放
- 跑通过 第一个请求:
.venv里装好了typesafe-sdk,环境变量TYPESAFE_API_KEY已设置
数据不用自己造:官方把 SIC 行业表和 60 份真实年报公开在 typesafe-public-examples 仓库,第 1 步带你下载。
跟我做
第 1 步:建好「行业组 → 大类」映射表(下载官方 SIC 数据)
为什么:上报逻辑需要一张「细级 → 粗级」映射表。SIC 的编码本身就是层级:四位行业码的前两位是行业组(major group,共 75 个),固定区间的行业组归入 10 个大类(division)——这层映射纯代码从编码规则推出,不需要模型参与。
在项目目录下载官方数据两件套(2026-11-03 本机核验可访问):
curl -sL -o sic_codes.tsv "https://raw.githubusercontent.com/typesafe-ai/typesafe-public-examples/main/cookbooks/classification_using_confidence/sic_codes.tsv"
curl -sL -o filings.jsonl "https://raw.githubusercontent.com/typesafe-ai/typesafe-public-examples/main/cookbooks/classification_using_confidence/filings.jsonl"新建 classify_sic.py,贴入映射部分:
# classify_sic.py —— 75 类行业分类:置信度不够就上报大类
# 依赖:pip install typesafe-sdk(Python 3.10+),环境变量里已有 TYPESAFE_API_KEY
from collections import defaultdict
from pathlib import Path
# 十个大类:SIC 行业组编码的固定区间(官方 cookbook 原表)
DIVISIONS = [
(1, 9, "agriculture, forestry and fishing"),
(10, 14, "mining"),
(15, 17, "construction"),
(20, 39, "manufacturing"),
(40, 49, "transportation, communications and utilities"),
(50, 51, "wholesale trade"),
(52, 59, "retail trade"),
(60, 67, "finance, insurance and real estate"),
(70, 89, "services"),
(91, 99, "public administration"),
]
# 444 个四位行业码 -> 75 个行业组(前两位)-> 10 个大类,全程零模型参与
INDUSTRIES: dict[str, str] = {}
for line in Path("sic_codes.tsv").read_text().splitlines()[1:]:
code, _office, title = line.split("\t")
INDUSTRIES[code] = title.lower()
GROUPS: dict[str, list[str]] = defaultdict(list)
for code in sorted(INDUSTRIES):
GROUPS[code[:2]].append(code)
def division(group: str) -> str:
"""行业组组号 -> 所属大类,纯查表。"""
number = int(group)
return next(name for low, high, name in DIVISIONS if low <= number <= high)
print(f"{len(INDUSTRIES)} industries -> {len(GROUPS)} major groups -> {len(DIVISIONS)} divisions")
print(f" group 35 = {division('35')} / {', '.join(INDUSTRIES[c] for c in GROUPS['35'][:3])} ...")运行:
.venv/bin/python classify_sic.py你应该看到(2026-11-03 本机用官方数据核验):
444 industries -> 75 major groups -> 10 divisions
group 35 = manufacturing / engines & turbines, farm machinery & equipment, lawn & garden tractors & home lawn & gardens equip ...- 我跑出了 444 → 75 → 10,group 35 落在 manufacturing
第 2 步:把 75 个行业组写成一道 Choice 题
为什么:选项光有名字不够——75 个行业组里只有 42 个在 SEC 列表里带「伞名」(如 group 20 的 food and kindred products),其余 33 个只有组号。官方的做法是用组内行业细目描述每个选项——读年报的人对照的本来就是这些细目。每组最多列 8 个细目:官方注释说,足够刻画特征,又不至于堆成一面文字墙。
接着上面的代码贴:
from typesafe_sdk import Choice
MAX_NAMED = 8 # 每组列出的细目数;官方值
def describe(group: str) -> str:
"""选项描述 = 伞名 + 组内细目;没有伞名的组只列细目。"""
umbrella = INDUSTRIES.get(f"{group}00")
inside = [INDUSTRIES[c] for c in GROUPS[group] if c != f"{group}00"][:MAX_NAMED]
listed = "; ".join(inside)
return (
f"{umbrella} — includes: {listed}"
if umbrella and listed
else (umbrella or listed)
)
print(f"group 20: {describe('20')[:150]}")
print(f"\ngroup 65: {describe('65')[:150]}")
# 问题原文来自官方 cookbook;完整问题写在 instructions 里
QUESTION = (
"Which broad industry does this company operate in? Judge the company's own operations "
"as this filing describes them."
)
def questions() -> dict:
return {
"group": Choice(
instructions=QUESTION,
criteria={group: describe(group) for group in sorted(GROUPS)},
)
}再运行一次,你应该看到(新增输出如下,与官方录制输出一致;前面步骤的打印保持不变):
group 20: food and kindred products — includes: meat packing plants; sausages & other prepared meat products; poultry slaughtering and processing; dairy product
group 65: real estate — includes: real estate operators (no developers) & lessors; operators of nonresidential buildings; operators of apartment buildings; less75 个选项会不会太多?不会。Choice 课讲过一题最多 255 个选项;官方 cookbook 的经验是约 240 个内仍然可靠,75 远在安全区——整个类目表一次请求就装得下。
💡 这就是 Jev 的独有价值
75 个选项连同描述在 criteria 里结构化地交给模型,答案只能是这 75 个组号之一——没有词表漂移,没有输出解析。更关键的是下一步:每个答案自带 confidence,你不用为「它确定吗」再发任何请求。
- 我能说出 group 20 的选项描述里,「伞名」和「细目」各是哪部分
第 3 步:一份年报一次请求,跑通 Jev 文本分类
为什么:filings.jsonl 里是 60 份真实 10-K 年报,每份只保留 Item 1「业务描述」——公司自述业务的部分,也是行业码唯一关心的部分,平均 1438 词。每份文件发一次请求、问一道 Choice:答案返回 choice(赢家的组号)、probabilities(75 个选项各自的权重)、confidence(分布集中程度的单一数字)。
第 4、5 步会对同一份文件反复取答案,所以先加一个小缓存把结果存进 my_cache.json——重跑脚本不重复计费,想强制重跑就删掉它(官方 notebook 用 cooksafe 的 JsonCache 做同样的事):
import json
from typesafe_sdk import TypeSafeClient
client = TypeSafeClient(model="jev-latest")
FILINGS = [json.loads(line) for line in Path("filings.jsonl").read_text().splitlines()]
_cache: dict[str, dict] = {}
if Path("my_cache.json").exists():
_cache.update(json.loads(Path("my_cache.json").read_text()))
def ask(filing_id: str, text: str) -> dict:
if filing_id not in _cache:
response = client.system_one(state=text, questions=questions())
answer = response.answers["group"]
_cache[filing_id] = {
"group": answer.choice,
"confidence": answer.confidence,
"probabilities": dict(answer.probabilities),
}
Path("my_cache.json").write_text(json.dumps(_cache))
return _cache[filing_id]
# 拿一份好分的文件先跑通:一家人寿保险公司
example = next(f for f in FILINGS if f["id"] == "33416_1998")
print(f"{len(FILINGS)} filings, {sum(f['words'] for f in FILINGS) // len(FILINGS)} words on average")
print(f"\n{example['id']} (filed {example['year']}, accession {example['accession']}):")
print(f" {example['text'][:230]}...")
print(f" filer's code: {example['sic']} {INDUSTRIES[example['sic']]}")
answer = ask(example["id"], example["text"])
print(f"\nmodel's answer: group {answer['group']} ({describe(answer['group']).split(' — ')[0][:46]}), conf {answer['confidence']:.2f}")运行:
.venv/bin/python classify_sic.py你应该看到(新增输出如下;检视行 2026-11-03 本机核验,末行是官方录制值——jev-1.12 实测这份文件 conf 1.00 → group 63。你用 jev-latest 跑,业务描述这么明确的文件标签走向大概率不变,置信度数值可能略有差异):
60 filings, 1438 words on average
33416_1998 (filed 1998, accession 0000033416-99-000001):
ITEM 1. BUSINESS Erie Family Life Insurance Company (hereinafter referred to as "The Company", the "Company" or "Erie Family Life") was incorporated in the Commonwealth of Pennsylvania on May 23, 1967 and commenced business on Sep...
filer's code: 6311 life insurance
model's answer: group 63 (life insurance; accident & health insurance; h), conf 1.00一家自述业务清清楚楚的公司,confidence 直接到顶。官方录制的 60 份答案里,confidence 从 0.22 到 1.00 都有——分布拉开,正是门控能干活的前提。费用提醒:全量跑 60 份 = 60 次请求,按输入 token 计费、输出免费,现行费率见模型与价格。
- 单文件请求返回了 choice、probabilities、confidence,答案是我 75 个选项之一
第 4 步:加置信度门控:≥ 0.9 报行业组,< 0.9 上报所属大类
为什么:这才是本课的主角。confidence ≥ 0.9,答案按行业组上报;低于 0.9,同一个答案改报它所属的大类——division(组号) 查表完成,零第二次调用。每份文件都带着能用的标签回来:不丢弃、不硬报、也不全量转人工。
接着贴:
CONFIDENT = 0.9 # 高于它报行业组组号;低于它上报所属大类(官方 SEC 场景实验值)
def classify(filing: dict) -> dict:
answer = ask(filing["id"], filing["text"])
sure = answer["confidence"] >= CONFIDENT
return {
"level": "group" if sure else "division",
"label": answer["group"] if sure else division(answer["group"]),
"confidence": answer["confidence"],
"group": answer["group"],
}
def show(filing: dict) -> None:
result = classify(filing)
named = describe(result["group"]).split(" — ")[0][:46]
print(
f" {filing['id']:>13} conf {result['confidence']:.2f} -> {result['level']:<8} "
f"{result['label']:<14} (group {result['group']}: {named})"
)
results = [(f, classify(f)) for f in FILINGS] # 60 份各一次请求,缓存防重复
by_conf = sorted(results, key=lambda fr: fr[1]["confidence"], reverse=True)
print("three filings the model was sure about:")
for f, _ in by_conf[:3]:
show(f)
print("\nthree it was not:")
for f, _ in by_conf[-3:]:
show(f)运行:
.venv/bin/python classify_sic.py你应该看到(新增输出如下,官方录制,jev-1.12、2026-08-12;你自己的头尾名单和数值会随版本与数据不同,但「好分的高置信、难分的低置信」的格局一致):
three filings the model was sure about:
310158_1996 conf 1.00 -> group 28 (group 28: chemicals & allied products)
33416_1998 conf 1.00 -> group 63 (group 63: life insurance; accident & health insurance; h)
352541_1996 conf 1.00 -> group 49 (group 49: electric, gas & sanitary services)
three it was not:
1372167_2013 conf 0.22 -> division manufacturing (group 38: search, detection, navagation, guidance, aeron)
1398633_2009 conf 0.23 -> division wholesale trade (group 50: wholesale-durable goods)
46653_1999 conf 0.29 -> division services (group 87: services-engineering, accounting, research, ma)盯着两端各三行,confidence 和「这份文件有多难分」严丝合缝:
- 三个 1.00:制药商、人寿保险公司、公用事业——纸面上都是控股公司,但每家的年报都点名了一个主导业务。
- 三个垫底的:两家是发展期公司,描述的是「打算做」的生意(一家自称「intends to operate as a software developer」,另一家「organized to enter into the computer security software industry」);第三家原本有两个业务板块,提交前几周刚卖掉一个。
- 于是它们仨回来的不是细级组号,而是 manufacturing / wholesale trade / services 三个大类标签。
💡 中文内容注意
官方语言支持说明:英语是主要训练语言,包括中文在内的其他语言能用但准确度较低。拿中文公司简介做 AI 行业分类前,先用你自己的数据跑一批,重点看 confidence 分布,再定阈值——这也是全栏目反复强调的第一步。
- 头尾各三行里,有把握的报 group、没把握的报 division(官方录制里垫底三份的 confidence 都不到 0.3)
第 5 步:量化「上报大类」买到什么
为什么:门控值不值,要拿数字说话。官方把 60 份录制答案对照 filer 自报的码,按两种策略打分:策略 A 强制每次都报行业组;策略 B 没把握(confidence < 0.9)就上报大类。
接着贴:
def correct(filing: dict, result: dict) -> bool:
gold_group = filing["sic"][:2]
if result["level"] == "group":
return result["label"] == gold_group
return result["label"] == division(gold_group)
sure = [(f, r) for f, r in results if r["level"] == "group"]
unsure = [(f, r) for f, r in results if r["level"] == "division"]
forced = sum(r["group"] == f["sic"][:2] for f, r in results)
broadened = sum(correct(f, r) for f, r in results)
print(f"forced to name a group every time {forced}/{len(results)} right")
print(
f" of those, the {len(sure)} it was sure about "
f"{sum(r['group'] == f['sic'][:2] for f, r in sure)}/{len(sure)} right"
)
print(
f" and the {len(unsure)} it was not "
f"{sum(r['group'] == f['sic'][:2] for f, r in unsure)}/{len(unsure)} right"
)
print(f"\nletting it answer coarsely when unsure {broadened}/{len(results)} useful answers")运行后你应该看到(新增输出如下,官方录制,jev-1.12):
forced to name a group every time 39/60 right
of those, the 30 it was sure about 27/30 right
and the 30 it was not 12/30 right
letting it answer coarsely when unsure 48/60 useful answers拆成一张表看清楚(比例由官方四组数字换算):
| 策略 | 有把握的 30 份 | 没把握的 30 份 | 合计 60 份 |
|---|---|---|---|
| 强制报行业组 | 27/30 对(90%) | 12/30 对(40%) | 39/60(65%) |
| 没把握就上报大类 | 27/30 对(90%) | 21/30 可用(70%) | 48/60(80%) |
三个读法:
- 有把握的一半:报行业组 90% 正确——这些可以放心自动采信。
- 没把握的一半:硬报行业组只有 40% 对;上报一级大类后 70% 可用。粗一级天然更容易对——manufacturing 一个大类就覆盖 20–39 二十个行业组,赢家只要落进正确区间,查表后的标签就对。
- 代价:那 30 份的标签变粗了。官方原话:如果大类对你的应用太粗、没法据此行动,这个分支就是把它交给人工的地方。
别忘了对账:有把握的一半也有 3/30 是错的——门控降低的是「把错标签当真」的比例,不是消灭错误。
- 我能说出 39/60、27/30、12/30、48/60 四个数各自的含义
📋 魔法 Prompt
本页的「Prompt」是给 API 的问题模板——抄走、替换【】占位符(带 ? 的是选填)就能用。
📝 置信度门控的两级分类模板(任意类目树)
预期效果:把任何有层级的标签体系做成「够确定报细级、不够确定报粗级」的分类器——一次请求、零第二次调用。
from typesafe_sdk import Choice
# 必填项:【】里换成你的类目树
CONFIDENT = 【置信度门槛:官方 SEC 场景用 0.9;从保守值起步,用自己的数据调】
FINE_LABELS = {
"【细级标签 A】": "【什么内容算 A——写成可对照的情境描述】",
"【细级标签 B】": "【什么内容算 B】",
# 细级标签总数 ≤ 255(硬上限),约 240 内可靠
}
COARSE_OF = {
"【细级标签 A】": "【A 所属的粗级标签】",
"【细级标签 B】": "【B 所属的粗级标签】",
}
QUESTIONS = {
"label": Choice(
instructions="【一句话问题:这条内容最属于哪个标签?只根据给定材料判断】",
criteria=FINE_LABELS,
),
}
# 输出规范:answer.choice / answer.confidence / answer.probabilities
# 门控:confidence >= CONFIDENT 报 answer.choice;否则报 COARSE_OF[answer.choice]
# 约束条件:
# ✅ 粗级映射用纯代码查表——不发第二次请求
# ✅ 每个细级标签写「可对照的情境描述」,光有名字往往不够
# ❌ 不要把「不确定」做成一个选项——门控是代码层的,不是模型标签
# ❌ 单级标签超过约 240 个别硬塞一题——改走逐层分类(见相关教程)✅ 选项描述生成器(从你自己的类目文件拼 criteria)
预期效果:官方用「伞名 + 最多 8 个组内细目」描述每个选项——你的类目树照做,从类目数据自动拼出每个选项的描述。
# 必填项:【】里换成你的类目数据源(伞名和子项列表)
# 选填项:没有伞名的标签 umbrella 传空串?,函数自动只列子项
def describe_label(umbrella: str, children: list[str], max_named: int = 8) -> str:
"""官方 describe() 的通用版:伞名 + 最多 max_named 个子项。"""
listed = "; ".join(children[:max_named])
if umbrella and listed:
return f"{umbrella} — includes: {listed}"
return umbrella or listed
# 约束条件:
# ✅ 子项挑最有代表性的(官方按编码顺序取前 8 个)
# ✅ 描述统一大小写风格——官方代码把全部行业标题统一转小写
# ❌ 别堆全部子项——官方注释:足够刻画特征即可,不堆文字墙
# ❌ 没有伞名的标签别硬造——官方 describe() 对 33 个无伞名组只列子项🔧 阈值标定脚本(用你自己的标注数据定 CONFIDENT)
预期效果:0.9 是官方 SEC 场景在 jev-1.12 上的实验值。换类目、换语言、换模型版本,门槛都要重新标定——这个脚本按候选门槛输出「自动采信比例 × 正确率」两张牌。
# 必填项:【】里换成你的标注样本(挑真实业务里最难判的一批:代表性案例 + 难例 + 反例)
GOLD = [
("【文本 1】", "【正确细级标签】"),
("【文本 2】", "【正确细级标签】"),
]
THRESHOLDS = [0.5, 0.6, 0.7, 0.8, 0.9, 0.95]
answers = {}
for text, _gold in GOLD:
response = client.system_one(state=text, questions=QUESTIONS)
a = response.answers["label"]
answers[text] = {"choice": a.choice, "confidence": a.confidence}
for t in THRESHOLDS:
auto = [(gold, answers[text]) for text, gold in GOLD if answers[text]["confidence"] >= t]
fine = sum(a["choice"] == gold for gold, a in auto)
coarse = sum(
COARSE_OF[a["choice"]] == COARSE_OF[gold]
for gold, a in auto
if a["choice"] != gold
)
print(f"threshold {t:.2f}: 自动采信 {len(auto)}/{len(GOLD)}, 细级正确 {fine}/{len(auto)}, 上报后粗级可用 {fine + coarse}/{len(auto)}")
# 约束条件:
# ✅ 标定样本要覆盖难例——全是好分的样本会把你骗到过低的门槛
# ✅ 门槛和问题定义放同一个文件,方便人审(官方 Agent Skill 建议)
# ❌ 别拿官方 0.9 直接上生产——官方 NOTE:正确阈值取决于你的领域和用例表现
# ❌ 金标先抽检再标定——过期的标签会把门槛带偏(官方实验前先筛样本)检查点 ✅
classify_sic.py跑出 444 → 75 → 10 的映射,group 35 落在 manufacturing- 60 份全跑完,level 列里 group 和 division 都出现了,没把握的那批 confidence 明显更低
- 单文件答案带 choice、probabilities、confidence 三个字段,答案永远是 75 个选项之一
- 我知道 39/60、27/30、12/30、48/60 的含义,且它们出自官方 jev-1.12(2026-08-12)录制
- 我知道 0.9 是 SEC 场景的实验门槛——换类目、换语言、换版本都要用标注数据重新标定
踩坑提醒
❌ 把「上报大类」做成第二次请求
现象:confidence 低于门槛时,再发一次请求问「那它属于哪个大类?」
为什么错:大类由组号唯一确定——division(组号) 查表就行。第二次请求多花钱,还引入新的不确定。官方原话:大类由行业组推出,所以没有第二次调用。
正确做法:门控分支里纯代码完成上报。同理,别把「不确定」做成第 76 个选项让模型选——要不要上报是代码根据 confidence 决定的,不是模型标签。
❌ 选项描述偷懒,只写标签名
现象:criteria 里每个选项只写 "manufacturing"、"services" 这种单词,答案质量差、confidence 普遍偏低。
为什么错:75 个行业组里 33 个在 SEC 列表里没有伞名,光给组号或单词,模型只能靠猜。官方用「伞名 + 最多 8 个组内细目」描述每个选项——读年报的人对照的本来就是这些细目。
正确做法:用第 2 步 describe() 的思路,给每个选项写「可对照的情境描述」;标签名和描述都会发给模型,描述才是区分选项的关键。
❌ 把 confidence 当「答案正确」的保证
现象:conf 1.00 的答案直接入库,不再抽查。
为什么错:confidence 描述的是概率分布的集中程度,不是答案正确的概率。官方实测里,有把握的 30 份也有 3 份是错的(27/30)。选项描述含糊、材料缺信息时,模型可以很自信地押错。
正确做法:把 confidence 当「要不要自动采信」的门控;题目质量另测——低置信的排查三方向(选项重叠 / 问题测了不止一件事 / state 信息不够)见 Confidence 置信度。
❌ 阈值照抄 0.9,版本跟着别名漂
现象:把 0.9 写死搬进自己的系统,一直用 jev-latest,某天新版本发布后行为变了。
为什么错:0.9 是官方 SEC 场景在 jev-1.12 上的实验值,官方 NOTE 明说正确的阈值取决于你的领域和用例表现;而且 jev-latest 别名会随新版本移动,官方建议调过阈值的请求锁定版本 ID。
正确做法:用自己的标注数据标定门槛(魔法 Prompt 第 3 个模板);生产请求锁定版本 ID,response.model 让日志里每个答案有版本可查。更多 Jev 的失败模式见 Jev 1.13 踩坑手册。
❌ 金标数据不自检,测出的「准确率」没意义
现象:拿一批「模型答案 vs 人工标签」算正确率,数字很难看,开始怀疑模型。
为什么错:SEC 的行业码是 filer 自己选的,会过期——公司卖掉代码所指的业务后还留着代码。官方做实验前,把 60 份筛到「文本支持所带代码」才测:测的是这套方法,不是 EDGAR 元数据的质量。
正确做法:标定和评估前先抽检金标——标签过期的样本要么修掉,要么单独归档,别让脏标签污染你的阈值。
进阶技巧(可选)
🔧 换信号微调门控。 confidence 之外,官方列过两个替代指标:top 概率 p_max(含义随选项数变,要按题设阈值)和 top-to-second 比 p_max / p_second(两强相争的题最顺手)。
一个几何直观:75 个选项时 confidence 和 p_max 数值几乎重合——它的价值在把选项数归一化,同一个 0.9 门槛在 3 个选项和 75 个选项里含义一致。公式与取舍见 Confidence 置信度。
🔧 类目单级装不下时,改走逐层分类。 一题 255 个选项是硬上限,约 240 内可靠。你的类目树单级超过这个数、或需要逐级标签时,参考上一课 深层级分类:官方用 Choice 概率做并行 beam search 逐层走类目树。本课的「平铺一次问完 + 置信度上报」适合 75 这种单级装得下的场景——两条路线互补。
🔧 零代码先看官方例子。 官方 cookbook 页面上留了一个 Playground 分享链接——一份年报 + 75 选项的完整问题,点开就能看概率分布和 confidence,不写代码。控制台 Playground 在 console.typesafe.ai/playground。
本课小结
✅ 一道 75 选项的 Choice 完成 Jev 文本分类:criteria 用「伞名 + 最多 8 个细目」描述每个选项,答案永远是选项之一。
✅ 置信度门控:confidence ≥ 0.9 报行业组,低于它上报所属大类——查表完成,零第二次调用;每份数据都带可用标签回来。
✅ 官方 60 份 SEC 年报实测:强制报行业组 39/60(65%),加门控 48/60(80%)——有把握的 90% 对,没把握的从 40% 提到 70%。
✅ 0.9 是 SEC 场景在 jev-1.12 上的实验门槛:换类目、换语言、换版本,都要用自己的标注数据重新标定。
关键配置字段回顾:
| 字段 / 常数 | 本页的值 | 说明 |
|---|---|---|
CONFIDENT | 0.9 | ≥ 报行业组、< 上报大类;官方 SEC 场景实验值 |
MAX_NAMED | 8 | 每个选项描述里列出的细目数上限 |
| 选项数 | 75 | Choice 硬上限 255,约 240 内可靠 |
| 判定输入 | answer.choice / .confidence / .probabilities | choice 即最高概率选项 |
division(group) | 查表 | 行业组组号 → 10 个大类,纯代码 |
相关教程:
- 自一致性:给审核系统加「不确定」出口——同款「不确定就升级/转人工」思路,用在内容审核上
- 深层级分类:把内容分到类目最深处——类目单级装不下时的逐层走法
- 智能路由:意图分类加置信度门控——置信度门控做成完整分流系统的展开
下一课预告
同一道题只问一次,怎么知道它「稳不稳」?下一课:自一致性:给审核系统加「不确定」出口。
你会学到:
- 自一致性体检:同一条内容换新 uid 重复问 15 次,找出会翻标签的题
- Choice 题 top 概率低于 0.60 自动转人工,Noul 题 0.30–0.70 设不确定带
- 官方实测:加出口后决策一致率从 90.8% 升到 99.2%,代价是 25.8% 转人工
- 和本课的关系:本课用单次答案的 confidence 决定「报粗还是报细」,下一课用重复采样决定「哪道题根本不该自动执行」