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

State 状态:给 Jev 什么样的材料 ​

一次 Jev 请求只有两样东西:state(要判断的内容)+ questions(类型化问题)。这节课讲前一半——怎么准备 Jev state:三种形态怎么选、结构化工单怎么组织、哪些细节不该塞。

其实你在第一个请求里已经写过一次 state:route_ticket.py 里那条工单字符串就是。这节课用 8 分钟把它升级成好判断的材料。

学完你能做什么 ​

  • 按材料形状选出 Jev state 的三种形态之一:一段文本用字符串、多字段用 JSON 对象、同质多条目用文本数组
  • 把一张工单(对话 + 订单 + 政策)组织成一个结构化 state,让每个字段有名字、关系清晰
  • 用 dot path(`ticket.messages[0].text` 这种反引号路径)让 questions 精确指向 state 里的字段
  • 说出 state 的两个上下文限制(64k per-request / 32k per-question)和请求体四条硬规则
  • 解释「只发该题需要的字段」为什么既省钱又提准

你现在的困境 ​

  • 我把整段用户对话原样糊进 prompt 让 GPT 判断该转哪个组——一半 token 是「在吗」「谢谢」和签名档,账单翻倍,分类结果还时不时被签名档里的公司名带偏
  • 工单明明有标题、正文、客户等级好几个字段,我却只能在 prompt 里拼成一大段「这是一条工单,标题是……正文是……」——字段一多就拼漏,模型答错了我都分不清是材料给错了还是它没读懂
  • 材料和判断标准搅在同一段 prompt 里:想多问一句「这条严重吗」,整段材料就得复制重发一遍;想让模型只看「正文」字段,只能写「请忽略标题部分」——它照不照做全凭运气

什么时候用这一招 ​

当你需要:

  • 给 Jev 的每次判断准备材料——state 是模型唯一能看到的东西
  • 材料有结构(多字段、多条目),不想拼成一大段文本
  • 同一份材料要问好几道题,还想精确控制每道题看哪些字段

而且不想:

  • 把判断指令、参考政策、待判文本搅在同一段 prompt 里
  • 为无关内容付输入 token(Jev 按输入 token 计费)
  • 在模型答错时分不清是哪部分材料引入的干扰

🎒 开始前的准备 ​

  • 完成了日常使用导读:知道本栏目的路线——先学喂材料(本课),再学五种提问方式
  • 跑通过第一个请求:.venv 里装好了 typesafe-sdk,环境变量 TYPESAFE_API_KEY 已设置
  • 记得 route_ticket.py 的大致样子(client.system_one(state=..., questions=...)),忘了就回去扫一眼

核心思路 ​

官方的定义一句话:state 是你请 System One 模型评估的内容——可能是一条支持消息、一段文本、或你应用的当前状态。它通过请求的 state 字段传入,和问题放在一起(docs.typesafe.ai/concepts/state.md)。

一个好用的心智模型,官方原话:

把 state 想象成你在请专家小组做判断之前,呈现给他们的材料。

给专家小组的材料你会怎么准备?写清楚标题、把订单记录原样附上、把政策条文单独放一页——而不是把三样东西搅成一段话。这就是本课的全部主线。

先看它和你熟悉的 prompt 上下文有什么不同:

聊天 LLM 的 promptJev state
材料与指令混在一段文本里,改问题要重发全文state 只放材料,questions 单独定义
字段引用靠「请看第二段」「请忽略标题」dot path 精确指到字段,如 `ticket.body`
无关内容糊进去什么读什么,寒暄签名档全算 token只放判断需要的字段,裁剪有依据
复用换个判断就整段重写同一份 state 挂多道题反复用

还有一条贯穿全课的事实:Jev 每次请求只看到你发给它的 state,没有隐式记忆——客户上个月告诉过你什么,不放进这次的 state 它就不知道(这也是后面简单记忆系统场景要做的事)。

跟我做 ​

第 1 步:用最简字符串 state 发一次请求 ​

为什么:最简单的 state 就是一段纯文本,先用它在 30 秒内确认「state = 材料」的手感。

文档里最简的例子就是这个字符串:

python
state = "My card was charged twice."

配上文档里那道 billing 问题,就是一个完整请求:

python
# 运行前:export TYPESAFE_API_KEY="<你的API Key>"
# 安装:pip install typesafe-sdk(注意包名是连字符,import 是下划线 typesafe_sdk)
from typesafe_sdk import Noul, TypeSafeClient

state = "My card was charged twice."

