From 362321c460c310f718d1519ede93b729c6492cc4 Mon Sep 17 00:00:00 2001 From: hupenglong1 Date: Fri, 22 May 2026 20:56:57 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/api/server.py | 6 + .../training/training-pipeline-panel.tsx | 132 ++++++++++++++++-- skills/model-iteration/SKILL.md | 45 +++++- 3 files changed, 164 insertions(+), 19 deletions(-) diff --git a/backend/api/server.py b/backend/api/server.py index 618eba6..8583065 100644 --- a/backend/api/server.py +++ b/backend/api/server.py @@ -6128,6 +6128,9 @@ def _build_pipeline_items( # amber pill ("等待人工确认") instead of the spinning blue "IN PROGRESS" # used for actual running steps. gate_status = 'waiting' if raw_status == 'running' else raw_status + # Structured fields for the gate body — agent should prefer these so + # the UI can render labeled rows (现状 / 提议 / 请选). `reason` is kept + # as a free-text fallback for entries that haven't been migrated. gate_items.append({ 'type': 'gate', 'key': gate_key, @@ -6138,6 +6141,9 @@ def _build_pipeline_items( 'description': meta_g['description'], 'status': gate_status, 'reason': entry.get('reason'), + 'summary': entry.get('summary'), + 'proposal': entry.get('proposal'), + 'ask': entry.get('ask'), '_order_key': (run_index, 99, gate_position_index.get(gate_key, 0)), }) diff --git a/frontend/app/components/training/training-pipeline-panel.tsx b/frontend/app/components/training/training-pipeline-panel.tsx index 682689d..a837eb9 100644 --- a/frontend/app/components/training/training-pipeline-panel.tsx +++ b/frontend/app/components/training/training-pipeline-panel.tsx @@ -68,6 +68,9 @@ type GateItem = { description: string; status: CardStatus; reason?: string | null; + summary?: string | null; + proposal?: string | null; + ask?: string | null; }; type PipelineItem = RoundItem | GateItem; @@ -485,6 +488,95 @@ function CardArrow() { ); } +function parseGateReason(reason: string | null | undefined): { + summary: string | null; + proposal: string | null; + ask: string | null; +} { + const text = (reason ?? "").trim(); + if (!text) return { summary: null, proposal: null, ask: null }; + // Find the ask: greedy from any "是否/要不要/能否/还是/请..." question phrase + // to end. Fallback: the trailing sentence containing ?/?. + const askPatterns = [ + /(是否[^。]*$)/, + /(要不要[^。]*$)/, + /(能否[^。]*$)/, + /(请[^。]{0,40}[??][^。]*$)/, + ]; + let ask: string | null = null; + let body = text; + for (const pat of askPatterns) { + const m = text.match(pat); + if (m && m.index !== undefined) { + ask = m[0].trim().replace(/^[。;;,,\s]+/, ""); + body = text.slice(0, m.index).trim().replace(/[。;;,,\s]+$/, ""); + break; + } + } + if (!ask) { + // Fallback: split off trailing sentence(s) containing ? or ? + const qIdx = Math.max(text.lastIndexOf("?"), text.lastIndexOf("?")); + if (qIdx >= 0) { + // expand back to previous 。 or start of string + const prevDot = Math.max( + text.lastIndexOf("。", qIdx - 1), + text.lastIndexOf(";", qIdx - 1), + text.lastIndexOf(";", qIdx - 1), + ); + ask = text.slice(prevDot + 1).trim().replace(/^[。;;,,\s]+/, ""); + body = text.slice(0, prevDot + 1).trim().replace(/[。;;,,\s]+$/, ""); + } + } + const sentences = body + .split(/。/) + .map((s) => s.trim()) + .filter(Boolean); + if (sentences.length === 0) { + return { summary: null, proposal: null, ask }; + } + if (sentences.length === 1) { + return { summary: sentences[0], proposal: null, ask }; + } + const proposalKw = + /(H\d|H[1-3]\b|拟|要做|计划|提议|方案|增强|仿写|修改|修\s*\d|fix|patch|候选|逐条审|relabel|sft|train)/i; + let splitIdx = sentences.findIndex((s) => proposalKw.test(s)); + if (splitIdx <= 0) splitIdx = Math.max(1, Math.floor(sentences.length / 2)); + const summary = sentences.slice(0, splitIdx).join("。 "); + const proposal = sentences.slice(splitIdx).join("。 "); + return { summary, proposal, ask }; +} + +function GateBodyRow({ + label, + text, + tone, +}: { + label: string; + text: string; + tone: "status" | "proposal" | "ask"; +}) { + const labelTone = + tone === "ask" + ? "bg-amber-500/25 text-amber-800 dark:bg-amber-400/30 dark:text-amber-100" + : tone === "proposal" + ? "bg-sky-500/15 text-sky-700 dark:bg-sky-400/20 dark:text-sky-200" + : "bg-zinc-500/15 text-zinc-700 dark:bg-zinc-400/20 dark:text-zinc-200"; + const textTone = tone === "ask" ? "text-foreground font-medium" : "text-foreground/85"; + return ( +
+ + {label} + +

{text}

+
+ ); +} + function GateCard({ item, onClick, @@ -500,12 +592,18 @@ function GateCard({ item.gate_kind === "human-review" ? "在右侧对话里给出 OK / 调整后的指令,agent 才会继续。" : "在右侧对话里回 OK / 修改建议,确认后进入下一轮训练。"; + // Prefer structured fields; fall back to heuristic parsing of `reason`. + const parsed = parseGateReason(item.reason); + const summary = item.summary ?? parsed.summary; + const proposal = item.proposal ?? parsed.proposal; + const ask = item.ask ?? parsed.ask; + const hasStructured = Boolean(summary || proposal || ask); return (
- + {numLabel} -
- -
-
+
- + + {item.title} - + {item.run_id}
- {item.reason ? ( -

+ {hasStructured ? ( +

+ {summary ? ( + + ) : null} + {proposal ? ( + + ) : null} + {ask ? ( + + ) : null} +
+ ) : item.reason ? ( +

{item.reason}

) : ( -

+

{item.description}

)} {isWaiting ? ( -

+

{hint}

) : null} diff --git a/skills/model-iteration/SKILL.md b/skills/model-iteration/SKILL.md index da3f256..3cbedf9 100644 --- a/skills/model-iteration/SKILL.md +++ b/skills/model-iteration/SKILL.md @@ -444,15 +444,25 @@ assets/ #### 写法 +**强烈推荐结构化三段写法**(`summary` / `proposal` / `ask`)——UI 会渲染成"现状 / 提议 / 请选"三个带标签行,用户一眼看明白。`reason` 留作 fallback。 + ```bash -# R0 baseline 之后想让用户拍板是否进 train -echo '{"step":"human-check","status":"running","run_id":"R0","reason":"R0 baseline 分析完成,目标子集 X.X% 偏低,确认是否按当前 H1 候选进 R1 train","ts":"'$(date -Iseconds)'"}' >> $S +# R0 baseline 之后想让用户拍板是否进 train(推荐结构化) +echo '{"step":"human-check","status":"running","run_id":"R0", + "summary":"R0 baseline 完成;复杂导航过召专项0511 = 58.89%(53/90),37 错全为 complex 误判", + "proposal":"H1(标签纠错):把 7 条原标 complex=true 但实际是简单导航的样本改回 complex=false。 H2(数据增强):仿写 100 条 complex=false 的简单导航 query 补进训练集,平衡正负样本", + "ask":"是否按 H1+H2 一起进 R1 train?还是先只跑 H1 验证一轮?", + "ts":"'$(date -Iseconds)'"}' >> $S -# R1 评测发现 regression,需要用户决定是否回滚 -echo '{"step":"human-review","status":"running","run_id":"R1","reason":"R1 vs R0:导航bvt -3.2pp, 可聊可控 -25pp,建议回滚","ts":"'$(date -Iseconds)'"}' >> $S +# R1 评测发现 regression +echo '{"step":"human-review","status":"running","run_id":"R1", + "summary":"R1 vs R0:导航bvt -3.2pp,可聊可控 -25pp,目标子集 +1.4pp", + "proposal":"回滚到 R0 权重;下一轮把 H2 仿写量减半,避免对 complex=false 过拟合", + "ask":"回滚 R0 还是继续跑 R2 看曲线?", + "ts":"'$(date -Iseconds)'"}' >> $S -# 候选量超 200 条命中触发条件 #3 -echo '{"step":"human-check","status":"running","run_id":"R0","reason":"候选量 412 条命中触发条件 #3","ts":"'$(date -Iseconds)'"}' >> $S +# fallback:只写 reason 也能跑(前端会启发式切分),但不如结构化清晰 +echo '{"step":"human-check","status":"running","run_id":"R0","reason":"候选量 412 命中规则上限,需要人工定更精细 pattern 收窄","ts":"'$(date -Iseconds)'"}' >> $S ``` **顺序不能反**:先 echo gate entry → 再 chat reply 给用户。否则用户先看到聊天问话、UI 里却没卡,会困惑"流程是不是卡死了"。 @@ -464,9 +474,30 @@ echo '{"step":"human-check","status":"running","run_id":"R0","reason":"候选量 | `step` | ✅ | `human-check` 或 `human-review` | | `status` | ✅ | 卡进入时写 `running`;用户回复后写 `complete` | | `run_id` | ✅ | 当前所在轮次(决定 gate 插在哪个 section 后) | -| `reason` | ⭕ | 触发原因——直接展示给用户看,写具体一些("Gold drift 12 条,触发 #1" 比 "需要人审" 强得多) | +| `summary` | 🔼 | **现状一句话**:跑了什么、关键数字。例:`R0 baseline 完成;专项 58.89%(53/90),37 错全为 complex 误判` | +| `proposal` | 🔼 | **打算怎么干**:每个 H 单独说"H? (类型):具体做什么"。详见下面规则 | +| `ask` | 🔼 | **让用户选什么**:用问句给出 A/B 选项。例:`是否按 H1+H2 进 R1 train?还是先只跑 H1 验证一轮?` | +| `reason` | ⭕ | 兜底用:没写 summary/proposal/ask 时前端会拿 reason 做启发式切分。但**优先用结构化三段**,别只写 reason | | `ts` | ✅ | ISO 时间戳 | +🔼 = 强烈推荐写——三段都填 UI 会变成清晰的"现状/提议/请选"分块;都不填只填 reason 也能跑,但用户要自己抠语义。 + +#### proposal 写法规则(关键) + +**每个假设单独一句,结构 = `H? (一两个字概括类型):具体动作 + 数量 + 目标`**。比如: + +- ✅ `H1(标签纠错):把 7 条原标 complex=true 但实际是简单导航的样本改回 complex=false` +- ✅ `H2(数据增强):仿写 100 条 complex=false 的简单导航 query 补进训练集` +- ❌ `H1 修 7 条 mislabeled` ← 用户要猜 mislabeled 是什么意思 +- ❌ `H2 仿写 100 条 complex=false 简单导航` ← 没说补到哪、为啥补 + +**不要写进 proposal 的内容**: +- §X.X.X 规则引用(`触发 §4.0.1 Step B`、`命中条件 #3`)—— 用户不关心你按哪条规则做的,只关心你要做什么 +- 流程自洽说明(`需要人工逐条审 1/0`、`走 sanity check`、`过 label-master 复核`)—— 这些是 agent 内部流程,对用户决策没用 +- 候选量区间括号注释(`100~150 触发 51-200 区间`)—— 数量 OK,区间归属归属是规则细节,删 + +写 proposal 时问自己:**"这句话如果交给一个新加入的产品同学看,他能不能 5 秒内明白要干什么"**。能 = ✅;得回头查 §X.X.X 才能懂 = ❌,重写。 + **用户拍板回复后**:append `status:"complete"` 同 step + run_id 一行,gate 卡变 COMPLETED;然后才继续往下走(继续/回滚/调参)。 **两种 gate 的区分(约定)**: