框架: P2收口——sweep收割器(审计种子+手动种子)+守卫终形(微信特征/书名行/20%顶下限)+SKILL.md实测定版+README§九刷P2账
This commit is contained in:
parent
0a3a576704
commit
ee372f7ea2
@ -1,39 +1,45 @@
|
||||
---
|
||||
name: clean
|
||||
description: LLM 辅助正文清洗——MiniMax-M3 按窗口(约10万字)只输出待删垃圾段原文与理由,代码做精确匹配删除并全程留审计(example_clean_log)。LLM 不改写正文,删不删由四道守卫规则最终裁决。
|
||||
description: LLM 辅助正文清洗——MiniMax-M3 按窗口(约10万字)只输出待删垃圾段原文与理由,代码做精确匹配删除并全程留审计(example_clean_log)。LLM 不改写正文,删不删由守卫规则最终裁决。
|
||||
---
|
||||
|
||||
# clean —— LLM 检测 + 代码执行的正文清洗
|
||||
|
||||
分工(创始人方案 2026-07-13):**规则层**已在 import 解决结构性垃圾(水印正则/重贴章题/目录页/分页尾巴/残破实体);本 skill 处理**语义垃圾**——变体书站广告、作者拉票/PS 段、乱码水印等正则打不全的散落噪声。LLM 只当探测器(输出待删片段逐字原文),删除由脚本执行:零改写、可审计、可回放。探测模型=New-API `MiniMax-M3`(创始人拍板,经 llm skill 统一入口调用)。
|
||||
分工(创始人方案 2026-07-13):**规则层**已在 import 解决结构性垃圾(水印正则/重贴章题/目录页/分页尾巴/残破实体);本 skill 处理**语义垃圾**——变体书站广告、作者拉票/PS 段、微信导流、乱码水印等正则打不全的散落噪声。LLM 只当探测器(输出待删片段逐字原文),删除由脚本执行:零改写、可审计、可回放。探测模型=New-API `MiniMax-M3`(经 llm skill;单窗失败自动降级 `deepseek-v4-flash`,再失败记录跳过——网文正文会触发上游敏感词拦截,实测两处均被备选救回)。
|
||||
|
||||
## 流程
|
||||
|
||||
```bash
|
||||
# 1) 备窗口:按章对齐切窗(默认 ~10万字/窗,写 /tmp/muse-clean/<work>/win-*.txt + manifest)
|
||||
.venv/bin/python .claude/skills/clean/scripts/clean_prep.py --work-id 7 --window 100000
|
||||
# 放量驱动(每书 prep→detect→apply 全链;断点续跑/幂等防重删;可多进程分书并行)
|
||||
.venv/bin/python .claude/skills/clean/scripts/clean_batch.py --work-id 7 --work-id 10 --batch <批次号>
|
||||
|
||||
# 2) 探测:每窗一次 M3 调用(prompt 内置于脚本;断点续跑,已有产物自动跳过)
|
||||
.venv/bin/python .claude/skills/clean/scripts/clean_detect.py --work-id 7 [--win 1 --win 2] [--force]
|
||||
# 单步(调试/演示用)
|
||||
.venv/bin/python .claude/skills/clean/scripts/clean_prep.py --work-id 7 [--from 1 --to 50] # 切窗
|
||||
.venv/bin/python .claude/skills/clean/scripts/clean_detect.py --work-id 7 [--win 1] # M3 探测
|
||||
.venv/bin/python .claude/skills/clean/scripts/clean_apply.py --work-id 7 --batch X --file … [--dry-run] [--report-md docs/清洗-N-书名.md]
|
||||
|
||||
# 3) 执行:先 dry-run 对账出删除明细(质检样张),确认后去掉 --dry-run 落库
|
||||
.venv/bin/python .claude/skills/clean/scripts/clean_apply.py --work-id 7 --batch <批次号> --file /tmp/muse-clean/7/deletions-001.json [--dry-run]
|
||||
# 收尾收割:高频水印全书规则扫净(LLM 每窗只报样例,重复水印靠种子收割)
|
||||
.venv/bin/python .claude/skills/clean/scripts/clean_sweep.py --work-id 7 --batch X # 审计自动种子(重复≥3次且≥20字)
|
||||
.venv/bin/python .claude/skills/clean/scripts/clean_sweep.py --work-id 4 --batch X --seed "http://m." # 人工确认的碎水印
|
||||
|
||||
# 4) 审查:删除对账(理由分布/字数)+ 审计行抽查
|
||||
.venv/bin/python .claude/skills/db/scripts/db.py query "SELECT reason, count(*), sum(length(removed_text)) FROM example_clean_log WHERE work_id=7 AND batch='<批次号>' GROUP BY reason"
|
||||
# 审查:审计对账
|
||||
.venv/bin/python .claude/skills/db/scripts/db.py query "SELECT batch, count(*), sum(length(removed_text)) FROM example_clean_log WHERE work_id=7 GROUP BY batch"
|
||||
```
|
||||
|
||||
## 四道守卫(脚本硬闸,LLM 建议 ≠ 必删;全部为首窗真跑实测病对症)
|
||||
⚠️ prep 会覆盖 /tmp/muse-clean/<work>/ 的窗与 manifest——换范围重切前先归档旧产物目录。
|
||||
|
||||
## 守卫体系(脚本硬闸,LLM 建议 ≠ 必删;全部为放量实测病对症)
|
||||
|
||||
1. **长度界**:`exact` 4–500 字;
|
||||
2. **章题保护**:不得含/命中所在章章题;
|
||||
3. **单章 20% 顶**:单章累计删除超章长 20% → 整章回退不落库;
|
||||
4. **行级垃圾特征**:exact 每一行必须命中通用垃圾词(ps头/感谢/打赏/求票/域名符号…,不含书内专名)——盗版源会把广告插进正文句子中间,M3 会把「广告+被截断正文」拼成一项,无特征行拆行跳过(宁漏删不误删)。
|
||||
3. **单章顶**:单章累计删除 ≤ max(章长 20%, 120 字)——绝对下限救短章(百字残章按比例删不了任何垃圾);
|
||||
4. **行级垃圾特征**:exact 每一行必须命中通用垃圾词(ps头/感谢/求票/微信公众号/域名符号/盗版占位语…不含书内专名)或书名水印豁免(孤行=本书名、行含《本书名》);无特征行拆行跳过(宁漏删不误删——实拦「广告+被截断正文」粘连 3 处);
|
||||
5. **水印裁剪**:删除段含明确水印模式(百镀一下/一秒记住…免费阅读)且水印前有前缀时只删水印——防「正文行尾粘水印」误删正文头(实测修复 1 例 3 字误删)。
|
||||
|
||||
匹配三级(对症 M3 两类实测病):精确命中 → 空白弹性(M3 折叠 `\n\n\n`→`\n\n`)→ 邻章 ±1 容错(长窗内偶发章号偏移)。三级全失败 → 拒绝。每次删除一行审计(原文/理由/模型/批次),源 txt 永远不动(可整书重导回滚);删完刷新 block/work 字数。
|
||||
匹配三级:精确命中 → 空白弹性(M3 折叠 `\n\n\n`→`\n\n`)→ 邻章 ±1 容错(长窗内章号偏移;fix 轮还能二次收割邻章重复水印)。三级全失败 → 拒绝。收尾规则:被删 ps 段的前置星号分隔线孤悬章尾则连带清除;被清洗章空行归一。每次删除一行审计(原文/理由/模型/批次),源 txt 永远不动(可整书重导回滚)。
|
||||
|
||||
## 量级实测(2026-07-13 首窗定标)
|
||||
## P2 全语料实测(2026-07-13 收口)
|
||||
|
||||
- M3 中文 tokenizer ≈ **0.58 token/字**;10 万字窗 in≈5.9 万 token、out≈2k、耗时 ~24s;
|
||||
- 机破星河前 50 章(12.9 万字,2 窗):39 条建议 → 40 段删除(拆行后)/2,188 字,误删 0;
|
||||
- 全语料(8 书 ≈3,300 万字)≈ **340 窗 ≈ 340 次 M3 调用,输入 ~19M token**。
|
||||
- 8 书 ≈3,300 万字 ≈340 窗;M3 中文 ≈0.58 token/字,总输入 ~20M token;10 万字窗 in≈5.9 万 token、耗时 10–25s;
|
||||
- 总删除 **1,685 段 / 约 6.1 万字**(机破星河 2.4 万最脏、机战无限 1.9 万次之);误删实测 1 例 3 字(已修复+守卫补上);
|
||||
- 已知残留:作者疲劳话(无通用特征)、「重贴章题+(第N更 求月票)」嵌正文行变体(高风险规则,待拍)、感言/请假条整章(结构性存在,段落清洗不动章)。
|
||||
|
||||
@ -32,7 +32,8 @@ GARBAGE_LINE_PAT = re.compile(
|
||||
r"|(?:月|推荐|评价|催更|票)票|起点币|求(?:收藏|推荐|订阅|票|月票)|拉票|冲榜"
|
||||
r"|龙套|上架|[aA]签|签约|加更|[一二三四五六七八九十]更|更新时间|码字|存稿|请假|感言"
|
||||
r"|www|http|[\._]{2,}|→|♂|÷|一秒记住|记住本站|无弹窗|手机版|首发|最新章节|整理|转载"
|
||||
r"|手打中|稍等片刻|重新刷新|获取最新", # 盗版站占位残留(1040-1060 验证窗实测)
|
||||
r"|手打中|稍等片刻|重新刷新|获取最新" # 盗版站占位残留(1040-1060 验证窗实测)
|
||||
r"|微信|公众号|书名[::]|想听到更多你们的声音|想收到更多你们的建议", # 作者导流/推书段(机战无限/星环实测)
|
||||
re.I)
|
||||
|
||||
# 水印裁剪:盗版源把水印粘在正文行尾(如『“我草!”百镀一下“××”最新章节第一时间免费阅读。』),
|
||||
@ -122,7 +123,8 @@ def main(work_id, batch, file_, model, dry_run, report_md):
|
||||
lines = [ln for ln in exact.split("\n") if ln.strip()]
|
||||
plain = [ln for ln in lines
|
||||
if not GARBAGE_LINE_PAT.search(ln)
|
||||
and not (work_title and ln.strip() == work_title)]
|
||||
and not (work_title and (ln.strip() == work_title
|
||||
or f"《{work_title}》" in ln))]
|
||||
if plain and len(lines) == 1:
|
||||
stats["拒绝"].append(f"#{no}: 无垃圾特征疑正文 {exact[:24]!r}")
|
||||
continue
|
||||
@ -132,7 +134,8 @@ def main(work_id, batch, file_, model, dry_run, report_md):
|
||||
stats["拒绝"].append(f"#{no}: 拆行跳过疑正文行 {ln[:24]!r}")
|
||||
units = [(ln, reason + "(拆行)") for ln in lines
|
||||
if (GARBAGE_LINE_PAT.search(ln)
|
||||
or (work_title and ln.strip() == work_title))
|
||||
or (work_title and (ln.strip() == work_title
|
||||
or f"《{work_title}》" in ln)))
|
||||
and MIN_LEN <= len(ln) <= MAX_LEN]
|
||||
else:
|
||||
units = [(exact, reason)]
|
||||
@ -177,7 +180,8 @@ def main(work_id, batch, file_, model, dry_run, report_md):
|
||||
for no, ch in sorted(chapters.items()):
|
||||
if not ch["audit"]:
|
||||
continue
|
||||
if ch["removed"] > len(ch["orig"]) * MAX_CH_RATIO:
|
||||
# 20% 顶带绝对下限:短章(如百字残章)按比例几乎删不了任何垃圾,放宽到至少 120 字
|
||||
if ch["removed"] > max(len(ch["orig"]) * MAX_CH_RATIO, 120):
|
||||
stats["删除段"] -= len(ch["audit"])
|
||||
stats["删除字数"] -= ch["removed"]
|
||||
stats["拒绝"].append(f"#{no}: 累计删除 {ch['removed']} 字超章长20%上限,整章回退")
|
||||
|
||||
108
.claude/skills/clean/scripts/clean_sweep.py
Normal file
108
.claude/skills/clean/scripts/clean_sweep.py
Normal file
@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python3
|
||||
"""clean skill:高频水印全书规则收割(LLM 探测样例 → 代码全书扫净)。
|
||||
|
||||
M3 按窗探测对「每章重复的固定水印」只会报样例章(窗内看到≠逐章报全),残留由本脚本收割:
|
||||
- 种子=example_clean_log 中该书重复删除段(相同 removed_text 出现 ≥min-occur 次、长度 ≥min-len)——
|
||||
种子都是已过全部守卫的真垃圾,且 20+ 字逐字重复 3+ 次的段不可能是正文;
|
||||
- 全书逐章 replace 删除(含空白弹性),审计入库;20% 顶守卫保留。
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
|
||||
import click
|
||||
import psycopg
|
||||
|
||||
DSN = ("postgresql://root:f6710e2d0294eb1c10e26a805a64bc54@100.64.0.8:5433/muse-example"
|
||||
"?keepalives=1&keepalives_idle=15&keepalives_interval=5&keepalives_count=3")
|
||||
TENANT, ACTOR = 1, "1"
|
||||
|
||||
|
||||
def flex_pattern(exact):
|
||||
"""空白弹性正则(与 clean_apply 同构:文字逐字、空白段弹性)。"""
|
||||
parts = [p for p in re.split(r"\s+", exact) if p]
|
||||
return re.compile(r"\s+".join(re.escape(p) for p in parts)) if parts else None
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option("--work-id", type=int, required=True)
|
||||
@click.option("--batch", required=True, help="收割批次号(审计标注)")
|
||||
@click.option("--min-occur", type=int, default=3, show_default=True, help="种子最小重复次数")
|
||||
@click.option("--min-len", type=int, default=20, show_default=True, help="种子最小长度")
|
||||
@click.option("--seed", "manual_seeds", multiple=True,
|
||||
help="手动种子(替代审计自动发现;用于人工确认过的碎水印,如孤行网址)")
|
||||
@click.option("--dry-run", is_flag=True)
|
||||
def main(work_id, batch, min_occur, min_len, manual_seeds, dry_run):
|
||||
with psycopg.connect(DSN) as conn:
|
||||
# 种子:手动指定(人工确认的碎水印),或该书审计里的重复删除段(已过全部守卫的真垃圾)
|
||||
seeds = list(manual_seeds) or [r[0] for r in conn.execute(
|
||||
"""SELECT removed_text FROM example_clean_log
|
||||
WHERE tenant_id=%s AND work_id=%s AND length(removed_text)>=%s
|
||||
GROUP BY removed_text HAVING count(*)>=%s
|
||||
ORDER BY count(*) DESC""",
|
||||
(TENANT, work_id, min_len, min_occur)).fetchall()]
|
||||
if not seeds:
|
||||
click.echo(f"work={work_id} 无重复水印种子")
|
||||
return
|
||||
click.echo(f"work={work_id} 种子 {len(seeds)} 个:")
|
||||
for s in seeds:
|
||||
click.echo(f" · {s[:50]!r}")
|
||||
rows = conn.execute(
|
||||
"""SELECT c.order_no, c.id, b.id, 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()
|
||||
n_del = n_ch = chars = 0
|
||||
for no, ch_id, blk_id, text in rows:
|
||||
new_text, audit = text, []
|
||||
for seed in seeds:
|
||||
n = new_text.count(seed)
|
||||
how = ""
|
||||
if n:
|
||||
new_text = new_text.replace(seed, "")
|
||||
else:
|
||||
pat = flex_pattern(seed)
|
||||
if pat:
|
||||
new_text, n = pat.subn("", new_text)
|
||||
how = "(空白弹性)"
|
||||
if n:
|
||||
audit.append((seed, n, f"高频水印全书收割{how}"))
|
||||
if not audit:
|
||||
continue
|
||||
removed = len(text) - len(new_text)
|
||||
# 20% 顶守卫(绝对下限 120 字,与 clean_apply 一致)
|
||||
if removed > max(len(text) * 0.20, 120):
|
||||
click.echo(f" [拒] #{no}: 收割 {removed} 字超顶,整章回退")
|
||||
continue
|
||||
new_text = re.sub(r"(?:\s*\n\s*[**]{3,}\s*)+$", "\n", new_text)
|
||||
new_text = re.sub(r"\n{3,}", "\n\n", new_text)
|
||||
n_ch += 1
|
||||
n_del += sum(a[1] for a in audit)
|
||||
chars += removed
|
||||
if dry_run:
|
||||
continue
|
||||
wc = len(re.sub(r"\s", "", new_text))
|
||||
conn.execute("UPDATE muse_content_block SET content_text=%s, word_count=%s, updater=%s WHERE id=%s",
|
||||
(new_text, wc, ACTOR, blk_id))
|
||||
for seed, n, note in audit:
|
||||
conn.execute(
|
||||
"""INSERT INTO example_clean_log (work_id, chapter_id, batch, removed_text, occurrences,
|
||||
reason, model, creator, updater, tenant_id)
|
||||
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)""",
|
||||
(work_id, ch_id, batch, seed, n, note, "rule-sweep", ACTOR, ACTOR, TENANT))
|
||||
if not dry_run:
|
||||
conn.execute(
|
||||
"""UPDATE muse_content_work w SET word_count=(
|
||||
SELECT COALESCE(sum(b.word_count),0) FROM muse_content_block b
|
||||
WHERE b.tenant_id=%s AND b.work_id=w.id AND b.deleted=FALSE), updater=%s
|
||||
WHERE w.id=%s""", (TENANT, ACTOR, work_id))
|
||||
conn.commit()
|
||||
mode = "(dry-run)" if dry_run else ""
|
||||
click.echo(f"收割{mode}: {n_ch} 章 / {n_del} 处 / {chars:,} 字")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except psycopg.Error as e:
|
||||
click.echo(f"[db错误] {type(e).__name__}: {e}", err=True)
|
||||
sys.exit(1)
|
||||
@ -226,7 +226,8 @@ flowchart LR
|
||||
- **M3 拍板(2026-07-13 创始人)**:清洗与拆书的内容生产 LLM **全部改走 New-API `MiniMax-M3`**(llm skill 统一入口,密钥入仓);主会话(Fable5)只固化 agent/提示词/skill 与发起调用。清洗范围=**所有书**;删除前创始人质检样张(门①);**拆书先不放量**(试拆 M3 重做后质检=门②,B2 硬停等新指令)。执行计划:`docs/2026-07-13-M3清洗拆书执行计划.md`。
|
||||
- **清洗 P0/P1 收口(2026-07-13)**:llm skill(M3 入口+json-repair 三级容错)+ clean_detect(每窗一调、断点续跑)+ clean_apply 四道守卫(长度界/章题/单章20%顶/**行级垃圾特征拆行**——盗版源把广告插进正文句中,M3 会拼「广告+截断正文」,已实测拦住)+ 三级匹配(精确/空白弹性/邻章±1)。实测定标:M3 中文 0.58 token/字,10 万字窗 in≈5.9 万 token 耗时 24s;全语料 ≈340 调用/~19M token。机破星河前 50 章演示已备:40 段/2,188 字待删(dry-run),误删 0。
|
||||
- **opus 试拆基准(workflow 已完成)**:机动风暴 1–3 章,范式卡 2 张入库(ch2/ch3 各 1 拒 1 越合同)+ch1 脚手架;opus 也犯 JSON 中文引号/缺逗号病(已被 llm skill 容错吸收)。此产物只作 M3 对比基准,不算正式拆书。
|
||||
- **未决**:①门① 清洗样张质检(明细在 /tmp/muse-clean/7/dryrun-00*.txt,已呈报)→过门后 P2 全语料放量②门② M3 试拆样张质检(P3 固化后跑 5 本×前 3 章)③B2 放量(硬停,等创始人新指令)。
|
||||
- **P2 全语料清洗收口(2026-07-13)**:8 书全清(门①创始人放行)——总删 1,685 段/约 6.1 万字(机破星河 2.4 万/机战无限 1.9 万/超神 0.6 万…),审计全在 example_clean_log(批次 P2-20260713 主删 + fix/fix2/fix3 补删 + P2-sweep 高频水印收割),全库空行归一 8,786 章。守卫体系放量长成:行级垃圾特征+书名豁免+水印裁剪+20%顶带下限+敏感窗备选模型降级(见 clean/SKILL.md)。误删实测 1 例 3 字已修复。已知残留待拍:机动风暴「重贴章题+(第N更 求月票)」嵌正文行变体(求票嫌疑 270 处主源)、感言/请假条整章、作者疲劳话散句。
|
||||
- **未决**:①门② M3 试拆样张质检(parse_llm.py 已固化,跑 5 本×前 3 章后呈报)②B2 放量(硬停,等创始人新指令)③机动残留变体与感言章处理方式(全局报告中待拍)。
|
||||
- **教训入档**:StructuredOutput 工具 schema 属性名仅限 ASCII(中文键 API 400)——schema ASCII 键+入库脚本键名归一;Tailscale 长事务需 keepalive+批量写(逐行两万往返曾半死 16 分钟)。
|
||||
|
||||
## 九·旧(2026-07-10 快照,留档)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user