框架: parse_llm试拆首轮对症四改——细纲绝对字数上限+超标压缩重试+source机械回填+归型五型均衡纪律(15章实测:scaffold全过/craft偏科待验)

This commit is contained in:
zizi 2026-07-13 16:14:38 +08:00
parent ee372f7ea2
commit 7be76b9ebb

View File

@ -10,6 +10,7 @@
"""
import json
import pathlib
import re
import subprocess
import sys
@ -54,12 +55,15 @@ CONTRACTS = {
def scaffold_prompt(title, ch, ch_title, text, prev_entities):
ents = json.dumps(prev_entities, ensure_ascii=False) if prev_entities else "(第一章,为空)"
# 绝对字数上限比抽象比例对 LLM 更可执行(首轮实测 M3 按比例会写到 9.8%–19%)
wc = len(text.replace("\n", "").replace(" ", ""))
cap = max(60, int(wc * 0.05))
return f"""【muse 创作实验台·拆书 2b·脚手架 pass】
{IDENTITY}
【功能指令(parse-book 逐章内环)】
对参考书《{title}》第 {ch} 章《{ch_title}》(正文附后):
1) 逆推本章细纲:章目标/关键事件/出场角色/伏笔动作(埋·推·收)/章末钩子。字数=章正文的 3–5%(3000字章→100–150字),硬顶 8%,超标会被校验脚本退回。细纲是结构骨架,不是缩写复述。
对参考书《{title}》第 {ch} 章《{ch_title}》(正文附后,约 {wc} 字):
1) 逆推本章细纲:章目标/关键事件/出场角色/伏笔动作(埋·推·收)/章末钩子。**细纲全文不得超过 {cap} 字**(章正文的 5%;超标会被校验脚本机械退回)。细纲是结构骨架,不是缩写复述——用短语与分号,不写完整句子。
2) 抽实体增量(脚手架级索引):{ENTITY_CRITERIA}
判重:下方是前文已收录实体,不重报(除非本章给出新身份,则在一句话摘要里并入)。
@ -83,7 +87,8 @@ def patterns_prompt(title, ch, ch_title, text, outline, existing_cards):
{json.dumps(CONTRACTS, ensure_ascii=False)}
纪律:
- 宁缺毋滥:一章 0–3 张为常态,只收本章表现突出、可跨书复用的写法;平庸章可出 0 张。
- 归型走判据;五型之外一律不出卡。
- 归型走判据;五型之外一律不出卡。**先按判据定 type,再只用该 type 字段表里的中文 key 填 fields——混用他型字段=机械拒卡**(如 trope 的「公式步骤」不得出现在 craft 卡里)。
- **五型都是候选,不要把一切归成 craft**:整场武力对抗→combat;整场情绪戏→emotion;拍卖/谈判/审讯等场景公式→scene_pattern;跨章复用的情节公式→trope;craft 只留给「单点装置」(删去它场景仍成立)。type 值必须与 fields 所用字段表同型。
- **脱敏红线**:只写抽象结构与手法归纳,严禁抄录原文;≥15 连续字与原文重合=校验脚本机械拒卡。卡名与字段值用「主角/强敌/导师」等抽象指代,**不得出现书内专名**(人名/地名/机甲名/组织名)——专名只允许出现在 source.anchor 里。
- fields 按该型字段合同的中文 key 填;没证据的 key 省略,不编造。
- 判重:下方已积累卡名录,不与之重复立同义卡。
@ -176,14 +181,24 @@ def main(work_id, from_, to, model):
# pass1 脚手架(断点续跑:done 跳过)
if s_st != "done" or outline is None:
try:
data, usage = m3_json(scaffold_prompt(title, ch, ch_title, text, prev), model,
("outline", "entities"))
p1 = scaffold_prompt(title, ch, ch_title, text, prev)
data, usage = m3_json(p1, model, ("outline", "entities"))
total_in += usage.get("prompt_tokens", 0)
total_out += usage.get("completion_tokens", 0)
ok, out = ingest("scaffold", work_id, ch, data)
# 比例超标是 M3 高频病:带压缩指令重试 1 次(首轮实测 10/15 章超标)
if not ok and "细纲比例" in out:
cap = max(60, int(len(re.sub(r"\s", "", text)) * 0.05))
data, usage = m3_json(
p1 + f"\n\n【重试】上次细纲 {len(data['outline'])} 字超标被退回。"
f"压缩到 {cap} 字以内:只留章目标/关键事件/伏笔动作/钩子,删掉一切修饰与过程描述。",
model, ("outline", "entities"))
total_in += usage.get("prompt_tokens", 0)
total_out += usage.get("completion_tokens", 0)
ok, out = ingest("scaffold", work_id, ch, data)
click.echo(f" {out}")
if not ok:
continue # 比例超标等已记 task failed,重跑本命令补
continue # 已记 task failed,重跑本命令补
outline = data["outline"]
except RuntimeError as e:
click.echo(f" #{ch} 脚手架 M3 失败: {e}")
@ -195,6 +210,13 @@ def main(work_id, from_, to, model):
("cards",))
total_in += usage.get("prompt_tokens", 0)
total_out += usage.get("completion_tokens", 0)
# source 机械回填:book/chapter 是调用侧确定数据,不依赖 LLM 自觉
#(实测 M3 偶发 source=null 整批被拒);anchor 是内容性字段,缺时用 brief 兜底
for c in data.get("cards") or []:
src = c.get("source") or {}
c["source"] = {"book": src.get("book") or title,
"chapter": src.get("chapter") or f"第{ch}章 {ch_title}",
"anchor": src.get("anchor") or c.get("brief") or ""}
_, out = ingest("patterns", work_id, ch, data)
click.echo(f" {out}")
except RuntimeError as e: