model-iteration: runDic 单一可信源 + label-master 逐条调用
- 新增 scripts/resolve_run_ids.sh:从 workflow5/ 解析出 SFT_RUNDIC / EVAL_RUNDIC,禁止 agent 心算 +1 - submit_sft_via_cml.sh 拆成 <SFT_RUNDIC> <EVAL_RUNDIC> [PREV_RUNDIC] 三参,yaml 用 SFT_RUNDIC,cml workflow run 用 EVAL_RUNDIC - program.md §746 / §5.2 / §1.2 同步约束,并加 R2 落盘多 +1 的踩坑案例 - §4.0.1 Step A.5 / §4.5 label-master 复核改逐条调用(≤8 路并发),禁止批量塞多条 query Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -16,12 +16,11 @@
|
||||
用户发 **"开始,<需求集合名>"**(如 `"开始,icl_test"`)→ **立即用当前模型跑一轮评测**,不再中途确认参数,直到报告完成:
|
||||
|
||||
1. **Setup 检查**:cml 环境 + SSH key(见下)、从 `config.yaml` 读默认参数、初始化 `error_registry.jsonl`
|
||||
2. **准备评测参数**:确定 `model_path_new`(当前模型)/ `model_path_old`(基线),自动分配 runDic(扫描 `run_history_dir` 最大值 +1);首次评测两者设为同一基准模型
|
||||
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%)
|
||||
5. **问题分析 & 报告**(Step 2):根因归类(reward/data/格式/hparam)、跨轮 diff(persistent/new/regressed)、需求集合深度分析(训练数据关联 + reward 对齐),写入 `results/workflow<runDic>.md`
|
||||
6. **决策分支**:全部达标 → 部署并结束;否则 → 形成假设(Step 3)→ 按归因干预:数据增强(Step 4)/ 改 reward / 改格式 / 调超参 → SFT 训练(Step 5)→ 记录到 `iteration_log.jsonl`(Step 6)→ 回到第 3 步评测新 checkpoint
|
||||
7. **全程不打断用户**,循环直到达标或人类打断
|
||||
4. **分层结果分析 + 问题分析 & 报告**(Step 1):按优先级逐层检查 需求集合(≥95%)→ 大盘车载(降幅≤0.3%)→ specific test(降幅≤1%);做根因归类(reward/data/格式/hparam)、跨轮 diff(persistent/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. **全程不打断用户**,循环直到达标或人类打断
|
||||
|
||||
> **关键**:"开始"的第一个动作永远是**评测当前模型**,而不是直接训练。先看清楚当前模型在各个集合上的表现和错误分布,再基于证据形成本轮假设 → 做干预。**不要没看评测就开始训练。**
|
||||
|
||||
@@ -64,7 +63,7 @@ ssh -T git@git.n.xiaomi.com
|
||||
"$AUTORESEARCH_CHAT_ROOT/ai-planning"
|
||||
```
|
||||
|
||||
之后所有训练数据读写都走 `$AUTORESEARCH_CHAT_ROOT/ai-planning/...`(包括 augment_<runDic>.jsonl 写入、近邻检索、训练集修改)。`prepare_and_train_sft.py` 用 `Path(__file__)/../ai-planning` 解析数据目录,因为脚本被推到了 `$AUTORESEARCH_CHAT_ROOT/scripts/` 旁边,自然落在 chat 副本上。
|
||||
之后所有训练数据读写都走 `$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`。
|
||||
|
||||
@@ -185,11 +184,11 @@ for k in prev:
|
||||
|
||||
**决策**:
|
||||
- 全部达标 → 部署(`python modules/cloudml_deploy.py <rl_checkpoint>`),记录,循环结束
|
||||
- 不达标 → 进入 Step 2,**分析顺序:需求集合 → 车载大盘 → specific test**
|
||||
- 不达标 → 在 Step 1 内继续做问题分析(见下文),**分析顺序:需求集合 → 车载大盘 → specific test**
|
||||
|
||||
> **首次评测差异**:由于新旧模型相同,"降幅"和"vs 基线"无意义(均为 0)。此轮只记录各集合的**绝对准确率**作为 baseline,不做达标/不达标判定,不触发部署。直接进入 Step 2 分析基准模型的错误分布,为后续迭代建立对照基准。
|
||||
> **首次评测差异**:由于新旧模型相同,"降幅"和"vs 基线"无意义(均为 0)。此轮只记录各集合的**绝对准确率**作为 baseline,不做达标/不达标判定,不触发部署。直接在 Step 1 内分析基准模型的错误分布,为后续迭代建立对照基准。
|
||||
|
||||
### 2. 问题分析 & 报告
|
||||
### 1.A 问题分析 & 报告(在 Step 1 同一 step 内继续做,不再单独开 step)
|
||||
|
||||
目标不是"列错误",是回答三个问题:
|
||||
- **根因属于哪一类?**(reward / data / 格式)
|
||||
@@ -207,7 +206,7 @@ for k in prev:
|
||||
| 旧对新错率 | B 数 / 总数 × 100% |
|
||||
| 相对基线变化 | 新模型准确率 − 基线准确率(百分点) |
|
||||
| 准确率 | 该子集 GSB 统一准确率(`cleaned_predict` vs `label`) |
|
||||
| 分流错误 | `origin_predict_base` 含 `ComplexTask` 且 `origin_predict_dev` 以 `complex=false` 前缀开头。complex 判断本身错了,**即使 cleaned tag 相同也计为错** |
|
||||
| 分流错误 | base 模型和 dev 模型对该 case 的「是否复杂」二值判断不一致(一边判复杂、一边判不复杂),cleaned tag 是否相同不影响判定。具体怎么从两列原始输出里抽出「是否复杂」这个判断,由 agent 分析前先 head 当前评测产出反推,**不要照抄历史固定字符串** |
|
||||
| 语义错误 | `cleaned_predict_base != cleaned_predict_dev`,意图/tag 本身判错 |
|
||||
| 持久错误 | 该 case 在上一轮也错(查 `error_registry.jsonl`) |
|
||||
| 新引入错误 | 该 case 在上一轮对,本轮错 —— **最危险的信号,说明上轮干预有副作用** |
|
||||
@@ -216,8 +215,8 @@ for k in prev:
|
||||
|
||||
| 字段 | 含义 |
|
||||
| --- | --- |
|
||||
| `origin_predict_base` | 旧模型原始输出,可能是 `ComplexTask(tag="...")` |
|
||||
| `origin_predict_dev` | 新模型原始输出,始终以 `complex=true/false` 前缀开头 |
|
||||
| `origin_predict_base` | 旧模型原始输出。具体格式(是否含 `complex=` 前缀、tag 包装、自定义 class 名等)以当前评测产出为准,**分析前 head 一下实物** |
|
||||
| `origin_predict_dev` | 新模型原始输出。同上,格式以当前产出为准 |
|
||||
| `cleaned_predict_*` | 清洗后输出,用于 GSB 对比 |
|
||||
| `label` / `code_label_base` | ground truth;`label` 为空时回退解析 `code_label_base` |
|
||||
|
||||
@@ -251,7 +250,104 @@ for k in prev:
|
||||
2. **失败 case 与训练数据的关联**:对每个失败 case 在 `ai-planning/data/train_set/zk_intent/` 下所有 `.jsonl`(包括历史增强 `augment_*.jsonl` 和原始种子训练文件,排除 `*_valid.jsonl`)里做近似检索(前 3 近邻),三档判定:
|
||||
- **无近邻**(最高相似度 < 阈值)→ 分布外,补数据
|
||||
- **有近邻 + 近邻 label 与本 case gold 全部一致** → 训练数据正确,SFT 学不动 → 加 epoch / 改 reward
|
||||
- **有近邻 + 任一近邻 label 与 gold 矛盾** ⚠️ → **训练集打错标**,必须**逐条列出该 mislabeled 训练样本**:`{train_file:line, hash, 当前 label, 应改 label, 与失败 case 相似度, 违反的标签规则}`,进入下一轮的数据修订清单
|
||||
- **有近邻 + 任一近邻 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 信号、不是结论信号。
|
||||
|
||||
⚠️ **算法选型**:错例集 O(几十)、训练集 O(几万) — 不要用复杂度高、要把训练集全量物化成稀疏矩阵或 embedding 的算法(如 `sklearn.TfidfVectorizer.fit_transform` 全量 + cosine — 实测 35k 行字符 n-gram 600s 仍 timeout)。
|
||||
|
||||
⚠️ **>1 万行训练集做近邻必须先粗筛后精排,不许直接 O(M·N) 全量比对**——37 错例 × 30k 训练 = 1.1M 次精排,朴素写法实测 ~3 min(v32 R0 case:错例侧逐条扫训练集 + 每例剪枝仍 182s,且后续每加一步 mislabel 定位都要再跑一遍)。两段式:
|
||||
|
||||
1. **粗筛(倒排索引,全表只扫一次)**:训练集一次性建 `token → set(line_idx)` 倒排表,key 用 query 的 2-gram 字符 token(30k 行 query 通常 3k–5k 唯一 token,建表 O(N) 秒级)。
|
||||
2. **错例侧逐条取候选**:每条错例提它自己的 2-gram tokens,union 训练集对应 posting list → 通常每条得到 50–300 候选行(不是 30k)。
|
||||
3. **精排(小集合上做精细打分)**:仅对粗筛候选跑 Jaccard / char-overlap / Levenshtein 取 top-3。
|
||||
|
||||
预算对照(37 × 30k):朴素 O(M·N)=1.1M 次精排(~3 min);倒排粗筛后 ~37×200=7.4k 次精排(~10s)。错例侧的"轻量剪枝"只是常数优化,**不能替代粗筛**——粗筛把候选基数砍到 1/100 才是数量级提速的来源。
|
||||
|
||||
⚠️ **训练集 + 倒排索引必须 pickle 缓存到 /tmp,不要每个脚本重建**:dist-analysis 一轮要跑 kn_search / judge_check / mislabel_list / cross_subset 等多个脚本,每个都要读 30k 行 all_train.jsonl + 重建倒排索引——v32 R0 实测 5 次重读累计 ~50s 全是浪费。pod `/tmp` 是本地 SSD,`pickle.dump` 之后所有后续脚本 unpickle <1s。模板:
|
||||
|
||||
```python
|
||||
# 每个会读 all_train 的脚本头部统一这一段,cache key 用文件 mtime 自动失效
|
||||
import os, json, pickle
|
||||
from pathlib import Path
|
||||
ALL_TRAIN = Path(os.environ['AUTORESEARCH_CHAT_ROOT']) / 'ai-planning/data/train_set/zk_intent/all_train.jsonl'
|
||||
CACHE = Path(f'/tmp/all_train_{int(ALL_TRAIN.stat().st_mtime)}.pkl') # mtime 当版本号
|
||||
IDX_CACHE = Path(f'/tmp/all_train_idx_{int(ALL_TRAIN.stat().st_mtime)}.pkl')
|
||||
|
||||
if CACHE.exists():
|
||||
rows = pickle.loads(CACHE.read_bytes()) # <1s
|
||||
else:
|
||||
rows = [json.loads(l) for l in ALL_TRAIN.open('r', encoding='utf-8')] # 5–15s NFS
|
||||
CACHE.write_bytes(pickle.dumps(rows))
|
||||
|
||||
# 倒排索引同样缓存(first script 建好就一直复用)
|
||||
if IDX_CACHE.exists():
|
||||
inv = pickle.loads(IDX_CACHE.read_bytes())
|
||||
else:
|
||||
inv = {}
|
||||
for i, r in enumerate(rows):
|
||||
q = r.get('query', '')
|
||||
for tok in {q[k:k+2] for k in range(len(q)-1)}: # 2-gram
|
||||
inv.setdefault(tok, set()).add(i)
|
||||
IDX_CACHE.write_bytes(pickle.dumps(inv))
|
||||
```
|
||||
|
||||
忌:每个脚本头都重写一遍 `for line in open(jsonl): json.loads(line)`;用 `/tmp/<runDic>.pkl` 而忽略 mtime(训练集中途被改不失效);把 cache 写到 NFS(白做了)。
|
||||
|
||||
⚠️ **stdout 是下一轮 LLM 的 input,分析脚本只 print 决策聚合,明细落 `/tmp/<step>_<runDic>_detail.json`**:每个分析脚本的 stdout 会原样喂回 LLM 当下一轮上下文,每 KB 都要 prefill。v32 R0 实测 kn_search 一次 stdout 18KB(37 错例 × 3 邻居 × 6 列)、judge_check 4.7KB、mislabel 11.3KB,整个 dist-analysis 累计 ~50KB 全是重复模式行。kn_search 模板(其他步骤同构):
|
||||
|
||||
```python
|
||||
# 不要 print 整张 candidate × neighbors 表;只 print 三档分布 + 每档 top-3 范例
|
||||
import json, collections, os
|
||||
buckets = collections.Counter()
|
||||
exemplars = collections.defaultdict(list)
|
||||
detail = [] # 全量明细,落盘不 print
|
||||
for case in failed_cases:
|
||||
neigh = topk(case, k=3) # 粗筛 → 精排已就位
|
||||
verdict = classify(case, neigh) # 'no_neighbor' / 'consistent' / 'mislabel'
|
||||
buckets[verdict] += 1
|
||||
if len(exemplars[verdict]) < 3:
|
||||
exemplars[verdict].append({
|
||||
'query': case['query'][:50],
|
||||
'gold': case['gold'],
|
||||
'top_neigh': [{'q': n['query'][:50], 'label': n['label']} for n in neigh],
|
||||
})
|
||||
detail.append({'case': case['idx'], 'verdict': verdict, 'neigh': neigh})
|
||||
|
||||
detail_path = f'/tmp/kn_search_{os.environ["RUN_DIC"]}_detail.json'
|
||||
json.dump(detail, open(detail_path, 'w'), ensure_ascii=False)
|
||||
|
||||
# 只 print 决策需要的聚合(目标 < 2KB)
|
||||
print(f'[kn_search] total={len(failed_cases)} buckets={dict(buckets)} detail={detail_path}')
|
||||
for v, exs in exemplars.items():
|
||||
print(f'-- top-3 of {v} --')
|
||||
for e in exs:
|
||||
print(json.dumps(e, ensure_ascii=False))
|
||||
```
|
||||
|
||||
忌:把每条错例的 k 个邻居全 print;`DataFrame.to_string()` 直接喂 stdout(自带 padding,一张表 2KB+);用 `cat > /tmp/x.py <<EOF ... EOF` 写脚本(runtime 会把每行 PS2 提示符 `>` 回显进 stdout,一次累计 1–2KB),改 `python3 - <<'PY' ... PY` 或 `python3 -c "$(base64 -d <<<'...')"` 注入。
|
||||
|
||||
⚠️ **大文件 I/O**:>10MB 量级的远端 CSV/JSONL(如本步要读的 `specific_comparison.csv`,常见 90+MB),**先用 bash `awk/grep/head` 在远端切目标子集再处理**。直接 `pd.read_csv` 整体加载、或把 DataFrame 当返回值穿过工具调用 → 会撞 runtime 输出/返回大小限制(实测 99MB 直接读报"太大了")。
|
||||
|
||||
⚠️ **执行通道**:所有 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 根因归类
|
||||
|
||||
@@ -499,10 +595,14 @@ def get_label(row: dict) -> str:
|
||||
return clb.strip("[]'").replace('\\n', '\n ')
|
||||
|
||||
def classify(row: dict) -> tuple[bool, bool]:
|
||||
"""返回 (is_triage_err, is_semantic_err)"""
|
||||
opb, opd = row['origin_predict_base'], row['origin_predict_dev']
|
||||
"""返回 (is_triage_err, is_semantic_err)
|
||||
|
||||
is_triage_err 的判定无法用通用代码——agent 先 head 当前
|
||||
origin_predict_base / origin_predict_dev 实物,根据真实格式现写 parser
|
||||
抽出 base / dev 的「是否复杂」二值判断再比对。
|
||||
"""
|
||||
cpb, cpd = row['cleaned_predict_base'], row['cleaned_predict_dev']
|
||||
is_triage = 'ComplexTask' in opb and opd.startswith('complex=false')
|
||||
is_triage = ... # TODO: 见 docstring,按当前评测产出格式现写
|
||||
is_semantic = cpb != cpd
|
||||
return is_triage, is_semantic
|
||||
|
||||
@@ -544,7 +644,7 @@ for r in b_rows:
|
||||
stats[sc][tag] += 1
|
||||
```
|
||||
|
||||
### 3. 本轮假设(基于 Step 1–2 的证据)
|
||||
### 3. 本轮假设(基于 Step 1 的证据)
|
||||
|
||||
**只有证据充分时才往下走。** 在开始训练前,写入 `results/iteration_log.jsonl` 的 `hypothesis` 字段:
|
||||
- **假设**:本轮要验证什么?(例:"分流错误主因是 reward 对 complex 误判的惩罚太弱")
|
||||
@@ -557,14 +657,14 @@ for r in b_rows:
|
||||
|
||||
### 4. 数据生成(GPT-5.4 从 badcase 增强)
|
||||
|
||||
**只在 Step 2.4 归因为 `data` 时做。** 其他归因直接跳过 Step 4,按下表路由:
|
||||
**只在 §2.4 归因为 `data` 时做。** 其他归因直接跳过 Step 4,按下表路由:
|
||||
|
||||
| 归因 | 本步动作 | 跳到哪步 |
|
||||
|---|---|---|
|
||||
| `data` | 做 Step 4 数据增强(在已有数据基础上增删改) | Step 5(SFT 重训) |
|
||||
| `格式`(prompt / label schema 不对齐) | 改 prompt 模板或 label 渲染逻辑 | Step 5(SFT 重训,格式变了必须重新 SFT) |
|
||||
| `hparam` | 改 `config.yaml`(LR / batch / epoch 等) | Step 5(SFT 重训) |
|
||||
| 假设本身站不住(Step 2 证据不支持 Step 3 的假设) | 不做任何训练 | **回 Step 3**,重写假设 |
|
||||
| 假设本身站不住(Step 1 证据不支持 Step 3 的假设) | 不做任何训练 | **回 Step 3**,重写假设 |
|
||||
|
||||
换句话说:Step 4 是"data 归因专属"的干预入口,其他归因各有各的干预点,往下找对应的 Step 就行。
|
||||
|
||||
@@ -643,14 +743,44 @@ with open(out_csv, 'w', encoding='utf-8-sig') as fp:
|
||||
|
||||
候选阶段(246 条等量级)的 CSV 必须写到 `output/relabel_candidates_<runDic>.csv` 让分析卡片可读;确认修改阶段(用户审核后落盘的 135 条)写入 `results/data_clean_<runDic>/modified_samples.jsonl` 让数据增强卡片可读。**不要混落**——把候选 CSV 写到 `/tmp/` 或 `data_clean_/` 都会让 UI 看不到。
|
||||
|
||||
**Step B:量级判定(决定是否走人审)**
|
||||
> **runDic 命名语义(backend 已对齐,改 backend 时勿 revert)**:augment 类产物(`data_clean_<N>/`、`augment_raw/augment_<N>_raw.jsonl`、`zk_intent/augment_<N>.jsonl`、`label_master_review.jsonl`)的 `<N>` 永远是**「源轮次的 runDic」**,也就是 `R{n-1}.runDic`——agent 在 R{n} 跑 augment 时处理的是 R{n-1} 评测落地的错例,文件命名沿用 R{n-1} 的 workflow id。Backend 的 step-detail 端点 (`server.py` 内 `_step_artifact_paths('augment', ...)` 调用方) 在 `step=='augment'` 时也是查 `R{n-1}` 的 runDic,跟落盘命名一一对应。其他 step(cml / dist-analysis / hypothesis / gold-drift)的产物按本轮自己的 runDic 命名,没有偏移。**不要把 augment 产物改名成 R{n} 的 runDic**——会同时打破历史归档和 backend 解析。
|
||||
>
|
||||
> ⚠️ **runDic 不许手算**:augment 落盘、SFT yaml、归档目录、cml workflow run 用的 runDic **必须**从 `scripts/resolve_run_ids.sh` 取,不准 agent 自己 `ls workflow5 | tail -1` 然后心算 +1。规则简单到没必要靠模型:
|
||||
>
|
||||
> ```bash
|
||||
> eval "$(./scripts/resolve_run_ids.sh)"
|
||||
> # 现在 shell 里有:
|
||||
> # $SFT_RUNDIC = R{n-1}.runDic (augment 落盘 / yaml / 归档全用这个,不 +1)
|
||||
> # $EVAL_RUNDIC = R{n-1}.runDic + 1 (仅 cml workflow run 时使用,唯一一次 +1)
|
||||
> ```
|
||||
>
|
||||
> 已踩坑:R2 augment 把 `data_clean_<N>/` 写成了 `EVAL_RUNDIC`(多 +1 一次),跳过了 R1.runDic 整段命名空间,前端 augment 卡片直接读不到。**写 augment / SFT / 归档脚本时一律用 `$SFT_RUNDIC`,不要写 `${RUNDIC}` 也不要写 `${EVAL_RUNDIC}`**。
|
||||
|
||||
| 候选量 | 处理 |
|
||||
**Step A.5:label-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 + 自动改 |
|
||||
| 51 ~ 200 条 | **必须**全量导出到飞书 sheet 让人逐条审 1/0(不抽样) |
|
||||
| ≤ 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
|
||||
@@ -725,7 +855,7 @@ ai-planning/data/train_set/zk_intent/
|
||||
|
||||
#### 4.1 输入
|
||||
|
||||
- **Badcase 来源**:Step 2 里 `纯模型GSB == 'B'` 的 case。按两个维度排优先级:
|
||||
- **Badcase 来源**:Step 1 里 `纯模型GSB == 'B'` 的 case。按两个维度排优先级:
|
||||
1. **测试集维度**:本次需求集合 > 其他 specific test / 大盘(需求集合是当前瓶颈,优先补)
|
||||
2. **跨轮维度**:`new` / `regressed` > `persistent`(新引入/回退的先处理,持久错误兜底)
|
||||
|
||||
@@ -969,7 +1099,7 @@ def check_label_rules(query: str, label: str, history: list = None) -> tuple[boo
|
||||
|
||||
##### 触发时机
|
||||
|
||||
每轮 Step 2 错例分析后,对**所有未被 R1~R{N} 已知规则覆盖的错例**做自动归纳。
|
||||
每轮 Step 1 错例分析后,对**所有未被 R1~R{N} 已知规则覆盖的错例**做自动归纳。
|
||||
|
||||
##### Confidence 的定义(两个独立指标,必须同时满足)
|
||||
|
||||
@@ -1103,14 +1233,14 @@ JSONL 行里的 `sub_cate` 字段仅用于内部路由/去重,归档时丢弃
|
||||
|
||||
#### 4.5 label-master 标签复核(落盘后、SFT 前,**强制**)
|
||||
|
||||
任何写入训练目录的修改/新增样本都必须经 [label-master skill](../../label-master/SKILL.md) 复核**两层**:格式 + 语义。**没过这关不许进 Step 5。**
|
||||
任何写入训练目录的修改/新增样本都必须经 [label-master skill](../../label-master/SKILL.md) 复核:格式 + 语义。**没过这关不许进 Step 5。**
|
||||
|
||||
**复核范围**(两份必须全过):
|
||||
**复核范围**(两份必须过格式层;语义层只对 H2):
|
||||
|
||||
| 文件 | 来源 | 复核什么 |
|
||||
|---|---|---|
|
||||
| `$AUTORESEARCH_CHAT_ROOT/results/data_clean_<runDic>/modified_samples.jsonl` | H1 改标(complex 翻转 / tag 改写等) | 改后的 `output` 字段 |
|
||||
| `$AUTORESEARCH_CHAT_ROOT/ai-planning/data/train_set/zk_intent/augment_<runDic>.jsonl` | H2 仿写(新增训练样本) | `output` 字段 |
|
||||
| 文件 | 来源 | 层 1(格式) | 层 2(语义,Skill 调 label-master) |
|
||||
|---|---|---|---|
|
||||
| `$AUTORESEARCH_CHAT_ROOT/results/data_clean_<runDic>/modified_samples.jsonl` | H1 改标(complex 翻转 / tag 改写等) | ✅ 必跑 | ❌ 已在 §4.0.1 Step A.5 完成,**不再重跑**(label-master 不会推翻自己的推荐,重跑只是浪费 token) |
|
||||
| `$AUTORESEARCH_CHAT_ROOT/ai-planning/data/train_set/zk_intent/augment_<runDic>.jsonl` | H2 仿写(新增训练样本,未经过 §4.0.1) | ✅ 必跑 | ✅ 必跑 |
|
||||
|
||||
**层 1:格式校验(确定性,自动跑)**
|
||||
|
||||
@@ -1128,16 +1258,17 @@ python skills/label-master/scripts/validate_label_output.py \
|
||||
|
||||
任一行 fail(tag 不存在 / Agent 包装错 / function 引用错)→ **必须修,不许直接跳过**。修完原地重跑直到全过。
|
||||
|
||||
**层 2:语义复核(Skill 调用 label-master Agent)**
|
||||
**层 2:语义复核(Skill 调用 label-master Agent,仅 H2)**
|
||||
|
||||
格式过了不代表标对了。每条修改/新增样本要用 label-master 做边界判断:
|
||||
H1 在 §4.0.1 Step A.5 已经过 label-master 推荐覆盖,这里**不再重跑**。H2 仿写是 §4.2 GPT 新增的样本,没经过 Step A.5,必须在此补一次语义判定:
|
||||
|
||||
1. 抽出 `(query, output_after)` 或 `(query, output)` 对,按 `sub_cate` 分组(每组 ≤30 条作为一批)
|
||||
2. 每批用 `Skill(skill="label-master", args=...)` 调用,args 里给:
|
||||
- 全部 `(query, 当前 label)` 对
|
||||
- 让 label-master 按 §决策流程 / §候选召回索引 / §高频混淆边界 判定每条
|
||||
- 输出格式:每条 → `{verdict: 通过 | 不通过, 推荐标签, 排除理由, 易混淆边界}`
|
||||
3. 收集 verdict,写入 `$AUTORESEARCH_CHAT_ROOT/results/data_clean_<runDic>/label_master_review.jsonl`,每行一条 verdict
|
||||
1. 从 `augment_<runDic>.jsonl` 抽出 `(query, output)` 对
|
||||
2. **逐条**调 `Skill(skill="label-master", args=...)`——每次只传**一条** `(query, 当前 label)`:
|
||||
- 让 label-master 按 §决策流程 / §候选召回索引 / §高频混淆边界 判定该条
|
||||
- 输出格式:`{verdict: 通过 | 不通过, 推荐标签, 排除理由, 易混淆边界}`
|
||||
- ⚠️ **禁止把多条 query 塞进同一次 Skill 调用**——批量调用会让 label-master 在多条之间相互锚定,分错率显著升高(已踩坑)
|
||||
- 可在同一轮 assistant response 里并发多次 Skill 调用(建议 ≤ 8 路并发)以提升吞吐
|
||||
3. 收集 verdict,写入 `$AUTORESEARCH_CHAT_ROOT/results/data_clean_<runDic>/label_master_review.jsonl`,每行一条 verdict(只含 H2 条目)
|
||||
|
||||
**verdict 处置规则(自动)**:
|
||||
|
||||
@@ -1345,19 +1476,25 @@ R23-R28 期间用本地 `nohup python3 prepare_and_train_sft.py train ...` 启
|
||||
|
||||
##### 一键提交脚本
|
||||
|
||||
⚠️ **runDic 不许手敲**:先 `eval` 一下 `resolve_run_ids.sh` 拿到这一轮的 `$SFT_RUNDIC` / `$EVAL_RUNDIC`,再原样传给 `submit_sft_via_cml.sh`。R2 踩过的坑就是手算 runDic 时把 SFT yaml 的命名也跟着 +1,导致 `data_clean_<EVAL_RUNDIC>/` 跳过了 R1 命名空间。
|
||||
|
||||
```bash
|
||||
cd "$AUTORESEARCH_CHAT_ROOT"
|
||||
./scripts/submit_sft_via_cml.sh <RUNDIC> [PREV_RUNDIC]
|
||||
eval "$(./scripts/resolve_run_ids.sh)" # 解析出 $SFT_RUNDIC / $EVAL_RUNDIC
|
||||
./scripts/submit_sft_via_cml.sh "$SFT_RUNDIC" "$EVAL_RUNDIC"
|
||||
|
||||
# 例:R29 训练(上一轮 R28)
|
||||
./scripts/submit_sft_via_cml.sh 17756 17755
|
||||
# 等价示例(仅供阅读,**实际调用一律走 eval 上面那行**):
|
||||
# R29 上一轮已落盘的 workflow 是 17755,因此 SFT_RUNDIC=17755, EVAL_RUNDIC=17756
|
||||
# ./scripts/submit_sft_via_cml.sh 17755 17756
|
||||
```
|
||||
|
||||
`submit_sft_via_cml.sh` 接受三个位置参数:`<SFT_RUNDIC> <EVAL_RUNDIC> [PREV_RUNDIC]`。`PREV_RUNDIC` 默认 `SFT_RUNDIC-1`,仅用作上一轮 `sft_output_r<PREV_RUNDIC>` 改名。脚本内部断言 `EVAL_RUNDIC > SFT_RUNDIC`,否则直接报错退出。
|
||||
|
||||
脚本做的事:
|
||||
1. 渲染 `scripts/sft_train_job.yaml.tpl` 为本轮 yaml(替换 RUNDIC / PREV_RUNDIC)
|
||||
1. 渲染 `scripts/sft_train_job.yaml.tpl` 为本轮 yaml(替换 `{RUNDIC}` → `$SFT_RUNDIC`、`{PREV_RUNDIC}` → `$PREV_RUNDIC`)
|
||||
2. `cml custom_train submit --filename <yaml>` 提交任务,拿 `JobID`
|
||||
3. 后台 watcher 进程:每 60s `cml custom_train describe` 轮询任务状态
|
||||
4. 任务 succeed → 验证 `sft_output/_SUCCESS` 存在 → 自动起 cml workflow run 评测
|
||||
4. 任务 succeed → 验证 `sft_output/_SUCCESS` 存在 → 自动起 cml workflow run,**评测的 `runDic=$EVAL_RUNDIC`**(这是整条流水线唯一一次 +1)
|
||||
5. 任务 failed/killed → 写日志报警
|
||||
|
||||
##### yaml 模板要点(`scripts/sft_train_job.yaml.tpl`)
|
||||
@@ -1523,9 +1660,14 @@ tail -f /tmp/r<RUNDIC>_logs/cml_watcher.log
|
||||
|
||||
1. **触发原因**:哪一条信号 + 具体数字
|
||||
2. **现状量化**:目标子集 +X / 其他子集 -Y / 大盘 ±Z
|
||||
3. **2-4 个选项**(用 AskUserQuestion 工具):每个选项含预期收益 + 风险
|
||||
4. **推荐选项**:基于经验给出推荐(标 "(推荐)")
|
||||
5. **本地产物**:全量错例 csv / 飞书 sheet 链接
|
||||
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 项)
|
||||
|
||||
### 介入后的恢复
|
||||
|
||||
|
||||
Reference in New Issue
Block a user