mcpField
B
book-sft-pipeline

book-sft-pipeline

github.com/muratcankoylan数据与数据库18.2k85 次安装2 周前更新
代码仓库

关于 book-sft-pipeline

Book SFT Pipeline 是将书籍文本转为监督微调数据集并训练风格迁移模型的技能,适用于研究者和模型训练者。它覆盖分段、指令生成、数据构建、LoRA 训练与验证。

主要功能

  1. 01从 ePub 提取文本
  2. 02按语义边界分段
  3. 03生成多样化训练指令
  4. 04构建消息格式数据集
  5. 05使用 Tinker 进行 LoRA 训练
  6. 06通过新场景与原创性验证

适用场景

  1. 01从文学作品构建微调数据集
  2. 02训练模仿作者文风的模型
  3. 03为长文本设计分段流程
  4. 04评估模型是否学到文风而非情节

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/muratcankoylan/agent-skills-for-context-engineering --skill book-sft-pipeline

安装后在 Claude Code 中输入/book-sft-pipeline确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

README.md
examples/gertrude-stein/
dataset_sample.jsonl
README.md
sample_outputs.md
training_config.json
references/
segmentation-strategies.md
tinker-format.md
tinker.txt
scripts/
pipeline_example.py

详细介绍

书籍 SFT 流水线

一套完整的系统,用于将书籍转换为 SFT 数据集并训练风格迁移模型。本技能介绍从原始 ePub 到能够以任意作者的口吻写作的模型的完整流程。

何时启用

在以下情形启用此技能:

  • 从文学作品构建微调数据集
  • 创建作者口吻或风格迁移模型
  • 为 Tinker 或类似的 SFT 平台准备训练数据
  • 为长篇内容设计文本分段流水线
  • 使用有限数据训练小型模型(8B 或更小)

核心概念

书籍 SFT 的三大支柱

1. 智能分段 文本片段必须在语义上连贯。在句子中间切分会让模型学会生成零散的内容。目标:每个片段 150-400 个单词,并始终在自然边界处切分。

2. 多样化的指令生成 使用多个提示词模板和系统提示词,避免过拟合。单一提示词风格会导致记忆化。使用 15 个以上的提示词模板和 5 个以上的系统提示词。

3. 风格优先于内容 目标是学习作者的节奏和词汇模式,而不是记住情节。合成指令应描述发生的事情,而不引用原文。

流水线架构

┌─────────────────────────────────────────────────────────────────┐
│                    ORCHESTRATOR AGENT                           │
│  Coordinates pipeline phases, manages state, handles failures   │
└──────────────────────┬──────────────────────────────────────────┘
                       │
       ┌───────────────┼───────────────┬───────────────┐
       ▼               ▼               ▼               ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│  EXTRACTION  │ │ SEGMENTATION │ │  INSTRUCTION │ │   DATASET    │
│    AGENT     │ │    AGENT     │ │    AGENT     │ │   BUILDER    │
│ ePub → Text  │ │ Text → Chunks│ │ Chunks →     │ │ Pairs →      │
│              │ │ 150-400 words│ │ Prompts      │ │ JSONL        │
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
                       │
       ┌───────────────┴───────────────┐
       ▼                               ▼
┌──────────────┐               ┌──────────────┐
│   TRAINING   │               │  VALIDATION  │
│    AGENT     │               │    AGENT     │
│ LoRA on      │               │ AI detector  │
│ Tinker       │               │ Originality  │
└──────────────┘               └──────────────┘

阶段 1:文本提取

关键规则
  1. 始终优先使用 ePub,而非 PDF——OCR 错误会成为模型学到的模式
  2. 按段落提取——从 <p> 标签提取,以保留段落分隔
  3. 移除正文前后的内容——版权信息和目录会污染数据集
# Extract text from ePub paragraphs
from epub2 import EPub
from bs4 import BeautifulSoup

def extract_epub(path):
    book = EPub(path)
    chapters = []
    for item in book.flow:
        html = book.get_chapter(item.id)
        soup = BeautifulSoup(html, 'html.parser')
        paragraphs = [p.get_text().strip() for p in soup.find_all('p')]
        chapters.append('\n\n'.join(p for p in paragraphs if p))
    return '\n\n'.join(chapters)

阶段 2:智能分段

更小的片段 + 重叠

