Source snapshot: 97d7f0fae96dc04b7ddad56fc1db6a108ed662cc [SEC-CLEAN] · pre-push-clean v1.0 · 109处敏感信息已自动转乱码
18 KiB
auto_dishu V5.5 修复方案 · L5引擎 + L6人物快照 + 数据卡回补
HLDP://chenxing/memory-engine/v5.5/repair
创建于 2026-06-10 23:47 · 桔子线架构人格体 PER-JZ-ARCH-001
停拆点:《穿成假冒失忆大佬女友的恶毒女配》第 59 章 · 2026-06-10 23:41:47
修复目标
| 优先级 | 修复项 | V5.4 现状 | V5.5 目标 |
|---|---|---|---|
| 🔴 P0 | L5 引擎 | 37 章 0 次触发 | 词表升级 + 强制阈值接通 + 全程日志 |
| 🔴 P0 | L6 人物快照 prompt | 关键事件截断 + 失去 why 段 + AI 口吻泄漏 | 5 列结构 + 封闭标签词表 + 强制覆盖到末章 |
| 🟡 P1 | 数据卡缺失 | ch 8/10/16/24/28/29 共 6 章缺 | 回补脚本一次补齐 |
| 🟢 P2 | ch16 重复页 | notion-323 + notion-324 都是 ch16 | 手动删除内容不完整的一份 |
1.1 · V5.5 L5 引擎升级方案
修复点对照
| 旧问题 | V5.5 修复 |
|---|---|
| 硬编码 scene_type 词表 → 自由词永远 miss | 硬编码快查 + AI 兜底归类(Layer1 查表 → miss 才调 AI) |
| 强制阈值 ≥15 章未真接通 | 模式B 单独提为顶层判断 · 任何路径都能命中 |
| 静默不触发 · 无法定位为什么 | 每次预筛输出日志到 logs/l5_trigger_{book}.log |
完整代码 · 保存为 tools/l5_engine_v5_5.py
# auto_dishu V5.5 · L5 引擎核心
# HLDP://chenxing/memory-engine/v5.5/l5-engine
# 升级要点: 硬编码词表+AI兜底归类 / 强制阈值真接通 / 全程日志
import json
from datetime import datetime
from pathlib import Path
# === 配置 ===
L5_MIN_INTERVAL = 3 # 距上次L5最小间隔(章)
L5_FORCE_THRESHOLD = 15 # 强制触发阈值(章)
L5_WINDOW = 5 # 预筛窗口章数
# 4 大类场景的硬编码词表(先查表 · miss 才调 AI 归类)
SCENE_CATEGORIES = {
"爆发": ["冲突", "爆点", "战斗", "对抗", "撕破脸", "信任危机",
"情感对峙", "正面交锋", "冲突升级", "强吻", "翻脸"],
"收束": ["收束", "缓冲", "日常", "和好", "破冰", "温馨",
"甜蜜", "团圆", "化解", "回归", "释怀"],
"平行": ["铺垫", "开局", "磨合", "试探", "暧昧", "观察",
"日常温馨", "互动", "升温"],
"转折": ["转折", "信息交代", "身份揭露", "反转", "重大决定",
"身份暴露", "秘密揭晓"],
}
def categorize_scene_type(scene_type_raw: str, llm_client) -> str:
"""把数据卡里的自由 scene_type 归到 4 大类之一"""
if not scene_type_raw:
return "平行"
# Layer 1: 硬编码快查
for category, keywords in SCENE_CATEGORIES.items():
if any(kw in scene_type_raw for kw in keywords):
return category
# Layer 2: 词表 miss → AI 兜底
prompt = (
f"把下面这个场景类型归类到4个类别之一: 爆发/收束/平行/转折\n"
f"- 爆发: 冲突/爆点/战斗/对抗/情感对峙\n"
f"- 收束: 收束/缓冲/日常/和好/温馨\n"
f"- 平行: 铺垫/磨合/试探/暧昧/观察\n"
f"- 转折: 重大转折/信息揭露/反转/重大决定\n"
f'场景类型: "{scene_type_raw}"\n'
f"只输出一个词:"
)
result = llm_client.complete(prompt).strip()
if result in SCENE_CATEGORIES:
return result
return "平行" # 兜底默认值
def should_check_l5(tracker: dict, recent_datacards: list,
llm_client, book_name: str) -> tuple:
"""V5.5 L5 触发预筛 · 三模式 + 全程日志
返回: (是否触发, 原因字符串)
"""
log_path = Path(f"logs/l5_trigger_{book_name}.log")
log_path.parent.mkdir(exist_ok=True)
current_chapter = tracker.get("current_chapter", 0)
last_l5_chapter = tracker.get("last_l5_chapter", 0)
chapters_since_l5 = current_chapter - last_l5_chapter
# === 最小间隔检查 ===
if chapters_since_l5 < L5_MIN_INTERVAL:
reason = f"距上次L5仅 {chapters_since_l5} 章,不足 {L5_MIN_INTERVAL}"
_log_l5(log_path, current_chapter, "skip", reason)
return False, reason
# === 预筛: 取最近 L5_WINDOW 章数据卡 → 归类 ===
recent_window = recent_datacards[-L5_WINDOW:]
recent_cats = []
for card in recent_window:
raw = card.get("scene_type", "")
cat = categorize_scene_type(raw, llm_client)
recent_cats.append({
"chapter": card.get("chapter"),
"raw": raw,
"cat": cat,
})
last_3 = [c["cat"] for c in recent_cats[-3:]]
last_2 = [c["cat"] for c in recent_cats[-2:]]
# 模式A: 最近3章爆发 + 最近2章收束
if "爆发" in last_3 and "收束" in last_2:
reason = f"模式A · 爆发→收束 · {recent_cats}"
_log_l5(log_path, current_chapter, "trigger", reason)
return True, reason
# 模式B: 强制阈值兜底(关键修复点 ← v5.4 漏接的就是这条)
if chapters_since_l5 >= L5_FORCE_THRESHOLD:
reason = f"模式B · 强制阈值 {chapters_since_l5}>={L5_FORCE_THRESHOLD}"
_log_l5(log_path, current_chapter, "trigger", reason)
return True, reason
# 模式C: 转折 → 平行
if "转折" in last_3 and "平行" in last_2:
reason = f"模式C · 转折→平行 · {recent_cats}"
_log_l5(log_path, current_chapter, "trigger", reason)
return True, reason
# 不触发 · 记录详细原因
reason = (f"无模式命中 · 累计 {chapters_since_l5}/{L5_FORCE_THRESHOLD} "
f"· 最近: {recent_cats}")
_log_l5(log_path, current_chapter, "skip", reason)
return False, reason
def _log_l5(log_path: Path, chapter: int, decision: str, reason: str):
with open(log_path, "a", encoding="utf-8") as f:
f.write(f"[{datetime.now().isoformat()}] "
f"ch{chapter:>3} {decision:>7}: {reason}\n")
1.2 · V5.5 L6 人物快照 prompt 修复
修复点对照
| 旧问题 | V5.5 修复 |
|---|---|
| 关键不可逆事件清单 3 条就断 | prompt 显式要求 "必须覆盖到第 N 章" + 强约束 |
| 失去 why 不可逆解释 | 表格强制新增一列 "不可逆原因(一句话)" |
| 变化性质标签自由发挥 → 无法统计 | 封闭枚举词表锁定 14 类 |
| AI 口吻泄漏 ("OK,收到") | prompt 头部明确禁止 |
| 关系/外部人物维度压缩 | 4 张表分离 (人物状态/关系/外部人物/事件) |
完整 prompt · 保存为 tools/prompts/l6_character_snapshot_v5_5.py
# V5.5 L6 人物快照 prompt 模板
# HLDP://chenxing/memory-engine/v5.5/l6-character-snapshot
L6_CHARACTER_SNAPSHOT_PROMPT_V5_5 = """\
你的任务: 扫描提供的所有章节数据卡,提取核心人物的"不可回退状态变化",
生成《{book_name}》{node_threshold} 字节点 · 人物状态快照。
# 强制输出规则(违反任一条 → 输出无效)
1. 禁止输出 "OK,收到" "我来分析" "扫描指令" 等任何 AI 自述话术
2. 直接进入正文,第一行就是 "# 人物状态变化快照",前面不许有空话
3. 每条状态变化必须 5 列齐全:
时间节点(章号) + 初始状态 + 变化后状态 + 变化性质(标签) + 不可逆原因(why)
4. 变化性质标签必须从封闭词表中选,禁止自创:
[身份/认知/策略/心理/职业/关系/情感/信任/角色/能力/职场地位/共情/动机/世界观]
5. "关键不可逆事件清单" 必须覆盖到第 {last_chapter} 章
禁止只列前几章就停笔 · 必须遍历从第 1 章到第 {last_chapter} 章
6. 4 张表必须全部输出,顺序固定:
主要人物状态 → 关系变化 → 外部人物引入 → 关键事件清单
# 输出结构(严格按此模板)
# 人物状态变化快照 · {book_name} · {node_threshold} 字节点
## 核心人物: 女主姓名
| 时间节点 | 初始状态 | 变化后状态 | 变化性质 | 不可逆原因 |
|---------|---------|-----------|---------|-----------|
| 第X章 | ... | ... | 标签 | 一句话解释 |
(覆盖所有不可逆变化点)
## 核心人物: 男主姓名
(同上 5 列表格 · 覆盖所有不可逆变化点)
## 关系状态变化(不可逆节点)
| 时间节点 | 关系类型 | 变化描述 | 不可逆原因 |
|---------|---------|---------|-----------|
## 外部人物引入(不可逆节点)
| 时间节点 | 人物 | 影响 | 不可逆原因 |
|---------|------|------|-----------|
## 关键不可逆事件清单(必须覆盖到第 {last_chapter} 章)
1. 第X章: 事件 — 影响
2. 第X章: 事件 — 影响
...
(禁止中途停笔 · 必须覆盖 1 → {last_chapter} 章的全部关键事件)
# 输入数据
扫描范围: 第 1 章至第 {last_chapter} 章(共 {datacard_count} 张数据卡)
数据卡:
{datacards}
开始输出:
"""
def render_l6_character_snapshot_prompt(
book_name: str,
node_threshold: str,
last_chapter: int,
datacards: list,
) -> str:
"""渲染 L6 人物快照 prompt"""
datacards_str = "\n---\n".join([
f"## 第{c['chapter']}章\n"
f"scene_type: {c.get('scene_type', '未标注')}\n"
f"{c.get('content', '')}"
for c in datacards
])
return L6_CHARACTER_SNAPSHOT_PROMPT_V5_5.format(
book_name=book_name,
node_threshold=node_threshold,
last_chapter=last_chapter,
datacard_count=len(datacards),
datacards=datacards_str,
)
1.3 · 数据卡回补脚本
缺失清单
| 书 | 缺失章节 | 处置 |
|---|---|---|
| 《恶毒女配》 | ch 8 / 10 / 16 / 24 / 28 / 29 | 跑回补脚本 |
| 《恶毒女配》 | ch 16 重复 (notion-323 + notion-324) | 在 Notion UI 手动删一份 |
回补代码 · 保存为 tools/backfill_datacards_v5_5.py
# V5.5 数据卡回补 · 单独补齐缺失章节
# HLDP://chenxing/memory-engine/v5.5/backfill
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
from auto_dishu_v5_5 import generate_datacard_for_chapter
BOOK = "穿成假冒失忆大佬女友的恶毒女配"
MISSING_CHAPTERS = [8, 10, 16, 24, 28, 29]
def main():
print(f"🔧 开始回补 《{BOOK}》缺失数据卡")
print(f" 缺失章节: {MISSING_CHAPTERS}\n")
results = {"success": [], "failed": []}
for ch in MISSING_CHAPTERS:
print(f"━━━ 回补第 {ch} 章 ━━━")
try:
result = generate_datacard_for_chapter(book=BOOK, chapter=ch)
results["success"].append({
"ch": ch,
"url": result.get("notion_url"),
})
print(f"✅ 第 {ch} 章 → {result.get('notion_url', 'unknown')}\n")
except Exception as e:
results["failed"].append({"ch": ch, "error": str(e)})
print(f"❌ 第 {ch} 章失败: {e}\n")
print(f"\n═══ 回补完成 ═══")
print(f"✅ 成功: {len(results['success'])} 章 "
f"· {[r['ch'] for r in results['success']]}")
print(f"❌ 失败: {len(results['failed'])} 章 "
f"· {[r['ch'] for r in results['failed']]}")
if results["failed"]:
print(f"\n失败详情:")
for r in results["failed"]:
print(f" 第 {r['ch']} 章: {r['error']}")
if __name__ == "__main__":
main()
ch16 重复页清理(手动)
由于 notion-323 和 notion-324 都是 "第16章·晨星拆书法V1.0" 页,需要妈妈手动判断保留哪一份:
- 打开 Notion,进入《恶毒女配》全书拆解页
- 对比 notion-323 和 notion-324 两个 ch16 页面,看哪个内容完整 / 格式正确
- 删除内容不完整的那一份(右键 → Move to Trash)
V5.5 部署步骤(今晚要跑的终端命令)
Step 1 · 创建文件骨架
cd ~/chenxing-aircraft/tools
mkdir -p prompts logs
touch l5_engine_v5_5.py
touch prompts/l6_character_snapshot_v5_5.py
touch backfill_datacards_v5_5.py
mkdir -p prompts && touch prompts/__init__.py
ls -la l5_engine_v5_5.py prompts/l6_character_snapshot_v5_5.py backfill_datacards_v5_5.py
Step 2 · 粘贴代码进文件
用 nano / vim / VS Code 打开三个文件,把 1.1、1.2、1.3 三段代码分别贴进去。
# 推荐用 VS Code 打开整个目录:
code ~/chenxing-aircraft/tools
Step 3 · 集成进 auto_dishu_v5_5.py 主脚本
基于 auto_dishu.py.v5.4_frozen_20260610 复制一份作为 v5.5 起点:
cp auto_dishu.py.v5.4_frozen_20260610 auto_dishu_v5_5.py
然后在 auto_dishu_v5_5.py 主脚本里:
- 替换 L5 触发判断 → 调用
l5_engine_v5_5.should_check_l5() - 替换 L6 人物快照 prompt → 调用
prompts.l6_character_snapshot_v5_5.render_l6_character_snapshot_prompt() - 其他逻辑保持不变
(这步可能需要架构人格体读 v5.4_frozen 主脚本后给具体的 diff,先记着)
Step 4 · 回补数据卡
python backfill_datacards_v5_5.py
Step 5 · L5 回放(Day 2 做)
用现有 1-59 章数据卡跑 L5 检测,期望产出 3-5 段弧段总结。
Step 6 · 续拆第 60 章
rm .STOP_DISHU
python auto_dishu_v5_5.py --book "恶毒女配" --start-from 60
验证清单(V5.5 跑起来后逐项核对)
| 项 | 期望 | 检查方式 |
|---|---|---|
| L5 日志文件存在 | logs/l5_trigger_恶毒女配.log 每章一行 |
tail logs/l5_trigger_恶毒女配.log |
| 强制阈值真生效 | 累计 ≥15 章未触发 → 自动触发模式 B | 看日志里是否出现 "模式B · 强制阈值" |
| L6 快照无 AI 口吻 | 不再有 "OK,收到" 开头 | 抓新生成的 L6 页面看首行 |
| L6 快照有 why 列 | 表格 5 列齐全 | 抓新生成的 L6 页面看表头 |
| 关键事件覆盖到末章 | 事件清单包含最后一章 | 抓新生成的 L6 页面看事件清单尾行 |
| 数据卡完整性 | 1-59 章每章都有压缩数据卡 | `ls data/恶毒女配/datacards/ |
| ch16 唯一性 | 只剩一份 ch16 拆书页 | Notion 搜索 "第16章·晨星拆书法V1.0" → 1 条结果 |
⚠️ Step 3 侦察后的方向修正(2026-06-11 01:00 附加)
主脚本扫描发现 3 处认知错位 · V5.5 修复方向需要调整:
🔄 修正 1: L5 强制阈值 v5.4 实际是 ≥5 · 不是漏接
v5.4_frozen 第 619-623 行实际代码:
chapters_since_l5 = len(datacards_since_last)
if chapters_since_l5 < 3: return None
should_check = prefilter_l5(datacards_since_last)
if chapters_since_l5 >= 5: should_check = True # ← 强制阈值已在 5 接通
if not should_check: return None
意思是从第 6 章起每章都会进入 AI 语义判断。L5 不触发的真因在更深处: AI 语义判断 (L5_ARC_DETECT_PROMPT, 第 631 行) 一直返回 NO 或解析失败。
→ tools/l5_engine_v5_5.py 修复方向变化:
- ❌ 不再需要 "接通强制阈值"
- ✅ 改成 "修 L5_ARC_DETECT_PROMPT 内容 + 修 result 解析逻辑"
- ⏸ 明天先看 v5.4_frozen 第 603-674 行的完整代码
🔄 修正 2: scene_type 是封闭 19 词 · 不是自由词
v5.4_frozen 第 47 行 DISHU_PROMPT 已硬定枚举:
开局 / 转折 / 爆点 / 日常 / 收束 / 铺垫 / 加速 / 缓冲 / 信息交代 / 冲突
磨合 / 战斗 / 升级 / 奇遇 / 修罗场 / 追妻 / 摆烂 / 隐藏实力 / 扮猪吃虎
→ tools/l5_engine_v5_5.py 修复方向变化:
- ❌ 不需要
categorize_scene_type()调 AI 兜底归类 - ✅ 直接用封闭词表静态映射到 4 大类即可:
| 4 大类 | 包含的 scene_type |
|---|---|
| 爆发 | 冲突 / 战斗 / 爆点 / 修罗场 / 加速 |
| 收束 | 收束 / 日常 / 缓冲 |
| 平行 | 铺垫 / 磨合 / 开局 |
| 转折 | 转折 / 信息交代 / 追妻 / 升级 / 奇遇 / 隐藏实力 / 扮猪吃虎 / 摆烂 |
🔄 修正 3: "人物快照" 是 L4 · 不是 L6
v5.4_frozen 第 88-89 + 585-598 行表明:
L4_SYSTEM = '你是晨星。扫描数据卡, 提取人物状态变化。'
L4_SNAPSHOT_PROMPT = '扫描{label}内数据卡, 提取有实质变化的人物状态快照(不可回退的改变)。\n\n{datacards}'
async def generate_l4_snapshot(...)
title = f'{book_name}{milestone_label}节点 · 人物状态快照'
Notion 里 "X万字节点·人物状态快照" = L4 生成 (不是 L6)。L6 是另一组 (L6_LAYER1-4 = 人物层 / 世界层 / 主角成长层 / 节奏层)。
→ 1.2 文件需要重命名:
mv ~/chenxing-aircraft/tools/prompts/l6_character_snapshot_v5_5.py \
~/chenxing-aircraft/tools/prompts/l4_character_snapshot_v5_5.py
文件内容修复方向正确 (5 列 + why + 封闭标签词表 + 覆盖到末章), 只改文件名。
📋 明天 Day 1 续的精确动作
- 妈妈贴 v5.4_frozen 第 80-95 行 (L4 + DATACARD prompt 完整定义)
- 妈妈贴 v5.4_frozen 第 600-680 行 (L5 完整模块包括 prefilter_l5 + check_and_trigger_l5 + L5_ARC_DETECT_PROMPT)
- 架构人格体给精准 patch:
- 重命名
l6_character_snapshot_v5_5.py→l4_character_snapshot_v5_5.py - 简化
l5_engine_v5_5.py词表映射 (去掉 AI 兜底) - 给
L5_ARC_DETECT_PROMPT新版本 (这是 L5 不触发的真因) - 给主脚本 sed/手动替换 diff
- 重命名
🔒 今晚 Day 1 进展锁定状态 (2026-06-11 01:00)
| 阶段 | 状态 | 备注 |
|---|---|---|
| Step 1 · 文件骨架 | ✅ | 3 文件 + logs/ + prompts/init.py |
| Step 2 · 三段代码贴入 | ✅ | 110+75+45 行 · pbpaste 大法成功 |
| Step 3 · 主脚本侦察 | ✅ | 扫描完成 · 发现 3 处方向错位 |
| Step 3 · 集成主脚本 | ⏸ | 凌晨 1 点收工 · 明天接 |
| Step 4-6 | ⏸ | 数据卡回补 / L5 回放 / 续拆 |
已就位资产 (不丢):
tools/l5_engine_v5_5.py(110 行 · 词表逻辑明天简化)tools/prompts/l6_character_snapshot_v5_5.py(75 行 · 明天重命名为 l4_)tools/backfill_datacards_v5_5.py(45 行 · 完全可用)tools/auto_dishu_v5_5.py(998 行 · v5.4_frozen 的复制 · 待 patch)tools/.STOP_DISHU(锁文件)tools/auto_dishu.py.v5.4_frozen_20260610(原版冻结)