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:
王森浩
2026-05-26 14:12:04 +08:00
7 changed files with 103 additions and 32 deletions
+51 -17
View File
@@ -1768,12 +1768,11 @@ def create_app(state: AgentState) -> FastAPI:
if isinstance(run_id, str) and run_id.strip():
rid = run_id.strip()
per_round = _resolve_run_dic_per_round(state_entries)
# augment 处理的是「上一轮评测的错例」,产物按 R{n-1} 的 runDic 命名
# data_clean_<R{n-1}>/、augment_<R{n-1}>.jsonl ...),不是本轮 eval workflow
# augment 产物的 runDic = resolve_run_ids.sh 在当轮 eval 落盘后取到的
# max(已落盘 workflow) = 当轮 eval 自身的 workflow id,即 per_round[R{n}]
# (不是 R{n-1},脚本取的是 eval 之后的 max,此时 max 已经包含当轮 eval)
if step == 'augment':
m = re.match(r'^[Rr](\d+)$', rid)
if m and int(m.group(1)) >= 1:
run_dic = per_round.get(f'R{int(m.group(1)) - 1}')
pass # 直接走下面 per_round.get(rid) 取本轮 runDic
if run_dic is None:
run_dic = per_round.get(rid)
if run_dic is None:
@@ -6395,9 +6394,20 @@ def _build_pipeline_items(
for c in sec['cards']:
c.pop('_order', None)
# 4. Build round items in display order.
# Order: by run_index ascending, then within same round: train before analysis.
section_order = {'baseline': 0, 'train': 0, 'analysis': 1}
# 3b. If a gate is running for a given run_id, downgrade running cards
# in that run to 'waiting' (the step is blocked on human review).
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]] = []
for (run_id, section_type), sec in sections.items():
run_index = _parse_run_index(run_id)
@@ -6415,7 +6425,7 @@ def _build_pipeline_items(
'description': _SECTION_DESCRIPTIONS[section_type],
'status': round_status,
'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.
@@ -6499,7 +6509,9 @@ def _compute_in_flight(
seen_keys: set[str] = set()
for idx in range(len(state_entries) - 1, -1, -1):
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
step = entry.get('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:
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]] = {}
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
step = entry.get('step')
if not isinstance(step, str) or not step:
@@ -6559,6 +6584,8 @@ def _compute_in_flight(
status = entry.get('status')
if status not in ('complete', 'running'):
continue
if status == 'running' and gate_running_for_run:
status = 'waiting'
meta = _CARD_META.get((step, target_section), {
'icon': 'clock',
'title': step,
@@ -6634,7 +6661,9 @@ def _apply_program_state(
# whenever a running section is hidden.
latest_per_step: dict[str, str] = {}
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
step = entry.get('step')
status = entry.get('status')
@@ -6655,15 +6684,19 @@ def _apply_program_state(
statuses = list(latest_per_step.values())
if not statuses:
payload['status']['state'] = 'pending'
elif non_gate_running:
elif non_gate_running and not gate_running:
payload['status']['state'] = 'running'
elif gate_running:
# Only HiTL gates are 'running' — agent has handed control back to the
# user. Surface as 'waiting' so the header pill says "等待人工" instead
# of the spinning "Running" badge.
# HiTL gate is running — agent has handed control back to the user.
# Even if a parent step (e.g. augment) is still technically 'running',
# the gate blocks progress. Surface as 'waiting' so the header pill
# says "等待人工" instead of the spinning "Running" badge.
payload['status']['state'] = 'waiting'
elif all(s == 'complete' for s in statuses):
payload['status']['state'] = 'complete'
if run_active:
payload['status']['state'] = 'running'
else:
payload['status']['state'] = 'complete'
elif 'complete' in statuses:
payload['status']['state'] = 'running'
elif 'failed' in statuses:
@@ -6702,6 +6735,7 @@ def _apply_program_state(
and payload['status']['state'] == 'running'
and running_steps
and not all_watched
and not gate_running
):
session_failure_status = 'failed'