Skip to content
📚 日常使用第 5 / 7 节

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:

python
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].text request a refund?

你应该看到:一道措辞干净的是非题——只有一个判断、接近 1 明确代表「是」、边界没有歧义。

  • 我的问题只有一个是非判断,且接近 1 明确代表「是」

第 2 步:用 typesafe-sdk 发出第一个 Noul 请求 ​

为什么:先把官方双题示例原样跑通,建立「已知正确」的基线——换成自己的问题后排错才有参照。

新建 noul_support.py,贴入官方示例(一条要求转人工的客服消息,同时问两件事):

python
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)

运行:

bash
.venv/bin/python noul_support.py

你应该看到:

text
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 步请求的完整返回:

json
{
  "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。

最简形态(官方示例):

python
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 之间不走代码路径,转人处理
避免贴着寥寥几个测试用例把阈值精调到「全对」

完整路由示例(官方):两道题一次问完,任一题落中间就转人工,重复联系抬高优先级:

python
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 写成对象:问题固定在一个字段,数据放其他字段,由代码批量生成。官方的简历查重例子——新到的简历和候选库里可能是同一个人的记录逐一对比:

python
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 步一个思路

官方示例的返回(三条候选记录):

json
{
  "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 塞两个条件 ​

text
instructions: "Is the customer angry and asking for a refund?"

「生气」和「要退款」是两个判断,挤在一道题里模型只能一起估:一个条件真、一个条件假的消息没位置放,0.6 说不清是「有点生气但没要钱」还是「没生气但要钱」。

正确做法:一个条件一道 Noul,一次请求并行发出,代码里组合——第 4 步的 route() 就是「要人工 × 重复联系」两题组合的例子。

❌ 反着问,接近 1 变成「否」 ​

text
反例: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.030.0(No experience)
I have used Python occasionally for small scripts alongside my main Java work.0.141.0(Some familiarity)
I used Python every day for two years in my last job, mostly data pipelines.0.812.05(Regular use in a job)
I have written Python daily for eight years, including maintaining a large Django codebase.0.922.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 的距离:

python
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):

json
{
  "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 比
搞懂AI关于我们怎么写隐私说明

独立的第三方教程,与文中提到的任何产品及其公司没有隶属或合作关系;产品名称和商标归各自所有者。价格和政策以官方页面为准。