research(jev-intent): fix2 把来源 B 现行与高置信错误补进报告
离线从 cache 聚合,不重跑模型。无焦点层 Flash 69.7% 低于 Jev 78.8%。采集层相对门槛标不可判。
This commit is contained in:
@@ -244,6 +244,180 @@
|
||||
"cost_usd": 0.001293768
|
||||
}
|
||||
},
|
||||
"current_source_b": {
|
||||
"n": 157,
|
||||
"unavailable": 0,
|
||||
"intent_acc": 0.89171974522293,
|
||||
"answer_class_acc": 0.9702970297029703,
|
||||
"answer_class_n": 101,
|
||||
"dated_acc": 0.9363057324840764,
|
||||
"all_acc": 0.8407643312101911,
|
||||
"high_conf_error_rate": null,
|
||||
"low_conf_coverage": null,
|
||||
"low_conf_recall": null,
|
||||
"no_vs_unsure": 0,
|
||||
"median_ms": 685.0,
|
||||
"p95_ms": 938.0,
|
||||
"mean_input_tokens": 516.6496815286624,
|
||||
"cost_usd": 0.0034067880000000004
|
||||
},
|
||||
"current_source_b_by_layer": {
|
||||
"choice": {
|
||||
"n": 17,
|
||||
"unavailable": 0,
|
||||
"intent_acc": 0.8823529411764706,
|
||||
"answer_class_acc": 1.0,
|
||||
"answer_class_n": 1,
|
||||
"dated_acc": 0.8823529411764706,
|
||||
"all_acc": 0.8823529411764706,
|
||||
"high_conf_error_rate": null,
|
||||
"low_conf_coverage": null,
|
||||
"low_conf_recall": null,
|
||||
"no_vs_unsure": 0,
|
||||
"median_ms": 657.0,
|
||||
"p95_ms": 763.0,
|
||||
"mean_input_tokens": 450.5882352941176,
|
||||
"cost_usd": 0.00032172
|
||||
},
|
||||
"collect": {
|
||||
"n": 107,
|
||||
"unavailable": 0,
|
||||
"intent_acc": 0.9532710280373832,
|
||||
"answer_class_acc": 0.97,
|
||||
"answer_class_n": 100,
|
||||
"dated_acc": 0.9439252336448598,
|
||||
"all_acc": 0.8785046728971962,
|
||||
"high_conf_error_rate": null,
|
||||
"low_conf_coverage": null,
|
||||
"low_conf_recall": null,
|
||||
"no_vs_unsure": 0,
|
||||
"median_ms": 689.0,
|
||||
"p95_ms": 938.0,
|
||||
"mean_input_tokens": 528.9345794392524,
|
||||
"cost_usd": 0.0023770320000000003
|
||||
},
|
||||
"none": {
|
||||
"n": 33,
|
||||
"unavailable": 0,
|
||||
"intent_acc": 0.696969696969697,
|
||||
"answer_class_acc": null,
|
||||
"answer_class_n": 0,
|
||||
"dated_acc": 0.9393939393939394,
|
||||
"all_acc": 0.696969696969697,
|
||||
"high_conf_error_rate": null,
|
||||
"low_conf_coverage": null,
|
||||
"low_conf_recall": null,
|
||||
"no_vs_unsure": 0,
|
||||
"median_ms": 715.0,
|
||||
"p95_ms": 978.0,
|
||||
"mean_input_tokens": 510.8484848484849,
|
||||
"cost_usd": 0.0007080360000000001
|
||||
}
|
||||
},
|
||||
"source_b_jev_self_consistency": {
|
||||
"all": 0.9617834394904459,
|
||||
"choice": 0.9411764705882353,
|
||||
"collect": 0.9906542056074766,
|
||||
"none": 0.8787878787878788
|
||||
},
|
||||
"source_b_none_confusion": {
|
||||
"jev": {
|
||||
"labels": [
|
||||
"provide_new_evidence",
|
||||
"stop_rectification",
|
||||
"ask_about_result",
|
||||
"unclear",
|
||||
"answer_current_focus"
|
||||
],
|
||||
"counts": {
|
||||
"provide_new_evidence": {
|
||||
"provide_new_evidence": 24,
|
||||
"stop_rectification": 0,
|
||||
"ask_about_result": 0,
|
||||
"unclear": 0,
|
||||
"answer_current_focus": 0
|
||||
},
|
||||
"stop_rectification": {
|
||||
"provide_new_evidence": 0,
|
||||
"stop_rectification": 0,
|
||||
"ask_about_result": 0,
|
||||
"unclear": 0,
|
||||
"answer_current_focus": 0
|
||||
},
|
||||
"ask_about_result": {
|
||||
"provide_new_evidence": 0,
|
||||
"stop_rectification": 0,
|
||||
"ask_about_result": 1,
|
||||
"unclear": 0,
|
||||
"answer_current_focus": 0
|
||||
},
|
||||
"unclear": {
|
||||
"provide_new_evidence": 0,
|
||||
"stop_rectification": 0,
|
||||
"ask_about_result": 0,
|
||||
"unclear": 1,
|
||||
"answer_current_focus": 7
|
||||
},
|
||||
"answer_current_focus": {
|
||||
"provide_new_evidence": 0,
|
||||
"stop_rectification": 0,
|
||||
"ask_about_result": 0,
|
||||
"unclear": 0,
|
||||
"answer_current_focus": 0
|
||||
}
|
||||
},
|
||||
"other": 0,
|
||||
"n": 33
|
||||
},
|
||||
"current": {
|
||||
"labels": [
|
||||
"provide_new_evidence",
|
||||
"stop_rectification",
|
||||
"ask_about_result",
|
||||
"unclear",
|
||||
"answer_current_focus"
|
||||
],
|
||||
"counts": {
|
||||
"provide_new_evidence": {
|
||||
"provide_new_evidence": 21,
|
||||
"stop_rectification": 0,
|
||||
"ask_about_result": 0,
|
||||
"unclear": 0,
|
||||
"answer_current_focus": 3
|
||||
},
|
||||
"stop_rectification": {
|
||||
"provide_new_evidence": 0,
|
||||
"stop_rectification": 0,
|
||||
"ask_about_result": 0,
|
||||
"unclear": 0,
|
||||
"answer_current_focus": 0
|
||||
},
|
||||
"ask_about_result": {
|
||||
"provide_new_evidence": 0,
|
||||
"stop_rectification": 0,
|
||||
"ask_about_result": 0,
|
||||
"unclear": 1,
|
||||
"answer_current_focus": 0
|
||||
},
|
||||
"unclear": {
|
||||
"provide_new_evidence": 1,
|
||||
"stop_rectification": 0,
|
||||
"ask_about_result": 0,
|
||||
"unclear": 2,
|
||||
"answer_current_focus": 5
|
||||
},
|
||||
"answer_current_focus": {
|
||||
"provide_new_evidence": 0,
|
||||
"stop_rectification": 0,
|
||||
"ask_about_result": 0,
|
||||
"unclear": 0,
|
||||
"answer_current_focus": 0
|
||||
}
|
||||
},
|
||||
"other": 0,
|
||||
"n": 33
|
||||
}
|
||||
},
|
||||
"representativeness": {
|
||||
"note": "来源 B 与来源 C 同层 intent 准确率:choice B 94.1% vs C 99.0%(差 4.9%,n_B=17);collect B 94.4% vs C 92.2%(差 2.1%,n_B=107);none B 78.8% vs C 96.0%(差 17.2%,n_B=33)。 有层差 > 10pp,结论降为缺数据。",
|
||||
"fail": true,
|
||||
@@ -101740,7 +101914,7 @@
|
||||
},
|
||||
"conclusion": {
|
||||
"verdict": "缺数据",
|
||||
"reason": "来源 B 与来源 C 同层 intent 准确率差 > 10pp,模拟语料不代表真人,来源 C 门槛结论降为缺数据。来源 B 与来源 C 同层 intent 准确率:choice B 94.1% vs C 99.0%(差 4.9%,n_B=17);collect B 94.4% vs C 92.2%(差 2.1%,n_B=107);none B 78.8% vs C 96.0%(差 17.2%,n_B=33)。 有层差 > 10pp,结论降为缺数据。 同时来源 C 绝对门槛未过:低置信召回 23.1% / 45.5% / 25.0% < 60%(错了却仍高置信)。 相对 −3pp(同一样本):choice Jev 99.0% vs 现行 98.8%(门槛 95.8%,过);collect Jev 92.2% vs 现行 97.5%(门槛 94.5%,未过);none Jev 96.0% vs 现行 94.0%(门槛 91.0%,过)。",
|
||||
"reason": "来源 B 与来源 C 同层 intent 准确率差 > 10pp,模拟语料不代表真人,来源 C 门槛结论降为缺数据。来源 B 与来源 C 同层 intent 准确率:choice B 94.1% vs C 99.0%(差 4.9%,n_B=17);collect B 94.4% vs C 92.2%(差 2.1%,n_B=107);none B 78.8% vs C 96.0%(差 17.2%,n_B=33)。 有层差 > 10pp,结论降为缺数据。 同时来源 C 绝对门槛未过:低置信召回 23.1% / 45.5% / 25.0% < 60%(错了却仍高置信)。 相对 −3pp(同一样本):choice Jev 99.0% vs 现行 98.8%(门槛 95.8%,过);collect Jev 92.2% vs 现行 97.5%(门槛 94.5%,不可判(复核与对照同源));none Jev 96.0% vs 现行 94.0%(门槛 91.0%,过)。",
|
||||
"if_connect": "不得上线。先补真机样本或重造更像真人的来源 C,再测。"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
## 结论
|
||||
|
||||
**缺数据**。来源 B 与来源 C 同层 intent 准确率差 > 10pp,模拟语料不代表真人,来源 C 门槛结论降为缺数据。来源 B 与来源 C 同层 intent 准确率:choice B 94.1% vs C 99.0%(差 4.9%,n_B=17);collect B 94.4% vs C 92.2%(差 2.1%,n_B=107);none B 78.8% vs C 96.0%(差 17.2%,n_B=33)。 有层差 > 10pp,结论降为缺数据。 同时来源 C 绝对门槛未过:低置信召回 23.1% / 45.5% / 25.0% < 60%(错了却仍高置信)。 相对 −3pp(同一样本):choice Jev 99.0% vs 现行 98.8%(门槛 95.8%,过);collect Jev 92.2% vs 现行 97.5%(门槛 94.5%,未过);none Jev 96.0% vs 现行 94.0%(门槛 91.0%,过)。
|
||||
**缺数据**。来源 B 与来源 C 同层 intent 准确率差 > 10pp,模拟语料不代表真人,来源 C 门槛结论降为缺数据。来源 B 与来源 C 同层 intent 准确率:choice B 94.1% vs C 99.0%(差 4.9%,n_B=17);collect B 94.4% vs C 92.2%(差 2.1%,n_B=107);none B 78.8% vs C 96.0%(差 17.2%,n_B=33)。 有层差 > 10pp,结论降为缺数据。 同时来源 C 绝对门槛未过:低置信召回 23.1% / 45.5% / 25.0% < 60%(错了却仍高置信)。 相对 −3pp(同一样本):choice Jev 99.0% vs 现行 98.8%(门槛 95.8%,过);collect Jev 92.2% vs 现行 97.5%(门槛 94.5%,不可判(复核与对照同源));none Jev 96.0% vs 现行 94.0%(门槛 91.0%,过)。
|
||||
|
||||
若接:不得上线。先补真机样本或重造更像真人的来源 C,再测。
|
||||
|
||||
@@ -41,18 +41,49 @@
|
||||
|
||||
同一样本上 Jev vs 现行(相对门槛用这一表):
|
||||
|
||||
| 层 | n | Jev intent | 现行 intent | 差(Jev−现行) | 门槛现行−3pp |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: |
|
||||
| choice | 400 | 99.0% | 98.8% | 0.2% | 95.8% |
|
||||
| collect | 400 | 92.2% | 97.5% | -5.2% | 94.5% |
|
||||
| none | 100 | 96.0% | 94.0% | 2.0% | 91.0% |
|
||||
| 层 | n | Jev intent | 现行 intent | 差(Jev−现行) | 门槛现行−3pp | 判定 |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: | --- |
|
||||
| choice | 400 | 99.0% | 98.8% | 0.2% | 95.8% | 过 |
|
||||
| collect | 400 | 92.2% | 97.5% | -5.2% | 94.5% | 不可判(复核与对照同源) |
|
||||
| none | 100 | 96.0% | 94.0% | 2.0% | 91.0% | 过 |
|
||||
|
||||
## 代表性检验(来源 B vs 来源 C)
|
||||
|
||||
来源 B 已标注 157 条(点选 17 / 采集 107 / 无焦点 33;未标注 0),长度 P25/中位/P75/最长 = 12/21/28/172。人工标注,未用模型代标。Jev 在来源 B 上 intent:点选 94.1%、采集 94.4%、无焦点 78.8%;全集 91.1%。无焦点层差 17.2pp > 10pp。
|
||||
|
||||
来源 B 与来源 C 同层 intent 准确率:choice B 94.1% vs C 99.0%(差 4.9%,n_B=17);collect B 94.4% vs C 92.2%(差 2.1%,n_B=107);none B 78.8% vs C 96.0%(差 17.2%,n_B=33)。 有层差 > 10pp,结论降为缺数据。
|
||||
|
||||
来源 B 是真人 + 人工标注 + 线上模型三者齐备的唯一一组。现行无置信度,置信度三列为空。
|
||||
|
||||
| 范围 | n | Jev intent | 现行 intent | Jev 高置信错误 | Jev 低置信召回 | Jev 自洽 |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
|
||||
| 全集 | 157 | 91.1% | 89.2% | 6.4% | 24.1% | 96.2% |
|
||||
| choice | 17 | 94.1% | 88.2% | 0.0% | 66.7% | 94.1% |
|
||||
| collect | 107 | 94.4% | 95.3% | 5.6% | 18.2% | 99.1% |
|
||||
| none | 33 | 78.8% | 69.7% | 12.1% | 20.0% | 87.9% |
|
||||
|
||||
无焦点层现行 intent 69.7%、Jev 78.8%。现行更低,说明 78.8% 主要是这 33 条本身难,不是单 Jev 不行。
|
||||
|
||||
无焦点层 gold × 预测混淆计数(只有计数,无原文)。预测出现 `answer_current_focus` 是因为模型把无焦点句当成在回答采集题。
|
||||
|
||||
### gold × Jev(n=33)
|
||||
|
||||
| gold \ pred | provide_new_evidence | stop_rectification | ask_about_result | unclear | answer_current_focus |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: |
|
||||
| provide_new_evidence | 24 | 0 | 0 | 0 | 0 |
|
||||
| stop_rectification | 0 | 0 | 0 | 0 | 0 |
|
||||
| ask_about_result | 0 | 0 | 1 | 0 | 0 |
|
||||
| unclear | 0 | 0 | 0 | 1 | 7 |
|
||||
| answer_current_focus | 0 | 0 | 0 | 0 | 0 |
|
||||
|
||||
### gold × 现行(n=33)
|
||||
|
||||
| gold \ pred | provide_new_evidence | stop_rectification | ask_about_result | unclear | answer_current_focus |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: |
|
||||
| provide_new_evidence | 21 | 0 | 0 | 0 | 3 |
|
||||
| stop_rectification | 0 | 0 | 0 | 0 | 0 |
|
||||
| ask_about_result | 0 | 0 | 0 | 1 | 0 |
|
||||
| unclear | 1 | 0 | 0 | 2 | 5 |
|
||||
| answer_current_focus | 0 | 0 | 0 | 0 | 0 |
|
||||
|
||||
## 置信度–准确率曲线与 θ
|
||||
|
||||
推荐 θ = 0.9。点:
|
||||
@@ -154,6 +185,25 @@
|
||||
| 若同一句话既明确否定当前采集题又补充了新的带时间经历,intent 仍为 answer_current_focus 且 answer_class 为 no,不要改成 provide_new_evidence。 | Code keeps no + Noul true. Jev intent/Noul are independent. | Jev may split this pair; code does not re-vote intent from the Noul. |
|
||||
| 不要按关键词表或正则猜测,只根据当前问题与用户这句话的语义分类。 | Not sent. Jev has no keyword table in the question. | The meta-instruction is dropped (Jev answers the written question, not the intended one). |
|
||||
|
||||
## 限制
|
||||
|
||||
1. **复核 ≈ 生产提示,且复核模型 = 对照模型。** `REVIEW_RUBRIC` 与生产 `COLLECT_INSTRUCTIONS` 逐句对应;生成 / 复核 / 对照都是 `deepseek-flash`。进入测试集的 900 条是「Flash 用近生产提示能答对目标标签」的那 900 条,被剔的 36 条恰是 Flash 不同意的。因此来源 C 上现行 97.5% / 98.8% / 94.0% 是构造出来的上界。采集层「Jev 92.2% 未过相对门槛 94.5%」**不可当作 Jev 输给现行的证据**。
|
||||
2. **采集层 intent 错例的 gold 有争议。** 来源 C 采集层 Jev intent 错例 31 条:`provide_new_evidence → answer_current_focus` 17、`unclear → answer_current_focus` 9、`stop → unclear` 4、`ask → unclear` 1。17 条 pne 几乎全是「另外 2019 年我换工作搬了家」句式,若干尾句落在当前题域,按生产提示可读成 `answer_current_focus + unsure`。9 条 unclear(「一时半会儿真捋不明白」)按「记不清 → unsure」也读得通。两类合计 ≥ 20 条,占该层 intent 错例约 2/3。gold 来自「生成目标 + Flash 复核同意」,不等于人工真值。改写示例:
|
||||
- 「另外 2019 年换过工作,感情那会儿真没细想。」gold=provide_new_evidence;可读成在回答感情采集题。
|
||||
- 「另外 2019 年搬了家,工作那摊子反而没顾上细想。」gold=provide_new_evidence;可读成在回答工作采集题。
|
||||
- 「这事我一时真说不上来。」gold=unclear;按生产提示是 unsure。
|
||||
3. **语料仍不像真人。** 修复单 1 只把长度和人设写成硬红线(已过)。原单还要求按来源 B 的标点 / 语气词比例约束,未进红线:
|
||||
|
||||
| 指标 | 来源 B(真人) | 来源 C(模拟) |
|
||||
| --- | ---: | ---: |
|
||||
| 含标点 | 13% | 98.9% |
|
||||
| 含语气词(吧/呢/啊/嗯/哦/额/emm) | 4% | 25.6% |
|
||||
| 含年份或月份 | 81% | 47.8% |
|
||||
| 带年份句里写「2019」 | — | 236 / 429 = 55% |
|
||||
| `provide_new_evidence` 里是搬家/换工作 | — | 152 / 157 |
|
||||
|
||||
根因:生成脚本 `ALT_EVENT_HINTS` 给七个领域的「另一件事」全是搬家/换工作,年份未约束,模型收敛到「另外 2019 年搬过家」。这解释了模拟语料不代表真人的一部分,也解释了采集层错例为何长得一样。本单不修,留给产品决定是否再造一轮。
|
||||
|
||||
## 回退
|
||||
|
||||
任何上线方案必须保留回退到现行会话模型的路径。官方限流会动态调整。
|
||||
|
||||
@@ -119,3 +119,20 @@ cache 样本 id 与 `simulated.jsonl` 一致(900/900)。unavailable = 0。
|
||||
- `python -m pytest tests/test_jev_intent_research.py -q`:11 passed
|
||||
- 构建脚本无 `bank = {`;复核函数无 `re.search` 判标签
|
||||
- 线上分类器文件未改
|
||||
|
||||
## 修复轮 2(2026-09-19,`codex/rectification-jev-intent-research-fix2-20260919`)
|
||||
|
||||
任务书:`TASK-rectification-jev-intent-classifier-research-fix2-20260919.md`。**0 token**:从 `.cache/jev_intent/jev_runs_v2.json` 聚合已跑结果,未重造语料、未重跑模型。
|
||||
|
||||
补进报告的三组数(来源 B 157 条):
|
||||
|
||||
| 范围 | n | Jev intent | 现行 Flash intent | Jev 高置信错误 | Jev 低置信召回 | Jev 自洽 |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
|
||||
| 全集 | 157 | 91.1% | 89.2% | **6.4%** | 24.1% | 96.2% |
|
||||
| 点选 | 17 | 94.1% | 88.2% | 0.0% | 66.7% | 94.1% |
|
||||
| 采集 | 107 | 94.4% | 95.3% | 5.6% | 18.2% | 99.1% |
|
||||
| 无焦点 | 33 | 78.8% | **69.7%** | **12.1%** | 20.0% | 87.9% |
|
||||
|
||||
无焦点层 7 条 Jev 错全是 `unclear → answer_current_focus`。现行在无焦点上更低(69.7%),10 条错里 5 条同样是 `unclear → answer_current_focus`。说明 78.8% 主要是这 33 条难,不是单 Jev 不行。
|
||||
|
||||
采集层相对门槛改为「不可判(复核与对照同源)」。限制三节已写入报告。结论仍 **缺数据**,不上线。未立 BUG。
|
||||
|
||||
@@ -234,7 +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` | `PROGRESS-rectification-jev-intent-classifier-research-20260919.md` | **研究单**:TypeSafe Jev(只做 Choice/Score/Noul 的校准判断模型,$0.042/Mtok)能否接管校正流的意图分类。产品 09-19 授权评估(推翻 09-15「分类只用贵模型」需重新拍板)。Agent 模拟校正流造 ≥900 条中文语料(标签先定、独立复核)+ 真机样本做代表性锚,量准确率 / 高置信错误率 / 低置信召回 / 延迟;只离线测,不改线上 | 修复轮已验收:结论 **缺数据** 采纳,不上线;报告补漏单 `TASK-rectification-jev-intent-classifier-research-fix2-20260919.md` 待领取 | 红线 1–8、F1/F2/F5 通过;F3/F4 部分未通过:现行模型在来源 B 157 条上跑了没报、来源 B 上 Jev 高置信错误 6.4%(无焦点 12.1%)没进 MD、无焦点层差没到标签;复核提示 ≈ 生产提示且同模型,采集层相对门槛不可判 |
|
||||
| `TASK-rectification-jev-intent-classifier-research-20260919.md` | `PROGRESS-rectification-jev-intent-classifier-research-20260919.md` | **研究单**:TypeSafe Jev(只做 Choice/Score/Noul 的校准判断模型,$0.042/Mtok)能否接管校正流的意图分类。产品 09-19 授权评估(推翻 09-15「分类只用贵模型」需重新拍板)。Agent 模拟校正流造 ≥900 条中文语料(标签先定、独立复核)+ 真机样本做代表性锚,量准确率 / 高置信错误率 / 低置信召回 / 延迟;只离线测,不改线上 | **待验收**(fix2 已交,结论仍 **缺数据**) | `codex/rectification-jev-intent-research-fix2-20260919`。来源 B 现行 Flash 已入表(全集 89.2%、无焦点 69.7%);Jev 高置信错误全集 6.4% / 无焦点 12.1%;无焦点混淆全是 unclear→answer_current_focus。采集层相对门槛标不可判。 |
|
||||
|
||||
| `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` |
|
||||
|
||||
|
||||
@@ -327,6 +327,58 @@ def cache_key(run_id: str, sample: Mapping[str, Any]) -> str:
|
||||
return f"{run_id}:{sample['id']}:{digest}"
|
||||
|
||||
|
||||
NONE_CONFUSION_LABELS = (
|
||||
"provide_new_evidence",
|
||||
"stop_rectification",
|
||||
"ask_about_result",
|
||||
"unclear",
|
||||
"answer_current_focus",
|
||||
)
|
||||
|
||||
|
||||
def attach_from_cache(
|
||||
samples: Sequence[dict[str, Any]],
|
||||
cache: Mapping[str, Any],
|
||||
run_ids: Sequence[str],
|
||||
) -> dict[str, int]:
|
||||
missing = {rid: 0 for rid in run_ids}
|
||||
for sample in samples:
|
||||
for rid in run_ids:
|
||||
key = cache_key(rid, sample)
|
||||
if key in cache:
|
||||
sample[rid] = cache[key]
|
||||
else:
|
||||
missing[rid] += 1
|
||||
return missing
|
||||
|
||||
|
||||
def strip_confidence(metrics: Mapping[str, Any] | None) -> dict[str, Any] | None:
|
||||
if metrics is None:
|
||||
return None
|
||||
out = dict(metrics)
|
||||
out["high_conf_error_rate"] = None
|
||||
out["low_conf_coverage"] = None
|
||||
out["low_conf_recall"] = None
|
||||
return out
|
||||
|
||||
|
||||
def confusion_counts(
|
||||
rows: Sequence[Mapping[str, Any]],
|
||||
pred_key: str,
|
||||
labels: Sequence[str] = NONE_CONFUSION_LABELS,
|
||||
) -> dict[str, Any]:
|
||||
counts = {gold: {pred: 0 for pred in labels} for gold in labels}
|
||||
other = 0
|
||||
for row in rows:
|
||||
gold = (row.get("gold") or {}).get("intent")
|
||||
pred = (row.get(pred_key) or {}).get("intent")
|
||||
if gold in counts and pred in counts[gold]:
|
||||
counts[gold][pred] += 1
|
||||
else:
|
||||
other += 1
|
||||
return {"labels": list(labels), "counts": counts, "other": other, "n": len(rows)}
|
||||
|
||||
|
||||
def run_batch(
|
||||
samples: Sequence[Mapping[str, Any]],
|
||||
*,
|
||||
@@ -471,8 +523,8 @@ def write_markdown(report: Mapping[str, Any]) -> None:
|
||||
lines.append("")
|
||||
lines.append("同一样本上 Jev vs 现行(相对门槛用这一表):")
|
||||
lines.append("")
|
||||
lines.append("| 层 | n | Jev intent | 现行 intent | 差(Jev−现行) | 门槛现行−3pp |")
|
||||
lines.append("| --- | ---: | ---: | ---: | ---: | ---: |")
|
||||
lines.append("| 层 | n | Jev intent | 现行 intent | 差(Jev−现行) | 门槛现行−3pp | 判定 |")
|
||||
lines.append("| --- | ---: | ---: | ---: | ---: | ---: | --- |")
|
||||
for layer in ("choice", "collect", "none"):
|
||||
cell = paired.get(layer) or {}
|
||||
def pct(value: float | None) -> str:
|
||||
@@ -481,8 +533,16 @@ def write_markdown(report: Mapping[str, Any]) -> None:
|
||||
cur = cell.get("current_intent")
|
||||
delta = None if jev is None or cur is None else jev - cur
|
||||
gate = None if cur is None else cur - 0.03
|
||||
if layer == "collect":
|
||||
verdict = "不可判(复核与对照同源)"
|
||||
elif jev is None or cur is None:
|
||||
verdict = "—"
|
||||
elif round(jev * 100, 1) >= round((cur - 0.03) * 100, 1):
|
||||
verdict = "过"
|
||||
else:
|
||||
verdict = "未过"
|
||||
lines.append(
|
||||
f"| {layer} | {cell.get('n', 0)} | {pct(jev)} | {pct(cur)} | {pct(delta)} | {pct(gate)} |"
|
||||
f"| {layer} | {cell.get('n', 0)} | {pct(jev)} | {pct(cur)} | {pct(delta)} | {pct(gate)} | {verdict} |"
|
||||
)
|
||||
else:
|
||||
lines.append(report["meta"].get("current_model_note") or "未跑现行模型。")
|
||||
@@ -492,6 +552,65 @@ def write_markdown(report: Mapping[str, Any]) -> None:
|
||||
"",
|
||||
report["metrics"]["representativeness"]["note"],
|
||||
"",
|
||||
]
|
||||
b_jev = report["metrics"].get("source_b")
|
||||
b_jev_layer = report["metrics"].get("source_b_by_layer") or {}
|
||||
b_cur = report["metrics"].get("current_source_b")
|
||||
b_cur_layer = report["metrics"].get("current_source_b_by_layer") or {}
|
||||
b_cons = report["metrics"].get("source_b_jev_self_consistency") or {}
|
||||
if b_jev:
|
||||
def pct(value: float | None) -> str:
|
||||
return "—" if value is None else f"{value:.1%}"
|
||||
lines += [
|
||||
"来源 B 是真人 + 人工标注 + 线上模型三者齐备的唯一一组。现行无置信度,置信度三列为空。",
|
||||
"",
|
||||
"| 范围 | n | Jev intent | 现行 intent | Jev 高置信错误 | Jev 低置信召回 | Jev 自洽 |",
|
||||
"| --- | ---: | ---: | ---: | ---: | ---: | ---: |",
|
||||
]
|
||||
rows_spec = [("全集", b_jev, b_cur, b_cons.get("all"))]
|
||||
for layer in ("choice", "collect", "none"):
|
||||
rows_spec.append((
|
||||
layer,
|
||||
b_jev_layer.get(layer) or {},
|
||||
b_cur_layer.get(layer) or {},
|
||||
b_cons.get(layer),
|
||||
))
|
||||
for name, jev_m, cur_m, cons in rows_spec:
|
||||
lines.append(
|
||||
f"| {name} | {jev_m.get('n', 0)} | {pct(jev_m.get('intent_acc'))} | {pct((cur_m or {}).get('intent_acc'))} | "
|
||||
f"{pct(jev_m.get('high_conf_error_rate'))} | {pct(jev_m.get('low_conf_recall'))} | {pct(cons)} |"
|
||||
)
|
||||
lines.append("")
|
||||
none_cur = (b_cur_layer.get("none") or {}).get("intent_acc")
|
||||
none_jev = (b_jev_layer.get("none") or {}).get("intent_acc")
|
||||
if none_cur is not None and none_jev is not None:
|
||||
lines.append(
|
||||
f"无焦点层现行 intent {none_cur:.1%}、Jev {none_jev:.1%}。"
|
||||
"现行更低,说明 78.8% 主要是这 33 条本身难,不是单 Jev 不行。"
|
||||
)
|
||||
lines.append("")
|
||||
confusion = report["metrics"].get("source_b_none_confusion") or {}
|
||||
if confusion:
|
||||
lines += [
|
||||
"无焦点层 gold × 预测混淆计数(只有计数,无原文)。预测出现 `answer_current_focus` 是因为模型把无焦点句当成在回答采集题。",
|
||||
"",
|
||||
]
|
||||
for title, key in (("gold × Jev", "jev"), ("gold × 现行", "current")):
|
||||
table = confusion.get(key) or {}
|
||||
labels = table.get("labels") or list(NONE_CONFUSION_LABELS)
|
||||
counts = table.get("counts") or {}
|
||||
lines.append(f"### {title}(n={table.get('n', 0)})")
|
||||
lines.append("")
|
||||
header = "| gold \\ pred | " + " | ".join(labels) + " |"
|
||||
sep = "| --- | " + " | ".join("---:" for _ in labels) + " |"
|
||||
lines.append(header)
|
||||
lines.append(sep)
|
||||
for gold in labels:
|
||||
row_counts = counts.get(gold) or {}
|
||||
cells = " | ".join(str(row_counts.get(pred, 0)) for pred in labels)
|
||||
lines.append(f"| {gold} | {cells} |")
|
||||
lines.append("")
|
||||
lines += [
|
||||
"## 置信度–准确率曲线与 θ",
|
||||
"",
|
||||
f"推荐 θ = {report['metrics']['theta']['recommended_theta']}。点:",
|
||||
@@ -530,6 +649,25 @@ def write_markdown(report: Mapping[str, Any]) -> None:
|
||||
lost = row["lost"].replace("|", "\\|") if row["lost"] else "—"
|
||||
lines.append(f"| {row['production']} | {row['jev']} | {lost} |")
|
||||
lines += [
|
||||
"",
|
||||
"## 限制",
|
||||
"",
|
||||
"1. **复核 ≈ 生产提示,且复核模型 = 对照模型。** `REVIEW_RUBRIC` 与生产 `COLLECT_INSTRUCTIONS` 逐句对应;生成 / 复核 / 对照都是 `deepseek-flash`。进入测试集的 900 条是「Flash 用近生产提示能答对目标标签」的那 900 条,被剔的 36 条恰是 Flash 不同意的。因此来源 C 上现行 97.5% / 98.8% / 94.0% 是构造出来的上界。采集层「Jev 92.2% 未过相对门槛 94.5%」**不可当作 Jev 输给现行的证据**。",
|
||||
"2. **采集层 intent 错例的 gold 有争议。** 来源 C 采集层 Jev intent 错例 31 条:`provide_new_evidence → answer_current_focus` 17、`unclear → answer_current_focus` 9、`stop → unclear` 4、`ask → unclear` 1。17 条 pne 几乎全是「另外 2019 年我换工作搬了家」句式,若干尾句落在当前题域,按生产提示可读成 `answer_current_focus + unsure`。9 条 unclear(「一时半会儿真捋不明白」)按「记不清 → unsure」也读得通。两类合计 ≥ 20 条,占该层 intent 错例约 2/3。gold 来自「生成目标 + Flash 复核同意」,不等于人工真值。改写示例:",
|
||||
" - 「另外 2019 年换过工作,感情那会儿真没细想。」gold=provide_new_evidence;可读成在回答感情采集题。",
|
||||
" - 「另外 2019 年搬了家,工作那摊子反而没顾上细想。」gold=provide_new_evidence;可读成在回答工作采集题。",
|
||||
" - 「这事我一时真说不上来。」gold=unclear;按生产提示是 unsure。",
|
||||
"3. **语料仍不像真人。** 修复单 1 只把长度和人设写成硬红线(已过)。原单还要求按来源 B 的标点 / 语气词比例约束,未进红线:",
|
||||
"",
|
||||
"| 指标 | 来源 B(真人) | 来源 C(模拟) |",
|
||||
"| --- | ---: | ---: |",
|
||||
"| 含标点 | 13% | 98.9% |",
|
||||
"| 含语气词(吧/呢/啊/嗯/哦/额/emm) | 4% | 25.6% |",
|
||||
"| 含年份或月份 | 81% | 47.8% |",
|
||||
"| 带年份句里写「2019」 | — | 236 / 429 = 55% |",
|
||||
"| `provide_new_evidence` 里是搬家/换工作 | — | 152 / 157 |",
|
||||
"",
|
||||
"根因:生成脚本 `ALT_EVENT_HINTS` 给七个领域的「另一件事」全是搬家/换工作,年份未约束,模型收敛到「另外 2019 年搬过家」。这解释了模拟语料不代表真人的一部分,也解释了采集层错例为何长得一样。本单不修,留给产品决定是否再造一轮。",
|
||||
"",
|
||||
"## 回退",
|
||||
"",
|
||||
@@ -570,8 +708,9 @@ def decide_verdict(report: dict[str, Any]) -> dict[str, str]:
|
||||
if jev is None or cur is None:
|
||||
continue
|
||||
ok = round(jev * 100, 1) >= round((cur - 0.03) * 100, 1)
|
||||
mark = "不可判(复核与对照同源)" if layer == "collect" else ("过" if ok else "未过")
|
||||
bits.append(
|
||||
f"{layer} Jev {jev:.1%} vs 现行 {cur:.1%}(门槛 {cur-0.03:.1%},{'过' if ok else '未过'})"
|
||||
f"{layer} Jev {jev:.1%} vs 现行 {cur:.1%}(门槛 {cur-0.03:.1%},{mark})"
|
||||
)
|
||||
if bits:
|
||||
relative_note = " 相对 −3pp(同一样本):" + ";".join(bits) + "。"
|
||||
@@ -693,11 +832,12 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||
parser.add_argument("--sample-fraction", type=float, default=1.0)
|
||||
parser.add_argument("--current-second-fraction", type=float, default=1.0 / 3)
|
||||
parser.add_argument("--sample-seed", type=int, default=20260919)
|
||||
parser.add_argument("--offline", action="store_true", help="aggregate from cache only, no API calls")
|
||||
args = parser.parse_args(argv)
|
||||
if not args.current_only and not os.environ.get("TYPESAFE_API_KEY"):
|
||||
if not args.offline and not args.current_only and not os.environ.get("TYPESAFE_API_KEY"):
|
||||
print("TYPESAFE_API_KEY missing", file=sys.stderr)
|
||||
return 2
|
||||
if args.current_only and not os.environ.get("DEEPSEEK_API_KEY"):
|
||||
if not args.offline and args.current_only and not os.environ.get("DEEPSEEK_API_KEY"):
|
||||
print("DEEPSEEK_API_KEY missing", file=sys.stderr)
|
||||
return 2
|
||||
synthetic = load_jsonl(SAMPLES_DIR / "synthetic.jsonl")
|
||||
@@ -715,8 +855,13 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||
_merge_report_preds(samples)
|
||||
cache = load_cache()
|
||||
current_sample: list[dict[str, Any]] = []
|
||||
second_current: list[dict[str, Any]] = []
|
||||
if args.offline:
|
||||
miss_c = attach_from_cache(samples, cache, ("jev_1", "jev_2", "current_1", "current_2"))
|
||||
miss_b = attach_from_cache(source_b, cache, ("jev_1", "jev_2", "current_1"))
|
||||
print(json.dumps({"offline_missing": {"samples": miss_c, "source_b": miss_b}}, ensure_ascii=False), flush=True)
|
||||
try:
|
||||
if not args.current_only:
|
||||
if not args.offline and not args.current_only:
|
||||
run_batch(samples, workers=args.workers, run_id="jev_1", cache=cache)
|
||||
save_cache(cache)
|
||||
second = samples
|
||||
@@ -734,7 +879,7 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||
run_batch(source_b, workers=args.workers, run_id="jev_2", cache=cache)
|
||||
save_cache(cache)
|
||||
source_c_rows = [row for row in samples if row.get("source") == "C"]
|
||||
if args.current_only or os.environ.get("DEEPSEEK_API_KEY"):
|
||||
if not args.offline and (args.current_only or os.environ.get("DEEPSEEK_API_KEY")):
|
||||
if args.sample_fraction < 1:
|
||||
current_sample = stratified_sample(
|
||||
source_c_rows, fraction=args.sample_fraction, seed=args.sample_seed,
|
||||
@@ -802,6 +947,38 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||
layer: layer_metrics([row for row in source_b if row.get("layer") == layer], pred_key="jev_1")
|
||||
for layer in ("choice", "collect", "none")
|
||||
} if source_b else {}
|
||||
current_source_b = strip_confidence(
|
||||
layer_metrics(source_b, pred_key="current_1") if source_b and any(row.get("current_1") for row in source_b) else None
|
||||
)
|
||||
current_source_b_by_layer = {
|
||||
layer: strip_confidence(layer_metrics(
|
||||
[row for row in source_b if row.get("layer") == layer and row.get("current_1")],
|
||||
pred_key="current_1",
|
||||
))
|
||||
for layer in ("choice", "collect", "none")
|
||||
} if source_b and any(row.get("current_1") for row in source_b) else {}
|
||||
source_b_jev_self_consistency = (
|
||||
{
|
||||
"all": self_consistency(source_b, "jev_1", "jev_2"),
|
||||
**{
|
||||
layer: self_consistency(
|
||||
[row for row in source_b if row.get("layer") == layer],
|
||||
"jev_1",
|
||||
"jev_2",
|
||||
)
|
||||
for layer in ("choice", "collect", "none")
|
||||
},
|
||||
}
|
||||
if source_b else {}
|
||||
)
|
||||
none_b = [row for row in source_b if row.get("layer") == "none"]
|
||||
source_b_none_confusion = (
|
||||
{
|
||||
"jev": confusion_counts(none_b, "jev_1"),
|
||||
"current": confusion_counts(none_b, "current_1"),
|
||||
}
|
||||
if none_b else {}
|
||||
)
|
||||
represent_fail = False
|
||||
represent_layers: dict[str, Any] = {}
|
||||
if not source_b or len(source_b) < 30:
|
||||
@@ -904,6 +1081,10 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||
"source_a": source_a_metrics,
|
||||
"source_b": source_b_metrics,
|
||||
"source_b_by_layer": source_b_by_layer,
|
||||
"current_source_b": current_source_b,
|
||||
"current_source_b_by_layer": current_source_b_by_layer,
|
||||
"source_b_jev_self_consistency": source_b_jev_self_consistency,
|
||||
"source_b_none_confusion": source_b_none_confusion,
|
||||
"representativeness": {
|
||||
"note": represent_note,
|
||||
"fail": represent_fail,
|
||||
@@ -933,6 +1114,11 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||
} for k, v in jev_metrics.items()},
|
||||
"self_consistency": jev_cons,
|
||||
"source_b_n": len(source_b),
|
||||
"current_source_b": {
|
||||
"n": (current_source_b or {}).get("n"),
|
||||
"intent": (current_source_b or {}).get("intent_acc"),
|
||||
},
|
||||
"source_b_none_confusion_n": (source_b_none_confusion.get("jev") or {}).get("n"),
|
||||
}, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
@@ -132,6 +132,16 @@ class JevIntentCorpusTests(unittest.TestCase):
|
||||
for name in names:
|
||||
self.assertNotIn(name, message)
|
||||
|
||||
def test_report_json_includes_current_source_b(self) -> None:
|
||||
path = ROOT / "docs" / "research" / "jev_intent_2026_09_19.json"
|
||||
if not path.is_file():
|
||||
self.skipTest("report json not generated yet")
|
||||
payload = json.loads(path.read_text(encoding="utf-8"))
|
||||
current = (payload.get("metrics") or {}).get("current_source_b")
|
||||
self.assertIsNotNone(current)
|
||||
self.assertEqual(current["n"], payload["meta"]["source_b_n"])
|
||||
self.assertIn("source_b_none_confusion", payload["metrics"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user