with TypeSafeClient(model="jev-latest") as client:  # 自动读 TYPESAFE_API_KEY
    response = client.system_one(
        state=state,
        questions={
            "billing": Noul(instructions="Is this message about billing?"),
        },
    )

print(response.answers["billing"].noul)

你应该看到:脚本打印出一个 0 到 1 之间的数。官方 OpenAPI 文档里有个几乎同款的示例——state 为 "I was charged twice for my subscription this month. Please refund the extra charge.",问题为 "Is this message about billing?",返回 noul: 0.98。一条表述明确的扣款诉求,拿到了 0.98 的 billing 概率;你的具体数值以实际输出为准。

  • 上面的脚本能跑通,打印出一个 0 到 1 之间的数

第 2 步:把 Jev state 从字符串升级成结构化 JSON 对象 ​

为什么:官方建议大多数请求用对象——「让 state 的每个部分有描述性名字、关系保持清晰」(Use an object for most requests)。字符串只在用例简单、只有一段文本时合适。

三种形态官方对照表:

形态什么时候用例子
字符串一条消息、一篇文章、一段文本"My card was charged twice."
JSON 对象有名字的字段、相关记录、应用状态{"message": "…", "order_id": "A-104"}
文本数组一串消息或记录["Hi", "My customer number is TS1337.", "My card was charged twice."]

待判材料一复杂,字符串的短板就出来了:

字符串 stateJSON 对象 state
dot path 引用只能指向整体每个字段都能被问题单独引用
判断依据模型自己从全文里找字段名即语义,判断对得上字段
适合单一文本的简单判断多字段复杂判断、多题并行

官方的结构化范例是一张支持工单——一段对话、一个订单、一条退款政策,装进一个 state:

python
# 运行前:export TYPESAFE_API_KEY="<你的API Key>"
from typesafe_sdk import TypeSafeClient

state = {  # 官方文档例子:docs/concepts/state.md
    "ticket": {
        "subject": "Duplicate charge",
        "messages": [
            {"from": "customer", "text": "I was charged twice for order A-104. Please refund the duplicate."},
            {"from": "support", "text": "We are checking the charges."}
        ]
    },
    "order": {
        "id": "A-104",
        "charges": [
            {"amount_usd": 49, "status": "captured"},
            {"amount_usd": 49, "status": "captured"}
        ]
    },
    "refund_policy": "Duplicate charges are eligible for a refund."
}

注意两点:

  • 这是一个 state,尽管它包含对话、订单和政策三块——官方建议「当决策需要比较这些部分时,把相关信息放在一起」
  • 在 Python 里,字符串、字典、列表都直接传给 client.system_one(state=...),不用序列化成 JSON 字符串

你应该看到:state 只是一个普通的 Python 字典,还没有网络请求发生——这一步只是把材料备好。refund_policy 是待判材料的一部分,所以不写成 instructions 里的提示词(下一节展开)。

  • 能说出 state 里的 refund_policy 为什么不写成 instructions 里的提示词——因为它是待判材料的一部分,不是问题(下一节展开)

第 3 步:用 dot path 让 questions 指向 Jev state 的具体字段 ​

为什么:state 变成对象后,需要一种方式告诉模型「这道题只看这个字段」。官方语法:在 instructions 里用反引号包住的点/下标路径写字段名。

python
# 运行前:export TYPESAFE_API_KEY="<你的API Key>"
questions = {  # 官方文档例子:docs/primitives.md「Reference specific fields」
    "refund_requested": {
        "type": "noul",
        "instructions": "Does `ticket.messages[0].text` request a refund?",
    },
    "policy_supports_refund": {
        "type": "noul",
        "instructions": (
            "Does `refund_policy` support the refund requested "
            "in `ticket.messages[0].text`, given `order.charges`?"
        ),
    },
}

with TypeSafeClient(model="jev-latest") as client:
    response = client.system_one(state=state, questions=questions)

print(response.answers["refund_requested"].noul)
print(response.answers["policy_supports_refund"].noul)

响应骨架长这样(顶层三字段 model / answers / usage,Noul 题只返回 type 和 noul 两个字段,字段名来自官方 schema,占位符含义见下方说明):

json
{
  "model": "<实际回答该请求的模型 ID,别名会解析成版本号,如 jev-1.14.0>",
  "answers": {
    "refund_requested": {
      "type": "noul",
      "noul": "<0 到 1 之间的概率>"
    },
    "policy_supports_refund": {
      "type": "noul",
      "noul": "<0 到 1 之间的概率>"
    }
  },
  "usage": {"input_tokens": "<整数>", "output_tokens": "<整数>"}
}

