
Jev
Jev 类型安全判断 API 入门:Noul、Choice、Score 三种问题类型与 Python 调用
Jev 是面向软件调用的判断型模型,用 Noul、Choice、Score 三种问题类型返回带类型的判断值与概率。本文介绍 state 与 questions 的组织方式、三种类型的输入输出结构、HTTP API 与 Python 调用方法,以及阈值设定、概率校准等落地注意事项。
Jev 是一个面向软件调用的判断型模型:它不负责写文章,而是把「这段文本属于哪一类」「这件事是否成立」「程度落在哪一档」这类判断,以带类型的值和概率返回。它适合已经能处理 JSON API 的工程师,用来做工单分流、紧急度判定、内容审核、贡献度评估等需要把结果直接接进代码分支的场景。站内也有对应的工具页可以对照查看:Jev。本文按公开信息整理三种问题类型的用法、请求结构、Python 调用方式与实测中值得注意的细节。
准备工作
在动手写代码之前,需要先确认几件事。
- 账号与 API 密钥:在控制台的「API keys」页面创建密钥。密钥按组织为单位管理,创建后显示的密文只出现一次,需要立刻保存。把密钥放进环境变量或密钥管理服务,不要写进代码仓库。
- 请求头:调用时把密钥作为 Bearer 令牌放进 HTTP 请求头。
- 模型版本:请求体里用
model字段指定版本。为了结果可复现,建议固定一个具体版本号,而不是依赖默认值。本文示例统一使用jev-1.13.0。 - 输入形态:Jev 只接受文本输入,
state可以是字符串、JSON 对象或数组。它不直接处理图片、音频、视频。 - 语言:公开文档以英语作为主要训练语言,其他语言建议用真实数据自行评估效果,不要假设英语上的表现可以直接迁移。
- 费用:价格与配额属于易变信息,务必以官网当前公布的信息为准,不要按本文或任何二手资料里的数字做预算。
另外要区分两件事:Jev 返回的是「判断结果与概率」,不是业务上的正确答案。概率经过校准,指的是在大量同类样本上预测概率与实际发生比例大致吻合,而不是「这一条判断有 80% 的概率是对的」。阈值怎么定、中间地带要不要转人工,都由调用方决定。
操作步骤
第一步:把输入拆成 state 与 questions
一次请求由两部分组成:state 是判断对象以及判断所需的上下文,questions 是针对同一个 state 的一组问题。组织输入时,把「判断什么」和「按什么标准判断」分开,结构会清楚很多。
| 字段 | 作用 | 工单分类场景示例 |
|---|---|---|
state | 判断对象与必要上下文 | 工单正文、退款政策文本 |
questions | 针对同一 state 的问题集合 | 负责部门、紧急程度、不满强度 |
每个问题的 type | 返回值类型 | noul、choice、score |
每个问题的 instructions | 要判断什么 | 应由哪个团队处理 |
每个问题的 criteria | 候选或评价标准 | 技术团队处理缺陷与故障 |
几个关键机制需要先理解:
- state 只读一次,同一请求内的各个问题针对同一份 state 并行评估。
- 问题之间相互独立,同一个请求里的问题不会互相参考答案。所以不要设计「先判断 A,再根据 A 判断 B」的链式问题,而是拆成独立问题,在应用层组合结果。
- 问题 ID 由调用方指定,返回值按同样的 ID 组织,方便和原字段对应。ID 不会发送给模型,也不参与推理,所以
q1这类机械命名也能跑,但取一个能读懂判断意图的名字更利于维护。 - instructions 与 criteria 里可以用对象和数组,不只是字符串。条件复杂时,把判断拆成更小的问题,再用代码合并输出,通常比堆一段长描述更可靠。
第二步:选择问题类型
| 类型 | 问的是什么 | 主要返回值 | 典型场景 |
|---|---|---|---|
| Noul | 是或否 | 0 到 1 之间的 noul 值 | 是否在要求紧急处理 |
| Choice | 候选中的哪一个 | 选中项、各候选概率、confidence | 属于账单、技术还是销售 |
| Score | 落在定义好的哪一档 | 档位期望值、各档概率、legend、confidence | 不满强度处于三档中的哪一档 |
第三步:写 Noul 问题
Noul 用于真伪判断,返回的 noul 表示「为真」的概率,越接近 1 越偏向「是」,越接近 0 越偏向「否」。Noul 没有单独的 confidence 字段。
下面这个例子把冰淇淋三明治的定义放进 state,问它是不是三明治,并在 criteria 里分别定义 true 和 false 的含义:
{
"state": {
"food": "Ice cream sandwich",
"definition": "An ice cream sandwich is a frozen dessert with a layer of ice cream between two cookies, wafers, or thin pieces of cake."
},
"model": "jev-1.13.0",
"questions": {
"is_sandwich": {
"type": "noul",
"instructions": "Is `food` a sandwich?",
"criteria": {
"true": "A sandwich is a food dish where a filling, such as meat, cheese, vegetables, or spread, is placed between structural starch",
"false": "The food has no bread enclosing a filling or uses only a single slice of bread, or uses a non-bread wrapper such as a tortilla, wafer, or cookie."
}
}
}
}
返回结构如下:
{
"answers": {
"is_sandwich": {
"type": "noul",
"noul": 0.33
}
}
}
这个输入得到的结果是 0.33。因为 criteria.false 里明确把「用威化或饼干夹起来」也算作非三明治,结果偏向「不是」这一侧是符合定义的。同一个问题重复执行时数值不会完全一致,实测中在 0.33 到 0.35 之间浮动。
在代码里使用 Noul 时,不要把返回值直接当成布尔值。应当根据业务上的损失来设定阈值:如果误判为「是」的代价很高,就把阈值定高一些,并把落在中间地带的结果转给人工处理。
第四步:写 Choice 问题
Choice 用于从给定候选中选一个。返回的 choice 是概率最高的候选,probabilities 是每个候选的概率,confidence 用 0 到 1 概括分布的集中程度。注意 confidence 既不是「选中项的概率」,也不是「正确率」,公开资料没有给出它的具体计算公式,不要按某个公式去反推。
候选可以只写名字,也可以附带描述信息。下面这个例子同时提了两个问题,一个只给候选名,另一个给候选配上说明和色值:
{
"state": { "condition": "Tornado's a-comin" },
"model": "jev-1.13.0",
"questions": {
"sky_color": {
"type": "choice",
"instructions": "What color is the sky?",
"criteria": {
"indigo": "",
"electric blue": "",
"baby blue": "",
"gray": "",
"lavender": "",
"salmon": "",
"seafoam green": ""
}
},
"sky_color_with_desc": {
"type": "choice",
"instructions": {
"object": "sky",
"question": "what color is `object`?"
},
"criteria": {
"indigo": {
"description": "very dark, deep blue, like fountain-pen ink",
"hex": "#280868",
"rgb": { "red": 40, "green": 8, "blue": 104 },
"hsl": { "hue": 260, "saturation": "86%", "lightness": "22%" }
},
"electric blue": {
"description": "bright, punchy blue, like a swimming pool",
"hex": "#1585E7",
"rgb": { "red": 21, "green": 133, "blue": 231 },
"hsl": { "hue": 208, "saturation": "83%", "lightness": "49%" }
},
"baby blue": {
"description": "a very pale, soft blue, like a robin's egg",
"hex": "#C2E4F1",
"rgb": { "red": 194, "green": 228, "blue": 241 },
"hsl": { "hue": 197, "saturation": "63%", "lightness": "85%" }
},
"gray": {
"description": "a pale slate, like a heron's feathers",
"hex": "#B1CBD1",
"rgb": { "red": 177, "green": 203, "blue": 209 },
"hsl": { "hue": 191, "saturation": "26%", "lightness": "76%" }
},
"lavender": {
"description": "a dusty blue-purple, like wisteria blossoms",
"hex": "#847CBF",
"rgb": { "red": 132, "green": 124, "blue": 191 },
"hsl": { "hue": 247, "saturation": "34%", "lightness": "62%" }
},
"salmon": {
"description": "a warm, almost-orange pink, like coral",
"hex": "#FB7A65",
"rgb": { "red": 251, "green": 122, "blue": 101 },
"hsl": { "hue": 8, "saturation": "95%", "lightness": "69%" }
},
"seafoam green": {
"description": "a muted, grayish green, like dried eucalyptus leaves",
"hex": "#94BDB5",
"rgb": { "red": 148, "green": 189, "blue": 181 },
"hsl": { "hue": 168, "saturation": "24%", "lightness": "66%" }
}
}
}
}
}
返回结果:
{
"answers": {
"sky_color": {
"type": "choice",
"choice": "seafoam green",
"confidence": 0.48,
"probabilities": {
"electric blue": 0,
"seafoam green": 0.56,
"gray": 0.4,
"baby blue": 0,
"indigo": 0.03,
"lavender": 0,
"salmon": 0
}
},
"sky_color_with_desc": {
"type": "choice",
"choice": "indigo",
"confidence": 0.33,
"probabilities": {
"electric blue": 0,
"seafoam green": 0.36,
"gray": 0.21,
"indigo": 0.43,
"baby blue": 0,
"lavender": 0,
"salmon": 0
}
}
}
}
两点值得注意。第一,返回的概率保留两位小数,前一个问题的概率合计是 0.99,不要把它当成严格归一化的分布去做校验。第二,这两个问题不仅候选描述不同,instructions 的结构和附加的色值也不同,所以不能据此得出「加了描述所以答案变了」的结论。同一个 state 下,只要问题和候选的定义变了,输出分布就可能变。
第五步:写 Score 问题
Score 按定义好的有序档位评估状态。返回值包含 score、各档位的 probabilities、把编号和说明对应起来的 legend,以及 confidence。score 是档位编号的期望值,也就是每个编号乘以对应概率再求和,因此可能是小数。它不是「概率最高的那一档」。
下面这个例子用五档评估两个主体对一件作品的贡献程度:
{
"state": {
"scenario": "Naruto is a Celebes crested macaque living in the Tangkoko nature reserve in North Sulawesi, Indonesia. David Slater, a wildlife photographer shooting macaques in the reserve, leaves his camera unattended, and Naruto repeatedly activates the shutter, producing hundreds of images, including a remarkably sharp, grinning self-portrait reminiscent of a human selfie. Slater later processes and publishes the best photographs in a book that names him as the copyright owner.",
"subject": "Naruto",
"human": "Slater",
"creative_work": "the grinning self-portrait"
},
"model": "jev-1.13.0",
"questions": {
"subject_contribution": {
"type": "score",
"instructions": "How much did `subject` contribute to `creative_work`?",
"criteria": ["None", "Minorly", "Moderately", "Majorly", "Completely"]
},
"human_contribution": {
"type": "score",
"instructions": "How much did `human` contribute to `creative_work`?",
"criteria": ["None", "Minorly", "Moderately", "Majorly", "Completely"]
}
}
}
返回结果:
{
"answers": {
"subject_contribution": {
"type": "score",
"score": 3.43,
"confidence": 0.56,
"legend": {
"0": "None",
"1": "Minorly",
"2": "Moderately",
"3": "Majorly",
"4": "Completely"
},
"probabilities": {
"0": 0,
"1": 0,
"2": 0.03,
"3": 0.48,
"4": 0.49
}
}
}
}
另一个问题的结果是 1.19,confidence 为 0.69。两个问题都返回了 0 到 4 这五个编号上的概率分布。
这里有一个容易踩的坑:用返回的两位小数概率去反算 score,结果和返回值对不上。上面这个例子按显示概率算是 3.46,实际返回 3.43;另一个问题按显示概率算是 1.21,实际返回 1.19。仅凭这些记录无法确定差异来源,所以实现里应当同时使用返回的 score 和概率,不要自己用显示值重算一遍再覆盖它。
第六步:调用 HTTP API
接口是 POST,请求体包含 state、model、questions 三部分。下面用 Python 标准库 urllib 演示,一次请求同时判断「负责部门」「不满强度」「紧急程度」三件事:
import json
import os
from urllib.request import Request, urlopen
api_key = os.environ["JEV_API_KEY"]
payload = {
"state": "今朝からAPIが500エラーを返し続けていて、本番サービスが止まっています。至急対応してください!",
"model": "jev-1.13.0",
"questions": {
"department": {
"type": "choice",
"instructions": "この問い合わせはどのチームが担当すべきか",
"criteria": {
"billing": "請求・支払い・契約に関する問い合わせ",
"technical": "不具合・障害・仕様に関する問い合わせ",
"sales": "導入検討・見積もりに関する問い合わせ"
}
},
"urgency": {
"type": "noul",
"instructions": "この問い合わせは緊急の対応を求めているか",
"criteria": {
"true": "サービス停止やデータ損失など、即時の対応が必要な状態",
"false": "即時の対応を要しない状態"
}
},
"dissatisfaction": {
"type": "score",
"instructions": "この問い合わせに表れている不満の強さ",
"criteria": ["None", "Mild", "Moderate", "Strong", "Severe"]
}
}
}
request = Request(
"https://api.typesafe.ai/v1/jev",
data=json.dumps(payload).encode("utf-8"),
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}"
},
method="POST"
)
with urlopen(request) as response:
result = json.loads(response.read().decode("utf-8"))
answers = result["answers"]
# Choice:取最高概率候选
department = answers["department"]["choice"]
# Noul:按业务损失设定阈值,中间地带转人工
urgency_score = answers["urgency"]["noul"]
if urgency_score >= 0.8:
route = "oncall"
elif urgency_score <= 0.2:
route = "normal"
else:
route = "human_review"
# Score:使用返回的期望值,不要用显示概率重算
dissatisfaction = answers["dissatisfaction"]["score"]
print(department, route, dissatisfaction)
要点是:state 只发一次,三个问题并行评估;每个问题的 ID 在返回的 answers 里原样出现,直接按 ID 取值即可;Noul 的阈值由业务决定,不要写死在库里。
一个完整示例
把上面的步骤串起来,做一个最小可运行的工单分流脚本。它读取环境变量里的密钥,发一次请求,然后按结果决定路由。
import json
import os
from urllib.request import Request, urlopen
API_URL = "https://api.typesafe.ai/v1/jev"
MODEL = "jev-1.13.0"
def judge(ticket_text):
payload = {
"state": ticket_text,
"model": MODEL,
"questions": {
"department": {
"type": "choice",
"instructions": "Which team should handle this inquiry?",
"criteria": {
"billing": "Questions about invoices, payments, or contracts",
"technical": "Questions about defects, outages, or behavior",
"sales": "Questions about evaluation, pricing, or procurement"
}
},
"urgent": {
"type": "noul",
"instructions": "Does this inquiry require urgent handling?",
"criteria": {
"true": "The service is down or data is at risk, requiring immediate action",
"false": "No immediate action is required"
}
},
"severity": {
"type": "score",
"instructions": "How strong is the dissatisfaction expressed in this inquiry?",
"criteria": ["None", "Mild", "Moderate", "Strong", "Severe"]
}
}
}
request = Request(
API_URL,
data=json.dumps(payload).encode("utf-8"),
headers={
"Content-Type": "application/json",
"Authorization": "Bearer " + os.environ["JEV_API_KEY"]
},
method="POST"
)
with urlopen(request) as response:
return json.loads(response.read().decode("utf-8"))["answers"]
def route(answers):
department = answers["department"]["choice"]
urgent = answers["urgent"]["noul"]
severity = answers["severity"]["score"]
if urgent >= 0.8:
queue = "oncall"
elif urgent <= 0.2:
queue = "standard"
else:
queue = "human_review"
return {
"department": department,
"queue": queue,
"severity": severity,
"raw": answers
}
if __name__ == "__main__":
text = "Our production API has been returning 500 errors since this morning and the service is down. Please respond immediately."
answers = judge(text)
print(json.dumps(route(answers), ensure_ascii=False, indent=2))
运行前先设置环境变量:
export JEV_API_KEY="你的密钥"
python ticket_router.py
输出里会包含选中的部门、按阈值决定的路由队列、不满强度的期望值,以及完整的原始返回,便于排查。如果要把结果写进数据库或日志,建议把 answers 整体保留,因为概率分布和 confidence 在事后分析阈值是否合理时很有用。
注意事项
- 概率是校准过的,但不是单条正确率。校准说的是在大量样本上预测概率与实际发生比例大致吻合。不要把它读成「这一条有 80% 的概率是对的」。
- 重复执行结果不会完全一致。同一个输入多次调用,数值会在小范围内浮动。需要可复现时固定
model版本,并在业务上容忍这个波动。 - 不要用显示概率反算 Score。返回的概率保留两位小数,按它重算期望值会和返回的
score有偏差,直接用返回值。 - Choice 的概率合计可能不是 1。同样是显示精度造成的,不要拿它做严格校验。
- confidence 的含义要自己确认。公开资料没有给出它的计算公式,不要假设它等于选中项概率或正确率,业务阈值应当基于自己的数据来定。
- 问题之间相互独立。同一请求内的问题不会互相参考答案,需要链式判断时拆成多个问题,在应用层组合。
- 问题 ID 不参与推理。它只用于把返回值和调用方字段对应起来,命名可以自由,但建议取可读的名字。
- 只支持文本。图片、音频、视频不能直接作为输入。
- 非英语输入需要自行评估。公开文档以英语为主要训练语言,其他语言建议用真实数据验证效果。
- 复杂条件要拆小。与其写一段很长的判断说明,不如拆成多个小问题,再用代码合并输出,例如把「是否要求退款」和「按规则是否符合退款条件」分开问。
- 阈值由调用方决定。Jev 不会替你决定多高的概率算「是」,要按误判成本来设定,并为中间地带设计人工兜底。
- 价格与配额以官网为准。计费方式、可用模型版本、限额都可能调整,接入前请查阅当前公布的信息。