框架: 抽检4高2中修复(前缀去重/出场章并入更新卡/关系落点统一/覆写回退拦截/别名准入/立卡剥括号)+存量回洗

This commit is contained in:
zizi 2026-07-15 10:42:58 +08:00
parent e697ee0233
commit b5b269f97b

View File

@ -168,7 +168,7 @@ def observe_prompt(contracts, title, a, b, text, onstage):
1) 新名字正文出现在场已知实体清单里没有的实体六型{"/".join(ENTITY_TYPES)}每个给名称别名一句话摘要按该型合同能填的字段有正文证据才填出场章号若你怀疑它其实是清单中某已知实体的别名/改名/化名疑似别名指向
2) 已知实体新信息清单中实体在本窗的实质新信息境界变化/性格显露/重大经历/立场转变一条 60 字观察点没有实质新信息的不要报
3) 纯出场清单中实体本窗出现但无实质新信息的只报名称+出场章
纪律一次性龙套单章无名或仅路过不报进新名字实体判据与字段以合同为准无证据不填不脑补
纪律一次性龙套单章无名或仅路过不报进新名字实体判据与字段以合同为准无证据不填不脑补数值战力/指数/排名等必须正文原样出现才可写禁止推算或编造组织改组/合并产生的新组织是**新实体**走新名字不是旧组织的别名疑似别名指向只在确为同一实体改名/化名时填
六型字段合同
{render_entity_contracts(contracts, ENTITY_TYPES)}
@ -192,10 +192,10 @@ def update_prompt(contracts, title, a, b, text, cards_with_obs):
for d, p, o in cards_with_obs], ensure_ascii=False, indent=1)
return f"""【功能指令(parse-book 作品面升格·卡增量更新)】
下列每张卡给出当前卡全文本窗观察点对照本窗正文**只输出需要变更的字段**
- 覆写类字段性格底色/说话方式/当前状态等标量输出更准确的新值整字段替换
- 追加类字段成长弧线/演变轨迹等数组只输出本窗新增条目不要重抄旧条目
- 覆写类字段性格底色/说话方式/当前状态等标量**必须输出该字段完整的新全量值**旧值里仍然成立的信息要保留进新值禁止只写"新增…"式增量那会把旧信息抹掉
- 追加类字段成长弧线/演变轨迹等数组只输出本窗新增条目不要重抄旧条目不要自己加 [窗N] 前缀系统会加
- 没有变化的字段不要输出整卡无实质变化则不输出该卡
纪律以正文为证据不脑补字段 key 必须来自该型合同每卡别名有新发现可在别名新增里给只收**该实体自己**的新别名他人对它的称呼算它对别的实体的称呼不算窗2实测魔鬼金是主角喊宠物机的诨名错报成了主角别名
纪律以正文为证据不脑补数值必须正文原样出现禁止推算字段 key 必须来自该型合同每卡别名有新发现可在别名新增里给只收**该实体自己**的新别名他人对它的称呼算它对别的实体的称呼不算且必须是可在正文原样出现的纯名字禁带括号注释与说明文字
相关型字段合同
{render_entity_contracts(contracts, sorted({p.get("type") for _, p, _ in cards_with_obs} & set(ENTITY_TYPES)))}
@ -241,8 +241,31 @@ def relation_prompt(contracts, title, a, b, text, char_cards, existing_rels):
# ── 合并与审计(三类字段演进 + 撤销依据)──
WIN_PREFIX_RE = None # 延迟编译(模块顶部 import re 已有)
def _strip_prefix(s):
"""剥条目行首的 [窗N]/[本窗] 前缀(可能多重堆叠),返回纯内容。"""
import re as _re
global WIN_PREFIX_RE
if WIN_PREFIX_RE is None:
WIN_PREFIX_RE = _re.compile(r"^(?:\[窗\d+\]\s*|\[本窗\]\s*)+")
return WIN_PREFIX_RE.sub("", str(s)).strip()
def _clean_alias(al):
"""别名机械准入(抽检#6拒收含括号注释/超长的备忘录式别名——预扫精确匹配永不命中=死数据。"""
al = (al or "").strip()
if not al or len(al) > 12:
return None
if any(c in al for c in "()。,,"):
return None
return al
def merge_card(conn, draft_id, win_no, changes, alias_new):
"""按 5.1 三类规则合并变更字段:数组/白名单=追加(条目带窗号),标量=覆写留审计。"""
"""按 5.1 三类规则合并变更字段:数组/白名单=追加(剥模型自带前缀+同文去重后带窗号),
标量=覆写留审计增量式假全量拦截转追加抽检#4 信息回退病)。"""
payload = conn.execute("SELECT draft_payload FROM muse_knowledge_draft WHERE id=%s",
(draft_id,)).fetchone()[0]
fields = payload.setdefault("字段", {})
@ -252,10 +275,20 @@ def merge_card(conn, draft_id, win_no, changes, alias_new):
[a for a in (changes.pop("别名新增", None) or []) if isinstance(a, str)]
for k, v in changes.items():
is_append = k in APPEND_FIELDS or isinstance(v, list) or isinstance(fields.get(k), list)
# 覆写值以增量口吻开头=模型把增量当全量(抽检#4新值会抹掉旧基线转追加不覆写
if not is_append and isinstance(v, str) and \
any(v.lstrip().startswith(w) for w in ("新增", "本窗", "另外", "此外")):
is_append = True
if is_append:
items = v if isinstance(v, list) else [v]
old = fields.get(k) if isinstance(fields.get(k), list) else ([fields[k]] if fields.get(k) else [])
fields[k] = old + [f"[窗{win_no}] {it}" for it in items if it]
seen = {_strip_prefix(x) for x in old} # 同文去重(抽检#1 重复病)
for it in items:
core = _strip_prefix(it) # 剥模型自带前缀(抽检#1 堆叠病)
if core and core not in seen:
old.append(f"[窗{win_no}] {core}")
seen.add(core)
fields[k] = old
else:
conn.execute(
"""INSERT INTO example_upgrade_audit
@ -265,17 +298,18 @@ def merge_card(conn, draft_id, win_no, changes, alias_new):
if fields.get(k) is not None else None,
json.dumps(v, ensure_ascii=False), TENANT))
fields[k] = v
for al in alias_new or []:
if al and al.strip() and al.strip() != payload.get("名称"):
for al0 in alias_new or []:
al = _clean_alias(al0)
if al and al != payload.get("名称"):
payload.setdefault("别名", [])
if al.strip() not in payload["别名"]:
payload["别名"].append(al.strip())
if al not in payload["别名"]:
payload["别名"].append(al)
conn.execute(
"""INSERT INTO example_upgrade_alias
(work_id, canonical_name, alias, evidence_window, verdict_by, tenant_id)
VALUES (%s,%s,%s,%s,'ai',%s)
ON CONFLICT (tenant_id, work_id, alias) DO NOTHING""",
(payload.get("_work_id") or 0, payload.get("名称"), al.strip(), win_no, TENANT))
(payload.get("_work_id") or 0, payload.get("名称"), al, win_no, TENANT))
conn.execute(
"""UPDATE muse_knowledge_draft SET draft_payload=%s, revision=revision+1,
updater='upgrade' WHERE id=%s""",
@ -289,9 +323,21 @@ def merge_card(conn, draft_id, win_no, changes, alias_new):
def new_card(conn, work_id, win_no, ent):
"""立初卡payload 全字段以旧值=NULL 入审计G7错认拆回可还原初始态"""
payload = {"type": ent[""], "名称": ent["名称"].strip(),
"别名": [a.strip() for a in ent.get("别名", []) if a and a.strip()],
"""立初卡payload 全字段以旧值=NULL 入审计G7错认拆回可还原初始态
名称剥括号注抽检#7 根因):「白色游魂(无名侦察兵)」这类名称使预扫精确匹配失明
主名之外的括号内容若像名字则转别名否则丢弃"""
import re as _re
raw = ent["名称"].strip()
m = _re.match(r"^(.+?)[(](.+?)[)]\s*$", raw)
extra_alias = []
if m:
raw = m.group(1).strip()
note = _clean_alias(m.group(2))
if note:
extra_alias.append(note)
payload = {"type": ent[""], "名称": raw,
"别名": [x for x in ([_clean_alias(a) for a in ent.get("别名", [])] + extra_alias) if x],
"一句话摘要": ent.get("一句话摘要", ""),
"字段": ent.get("字段", {}) or {},
"出场章": sorted(set(ent.get("出场章", []))),
@ -477,10 +523,14 @@ def run(work_id, max_windows, max_calls, model, redo_window):
VALUES (%s,%s,%s,%s,%s,%s,%s)""",
(work_id, win_no, ch, ent.get("", ""), nm,
ent.get("一句话摘要", ""), TENANT))
pres_add = {} # did -> set(出场章)——被更新的卡也要记出场章(抽检#2只走纯出场路径整窗丢章
for it in obs.get("已知实体新信息", []):
nm = (it.get("名称") or "").strip()
if nm in name_map and it.get("观察点"):
to_update.setdefault(name_map[nm][0], []).append(it["观察点"])
did = name_map[nm][0]
to_update.setdefault(did, []).append(it["观察点"])
pres_add.setdefault(did, set()).update(
c for c in (it.get("出场章") or []) if isinstance(c, int))
# ④ 卡更新分批≤6
items = sorted(to_update.items())
for i in range(0, len(items), UPDATE_BATCH):
@ -529,10 +579,28 @@ def run(work_id, max_windows, max_calls, model, redo_window):
if ja not in id2name or yi not in id2name or ja == yi:
continue
key = tuple(sorted((ja, yi)))
evo = f"[窗{win_no}] {r.get('本窗演变', '')}"
evo_core = _strip_prefix(r.get("本窗演变", ""))
if key in exist:
# 落点统一(抽检#3其他字段与演变轨迹全并进 rp["字段"]
# 顶层遗留的演变轨迹迁移进字段后删除,避免双份分裂
rid, rp = exist[key]
rp.setdefault("演变轨迹", []).append(evo)
f2 = rp.setdefault("字段", {})
if "演变轨迹" in rp: # 存量顶层迁移
legacy = rp.pop("演变轨迹")
base = f2.get("演变轨迹") or []
f2["演变轨迹"] = (base if isinstance(base, list) else [base]) + \
(legacy if isinstance(legacy, list) else [legacy])
for k2, v2 in (r.get("其他字段") or {}).items():
if k2 == "演变轨迹":
continue # 演变只走本窗演变通道
f2[k2] = v2 # 覆写(当前状态等保最新)
if evo_core:
lst = f2.setdefault("演变轨迹", [])
if not isinstance(lst, list):
lst = [lst]
f2["演变轨迹"] = lst
if evo_core not in {_strip_prefix(x) for x in lst}:
lst.append(f"[窗{win_no}] {evo_core}")
if r.get("关系类型"):
rp["关系类型"] = r["关系类型"]
conn.execute(
@ -540,12 +608,14 @@ def run(work_id, max_windows, max_calls, model, redo_window):
revision=revision+1 WHERE id=%s""",
(json.dumps(rp, ensure_ascii=False), rid))
else:
f2 = dict(r.get("其他字段") or {})
f2["演变轨迹"] = [f"[窗{win_no}] {evo_core}"] if evo_core else []
rp = {"type": RELATION_TYPE,
"名称": f"{id2name[ja]}×{id2name[yi]}",
"甲方draft": ja, "乙方draft": yi,
"甲方名称": id2name[ja], "乙方名称": id2name[yi],
"关系类型": r.get("关系类型", ""),
"演变轨迹": [evo], "字段": r.get("其他字段", {}) or {},
"字段": f2,
"来源": f"升格@窗{win_no}", "状态": "草稿",
"目标库": "本书作品库", "可见范围": "本书私有",
"_work_id": work_id}
@ -556,17 +626,20 @@ def run(work_id, max_windows, max_calls, model, redo_window):
VALUES (%s,'entity',%s,'pending',%s,%s,'upgrade','upgrade',%s)""",
(work_id, json.dumps(rp, ensure_ascii=False), SOURCE_TYPE,
work_id, TENANT))
# ⑥ 机械收尾:纯出场留档 + 窗置 done
# ⑥ 机械收尾:出场章统一并入(纯出场 + 被更新卡,抽检#2+ 窗置 done
for it in obs.get("纯出场", []):
nm = (it.get("名称") or "").strip()
if nm in name_map:
did = name_map[nm][0]
p = conn.execute("SELECT draft_payload FROM muse_knowledge_draft WHERE id=%s",
(did,)).fetchone()[0]
chs = sorted(set(p.get("出场章", [])) | set(it.get("出场章", [])))
p["出场章"] = chs
conn.execute("UPDATE muse_knowledge_draft SET draft_payload=%s WHERE id=%s",
(json.dumps(p, ensure_ascii=False), did))
pres_add.setdefault(name_map[nm][0], set()).update(
c for c in (it.get("出场章") or []) if isinstance(c, int))
for did, chs in pres_add.items():
if not chs:
continue
p = conn.execute("SELECT draft_payload FROM muse_knowledge_draft WHERE id=%s",
(did,)).fetchone()[0]
p["出场章"] = sorted(set(p.get("出场章", [])) | chs)
conn.execute("UPDATE muse_knowledge_draft SET draft_payload=%s WHERE id=%s",
(json.dumps(p, ensure_ascii=False), did))
conn.execute(
"""UPDATE example_upgrade_window SET status='done', error_message=NULL,
updater='upgrade' WHERE tenant_id=%s AND work_id=%s AND window_no=%s""",