Noul 是非题:拿到 0 到 1 的真实概率
上一课的 Score 测「到什么程度」。但很多判断连程度都不用问,就是一刀的是与非:这条消息要不要转人工、这份简历提没提分布式系统、这条评论含不含个人信息。
这类问题交给 Jev Noul:出一道是非题,Jev 返回「答案为是」的概率——0 到 1 之间的一个数。
其实你在第一个请求里已经用过它——工单示例里的 is_urgent 就是一道 Noul,那条「API 全线 500」的工单拿回了 0.99。这节课把它讲透,全程 8 分钟。
学完你能做什么
- 写出一道干净的 Jev Noul 是非题:一个是非一题,接近 1 稳定代表「是」,边界微妙时用
criteria写清 true / false - 读懂
noul数值:接近 1 强「是」、接近 0 强「否」、接近 0.5 五五开,并说清为什么没有单独的 confidence - 用 0.7 / 0.3 起点阈值把概率变成代码分支,按错误的代价上下调
- 一次请求发一串 Noul 批量核查清单,用结构化
instructions让代码批量生成问题 - 说清什么时候用 Noul、什么时候换 Score——0.5 不是「中等程度」
你现在的困境
- 让 GPT 回答是非题,它先分析三段再下结论,还爱说「总体而言是的,但也不完全」——代码没法用
- 同一条消息问两遍,一次「是」一次「否」,没有稳定的概率,阈值永远定不下来
- 硬让它只回数字,0.9 和 0.6 都说得出口,但没人知道这两个数字是不是同一种「确定」,该复核哪些全靠猜
什么时候用这一招
当你需要:
- 干净的是非判断:消息是否在要退款、简历是否提到分布式系统、评论是否包含个人信息
- 概率直接进代码:过阈值接
if、按概率排序、多题组合后加权 - 一串是非条件一次问完:清单式核查,一个条件一道题
而且不想:
- 解析聊天模型自由发挥的「是的,不过……」
- 把是非标准混进 prompt,模型一换版标准就漂
- 在模型自己都没把握时(值落在 0.5 附近)还放它自动执行
🎒 开始前的准备
- 完成了 State 状态:知道要判断的内容放在
state里(字符串、JSON 对象、文本数组都行) - 跑通过第一个请求:
.venv里装好了typesafe-sdk,TYPESAFE_API_KEY已在环境变量里
核心思路
Noul 回答「是不是」。 官方给的适用场景:这条消息是否在要退款、这份简历是否提到分布式系统、这条评论是否包含个人信息——答案只有两种,而且概率本身就是有用的信号。这个概率是校准的:Jev 为校准的决策训练,概率对齐真实结果,0.7 这样的数字才经得起当阈值用。
一道 TypeSafe Noul 题只有三个字段:
| 字段 | 写什么 | 例子 |
|---|---|---|
type | 固定 "noul" | — |
instructions | 完整的是非问题,或一句让模型判断的陈述 | Is the customer asking for a human agent? |
criteria | 可选:true / false 各一句话,说清什么算「是」、什么算「否」 | 见第 1 步 |
问题 ID 自取(如 is_human_escalation),不会发给模型——完整问题必须写在 instructions 里,哪怕 ID 看起来已经不言自明。
返回就一个数。 noul = 答案为「是」的概率:0 是否、1 是。没有单独的 confidence——是非只有两种结果,一个概率就把整个分布说完了;Choice / Score 的概率铺在多个选项或等级上,才需要 confidence 来概括分布形状。
三种题型怎么选。
| 题型 | 答案形态 | 代码里的用法 |
|---|---|---|
| Choice | 固定选项挑一个,选项之间没有顺序 | 映射到 N 条代码路径 |
| Score | 光谱上的位置,每级你能描述 | 过阈值、排序 |
| Noul(本课) | 干净的是/否,概率本身是信号 | 一个 if |
官方判据:固定选项、无顺序 → Choice;光谱上每级能描述 → Score;干净是非且概率有用 → Noul。两个都像时,选答案能被代码直接行动的那个。
noul 不是程度尺。 它只测「你问的那个命题为真」的概率。0.5 = 五五开,不是「中等」——「候选人 Python 强不强」里的「强」没定义清楚时,0.5 什么也说明不了。要量程度,用 Score 的定义等级(官方对照实验见踩坑提醒第 3 条)。
跟我做
第 1 步:写出一道干净的 Jev Noul 是非题(instructions 与可选 criteria)
为什么:一道 Jev 是非题的 noul 值,含义完全由措辞决定。两个条件挤一题、方向反着写、边界模糊——数值就读不懂,阈值也白定。
官方写题守则:
- 一道 Noul 只问一个是非。「客户是否又生气又在要退款?」是两个判断,模型只能一起估,值的意义变浑。拆成两道,代码里组合(第 4 步就是这么做的)
- 让接近 1 稳定代表「是」。 Does the message contain personal data? 很清楚;Is the message free of personal data? 方向反了,读数的代码会整体接反
- 陈述句和疑问句都行。 The customer is requesting a refund——接近 1 就是陈述为真。两种措辞都在自己的数据上试试,留下表现好的
- 边界不留中间地带。 Does this candidate have any Python experience? 里的 any 把边界钉死;边界微妙时,加
criteria写清 true / false 各算什么
边界微妙的官方例子——「是否重复联系」加了 criteria:
from typesafe_sdk import Noul, NoulCriteria
Noul(
instructions="Has the customer contacted support about this before?",
criteria=NoulCriteria(
true="Mentions a prior attempt, ticket, or that they have asked before",
false="No sign of any previous contact",
),
)两个补充:
- 官方原话:instruction is enough for most Nouls——大多数题一句话就够,先不加
criteria跑一版,答案不理想再补 - state 是结构化对象时,用反引号包住的路径点名要判断的字段:Does
ticket.messages[0].textrequest a refund?
你应该看到:一道措辞干净的是非题——只有一个判断、接近 1 明确代表「是」、边界没有歧义。
- 我的问题只有一个是非判断,且接近 1 明确代表「是」
第 2 步:用 typesafe-sdk 发出第一个 Noul 请求
为什么:先把官方双题示例原样跑通,建立「已知正确」的基线——换成自己的问题后排错才有参照。
新建 noul_support.py,贴入官方示例(一条要求转人工的客服消息,同时问两件事):
from typesafe_sdk import Noul, NoulCriteria, TypeSafeClient
with TypeSafeClient() as client:
response = client.system_one(
model="jev-latest",
state="I have asked three times now. Can I please just talk to a real person?",
questions={
"is_human_escalation": Noul(
instructions="Is the customer asking for a human agent?",
),
"is_repeat_contact": Noul(
instructions="Has the customer contacted support about this before?",
criteria=NoulCriteria(
true="Mentions a prior attempt, ticket, or that they have asked before",
false="No sign of any previous contact",
),
),
},
)
print(response.answers["is_human_escalation"].noul)
print(response.answers["is_repeat_contact"].noul)运行:
.venv/bin/python noul_support.py你应该看到:
0.99
0.93官方的解读:消息明说 talk to a real person,「要人工」是 0.99;I have asked three times now 命中 is_repeat_contact 的 true 描述(提到以前问过),是 0.93。示例值可能随 Jev 版本变化,不必纠结小数点后的差异。
- 我跑出了两个接近 1 的数(示例值 0.99 / 0.93)
💡 中文内容能不能判断
可以。官方说明:英语是主要训练语言、准确度最好,包括中文在内的其他语言能用但准确度较低。中文场景上线前先用自己的数据跑一批;对没有 confidence 的 Noul,重点看有多少值落进 0.3–0.7 的不确定带(第 4 步)。
第 3 步:读懂 noul 数值:0、0.5、1 各代表什么
为什么:单个数字没有参照系读不懂。官方为同一道题(is_human_escalation)录了六条不同消息的答案,是最直观的标尺。
先看第 2 步请求的完整返回:
{
"model": "jev-1.13.0",
"answers": {
"is_human_escalation": {
"type": "noul",
"noul": 0.99
},
"is_repeat_contact": {
"type": "noul",
"noul": 0.93
}
},
"usage": {
"input_tokens": 360,
"output_tokens": 39
}
}你应该看到:每个答案对象里只有 type 和 noul 两个字段,noul 和你 print 出来的 0.99 / 0.93 一致。"model": "jev-1.13.0" 是官方录制示例时的版本;你现在用 jev-latest 跑,这里显示当前模型版本。
六条消息的对照(官方录制的 jev-1.13.0 答案,消息为原始英文 state):
| 客户消息(state) | noul |
|---|---|
| Thanks, that fixed it! | 0.02 |
| How do I reset my password? | 0.07 |
| I need this sorted today, whatever it takes. | 0.26 |
| Are you a bot? | 0.40 |
| Is there any way to speak to someone about my invoice? | 0.84 |
| I have asked three times now. Can I please just talk to a real person? | 0.99 |
怎么读:
- 这个数是答案和确定性合在一起的。 接近 1 强「是」,接近 0 强「否」,接近 0.5 是模型给是/否差不多的概率——它自己也不确定
- 0.26:很急,但没要人。 I need this sorted today 传递紧迫感,但从没开口要人工——两个判断分得开,值就低
- 0.40:暗示,没明说。 Are you a bot? 暗示想找人却没直说,模型几乎五五开
- 官方点明:0.26 和 0.40 这类「落中间」的值,正是要靠你代码里的阈值来裁决的消息
为什么没有 confidence? Choice / Score 的概率铺在多个选项或等级上,confidence 把分布形状概括成一个数;是非只有两种结果,一个 noul 已经完整描述了分布。想和 Choice / Score 用同一套门槛时可以自算,见进阶技巧的 |2p − 1|。
💡 另一个取值入口
快速开始课的示例用 response.nouls["is_urgent"].noul,本课的 primitives 示例用 response.answers["id"].noul——两个入口指向同一批答案,照官方示例抄即可。
第 4 步:设 noul 概率阈值,把概率变成代码分支
为什么:概率要变成行动才算用起来。Noul 最常见的用法就是阈值切成布尔值,接一个 if。
最简形态(官方示例):
wants_human = response.answers["is_human_escalation"].noul > 0.9
if wants_human:
route_to_agent(ticket)
else:
route_to_bot(ticket)阈值定在哪?官方给的起点与调整规则:
| 规则 | 做法 |
|---|---|
| 起点 | yes ≥ 0.7、no ≤ 0.3 |
| 只关心「是」 | 0.7 一个阈值就够 |
| 假「是」代价高 | 调高——比如叫醒值班的人、直接退款 |
| 漏掉真「是」代价高 | 调低——比如漏标安全问题 |
| 0.3–0.7 之间 | 不走代码路径,转人处理 |
| 避免 | 贴着寥寥几个测试用例把阈值精调到「全对」 |
完整路由示例(官方):两道题一次问完,任一题落中间就转人工,重复联系抬高优先级:
from typesafe_sdk import Noul, NoulCriteria, TypeSafeClient
SUPPORT_QUESTIONS = {
"is_human_escalation": Noul(
instructions="Is the customer asking for a human agent?",
),
"is_repeat_contact": Noul(
instructions="Has the customer contacted support about this before?",
criteria=NoulCriteria(
true="Mentions a prior attempt, ticket, or that they have asked before",
false="No sign of any previous contact",
),
),
}
YES = 0.7
NO = 0.3
def route(message: str) -> None:
with TypeSafeClient() as client:
response = client.system_one(
model="jev-latest",
state=message,
questions=SUPPORT_QUESTIONS,
)
answers = response.answers
wants_human = answers["is_human_escalation"].noul
repeat = answers["is_repeat_contact"].noul
if NO < wants_human < YES or NO < repeat < YES:
# The model isn't sure either way. Let a person decide.
send_to_review(message)
return
priority = "high" if repeat >= YES else "normal"
if wants_human >= YES:
route_to_agent(message, priority=priority)
else:
route_to_bot(message, priority=priority)route_to_agent / route_to_bot / send_to_review 是你自己的处理函数,官方示例只演示判断逻辑。
你应该看到:
- 那条「asked three times」消息:0.99 / 0.93 双双过 YES → 转人工,且 repeat ≥ 0.7 把优先级抬成 high
- How do I reset my password?:两题都是 0.07 → 走机器人
上线后怎么调?官方给了两条方向,外加一个不变量:
复核的人看不过来 → 收窄 NO 和 YES 的间距,让更多消息落进代码路径
错误路由变多 → 加宽间距,让更多消息转人
想加新判断(是否提到付款、是否含个人信息)→ 往
SUPPORT_QUESTIONS加一道 Noul,请求数还是 1我的代码里有明确的 YES / NO 阈值,0.3–0.7 之间的值有人工出口
第 5 步:一次请求发一串 Noul:清单批量核查与结构化 instructions
为什么:很多判断天然是清单——简历和候选库里每条记录是否同一个人、文档每对相邻行是否被换行切断。官方做法:一个条件一道 Noul,全部塞进一个请求;问题并行评估,加题几乎不增加响应时间,只多花一点问题 token(一次请求批量发 13 个问题的实测收益见并行提问)。
每题还要带数据时,把 instructions 写成对象:问题固定在一个字段,数据放其他字段,由代码批量生成。官方的简历查重例子——新到的简历和候选库里可能是同一个人的记录逐一对比:
from typesafe_sdk import Noul, TypeSafeClient
SAME_PERSON = "Is the resume for the same person as `potential_duplicate`?"
def duplicate_questions(candidates: list[dict]) -> dict[str, Noul]:
"""One Noul per candidate record, all asking the same question."""
return {
f"same_as_record_{candidate['id']}": Noul(
instructions={
"potential_duplicate": {
"name": candidate["name"],
"location": candidate["location"],
"last_employer": candidate["last_employer"],
},
"question": SAME_PERSON,
},
)
for candidate in candidates
}
def find_duplicates(resume: dict, candidates: list[dict]) -> list[str]:
with TypeSafeClient() as client:
response = client.system_one(
model="jev-latest",
state={"resume": resume},
questions=duplicate_questions(candidates),
)
return [
question_id
for question_id, answer in response.answers.items()
if answer.noul > 0.7
]三个细节:
- 问题 ID 由代码拼上记录的数据库 ID(
same_as_record_18),答案原样挂回同一个 ID question字段所有题一模一样,变的只是potential_duplicate里的数据find_duplicates用 0.7 筛「是」——中间值同样应该转人,和第 4 步一个思路
官方示例的返回(三条候选记录):
{
"model": "jev-1.13.0",
"answers": {
"same_as_record_18": {
"type": "noul",
"noul": 0.74
},
"same_as_record_42": {
"type": "noul",
"noul": 0.09
},
"same_as_record_77": {
"type": "noul",
"noul": 0.08
}
},
"usage": {
"input_tokens": 535,
"output_tokens": 58
}
}官方解读:记录 18 名字拼写不同、但地点和雇主都对上 → 0.74;记录 42 同名、不同城市不同雇主 → 0.09;记录 77 名字相近、地点相同、雇主不同 → 0.08。
你应该看到:find_duplicates() 返回 ['same_as_record_18']——只有 0.74 过了 0.7 阈值。
同样的结构化 instructions 还能逐字段核查便宜模型的提取结果——级联提取场景页展开这个用法(每个字段同一套问题,main_question 放问题文本,field_spec、extracted_field 逐字段变化)。
检查点 ✅
- 我知道
noul是「答案为是」的概率:接近 1 强是、接近 0 强否、接近 0.5 五五开 - 我跑出了官方示例的 0.99 / 0.93,能说出它们分别对应消息里的哪句话
- 我知道 Noul 答案没有单独的 confidence,以及为什么
- 我的起点阈值是 yes ≥ 0.7、no ≤ 0.3,0.3–0.7 转人,并按错误的代价调整
- 我的一道 Noul 只问一个是非,批量核查时一条件一题、一次请求发完
踩坑提醒
❌ 一道 Noul 塞两个条件
instructions: "Is the customer angry and asking for a refund?"「生气」和「要退款」是两个判断,挤在一道题里模型只能一起估:一个条件真、一个条件假的消息没位置放,0.6 说不清是「有点生气但没要钱」还是「没生气但要钱」。
正确做法:一个条件一道 Noul,一次请求并行发出,代码里组合——第 4 步的 route() 就是「要人工 × 重复联系」两题组合的例子。
❌ 反着问,接近 1 变成「否」
反例:Is the message free of personal data? # 接近 1 = 没有个人信息,和读数直觉相反
正例:Does the message contain personal data? # 接近 1 = 有个人信息方向反了数值照样能跑,但三个月后读这段代码的人(包括你自己)会按直觉把高值当「有问题」——整个逻辑接反。官方守则:措辞让接近 1 稳定代表「是」。
❌ 拿 noul 当程度尺:0.5 不是「中等」
想测「候选人 Python 强不强」?官方用同一批候选人做了 Noul 与四级 Score 的对照:
| 候选人自述(state) | Noul:Is the candidate strong in Python? | Score:四级经验量表 |
|---|---|---|
| My experience is in Java and Go. I have not used Python. | 0.03 | 0.0(No experience) |
| I have used Python occasionally for small scripts alongside my main Java work. | 0.14 | 1.0(Some familiarity) |
| I used Python every day for two years in my last job, mostly data pipelines. | 0.81 | 2.05(Regular use in a job) |
| I have written Python daily for eight years, including maintaining a large Django codebase. | 0.92 | 2.89(Deep expertise) |
Noul 只判断「强」这一个命题,值是它为真的可能性。你可以在代码里把 0.3–0.7 定义成「有些经验」,但模型看不到你的定义——中间值可能是「中等经验」,也可能是「说不清」,候选人之间的间距也不是你选的。Score 逐级对照你写的描述,每个候选人都落在你定义的等级上或附近;不同意哪级,改描述重跑。
正确做法:要是非决策,把条件定义清楚再问——官方例句 Does the resume state that the candidate has used Python at work?;要量程度,用 Score 的定义等级。
❌ 阈值贴着几个测试用例精调
拿五六条测试消息反复跑,把阈值调到每条都分对——官方明确反对:换一批用例,这个「完美阈值」往往就靠不住。正确做法:从 0.7 / 0.3 起步,按第 4 步的代价规则调,中间值给人,上线后按复核量和错误率收窄或加宽。
更多通用失败模式(字面理解、数数不可靠、日期比较放代码里)见 Jev 1.13 踩坑手册。
进阶技巧(可选)
给 Noul 自算一个「置信度」。 Noul 没有 confidence,但想用同一套门槛代码处理 Noul 和 Choice / Score 时,官方给了公式——取离 0.5 的距离:
p = answer.noul
confidence = abs(2 * p - 1) # p=0.5 时为 0,p=0 或 1 时为 1它就是把 Choice 的 confidence 公式套在「是/否」两个选项上,因此和 Choice 的 confidence 同标尺。公式的推导在下一课 Confidence 置信度。
疑问句和陈述句都试试。 The customer is requesting a refund 这样的陈述句和疑问句效果一样——接近 1 就是陈述为真。官方建议两种措辞都在自己的数据上跑一遍,留下表现好的那个。
不写代码先体验。 控制台的 Playground(https://console.typesafe.ai/playground)里贴上 state、加上问题,免代码看答案。
绕过 SDK 直连 API。 POST https://api.typesafe.ai/v1/systemone,noul 问题的原始 JSON 长这样(快速开始课的 is_urgent):
{
"state": "Our API integration started returning 500 errors on every request about 20 minutes ago, and we can't process any customer orders until this is fixed.",
"model": "jev-latest",
"questions": {
"is_urgent": {
"type": "noul",
"instructions": "The message conveys urgency or time-sensitivity"
}
}
}返回里 is_urgent 的答案是 {"type": "noul", "noul": 0.99}(官方示例录制值)。请求字段约束的完整定义见 API 速查。
本课小结
✅ Jev Noul 的用法:一道是非一题,instructions 写完整问题(可选 criteria 定义是/否边界),Jev 返回「答案为是」的概率。
✅ 读数:接近 1 强是、接近 0 强否、接近 0.5 五五开;没有单独的 confidence——两结果分布一个数就说完了。
✅ 阈值:起点 yes ≥ 0.7、no ≤ 0.3;假「是」代价高调高、漏真「是」代价高调低;0.3–0.7 转人;别贴几个用例精调。
✅ 批量:清单式判断一条件一题塞进同一请求;instructions 可写成对象,代码批量生成问题。
✅ 选型:干净是非且概率有用 → Noul;量程度 → Score;固定选项 → Choice。
关键配置字段回顾:
| 字段 | 推荐写法 | 说明 |
|---|---|---|
type | "noul" | 固定值 |
instructions | 一个是非问题或一句陈述;接近 1 = 是 | 复杂 state 用反引号路径点名字段 |
criteria | 可选 NoulCriteria(true=…, false=…) | 边界微妙时加,大多数题不用 |
| 答案访问 | response.answers["id"].noul | 也可 response.nouls["id"].noul |
想看 Jev Noul 在真实场景里怎么用:自一致性(保险理赔按 14 条 rubric 打是非分、跨多次运行量稳定性);搜索与匹配(重排序直接拿 noul 概率排序,不走阈值);级联提取(结构化 instructions 逐字段核查提取结果)。
下一课预告
Noul 没有 confidence,那 Choice / Score 的 confidence 是怎么从概率分布算出来的、阈值又该怎么随风险缩放?下一课:Confidence 置信度:模型说「我不确定」时怎么办。
你会学到:
- confidence 的计算公式:Choice 和 Score 各有一套,都能从 probabilities 手工验算
- 三档用法:高自动执行、中确认或标记、低转人工
- 阈值随风险缩放:0.5 兜底,
approve_transfer这类高危操作要 > 0.9 - 不用官方 confidence 时的替代指标:top 概率、top-to-second 比