这里的规律:

  • `ticket.messages[0].text` = 点路径 + 数组下标,指向客户的第一条消息
  • 一道题可以引用多个字段:policy_supports_refund 同时用了 refund_policy、ticket.messages[0].text、order.charges
  • 这就是内容与问题分离:state 放内容和支持性事实(工单、订单、政策),questions 定义判断。换问题不用动 state,多问一道题不用重发材料

你应该看到:两行输出,各是一个 0 到 1 之间的概率;响应结构见上方骨架(具体数值官方文档未提供,以你的实际输出为准)。

同一份 state 可以挂任意多道题,还能混题型——所有题对同一个 state 并行、独立评估,加题几乎不增加响应时间(这一点在并行提问那课展开)。

  • 能写出指向 order.charges 里第二笔扣款金额的 dot path:`order.charges[1].amount_usd`

第 4 步:在上下文限制内裁剪你的 state ​

为什么:state 不是越多越好——它既受硬限制约束,又直接影响准确度和账单。

两条上下文限制(Jev 1.14 / 1.13 相同):

限制含义默认值
per-request contextstate + 所有问题加起来64k token
per-question contextstate + 任意单个问题32k token

要点:

  • 问题数量没有上限,限制只看体积——两个 context 都装得下就行
  • state 通常是大头(问题往往很短),而且两个限制都包含 state 本身——先被撑爆的往往不是问题,是 state
  • state 逐项算过账:整个请求的输入 token(state + 全部问题)只计费一次,但塞进去的每个无关字段都实打实花钱
  • 真超了:加请求头 TypeSafe-Danger-Zone-Long-Context: true 可以把两个限制都提到 262k,但官方警告它「更慢且更不准,只给需要的请求开」——正确做法是先裁剪,长上下文是最后手段(详见模型与价格)

除上下文外,请求体还有四条硬规则(docs.typesafe.ai/api/errors):

规则限制
请求体大小≤ 32 MiB(不含 header)
编码UTF-8 JSON,无 BOM
嵌套state / instructions / criteria 各最多 64 层,更深要拍平
数字只接受有限数——NaN、Infinity、1e400 都会被拒;ID 和大数用字符串传

最后一条最常咬人:工单里的订单号、用户 ID,放进 state 时写成 "A-104" 这样的字符串,不要写成裸数字。

怎么裁——官方设计准则就一句:「只放与当前问题相关的上下文,帮模型避开干扰和 context rot」。具体三招:

  1. 先过滤再发:在代码里先筛出该题需要的字段,别把整个数据库行原样丢进去
  2. 别把 JSON 拼成字符串:schema、分类树、数据库行本来就是 JSON——整个传进来或只传相关子字段,不要序列化进字符串模板
  3. 没法在代码里过滤时,用一道 Noul 先过滤:先问「这段和判断相关吗」,只把相关的送进下一批题(RAG 段落把关就是这么干的)

省的不只是 token。Jev 按输入 token 计费、输出免费,而官方踩坑手册直接说:state 里无关内容越多准确度越低——无关细节是干扰项,还会让你定位不了错在哪(下一节展开)。

  • 写 state 前能回答:「这道题真正需要哪几个字段?」答不上来就先别发

检查点 ✅ ​

  • 能说出 state 的三种形态和各自适用场景(字符串 = 一段文本、对象 = 多字段、数组 = 同质多条目)
  • 能写出一个 dot path,指向结构化 state 里任意字段(含数组下标)
  • 知道两条上下文限制的默认值:64k per-request / 32k per-question,且都包含 state 本身
  • 知道订单号这类 ID 要用字符串放进 state,不能写裸数字
  • state 里没有与判断无关的寒暄、签名档、整段未过滤的原始日志

踩坑提醒 ​

❌ 坑 1:把大 state 当「上下文越全越好」 ​

现象:想着「多给点背景总没错」,把客户的全部历史工单、聊天寒暄、签名档全塞进 state。

为什么错:官方踩坑手册(适用于 jev-1.13)把它列为已知失败模式——「state 里与决策无关的内容越多,准确度越低,无关细节是干扰项,大 state 还让你更难定位是哪部分输入导致了错误答案」。你多花的输入 token 买到的是更低的准确度。

正确做法:发送前在代码里过滤,只发该题需要的字段;没法在代码里过滤时,先用一道 Noul 问相关性再筛。

❌ 坑 2:问题里绕着弯引用字段 ​