与较大的片段(250-650 个单词)相比,较小的片段(150-400 个单词)能生成更多训练示例,并带来更好的风格迁移效果。

def segment(text, min_words=150, max_words=400):
    paragraphs = text.split('\n\n')
    chunks, buffer, buffer_words = [], [], 0
    
    for para in paragraphs:
        words = len(para.split())
        if buffer_words + words > max_words and buffer_words >= min_words:
            chunks.append('\n\n'.join(buffer))
            # Keep last paragraph for overlap
            buffer = [buffer[-1], para] if buffer else [para]
            buffer_words = sum(len(p.split()) for p in buffer)
        else:
            buffer.append(para)
            buffer_words += words
    
    if buffer:
        chunks.append('\n\n'.join(buffer))
    return chunks
预期结果

对于一本 86,000 个单词的书:

  • 旧方法(250-650 个单词):约 150 个片段
  • 新方法(150-400 个单词 + 重叠):约 300 个片段
  • 每个片段使用 2 个变体:600 个以上的训练示例

阶段 3:多样化的指令生成

关键见解

使用单一提示词模板会导致记忆化。多样化的模板能教会模型底层风格。

SYSTEM_PROMPTS = [
    "You are an expert creative writer capable of emulating specific literary styles.",
    "You are a literary writer with deep knowledge of classic prose styles.",
    "You are a creative writer skilled at emulating distinctive authorial voices.",
    "You write prose that captures the essence of modernist literature.",
    "You are a talented writer who can channel classic American authors.",
]

PROMPT_TEMPLATES = [
    "Write a passage in the style of {author}: {desc}",
    "Channel {author}'s voice to write about: {desc}",
    "In {author}'s distinctive prose style, describe: {desc}",
    "Write this scene as {author} would have: {desc}",
    "Using {author}'s repetitive technique, describe: {desc}",
    "Capture the rhythm of {author} in this passage: {desc}",
    "Write like {author}: {desc}",
    "In the voice of {author}, write: {desc}",
    "This is a literary exercise. Write like {author}: {desc}",
    "Can you write in {author}'s style? {desc}",
]
指令生成
INSTRUCTION_PROMPT = """Describe what is happening in this excerpt in 2-3 sentences.
Focus on: characters present, actions, emotions, setting.
Do NOT quote the text directly.

Excerpt:
{text}
"""

# Use a fast, cheap LLM (e.g., Gemini Flash)
instruction = llm_call(INSTRUCTION_PROMPT.format(text=chunk))

阶段 4:数据集构建

消息格式
{
    "messages": [
        {"role": "system", "content": "You are an expert creative writer..."},
        {"role": "user", "content": "Write in the style of Author: Scene description..."},
        {"role": "assistant", "content": "The actual book text from chunk..."}
    ]
}
每个片段的多个变体
def build_examples(chunk, instruction, author, variants=2):
    examples = []
    for i in range(variants):
        system = SYSTEM_PROMPTS[i % len(SYSTEM_PROMPTS)]
        template = PROMPT_TEMPLATES[(chunk.id + i) % len(PROMPT_TEMPLATES)]
        user = template.format(author=author, desc=instruction)
        examples.append({"messages": [
            {"role": "system", "content": system},
            {"role": "user", "content": user},
            {"role": "assistant", "content": chunk.text}
        ]})
    return examples

阶段 5:在 Tinker 上进行 LoRA 训练

配置
CONFIG = {
    "model_name": "Qwen/Qwen3-8B-Base",  # Base, not instruct
    "lora_rank": 32,                      # 352MB adapter
    "learning_rate": 5e-4,                # Higher for LoRA
    "batch_size": 4,
    "epochs": 3,
}
为什么使用基础模型?

使用基础(预训练)模型,而不是经过指令微调的版本:

  • 基础模型更容易适应新风格
  • 指令微调模型具有不易覆盖的模式
  • 风格是一种底层模式,基础模型更擅长捕捉
训练循环
import tinker
from tinker import types

training_client = await service_client.create_lora_training_client_async(
    base_model="Qwen/Qwen3-8B-Base",
    rank=32
)

for epoch in range(3):
    for batch in batches:
        await training_client.forward_backward_async(batch, loss_fn="cross_entropy")
        await training_client.optim_step_async(types.AdamParams(learning_rate=5e-4))

result = await training_client.save_weights_for_sampler_async(name="final")

阶段 6:验证

现代场景测试

