框架: README§九批6-7全线放量记录+compact恢复锚点;固化salvage/rewash脚本入仓
This commit is contained in:
parent
f9eddbb06b
commit
fd1628084d
129
.claude/skills/parse-book/scripts/parse_rewash.py
Normal file
129
.claude/skills/parse-book/scripts/parse_rewash.py
Normal file
@ -0,0 +1,129 @@
|
||||
#!/usr/bin/env python3
|
||||
"""存量回洗(一次性,抽检 2026-07-15 后):
|
||||
① 追加字段前缀堆叠/[本窗]残留清洗 + 同文去重(34+9 张卡)
|
||||
② 关系卡顶层演变轨迹迁移进 字段.演变轨迹(双份分裂修复)
|
||||
③ 出场章全量重建:卡名+别名 机械预扫全书正文(零 AI,比增量准)
|
||||
④ 脏别名清理:备忘录行/括号注释/错认两行(橡树妖/雷行天下)
|
||||
⑤ 占位值清理:字段值含「续写不可见」的删字段
|
||||
"""
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
|
||||
import psycopg
|
||||
|
||||
DSN = ("postgresql://root:f6710e2d0294eb1c10e26a805a64bc54@100.64.0.8:5433/muse-example"
|
||||
"?keepalives=1")
|
||||
TENANT = 1
|
||||
PREFIX_RE = re.compile(r"^(?:\[[窗本][^\]]{0,6}\]\s*)+") # 含 [窗本窗] 等模型自造变体
|
||||
WIN_NUMS = re.compile(r"\[窗(\d+)\]")
|
||||
|
||||
# 7 型合同合法 key(越合同 key 清洗用,运行时从库加载)
|
||||
VALID_KEYS = {}
|
||||
|
||||
|
||||
def load_valid_keys(conn):
|
||||
for (sk, snap) in conn.execute(
|
||||
"""SELECT s.schema_key, v.field_contract_snapshot FROM muse_meta_schema s
|
||||
JOIN muse_meta_schema_version v ON v.id=s.active_version_id
|
||||
WHERE s.tenant_id=1 AND s.schema_key IN
|
||||
('character','location','item','faction','power_system','event','character_relation')"""):
|
||||
VALID_KEYS[sk] = {f["key"] for f in snap.get("特有字段", [])} | {"一句话摘要", "演变轨迹"}
|
||||
|
||||
|
||||
def clean_list(items):
|
||||
"""剥堆叠前缀+同文去重;窗号取原条目最内层(最后一个),无窗号保持裸条目。"""
|
||||
out, seen = [], set()
|
||||
for it in items:
|
||||
s = str(it)
|
||||
core = PREFIX_RE.sub("", s).strip()
|
||||
if not core or core in seen:
|
||||
continue
|
||||
seen.add(core)
|
||||
nums = WIN_NUMS.findall(s[:len(s) - len(core)])
|
||||
out.append(f"[窗{nums[-1]}] {core}" if nums else core)
|
||||
return out
|
||||
|
||||
|
||||
def rebuild_presence(chapters, names):
|
||||
"""全书逐章扫描名字集合 → 出场章号列表。"""
|
||||
hits = set()
|
||||
for order_no, text in chapters:
|
||||
if any(nm in text for nm in names):
|
||||
hits.add(order_no)
|
||||
return sorted(hits)
|
||||
|
||||
|
||||
def main():
|
||||
stats = {"前缀清洗卡": 0, "关系迁移卡": 0, "出场章重建卡": 0, "删别名行": 0, "删占位字段": 0}
|
||||
with psycopg.connect(DSN) as conn:
|
||||
load_valid_keys(conn)
|
||||
for work_id in (4, 8):
|
||||
chapters = conn.execute(
|
||||
"""SELECT c.order_no, b.content_text FROM muse_content_chapter c
|
||||
JOIN muse_content_block b ON b.chapter_id=c.id AND b.deleted=FALSE
|
||||
WHERE c.tenant_id=%s AND c.work_id=%s AND c.deleted=FALSE
|
||||
ORDER BY c.order_no""", (TENANT, work_id)).fetchall()
|
||||
cards = conn.execute(
|
||||
"""SELECT id, draft_payload FROM muse_knowledge_draft
|
||||
WHERE tenant_id=%s AND work_id=%s AND source_type='upgrade_book'
|
||||
AND deleted=FALSE""", (TENANT, work_id)).fetchall()
|
||||
for did, p in cards:
|
||||
changed = False
|
||||
fields = p.get("字段") or {}
|
||||
# ② 关系卡顶层演变迁移
|
||||
if p.get("type") == "character_relation" and "演变轨迹" in p:
|
||||
legacy = p.pop("演变轨迹")
|
||||
base = fields.get("演变轨迹") or []
|
||||
fields["演变轨迹"] = (base if isinstance(base, list) else [base]) + \
|
||||
(legacy if isinstance(legacy, list) else [legacy])
|
||||
p["字段"] = fields
|
||||
changed = True
|
||||
stats["关系迁移卡"] += 1
|
||||
# ①⑤ 数组前缀清洗 + 占位字段删除 + 越合同畸形 key 清洗(窗29黄蜂针实测)
|
||||
vk = VALID_KEYS.get(p.get("type"))
|
||||
for k in list(fields.keys()):
|
||||
if vk is not None and k not in vk:
|
||||
del fields[k]
|
||||
changed = True
|
||||
stats["删越合同key"] = stats.get("删越合同key", 0) + 1
|
||||
continue
|
||||
v = fields[k]
|
||||
if isinstance(v, str) and "续写不可见" in v:
|
||||
del fields[k]
|
||||
changed = True
|
||||
stats["删占位字段"] += 1
|
||||
elif isinstance(v, list):
|
||||
nv = clean_list(v)
|
||||
if nv != v:
|
||||
fields[k] = nv
|
||||
changed = True
|
||||
stats["前缀清洗卡"] += 1
|
||||
# ③ 出场章全量重建(实体卡;关系卡无出场章语义)
|
||||
if p.get("type") != "character_relation":
|
||||
names = {p.get("名称", "")} | set(p.get("别名") or [])
|
||||
names = {n for n in names if n and len(n) >= 2} # 单字名误命中太多,跳过
|
||||
if names:
|
||||
nv = rebuild_presence(chapters, names)
|
||||
if nv and nv != p.get("出场章"):
|
||||
p["出场章"] = nv
|
||||
changed = True
|
||||
stats["出场章重建卡"] += 1
|
||||
if changed:
|
||||
conn.execute(
|
||||
"UPDATE muse_knowledge_draft SET draft_payload=%s, updater='rewash' WHERE id=%s",
|
||||
(json.dumps(p, ensure_ascii=False), did))
|
||||
# ④ 脏别名:备忘录/括号/超长 + 抽检定点两行错认
|
||||
r = conn.execute(
|
||||
"""UPDATE example_upgrade_alias SET deleted=TRUE, updater='rewash'
|
||||
WHERE tenant_id=%s AND deleted=FALSE AND (
|
||||
alias ~ '[(())。,:]' OR length(alias) > 12
|
||||
OR canonical_name ~ '[(())。,:]'
|
||||
OR alias IN ('橡树妖','雷行天下'))""", (TENANT,))
|
||||
stats["删别名行"] = r.rowcount
|
||||
conn.commit()
|
||||
print(json.dumps(stats, ensure_ascii=False))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
111
.claude/skills/parse-book/scripts/parse_salvage.py
Normal file
111
.claude/skills/parse-book/scripts/parse_salvage.py
Normal file
@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env python3
|
||||
"""顽固敏感章分段抢救(创始人 2026-07-15「定点处理遗留」)。
|
||||
|
||||
原理:内容安全拦截多为全文综合触发——把章正文对半拆成两段,各自独立走
|
||||
scaffold 抽取(含 M3→M2.7→deepseek 降级链),机械合并两半结果后走原守卫入库。
|
||||
每半的细纲上限=半章正文 5%,合并后自然满足全章 5% 上限(ingest 校验不变)。
|
||||
任一半降级链仍全败 → 该章保持 failed,不硬磕(不无限烧额度)。
|
||||
|
||||
用法:salvage --work-id N 或 salvage --all(跑全部 failed 章)
|
||||
"""
|
||||
import json
|
||||
import sys
|
||||
|
||||
import click
|
||||
import psycopg
|
||||
|
||||
sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parent))
|
||||
sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parents[2] / "llm" / "scripts"))
|
||||
from parse_llm import (m3_json, scaffold_prompt, ingest, SensitiveHardStop, # noqa: E402
|
||||
DSN, TENANT)
|
||||
|
||||
MODEL = "MiniMax-M3"
|
||||
|
||||
|
||||
def salvage_chapter(conn, work_id, title, ch, ch_title, text, prev):
|
||||
"""对半拆分两段抽取→机械合并→守卫入库。返回 (是否成功, 说明)。
|
||||
|
||||
极短章(<1500 字)不拆半:细纲 60 字下限×2 段会超过正文本身(星环#97 实测
|
||||
比例 113.7% 被守卫退回),整章单发重试一次。"""
|
||||
# 感言/公告章(极短+作者口吻):无正文可拆,诚实标注直接入库,零 LLM(星环#97 实测 116 字感言)
|
||||
plain = text.strip()
|
||||
if len(plain) < 500 and any(w in plain for w in ("作者", "感言", "请假", "推荐票", "月票", "补更", "上架")):
|
||||
ok, out = ingest("scaffold", work_id, ch,
|
||||
{"outline": "(作者感言/公告章,无正文内容)", "entities": [], "hints": []})
|
||||
return ok, "感言章标注入库" if ok else out[-120:]
|
||||
if len(text) < 1500:
|
||||
halves = [text]
|
||||
else:
|
||||
mid = len(text) // 2
|
||||
# 对半点对齐段落边界(往后找最近换行,避免句子拦腰斩)
|
||||
cut = text.find("\n", mid)
|
||||
cut = cut if cut != -1 else mid
|
||||
halves = [text[:cut], text[cut:]]
|
||||
outlines, ents, hints = [], {}, []
|
||||
n = len(halves)
|
||||
for i, half in enumerate(halves, 1):
|
||||
seg_note = f"(第{i}/{n}段)" if n > 1 else ""
|
||||
p = scaffold_prompt(title, ch, f"{ch_title}{seg_note}", half, prev)
|
||||
try:
|
||||
data, _ = m3_json(p, MODEL, ("outline", "entities", "hints"))
|
||||
except SensitiveHardStop:
|
||||
return False, f"第{i}/2段降级链仍全败"
|
||||
outlines.append(str(data.get("outline") or "").strip())
|
||||
for e in data.get("entities") or []:
|
||||
if isinstance(e, dict):
|
||||
nm = (e.get("名称") or e.get("name") or "").strip()
|
||||
if nm and nm not in ents:
|
||||
ents[nm] = e
|
||||
hints.extend(h for h in (data.get("hints") or []) if isinstance(h, dict))
|
||||
outline = ";".join(o for o in outlines if o)
|
||||
# 合并后超 8% 硬顶(降级模型偶超标,星环#1664 实测 11%):按「;」从尾机械砍段到 7% 留余量
|
||||
import re as _re
|
||||
wc = len(_re.sub(r"\s", "", text))
|
||||
cap = max(60, int(wc * 0.07))
|
||||
while len(_re.sub(r"\s", "", outline)) > cap and ";" in outline:
|
||||
outline = outline.rsplit(";", 1)[0]
|
||||
merged = {"outline": outline,
|
||||
"entities": list(ents.values()), "hints": hints}
|
||||
ok, out = ingest("scaffold", work_id, ch, merged)
|
||||
return ok, out[-120:]
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option("--work-id", type=int, default=0, help="只跑指定书(0=全部)")
|
||||
@click.option("--limit", type=int, default=0, help="最多抢救几章(0=不限)")
|
||||
def main(work_id, limit):
|
||||
with psycopg.connect(DSN) as conn:
|
||||
cond = "AND t.work_id=%s" % work_id if work_id else ""
|
||||
rows = conn.execute(f"""
|
||||
SELECT t.work_id, w.title, c.order_no, c.title, b.content_text
|
||||
FROM example_parse_task t
|
||||
JOIN muse_content_chapter c ON c.id=t.chapter_id
|
||||
JOIN muse_content_block b ON b.chapter_id=c.id AND b.deleted=FALSE
|
||||
JOIN muse_content_work w ON w.id=t.work_id
|
||||
WHERE t.scaffold_status='failed' AND c.deleted=FALSE {cond}
|
||||
ORDER BY t.work_id, c.order_no""").fetchall()
|
||||
click.echo(f"待抢救 failed 章:{len(rows)}")
|
||||
saved = failed = 0
|
||||
for wid, title, ch, ch_title, text in rows[:limit or None]:
|
||||
# 前文实体名清单(复用章级压缩口径)
|
||||
with psycopg.connect(DSN) as conn:
|
||||
prev = [e for (es,) in conn.execute(
|
||||
"""SELECT s.entities FROM example_parse_scaffold s
|
||||
JOIN muse_content_chapter c ON c.id=s.chapter_id
|
||||
WHERE s.tenant_id=%s AND s.work_id=%s AND c.order_no<%s AND s.deleted=FALSE""",
|
||||
(TENANT, wid, ch)).fetchall() for e in es]
|
||||
try:
|
||||
ok, note = salvage_chapter(conn, wid, title, ch, ch_title, text, prev)
|
||||
except Exception as e: # 单章异常不挡后续章
|
||||
ok, note = False, f"异常: {str(e)[:100]}"
|
||||
if ok:
|
||||
saved += 1
|
||||
click.echo(f" ✓ 救回 {title}#{ch}")
|
||||
else:
|
||||
failed += 1
|
||||
click.echo(f" ✗ 仍败 {title}#{ch}: {note}")
|
||||
click.echo(f"=== 抢救收尾:救回{saved} / 仍败{failed} ===")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -237,6 +237,7 @@ flowchart LR
|
||||
- **B2 全本拆书·限流分批(2026-07-14 创始人拍板:暂停→分批,每批 ≤3000 次请求、批间隔 5 小时)**:首夜 5 路并行拆至 425–544/书(合计 ~2570 章落库)后按指令暂停。**限流纪律**:请求含重试保守全计,每批按 ~2700 章排(章级=每章 1 次调用+少量病症重试;窗级出卡=每窗 5–8 次);批间隔 ≥5 小时,宁晚勿早。**批1 定时失败→09:09 脱离补跑**:sleep 18000 定时挂在 Claude Code 后台任务上,上会话退出被带走→06:40 未开跑、零请求(额度未亏);改 nohup+disown 脱离会话补跑五路(深空→594/机动→673/超神→1455/星环→1722/机战→827,共 2700 章,断点续跑跳已完成章,日志 /tmp/muse-b1-*.log)。**跨会话根因未除**:本地后台长任务随 Claude Code 会话消亡,彻底可靠需放 mini-desktop tmux/nohup(待定);`pkill -f parse_llm` 模式串会连带杀定时器。**批2**=机战章级收口(→2901)+四书窗大纲续切+余量开出卡;批3+=出卡/终检/审核/样张,每批开跑前按预算精排。**shell 教训**:定时批命令 `A && B & C &` 中 C 不等 A(`&` 分组陷阱),必须 `A && { B & C & wait; }`——首挂曾致 4 书抢跑约 1 分钟,当场杀掉,烧个位数请求。**章级完成后的续跑序**:窗大纲续切(`window --to 末章`——机动已有 win1-37/深空 win1-36/其余至 50,续跑自动从缺口接;换窗参数才需清窗行)→ 分型出卡 `cards --redo 不带`(断点续跑跳已出窗)→ `check` 终检 → `review` 审核 → `export` 样张呈报。
|
||||
- **B2 停跑·缓存与敏感优化前置(2026-07-14 创始人拍板)**:创始人点破**限次数是果不是因**——每次请求 token 太贵→同配额可用次数缩水→被迫连砍额度(4000→3000→1000→500);省 token=省次数一根链,先前把两者当并列选项是错判。停跑时批2实发280(机动16/深空43/超神81/星环67/机战73),**其中66次(23%)被上游内容安全 new_sensitive(1026) 拦截空耗**(机动/星环暴力战争情节,每敏感章重试3次)。双重漏水:①缓存没吃到(scaffold_prompt/window_cards_prompt 把 title/章号/cap/batch_note 变量前置,破坏 read-context 规定的"稳定度递减吃前缀缓存")②敏感空耗23%。**优化落地前不重启批次**,三前置(均省次数):①缓存重排(固定规则前置、正文/章号/大纲尾置;窗级合同+金标准纪律上千字是最大靶,拆书自拼消息不受"阶段一子代理不能共享缓存"豁免)②拆书补敏感换模型/跳过机制(清洗侧已有先例)③读 token 明细验 M3+New-API 真支持缓存(usage 带 *_tokens_details 疑含 cached_tokens,需1次诊断)。方案待出给创始人。
|
||||
- **B2 优化落地·批3重启(2026-07-14 实测纠正预判)**:①**真凶是前文实体膨胀,不是缓存**——章级 prompt 把前文实体全档(型+名+摘要)累积重发,深空557章2851实体≈15万token/章占输入97%、随章号雪崩;缓存只缓存"每次一样的固定前缀"、救不了每章都变的实体清单,此路错判。②**压缩(主力)**:实体索引全档json→名字清单(判重只需名字),每章16万→2万token砍86%(深空500实测14万→1.95万,细纲/实体/线索质量正常),同预算次数×7-8,直达创始人目标2-3000。③**缓存诊断**:M3经New-API自动缓存恒命中128token(其内部模板,与我方内容无关);换 Anthropic /v1/messages+cache_control 仍 cache_creation=0(M3上游不支持显式缓存);M2.7/deepseek=0——要吃缓存须换模型,但压缩后缓存仅省固定前缀1k小头,不值当;提示词已缓存重排(固定前置)备将来换模型。④**敏感降级链**:撞 new_sensitive→MiniMax-M2.7→deepseek-v4-flash→全失败硬停,实测 M2.7 救回深空/机动密集敏感章(深空连测3章皆敏感)。**批3=500次自停闸**(wrapper PID精确kill+计数 grep [llm]|[敏感] 含敏感失败请求防超支,哨兵盯收尾)。**教训**:创始人两次纠偏单一归因(省钱=省次数是一根链;缓存/压缩/检索是多重方向非二选一)。检索召回(名字清单再压)留后续。
|
||||
- **批6-7 全线放量+质量闭环(2026-07-15,compact恢复锚点)**:批6(2400闸→1211章,敏感救回423,超神/星环/机战大步收口)。批7(创始人放量5000,中途令牌余额见底403中断→充值恢复):**章级 7344/7345 收官**(顽固59章分段抢救救回57、感言章#97/坏章#811诚实标注入库;唯一余量见库);fable5抽检(事实4.4/合同3.8/生长2.8/归并4.0,判"需修后再放量")→**4高2中全修+两轮存量回洗**(前缀去重/出场章全书重扫重建312卡/关系落点统一57卡/覆写回退拦截/越合同key裁剪38处/别名准入);定点修复7调用全过(4卡audit旧值合并/黄蜂针净化剥雷行天下/泰加+白色游魂2对并卡)。**关键机制修复(勿回退)**:max_tokens=512000(创始人拍板不设限;New-API按max_tokens预扣费,余额须≥并发×$0.154否则403)+chat()撞模型上限自适应降档(M2.7上限196608实测)+细纲守卫60字绝对豁免+升格failed窗重跑先撤销+连续2窗失败才停书+模型输出str防御。**compact时在跑(nohup脱离会话,勿重启)**:升格路(机动52+/80窗→深空116窗,日志/tmp/muse-b7-upgrade.log)+出卡双路(5书窗大纲续切→分型出卡,/tmp/muse-b7-cards[AB].log)+全局守护闸4700(/tmp/muse-b7-cards-main.log出STOP行=收尾)。跑完续跑序:check终检→review审核→export样张+升格样张(机动全书+深空末段压测数据)呈报创始人。固化脚本:parse_salvage.py(顽固章分段抢救)/parse_rewash.py(存量回洗)/parse_upgrade.py(作品面升格)。待创始人:样张过目、B3检索验证、B5确认门。
|
||||
- **批3-5 分批拆(2026-07-14,创始人逐批给额度)**:批3(501次→322章)、批4(1000次→572章,529升5%)、**批5 收尾(2000次自停零超支→1133章:星环250/机战510/超神373;M2.7救回281章,0硬停,529降至1.7%)**。压缩+敏感降级实测有效。**章级全局 5500/7345=75%**:机动673✓/深空593(仅#143)/超神1438(差17)/星环1194/机战1602。**待办**:批6待创始人发额度(剩余约1845章≈2100次可全收口章级)、深空#143顽固敏感(备用模型疑也撞,单独攻)、机动/深空章级已完成可进窗大纲+出卡。续跑序:章级完→window切窗大纲→cards分型出卡→check终检→review审核→export样张。
|
||||
- **参考书作品面数据入库(2026-07-14 创始人指令+同日认可开工)**:「这几本书本身也是被导入的作品,对应的数据也都需要入库」——**方案 v6 定稿并已批准:`docs/2026-07-14-参考书作品面数据入库方案.md`**。四轮独立评审+创始人五条反馈换核,核心机制=**全实体统一生长**(废v4「前15全卡/轻卡」分层——创始人质疑成立):一实体一卡(唯一键=作品×型×归一名×范围,范围恒「本书私有」),每窗「机械预扫在场实体→实体观察→判重(留档/别名→嵌入近邻→AI终判)→卡更新(只对有新信息,批≤6张)→关系增量」,字段三类演进(底色覆写留审计/演进追加带窗号/当前态保最新);两阶段合并:拆书期候选卡就地累积,创作期走变更提案(draft表entity_id/proposed_changes/snapshot三列原生支持);正文窗直抽(3-5万字/窗,同窗调用连发吃缓存);防膨胀双设计(索引=窗内机械预扫命中集不随书涨/观察输出超12新名或15章对半分段)。**5张升格工作表已建**(`db/ddl/94`:别名/出场留档/窗状态/卡水位/覆写审计)。作品面抽取估3000-4800次,**总额闸5000封顶**,试跑校准;全链从2026-07-14起约5900-8400次。**下一步**:首书机动风暴~10窗+深空末段3窗压测出样张→创始人过目→放量。M3无思考确认(实测reasoning_tokens=0,中位out=331),维持不开(质量已达标,思考按输出计费烧次数,深推理单点用强模型)。
|
||||
- **未决**:①机动残留变体与感言章处理方式②跨书同功母卡归并=放量后公共面课题③S5 参数 A/B(M3 输出方差根治口)④S6 pacing 书级抽取(等整本拆完,依赖全本+作品面入库)。
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user