Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu
17 KiB
研究单 · TypeSafe Jev 能否接管生时校正的意图分类(2026-09-19)
- 基线:
origin/staging@bdf027a0。 - 分支:
codex/rectification-jev-intent-classifier-research-20260919,worktree.worktrees/rectification-jev-intent-classifier-research-20260919。 - 性质:离线对照测量,不改线上行为、不改模型选择。 有结论才另立实现单。
- 外部依赖:TypeSafe API key(产品负责人正在申请)。key 到手前本单只能做 §5 的 T0/T1(T0 造语料需要会话模型凭据,与 key 无关),其余写进
BLOCKED.md。 - 产品 2026-09-19 口径:真机样本必然不足,由 Agent 模拟校正流程造语料,token 消耗不设上限。
1. 背景与问题
校正流里,用户每发一条自由文本,服务端先调一次「意图分类」再决定走哪条分支(frontend/src/lib/rectification-agentic/v9/turn-intent-classifier.ts 的 classifyRectificationTurnIntent,POST /api/rectification/agent 的 message 分支调用它,见 frontend/src/app/api/rectification/agent/route.ts 的 classifyTurnIntentWithRetry 四个调用点)。分类输出是一个严格 schema:
| 字段 | 取值 | 含义 |
|---|---|---|
intent |
answer_current_focus / provide_new_evidence / stop_rectification / ask_about_result / unclear |
用户这句话在干什么 |
answer_class |
yes / weak_yes / no / unsure / null |
只在 answer_current_focus 时非空 |
has_new_dated_event |
true / false |
同一句里是否还带了新的带时间经历 |
现状:这一步走会话选定的贵模型 + 结构化输出,失败重试一次后 fail-open 为 classifier_unavailable(BUG-721 修复后回「这边没接上」)。没有置信度,只有「分出来了 / 没分出来」两态。
候选替代物:TypeSafe 的 jev-1.13(文档 https://docs.typesafe.ai/,2026-09-17 版)。它不是生成模型,只回答 Choice / Score / Noul 三种题型,返回选项概率与校准过的置信度,输入 $0.042/Mtok、输出免费。题型与本分类器逐字段对口:intent → Choice,answer_class → Choice,has_new_dated_event → Noul。
2. 为什么不能直接接
三条来自官方文档、必须先用数据证伪或证实的风险:
- 中文准确率。 官方原文:"English is the primary training language... Other languages, including CJK scripts, are handled but not equally well; test on your own content before relying on Jev for a non-English workload." 我们的输入 100% 是中文口语(「那会儿没什么变化」「大概大二吧」「先不弄了」)。
- 字面理解。 官方短板表第 1 条:"answers the question you wrote, not the one you meant." 现行提示里大量「通常是…而不是…」「不要按 A/B/C/D 位置猜」这类意图性约束,搬到 Jev 上要改写成逐条字面条件。
- 结构不变量不保证。 官方明说同一问题问成 Noul 和 Choice 得数不可互比、
P(是)+P(不是)不等于 1。所以intent与answer_class两题的组合合法性(answer_current_focus才允许非空answer_class)必须由代码强制,不能指望模型自洽。
另外:官方警告限流「动态调整、可能不经通知变化」。任何上线方案都必须有回退到现行模型的路径,这条不在本单量,但结论段必须写明。
3. 决策记录
- 2026-09-15 产品决定「意图分类继续用会话选定的贵模型,不引入便宜快模型;后续不得以性能为由重提,要重提须先拿到新授权」(
TASK-rectification-failure-attribution-20260915.md§4.6、§让步顺序)。 - 2026-09-19 产品负责人主动提出评估 Jev,并授权本研究单。 这是新的授权来源,本单据此成立。但授权范围只到「离线测量」;是否上线、以何种方式上线(Jev 先判 + 低置信回退 / 双跑影子 / 不接)由本单结论后产品再拍板,本单不得直接改
classifyRectificationTurnIntent的模型选择。 - 本单只评估意图分类这一个位置。以下三处明确不在评估范围,执行方不得顺带试:证据抽取(
rectification-record-evidence-batch要生成摘要与原文定位,Jev 不生成文本)、候选分钟打分(Python 引擎算术,Jev 官方明说数值与日期是弱项)、旁白 / 交付卡 / 报告生成。
4. 硬红线
- 不得改线上行为。
turn-intent-classifier.ts、route.ts、mastra/model.ts、模型目录一律不动;不得新增「工具模型」角色。研究代码全部放scripts/research/与docs/research/。 - key 不进仓库、不进前端、不进日志。 只从环境变量
TYPESAFE_API_KEY读;.env*不得提交;进度记录与研究报告不得出现 key 片段。 - 真实用户消息不得提交。 从 staging 库抽的样本只存在 worktree 外的本地目录(或
.gitignore覆盖的路径),报告里只出现聚合数字与经过改写的示例(改写到无法对应任何真实会话;不含姓名、出生资料、地名、单位名)。违反即整单作废(AGENTS.md§8)。 - 不得为了让 Jev 看起来更准而改样本标签。 标注真值先于跑模型固定,跑完不得回头改;确有标注错误的,单独列一张「标注争议表」并说明双方判法,不得静默改。
- Python 改动跑
.venv/bin/python scripts/run_quality_gate.py --profile quick;既有缺口(test_shadbala_endpoint_returns_ranked_planet_strength时区依赖、npm test无 Docker 的 27 条)与基线逐条一致即可,不计入。 - 结论只允许三种:可接 / 不可接 / 缺数据,每一种都要落到 T3 的数字;不接受「感觉更准」「大体一致」。
5. 任务分解
T0 · 样本集(不需要 TypeSafe key;需要会话模型凭据)
产品 2026-09-19 已明确:真机样本肯定不够,用 Agent 模拟校正流程造语料,token 消耗不设上限。样本分三个来源,作用不同,不得混报:
来源 A · 既有合成样本(提交)。 frontend/tests/rectification-turn-intent-classifier.test.ts 里的 10 条用例(userMessage + 焦点 schema + 期望输出)直接转成样本。
来源 B · 真实会话样本(本地,不提交;作用是校准锚)。 从 staging 库 agentic_rectification_turns 抽 user_message 非空的行,连同当轮焦点(question_id 对应的 expected_answer_schema)与 case_status,输出到 worktree 外。有多少算多少,最少 30 条;它不是主测试集,而是用来检验来源 C 像不像真人(见 T3「代表性检验」)。同时记录真实消息的长度分布(字数的中位数 / P25 / P75)与标点 / 语气词比例,作为来源 C 的生成约束。来源 B 的真值由执行方逐条人工标注(条数少,不走模型复核),能从当轮后续动作反推现行模型当时的实际分类的(走了 applyRectificationChoice 即 answer_current_focus + 对应 optionId 的 answer_class;回了 classifierUnavailableReply 记 unavailable)另存一列。
来源 C · Agent 模拟语料(提交;主测试集,目标 ≥ 900 条)。 分三步,scripts/research/jev_intent_corpus_build.py:
-
问题来自真实引擎,不手写。 用
references/real_case_calibration/minute_rectification_holdout_v4.json的 20 例公开 AA 案例,走scripts/rectification/event_probes.py的discriminating_event_probes离线出点选题(题干 + 四个带answer_class的options),采集题用frontend/src/lib/rectification-agentic/v9/里既有的采集题文案(七领域 + 定向补事),无焦点层用focus: null。每例每层各取若干题,去重后作为「问题池」。 -
标签先定,再造回复。 对问题池里每道题,按下面的配额抽目标标签,再让会话模型(当前默认贵模型即可)以指定人设写一条中文回复,要求回复必须体现该标签、且不得出现选项原文(避免逐字匹配)。人设至少 8 种轮换:惜字如金(≤ 6 字)、口语啰嗦、先答后补一件事、先否定再补一件事、答非所问、不耐烦想停、追问结果、用方言/网络语。长度分布按来源 B 的中位数 / P25 / P75 约束(若来源 B 不足 30 条,用 P25 ≤ 6 字、中位 ≤ 15 字、P75 ≤ 40 字兜底)。
层 目标条数 标签配额(大致) 点选题 400 answer_current_focus4 类answer_class各 15%(其中 1/4 带has_new_dated_event=true)、provide_new_evidence15%、stop8%、ask_about_result8%、unclear9%采集题 400 no25%、unsure20%、yes/weak_yes25%(其中 1/3 带新经历)、provide_new_evidence12%、stop6%、ask_about_result6%、unclear6%无焦点 100 provide_new_evidence50%、stop15%、ask_about_result15%、unclear20% -
独立复核,只留双方一致的。 另起一次调用(不同提示、看不到目标标签)对每条回复重新标注;与目标标签一致的进入测试集,不一致的进「标注争议表」(提交,供人工看),不得用第三次调用投票硬拉回来。争议率 > 15% 说明生成提示有问题,先修生成提示再重跑,不得带病进入 T2。
生成与复核的模型名、版本、提示原文、每类争议率写进报告。同一模型既生成又复核是允许的(提示不同、看不到标签即可),但要写明。
真值固化。 三个来源的标签在 T2 之前 sha256 固化并写进报告;跑完不得回头改(红线 4)。来源 C 的最终文件 scripts/research/jev_intent_samples/simulated.jsonl 提交;来源 B 路径进 .gitignore。
验收:scripts/research/jev_intent_samples/ 下有 synthetic.jsonl(来源 A)、simulated.jsonl(来源 C,≥ 900 条且三层配额偏差 ≤ 20%)、disputed.jsonl(争议表)、README.md(来源 B 的抽取 SQL、长度分布、本地路径约定、三份 sha256)。合成回复里不得出现真实姓名、出生资料、地名、单位名(公开案例本身的姓名也不得进回复正文)。
T1 · 题目改写(不需要 key)
把现行两段提示(点选题版 / 采集题版)改写成 Jev 的问题定义,落 scripts/research/jev_intent_questions.py:
intent:Choice,五个选项各带一句字面判据(不用「通常」「不要猜」这类词),并按官方建议保留unclear作兜底项。answer_class:Choice,选项集按当轮焦点动态生成(点选题只放该题实际存在的answer_class;采集题固定yes / weak_yes / no / unsure)。has_new_dated_event:Noul,判据写成「这句话里是否出现了一件新的、带大概年或月的经历,且它不是对当前问题的直接回答」。state用对象:{"current_question": ..., "options": [...], "user_message": ..., "case_status": ...},与现行agent.generate的 JSON 输入同构,便于对照。- 三题同一请求并行(官方 fan-out 模式),每条样本一次调用。
组合合法性在代码里强制:intent != answer_current_focus 时 answer_class 置 null,不看模型。
验收:问题定义与官方 primitives/advanced 的 JSON 结构形式一致;文档化每一条判据与现行提示原句的对应关系(表格,一行一条),改写丢失的语义单列。
T2 · 对照跑(需要 key)
scripts/research/jev_intent_probe.py:读样本 → 调 POST /v1/systemone(模型固定 jev-1.13.0,不用 jev-latest 别名,理由是官方明说别名会移动)→ 记录每条的三题答案、各选项概率、confidence、usage.input_tokens、端到端毫秒数(本机到 api.typesafe.ai 的 wall clock,另记 SDK 报告的服务端耗时若有)→ 写 docs/research/jev_intent_2026_09_19.json(来源 B 的行只写聚合,不写原文)。
同一批样本用现行分类器(classifyRectificationTurnIntent,用会话默认模型)也跑一遍,走 frontend 里的 tsx 脚本或既有测试夹具,记同样的字段(现行模型没有置信度,记 null)。两边都跑 2 次,报自洽率(同一输入两次输出是否一致)。
T3 · 指标与报告
docs/research/jev_intent_2026_09_19.md,按焦点类型分层给出:
| 指标 | 定义 | 门槛(可接的必要条件) |
|---|---|---|
intent 准确率 |
与 T0 真值一致的比例 | 来源 C 三层各 ≥ 现行模型准确率 − 3 个百分点;来源 B 单独报 |
answer_class 准确率 |
仅在真值 intent = answer_current_focus 的子集上算 |
同上 |
has_new_dated_event 准确率 |
全集 | 同上 |
| 高置信错误率 | confidence ≥ 0.8 且答错的占全集比例 |
≤ 3%(这是接了之后会直接写库的那部分) |
| 低置信覆盖率 | confidence < 0.5 的占比 |
报数,不设门槛;这是回退到贵模型的流量 |
| 低置信召回 | 答错样本里落在 confidence < 0.5 的比例 |
≥ 60%(置信度是否真的能把错的挑出来) |
no vs unsure 混淆 |
采集题里两者互判错的数量 | 单独列;这两者写库语义不同(declined / skipped) |
| 自洽率 | 两次跑同一样本输出一致的比例 | 报数,与现行模型并列 |
| 中位延迟 / P95 | 端到端毫秒 | 报数,与现行模型并列 |
| 每次调用输入 token 与折算成本 | 由 usage 得 |
报数 |
代表性检验(来源 B 对来源 C)。 同一模型在来源 B 与来源 C 上的 intent 准确率之差若 > 10 个百分点,判定模拟语料不代表真人,来源 C 上的门槛结论降级为「缺数据」,报告必须写明差在哪一类标签;不得只报来源 C 的数。
另加错例画像:Jev 错、现行对的样本按 §2 三条风险归类(中文口语 / 字面理解 / 其他),每类给 2–3 条改写后的示例。
结论段三选一,并附「若接,建议的接法」:只允许在 (a) Jev 先判、confidence < θ 回退现行模型,(b) 影子双跑只记日志不写库,(c) 不接 之间选,给出 θ 的推荐值与依据(从 T3 的置信度–准确率曲线上取,不得拍脑袋)。
T4 · 记录
docs/tasks/PROGRESS-rectification-jev-intent-classifier-research-20260919.md:样本数(分来源、分层)、真值sha256、两边模型版本、跑的时间、指标表、偏离与原因。BLOCKED.md:key 未到手期间挂一条「T2/T3 阻塞,替代证据 = T0/T1 产出」;到手后划掉。- 不写
docs/BUG_HISTORY.md(本单不是 Bug);若过程中发现现行分类器的确定性错误(同一输入稳定分错),另立 BUG 编号(当前最大 BUG-969,从 BUG-970 起),只记事实,不修。 docs/tasks/README.md状态板加一行(生时校正段)。
6. 让步顺序
- 来源 B 不足 30 条:代表性检验降为「只报数、不判定」,报告标明;来源 C 照常跑。 1a. 来源 C 争议率修两轮提示仍 > 15%:按实际通过条数跑,配额偏差如实报,不得放宽复核标准。
- 现行模型对照跑时间超预算:来源 B 与来源 C 都必须至少跑一次,自洽率的第二次可以只跑来源 C 的 1/3 抽样。产品已明确 token 不设上限,不得以成本为由缩样本。
- Jev 限流(429):SDK 默认退避重试;仍失败的样本记
unavailable计入分母,不得剔除。 - key 迟迟不到:T0/T1 完成后回报,状态板标「阻塞:等 key」,不得虚报 T2。
7. 开工前置命令
git fetch origin --prune
git worktree add -b codex/rectification-jev-intent-classifier-research-20260919 \
.worktrees/rectification-jev-intent-classifier-research-20260919 origin/staging
cd .worktrees/rectification-jev-intent-classifier-research-20260919
git status -sb | head -1 # 确认分支
.venv/bin/python -m pip install typesafe-sdk # 只装到 .venv,不改 requirements*.txt
export TYPESAFE_API_KEY=... # 从产品负责人处拿,只放 shell 环境
本单不涉及引擎 / 镜像 / 发布,不要求跑 scripts/pre_work_check.py。
8. 附:官方文档要点(2026-09-17 版,执行方不必再读一遍)
| 项 | 值 |
|---|---|
| 端点 | POST https://api.typesafe.ai/v1/systemone,Authorization: Bearer $TYPESAFE_API_KEY |
| 模型 | jev-1.13.0(别名 jev-latest / jev-preview 当前同指向;用版本号) |
| 价格 | $42 / Btok 输入,输出免费 |
| 限流 | 250k tok/s、1200 req/min,动态调整 |
| 上下文 | 64k / 请求;state + 最长一题 ≤ 32k |
| 输入 | 纯文本(字符串 / JSON 对象 / 数组) |
| 返回 | Choice:choice、probabilities、confidence;Score:score、legend、probabilities、confidence;Noul:noul(0–1,无 confidence) |
| SDK | pip install typesafe-sdk,TypeSafeClient().system_one(state=..., questions={...}) |
| 短板(官方列) | 字面理解、算数与计数、日期比较、多跳间接、大而杂的 state、对抗性内容、指令与判据矛盾、结构不变量、不生成文本 |
| 语言 | 英语为主;CJK「可用但准确率更低,先在自己数据上测」 |
| 数据 | 不用客户请求训练;企业版有 ZDR |