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 的 prompt | Jev state | |
|---|---|---|
| 材料与指令 | 混在一段文本里,改问题要重发全文 | state 只放材料,questions 单独定义 |
| 字段引用 | 靠「请看第二段」「请忽略标题」 | dot path 精确指到字段,如 `ticket.body` |
| 无关内容 | 糊进去什么读什么,寒暄签名档全算 token | 只放判断需要的字段,裁剪有依据 |
| 复用 | 换个判断就整段重写 | 同一份 state 挂多道题反复用 |
还有一条贯穿全课的事实:Jev 每次请求只看到你发给它的 state,没有隐式记忆——客户上个月告诉过你什么,不放进这次的 state 它就不知道(这也是后面简单记忆系统场景要做的事)。
跟我做
第 1 步:用最简字符串 state 发一次请求
为什么:最简单的 state 就是一段纯文本,先用它在 30 秒内确认「state = 材料」的手感。
文档里最简的例子就是这个字符串:
state = "My card was charged twice."配上文档里那道 billing 问题,就是一个完整请求:
# 运行前: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."] |
待判材料一复杂,字符串的短板就出来了:
| 字符串 state | JSON 对象 state | |
|---|---|---|
| dot path 引用 | 只能指向整体 | 每个字段都能被问题单独引用 |
| 判断依据 | 模型自己从全文里找 | 字段名即语义,判断对得上字段 |
| 适合 | 单一文本的简单判断 | 多字段复杂判断、多题并行 |
官方的结构化范例是一张支持工单——一段对话、一个订单、一条退款政策,装进一个 state:
# 运行前: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 里用反引号包住的点/下标路径写字段名。
# 运行前: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,占位符含义见下方说明):
{
"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 context | state + 所有问题加起来 | 64k token |
| per-question context | state + 任意单个问题 | 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」。具体三招:
- 先过滤再发:在代码里先筛出该题需要的字段,别把整个数据库行原样丢进去
- 别把 JSON 拼成字符串:schema、分类树、数据库行本来就是 JSON——整个传进来或只传相关子字段,不要序列化进字符串模板
- 没法在代码里过滤时,用一道 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 而不是让模型自由发挥
- 选项顺序也会影响答案——怎么用「重排选项验一致性」自查