# 长篇小说 SFT 数据生成 将网文/剧本逆向拆解为"AI 可学习的思考步骤",生成三类 SFT 训练数据。 --- ## 设计思路 ### 整体流程 ``` 原文 txt │ ▼ step1_analyze.py(一次 LLM 调用,500K 窗口) 分析 JSON [outline / characters / beats] │ ▼ step2_build_sft.py(每个 beat 2-3 次 LLM 调用) 三类 JSONL task1_structure_planning.jsonl task2_scene_continuation.jsonl task3_shuang_injection.jsonl ``` ### Beat 切分与定位 分析时将全文按 Scene-Sequel 结构切分为若干 **beat**(叙事单元)。 Beat 边界通过 **文本锚点** 定位:LLM 从原文逐字复制每个 beat 开头的 20-25 个字符,Python 用 `str.find()` 精确定位字符位置(渐进缩短前缀至 8 字兜底)。不依赖章节标题格式,适用于任意命名风格。 ### 三个 SFT 任务 #### Task 1 — 结构规划(Structure Planning) **目标**:让模型学会在给定故事状态时,规划下一个 Scene-Sequel 单元的结构。 | | 内容 | |---|---| | 输入 | 故事状态(MICE 线程、上一个 Disaster/Decision、当前位置)+ 上文(最近 800 字) | | 输出 | `` 叙事状态分析 + 续写决策 `` + 结构规划 JSON | 输出 JSON 字段:`scene`(goal/conflict_type/disaster/pacing)、`sequel`(reaction/dilemma/decision)、`hooks`、`shuang_point`、`mice_advancement` **数据来源**:以该 beat 的实际结构作为参考信息,由 LLM 逆向生成"事前规划"视角的 CoT,用户侧输入不包含 beat 实际内容。 --- #### Task 2 — 场景续写(Scene Continuation) **目标**:让模型学会根据结构规划生成正文。 | | 内容 | |---|---| | 输入 | 上文(500-1500 字)+ Task 1 输出的结构规划 | | 输出 | `` 上文理解 + 写法决策 `` + 续写正文 | **数据来源**:CoT 由 LLM 生成(给定 Task 1 规划 + beat 前 300 字 hint),正文使用原文 beat 文本作为 ground truth。 --- #### Task 3 — 爽点注入(Shuang Point Injection) **目标**:让模型学会将平淡草稿改写为带爽点的版本。 | | 内容 | |---|---| | 输入 | 平淡草稿 + 爽点类型(打脸/升级/装逼/获得/碾压)+ 强度(low/medium/high) | | 输出 | `` 草稿分析 + 爽点设计 `` + 增强版正文 + 修改说明 | **数据来源**:仅处理分析中标记 `has_shuang=true` 的 beat。LLM 从原文生成"平淡草稿"(去掉爽点保留情节),原文作为增强版 ground truth。 --- ### 多窗口连贯性 超过 500K 字符的小说分多个窗口处理,后续窗口通过 `--prev-analysis` 接收前一窗口的人物/线索元信息,确保全书人物关系和 MICE 线程不断档。 --- ## 文件结构 ``` sft/ step1_analyze.py # 500K 窗口分析 → analysis JSON step2_build_sft.py # analysis JSON → 三类 JSONL run_pipeline.py # 一键批量运行,支持断点续跑 README.md runs/{书名}/ # 运行输出(由 run_pipeline.py 自动创建) analysis/ w0.json # 第 0 个窗口分析结果 w1.json # 第 1 个窗口分析结果(如有) sft_raw/ w0/ # 第 0 个窗口的 SFT 数据 task1_structure_planning.jsonl task2_scene_continuation.jsonl task3_shuang_injection.jsonl stats.json merged/ # 所有窗口合并后的最终数据 task1_structure_planning.jsonl task2_scene_continuation.jsonl task3_shuang_injection.jsonl stats.json pipeline.log # 运行日志 ``` --- ## 环境配置 ``` # .env(项目根目录) ALI_API_KEY=sk-... ALI_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1 ``` 依赖: ```bash pip install openai python-dotenv ``` --- ## 用法 ### 一键运行(推荐) ```bash cd examples/analyze_story/sft python run_pipeline.py --novel ../input/大奉打更人.txt ``` 断点续跑(重新执行同一命令,已完成的窗口自动跳过): ```bash python run_pipeline.py --novel ../input/大奉打更人.txt ``` ### 常用参数 ``` --novel 小说 txt 文件路径(必填) --output-dir 输出根目录(默认 sft/runs/{书名}/) --window-size 每窗口字符数(默认 500000) --model 模型名称(默认 qwen-plus) --context-chars 上文字符数,Task1/2 使用(默认 800) --concurrency step2 并发调用数(默认 5) --skip-task N 跳过某个任务,可多次指定(例:--skip-task 3) --only-step 1 只跑分析,不生成 SFT --only-step 2 只生成 SFT(需要 analysis/ 已存在) --force 强制重跑,忽略已有文件 ``` ### 批量处理多本书 ```bash for f in ../input/*.txt; do python run_pipeline.py --novel "$f" --concurrency 8 done ``` ### 单独调用 ```bash # 只分析第一个窗口 python step1_analyze.py \ --novel ../input/大奉打更人.txt \ --output runs/大奉打更人/analysis/w0.json # 只生成 SFT 数据 python step2_build_sft.py \ --analysis runs/大奉打更人/analysis/w0.json \ --novel ../input/大奉打更人.txt \ --output-dir runs/大奉打更人/sft_raw/w0/ ``` --- ## JSONL 格式 每行一条训练样本: ```json { "messages": [ {"role": "system", "content": "..."}, {"role": "user", "content": "..."}, {"role": "assistant", "content": "\n...\n\n\n..."} ], "metadata": { "task_type": "structure_planning | scene_continuation | shuang_injection", "source_file": "大奉打更人", "chapter": "第4章", "position_percent": 3.8, "mice_thread": "税银案", "beat_id": "beat_003", "word_count": 3200 } } ```