Files
Jyotisha/docs/tasks/TASK-rectification-jev-intent-classifier-research-20260919.md
T

164 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 研究单 · 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. 为什么不能直接接
三条来自官方文档、必须先用数据证伪或证实的风险:
1. **中文准确率。** 官方原文:*"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% 是中文口语(「那会儿没什么变化」「大概大二吧」「先不弄了」)。
2. **字面理解。** 官方短板表第 1 条:*"answers the question you wrote, not the one you meant."* 现行提示里大量「通常是…而不是…」「不要按 A/B/C/D 位置猜」这类意图性约束,搬到 Jev 上要改写成逐条字面条件。
3. **结构不变量不保证。** 官方明说同一问题问成 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. 硬红线
1. **不得改线上行为。** `turn-intent-classifier.ts`、`route.ts`、`mastra/model.ts`、模型目录一律不动;不得新增「工具模型」角色。研究代码全部放 `scripts/research/` 与 `docs/research/`。
2. **key 不进仓库、不进前端、不进日志。** 只从环境变量 `TYPESAFE_API_KEY` 读;`.env*` 不得提交;进度记录与研究报告不得出现 key 片段。
3. **真实用户消息不得提交。** 从 staging 库抽的样本只存在 worktree 外的本地目录(或 `.gitignore` 覆盖的路径),报告里只出现**聚合数字**与**经过改写的示例**(改写到无法对应任何真实会话;不含姓名、出生资料、地名、单位名)。违反即整单作废(`AGENTS.md` §8)。
4. **不得为了让 Jev 看起来更准而改样本标签。** 标注真值先于跑模型固定,跑完不得回头改;确有标注错误的,单独列一张「标注争议表」并说明双方判法,不得静默改。
5. Python 改动跑 `.venv/bin/python scripts/run_quality_gate.py --profile quick`;既有缺口(`test_shadbala_endpoint_returns_ranked_planet_strength` 时区依赖、`npm test` 无 Docker 的 27 条)与基线逐条一致即可,不计入。
6. 结论只允许三种:**可接 / 不可接 / 缺数据**,每一种都要落到 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`:
1. **问题来自真实引擎,不手写。** 用 `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`。每例每层各取若干题,去重后作为「问题池」。
2. **标签先定,再造回复。** 对问题池里每道题,按下面的配额抽目标标签,再让会话模型(当前默认贵模型即可)以**指定人设**写一条中文回复,要求回复**必须**体现该标签、且不得出现选项原文(避免逐字匹配)。人设至少 8 种轮换:惜字如金(≤ 6 字)、口语啰嗦、先答后补一件事、先否定再补一件事、答非所问、不耐烦想停、追问结果、用方言/网络语。长度分布按来源 B 的中位数 / P25 / P75 约束(若来源 B 不足 30 条,用 P25 ≤ 6 字、中位 ≤ 15 字、P75 ≤ 40 字兜底)。
| 层 | 目标条数 | 标签配额(大致) |
| --- | --- | --- |
| 点选题 | 400 | `answer_current_focus` 4 类 `answer_class` 各 15%(其中 1/4 带 `has_new_dated_event=true`)、`provide_new_evidence` 15%、`stop` 8%、`ask_about_result` 8%、`unclear` 9% |
| 采集题 | 400 | `no` 25%、`unsure` 20%、`yes/weak_yes` 25%(其中 1/3 带新经历)、`provide_new_evidence` 12%、`stop` 6%、`ask_about_result` 6%、`unclear` 6% |
| 无焦点 | 100 | `provide_new_evidence` 50%、`stop` 15%、`ask_about_result` 15%、`unclear` 20% |
3. **独立复核,只留双方一致的。** 另起一次调用(不同提示、看不到目标标签)对每条回复重新标注;与目标标签一致的进入测试集,不一致的进「标注争议表」(提交,供人工看),**不得**用第三次调用投票硬拉回来。争议率 > 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. 让步顺序
1. 来源 B 不足 30 条:代表性检验降为「只报数、不判定」,报告标明;来源 C 照常跑。
1a. 来源 C 争议率修两轮提示仍 > 15%:按实际通过条数跑,配额偏差如实报,不得放宽复核标准。
2. 现行模型对照跑时间超预算:来源 B 与来源 C 都必须至少跑一次,自洽率的第二次可以只跑来源 C 的 1/3 抽样。产品已明确 token 不设上限,不得以成本为由缩样本。
3. Jev 限流(429):SDK 默认退避重试;仍失败的样本记 `unavailable` 计入分母,不得剔除。
4. key 迟迟不到:T0/T1 完成后回报,状态板标「阻塞:等 key」,不得虚报 T2。
## 7. 开工前置命令
```bash
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 |