diff --git a/docs/tasks/README.md b/docs/tasks/README.md index d161e348..c81971f7 100644 --- a/docs/tasks/README.md +++ b/docs/tasks/README.md @@ -234,6 +234,7 @@ | `TASK-rectification-open-collect-invite-20260914.md` | `PROGRESS-rectification-open-collect-invite-20260914.md` | **P0**:固定七条采集线问完后只说「能问的都问完了」,用户不知道还能补经历、也不知道补了有用;而两轮研究证明补带年月经历是唯一有效手段。产品拍板:交付卡照出 + 卡上给不限领域的补充邀请(先要确切日期,再退年月;举七条线之外的例子),补完必须可见生效(BUG-689) | 待验收 | `codex/rectification-open-collect-invite-20260914` | | `TASK-rectification-cluster-width-research-20260914.md` | `PROGRESS-rectification-cluster-width-research-20260914.md` | **研究单**:上一轮证明调权重改不动交付区间宽度——所有方案宽度中位数都等于整个搜索窗。先确认 sweep 的宽度口径是否含淘汰(M0),再画簇结构像(M1),最后量三个改法:放宽簇上限、按分差决定是否合并、交付区间改分位覆盖(M2)。真值覆盖率不得下降 | 待验收 | `codex/rectification-cluster-width-research-20260914` | +| `TASK-rectification-jev-intent-classifier-research-20260919.md` | — | **研究单**:TypeSafe Jev(只做 Choice/Score/Noul 的校准判断模型,$0.042/Mtok)能否接管校正流的意图分类。产品 09-19 授权评估(推翻 09-15「分类只用贵模型」需重新拍板)。Agent 模拟校正流造 ≥900 条中文语料(标签先定、独立复核)+ 真机样本做代表性锚,量准确率 / 高置信错误率 / 低置信召回 / 延迟;只离线测,不改线上 | 待领取(T2 等 TypeSafe key) | — | | `TASK-rectification-minute-resolution-research-20260914.md` | `PROGRESS-rectification-minute-resolution-research-20260914.md` | **研究单**:候选分不开的根因是打分尺度——窗口内恒定项 11.5 分 vs 随分钟变化项 2.125 分(≈5:1)。先修封存基准(v3 每例仅 3 件事且被标 invalidated)出 v4,再离线量五个改法:分盘除数、去底座、**KP 宫头子主计分(产品 09-14 拍板,推翻 BUG-325 一条红线)**、年精度事件改边际似然、聚类签名层对齐。有收益才立实现单 | 待验收 | `codex/rectification-minute-resolution-research-20260914` | diff --git a/docs/tasks/TASK-rectification-jev-intent-classifier-research-20260919.md b/docs/tasks/TASK-rectification-jev-intent-classifier-research-20260919.md new file mode 100644 index 00000000..23d52aad --- /dev/null +++ b/docs/tasks/TASK-rectification-jev-intent-classifier-research-20260919.md @@ -0,0 +1,163 @@ +# 研究单 · 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 |