框架: 升格串行铁律——失败当场重试不跳窗+迟到覆写闸+缺键宽容

创始人拍板:窗串行生长,失败不得跳窗后补(断层+覆盖风险)。
- run 窗循环改 while:失败当场撤销半写入同窗重试一次,仍败停书(断点=本窗)
- merge_card 加水位闸:win_no<卡水位的覆写只留审计不动卡体(防补跑时间倒流)
- 水位更新改 GREATEST 防倒退
- 更新批缺'更新'键宽容为空批(prompt 明确空批输出+代码兜底,6窗实测死因)
This commit is contained in:
zizi 2026-07-15 21:22:54 +08:00
parent 4960dfdf7a
commit 0ac8f7ce04

View File

@ -200,7 +200,7 @@ def update_prompt(contracts, title, a, b, text, cards_with_obs):
相关型字段合同
{render_entity_contracts(contracts, sorted({p.get("type") for _, p, _ in cards_with_obs} & set(ENTITY_TYPES)))}
输出规则只输出一个 JSON 对象
输出规则只输出一个 JSON 对象即使本批所有卡都无变化也必须输出 {{"更新": []}}不得省略"更新"
{{"更新": [{{"draft_id": 数字, "变更字段": {{"字段key": "新值或新增条目数组"}}, "别名新增": []}}]}}
本窗材料
@ -272,6 +272,12 @@ def merge_card(conn, draft_id, win_no, changes, alias_new, valid_keys=None):
payload = conn.execute("SELECT draft_payload FROM muse_knowledge_draft WHERE id=%s",
(draft_id,)).fetchone()[0]
fields = payload.setdefault("字段", {})
# 卡水位该卡最后一次被更新的窗号补跑迟到窗如窗41在窗80后补跑的覆写类字段
# 若直接落卡会把书末态倒写回中期态时间倒流污染2026-07-15 补跑实测坐实)。
# 闸门win_no < 水位 ⇒ 覆写只留审计不动卡体;追加类带窗号标签乱序无害,不拦。
wm_row = conn.execute("SELECT watermark_window FROM example_upgrade_card_state WHERE draft_id=%s",
(draft_id,)).fetchone()
watermark = wm_row[0] if wm_row else 0
changes = dict(changes or {})
# 模型偶把「别名新增」混进变更字段窗2实测摘出来并入别名流程不落卡体字段
alias_new = list(alias_new or []) + \
@ -301,6 +307,15 @@ def merge_card(conn, draft_id, win_no, changes, alias_new, valid_keys=None):
old.append(f"[窗{win_no}] {core}")
seen.add(core)
fields[k] = old
elif win_no < watermark:
# 迟到覆写弃用:本窗时序早于卡已生长到的窗位,覆写会让卡态倒流——
# 只留审计(标记可查),卡体保持高窗态不动
conn.execute(
"""INSERT INTO example_upgrade_audit
(draft_id, window_no, field_name, old_value, new_value, tenant_id)
VALUES (%s,%s,%s,NULL,%s,%s)""",
(draft_id, win_no, ("迟到覆写弃用:" + str(k))[:100],
json.dumps(v, ensure_ascii=False)[:2000], TENANT))
else:
conn.execute(
"""INSERT INTO example_upgrade_audit
@ -329,7 +344,9 @@ def merge_card(conn, draft_id, win_no, changes, alias_new, valid_keys=None):
conn.execute(
"""INSERT INTO example_upgrade_card_state (draft_id, work_id, watermark_window, tenant_id)
VALUES (%s,%s,%s,%s)
ON CONFLICT (draft_id) DO UPDATE SET watermark_window=EXCLUDED.watermark_window,
ON CONFLICT (draft_id) DO UPDATE
SET watermark_window=GREATEST(example_upgrade_card_state.watermark_window,
EXCLUDED.watermark_window),
update_time=now()""",
(draft_id, payload.get("_work_id") or 0, win_no, TENANT))
@ -478,8 +495,14 @@ def run(work_id, max_windows, max_calls, model, redo_window):
click.echo(f"[撤销] failed 窗{r0[0]} 旧写入回滚后重跑")
undo_window(conn, work_id, r0[0])
done_n = 0
fail_streak = 0 # 连续失败计数单窗偶发失败网络瞬断continue连续 2 窗停书(系统性问题)
for win_no, a, b in wins:
# 串行铁律(创始人 2026-07-15 拍板):窗与窗是串行生长——后窗的预扫/判重/更新
# 全依赖前窗长成的卡。窗失败绝不跳窗(跳窗=知识断层+事后补跑有覆盖风险),
# 而是当场撤销半写入→整窗重试一次(挡偶发病);仍败→停书,断点就在本窗,
# 下次启动从本窗续跑failed 先撤销机制),串行语义天然无损。
wi = 0
retried = False # 当前窗是否已当场重试过
while wi < len(wins):
win_no, a, b = wins[wi]
if max_windows and done_n >= max_windows:
break
if max_calls and calls["n"] >= max_calls:
@ -552,7 +575,16 @@ def run(work_id, max_windows, max_calls, model, redo_window):
p = conn.execute("SELECT draft_payload FROM muse_knowledge_draft WHERE id=%s",
(did,)).fetchone()[0]
cards.append((did, p, obs_pts))
upd, _ = call(update_prompt(contracts, title, a, b, text, cards), ("更新",))
# 缺"更新"键宽容为空批prompt 教"无变化的卡不输出",某批恰好全无变化时
# 模型会顺势连键一起省批7实测 6 窗全死于此)。语义上缺键≈空批,按空批放行
# 并留警告日志可审计;其余格式错误(乱码/解析失败)仍原样抛、窗照 fail。
try:
upd, _ = call(update_prompt(contracts, title, a, b, text, cards), ("更新",))
except RuntimeError as e:
if "缺少必需键" not in str(e):
raise
print(f"[宽容] 窗{win_no} 更新批缺键按空批放行: {str(e)[:80]}", file=sys.stderr)
upd = {"更新": []}
valid = {d for d, _, _ in cards}
# 每卡按其型的合同 key 集校验(+一句话摘要),越合同 key 裁剪留审计
did2keys = {d: {f["key"] for f in contracts.get(p.get("type"), {}).get("字段", [])}
@ -665,7 +697,8 @@ def run(work_id, max_windows, max_calls, model, redo_window):
click.echo(f"{win_no}✓ ({a}-{b}章) 在场{len(onstage)} 新名字{new_n} "
f"更新卡{len(to_update)} 调用累计{calls['n']}")
done_n += 1
fail_streak = 0
wi += 1
retried = False
except SensitiveHardStop as e:
with psycopg.connect(DSN) as conn:
conn.execute(
@ -682,14 +715,17 @@ def run(work_id, max_windows, max_calls, model, redo_window):
WHERE tenant_id=%s AND work_id=%s AND window_no=%s""",
(str(e)[:500], TENANT, work_id, win_no))
conn.commit()
fail_streak += 1
# 偶发失败(网络瞬断/单窗输出畸形跳去下一窗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
if not retried:
# 串行铁律第一层:当场撤销半写入→同窗立即重试(挡网络瞬断/偶发格式病)
with psycopg.connect(DSN) as conn:
undo_window(conn, work_id, win_no)
conn.commit()
retried = True
click.echo(f" ↻ 窗{win_no} 失败,撤销后当场重试(串行铁律不跳窗): {str(e)[:150]}")
continue
# 第二层重试仍败:停书,断点=本窗;下次启动 failed 先撤销、从本窗续跑
click.echo(f" ⛔ 窗{win_no} 当场重试仍失败,本书升格停(断点=本窗,续跑从此接续): {str(e)[:200]}")
return
click.echo(f"{title}》本次完成 {done_n}LLM 调用 {calls['n']}")