现象:不想学 dot path,在 instructions 里写「请看 state 里嵌套在 ticket 下 messages 数组第一项的 text 字段」。

为什么错:官方踩坑手册的 Indirection(间接引用)条目:需要多跳推理或双重否定的 instructions 回答更不可靠。

正确做法:直接用反引号路径 `ticket.messages[0].text`,一步指到字段。

❌ 坑 3:把判断标准写进 state ​

现象:state = {"text": "判断这条工单是不是退款诉求:我订的东西三天没到"}。

为什么错:判断指令混进材料,换问题就得重做 state;这也不是 state 的用途——state 放内容和支持性事实,判断写在 questions 的 instructions 和 criteria 里。

正确做法:state = {"subject": "物流延迟", "body": "我订的东西三天没到"},问题写在 questions 里、用 dot path 指回 body。领域规则也一样:编码进 instructions 和 criteria,而不是垫在 state 底下。

❌ 坑 4:用裸数字传 ID ​

现象:{"order_id": 1000042}——顺手把数据库整型 ID 直接放进 state。

为什么错:API 的数字规则只接受有限数——NaN、Infinity、大到无法表示的数(如 1e400)会被直接拒绝(400);同时官方明确建议「标识符和精确大数用字符串传」。ID 是标识符不是量,裸数字既容易踩数字规则,也没有 "A-104" 这样的可读性。

正确做法:ID 和精确大数一律字符串:{"order_id": "1000042"}。

❌ 坑 5:装不上 typesafe-sdk / import 报错 ​

现象:pip install typesafe 能装上,但 from typesafe_sdk import ... 报 ModuleNotFoundError。

为什么错:PyPI 上那个 typesafe 是无关的同名包。pip 包名是连字符 typesafe-sdk,import 是下划线 typesafe_sdk。

正确做法:pip install typesafe-sdk,然后 from typesafe_sdk import Noul, TypeSafeClient。

进阶技巧(可选) ​

🔧 图像也能放进 state。图可以出现在文本能出现的任何位置——state、instructions、criteria 都行;一个 state 可以图文混排、放多张图、像其他数据一样带标签或嵌套。默认关闭,需要每个含图请求加 TypeSafe-Danger-Zone-Images: true 请求头,Python SDK v0.7.5 起支持 Image 对象直接放 state。注意官方标注它还是「dreamer-ready」(试验性,格式与准确度可能变)——详见 Box 与图像。

🔧 音频视频不行,但能转。Jev 不接受音频和视频,官方建议是转成文本或结构化字段再放进 state。

🔧 答案可以进下一棒的 state。Jev 的答案本身就是类型化值——你的代码可以对它比较、过阈值、排序,或放进后续请求的 state。官方的记忆系统 cookbook 就是这么玩的:每张工单一个 state,值得记的内容连同 Jev 对它们的答案在下次调用前加进 state(见简单记忆系统)。

🔧 中文 state 能用,但要盯紧 confidence。英语是 Jev 的主要训练语言、准确度最好;CJK 中文被支持但准确度较低——官方建议非英文内容上线前先用自己的数据测,并重点关注 confidence。判断路由时更是如此。

本课小结 ​

✅ 回看「学完你能做什么」——三种形态、结构化组织、dot path、两条上下文限制、先过滤再发,现在你应该能逐条做到。

一句话收束:Jev state 是模型唯一能看到的材料——形态按材料形状选,判断写在 questions 里用 dot path 指回字段,上下文限制内无关细节一个不塞。

关键要点回顾:

要点推荐做法说明
state 形态多数字段场景用 JSON 对象字段有名字、关系清晰;纯文本才用字符串
字段引用反引号 dot path:`ticket.messages[0].text`写在问题的 instructions 里
内容边界材料与支持性事实进 state,判断进 questions换问题不动 state
上下文64k per-request / 32k per-question长上下文 header 是「更慢更不准」的最后手段
识别字段ID、大数用字符串数字只接受有限值
裁剪只发该题需要的字段代码先过滤,省 token 也保准确度

下一课预告 ​

材料备好了,下一课学第一种提问方式:Choice 选择题——从固定选项里挑一个,返回选项、每个选项的概率和置信度。

你会学到:

  • 用 criteria 定义选项 map,答案永远落在你给的选项里
  • 读懂 choice / probabilities / confidence 三个返回字段,拿它们直接驱动代码分支
  • 为什么「答案是一组已知选项」时该用 Choice 而不是让模型自由发挥
  • 选项顺序也会影响答案——怎么用「重排选项验一致性」自查
搞懂AI关于我们怎么写隐私说明

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