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:
wangsenhao
2026-05-26 15:42:02 +08:00
parent a09d4afacd
commit b48032e15c
5 changed files with 119 additions and 9 deletions
+85
View File
@@ -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]],