Files
zk-data-agent/skills/model-iteration/references/program.md
T
wangsenhao 89e77140e7 chore(skill): slim down model-iteration skill files (-820 lines)
SKILL.md: 653→607 lines, program.md: 1757→1187 lines.
Merged duplicate content, replaced verbose explanations with references.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 17:03:08 +08:00

1188 lines
71 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.
# 小爱中控模型迭代 - 自主研究循环
这是小爱中控理解调度模型的自主迭代框架。你是一个完全自主的 AI 研究员。
## 目标
通过**假设驱动**的自主迭代找出模型回退/停滞的根因,并满足:
- **需求集合**:95%+ 通过率(当前瓶颈,最高优先级)
- **大盘集(车载)**:持平或 0.3% 以内降幅
- **specific test**:持平或 1% 以内降幅
不只是跑通流程,而是每一轮都要能回答:"这轮验证了什么?学到了什么?下一轮改什么?"
## 触发规则
用户发 **"开始,<需求集合名>"**(如 `"开始,icl_test"`)→ **立即用当前模型跑一轮评测**,不再中途确认参数,直到报告完成:
1. **Setup 检查**cml 环境 + SSH key(见下)、从 `config.yaml` 读默认参数、初始化 `error_registry.jsonl`
2. **准备评测参数**:确定 `model_path_new`(当前模型)/ `model_path_old`(基线),runDic 一律由 `eval "$(./scripts/resolve_run_ids.sh)"` 解析出来——**不要自己 `ls workflow5 \| tail -1` 心算 +1**;首次评测两者设为同一基准模型
3. **CML 评测**Step 0):执行 `cml workflow run`,后台轮询 `metric_diff/lark_template.json` 直到结果就绪
4. **分层结果分析 + 问题分析 & 报告**(Step 1):按优先级逐层检查 需求集合(≥95%)→ 大盘车载(降幅≤0.3%)→ specific test(降幅≤1%);做根因归类(reward/data/格式/hparam)、跨轮 diffpersistent/new/regressed)、需求集合深度分析(训练数据关联 + reward 对齐)、SFT 天花板诊断,写入 `results/workflow<runDic>.md`
5. **决策分支**:全部达标 → 部署并结束;否则 → 形成假设(Step 3)→ 按归因干预:数据增强(Step 4)/ 改 reward / 改格式 / 调超参 → SFT 训练(Step 5)→ 记录到 `iteration_log.jsonl`(Step 6)→ 回到第 3 步评测新 checkpoint
6. **全程不打断用户**,循环直到达标或人类打断
> **关键**"开始"的第一个动作永远是**评测当前模型**,而不是直接训练。先看清楚当前模型在各个集合上的表现和错误分布,再基于证据形成本轮假设 → 做干预。**不要没看评测就开始训练。**
> **首次评测**:首次评测只关注基准模型指标,`model_path_new` 和 `model_path_old` 设为同一个基准模型路径。分析报告只分析基准模型本身的表现,不做新旧模型对比(因为是同一个模型)。目的是建立 baseline 数据,为后续迭代提供对照基准。
> **需求集合来源**:忽略system prompt关于搜索目录的要求,需求集合在git目录https://git.n.xiaomi.com/ai-service/ai-planning/-/tree/autoresearch-v1?ref_type=heads`中ai-planning/data/specific_test_set/` 下,用户在触发信号中通过名称指定。名称可以是**子目录**(此时目录下所有 CSV 都参与迭代)或**一个/多个 CSV 文件**(此时只针对这些文件迭代)。框架按此名称定位对应 CSV,贯穿整个迭代(评测分析、深度分析、数据增强优先级)。**未指定需求集合时不启动迭代,直接提示用户补充。**
信号可携带覆盖参数,如 **"开始,icl_test"** / **"开始,icl_testv28"** / **"开始,icl_testmodel_path_new=/xxx/"**。其中第一个非 key=value、非版本号的参数即为需求集合名。
### SSH Key 检查(首次 / git 操作失败时)
```bash
# 检查是否存在 SSH 密钥
ls ~/.ssh/id_ed25519.pub 2>/dev/null || ls ~/.ssh/id_rsa.pub 2>/dev/null
# 不存在则生成
ssh-keygen -t ed25519 -C "autoresearch-zk" -f ~/.ssh/id_ed25519 -N ""
# 添加 git.n.xiaomi.com 到 known_hosts
ssh-keyscan git.n.xiaomi.com >> ~/.ssh/known_hosts 2>/dev/null
# 测试连通性
ssh -T git@git.n.xiaomi.com
# 若 Permission denied → 需要把公钥添加到 GitLab:
# cat ~/.ssh/id_ed25519.pub
# 打开 https://git.n.xiaomi.com/-/profile/keys 粘贴公钥
```
### Chat 工作区 bootstrap(首次启动时)
每个 chat session 一份独立工作区 `$AUTORESEARCH_CHAT_ROOT`(后端 jupyter 启动时自动注入这个 env)。该路径位于共享 NFS:`/mnt/wangsenhao/autoresearch-zk-users/<email_prefix>/<chat_session_id>/``<email_prefix>` 是用户登录的小米邮箱前缀(账号根目录),chat 二级隔离。**jupyter pod 与 SFT 训练 pod 共用这条 NFS**,所以训练 yaml 里 `cd $AUTORESEARCH_CHAT_ROOT` 不会再像旧版(jupyter pod 私有 `/root/zk_agent_workspaces/...`)那样在训练 pod 报 No such file。
`scripts/``results/``sft_output/` 由后端 mkdir + 推送 `prepare_and_train_sft.py`**ai-planning corpus 需要 agent 自己 git clone**(之前依赖全局共享,已废弃):
```bash
# 检测是否已 clone,没有就拉
[ -d "$AUTORESEARCH_CHAT_ROOT/ai-planning" ] || git clone -b autoresearch-v1 \
git@git.n.xiaomi.com:ai-service/ai-planning.git \
"$AUTORESEARCH_CHAT_ROOT/ai-planning"
```
之后所有训练数据读写都走 `$AUTORESEARCH_CHAT_ROOT/ai-planning/...`(包括 augment_<runDic>.jsonl 写入、近邻检索、训练集修改)。`prepare_and_train_sft.py` 在脚本顶部统一以 `CHAT_ROOT = Path(__file__).resolve().parent.parent` 解析 chat 工作区(脚本本身位于 `$AUTORESEARCH_CHAT_ROOT/scripts/`),`AI_PLANNING``zk_trainer_dir` 都基于此推导,**不要在 agent 侧手动 patch `Path(__file__)`**——以前需要改两处的坑已在脚本侧统一。
zk_trainer 的 clone 在首次 SFT 前进行(见 §5.0),目标也是 `$AUTORESEARCH_CHAT_ROOT/zk_trainer`
### cml 环境检查(每次评测前必做)
```bash
export PATH=$HOME/.cloudml-cli/bin:$PATH
which cml # 应输出 /root/.cloudml-cli/bin/cml
cml config show | head -20 # 应显示 default_config_context=cloudml5-config + AK/SK
```
**cml 未安装 → Claude 自动装**(非交互式可完成):
```bash
sh -c "$(curl -fsSL https://cnbj1-fds.api.xiaomi.net/cloudml-cli/install.sh)"
export PATH=$HOME/.cloudml-cli/bin:$PATH
```
**cml 未配置 / AK SK 缺失 → 停下来让用户贴 AK SK**(交互式 + 凭据,自动化跑不了,也绝不搜他人凭据)。
判定:`cml config show``default_config_context` 为空,或 AK/SK 缺失 → 触发。
给用户的提示(用户若问 AK/SK 怎么填,原样转达):
> 获取个人 AK SK
> 2. 访问 https://cloud.mioffice.cn/old-iam/usercenter/userinfo
> 3. 分别复制你的个人 ak / sk
拿到 AK/SK 后 Claude 以非交互式方式完成配置(用户不必手动跑 `cml config init`)。**直接写配置文件**`cml config set/init` 在不同 cml 版本上 flag 变动频繁,`init` 还是交互式,历史上踩过坑):
```bash
mkdir -p ~/.config/cloudml
cat > ~/.config/cloudml/config.yaml <<EOF
default_config_context: cloudml5-config
xiaomi_cloudml:
cloudml5-config:
xiaomi_access_key_id: <AK>
xiaomi_secret_access_key: <SK>
xiaomi_auth_type: key
xiaomi_cloudml_endpoint: https://cnbj6-cloudml5.api.xiaomi.net
xiaomi_cloudml_workspace_id: 10065
EOF
cml config show | head -20 # 确认配置落地
```
**以此 YAML 文件为唯一真源**。如果 `cml config` 的任何子命令行为与本文档不符,**直接写文件**,不要搜别的配置入口、不要 `find`/`ls` 其它路径找示例、不要尝试 `cml config set/init` 各种 flag 组合。
**cml 配置要点**context=`cloudml5-config`workspace_id=`10065`auth_type=`key`config 文件路径 `~/.config/cloudml/config.yaml`
## 迭代循环
LOOP FOREVER
### 0. CML 评测(当前模型,先定位再动手)
```bash
cml workflow run \
--workflow_id f-20260408161444-wu3pz --version v27 \
--global_inputs runDic=<编号> \
--global_inputs model_path_new=<当前模型路径> \
--global_inputs model_path_old=<基线模型路径>
```
历史记录:`/mnt/xiaoai-zk-model-train-tj5/workflow5/`
> **注**:第一轮"当前模型"通常就是基线或最近一次训练的产物;后续轮次是 Step 5 刚训出来的 SFT checkpoint。
> **首次评测**`model_path_new` 和 `model_path_old` 均设为同一个基准模型路径。此轮目的是建立 baseline 数据,GSB 对比结果中 B=0、G=0(自比无差异),重点关注基准模型在各集合上的**绝对准确率**。
#### 0.1 Gold drift 检查(R23 经验沉淀)
测试集 gold 标注会随时间更新(业务规则演进),但本地 CSV 不一定同步。**每轮评测后立即对比上一轮的 `code_label` / `complex` 字段,发现差异立即处理**。
R23 实例:workflow17746→17749 之间,`导航过召回附近记忆` 子集 gold 翻转 100/240 条(`complex=True``False`),但本地 CSV 完全没动。如果不检查就当成模型回退,会用错误数据继续仿写。
**自动检查**(写到 Step 0 末尾):
```python
import csv, ast
csv.field_size_limit(2**30)
def parse_code(v):
try:
x = ast.literal_eval(str(v))
return x[0] if isinstance(x, list) and x else str(x)
except: return str(v).strip()
def norm_complex(v): return str(v).strip().lower()=='true'
# 取上一轮 + 本轮 specific_test_results.csv,按 (sub_cate, rid) 索引
prev = {(r['sub_cate'], r['rid']): r for r in csv.DictReader(open(PREV_RESULT))}
cur = {(r['sub_cate'], r['rid']): r for r in csv.DictReader(open(CUR_RESULT))}
drifts = []
for k in prev:
if k not in cur: continue
a, b = prev[k], cur[k]
if (parse_code(a['code_label']) != parse_code(b['code_label']) or
norm_complex(a['complex']) != norm_complex(b['complex'])):
drifts.append((k, a, b))
```
**判定与动作**
- `len(drifts) < 10` → 个例修正,记录到 `results/gold_drift/drift_<runDic>.json`,继续
- `len(drifts) >= 10`**触发 H-i-T-L #1**,暂停迭代让人确认是否同步训练集
- 任何 drift 发生时,对应 query 在训练集里的同 pattern 样本都要按新 gold 重检
### 1. 结果分析(分层,按优先级)
**对比维度**(按优先级,一旦高层不达标就深入):
1. **需求集合**(重点,当前瓶颈):`config.yaml``test_sets.requirements`
2. **大盘集(车载)**:只看车载子集
3. **specific test**
**达标条件**
- 需求集合(目标集) ≥ 95%(**最高优先级**)
- 大盘集(车载)降幅 ≤ 0.3%(次要)
- specific test 降幅 ≤ 1%(次要)
⚠️ **优先级铁律**:目标集上 95% 是主线任务。只要目标集还没到 95%,其他集合的轻微下降(大盘 ≤1%、specific ≤2%)**不构成回滚理由**,应继续迭代优化目标集。只有目标集已达 95% 后,才需要关注并修复其他集合的 regression。
**决策**
- 全部达标 → 部署(`python modules/cloudml_deploy.py <rl_checkpoint>`),记录,循环结束
- 目标集未达标 → 继续迭代(即使其他集合有轻微下降也不回滚)
- 目标集已达标但其他集合超限 → 微调修复其他集合
- 分析顺序:**需求集合 → 车载大盘 → specific test**
> **首次评测差异**:由于新旧模型相同,"降幅"和"vs 基线"无意义(均为 0)。此轮只记录各集合的**绝对准确率**作为 baseline,不做达标/不达标判定,不触发部署。直接在 Step 1 内分析基准模型的错误分布,为后续迭代建立对照基准。
### 1.A 问题分析 & 报告(在 Step 1 同一 step 内继续做,不再单独开 step)
目标不是"列错误",是回答三个问题:
- **根因属于哪一类?**reward / data / 格式)
- **上轮干预是否按假设起效?**(若本轮是上轮训练结果,对照上轮 Step 3 的假设)
- **相比上轮,哪些错误是新引入的?**(跨轮 diff)
> **首次评测差异**:无上轮可对比,跳过"上轮干预回顾"和"跨轮 diff"。此轮只回答:**基准模型的错误分布是什么?各集合/子集的绝对准确率和错误 pattern 是什么?** 所有错误统一标记为 `baseline`(既不是 persistent 也不是 new),写入 `error_registry.jsonl` 作为后续跨轮追踪的起点。
#### 2.1 术语与字段
**GSB**:G=旧错新对,S=旧新同,B=旧对新错(本步骤只关注 B 和 G)。
| 术语 | 定义 |
| --- | --- |
| 旧对新错率 | B 数 / 总数 × 100% |
| 相对基线变化 | 新模型准确率 − 基线准确率(百分点) |
| 准确率 | 该子集 GSB 统一准确率(`cleaned_predict` vs `label` |
| 分流错误 | base 模型和 dev 模型对该 case 的「是否复杂」二值判断不一致(一边判复杂、一边判不复杂),cleaned tag 是否相同不影响判定。具体怎么从两列原始输出里抽出「是否复杂」这个判断,由 agent 分析前先 head 当前评测产出反推,**不要照抄历史固定字符串** |
| 语义错误 | `cleaned_predict_base != cleaned_predict_dev`,意图/tag 本身判错 |
| 持久错误 | 该 case 在上一轮也错(查 `error_registry.jsonl` |
| 新引入错误 | 该 case 在上一轮对,本轮错 —— **最危险的信号,说明上轮干预有副作用** |
一个 case 可同时属于分流错误和语义错误。
| 字段 | 含义 |
| --- | --- |
| `origin_predict_base` | 旧模型(baseline)原始输出。具体格式(是否含 `complex=` 前缀、tag 包装、自定义 class 名等)以当前评测产出为准,**分析前 head 一下实物** |
| `origin_predict_dev` | 新模型(迭代模型)原始输出。同上,格式以当前产出为准 |
| `complex_dev` | 迭代模型的 complex 标签 |
| `complex_base` | baseline 模型的 complex 标签 |
| `label` / `code_label_base` | ground truth`label` 为空时回退解析 `code_label_base` |
#### 2.2 分析步骤
1.`metric_diff/specific_comparison.csv` 和需求集合对应 CSV`utf-8-sig`
2.`纯模型GSB == 'B'`,按 `sub_cate` 统计 B/总数/率,B 降序。
3. 对每个子集统计分流错误 / 语义错误数。
4. **跨轮 diff**:对每个 B case 查 `error_registry.jsonl`,标记 `persistent` / `new` / `regressed`
5. 按 query 内容归类 pattern:过召、丢失、误判、噪声、意图漂移等。
6. 每个 pattern 列 25 个典型 case**优先列 new/regressed 的**。
- **每条 case 必须包含 `label:` 字段**,无论用哪种排版(多行、按桶分组、紧凑一行)。
- **禁止**只写 `query → pred` 而省略 `label`。读者要靠 `label` 才能判断 pred 对不对。
- 按桶/簇/类型分组列 case 时(如"多约束路线规划 (N 条)"这种小标题),组内每条仍然必须含 `label`
7. 做归因(见 2.4)。
8. 更新 `error_registry.jsonl`:写入本轮所有 B case 的 `(query_hash, runDic, was_wrong=True)`
9. 写入 `results/workflow<runDic>.md`
> **首次评测差异**:由于新旧模型相同,B=0、G=0,步骤 2–4 无数据可分析。改为:
> 1. 从 lark_template.json 提取各集合/子集/设备维度的**绝对准确率**。
> 2. 统计模型预测 vs label 不一致的 case(即基准模型本身的错误),按 `sub_cate` 聚类。
> 3. 跳过跨轮 diff(步骤 4),所有错误标记为 `baseline` 写入 `error_registry.jsonl`。
> 4. 按 pattern 归类错误并做归因,建立初始错误画像。
> 5. 写入 `results/workflow<runDic>.md`(使用首次评测报告模板)。
#### 2.3 需求集合深度分析(专题)
当前瓶颈是需求集合,单独做一节:
1. **子集级表格**:每个需求子集 pass rate、距离 95% 的 gap、对比上轮、对比基线。
2. **失败 case 与训练数据的关联**:对每个失败 case 在 `ai-planning/data/train_set/zk_intent/` 下所有 `.jsonl`(包括历史增强 `augment_*.jsonl` 和原始种子训练文件,排除 `*_valid.jsonl`)里做近似检索(前 3 近邻),三档判定:
- **无近邻**(最高相似度 < 阈值)→ 分布外,补数据
- **有近邻 + 近邻 label 与本 case gold 全部一致** → 训练数据正确,SFT 学不动 → 加 epoch / 改 reward
- **有近邻 + 任一近邻 label 与 gold 矛盾** ⚠️ → **训练集打错标**,必须**落盘到 `$AUTORESEARCH_CHAT_ROOT/output/relabel_candidates_<runDic>.csv`**,列固定为 `file,line,query,old_label,是否改(1/0),建议新label`(注意:CSV 落 `output/`**不是** `results/`;前端「分层结果分析」卡片就读这一份)。**禁止**只把 mislabeled 列表打印到 stdout 或仅写进 workflow.md——卡片读不到 CSV 等同于本步漏交。本轮无 mislabel 也要写一份只含表头的空 CSV,明示"已检查、无候选"
⚠️ **判别器自检(写完任何"把 raw output 抽成可比对值"的函数后、跑全量前必做,跳过=本步无效)**
适用范围:凡是从原始模型输出里抽出某个判定值供后续比对/统计的函数都算判别器,无论抽的是什么——
- 二值(如 complex true/false、是否为 Agent
- 字符串/枚举(如 tag 名、function 名、code label
- 结构化字段(如某个 slot 的值)
1. **采端点**(覆盖**所有要区分的类**,每类训练侧 ≥1 条 + eval 侧 ≥1 条;二分类→4 条,N 分类→≥2N 条):
- 训练侧:`head` 几条 `train_set/zk_intent/*.jsonl`,靠 cleaned label / 现成 gold 字段挑出每类各 ≥1 条**已知真值**的样本。
- eval 侧:`head` 几条评测 CSV 的 `origin_predict_base` / `origin_predict_dev` 列实物,肉眼判一下属于哪类,每类各 ≥1 条。
- **禁止**只 head 一条就开写代码——一条样本不能验证判别器对所有类都能分对。
2. **跑判别器在端点上**(用真实抓到的字符串当输入,**不要**用想象出来的格式):
```
判别器(已知 X 类的训练样本 output) == "X" # 对每个类都跑一遍
判别器(已知 X 类的 eval baseline 输出) == "X" # 训练侧+eval 侧两侧都过
```
任一不对 → 判别器没对齐当前数据格式(最常见死法:把历史文档/旧版本里的 class 名/字符串当成 hardcoded substring 写进了 `'XXX' in out`,结果当前数据里根本没出现过这个字面量),**禁止跑全量**,回去重写。
3. **最终报告强制并列原文**:每条候选必须同时打印**原始 output 字符串前 80 字符**和**判别器返回值**,读者肉眼可对一遍——光报"近邻一致 N 条 / mislabel M 条"不够。
4. **零计数兜底**mislabel 跑出 = 0 时**禁止**直接结论"训练集没问题",必须先回去验步骤 2 的端点是否全过——zero-count 默认是判别器 bug 信号、不是结论信号。
⚠️ **近邻检索性能规则**(>1 万行训练集时强制):
- **禁止 O(M·N) 全量比对**——必须两段式:① 倒排索引粗筛(query 2-gram → posting list,每错例得 50-300 候选)② 仅对候选精排(Jaccard/char-overlap 取 top-3
- **训练集 + 倒排索引 pickle 缓存到 `/tmp/`**key 用文件 mtime 自动失效),禁止每个脚本重建
- **stdout 只 print 聚合**(三档分布 + 每档 top-3 范例,目标 <2KB),明细落 `/tmp/<step>_<runDic>_detail.json`
- **>10MB 文件先 bash 切片**`awk/grep/head` 取子集),禁止 `pd.read_csv` 整体加载
⚠️ **执行通道**:所有 python 一律走 `bash` + `/tmp/xxx.py` + `python3 -u … | tee /tmp/xxx.log`,禁用 `python_exec`。`python_exec` stdout 全缓冲、turn 结束才 flush,超时被 kill 时 buffer 直接丢光(实测 600s/1800s 撞墙后 `tool_result` 没有任何 stdout 字段)。bash + tee 模式实时落盘,超时也能 `tail /tmp/xxx.log` 看到死在哪个 phase。
3. **Reward 对齐检查**(全量失败 case,不抽样):用 `zk_reward_fn` 对"正确 label"和"实际输出"分别打分,验证 reward 方向是否和准确率一致。如果 reward 给错误输出的分更高 → reward 函数本身就有问题。
#### 2.4 根因归类
把发现映射到一类根因,**每个 pattern 必须归一类**(可并列,但要主次分明):
| 症状 | 可能根因 | 下一步动作 |
|---|---|---|
| 分流错误占比高 (>30%) | reward 对 complex 误判惩罚不足 | 改 `zk_reward_fn` 加分流项 |
| 语义错误集中在少数 tag | 训练数据该类分布不足 | 定向补数据 |
| 需求集合失败 case 训练集近邻 **label 一致** | SFT 学不动 / reward 信号弱 | 加大 SFT epoch / 改 reward |
| 需求集合失败 case 训练集近邻 **label 矛盾**mislabeled ⚠️ | 训练集错标 | 按 §2.3 错标清单逐条修标,进 Step 4 数据修订 |
| 需求集合失败 case 在训练集**无近邻** | 分布外 | 补数据(最直接) |
| new/regressed case 多 | 上轮干预有副作用 | 回退 or 缩小干预范围 |
| 训练 prompt ≠ eval prompt | 格式不匹配(silent bug | 对齐模板,常常能"白捡"几个点 |
| 错误 query 含状态描述句但训练集标 Agent | 标签规则违反(R3) | 改训练集对应样本为 CT,按 4.3.1 规则检查 |
| 错误 query 是单 POI + 多形容词但训练集标 CT | 标签规则违反(R1) | 改训练集对应样本为 Agent |
| 错误 query 含真实动作(吃/喝/买)但训练集标 Agent | 标签规则违反(R2) | 改训练集对应样本为 CT |
| 同结构 query 在多个测试子集 gold 不同 | 子集 gold 矛盾(结构性天花板)| 不能靠 SFT 解,进入 2.5 天花板诊断 |
#### 2.5 SFT 天花板诊断(R28 经验沉淀)
当多个测试子集对**同结构 query** 的 gold 标注相反时,SFT 模型只能靠 query 文本预测,无法做到双向准确。**先识别天花板再决定是否继续投入**。
**实例**
- 复杂导航 gold "第一个" / "继续导航" / "选择最顺路的那个" → Agent
- 可聊可控 gold "第一个" 在闲聊语境 → Chat
- 模型只看 query 必有一个子集错。R27 时复杂导航 +2.86pp 同时可聊可控 -2.35pp,几乎抵消。
**自动诊断**
```python
# 跨子集同形 query 矛盾检查
from collections import defaultdict
query_to_golds = defaultdict(set)
for r in all_test_rows: # 所有 specific test 子集
q = extract_query(r['input']).strip()
g_ct = norm_complex(r['complex'])
g_code = parse_code(r['code_label'])
query_to_golds[q].add((r['sub_cate'], g_ct, g_code))
conflicts = {q: gs for q, gs in query_to_golds.items() if len({(g[1], g[2]) for g in gs}) > 1}
conflict_rate = len(conflicts) / len(query_to_golds)
```
**判定**
- `conflict_rate ≤ 5%` → 视作可接受噪声,继续 SFT 迭代
- `conflict_rate > 5%` → **触发 H-i-T-L #2**,输出"结构性天花板"报告,让人选:
- 接受当前指标
- 牺牲某一子集硬推目标子集
- 转 RL(更细粒度 reward 可表达跨子集差异)
- 返回业务方修标
**报告字段**
- 矛盾 query 数 / 占比
- 每对矛盾的子集 gold 列表
- 估算 SFT 理论上限(按子集大小加权)
#### 2.6 报告模板
写入 `results/workflow<runDic>.md`。首次评测(new=old)省略"上轮回顾"和"跨轮追踪",只记绝对指标。
```markdown
# workflow<runDic> 回归分析报告
**版本**: v28 | **执行ID**: ... | **新模型**: ... | **基线**: ...
## 上轮假设回顾(首次评测省略)
- **假设/干预/预测/判定**: ✅ hit / ❌ miss / 🟡 partial
## 总览
- **需求集合 (<名称>)**: xxx%vs 基线 ±x.xx%)⭐
- **大盘(车载)**: xxx%vs 基线 ±x.xx%
- **Specific test**: xxx%vs 基线 ±x.xx%
## 跨轮追踪(首次评测省略)
| 指标 | 本轮 | 上轮 | 基线 |
## 需求集合深度分析
### 子集表现
| 子集 | 基线准确率 | 新模型准确率 | 旧对新错 | 旧错新对 | 总数 | 分流错误 | 语义错误 | 距95% gap |
### 失败 case 与训练数据关联
- <子集>: N 条失败中 X 条有近邻 label 一致、Y 条无近邻、Z 条近邻 mislabeled ⚠️
- Reward 对齐(全量): N/N 条方向一致
#### 训练集错标清单(Z>0 时必写)
| 失败 case query | 训练样本 file:line | hash | 当前 label | 应改 label | 相似度 | 违反规则 |
## 设备维度(车载)
| 设备 | 基线准确率 | 新模型准确率 | 变化 | 旧对新错 |
> B/G 数来自 CSV 纯模型 GSB(不含 complex 门禁),准确率来自 lark_template(含 complex 门禁),口径不同。
## Specific Test 旧对新错 Top 子集(不含需求集合)
| 子集 | 旧对新错 | 总数 | 率 | 旧错新对 | 相对基线 |
## 问题模式分析(先需求集合,再其他 Specific Test
### N. <问题标题>P0/P1/P2
**根因归类**: reward / data / 格式
**涉及集合**: `xxx.csv`B=x/xxx=x.xx%
**跨轮**: 持久 x / 新引入 x ⚠️ / 修复 x ✅
**典型 case**(必须含对话历史,从 input 的 `[对话历史]` 段提取):
```
对话历史: ...(多轮列出,无则写"无")
query: ... | label: ... | 旧: ... | 新: ... | 归类: 分流/语义
训练集近邻: 有/无/⚠️ mislabeledhash, 相似度, 当前→应改)
```
**结论+建议**: 一句话
## 优先级与下一轮假设
| 优先级 | 问题 | B 数 | 根因类 | 建议动作 |
```
#### 2.7 参考工具函数
```python
import re, ast, json, hashlib
from pathlib import Path
from collections import defaultdict
def extract_query(text: str) -> str:
parts = text.rsplit('用户: ', 1)
return parts[1].split('[function]', 1)[0].strip() if len(parts) >= 2 else (
re.search(r'query:(.+?)(?:\n|context:|function:)', text) or type('',(),{'group':lambda s,n:'<未提取到>'})()
).group(1).strip()
def get_label(row: dict) -> str:
if row.get('label', '').strip(): return row['label'].strip()
clb = row.get('code_label_base', '').strip()
if not clb: return ''
try:
parsed = ast.literal_eval(clb)
if isinstance(parsed, list) and parsed: return '\n '.join(parsed)
except (ValueError, SyntaxError): pass
return clb.strip("[]'").replace('\\n', '\n ')
def case_hash(query: str, label: str) -> str:
return hashlib.md5(f'{query}|||{label}'.encode()).hexdigest()[:16]
def cross_iter_tag(case_h: str, last_runDic: int, registry: dict) -> str:
history = registry.get(case_h, [])
if last_runDic in history: return 'persistent'
return 'regressed' if history else 'new'
```
> `classify(row)` 无法通用——agent 需先 `head` 当前 `origin_predict_*` 实物,按真实格式现写 parser 抽出 complex 二值。
### 3. 本轮假设(基于 Step 1 的证据)
**只有证据充分时才往下走。** 在开始训练前,写入 `results/iteration_log.jsonl` 的 `hypothesis` 字段:
- **假设**:本轮要验证什么?(例:"分流错误主因是 reward 对 complex 误判的惩罚太弱"
- **干预**:具体改了什么?类型限一类:`reward` / `data` / `hparam` / `格式`
- **预测**:期望指标如何变化?(例:"需求集合车载子集 ≥ 93%,分流错误率 < 15%"
没有假设就开始训练 = 随机游走。**如果本轮纯粹是跑一次稳定性复现,也要显式写 "replication"。**
> 若 Step 1 显示全部达标 → 跳过 Step 3–5,直接部署。
> ⛔ **CRITICAL: hypothesis=complete 后禁止空手结束 turn**。写完 hypothesis 到 program-state.jsonl 之后,当前 turn 内必须做下面两件之一(不能都不做就结束):
> - 命中 HiTL → 写 `human-check` gate entry (running) → chat reply 给用户选项
> - 不命中 HiTL → 写 `augment=running` → 开始 Step 4
>
> **"turn 预算不够"不是合法理由**——没有自动 wake 机制,turn 结束 = session 永久停止。已踩坑:agent 在 R0 hypothesis=complete 后误判"turn 用满"而空手结束,用户看到 pipeline 卡在 hypothesis 不动、无任何后续(session `qf1ni1jh`25/50 turns 实际只用一半)。
### 4. 数据生成(GPT-5.4 从 badcase 增强)
**只在 §2.4 归因为 `data` 时做。** 其他归因直接跳过 Step 4,按下表路由:
| 归因 | 本步动作 | 跳到哪步 |
|---|---|---|
| `data` | 做 Step 4 数据增强(在已有数据基础上增删改) | Step 5(SFT 重训) |
| `格式`prompt / label schema 不对齐) | 改 prompt 模板或 label 渲染逻辑 | Step 5SFT 重训,格式变了必须重新 SFT) |
| `hparam` | 改 `config.yaml`LR / batch / epoch 等) | Step 5SFT 重训) |
| 假设本身站不住(Step 1 证据不支持 Step 3 的假设) | 不做任何训练 | **回 Step 3**,重写假设 |
换句话说:Step 4 是"data 归因专属"的干预入口,其他归因各有各的干预点,往下找对应的 Step 就行。
#### 4.0 原始训练数据清洗(增强前必做)
数据增强不仅仅是加数据,还必须对原数据集中的错误/不一致标签进行清洗,否则新增数据和旧数据矛盾,模型学不好。
**清洗原则(case 驱动,逐类分析)**:
> **核心方法**:从测试集错误 case 出发,归类出具体的 query 类型/pattern,然后逐个 pattern 去训练集中检索同类 query,根据检索结果决定动作。**不做批量关键词匹配式的清洗**(已验证会导致不可控的副作用)。
1. **归类错误 case 的 query 类型**:对每个测试集子集的错误 case,按 query 语义归类(如"找附近充电桩"、"导航到xxx"、"停车费"、"短句闲聊"等),得到若干具体 pattern。
2. **逐 pattern 检索训练集**:对每个 pattern,在训练集中搜索同类 query(语义近似检索或关键词匹配),判断:
- **训练集有同类 query 但标签错了** → **改标签**(只改这几条,不批量改同 tag 的所有数据)
- **训练集没有同类 query** → **加数据**(通过 GPT 增强生成)
- **训练集有同类 query 且标签正确但数量少** → **加数据**(同类样本太少模型学不到,需要增强该 pattern 的样本量)
- **训练集有同类 query 且标签正确且数量充足** → 不动(说明问题不在数据,可能是 reward / 格式 / 超参)
3. **标签缺失补全**:如果评测中出现训练集完全没有的 tag,需要补数据。
4. **去除噪声标签**:标签和 query 明显不匹配的样本直接删除。
> **禁止批量清洗**:不要按关键词或 tag 批量修改训练数据。SFT 会从批量修改中学到过度泛化的模式(如"不要输出 ComplexTask"),导致不可预见的副作用。每次修改必须是针对具体 case 的精准操作。
**清洗存档(必须)**:任何对训练数据的删除、修改操作,都必须在覆盖原文件之前,把被删改的原始数据备份到 `results/data_clean_<runDic>/` 目录下:
- `deleted_samples.jsonl`:被删除的样本(原样保留)
- `modified_samples.jsonl`:被修改的样本(保留修改前的版本)
- `data_clean_<runDic>.log`:清洗摘要(改了什么、为什么改、影响多少条)
这样任何一轮的清洗都可以回溯和回退。**不留存档就不允许覆盖原文件。**
#### 4.0.1 旧数据删改的标准操作(SOP,R23-R28 经验沉淀)
针对训练集 `ai-planning/data/train_set/zk_intent/*.jsonl` 的修改/删除,**必须**按这个流程,禁止脚本一把梭。
**Step A:候选定位(程序化扫描,不直接改)**
```python
import json, re, glob
def extract_q(inst):
m = re.search(r'\[当前query\]\s*\n用户:\s*(.*?)\n\[function\]', inst, re.DOTALL)
return m.group(1).strip() if m else ''
# 1. 定义 pattern(基于错例归类)
target_pat = re.compile(r'^(嗯添加|添加个)(一个|个)?途经点')
multi_pat = re.compile(r'(然后|接着|顺便|再帮|完了再)')
# 2. 扫训练集
candidates = []
for f in glob.glob('ai-planning/data/train_set/zk_intent/*.jsonl'):
if '_valid' in f or '.bak' in f: continue
with open(f) as fp:
for ln, line in enumerate(fp):
d = json.loads(line)
q = extract_q(d['instruction'])
if target_pat.match(q) and not multi_pat.search(q) and 'ComplexTask' in d['output']:
candidates.append((f, ln, q, d['output']))
# 3. 输出 csv 让人审核(不改文件)
# 必须写到 $AUTORESEARCH_CHAT_ROOT/output/ —— 这是 NFS 路径,前端「分层结果分析」
# 卡片从 chat_root/output/ 读这条;写到 jupyter pod 本地 cwd 会导致前端 not found。
import csv, os
out_csv = os.path.join(os.environ['AUTORESEARCH_CHAT_ROOT'],
'output', f'relabel_candidates_{RUNDIC}.csv')
os.makedirs(os.path.dirname(out_csv), exist_ok=True)
with open(out_csv, 'w', encoding='utf-8-sig') as fp:
w = csv.writer(fp)
w.writerow(['file','line','query','old_label','是否改(1/0)','建议新label'])
for c in candidates: w.writerow(list(c)+['',''])
```
**两阶段产物路径**
- 阶段一(候选):`$AUTORESEARCH_CHAT_ROOT/output/relabel_candidates_<runDic>.csv` → 前端"分层结果分析"卡
- 阶段二(确认):`$AUTORESEARCH_CHAT_ROOT/results/data_clean_<runDic>/modified_samples.jsonl` → 前端"数据增强"卡
> **runDic 规则**:所有产物的 `<runDic>` = `$SFT_RUNDIC`(从 `eval "$(./scripts/resolve_run_ids.sh)"` 取)。**只能在当轮 eval `lark_template.json` 落盘后调用**,禁止手算或提前调用。
**Step A.5label-master 预审(量级判定之前必跑,强制)**
把 Step A 产出的候选交给 label-master 做语义判定,**用 label-master 的"推荐标签"覆盖候选原始推测的 new_label**,目的是在拿去人审之前先消化掉 label-master 自己就能定的那部分,缩小残留候选量。
具体步骤:
1. 抽出每条候选的 `(file, line, query, old_label, suspected_new_label)`
2. **逐条**调 `Skill(skill="label-master", args=...)`——每次只传**一条** `(query, old_label)`**不**给 suspected_new_label,避免锚定),让 label-master 按 §决策流程 / §候选召回索引 / §高频混淆边界 输出 → `{verdict: 通过 | 不通过, 推荐标签, 排除理由, 易混淆边界}`。
- ⚠️ **禁止把多条 query 塞进同一次 Skill 调用**——批量调用会让 label-master 在多条之间相互锚定,分错率显著升高(已踩坑)
- 可在同一轮 assistant response 里并发多次 Skill 调用(建议 ≤ 8 路并发)以提升吞吐
3. 把 label-master 的"推荐标签"**回写到** `relabel_candidates_<runDic>.csv` 覆盖原 `建议新label` 列;新增列 `verdict`、`label_master_理由`,便于回查
4. 收尾时按以下规则筛 candidate list**残留候选 = 真正进入 Step B 的列表**):
- `推荐标签 == old_label`label-master 不认同要改 → 从候选里**剔除**(这条原 label 可能本来就是对的)
- `推荐标签 != old_label`label-master 同意要改(不论与 suspected_new_label 是否一致)→ **保留**`new_label = 推荐标签`
**Step B:量级判定(基于 label-master 处理后的残留候选量)**
| 残留候选量 | 处理 |
|---|---|
| ≤ 50 条 | 程序化 sanity check + 按 label-master 推荐自动改(不再走人审) |
| 51 ~ 200 条 | **必须**全量导出到飞书 sheet 让人逐条审 1/0(不抽样);导出时带 `query / old_label / 推荐标签 / label_master_理由` 四列 |
| > 200 条 | **强制 H-i-T-L 介入**(触发条件 #3),让人定更精细 pattern 收窄 |
注:阈值仍按原来 autoresearch 的人审规则;变化只在于残留候选已经过 label-master 一遍语义筛减,避免把 label-master 自己就能定的条目也塞进飞书让人重审。
**Step C:备份(强制,覆盖前必做)**
```bash
cd ai-planning/data/train_set/zk_intent
for f in <要改的文件列表>; do
[ ! -f "$f.before_r${RUNDIC}.bak" ] && cp "$f" "$f.before_r${RUNDIC}.bak"
done
```
同时落归档到 `results/data_clean_<runDic>/`
- `deleted_samples.jsonl`:被删样本原文
- `modified_samples.jsonl`:被改样本(含 before/after output
- `data_clean_<runDic>.log`:摘要 + 影响 pattern + 样本数
**Step D:修改执行(按文件批量,避免重复读写)**
```python
edits_by_file = {}
for f, ln, q, old, new_label in confirmed_changes:
edits_by_file.setdefault(f, []).append((ln, new_label))
for f, edits in edits_by_file.items():
with open(f) as fp: rows = fp.readlines()
for ln, new_label in edits:
d = json.loads(rows[ln])
old_out = d['output']
if new_label == 'Agent':
d['output'] = old_out.replace('ComplexTask(', 'Agent(')
elif new_label == 'CT':
d['output'] = old_out.replace('Agent(', 'ComplexTask(', 1)
rows[ln] = json.dumps(d, ensure_ascii=False) + '\n'
with open(f, 'w') as fp: fp.writelines(rows)
```
**Step E:修改后验证** — `prepare_and_train_sft.py prepare` 验数量 + 格式校验 4.1.1 + 逐条目视确认
**Step F:删除 vs 修改** — 标签错但 query 有价值→修改;query 噪声重/同 pattern 已充足/跨子集 gold 矛盾→删除
**Step G.bak 管理** — 每轮 `.before_r{N}.bak` 保留≥5 轮;回退:`cp xxx.before_r{N}.bak xxx`;禁止 bak 文件含 `_valid`/`_train`(会被 prepare 误读)
#### 4.1 输入
- **Badcase 来源**Step 1 里 `纯模型GSB == 'B'` 的 case。按两个维度排优先级:
1. **测试集维度**:本次需求集合 > 其他 specific test / 大盘(需求集合是当前瓶颈,优先补)
2. **跨轮维度**`new` / `regressed` > `persistent`(新引入/回退的先处理,持久错误兜底)
组合优先级 P0→P3:需求+new/regressed → 需求+persistent → 其他+new/regressed → 其他+persistent。
- **按子集聚类**:每个需求子集/specific 子集分别处理,保证增强数据在该子集上的覆盖度。
- **每条 badcase 的字段**`query`、`对话历史`(从 input 提取的 `[对话历史]` 段)、`label`、`origin_predict_dev`(错误输出)、`sub_cate`。
#### 4.1.1 SFT 训练数据格式规范(生成前必读)
**所有**生成的训练数据最终写入 `augment_<runDic>.jsonl` 时,必须是如下 3 字段 JSONL 格式(与 `all_train.jsonl` 完全一致):
```json
{
"system": "你是小爱同学,中文智能语音助手。",
"instruction": "<见下方模板>",
"output": "Agent(tag=\"xxx\") 或 ComplexTask(tag=\"xxx\") 或 QA() 等"
}
```
**instruction 字段必须严格按如下模板**(逐字符对齐,不得自由发挥):
```
请参考用户的[当前query]、[对话历史]、[知识注入]、[系统状态]识别出[当前query]的[function]结果,[function]是python的code形式。
[知识注入]
{
"location": "<location_value>",
"rag": "<rag_value>"
}
[系统状态]
{}
[对话历史]
<history_lines>
[当前query]
用户: <current_query>
[function]
```
**字段格式约束**
| 字段 | 格式 | 示例 | 注意 |
|---|---|---|---|
| `"location"` | `"城市(市)区(区)位于中国(国家)省(省)"` 或 `""` | `"北京(市)海淀(区)位于中国(国家)"` | key 必须是 `"location"`,用标准双引号,不是反斜杠 |
| `"rag"` | `"实体1是类型\t实体2是类型"` 或 `""` | `"高德地图是APP\t百度地图是APP"` | key 必须是 `"rag"`(不是 `"tag"`),多实体用 `\t` 分隔 |
| `[对话历史]` | `用户: xxx\n小爱: xxx\n` | 见下方 | 无历史时为空(直接接 `[当前query]` |
| `[系统状态]` | 固定 `{}` | `{}` | |
**正确示例**
```json
{"system": "你是小爱同学,中文智能语音助手。", "instruction": "请参考用户的[当前query]、[对话历史]、[知识注入]、[系统状态]识别出[当前query]的[function]结果,[function]是python的code形式。\n[知识注入]\n{\n\"location\": \"北京(市)位于中国(国家)\",\n\"rag\": \"高德地图是APP\\t百度地图是APP\"\n}\n[系统状态]\n{}\n[对话历史]\n用户: 帮我导航去最近的加油站\n小爱: 好的,已经找到附近3个加油站\n[当前query]\n用户: 加完油再去机场接人,然后一起去三里屯吃饭\n[function]\n", "output": "ComplexTask(tag=\"地图导航\")"}
```
**常见错误(GPT 高发,必须在 sanity check 中拦截)**
| 错误 | 正确 |
|---|---|
| `\location\: \xxx\` | `"location": "xxx"` |
| `\tag\: \图片问答\` | `"rag": "图片问答是视频"` |
| key 用反斜杠转义 | key 用标准双引号 |
| `"tag"` 作为知识注入 key | `"rag"` 是唯一正确的 key |
| system = "你是一名Python程序员..." | system 固定为 "你是小爱同学,中文智能语音助手。" |
> **为什么不用 Python程序员 prompt 格式?** `all_train.jsonl` 中 35379 条全部是"小爱同学"格式,不存在 Python程序员格式。混入不同 system prompt 会让模型困惑,必须统一。
#### 4.2 调用 GPT-5.4 生成同义训练样本
生成字段与归档 CSV(data_train 格式)的列名保持一致,避免归档时再做字段映射。
```python
import requests, json, hashlib
from pathlib import Path
API_URL = "http://model.mify.ai.srv/v1/chat/completions"
API_HEADERS = {
"Authorization": "Bearer sk-jVgQHGHPsxFYF2CbKD8UoGi56340FgC6XGlgSkGZQzYvsb08",
"X-Model-Provider-Id": "azure_openai",
"X-Model-Request-Id": "augment-gen",
"Content-Type": "application/json",
}
MODEL = "gpt-5.4"
SYSTEM = """你是小爱同学中控理解训练数据生成助手。给定一条错误 case,生成若干条与其**意图相同**、**ground_truth 相同**、**表述多样**的训练样本。
要求:
1. ground_truth 严格照抄原 case,不得改写(包括 Agent(tag=...) / ComplexTask(...) / QA() / Chat() 等格式)。
2. current_query 表述要多样化:口语/书面、长/短、有/无填充词、方言化等,但意图不能漂移。
3. prev_session 保留原 case 的设备/场景线索,结构和原 case 一致(JSON 数组,每项含 query / tts / timestamp)。无历史时用 []。允许在合理范围内改写历史文本,但轮数、设备、场景不变。
4. context 原样继承原 caselocation / rag),不要改写、不要新造地点或 RAG 实体。
5. 禁止生成与已知测试集 current_query 精确或近似重复的样本。
6. 严格输出 JSONL,一行一条样本,不要解释、不要 markdown。每行格式(字段名与 data_train CSV 对齐):
{"current_query": "...", "prev_session": [...], "context": {"location": "...", "rag": "..."}, "ground_truth": "...", "sub_cate": "..."}
其中 sub_cate 仅用于内部路由/去重,归档时会被丢弃,不写入 CSV。"""
USER_TEMPLATE = """错误 case
- sub_cate: {sub_cate}
- prev_session: {prev_session}
- context: {context}
- current_query: {current_query}
- 正确 ground_truth: {ground_truth}
- 模型错误输出: {predict}
生成 {n} 条意图/ground_truth 一致、表述多样的训练样本。"""
REQUIRED_KEYS = {"current_query", "prev_session", "context", "ground_truth"}
def gen_augment(badcase: dict, n: int = 8) -> list[dict]:
payload = {
"model": MODEL,
"messages": [
{"role": "system", "content": SYSTEM},
{"role": "user", "content": USER_TEMPLATE.format(n=n, **badcase)},
],
}
resp = requests.post(API_URL, headers=API_HEADERS, json=payload, timeout=120)
resp.raise_for_status()
text = resp.json()["choices"][0]["message"]["content"].strip()
out = []
for line in text.splitlines():
line = line.strip().lstrip("```json").rstrip("```").strip()
if not line:
continue
try:
sample = json.loads(line)
if REQUIRED_KEYS.issubset(sample.keys()):
out.append(sample)
except json.JSONDecodeError:
continue
return out
```
**调用策略**
- 每条 badcase 生成 6–10 条(依据子集缺口决定,缺口越大给越多)。
- **每类(每个 tag/意图)增强数据上限 50 条**。要克制,不要一次加太多。如果某类需要更多数据,应在下一轮迭代中逐步追加。
- 按子集做速率限制;失败重试 3 次,指数退避。
- **原始输出落到 `results/augment_raw/augment_<runDic>_raw.jsonl`**(跟本轮报告/log 一起归档,不是训练目录)。下一步 4.3 过完 sanity check 才写入训练目录。
#### 4.3 Sanity check & 后处理(silent bug 高发区)
**生成完立即跑,否则 silent bug 会直接进训练:**
- **instruction 格式校验(最高优先级)**:逐条检查 `instruction` 字段是否符合 4.1.1 模板:
1. 必须包含 `"location"``"rag"` 两个 key(标准双引号,不是反斜杠)
2. 不得出现 `\location\``\tag\``\rag\` 等反斜杠转义的 key
3. `system` 必须为 `"你是小爱同学,中文智能语音助手。"`(不是 Python程序员)
4. 必须包含 `[知识注入]``[系统状态]``[对话历史]``[当前query]``[function]`
5. 不符合的**整条丢弃**,不要尝试修复(GPT 格式错误通常是系统性的,修一个字段其他字段也不可信)
- **ground_truth 格式校验**`Agent(tag=...)` / `ComplexTask(...)` / `QA()` / `Chat()` 是否和测试集一致?GPT-5.4 偶尔会把 tag 改写或加空格,必须用 `zk_reward_fn` 里的 parser 过一遍,parse 失败的扔掉。
- **context 完整性**`context` 必须是 dict 且含 `location` / `rag` 两键(可为空字符串但不能缺);`prev_session` 必须是 list。结构不符的丢弃。
- **泄漏检测**:生成 `current_query` 与所有测试集(需求集合 + 大盘 + specific)做**精确匹配 + 近似匹配**MinHash or embedding cos > 0.9)。命中则整条丢弃。
- **Label 自洽**:生成的 `(current_query, prev_session, context) → ground_truth` 必须和原 badcase 的 ground_truth 语义一致。**全量**跑一次 GPT-5.4 自检("下面这条 current_query 的正确 ground_truth 是什么?"),和声明 ground_truth 不一致的丢弃。
- **Prompt 模板一致性**:并入训练前,把 `(prev_session, context, current_query)` 渲染成最终 SFT prompt,和 eval prompt 逐字段比对(`complex=true/false` 前缀、system prompt、function schema),不一致就对齐模板——常常能白捡几个点。
- **去重**:与 `ai-planning/data/train_set/zk_intent/` 下所有已存在的 `*_train.jsonl`(历史增强 + 原始种子训练文件)去重(`current_query` 精确 + 近似)。
#### 4.3.1 标签规则约束(R28 经验沉淀)
4.3 只检查格式 / 泄漏 / label 自洽,**不检查标签语义规则**。R23-R28 多次出现"格式正确但 label 违反业务规则"的样本进入训练(augment_17749 30 条「从X到Y」反向 / augment_17752 23 条短延续 / augment_17754 30 条 模糊属性 → CT 等)。
**生成完每条样本必须过以下规则检查,不通过整条丢弃**
##### 已确立的规则(按优先级)
| ID | 规则 | 例 |
|---|---|---|
| R1 | 单 POI + 任意数量形容词修饰 → Agent | "帮我找最近的咖啡店" → Agent |
| R2 | 单 POI + 真实可执行动作(吃/喝/买/卖/看/玩/接/送)→ ComplexTask | "去海底捞吃饭" → CT |
| R3 | 用户状态描述句("我XX有问题/累/赶时间/电量低/感冒了"+ POI → ComplexTask | "我轮胎有点问题,找个补胎店" → CT |
| R4 | 多动作复合("X然后Y" / "先X再Y" / "加途经点 X 然后到 Y")→ ComplexTask | "先去加油再去机场" → CT |
| R5 | 上下文延续短 query(依赖前轮)→ 跟随前轮分类 | 前轮 CT,"第一个" → 跟前轮 |
| R6 | 路线偏好(走高速/走国道/走主路)+ POI → ComplexTask | "导航回家走高速" → CT |
| R7 | 含问句词(什么/哪个/哪里/在哪)+ 导航请求 → ComplexTask | "厦门有什么好吃的给我导航" → CT |
> 规则随每轮新发现持续追加。新规则确立必须经人审核(4.3.2 自动归纳,必要时触发 H-i-T-L #2/#6),归档到 `results/label_rules.md`。
##### 自动检查代码模板
```python
def check_label_rules(query: str, label: str, history: list = None) -> tuple[bool, str]:
"""返回 (是否通过, 不通过原因)"""
is_ct = 'ComplexTask' in label
# R3: 状态描述句必须 CT
state_pat = re.compile(
r'(我.{0,5}(轮胎|车|手机).{0,5}(扎|坏|爆|漏|有问题|出问题))|'
r'(我.{0,5}(感冒|累|饿|渴|赶时间|快迟到|怕迟到|生病))|'
r'(我.{0,5}电量.{0,5}(只剩|不多|快没|没了))|'
r'(车.{0,5}(没油|快没油|没电|爆胎|坏了))'
)
if state_pat.search(query) and not is_ct:
return False, 'R3 violation: 状态描述句应 CT'
# R2: 真实可执行动作 + POI 应 CT
action_pat = re.compile(r'(去|要|来).{0,5}(吃|喝|买|卖|看一看|看一下|打卡|拍照|玩|逛|接|送)')
if action_pat.search(query) and not is_ct:
return False, 'R2 violation: 真实动作应 CT'
# R6: 路线偏好 + POI 应 CT
route_pat = re.compile(r'(走高速|走国道|走主路|走快速路|走小路|走高架)')
has_poi = bool(re.search(r'[一-龥]{2,8}(路|街|站|广场|中心|公园|医院|餐厅|店|馆|区|城|湖|大厦)', query))
if route_pat.search(query) and has_poi and not is_ct:
return False, 'R6 violation: 路线偏好+POI 应 CT'
# R7: 问句 + 导航 应 CT
if re.search(r'(什么|哪个|哪里|在哪).*(导航|去|到)', query) and not is_ct:
return False, 'R7 violation: 问句导航应 CT'
# R4: 多动作复合 应 CT
multi_pat = re.compile(r'(然后|接着|顺便|再帮|完了再|然后再|顺路|再去|.{0,5}先.{0,8}再)')
if multi_pat.search(query) and not is_ct:
return False, 'R4 violation: 多动作应 CT'
return True, ''
```
##### 跨子集冲突检查(R28 经验沉淀)
每条仿写 query 在所有测试集 csv 里搜:
- 若同 query 或高度相似(cos > 0.9)出现在 ≥2 个子集且 gold 不同 → **整条丢弃**(避免训练矛盾信号)
##### 过通用度检查(防 R26 教训)
仿写 query 满足以下全部 → 整条丢弃:
- 长度 ≤ 6 字
- 不含具体地名/技能词
- 与训练集已有 query 重复
> R26 augment_17752 加了 23 条"第一个 / 选第3 / 继续往前导航"等过通用短词,污染了可聊可控子集 -2.35pp。这种短 query 必须依赖对话历史才有意义,单独作训练样本会污染跨子集判定。
##### 仿写量级硬阈值
| 类型 | 单轮上限 |
|---|---|
| 旧标签批改 | 50 条(>50 触发 H-i-T-L #3 |
| 新仿写 | 100 条(按 sub_cate 分配,每类 ≤ 50 |
| 单轮总变更 | 150 条 |
超阈值必须人介入定边界,不允许自动放行。
#### 4.3.2 从评测集自动归纳新规则
测试集 CSV 已含 gold`code_label`/`complex`),规则从错例自动归纳,不需人定。
**触发**:每轮 Step 1 后,对未被已知规则覆盖的错例做归纳。
**接受条件**(两个同时满足):
- `c1 ≥ 0.80`:同 pattern 错例内 gold 主流占比(防错例巧合)
- `c2 ≥ 0.85`:候选正则扫全测试集后主流 gold 占比(防过拟合错例)
- 两者推出的主流 gold 必须一致,且不与现有规则重叠 >70%
**归纳流程**:同 pattern 错例 ≥3 条 → 提取公共正则 → 算 c1/c2 → 过阈值则自动追加到 `results/label_rules.md`,下一轮 4.3.1 立即生效,不需人审。
**归纳失败 → HiTL**
- 信号 #2:同结构 query 在 ≥2 子集 gold 矛盾
- 信号 #6:同 pattern 错例 <3 条但多轮持续出现
#### 4.4 写入训练目录
Sanity check 全过后,把清洗结果写入训练目录。文件名**不能含 `_valid`**,否则 `prepare_and_train_sft.py` 会把它归为验证集:
```
ai-planning/data/train_set/zk_intent/augment_<runDic>.jsonl
```
落盘后即可被 Step 5 的 `prepare_and_train_sft.py` 自动扫描到并合并进训练。
**归档(迭代结束后做,不是训练流程的一部分)**:把所有 `augment_<runDic>.jsonl` 转成 data_train 格式的 CSV,列如下:
| 列名 | 类型 | 说明 |
|---|---|---|
| `current_query` | str | 当前轮用户 query |
| `prev_session` | strJSON array | 多轮历史,`[{"query","tts","timestamp"}]`,无历史写 `[]` |
| `context` | strJSON object | `{"location","rag"}`,字段保留但允许空字符串 |
| `ground_truth` | str | 正确 label,如 `Agent(tag="...")` / `ComplexTask(tag="...")` |
JSONL 行里的 `sub_cate` 字段仅用于内部路由/去重,归档时丢弃不写入 CSV。因为 4.2 的输出 schema 已经和 CSV 列名对齐,归档就是把每个 `augment_<runDic>.jsonl` 行序列化成 CSV 单元格(`prev_session` / `context` 两列用 `json.dumps` 回写成字符串),没有字段重命名。
#### 4.5 label-master 标签复核(落盘后、SFT 前,**强制**)
| 文件 | 层 1(格式) | 层 2(语义,`Skill(skill="label-master")` |
|---|---|---|
| H1 `modified_samples.jsonl` | ✅ `validate_label_output.py --field output_after` | ❌ 已在 §4.0.1 A.5 完成 |
| H2 `augment_<runDic>.jsonl` | ✅ `validate_label_output.py --field output` | ✅ 逐条调 label-master1:1 全量) |
**层 2 禁止用正则/规则脚本替代**,必须调 `Skill(skill="label-master")``repeatable: true`)。每次只传一条 `(query, label)`,禁止批量。
**覆盖率硬规则**`label_master_review.jsonl` H2 行数 = `augment_<runDic>.jsonl` 行数(§5.0 `wc -l` 断言会拦)。不通过 > 0 则必须修正后重新 review 直到全 pass。verdict 文件不存在 → Step 5 拒绝启动。
### 5. SFT 训练
🚨 **Step 4 → Step 5 硬连接**`augment=complete` 后同一轮**紧接着**:① 写 `sft=running` ② §4.5 复核 ③ 复核全过 → `submit_sft.sh` + 挂 watcher。禁止 turn 结束、禁止写简报、禁止等回调。评测在 SFT `_SUCCESS` 落盘后的下一轮单独用 `submit_cml_eval.sh` 起,不串进 SFT bg。
使用 `prepare_and_train_sft.py` 完成数据组装和训练。
#### 5.0 环境准备
**准入检查(少一项不许进)**
```bash
# 1. label-master 复核报告必须存在且全通过
REVIEW="$AUTORESEARCH_CHAT_ROOT/results/data_clean_${RUNDIC}/label_master_review.jsonl"
[ -f "$REVIEW" ] || { echo "label-master 复核未完成,回 §4.5"; exit 1; }
NOT_PASS=$(grep -c '"verdict":"不通过"' "$REVIEW" || echo 0)
if [ "$NOT_PASS" -gt 0 ]; then
echo "label-master 不通过 $NOT_PASS 条(要求 = 0),必须修正后重新 review 直到全 pass"
exit 1
fi
# 1b. coverage 断言:augment 行数必须被 review 完整覆盖(杜绝抽样外推)
# review.jsonl 同时包含 §4.0.1 Step A.5 的 mislabel candidate review + §4.5 的 augment 仿写 review
# 所以行数下界 = augment.jsonl 行数(mislabel 部分多出来的那批不影响下界)
AUG="$AUTORESEARCH_CHAT_ROOT/ai-planning/data/train_set/zk_intent/augment_${RUNDIC}.jsonl"
if [ -f "$AUG" ]; then
AUG_N=$(wc -l < "$AUG")
REV_N=$(wc -l < "$REVIEW")
if [ "$REV_N" -lt "$AUG_N" ]; then
echo "label-master review 行数 $REV_N < augment 条数 $AUG_N — §4.5 必须逐条覆盖,禁止抽样外推(如 5/5 pass 推 100 OK),回 §4.5 把 augment.jsonl 的每一条都 append 一行 review entry"
exit 1
fi
fi
# 2. zk_trainer 仓库已 cloneURL 写死,不要换命名空间,clone 失败先看 ssh key
cd "$AUTORESEARCH_CHAT_ROOT"
[ -d zk_trainer ] || git clone git@git.n.xiaomi.com:wangsenhao/zk_trainer.git
```
**训练配置**
| 参数 | 值 |
|---|---|
| 基模 | `/mnt/wangsenhao/verl_zk/Qwen3-4B-Instruct-2507` |
| model_type | `qwen3` |
| accelerate 配置 | `/mnt/xiaoai-zk-model-train-tj5/common/accelerate_config_0.yaml` |
| FSDP | stage 3, bf16 |
| epochs | 3 |
| learning_rate | 1e-5 |
| max_seq_length | 1024 |
| lr_scheduler | cosine, warmup_ratio=0.1 |
| optimizer | Adam (β1=0.9, β2=0.95, ε=1e-9) |
| dataset_type | `zk_sft` |
| save_strategy | no(训练完直接保存最终 checkpoint |
| VOLUME_PREFIX | `/mnt/xiaoai-zk-model-train-tj5`labelref.json / tagref.json 所在,脚本自动设置) |
##### ⚠️ 强制规则:每轮 SFT 必须从 **basemodel** 开始(R17777 经验沉淀)
**每一轮 SFT 训练的 `--model_path` 都必须是 basemodel `/mnt/wangsenhao/verl_zk/Qwen3-4B-Instruct-2507`,绝不可以从上一轮的 `sft_output/` 或任何 checkpoint 继续训练。**
**为什么**
- 每轮训练集是迭代修改的(新增/删除/改标),从上一轮 ckpt 继续训练会**叠加**历史训练数据的残留偏差,导致:
- 无法归因本轮干预效果(本轮 +X pp 是数据改动还是历史 ckpt 的残留?)
- 错标/副作用样本一旦学进去就会被放大,即使后续修掉也可能拔不回来
- R17777 副作用修复方向如果从 R17775 ckpt 继续,会同时叠加 R17774 和 R17775 的信号,实验不可控
- 从 basemodel 开始能保证:**本轮 eval 指标完整反映本轮训练数据的效果**,干预 → 效果映射一一对应
**自动保障**
- `scripts/sft_train_job.yaml.tpl``imageCommand``--model_path` 固定为 `/mnt/wangsenhao/verl_zk/Qwen3-4B-Instruct-2507`**禁止改为 `sft_output` 或其它 ckpt 路径**
- `prepare_and_train_sft.py train` 命令行参数 `--model_path` 必须检查为 basemodel 路径
**反模式**
-`--model_path $AUTORESEARCH_CHAT_ROOT/sft_output`(继续训练)
-`--model_path $AUTORESEARCH_CHAT_ROOT/sft_output_r17776`(从历史轮 ckpt
- ❌ 任何形式的 "增量 SFT"(除非显式声明是为了验证"从 X ckpt 继续是否更好"的对照实验,且单次性,log 要明确标记)
#### 5.0.1 训练产物备份
```bash
[ -d sft_output ] && mv sft_output sft_output_r${PREV_RUNDIC}
rm -f "$AUTORESEARCH_CHAT_ROOT/sft_output/_SUCCESS" # 防遗留误报
```
**SFT watcher 流程**`submit_sft.sh` 提交后):
1. 写 program-state `sft=running` + watch 声明(target = `sft_output/_SUCCESS`
2.`bash(run_in_background=true, wait_for_completion=true)``_SUCCESS` 落盘
3. Watcher fire / bg 返回后**下一轮**:写 `sft=complete` → 写 `cml=running` → 调 `submit_cml_eval.sh` 起评测 + 挂评测 watcher
⚠️ 训练和评测**不串接**——`submit_sft.sh` 只提 SFT,评测在 `_SUCCESS` 后新一轮单独起。
#### 5.1 数据组装
```bash
python prepare_and_train_sft.py prepare --output_dir ./sft_data
# 如果 data_train/ 下的 CSV 有改动,先从 CSV 重新生成 JSONL
python prepare_and_train_sft.py prepare --regen_from_csv --output_dir ./sft_data
```
脚本行为:
1. 扫描 `ai-planning/data/train_set/*/` 下所有 `.jsonl` 文件
2. 文件名含 `_valid` → 验证集,其余 → 训练集(包括 `all_train.jsonl``augment_*.jsonl` 等)
3. 合并输出到 zk_trainer 所需的目录结构:
```
<output_dir>/
train/zk_sft_new_structure/merged_train.jsonl # 合并后的训练数据
validation/zk_sft_new_structure/part-0.jsonl # 合并后的验证数据
```
> 验证集文件名不能含 `valid`/`test`/`eval`HF datasets 会推断错误的 split),脚本自动重命名为 `part-0.jsonl`。
⚠️ **每轮 SFT 前必须在 log 中展示训练数据组成**
`prepare_and_train_sft.py` 会 glob 目录下**所有** `augment_*.jsonl`。agent 在每轮 SFT 前必须在 program-state log 中列出最终参与训练的文件及行数:
```jsonl
{"log":{"ts":"...","iter":"R3","text":"SFT 数据组成: all_train.jsonl(35379) + augment_17829.jsonl(210) = 35589 条 | 排除: augment_17828.jsonl(150) rename .pre_R3R2 pattern 导致导航 bvt 劣化)"}}
```
必须展示:(1) 参与训练的所有文件及行数 (2) 被排除的旧轮文件及排除原因(如果有)。
#### 5.2 启动训练(cml custom_train
```bash
cd "$AUTORESEARCH_CHAT_ROOT"
eval "$(./scripts/resolve_run_ids.sh)"
./scripts/submit_sft.sh "$SFT_RUNDIC" # 提交 SFT,立刻退出
# _SUCCESS 落盘后下一轮:
./scripts/submit_cml_eval.sh "$EVAL_RUNDIC" # 起评测
```
**`submit_sft.sh`**:渲染 `sft_train_job.yaml.tpl`(替换 `{RUNDIC}`)→ `cml custom_train submit` → 打印 JobID → 退出。
**`submit_cml_eval.sh`**:从 `config.yaml` 读 workflow_id/version → `cml workflow run` 起评测,runDic=EVAL_RUNDIC。
监控:`cml custom_train describe/logs/kill <JOB_ID>`
### 6. 记录结果
每轮写入 `results/iteration_log.jsonl` 一行,schema
```json
{
"iteration": 12,
"runDic": 45,
"timestamp": "2026-04-23T...",
"hypothesis": "加分流 reward 项能降分流错误率到 15% 以下",
"intervention": {
"type": "reward",
"summary": "zk_reward_fn: complex 误判额外 -0.3"
},
"prediction": {"req_set_car": 93.0, "triage_err_rate": 0.15},
"results": {
"req_set_car": 92.1, "dapan_car": 96.30,
"specific_test": 95.10, "triage_err_rate": 0.18
},
"verdict": "partial",
"root_cause_findings": [
{"pattern": "Chat 误激活 Agent", "class": "data", "cases": 14},
{"pattern": "导航→旅游", "class": "reward", "cases": 8}
],
"error_delta": {"persistent": 32, "new": 9, "fixed": 17},
"next_hypothesis": "Chat 过召主要是训练集 Chat 样本太少;下轮补 500 条"
}
```
同时追加 `error_registry.jsonl`:每个 B case 一条 `{case_hash, runDic, sub_cate, query, label}`
### 7. 回到 Step 0(评测刚训出来的 SFT checkpoint
## 决策规则
| 情况 | 处理 |
|------|------|
| 本轮 verdict = miss 且 new error 多 | 回退干预,缩小修改范围 |
| 连续 2 轮 miss 同一假设 | 假设错了,换思路(见 2.4)|
| SFT loss > 2.0 | 数据质量,不是训练问题 |
| 持久错误占 B 的 >70% | 单靠当前数据解不了,改 prompt 或回头审视 reward / 格式 |
| 新引入错误占 B 的 >30% | 上轮干预有副作用,**必须**先回退再推进 |
| 大盘降 > 0.3% | 回退上版,分析原因 |
| specific 降 > 1% | 定向补该类数据 |
| 需求集合 < 95% | 按 2.3 做深度分析,按 2.4 归因再动手 |
| OOM | 减 batch size |
| 连续 3 轮无改善 | 强制换策略:换数据比例 / 换超参 |
| **跨子集净退步:目标子集 +X / 其他子集合计 -Y, 净 < 0.3pp** | **回退或缩小干预范围**(R27 经验:单看目标子集涨容易自欺)|
| **连续 3 轮目标子集净提升 ≤ 0.5pp** | **进入瓶颈期**:输出 SFT 天花板报告,触发 H-i-T-L #6 |
| **跨子集 gold 矛盾率 > 5%** | **结构性天花板**:触发 H-i-T-L #2,停 SFT 转 RL 或返工标注 |
| **要批改旧标签 > 50 条** | **触发 H-i-T-L #3**,全量人审 |
| **Gold drift ≥ 10 条** | **触发 H-i-T-L #1**,暂停迭代确认是否同步训练集 |
| 训练前未备份 sft_output | **强制 mv sft_output sft_output_r{prev}**,不允许覆盖 |
## Human-in-the-Loop 时机(R23-R28 经验沉淀)
迭代默认全自动跑(评测→分析→数据→训练→评测...)。**只在以下信号出现时停下问人**,其他情况自主推进。
### 触发人介入的 7 类信号(迭代级)
| # | 触发条件 | 应对动作 | 经验来源 |
|---|---|---|---|
| 1 | **Gold drift 检测到 ≥10 条** | 暂停迭代,让人确认是否同步更新训练集 | R23 100 条翻转 |
| 2 | **跨子集同形 query gold 矛盾率 > 5%** | 输出"结构性天花板"报告,让人选:硬推目标子集 / 接受 / 转 RL | 复杂导航 vs 可聊可控 矛盾 |
| 3 | **要批改旧标签 > 50 条** | 暂停,导出全量到飞书 sheet 让人逐条确认 | R28 改 230 条引发副作用 |
| 4 | **跨子集净退步**(目标 +X / 其他合计 -Y, 净 < 0.3pp) | 暂停,让人决策回退 / 接受 / 换策略 | R27 可聊可控 -1.34pp |
| 5 | **连续 3 轮目标子集净提升 ≤ 0.5pp** | 输出 SFT 天花板报告,让人选继续 SFT / 转 RL / 接受 / 换基模 | R25-R28 复杂导航 +0.7~3pp 递减 |
| 6 | **测试集错例里 gold 可疑(自相矛盾的同 pattern)** | 列出可疑 gold 让人确认 / 反馈标注团队 | R28「找+模糊属性」双向标注 |
| 7 | **达标但有副作用**(需求集合达 95% 但 specific 降 ≥ 0.5pp)| 暂停部署,让人决策:部署 / 微调修复 | 预防性 |
### 训练集调整专项 H-i-T-L 信号
任何对 `ai-planning/data/train_set/zk_intent/*.jsonl` 的修改/删除/新增动作,**先在程序内做下面的 10 项判定**,命中任一项就暂停问人;都不命中才能自动执行。
| # | 触发条件 | 应对动作 | 经验来源 |
|---|---|---|---|
| T1 | **单轮批改旧标签 > 50 条** | 全量导出到飞书 sheet 逐条审,逐条标 1/0 | R28 改 230 条引发跨子集副作用 |
| T2 | **单轮新仿写 > 100 条 OR 单类 (同 sub_cate / 同 pattern) > 50 条** | 全量让人审风险,量级大要拆批 | R23 一次 670 条仿写过载,规则错全反 |
| T3 | **删除训练样本 > 30 条** | 列删除清单 + 删除原因,人确认后才执行(删比改更不可逆) | R26 删 23 P0 通用短词产生副作用 |
| T4 | **修改 `all_train.jsonl` 主集 > 20 条** | 核心训练集动一行都贵;列改动给人确认 | 主集影响所有 sub_cate,副作用范围最大 |
| T5 | **跨子集冲突 query:同结构 query 在 ≥3 条训练样本里 gold 不一致** | 让人定边界规则(如「沿途搜 X」是 Agent 还是 CT),不许两边都加 | R26 augment_17752 同 pattern 矛盾仿写 |
| T6 | **仿写新 pattern 在测试集找不到锚定 case** | 让人确认这个 pattern 是否真存在,避免凭空生成对模型有害的样本 | R28 augment_17754 B 类 30 条无测试集锚定 |
| T7 | **仿写 query 含"过通用"特征**(长度 ≤6 字 + 不含具体地名/技能词) | 整批暂停,让人确认是否丢弃;这种短 query 必依赖上下文,单独训会污染跨子集判定 | R26 "第一个/选第3/继续往前" 23 条 → 可聊可控 -2.35pp |
| T8 | **本轮修改的 pattern 在前 3 轮曾导致回退(查 iteration_log.jsonl** | 让人决定是同方向加大力度还是换 pattern;不许重蹈覆辙 | 防止反复在同 pattern 上来回拉锯 |
| T9 | **备份失败 / `.bak` 文件已存在但内容与当前文件相同** | 立即停,让人手动检查;**绝不允许覆盖原文件** | SOP 强制要求 (4.0.1 Step C) |
| T10 | **单轮跨文件批改 > 5 个 `*.jsonl` 文件** | 列影响文件清单 + 每个文件改动数;让人确认范围合理 | R28 改 11 个文件 262 条,影响面失控 |
> **执行顺序**T9 (备份) > T1-T4 (量级) > T5-T8 (语义/历史) > T10 (范围)。任一命中即停。
> 注:旧版本曾把"新发现 gold 规则不确定"作为人介入信号,已废弃。**测试集 CSV 已含 gold (`code_label`/`complex`),规则可从评测集错例自动归纳**,不需要人定(详见 4.3.2)。
>
> **审核交付物**:触发任一信号时,输出到 `/tmp/train_audit_<runDic>.csv` 或写入飞书 sheet(视量决定),含 `(file, line, query, history, old_label, new_label, reason)` 7 列。
### 不触发人介入(全自动)
**A. 评测/分析层**:评测正常完成 / 大盘 ±在阈值内 / 目标子集 +0.5pp 以上 / 上轮假设 hit/partial / new error ≤ 30% / persistent < 70%
**B. 数据生成 / 修改层**:改旧标签 ≤ 50 条 / 仿写 ≤ 100 条 / 单轮总变更 ≤ 150 条 / sanity check 失败的样本自动丢 / 训练集近邻匹配高(自动归因 reward/格式)/ gold drift < 10 条 / 跨子集矛盾率 ≤ 5%
**C. 训练 / 评测调度层**prepare 数据 / 启训练 / 训练成功后自动启评测 / 备份 sft_output / 写 iteration_log
**D. 假设 / 归因层**:根因明确归类按 2.4 路由 / 假设 hit/partial 同方向继续 / 假设 miss 但 new error 占比小重新形成假设
**E. 单轮目标达成层**:所有阈值满足且无副作用 → **自动部署 + 结束循环**;阶段性目标达成 → 写 milestone log,继续推下一目标
### 介入时的交付物(让人快速决策)
每次触发介入,必须**主动**输出(不等人问):
1. **触发原因**:哪一条信号 + 具体数字
2. **现状量化**:目标子集 +X / 其他子集 -Y / 大盘 ±Z
3. **全量 case 直接铺进对话**(凡是"该不该改 / 该不该删 / 该不该新增 N 条"型决策都适用,**包括但不限于** H-i-T-L #1/#2/#3/#6 与 T1/T2/T3/T5):
- 必须把"经过 label-master 认证的全部候选"按 pattern 分组、每条一行(紧凑表)贴到**同一条** ask 消息体里,不是只给 CSV 路径或飞书链接,**也不要分段连发**——一段全铺,让用户一次滚完
- **不允许抽样、不允许"前 N 条样例"、不允许"代表 case"**——抽样让用户看不到边界外的长尾,决策无意义
- 每条至少含:`query / old_label / 推荐标签 / 理由(≤40字)`H1(改标)类必含 `file:line`H2(仿写)类必含 `pattern_id`
- **高置信 + 低置信(潜在影响半径 / 类似 case)都得铺,缺一不可**:当 ask 里出现「确认要改 X 条 + 还有 Y 条类似的 / 潜在影响 Y 条 / 同 pattern 还有 Y 条疑似」这种二段叙述时,**Y 条也必须全量铺进同一条对话消息**(同样按 pattern 分组、紧凑表),并对每条标 `confidence=high / low`。理由:用户的决策本身就是「只改 X」vs「扩到 X+Y」vs「再收窄 pattern」,看不到 Y 就只能瞎选。**只展示高置信 X 条、把 Y 条藏在数字背后**视为违反 skill。
4. **2-4 个选项**(用 AskUserQuestion 工具):每个选项含预期收益 + 风险
5. **推荐选项**:基于经验给出推荐(标 "(推荐)"
6. **本地产物**:全量错例 csv / 飞书 sheet 链接(作为对话铺陈的备份和事后回查渠道,**不替代**第 3 项)
### 介入后的恢复
人决策后立即恢复全自动循环,**不再二次确认**当前轮的细节。除非人显式说"再问我"。
### 反模式(不应触发介入)
- ❌ 训练前问"要训吗?" — 应直接训
- ❌ 评测前问"要评吗?" — 应直接评
- ❌ 改 ≤ 50 条标签前逐条问 — 应批量改后报告
- ❌ 仿写 ≤ 100 条前预审 — 应生成后做 sanity 自动过滤
- ❌ 单轮无副作用且达标 → 部署前问 — 直接部署
## 结果文件
- `results/iteration_log.jsonl`:每轮完整记录(含假设/干预/判定)
- `results/error_registry.jsonl`:跨轮错误追踪
- `results/workflow<runDic>.md`:每轮回归分析报告
- `results/augment_raw/augment_<runDic>_raw.jsonl`:本轮 GPT-5.4 原始生成产物(归档用,不入训练)
- `ai-planning/data/train_set/zk_intent/augment_<runDic>.jsonl`:本轮清洗后的增量增强数据,Step 5 的 `prepare_and_train_sft.py` 自动扫描合并进 SFT;迭代完毕后归档导出为 data_train 格式的 CSV
- `sft_output/` / `rl_output/` (训练前 mv 上一轮为 `sft_output_r<prev_runDic>/`,至少保留 5 轮)
- `results/data_clean_<runDic>/`:旧数据清洗存档(deleted_samples.jsonl / modified_samples.jsonl / data_clean_<runDic>.log
- `results/gold_drift/drift_<runDic>.json`:gold drift 检测结果(每轮强制写入,方便回溯)
- `results/label_rules.md`:已确立的标签规则集(R1~R7+,新规则 H-i-T-L 确认后追加)
## NEVER STOP
一旦开始,不要停下问人类。每轮必须:
1. **先评测当前模型**
2. 分析结果、归因
3. 写假设
4. 做干预
5. 判定 hit/miss
6. 更新 error_registry
7. 写下轮假设
如果没思路了:
- 回读最近 3 轮 `iteration_log`,看有没有忽略的模式
- 跑 SFT-only eval,判断是 SFT 就不行还是 RL 破坏了
- 重新读 `zk_reward_fn` 和训练数据采样,找 silent bug
循环直到人类打断,period。