框架: 升格管线三处韧性修复(模型输出str防御/failed窗重跑先撤销/连续2窗失败才停书)

This commit is contained in:
zizi 2026-07-15 10:25:10 +08:00
parent b7eb71b370
commit e697ee0233

View File

@ -408,10 +408,19 @@ def run(work_id, max_windows, max_calls, model, redo_window):
undo_window(conn, work_id, redo_window) undo_window(conn, work_id, redo_window)
else: else:
wins = conn.execute( wins = conn.execute(
"""SELECT window_no, from_chapter, to_chapter FROM example_upgrade_window """SELECT window_no, from_chapter, to_chapter, status FROM example_upgrade_window
WHERE tenant_id=%s AND work_id=%s AND status!='done' AND deleted=FALSE WHERE tenant_id=%s AND work_id=%s AND status!='done' AND deleted=FALSE
ORDER BY from_chapter""", (TENANT, work_id)).fetchall() ORDER BY from_chapter""", (TENANT, work_id)).fetchall()
wins = [(r[0], r[1], r[2]) for r in wins]
# failed 窗重跑前必须先撤销旧写入(部分写入直接重跑会 double-append 追加字段)
for r0 in conn.execute(
"""SELECT window_no FROM example_upgrade_window
WHERE tenant_id=%s AND work_id=%s AND status='failed' AND deleted=FALSE""",
(TENANT, work_id)).fetchall():
click.echo(f"[撤销] failed 窗{r0[0]} 旧写入回滚后重跑")
undo_window(conn, work_id, r0[0])
done_n = 0 done_n = 0
fail_streak = 0 # 连续失败计数单窗偶发失败网络瞬断continue连续 2 窗停书(系统性问题)
for win_no, a, b in wins: for win_no, a, b in wins:
if max_windows and done_n >= max_windows: if max_windows and done_n >= max_windows:
break break
@ -426,6 +435,9 @@ def run(work_id, max_windows, max_calls, model, redo_window):
# ① 观察 # ① 观察
obs, usage = call(observe_prompt(contracts, title, a, b, text, onstage), obs, usage = call(observe_prompt(contracts, title, a, b, text, onstage),
("新名字", "已知实体新信息", "纯出场")) ("新名字", "已知实体新信息", "纯出场"))
# 模型输出防御深空窗5实测列表元素偶为裸字符串.get 直接炸)——统一只留 dict 元素
for k in ("新名字", "已知实体新信息", "纯出场"):
obs[k] = [x for x in (obs.get(k) or []) if isinstance(x, dict)]
# ② 判重+立卡门槛(机械) # ② 判重+立卡门槛(机械)
to_update = {} # draft_id -> [观察点…] to_update = {} # draft_id -> [观察点…]
for ent in obs.get("新名字", []): for ent in obs.get("新名字", []):
@ -480,7 +492,7 @@ def run(work_id, max_windows, max_calls, model, redo_window):
cards.append((did, p, obs_pts)) cards.append((did, p, obs_pts))
upd, _ = call(update_prompt(contracts, title, a, b, text, cards), ("更新",)) upd, _ = call(update_prompt(contracts, title, a, b, text, cards), ("更新",))
valid = {d for d, _, _ in cards} valid = {d for d, _, _ in cards}
for u in upd.get("更新", []): for u in [x for x in (upd.get("更新") or []) if isinstance(x, dict)]:
if u.get("draft_id") in valid and (u.get("变更字段") or u.get("别名新增")): if u.get("draft_id") in valid and (u.get("变更字段") or u.get("别名新增")):
merge_card(conn, u["draft_id"], win_no, merge_card(conn, u["draft_id"], win_no,
u.get("变更字段"), u.get("别名新增")) u.get("变更字段"), u.get("别名新增"))
@ -512,7 +524,7 @@ def run(work_id, max_windows, max_calls, model, redo_window):
exist = {tuple(sorted((r.get("甲方draft"), r.get("乙方draft")))): (rid, r) exist = {tuple(sorted((r.get("甲方draft"), r.get("乙方draft")))): (rid, r)
for rid, r in rels for rid, r in rels
if r.get("甲方draft") and r.get("乙方draft")} if r.get("甲方draft") and r.get("乙方draft")}
for r in rel_out.get("关系", []): for r in [x for x in (rel_out.get("关系") or []) if isinstance(x, dict)]:
ja, yi = r.get("甲方"), r.get("乙方") ja, yi = r.get("甲方"), r.get("乙方")
if ja not in id2name or yi not in id2name or ja == yi: if ja not in id2name or yi not in id2name or ja == yi:
continue continue
@ -564,6 +576,7 @@ def run(work_id, max_windows, max_calls, model, redo_window):
click.echo(f"{win_no}✓ ({a}-{b}章) 在场{len(onstage)} 新名字{new_n} " click.echo(f"{win_no}✓ ({a}-{b}章) 在场{len(onstage)} 新名字{new_n} "
f"更新卡{len(to_update)} 调用累计{calls['n']}") f"更新卡{len(to_update)} 调用累计{calls['n']}")
done_n += 1 done_n += 1
fail_streak = 0
except SensitiveHardStop as e: except SensitiveHardStop as e:
with psycopg.connect(DSN) as conn: with psycopg.connect(DSN) as conn:
conn.execute( conn.execute(
@ -580,8 +593,14 @@ def run(work_id, max_windows, max_calls, model, redo_window):
WHERE tenant_id=%s AND work_id=%s AND window_no=%s""", WHERE tenant_id=%s AND work_id=%s AND window_no=%s""",
(str(e)[:500], TENANT, work_id, win_no)) (str(e)[:500], TENANT, work_id, win_no))
conn.commit() conn.commit()
click.echo(f" ✗ 窗{win_no} 失败(已记录,续跑会重试): {str(e)[:200]}") fail_streak += 1
return # 偶发失败(网络瞬断/单窗输出畸形跳去下一窗failed 窗留给续跑(自动先撤销);
# 连续 2 窗失败=系统性问题,停书防连环烧额度
if fail_streak >= 2:
click.echo(f" ⛔ 连续 {fail_streak} 窗失败,本书升格停(末窗{win_no}: {str(e)[:200]}")
return
click.echo(f" ✗ 窗{win_no} 失败(已记录,续跑重试;连续第{fail_streak}次): {str(e)[:200]}")
continue
click.echo(f"{title}》本次完成 {done_n}LLM 调用 {calls['n']}") click.echo(f"{title}》本次完成 {done_n}LLM 调用 {calls['n']}")