feat: backend gate fallback + metric_diff docs + submit_sft fix
- Auto-inject human-review gate when agent asks question but forgets gate entry - Add complex_dev/complex_base field docs to program.md metric_diff table - Fix submit_sft.sh minor issues - jupyter_runtime improvements Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -2031,6 +2031,9 @@ def create_app(state: AgentState) -> FastAPI:
|
||||
session_id,
|
||||
)
|
||||
run_active = _is_run_active(state, account_id, session_id)
|
||||
state_entries = _maybe_inject_gate_fallback(
|
||||
state_path, state_entries, sessions_dir, session_id, run_active,
|
||||
)
|
||||
_apply_program_state(
|
||||
payload,
|
||||
state_entries,
|
||||
@@ -2104,6 +2107,9 @@ def create_app(state: AgentState) -> FastAPI:
|
||||
session_id,
|
||||
)
|
||||
run_active = _is_run_active(state, account_id, session_id)
|
||||
state_entries = _maybe_inject_gate_fallback(
|
||||
state_path, state_entries, sessions_dir, session_id, run_active,
|
||||
)
|
||||
_apply_program_state(
|
||||
payload,
|
||||
state_entries,
|
||||
@@ -6606,6 +6612,85 @@ def _compute_in_flight(
|
||||
}
|
||||
|
||||
|
||||
_QUESTION_PATTERN = re.compile(
|
||||
r'[??]|拍板|选择.*哪|决定|确认.*吗|建议.*哪|要不要|是否|请.*选|你.*决定|三个选项|哪个方案|哪条路'
|
||||
)
|
||||
|
||||
|
||||
def _last_assistant_text(sessions_dir: Path, session_id: str | None) -> str | None:
|
||||
if not session_id:
|
||||
return None
|
||||
safe_id = _safe_session_id(session_id)
|
||||
if safe_id is None:
|
||||
return None
|
||||
path = sessions_dir / safe_id / 'session.json'
|
||||
try:
|
||||
data = json.loads(path.read_text(encoding='utf-8'))
|
||||
except (OSError, json.JSONDecodeError):
|
||||
return None
|
||||
messages = data.get('messages')
|
||||
if not isinstance(messages, list):
|
||||
return None
|
||||
for msg in reversed(messages):
|
||||
if not isinstance(msg, dict):
|
||||
continue
|
||||
if msg.get('role') != 'assistant':
|
||||
continue
|
||||
content = msg.get('content')
|
||||
if isinstance(content, str) and content.strip():
|
||||
return content.strip()
|
||||
if isinstance(content, list):
|
||||
for block in content:
|
||||
if isinstance(block, dict) and block.get('type') == 'text':
|
||||
text = block.get('text', '').strip()
|
||||
if text:
|
||||
return text
|
||||
return None
|
||||
|
||||
|
||||
def _maybe_inject_gate_fallback(
|
||||
state_path: Path,
|
||||
state_entries: list[dict[str, Any]],
|
||||
sessions_dir: Path,
|
||||
session_id: str | None,
|
||||
run_active: bool | None,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""If agent's last message asks a question but no gate is running,
|
||||
append a synthetic human-review entry to program-state.jsonl."""
|
||||
if run_active is not False:
|
||||
return state_entries
|
||||
for entry in reversed(state_entries):
|
||||
step = entry.get('step')
|
||||
if step in ('human-check', 'human-review') and entry.get('status') == 'running':
|
||||
return state_entries
|
||||
last_text = _last_assistant_text(sessions_dir, session_id)
|
||||
if not last_text:
|
||||
return state_entries
|
||||
if not _QUESTION_PATTERN.search(last_text[-500:]):
|
||||
return state_entries
|
||||
run_id = None
|
||||
for entry in reversed(state_entries):
|
||||
rid = entry.get('run_id')
|
||||
if isinstance(rid, str) and rid.strip():
|
||||
run_id = rid.strip()
|
||||
break
|
||||
gate_entry: dict[str, Any] = {
|
||||
'step': 'human-review',
|
||||
'status': 'running',
|
||||
'run_id': run_id or 'R0',
|
||||
'reason': last_text[-300:],
|
||||
'ts': datetime_utc_iso(),
|
||||
'_synthetic': True,
|
||||
}
|
||||
try:
|
||||
with open(state_path, 'a', encoding='utf-8') as f:
|
||||
f.write(json.dumps(gate_entry, ensure_ascii=False) + '\n')
|
||||
except OSError:
|
||||
pass
|
||||
state_entries.append(gate_entry)
|
||||
return state_entries
|
||||
|
||||
|
||||
def _apply_program_state(
|
||||
payload: dict[str, Any],
|
||||
state_entries: list[dict[str, Any]],
|
||||
|
||||
@@ -1926,6 +1926,9 @@ function SkillInsertDialog() {
|
||||
className="min-w-0 flex-1 text-left"
|
||||
onClick={() => {
|
||||
insertComposerText(`Use the ${skill.name} skill.\n\n`);
|
||||
if (skill.name === "model-iteration") {
|
||||
dispatchSkillToggled({ skill: skill.name, enabled: true });
|
||||
}
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -219,8 +219,10 @@ for k in prev:
|
||||
|
||||
| 字段 | 含义 |
|
||||
| --- | --- |
|
||||
| `origin_predict_base` | 旧模型原始输出。具体格式(是否含 `complex=` 前缀、tag 包装、自定义 class 名等)以当前评测产出为准,**分析前 head 一下实物** |
|
||||
| `origin_predict_dev` | 新模型原始输出。同上,格式以当前产出为准 |
|
||||
| `origin_predict_base` | 旧模型(baseline)原始输出。具体格式(是否含 `complex=` 前缀、tag 包装、自定义 class 名等)以当前评测产出为准,**分析前 head 一下实物** |
|
||||
| `origin_predict_dev` | 新模型(迭代模型)原始输出。同上,格式以当前产出为准 |
|
||||
| `complex_dev` | 迭代模型的 complex 标签(true/false),从 `origin_predict_dev` 解析出的复杂度判定 |
|
||||
| `complex_base` | baseline 模型的 complex 标签(true/false),从 `origin_predict_base` 解析出的复杂度判定 |
|
||||
| `cleaned_predict_*` | 清洗后输出,用于 GSB 对比 |
|
||||
| `label` / `code_label_base` | ground truth;`label` 为空时回退解析 `code_label_base` |
|
||||
|
||||
|
||||
@@ -19,7 +19,9 @@ SFT_RUNDIC=${1:?usage: $0 <SFT_RUNDIC> [PREV_RUNDIC]}
|
||||
PREV_RUNDIC=${2:-$((SFT_RUNDIC-1))}
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT=${AUTORESEARCH_ROOT:-/mnt/wangsenhao/autoresearch-zk}
|
||||
# chat-isolated 时代 AUTORESEARCH_ROOT == AUTORESEARCH_CHAT_ROOT;优先用 chat
|
||||
# root,避免 agent 没 export AUTORESEARCH_ROOT 时回退到全局共享路径(已废弃)。
|
||||
ROOT=${AUTORESEARCH_ROOT:-${AUTORESEARCH_CHAT_ROOT:-$(dirname "$SCRIPT_DIR")}}
|
||||
TPL=${SFT_TRAIN_JOB_TEMPLATE:-$SCRIPT_DIR/sft_train_job.yaml.tpl}
|
||||
YAML=/tmp/sft_train_job_r${SFT_RUNDIC}.yaml
|
||||
|
||||
|
||||
+24
-6
@@ -297,14 +297,32 @@ class JupyterRuntimeSession:
|
||||
if not self.skills_root:
|
||||
return
|
||||
chat_root = self.chat_workspace_root
|
||||
src = f'{self.skills_root}/model-iteration/scripts/prepare_and_train_sft.py'
|
||||
dst = f'{chat_root}/scripts/prepare_and_train_sft.py'
|
||||
result = self.run_command(
|
||||
(
|
||||
# SKILL/program §5 让 agent 直接 `bash scripts/<x>` 调下面这些脚本,
|
||||
# 不同步过去 → agent 当 "脚本不存在" 处理后会肉手写 yaml,常把
|
||||
# imageCommand 里的 `/scripts/prepare_and_train_sft.py` 写丢前缀,
|
||||
# 导致训练 pod 报 "No such file or directory"。每次 bind 全量覆盖
|
||||
# 一份,跟 prepare_and_train_sft.py 同样 "刷最新版"。
|
||||
script_names = (
|
||||
'prepare_and_train_sft.py',
|
||||
'submit_sft.sh',
|
||||
'sft_train_job.yaml.tpl',
|
||||
'submit_cml_eval.sh',
|
||||
'resolve_run_ids.sh',
|
||||
)
|
||||
src_dir = f'{self.skills_root}/model-iteration/scripts'
|
||||
dst_dir = f'{chat_root}/scripts'
|
||||
copy_cmds = []
|
||||
for name in script_names:
|
||||
src = f'{src_dir}/{name}'
|
||||
dst = f'{dst_dir}/{name}'
|
||||
copy_cmds.append(
|
||||
f'if [ -f {shlex.quote(src)} ]; then '
|
||||
f'cp {shlex.quote(src)} {shlex.quote(dst)}; '
|
||||
f'cp {shlex.quote(src)} {shlex.quote(dst)} && '
|
||||
f'chmod +x {shlex.quote(dst)} 2>/dev/null || true; '
|
||||
f'fi'
|
||||
),
|
||||
)
|
||||
result = self.run_command(
|
||||
' && '.join(copy_cmds),
|
||||
timeout_seconds=timeout_seconds,
|
||||
max_output_chars=2000,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user