框架: 放量二修——出卡按型分批(百条线索全型聚类=认知超载退化为逐条转写,37章窗曾出74卡)+软删延后到有卡通过(防新卡全拒时旧卡净损失)+病症机械检测重试(空批/转写/丢实例键)
This commit is contained in:
parent
98118520c9
commit
df870b689f
@ -370,15 +370,9 @@ def cards(work_id, from_order, file_, model):
|
||||
(TENANT, work_id, from_order, to_order)).fetchall())
|
||||
contracts = {t: field_contract(conn, t) for t in PATTERN_TYPES}
|
||||
entity_names = load_entity_names(conn, work_id)
|
||||
# 幂等:重出本窗 = 软删本窗旧卡(0 卡保留旧卡,纪律同章级)
|
||||
if cards_in:
|
||||
conn.execute(
|
||||
"""UPDATE muse_knowledge_draft SET deleted=TRUE, updater=%s
|
||||
WHERE tenant_id=%s AND source_type='parse_book' AND source_id=%s
|
||||
AND draft_payload->>'窗起'=%s AND deleted=FALSE""",
|
||||
(ACTOR, TENANT, work_id, str(from_order)))
|
||||
sess = _session() # 嵌入通道(判重+新卡即时嵌入共用)
|
||||
ok, merged, rejected = 0, [], []
|
||||
# 先全量校验,收集通过集——软删旧卡延后到「确有新卡通过」才执行。
|
||||
# 教训(放量实测):软删在校验前时,新卡全拒(如 M3 整批丢实例键)=旧卡已删+新卡全拒=净损失
|
||||
passed, rejected = [], []
|
||||
for i, c in enumerate(cards_in):
|
||||
reasons, retyped = validate_card(c, contracts, entity_names, src)
|
||||
# 实例域校验:章号必须落在本窗内——越窗章号=编造(跨窗同手法靠嵌入判重归并)
|
||||
@ -412,6 +406,17 @@ def cards(work_id, from_order, file_, model):
|
||||
payload["跨窗待证"] = True # 审核环可见:结构装置只有单端实例
|
||||
if n_drop:
|
||||
payload["越窗实例弃"] = n_drop # 审计:M3 报了窗外章号
|
||||
passed.append((i, payload, inss))
|
||||
# 确有通过卡才软删本窗旧卡(幂等重出;全拒/空批不动旧卡——0 卡保护的窗级版)
|
||||
if passed:
|
||||
conn.execute(
|
||||
"""UPDATE muse_knowledge_draft SET deleted=TRUE, updater=%s
|
||||
WHERE tenant_id=%s AND source_type='parse_book' AND source_id=%s
|
||||
AND draft_payload->>'窗起'=%s AND deleted=FALSE""",
|
||||
(ACTOR, TENANT, work_id, str(from_order)))
|
||||
sess = _session() # 嵌入通道(判重+新卡即时嵌入共用)
|
||||
ok, merged = 0, []
|
||||
for i, payload, inss in passed:
|
||||
# ── 受控点①②:嵌入判重(失败不阻断——判重是增强不是红线,卡不能因通道故障丢)──
|
||||
embed_text = build_embed_text(payload)
|
||||
vec = None
|
||||
|
||||
@ -109,16 +109,20 @@ def scaffold_prompt(title, ch, ch_title, text, prev_entities):
|
||||
{text}"""
|
||||
|
||||
|
||||
def window_cards_prompt(title, a, b, stage_outline, ch_outlines, ch_hints, contracts):
|
||||
"""窗级聚类出卡 prompt(B4-S3 核心资产;金标准七条纪律全落于此)。"""
|
||||
def window_cards_prompt(title, a, b, stage_outline, ch_outlines, ch_hints, contracts, batch_note):
|
||||
"""窗级聚类出卡 prompt(B4-S3 核心资产;金标准七条纪律全落于此)。
|
||||
放量教训:百条线索一次全型聚类=认知超载,M3 退化为逐条转写(37 章窗出 74 张卡)——
|
||||
调用侧按型分批(batch_note 说明本批范围),每批线索几十条以内聚类才真实发生。"""
|
||||
return f"""【功能指令(parse-book 窗级聚类出卡+脱敏红线)】
|
||||
《{title}》第 {a}–{b} 章已完成章级分析。下面给你三层材料:阶段大纲、逐章细纲、逐章范式候选线索。
|
||||
《{title}》第 {a}–{b} 章已完成章级分析。下面给你三层材料:阶段大纲、逐章细纲、候选线索。
|
||||
{batch_note}
|
||||
任务:把候选线索**聚类归并**成「公共范式卡」——同一手法在多章的多次出现归并为一张母卡,带全部实例章号;线索不足处可从细纲的伏笔账(埋·推·收)补证跨章装置。
|
||||
|
||||
{render_contracts(contracts)}
|
||||
|
||||
出卡纪律:
|
||||
- 聚类优先:先把线索按「同一运作机制」分组(不同章的同类线索=同一卡的多个实例),再逐组判断是否值得出卡。一窗 0–6 张为常态,宁缺毋滥——聚不成组又不够突出的线索直接丢弃。
|
||||
- 聚类优先:先把线索按「同一运作机制」分组(不同章的同类线索=同一卡的多个实例),再逐组判断是否值得出卡。**本批最多 6 张,硬上限**——只出聚类后最能跨书复用的母卡;输出张数接近线索条数=你在逐条转写而不是聚类,整批作废。聚不成组又不够突出的线索直接丢弃。
|
||||
- **每张卡必须带 instances**(窗内章号+一句话定位);缺 instances 的卡=机械拒收。
|
||||
- 跨章证据:结构装置(伏笔类)尽量给出埋与收两端的实例章号;只有单章实例的会被标「跨窗待证」降低可信度。
|
||||
- 命名:卡名=作者口头会说的话(2–8 字,如「先抑后扬」「借刀杀人」),禁「XX式YY化ZZ」修饰语堆叠、禁书内专名、禁自造黑话。
|
||||
- 摘要:一句话**通用手法陈述**(抹掉本书信息后换任何题材仍成立),禁复述本书剧情、禁与实例定位雷同。
|
||||
@ -279,36 +283,46 @@ def cards(work_id, from_order, model, redo):
|
||||
ch_outlines = "\n".join(f"第{no}章《{ct}》:{o}" for no, ct, o, _ in rows)
|
||||
hints = [{"章": no, "线索": h} for no, _, _, hs in rows for h in (hs or [])]
|
||||
n_hint = len(hints)
|
||||
ch_hints = (json.dumps(hints, ensure_ascii=False, indent=1) if hints
|
||||
else "(各章均未报线索——只依据细纲伏笔账判断,出不了卡就给空数组)")
|
||||
try:
|
||||
p = window_cards_prompt(title, a, b, stage_ol, ch_outlines, ch_hints, contracts)
|
||||
data, usage = m3_json(p, model, ("cards",))
|
||||
total_in += usage.get("prompt_tokens", 0)
|
||||
total_out += usage.get("completion_tokens", 0)
|
||||
# 0 卡可疑重试(实测病:M3 输出方差大,同材料一次 10 卡一次秒回空数组):
|
||||
# 线索 ≥3 条却 0 卡=大概率分叉到偷懒路径,重试一次;仍 0 卡则接受(真无卡)
|
||||
if not (data.get("cards") or []) and n_hint >= 3:
|
||||
data, usage = m3_json(
|
||||
p + "\n\n【重试】上次输出了空数组,但本窗有 %d 条候选线索——请认真逐条聚类评估后再输出;"
|
||||
"确实全部不够格才允许空数组。" % n_hint, model, ("cards",))
|
||||
total_in += usage.get("prompt_tokens", 0)
|
||||
total_out += usage.get("completion_tokens", 0)
|
||||
_, out = ingest("cards", work_id, a, data, keyflag="--from-order")
|
||||
# 拒卡带原因重试一次:拒卡原因机械明确(占位符/越合同),带回 prompt 大概率可救;
|
||||
# 放量时每窗调用贵,救回率优先于省一次调用
|
||||
rej_lines = [ln for ln in out.splitlines() if ln.strip().startswith("[拒]")]
|
||||
if rej_lines:
|
||||
data, usage = m3_json(
|
||||
p + "\n\n【重试】上次输出有以下卡被机械校验拒收,逐条按原因修复后**重新输出全部卡**"
|
||||
"(被拒的修好,没被拒的原样保留):\n" + "\n".join(rej_lines),
|
||||
model, ("cards",))
|
||||
total_in += usage.get("prompt_tokens", 0)
|
||||
total_out += usage.get("completion_tokens", 0)
|
||||
_, out = ingest("cards", work_id, a, data, keyflag="--from-order")
|
||||
click.echo(f"win#{a}–{b}(线索 {n_hint} 条)\n {out}")
|
||||
except RuntimeError as e:
|
||||
click.echo(f"win#{a}–{b} 窗级 M3 失败: {e}")
|
||||
# 按型分批出卡(放量实测:百条线索一次全型聚类=认知超载,退化为逐条转写)——
|
||||
# 每批只给该型合同+该型线索,型内聚类真实可行;「?」型线索单独一批(五型合同全给,让 M3 先归型)
|
||||
by_type = {}
|
||||
for h in hints:
|
||||
t = h["线索"].get("型")
|
||||
by_type.setdefault(t if t in PATTERN_TYPES else "?", []).append(h)
|
||||
all_cards, win_in, win_out = [], 0, 0
|
||||
for t, hs in sorted(by_type.items()):
|
||||
sub_contracts = contracts if t == "?" else {t: contracts[t]}
|
||||
note = (f"本批只处理「{t}」型的 {len(hs)} 条线索(其余型另批处理),只出 {t} 型卡。"
|
||||
if t != "?" else
|
||||
f"本批是 {len(hs)} 条章级未归型的线索——先按各型判据归型,归不进五型的丢弃。")
|
||||
try:
|
||||
p = window_cards_prompt(title, a, b, stage_ol, ch_outlines,
|
||||
json.dumps(hs, ensure_ascii=False, indent=1), sub_contracts, note)
|
||||
data, usage = m3_json(p, model, ("cards",))
|
||||
win_in += usage.get("prompt_tokens", 0)
|
||||
win_out += usage.get("completion_tokens", 0)
|
||||
# 病症机械检测→带方重试一次(放量实测三病:方差空批/逐条转写/长清单丢实例键)
|
||||
cs = data.get("cards") or []
|
||||
no_ins = sum(1 for c in cs if not (c.get("instances") or c.get("实例")))
|
||||
sick = ("输出了空数组,但本批有 %d 条候选线索——认真逐条聚类评估后再输出,确实全部不够格才允许空数组"
|
||||
% len(hs) if (not cs and len(hs) >= 3) else
|
||||
"输出了 %d 张卡——这是逐条转写不是聚类。重新按「同一运作机制」分组归并,只出最能跨书复用的母卡,最多 6 张"
|
||||
% len(cs) if len(cs) > 8 else
|
||||
"有 %d 张卡缺 instances(窗内章号+定位)——instances 是必填键,缺=全部被机械拒收。重新输出全部卡并逐张补上"
|
||||
% no_ins if no_ins else None)
|
||||
if sick:
|
||||
data, usage = m3_json(p + "\n\n【重试】上次" + sick + "。", model, ("cards",))
|
||||
win_in += usage.get("prompt_tokens", 0)
|
||||
win_out += usage.get("completion_tokens", 0)
|
||||
cs = data.get("cards") or []
|
||||
all_cards += cs
|
||||
click.echo(f" 批[{t}] 线索{len(hs)} → 卡{len(cs)}")
|
||||
except RuntimeError as e:
|
||||
click.echo(f" 批[{t}] M3 失败: {e}(本批线索丢失,重跑 --redo 可补)")
|
||||
total_in += win_in
|
||||
total_out += win_out
|
||||
_, out = ingest("cards", work_id, a, {"cards": all_cards}, keyflag="--from-order")
|
||||
click.echo(f"win#{a}–{b}(线索 {n_hint} 条,分 {len(by_type)} 批)\n {out}")
|
||||
click.echo(f"《{title}》窗级出卡完成;token in={total_in:,} out={total_out:,}")
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user