Merge branch 'wsh_dev' into 'main'
fix: pipeline排序/runDic映射/label-master repeatable/达标优先级 See merge request wuyang6/zk-data-agent!4
This commit is contained in:
+50
-16
@@ -1768,12 +1768,11 @@ def create_app(state: AgentState) -> FastAPI:
|
|||||||
if isinstance(run_id, str) and run_id.strip():
|
if isinstance(run_id, str) and run_id.strip():
|
||||||
rid = run_id.strip()
|
rid = run_id.strip()
|
||||||
per_round = _resolve_run_dic_per_round(state_entries)
|
per_round = _resolve_run_dic_per_round(state_entries)
|
||||||
# augment 处理的是「上一轮评测的错例」,产物按 R{n-1} 的 runDic 命名
|
# augment 产物的 runDic = resolve_run_ids.sh 在当轮 eval 落盘后取到的
|
||||||
# (data_clean_<R{n-1}>/、augment_<R{n-1}>.jsonl ...),不是本轮 eval workflow
|
# max(已落盘 workflow) = 当轮 eval 自身的 workflow id,即 per_round[R{n}]
|
||||||
|
# (不是 R{n-1},脚本取的是 eval 之后的 max,此时 max 已经包含当轮 eval)
|
||||||
if step == 'augment':
|
if step == 'augment':
|
||||||
m = re.match(r'^[Rr](\d+)$', rid)
|
pass # 直接走下面 per_round.get(rid) 取本轮 runDic
|
||||||
if m and int(m.group(1)) >= 1:
|
|
||||||
run_dic = per_round.get(f'R{int(m.group(1)) - 1}')
|
|
||||||
if run_dic is None:
|
if run_dic is None:
|
||||||
run_dic = per_round.get(rid)
|
run_dic = per_round.get(rid)
|
||||||
if run_dic is None:
|
if run_dic is None:
|
||||||
@@ -6395,9 +6394,20 @@ def _build_pipeline_items(
|
|||||||
for c in sec['cards']:
|
for c in sec['cards']:
|
||||||
c.pop('_order', None)
|
c.pop('_order', None)
|
||||||
|
|
||||||
# 4. Build round items in display order.
|
# 3b. If a gate is running for a given run_id, downgrade running cards
|
||||||
# Order: by run_index ascending, then within same round: train before analysis.
|
# in that run to 'waiting' (the step is blocked on human review).
|
||||||
section_order = {'baseline': 0, 'train': 0, 'analysis': 1}
|
runs_with_gate_running: set[str] = set()
|
||||||
|
for entry in latest_gate_by_key.values():
|
||||||
|
if entry.get('status') == 'running':
|
||||||
|
runs_with_gate_running.add(entry['_run_id'])
|
||||||
|
if runs_with_gate_running:
|
||||||
|
for sec in sections.values():
|
||||||
|
if sec['run_id'] in runs_with_gate_running:
|
||||||
|
for card in sec['cards']:
|
||||||
|
if card['status'] == 'running':
|
||||||
|
card['status'] = 'waiting'
|
||||||
|
|
||||||
|
# 4. Build round items in display order (chronological by first_ts).
|
||||||
section_items: list[dict[str, Any]] = []
|
section_items: list[dict[str, Any]] = []
|
||||||
for (run_id, section_type), sec in sections.items():
|
for (run_id, section_type), sec in sections.items():
|
||||||
run_index = _parse_run_index(run_id)
|
run_index = _parse_run_index(run_id)
|
||||||
@@ -6415,7 +6425,7 @@ def _build_pipeline_items(
|
|||||||
'description': _SECTION_DESCRIPTIONS[section_type],
|
'description': _SECTION_DESCRIPTIONS[section_type],
|
||||||
'status': round_status,
|
'status': round_status,
|
||||||
'cards': sec['cards'],
|
'cards': sec['cards'],
|
||||||
'_order_key': (run_index, section_order.get(section_type, 9), sec.get('first_ts', '')),
|
'_order_key': sec.get('first_ts', ''),
|
||||||
})
|
})
|
||||||
|
|
||||||
# 5. Build gate items.
|
# 5. Build gate items.
|
||||||
@@ -6499,7 +6509,9 @@ def _compute_in_flight(
|
|||||||
seen_keys: set[str] = set()
|
seen_keys: set[str] = set()
|
||||||
for idx in range(len(state_entries) - 1, -1, -1):
|
for idx in range(len(state_entries) - 1, -1, -1):
|
||||||
entry = state_entries[idx]
|
entry = state_entries[idx]
|
||||||
if entry.get('kpi') or entry.get('log'):
|
if entry.get('kpi'):
|
||||||
|
continue
|
||||||
|
if entry.get('log') and not entry.get('step'):
|
||||||
continue
|
continue
|
||||||
step = entry.get('step')
|
step = entry.get('step')
|
||||||
if not isinstance(step, str) or not step:
|
if not isinstance(step, str) or not step:
|
||||||
@@ -6531,9 +6543,22 @@ def _compute_in_flight(
|
|||||||
if not target_run_id or not target_section:
|
if not target_run_id or not target_section:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# Check if a gate (human-check/human-review) is running for the target run_id
|
||||||
|
gate_running_for_run = False
|
||||||
|
for entry in state_entries:
|
||||||
|
step = entry.get('step')
|
||||||
|
if not isinstance(step, str) or not step:
|
||||||
|
continue
|
||||||
|
if _STEP_KIND.get(step) == 'gate' and entry.get('status') == 'running':
|
||||||
|
entry_run_id = entry.get('run_id')
|
||||||
|
if entry_run_id == target_run_id:
|
||||||
|
gate_running_for_run = True
|
||||||
|
|
||||||
latest_per_step: dict[str, dict[str, Any]] = {}
|
latest_per_step: dict[str, dict[str, Any]] = {}
|
||||||
for idx, entry in enumerate(state_entries):
|
for idx, entry in enumerate(state_entries):
|
||||||
if entry.get('kpi') or entry.get('log'):
|
if entry.get('kpi'):
|
||||||
|
continue
|
||||||
|
if entry.get('log') and not entry.get('step'):
|
||||||
continue
|
continue
|
||||||
step = entry.get('step')
|
step = entry.get('step')
|
||||||
if not isinstance(step, str) or not step:
|
if not isinstance(step, str) or not step:
|
||||||
@@ -6559,6 +6584,8 @@ def _compute_in_flight(
|
|||||||
status = entry.get('status')
|
status = entry.get('status')
|
||||||
if status not in ('complete', 'running'):
|
if status not in ('complete', 'running'):
|
||||||
continue
|
continue
|
||||||
|
if status == 'running' and gate_running_for_run:
|
||||||
|
status = 'waiting'
|
||||||
meta = _CARD_META.get((step, target_section), {
|
meta = _CARD_META.get((step, target_section), {
|
||||||
'icon': 'clock',
|
'icon': 'clock',
|
||||||
'title': step,
|
'title': step,
|
||||||
@@ -6634,7 +6661,9 @@ def _apply_program_state(
|
|||||||
# whenever a running section is hidden.
|
# whenever a running section is hidden.
|
||||||
latest_per_step: dict[str, str] = {}
|
latest_per_step: dict[str, str] = {}
|
||||||
for entry in state_entries:
|
for entry in state_entries:
|
||||||
if entry.get('kpi') or entry.get('log'):
|
if entry.get('kpi'):
|
||||||
|
continue
|
||||||
|
if entry.get('log') and not entry.get('step'):
|
||||||
continue
|
continue
|
||||||
step = entry.get('step')
|
step = entry.get('step')
|
||||||
status = entry.get('status')
|
status = entry.get('status')
|
||||||
@@ -6655,14 +6684,18 @@ def _apply_program_state(
|
|||||||
statuses = list(latest_per_step.values())
|
statuses = list(latest_per_step.values())
|
||||||
if not statuses:
|
if not statuses:
|
||||||
payload['status']['state'] = 'pending'
|
payload['status']['state'] = 'pending'
|
||||||
elif non_gate_running:
|
elif non_gate_running and not gate_running:
|
||||||
payload['status']['state'] = 'running'
|
payload['status']['state'] = 'running'
|
||||||
elif gate_running:
|
elif gate_running:
|
||||||
# Only HiTL gates are 'running' — agent has handed control back to the
|
# HiTL gate is running — agent has handed control back to the user.
|
||||||
# user. Surface as 'waiting' so the header pill says "等待人工" instead
|
# Even if a parent step (e.g. augment) is still technically 'running',
|
||||||
# of the spinning "Running" badge.
|
# the gate blocks progress. Surface as 'waiting' so the header pill
|
||||||
|
# says "等待人工" instead of the spinning "Running" badge.
|
||||||
payload['status']['state'] = 'waiting'
|
payload['status']['state'] = 'waiting'
|
||||||
elif all(s == 'complete' for s in statuses):
|
elif all(s == 'complete' for s in statuses):
|
||||||
|
if run_active:
|
||||||
|
payload['status']['state'] = 'running'
|
||||||
|
else:
|
||||||
payload['status']['state'] = 'complete'
|
payload['status']['state'] = 'complete'
|
||||||
elif 'complete' in statuses:
|
elif 'complete' in statuses:
|
||||||
payload['status']['state'] = 'running'
|
payload['status']['state'] = 'running'
|
||||||
@@ -6702,6 +6735,7 @@ def _apply_program_state(
|
|||||||
and payload['status']['state'] == 'running'
|
and payload['status']['state'] == 'running'
|
||||||
and running_steps
|
and running_steps
|
||||||
and not all_watched
|
and not all_watched
|
||||||
|
and not gate_running
|
||||||
):
|
):
|
||||||
session_failure_status = 'failed'
|
session_failure_status = 'failed'
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ description: 标签大师:读取和使用中控标签知识库,辅助标签
|
|||||||
when_to_use: 当用户要求判断 query 应该归属哪个标签、解释标签边界、校验数据生成目标中的 target、整理标签知识或分析标签混淆时使用。
|
when_to_use: 当用户要求判断 query 应该归属哪个标签、解释标签边界、校验数据生成目标中的 target、整理标签知识或分析标签混淆时使用。
|
||||||
aliases: label-knowledge, 标签大师, 标签知识, label, 标签体系
|
aliases: label-knowledge, 标签大师, 标签知识, label, 标签体系
|
||||||
allowed_tools: read_file, write_file, edit_file, grep_search, glob_search, ask_user_question, python_exec, bash
|
allowed_tools: read_file, write_file, edit_file, grep_search, glob_search, ask_user_question, python_exec, bash
|
||||||
|
repeatable: true
|
||||||
---
|
---
|
||||||
|
|
||||||
使用这个 skill 作为“标签知识理解和标签边界分析”的入口。它不直接把标签判断收敛成一个黑盒分类工具,而是指导 Agent 逐步阅读知识、比较候选、解释依据,并在不确定时向用户确认。
|
使用这个 skill 作为“标签知识理解和标签边界分析”的入口。它不直接把标签判断收敛成一个黑盒分类工具,而是指导 Agent 逐步阅读知识、比较候选、解释依据,并在不确定时向用户确认。
|
||||||
|
|||||||
@@ -65,6 +65,13 @@ when_to_use: |
|
|||||||
- [ ] **`step:"hypothesis"` entry 写在当前 round(R{n})名下**——不要写成 R{n+1}。后端把 hypothesis 归类为 analysis 类,是 R{n}·Baseline / R{n}·Analysis 的最后一张卡,不是 R{n+1}·Train 的开头。这样 gate(Human Check / Review)会插在 hypothesis 卡之后、R{n+1}·Train 之前,**用户看到假设内容再拍板是否进 train**。
|
- [ ] **`step:"hypothesis"` entry 写在当前 round(R{n})名下**——不要写成 R{n+1}。后端把 hypothesis 归类为 analysis 类,是 R{n}·Baseline / R{n}·Analysis 的最后一张卡,不是 R{n+1}·Train 的开头。这样 gate(Human Check / Review)会插在 hypothesis 卡之后、R{n+1}·Train 之前,**用户看到假设内容再拍板是否进 train**。
|
||||||
- [ ] **`hypothesis=complete` 之前,必须 append `iteration_log.jsonl` 一条 R{n} entry**,schema 至少包含 `iteration:n / runDic:<本轮编号> / timestamp / results:{<指标 key>:<float>...} / hypothesis`。**前端 metrics 折线图唯一数据源就是这个文件**,没写 → 图表彻底空白(state file 写得再全也不行)。已踩坑:R0 走完 dist-analysis + hypothesis 但漏了 iteration_log,前端 metrics 卡看不到任何点,指标空了一整轮。`results` 必须用 program.md 「§iteration_log schema」里固定的 metric key(`req_set_car / dapan_car / specific_test / triage_err_rate / bvt_nav / talkable_controllable` 等),key 拼错前端按缺失处理。
|
- [ ] **`hypothesis=complete` 之前,必须 append `iteration_log.jsonl` 一条 R{n} entry**,schema 至少包含 `iteration:n / runDic:<本轮编号> / timestamp / results:{<指标 key>:<float>...} / hypothesis`。**前端 metrics 折线图唯一数据源就是这个文件**,没写 → 图表彻底空白(state file 写得再全也不行)。已踩坑:R0 走完 dist-analysis + hypothesis 但漏了 iteration_log,前端 metrics 卡看不到任何点,指标空了一整轮。`results` 必须用 program.md 「§iteration_log schema」里固定的 metric key(`req_set_car / dapan_car / specific_test / triage_err_rate / bvt_nav / talkable_controllable` 等),key 拼错前端按缺失处理。
|
||||||
|
|
||||||
|
### Step 3 → Step 4 / gate 边界(**CRITICAL:hypothesis=complete 后禁止空手结束 turn**)
|
||||||
|
- [ ] **`hypothesis=complete` 写入 program-state.jsonl 之后,当前 turn 绝对不允许结束**——必须在同一轮里做下面两件之一(二选一,不能都不做):
|
||||||
|
- (A) 如果命中 HiTL 信号(§HiTL 阈值):写 `human-check` gate entry(running)→ chat reply 给用户选项 → 然后才可以让 turn 自然结束(gate=running 会让 UI 显示 "waiting" 卡片)
|
||||||
|
- (B) 如果不命中 HiTL(候选量 ≤ 50 全自动路径):直接写 `augment=running` → 开始执行增强
|
||||||
|
- [ ] **"turn 预算不够"不是合法理由**——50 turns 里跑完 hypothesis 最多用 15~20 turns,剩余空间远够写一条 gate entry 或启动 augment。如果你真的接近上限,也必须**先写 gate entry 再结束**,不能空手结束。
|
||||||
|
- [ ] **"下一轮 wake 继续"不存在**——没有自动 wake 机制。你结束了就是结束了,session 不会被再次调起,用户只看到 pipeline 卡在 hypothesis=complete 不动。
|
||||||
|
|
||||||
### Step 1 → Step 3 边界(**NEVER STOP 硬连接**,R0 / R1+ regression 都适用)
|
### Step 1 → Step 3 边界(**NEVER STOP 硬连接**,R0 / R1+ regression 都适用)
|
||||||
- [ ] dist-analysis 算完 delta(new_fail / new_fix / persistent / 子集分布)那一刻起,**同一轮 bash 不许结束**:紧接着写 `results/workflow<runDic>.md`(包含完整指标表 + 病灶定位 + 假设 + 回滚/继续建议)→ append `iteration_log.jsonl` R{n} entry(`results` 字段填本轮 metric,`hypothesis` 字段填下一动作)
|
- [ ] dist-analysis 算完 delta(new_fail / new_fix / persistent / 子集分布)那一刻起,**同一轮 bash 不许结束**:紧接着写 `results/workflow<runDic>.md`(包含完整指标表 + 病灶定位 + 假设 + 回滚/继续建议)→ append `iteration_log.jsonl` R{n} entry(`results` 字段填本轮 metric,`hypothesis` 字段填下一动作)
|
||||||
- [ ] **regression 场景同样适用**:哪怕 R1 出现导航bvt 纯劣化、可聊可控大幅 -25 这种"必须回滚"信号,**先把分析落到 workflow<runDic>.md 和 iteration_log,再用 chat reply 给人回滚选项**。文件先落、聊天再发——顺序不能反。
|
- [ ] **regression 场景同样适用**:哪怕 R1 出现导航bvt 纯劣化、可聊可控大幅 -25 这种"必须回滚"信号,**先把分析落到 workflow<runDic>.md 和 iteration_log,再用 chat reply 给人回滚选项**。文件先落、聊天再发——顺序不能反。
|
||||||
@@ -81,6 +88,7 @@ when_to_use: |
|
|||||||
- [ ] §4.0.1 Step C:备份 `.bak` 文件
|
- [ ] §4.0.1 Step C:备份 `.bak` 文件
|
||||||
- [ ] §4.1 输入准备 / §4.2 GPT 调用 / §4.3 sanity check / §4.4 写入
|
- [ ] §4.1 输入准备 / §4.2 GPT 调用 / §4.3 sanity check / §4.4 写入
|
||||||
- [ ] **§4.5 label-master 标签复核(落盘后必做,强制)**:H1 `modified_samples.jsonl` 只跑层 1(格式)—— 语义已在 §4.0.1 Step A.5 完成;H2 `augment_<runDic>.jsonl` 跑两层(`validate_label_output.py` 格式 + Skill 调用 `label-master` 语义),**逐条 1:1 全量覆盖**(`augment` 多少行 `label_master_review.jsonl` H2 部分就多少行,禁止抽样 / spot-check / X/X pass 外推),verdict 写 `results/data_clean_<runDic>/label_master_review.jsonl`,**不通过必须 = 0**(任何不通过必须修正后重新 review 直到全 pass 才能进 Step 5)
|
- [ ] **§4.5 label-master 标签复核(落盘后必做,强制)**:H1 `modified_samples.jsonl` 只跑层 1(格式)—— 语义已在 §4.0.1 Step A.5 完成;H2 `augment_<runDic>.jsonl` 跑两层(`validate_label_output.py` 格式 + Skill 调用 `label-master` 语义),**逐条 1:1 全量覆盖**(`augment` 多少行 `label_master_review.jsonl` H2 部分就多少行,禁止抽样 / spot-check / X/X pass 外推),verdict 写 `results/data_clean_<runDic>/label_master_review.jsonl`,**不通过必须 = 0**(任何不通过必须修正后重新 review 直到全 pass 才能进 Step 5)
|
||||||
|
- [ ] **§4.5 层 2 语义复核必须调用 `Skill(skill="label-master")`,禁止正则/规则脚本替代**:层 2 的本质是"用 label-master 知识体系对每条 (query, label) 做独立语义判定"。label-master 已标记 `repeatable: true`,可在同一 run 内多次调用。**禁止**:纯正则/关键词匹配脚本、"target 都一样所以直接 pass"逻辑、批量写 pass 不看 query 内容。已踩坑:R1 agent 写了个正则脚本充当"layer 2",完全没走语义判断。
|
||||||
|
|
||||||
### Step 4 → Step 5 边界(**NEVER STOP 硬连接**,反复踩坑)
|
### Step 4 → Step 5 边界(**NEVER STOP 硬连接**,反复踩坑)
|
||||||
- [ ] 写完 `augment=complete` 那一刻,**同一轮 bash 不许结束**:紧接着跑 §4.5 label-master 复核 → 写 `sft=running` → 调 `submit_sft.sh` → 挂 watcher(评测在 SFT `_SUCCESS` 落盘后的下一轮单独用 `submit_cml_eval.sh` 起,不要在 SFT bg 里串接评测)
|
- [ ] 写完 `augment=complete` 那一刻,**同一轮 bash 不许结束**:紧接着跑 §4.5 label-master 复核 → 写 `sft=running` → 调 `submit_sft.sh` → 挂 watcher(评测在 SFT `_SUCCESS` 落盘后的下一轮单独用 `submit_cml_eval.sh` 起,不要在 SFT bg 里串接评测)
|
||||||
@@ -418,9 +426,11 @@ echo '{"log":{"ts":"11:55:10","iter":"R1","text":"Step 1 dist-analysis: 写 work
|
|||||||
|
|
||||||
## 达标条件
|
## 达标条件
|
||||||
|
|
||||||
- **需求集合** ≥ 95%(最高优先级)
|
- **需求集合(目标集)** ≥ 95%(**最高优先级,一切以此为准**)
|
||||||
- **大盘集(车载)** 降幅 ≤ 0.3%
|
- **大盘集(车载)** 降幅 ≤ 0.3%(次要,目标集未达 95% 前不因大盘微降而回滚/停训)
|
||||||
- **specific test** 降幅 ≤ 1%
|
- **specific test** 降幅 ≤ 1%(次要,同上)
|
||||||
|
|
||||||
|
⚠️ **优先级铁律**:目标集上 95% 是主线任务。只要目标集还没到 95%,其他集合的轻微下降(大盘 ≤1%、specific ≤2%)**不构成回滚理由**,继续迭代。只有目标集已达标后,才需要关注并修复其他集合的 regression。
|
||||||
|
|
||||||
## 主流程
|
## 主流程
|
||||||
|
|
||||||
|
|||||||
@@ -178,13 +178,17 @@ for k in prev:
|
|||||||
3. **specific test**
|
3. **specific test**
|
||||||
|
|
||||||
**达标条件**:
|
**达标条件**:
|
||||||
- 需求集合 ≥ 95%
|
- 需求集合(目标集) ≥ 95%(**最高优先级**)
|
||||||
- 大盘集(车载)降幅 ≤ 0.3%
|
- 大盘集(车载)降幅 ≤ 0.3%(次要)
|
||||||
- specific test 降幅 ≤ 1%
|
- specific test 降幅 ≤ 1%(次要)
|
||||||
|
|
||||||
|
⚠️ **优先级铁律**:目标集上 95% 是主线任务。只要目标集还没到 95%,其他集合的轻微下降(大盘 ≤1%、specific ≤2%)**不构成回滚理由**,应继续迭代优化目标集。只有目标集已达 95% 后,才需要关注并修复其他集合的 regression。
|
||||||
|
|
||||||
**决策**:
|
**决策**:
|
||||||
- 全部达标 → 部署(`python modules/cloudml_deploy.py <rl_checkpoint>`),记录,循环结束
|
- 全部达标 → 部署(`python modules/cloudml_deploy.py <rl_checkpoint>`),记录,循环结束
|
||||||
- 不达标 → 在 Step 1 内继续做问题分析(见下文),**分析顺序:需求集合 → 车载大盘 → specific test**
|
- 目标集未达标 → 继续迭代(即使其他集合有轻微下降也不回滚)
|
||||||
|
- 目标集已达标但其他集合超限 → 微调修复其他集合
|
||||||
|
- 分析顺序:**需求集合 → 车载大盘 → specific test**
|
||||||
|
|
||||||
> **首次评测差异**:由于新旧模型相同,"降幅"和"vs 基线"无意义(均为 0)。此轮只记录各集合的**绝对准确率**作为 baseline,不做达标/不达标判定,不触发部署。直接在 Step 1 内分析基准模型的错误分布,为后续迭代建立对照基准。
|
> **首次评测差异**:由于新旧模型相同,"降幅"和"vs 基线"无意义(均为 0)。此轮只记录各集合的**绝对准确率**作为 baseline,不做达标/不达标判定,不触发部署。直接在 Step 1 内分析基准模型的错误分布,为后续迭代建立对照基准。
|
||||||
|
|
||||||
@@ -655,6 +659,12 @@ for r in b_rows:
|
|||||||
|
|
||||||
> 若 Step 1 显示全部达标 → 跳过 Step 3–5,直接部署。
|
> 若 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 增强)
|
### 4. 数据生成(GPT-5.4 从 badcase 增强)
|
||||||
|
|
||||||
**只在 §2.4 归因为 `data` 时做。** 其他归因直接跳过 Step 4,按下表路由:
|
**只在 §2.4 归因为 `data` 时做。** 其他归因直接跳过 Step 4,按下表路由:
|
||||||
@@ -743,18 +753,19 @@ 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 看不到。
|
候选阶段(246 条等量级)的 CSV 必须写到 `output/relabel_candidates_<runDic>.csv` 让分析卡片可读;确认修改阶段(用户审核后落盘的 135 条)写入 `results/data_clean_<runDic>/modified_samples.jsonl` 让数据增强卡片可读。**不要混落**——把候选 CSV 写到 `/tmp/` 或 `data_clean_/` 都会让 UI 看不到。
|
||||||
|
|
||||||
> **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 命名语义(backend 已对齐)**:augment 类产物(`data_clean_<N>/`、`augment_raw/augment_<N>_raw.jsonl`、`zk_intent/augment_<N>.jsonl`、`label_master_review.jsonl`)的 `<N>` = `$SFT_RUNDIC` = **当轮 eval 落盘后 `resolve_run_ids.sh` 取到的 max(已落盘 workflow)**。由于 resolve 是在当轮 eval 的 `lark_template.json` 落盘之后调用的,此时 max 就是当轮 eval 自身的 workflow id。Backend step-detail 端点对 augment 也直接用本轮(`per_round[R{n}]`)的 runDic 去读。其他 step(cml / dist-analysis / hypothesis / gold-drift)的产物同理按本轮自己的 runDic 命名。
|
||||||
>
|
>
|
||||||
> ⚠️ **runDic 不许手算**:augment 落盘、SFT yaml、归档目录、cml workflow run 用的 runDic **必须**从 `scripts/resolve_run_ids.sh` 取,不准 agent 自己 `ls workflow5 | tail -1` 然后心算 +1。规则简单到没必要靠模型:
|
> ⚠️ **runDic 不许手算**:augment 落盘、SFT yaml、归档目录、cml workflow run 用的 runDic **必须**从 `scripts/resolve_run_ids.sh` 取,不准 agent 自己 `ls workflow5 | tail -1` 然后心算 +1。规则简单到没必要靠模型:
|
||||||
>
|
>
|
||||||
> ```bash
|
> ```bash
|
||||||
> eval "$(./scripts/resolve_run_ids.sh)"
|
> eval "$(./scripts/resolve_run_ids.sh)"
|
||||||
> # 现在 shell 里有:
|
> # 现在 shell 里有:
|
||||||
> # $SFT_RUNDIC = R{n-1}.runDic (augment 落盘 / yaml / 归档全用这个,不 +1)
|
> # $SFT_RUNDIC = max(已落盘 workflow) = 当轮 eval 的 workflow id
|
||||||
> # $EVAL_RUNDIC = R{n-1}.runDic + 1 (仅 cml workflow run 时使用,唯一一次 +1)
|
> # augment 落盘 / yaml / 归档全用这个
|
||||||
|
> # $EVAL_RUNDIC = SFT_RUNDIC + 1(仅下一轮 cml workflow run 提交时使用)
|
||||||
> ```
|
> ```
|
||||||
>
|
>
|
||||||
> 已踩坑:R2 augment 把 `data_clean_<N>/` 写成了 `EVAL_RUNDIC`(多 +1 一次),跳过了 R1.runDic 整段命名空间,前端 augment 卡片直接读不到。**写 augment / SFT / 归档脚本时一律用 `$SFT_RUNDIC`,不要写 `${RUNDIC}` 也不要写 `${EVAL_RUNDIC}`**。
|
> **写 augment / SFT / 归档脚本时一律用 `$SFT_RUNDIC`,不要写 `${RUNDIC}` 也不要写 `${EVAL_RUNDIC}`**。
|
||||||
>
|
>
|
||||||
> 🚨 **调用时序(CRITICAL)**:`resolve_run_ids.sh` **只能在当轮 CML eval 的 `lark_template.json` 已落盘之后调用**。禁止在 eval 提交前或 watcher 等待期间预先调用——此时 max workflow 仍是上一轮的值,会导致 SFT_RUNDIC 偏移 -1(已踩坑:R0 workflow17823 评测中提前 resolve 得到 17822,augment 文件全部错位)。正确时序:`cml step=complete` → `resolve_run_ids.sh` → augment/SFT。每次进入 augment/SFT 步骤前**必须重新调用**,不许复用之前缓存的值。
|
> 🚨 **调用时序(CRITICAL)**:`resolve_run_ids.sh` **只能在当轮 CML eval 的 `lark_template.json` 已落盘之后调用**。禁止在 eval 提交前或 watcher 等待期间预先调用——此时 max workflow 仍是上一轮的值,会导致 SFT_RUNDIC 偏移 -1(已踩坑:R0 workflow17823 评测中提前 resolve 得到 17822,augment 文件全部错位)。正确时序:`cml step=complete` → `resolve_run_ids.sh` → augment/SFT。每次进入 augment/SFT 步骤前**必须重新调用**,不许复用之前缓存的值。
|
||||||
|
|
||||||
@@ -1262,6 +1273,8 @@ python skills/label-master/scripts/validate_label_output.py \
|
|||||||
|
|
||||||
**层 2:语义复核(Skill 调用 label-master Agent,仅 H2)**
|
**层 2:语义复核(Skill 调用 label-master Agent,仅 H2)**
|
||||||
|
|
||||||
|
⛔ **禁止用正则/规则脚本替代 LLM 语义判断**。层 2 不是格式校验,是"该 query 在 label-master 知识体系下是否应该标成这个 label"的语义问题。**唯一合法实现方式是调用 `Skill(skill="label-master")`**(label-master 已标记 `repeatable: true`,可在同一 run 内多次调用)。已踩坑:R1 agent 写了个检查 hist 里有没有导航触发词的正则脚本充当"layer 2",完全没走语义判断,导致错误样本全量混入训练集。
|
||||||
|
|
||||||
H1 在 §4.0.1 Step A.5 已经过 label-master 推荐覆盖,这里**不再重跑**。H2 仿写是 §4.2 GPT 新增的样本,没经过 Step A.5,必须在此补一次语义判定:
|
H1 在 §4.0.1 Step A.5 已经过 label-master 推荐覆盖,这里**不再重跑**。H2 仿写是 §4.2 GPT 新增的样本,没经过 Step A.5,必须在此补一次语义判定:
|
||||||
|
|
||||||
🚨 **覆盖率硬规则**:`label_master_review.jsonl` 里 H2 部分的行数**必须等于** `augment_<runDic>.jsonl` 的行数(1:1 全量覆盖)。**禁止抽样、禁止"前 N 条 spot-check"、禁止"5/5 pass 推 100 OK"**——§5.0 准入门会用 `wc -l` 拦。已踩坑:R17817 跑了 5 条样本就上报"100 条 pass",被 §5.0 退回。
|
🚨 **覆盖率硬规则**:`label_master_review.jsonl` 里 H2 部分的行数**必须等于** `augment_<runDic>.jsonl` 的行数(1:1 全量覆盖)。**禁止抽样、禁止"前 N 条 spot-check"、禁止"5/5 pass 推 100 OK"**——§5.0 准入门会用 `wc -l` 拦。已踩坑:R17817 跑了 5 条样本就上报"100 条 pass",被 §5.0 退回。
|
||||||
@@ -1485,6 +1498,16 @@ python prepare_and_train_sft.py prepare --regen_from_csv --output_dir ./sft_data
|
|||||||
|
|
||||||
> 验证集文件名不能含 `valid`/`test`/`eval`(HF datasets 会推断错误的 split),脚本自动重命名为 `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_R3(R2 pattern 导致导航 bvt 劣化)"}}
|
||||||
|
```
|
||||||
|
|
||||||
|
必须展示:(1) 参与训练的所有文件及行数 (2) 被排除的旧轮文件及排除原因(如果有)。
|
||||||
|
|
||||||
#### 5.2 启动训练(cml 任务,R29 起统一走这条路径)
|
#### 5.2 启动训练(cml 任务,R29 起统一走这条路径)
|
||||||
|
|
||||||
R23-R28 期间用本地 `nohup python3 prepare_and_train_sft.py train ...` 启动,会因登出/网络/会话退出而中断,并占用本地工作机 8 卡 GPU。**R29 起统一改为 cml custom_train submit 提交训练任务**,由 CloudML 调度到 `bj-nlp` 队列的 h20-96g 8 卡,本地零占用。
|
R23-R28 期间用本地 `nohup python3 prepare_and_train_sft.py train ...` 启动,会因登出/网络/会话退出而中断,并占用本地工作机 8 卡 GPU。**R29 起统一改为 cml custom_train submit 提交训练任务**,由 CloudML 调度到 `bj-nlp` 队列的 h20-96g 8 卡,本地零占用。
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# resolve_run_ids.sh —— SFT/EVAL runDic 单一可信来源
|
# resolve_run_ids.sh —— SFT/EVAL runDic 单一可信来源
|
||||||
#
|
#
|
||||||
# 规则(与 program.md §746 + §5.2 对齐):
|
# 规则(与 program.md §756 对齐):
|
||||||
# SFT_RUNDIC = workflow5/ 下「已落盘」(含 metric_diff/lark_template.json)的最大 runDic
|
# SFT_RUNDIC = workflow5/ 下「已落盘」(含 metric_diff/lark_template.json)的最大 runDic
|
||||||
# 即 R{n-1}.runDic —— augment 落盘、SFT yaml、modified_samples 归档全用这个值
|
# = 当轮 eval 自身的 workflow id(因为 resolve 在 eval 落盘后调用)
|
||||||
|
# augment 落盘、SFT yaml、modified_samples 归档全用这个值
|
||||||
# EVAL_RUNDIC = SFT_RUNDIC + 1
|
# EVAL_RUNDIC = SFT_RUNDIC + 1
|
||||||
# 仅 cml workflow run 提交本轮评测时使用
|
# 仅下一轮 cml workflow run 提交时使用
|
||||||
#
|
#
|
||||||
# 用法:
|
# 用法:
|
||||||
# eval "$(./scripts/resolve_run_ids.sh)"
|
# eval "$(./scripts/resolve_run_ids.sh)"
|
||||||
|
|||||||
@@ -2681,7 +2681,7 @@ class LocalCodingAgent:
|
|||||||
bundled = find_bundled_skill(skill_name, cwd=self.runtime_config.cwd)
|
bundled = find_bundled_skill(skill_name, cwd=self.runtime_config.cwd)
|
||||||
if bundled is not None:
|
if bundled is not None:
|
||||||
skill_key = bundled.name.strip().lower()
|
skill_key = bundled.name.strip().lower()
|
||||||
if active_skill_names is not None and skill_key in active_skill_names:
|
if active_skill_names is not None and skill_key in active_skill_names and not bundled.repeatable:
|
||||||
duplicate_count = 1
|
duplicate_count = 1
|
||||||
if duplicate_skill_counts is not None:
|
if duplicate_skill_counts is not None:
|
||||||
duplicate_count = duplicate_skill_counts.get(skill_key, 0) + 1
|
duplicate_count = duplicate_skill_counts.get(skill_key, 0) + 1
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ class BundledSkill:
|
|||||||
aliases: tuple[str, ...] = ()
|
aliases: tuple[str, ...] = ()
|
||||||
allowed_tools: tuple[str, ...] = ()
|
allowed_tools: tuple[str, ...] = ()
|
||||||
user_invocable: bool = True
|
user_invocable: bool = True
|
||||||
|
repeatable: bool = False
|
||||||
source: str = 'python'
|
source: str = 'python'
|
||||||
path: str | None = None
|
path: str | None = None
|
||||||
get_prompt: Callable[['LocalCodingAgent', str], str] = lambda _a, _args: ''
|
get_prompt: Callable[['LocalCodingAgent', str], str] = lambda _a, _args: ''
|
||||||
@@ -190,6 +191,7 @@ def _load_directory_skill(path: Path, *, source: str = 'directory') -> BundledSk
|
|||||||
aliases=_split_csv(metadata.get('aliases')),
|
aliases=_split_csv(metadata.get('aliases')),
|
||||||
allowed_tools=_split_csv(metadata.get('allowed_tools')),
|
allowed_tools=_split_csv(metadata.get('allowed_tools')),
|
||||||
user_invocable=_parse_bool(metadata.get('user_invocable'), default=True),
|
user_invocable=_parse_bool(metadata.get('user_invocable'), default=True),
|
||||||
|
repeatable=_parse_bool(metadata.get('repeatable'), default=False),
|
||||||
source=source,
|
source=source,
|
||||||
path=str(path),
|
path=str(path),
|
||||||
get_prompt=_directory_skill_prompt(body.strip()),
|
get_prompt=_directory_skill_prompt(body.strip()),
|
||||||
|
|||||||
Reference in New Issue
Block a user