fix: subprocess-based remote sync, pipeline status, skill updates

- Replace thread-based remote state sync with subprocess (sync_remote_state.py)
  to prevent thread pool exhaustion hanging the API
- Fix pipeline showing 'waiting' when a real step is running alongside a gate
- Fix watcher scanner path for linux accounts mode
- Disable label-master semantic review (Step A.5 + §4.5) per user request
- Correct field names: use is_model_correct_dev for accuracy, origin_predict_dev
  for model output (not cleaned_predict)
- Add prohibition against putting test set data in relabel_candidates

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hupenglong1
2026-05-28 14:35:35 +08:00
parent 6364fb437c
commit 98b3e586de
7 changed files with 492 additions and 330 deletions
+22 -88
View File
@@ -15,7 +15,7 @@
用户发 **"开始,<需求集合名>"**(如 `"开始,icl_test"`)→ **立即用当前模型跑一轮评测**,不再中途确认参数,直到报告完成:
1. **Setup 检查**cml 环境 + SSH key(见下)、`config.yaml` 读默认参数、初始化 `error_registry.jsonl`
1. **Setup 检查**cml 环境(缺失则自动安装,见下)+ SSH key + **git clone ai-planning 仓库**(见「Chat 工作区 bootstrap」)+ `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`
@@ -27,6 +27,8 @@
> **首次评测**:首次评测只关注基准模型指标,`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,贯穿整个迭代(评测分析、深度分析、数据增强优先级)。**未指定需求集合时不启动迭代,直接提示用户补充。**
>
> ⛔ **禁止因"本地找不到评测集文件"而停下来问用户路径**。评测集在 git 仓库里,找不到说明还没 clone——立即执行 bootstrap 的 `git clone -b autoresearch-v1` 拉取仓库,然后在 `$AUTORESEARCH_CHAT_ROOT/ai-planning/data/specific_test_set/` 下定位。同理,`cml` 命令不存在时按下方「cml 环境检查」自动安装,**不要停下来问用户**。只有 AK/SK 缺失(凭据类)才允许暂停询问。
信号可携带覆盖参数,如 **"开始,icl_test"** / **"开始,icl_testv28"** / **"开始,icl_testmodel_path_new=/xxx/"**。其中第一个非 key=value、非版本号的参数即为需求集合名。
@@ -52,7 +54,7 @@ ssh -T git@git.n.xiaomi.com
### 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。
每个 chat session 一份独立工作区 `$AUTORESEARCH_CHAT_ROOT`(后端 jupyter 启动时自动注入这个 env)。该路径位于共享 NFS:`/mnt/<email_prefix>/autoresearch-zk-users/<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**(之前依赖全局共享,已废弃):
@@ -209,9 +211,9 @@ for k in prev:
| --- | --- |
| 旧对新错率 | B 数 / 总数 × 100% |
| 相对基线变化 | 新模型准确率 − 基线准确率(百分点) |
| 准确率 | 该子集 GSB 统一准确率(`cleaned_predict` vs `label` |
| 准确率 | 该子集准确率(`is_model_correct_dev` 为 True 的比例 |
| 分流错误 | base 模型和 dev 模型对该 case 的「是否复杂」二值判断不一致(一边判复杂、一边判不复杂),cleaned tag 是否相同不影响判定。具体怎么从两列原始输出里抽出「是否复杂」这个判断,由 agent 分析前先 head 当前评测产出反推,**不要照抄历史固定字符串** |
| 语义错误 | `cleaned_predict_base != cleaned_predict_dev`,意图/tag 本身判错 |
| 语义错误 | `origin_predict_base != origin_predict_dev`,意图/tag 本身判错 |
| 持久错误 | 该 case 在上一轮也错(查 `error_registry.jsonl` |
| 新引入错误 | 该 case 在上一轮对,本轮错 —— **最危险的信号,说明上轮干预有副作用** |
@@ -221,6 +223,8 @@ for k in prev:
| --- | --- |
| `origin_predict_base` | 旧模型(baseline)原始输出。具体格式(是否含 `complex=` 前缀、tag 包装、自定义 class 名等)以当前评测产出为准,**分析前 head 一下实物** |
| `origin_predict_dev` | 新模型(迭代模型)原始输出。同上,格式以当前产出为准 |
| `is_model_correct_dev` | 新模型预测是否正确(True/False |
| `is_model_correct_base` | 旧模型预测是否正确(True/False |
| `complex_dev` | 迭代模型的 complex 标签 |
| `complex_base` | baseline 模型的 complex 标签 |
@@ -466,7 +470,7 @@ def cross_iter_tag(case_h: str, last_runDic: int, registry: dict) -> str:
#### 4.0 原始训练数据清洗(增强前必做)
> 🚨 **强制执行:每轮 augment 必须先跑 §4.0→§4.0.1,不得跳过直接做 H2 仿写。**
> 即使 hypothesis 归因为"训练集缺数据",也必须先逐 pattern 检索训练集确认是否存在 mislabel。只有当 §4.0.1 Step A 扫描结果 + Step A.5 label-master 预审后残留候选 = 0 时,才能判定"无需 H1 修改"并跳到 H2。**"这轮只需要加数据"不是跳过 H1 检查的合法理由**——上一轮加的新数据可能引入了新的标签冲突,必须每轮重新检索确认。
> 即使 hypothesis 归因为"训练集缺数据",也必须先逐 pattern 检索训练集确认是否存在 mislabel。只有当 §4.0.1 Step A 扫描结果候选 = 0 时,才能判定"无需 H1 修改"并跳到 H2。**"这轮只需要加数据"不是跳过 H1 检查的合法理由**——上一轮加的新数据可能引入了新的标签冲突,必须每轮重新检索确认。
>
> 缺少 H1 检查的 augment 视为不完整:`modified_samples.jsonl` 可以为空(代表确认无需修改),但 `data_clean_<runDic>.log` 必须记录"H1 扫描完成,0 候选"的结论,否则 §5.0 准入检查拒绝启动 SFT。
@@ -534,40 +538,24 @@ with open(out_csv, 'w', encoding='utf-8-sig') as fp:
for c in candidates: w.writerow(list(c)+['',''])
```
> ⛔ **`relabel_candidates` CSV 中的 `file` 列必须指向 `train_set/` 下的训练文件,禁止把测试集(`specific_test_set/`)的行写入候选**。测试集是只读评测参照,修改目标永远是训练集。流程是:测试集错例 → 归类 pattern → 检索训练集同类 query → 训练集中命中的条目才是候选。如果候选 CSV 里出现 `specific_test_set/` 路径,说明流程搞反了,必须重做。
**两阶段产物路径**
- 阶段一(候选):`$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.5label-master 预审**~~ — **已禁用,跳过此步**。候选直接进入 Step B 量级判定。
Step A 产出的候选交给 label-master 做语义判定,**用 label-master 的"推荐标签"覆盖候选原始推测的 new_label**,目的是在拿去人审之前先消化掉 label-master 自己就能定的那部分,缩小残留候选量。
**Step B:量级判定(基于 Step A 产出的候选量)**
具体步骤:
1. 抽出每条候选的 `(file, line, query, old_label, suspected_new_label)`
2. **用子 agent 调用 label-master**——**禁止在主 agent 上下文里直接调 Skill(skill="label-master")**,避免大量输出污染主 agent 上下文。做法:
- 主 agent 把待审列表写入 jsonl 文件(如 `scratchpad/lm_input.jsonl`,每行 `{query, old_label}`
- 起子 agent`delegate_agent(prompt="读取 <workspace>/scratchpad/lm_input.jsonl,对每条逐一调用 Skill(skill='label-master', args='query: ... label: ...'),把结果逐行 append 到 <workspace>/scratchpad/lm_output.jsonl(每行 {query, verdict, 推荐标签, 理由})。注意:每次 Skill 调用只传一条 query,禁止批量。", allow_shell=true)`
- 子 agent 完成后主 agent 读取 `lm_output.jsonl` 汇总结果
- ⚠️ **禁止把多条 query 塞进同一次 Skill 调用**——批量调用会让 label-master 在多条之间相互锚定(已踩坑)
- ⚠️ 子 agent prompt 里必须给出**完整的 workspace 绝对路径**`$AUTORESEARCH_CHAT_ROOT/scratchpad/...`),因为子 agent 没有父的环境变量
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_理由` 列 |
| ≤ 50 条 | 程序化 sanity check + 按 suspected_new_label 自动改 |
| 51 ~ 200 条 | **必须**全量导出到飞书 sheet 让人逐条审 1/0(不抽样);导出时带 `query / old_label / suspected_new_label` 列 |
| > 200 条 | **强制 H-i-T-L 介入**(触发条件 #3),让人定更精细 pattern 收窄 |
注:阈值仍按原来 autoresearch 的人审规则;变化只在于残留候选已经过 label-master 一遍语义筛减,避免把 label-master 自己就能定的条目也塞进飞书让人重审。
**Step C:备份(强制,覆盖前必做)**
```bash
@@ -892,47 +880,15 @@ ai-planning/data/train_set/zk_intent/augment_<runDic>.jsonl
JSONL 行里的 `sub_cate` 字段仅用于内部路由/去重,归档时丢弃不写入 CSV。因为 4.2 的输出 schema 已经和 CSV 列名对齐,归档就是把每个 `augment_<runDic>.jsonl` 行序列化成 CSV 单元格(`prev_session` / `context` 两列用 `json.dumps` 回写成字符串),没有字段重命名。
#### 4.5 label-master 标签复核(落盘后、SFT 前,**强制**
#### ~~4.5 label-master 标签复核~~ — **已禁用,跳过此步**
| 文件 | 层 1格式 | 层 2(语义,`Skill(skill="label-master")` |
|---|---|---|
| H1 `modified_samples.jsonl` | ✅ `validate_label_output.py --field output_after` | ✅ 逐条调 label-master1:1 全量)— §4.0.1 A.5 是粗筛,**不能替代落盘前的 layer2** |
| H2 `augment_<runDic>.jsonl` | ✅ `validate_label_output.py --field output` | ✅ 逐条调 label-master1:1 全量) |
**层 2 禁止用正则/规则脚本替代**,必须通过**子 agent** 调 `Skill(skill="label-master")``repeatable: true`)。每次只传一条 `(query, label)`,禁止批量。**禁止主 agent 直接调用 label-master**——上下文污染会导致主 agent 后续推理质量下降。子 agent 通过文件交换结果(主 agent 写 input jsonl → 子 agent 读取并逐条调 Skill → 写 output jsonl → 主 agent 读取汇总),见 §4.0.1 Step A.5 的详细做法。
⚠️ **层 2 必须校验完整 label(含 tag),不能只校验 complex 维度**
子 agent 调用 label-master 时的 args 格式:`query: <完整query> label: <完整output>`,例如:
```
Skill(skill="label-master", args="query: 顺路再去个加油站 label: Agent(tag=\"地图导航\")")
```
label-master 会按其决策流程判断该 query 的 **tag 归属**是否正确(如应该是"地图导航"还是"充电加油"),同时判断 **complex**Agent vs ComplexTask)和**输出格式**。
**禁止只传 complex=true/false 让 label-master 做二分类**——这不是 label-master 的设计用途。必须传完整的 `Agent(tag="xxx")``ComplexTask(tag="xxx")`,让 label-master 走完整的"候选召回→标签卡片→边界判定→推荐标签"流程。
label-master 的 verdict 必须同时覆盖:
1. **tag 是否正确**label-master 推荐的 tag 与当前 label 中的 tag 是否一致,不一致则 verdict=不通过
2. **complex 是否正确**Agent vs ComplexTask 是否正确
3. **输出格式是否合规**`Agent(tag="xxx")` / `ComplexTask(tag="xxx")` 格式是否规范
`label_master_review.jsonl` 每行必须包含字段:`query``label`(完整 output,如 `Agent(tag="地图导航")`)、`verdict`(通过/不通过)、`recommended_label`label-master 推荐的完整 output)、`reason`(判断依据,需说明 tag 判定理由)。
**以下情况视为不合格 review,§5.0 准入检查拒绝启动 SFT**
- review 行中缺少 `label``recommended_label` 字段
- `label` 字段只含 complex=true/false 而非完整 output
- `reason` 中只提及 complex 判定而未提及 tag 归属判断
⚠️ **H1 query 来源**review 时传给 label-master 的 `query` **必须**取自 `modified_samples.jsonl``query` 字段(Step C 已要求写入完整 query)。**禁止**从 `instruction_query_excerpt` 提取——该字段可能被截断导致 `extract_query` 返回空值。如果 `query` 字段缺失(旧格式兼容),必须用 `line_idx` + `file` 回原始训练文件读取完整 instruction 再 `extract_query`
**覆盖率硬规则**`label_master_review.jsonl` H2 行数 = `augment_<runDic>.jsonl` 行数(§5.0 `wc -l` 断言会拦)。不通过 > 0 则必须修正后重新 review 直到全 pass。verdict 文件不存在 → Step 5 拒绝启动。
仅保留层 1 格式校验(`validate_label_output.py`),不再调用 label-master 做语义复核。augment 完成后直接进入 Step 5 SFT。
### 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。
🚨 **Step 4 → Step 5 硬连接**`augment=complete` 后同一轮**紧接着**:① 写 `sft=running`层 1 格式校验(`validate_label_output.py``submit_sft.sh` + 挂 watcher。禁止 turn 结束、禁止写简报、禁止等回调。评测在 SFT `_SUCCESS` 落盘后的下一轮单独用 `submit_cml_eval.sh` 起,不串进 SFT bg。
⚠️ **写 `augment=complete` 时必须附带 `"count"` 字段**,值为本轮最终写入 `augment_<runDic>.jsonl` 的样本行数(经 dedup + label-master 过滤后的实际数)。示例:
⚠️ **写 `augment=complete` 时必须附带 `"count"` 字段**,值为本轮最终写入 `augment_<runDic>.jsonl` 的样本行数(经 dedup + 格式校验后的实际数)。示例:
```jsonl
{"step":"augment","status":"complete","run_id":"R5","count":49,"ts":"2026-05-26T21:51:32+08:00"}
```
@@ -945,29 +901,7 @@ Pipeline panel 用此字段展示增强条数;缺失则只显示文件名。
**准入检查(少一项不许进)**
```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
# 1c. H1 检查日志必须存在(§4.0 强制要求,即使无修改也要记录扫描结论)
# 1. H1 检查日志必须存在(§4.0 强制要求,即使无修改也要记录扫描结论)
CLEAN_LOG="$AUTORESEARCH_CHAT_ROOT/results/data_clean_${RUNDIC}/data_clean_${RUNDIC}.log"
[ -f "$CLEAN_LOG" ] || { echo "data_clean 日志不存在,说明 §4.0 H1 检查未执行,回 §4.0"; exit 1; }
@@ -1195,7 +1129,7 @@ eval "$(./scripts/resolve_run_ids.sh)"
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 路径或飞书链接,**也不要分段连发**——一段全铺,让用户一次滚完
- 必须把全部候选按 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。