使用原书中不可能出现的场景进行测试:

TEST_PROMPTS = [
    "Write about a barista making lattes",
    "Describe lovers communicating through text messages",
    "Write about someone anxious about climate change",
]

如果模型能将风格标记应用于现代场景,说明它学到的是风格,而不是内容。

原创性验证
# Search training data for output phrases
grep "specific phrase from output" dataset.jsonl
# Should return: No matches
AI 检测器测试

使用 GPTZero、Pangram 或 ZeroGPT 测试输出。

已知问题及解决方案

角色名称泄漏

症状:模型在新场景中使用原有角色名称。 原因:仅使用一本书,名称多样性有限。 解决方案:使用多本书训练,或添加合成示例。

模型照搬原句

症状:输出中包含训练数据中的原句。 原因:提示词变体太少,或训练轮数太多。 解决方案:使用 15 个以上的模板,并将训练轮数限制为 3 轮。

输出零散

症状:句子读起来不完整。 原因:分段不当,在意思尚未表达完整时就进行了切分。 解决方案:始终在段落边界处切分。

指南

  1. 始终优先选择 ePub,而非 PDF——OCR 错误会变成模型学会的模式
  2. 不要在句子中间截断——边界必须符合语法且完整
  3. 使用多样化的提示词——15 种以上的模板、5 种以上的系统提示词
  4. 使用基础模型——不要使用指令微调版本
  5. 使用更小的文本块——每块 150-400 个单词,以获得更多示例
  6. 预留测试集——至少 50 个示例
  7. 使用现代场景进行测试——证明风格迁移能力,而非记忆能力
  8. 验证原创性——使用 grep 在训练数据中搜索输出短语

预期结果

指标值
训练示例每本书 500-1000 个
模型Qwen/Qwen3-8B-Base
LoRA 秩32
适配器大小~350 MB
训练时间~15 分钟
损失下降90%+
风格迁移成功率~50% 完美

成本估算

组成部分成本
LLM(指令生成)~$0.50
Tinker 训练(15 分钟)~$1.50
总计~$2.00

与上下文工程技能集成

此示例应用了 Agent Skills for Context Engineering 集合中的多项技能:

project-development

该流水线遵循分阶段、幂等的架构模式:

  • 获取:从 ePub 中提取文本
  • 准备:将文本切分为训练文本块
  • 处理:生成合成指令
  • 解析:构建消息格式
  • 渲染:输出兼容 Tinker 的 JSONL
  • 训练:LoRA 微调
  • 验证:测试现代场景

每个阶段都可恢复,并会生成中间产物以供调试。

context-compression

分段是训练中的一种上下文压缩形式。上下文压缩的核心观点也适用于此:信息密度比信息数量更重要。较小且连贯的文本块(150-400 个单词)比更大且内容稀释的文本块能带来更好的风格迁移效果。

双层策略与上下文压缩评估相呼应:

  • 第 1 层:快速、确定性的压缩
  • 第 2 层:借助 LLM 处理边缘情况
multi-agent-patterns

该流水线采用监督者/编排者模式:

  • 编排者协调各阶段并管理状态
  • 专用 Agent(提取、分段、指令、构建)各自拥有隔离的上下文
  • 每个 Agent 只接收完成任务所需的信息

这符合以下原则:子 Agent 的主要作用是隔离上下文,而不是模拟角色。

evaluation

验证遵循最终状态评估模式:

  • 功能测试:输出是否符合预期的风格标记?
  • 原创性验证:内容是否确实由模型生成?
  • 外部验证:AI 检测器评分

“现代场景”测试是一种分布外评估,可用于证明泛化能力。

context-fundamentals

提示词多样性可避免模型在单一模式上出现注意力坍缩。使用相同的提示词结构训练时,模型会记住指令与响应之间的映射。多样化的模板会迫使注意力聚焦于风格模式本身。

参考资料

内部参考资料:

Agent Skills for Context Engineering 中的相关技能:

  • project-development - 流水线架构模式
  • context-compression - 压缩策略
  • multi-agent-patterns - Agent 协调
  • evaluation - 评估框架
  • context-fundamentals - 注意力与信息密度

外部资源:


技能元数据

创建时间:2025-12-26 最后更新:2025-12-28 作者:Muratcan Koylan 版本:2.0.0 独立运行:是(与主上下文工程合集分开)

评论

支持 Markdown