Skip to content

古籍不是上传一个 TXT 就结束了:ReadTao Content Studio 的导入与分段设计

约 3282 字大约 11 分钟

ReadTaoContent Studio数据建模文本处理

2026-06-26

古籍整理最容易被低估的部分,是把“文件里有文字”变成“每个公开段落都能回到具体来源”。这篇沿着 ReadTao Content Studio 的真实导入链路,拆解来源登记、原文件保存、正文与注文分层、语义分段和稳定段落身份。

上一篇里,我先梳理了 ReadTao 的整体架构:Content Studio 负责变化中的内容,Reader 只读取已经发布的快照。

这一篇继续往前走,从一份古籍原文件开始。

本文涉及的导入接口、来源分层和分段实现,都可以在 ReadTao GitHub 仓库 中找到。

我最初也很容易把“导入古籍”想成普通后台里的文件上传:选一个 TXT,解析成字符串,按空行切段,然后保存到数据库。

真正拿 KR5 文本做过以后,我发现这条路径几乎每一步都会丢信息。

  • 文件属于哪一个来源和提交版本?
  • 原始字符是否被规范化代码静默修改过?
  • 一行里哪些是经文,哪些是来源自带的注文?
  • 一条长经文应该是一段,还是几个语义段?
  • 将来发现断句不合适,怎样重做又不破坏已经发布的链接?

古籍导入不是把文本搬进数据库,而是在原始文件和公开段落之间建立一条可验证的映射。

先区分作品、底本和内容单元

ReadTao 没有把所有字段都放进一张 books 表。

这几个概念分别解决不同问题:

实体我用它表达什么
Work书库里作为一部独立典籍呈现的作品
Edition作品采用的具体底本或整理版本
Division卷、章、篇,或者一篇无层级的完整正文
Segment拥有稳定身份和锚点的语义段落
SegmentRevision这个段落在某次编辑中的具体原文内容

《道德经》和《道德真經注》不是“同一本书有没有注释”的开关差异。后者的完整题名、文本构成和来源注文都不同,因此可以作为独立 Work 管理。同一作品内部仍然可以保存多个 Edition,避免以后增加底本时重做整个数据结构。

这里最重要的拆分是 SegmentSegmentRevision

段落身份应该稳定,文字却可能继续修订。只要不是拆分或合并,修改一个异体字不应该让段落 URL 和历史引用全部失效。

来源登记比上传按钮更早

一份文件进入 Studio 以前,我先要求编辑者登记来源:

  • 来源 URL;
  • 仓库和文件路径;
  • 提交哈希或固定修订;
  • 获取时间;
  • 许可或公版依据;
  • 底本说明和溯源说明。

文件上传后,Backend 还会计算 SHA-256,并保存对象存储位置、大小、类型和 ETag。

浏览器不会直接拿到 MinIO 或 COS 密钥。原始文件由 Backend 代理写入私有对象存储,需要查看时再生成短时下载地址。

数据库也不保存一整份原文件大字段。它保存的是能够重新找到并验证文件的证据。

确认导入后的对象键大致按下面的结构组织:

sources/{source_id}/{sha256}/{filename}

这样做的意义不是为了目录好看,而是让“这次解析使用了哪份原文件”变得可检查。只要文件内容发生一个字节变化,校验和就会不同。

我给自己的来源底线

一个公开段落至少应该能够回到作品、底本、内容单元、原始文件和发布版本。只有一段脱离来源的“干净文本”,并不等于已经完成古籍数字化。

原文件不能在解析时被覆盖

上传完成后,解析器会读取 Mandoku/Org 风格的结构标记、元数据、页码锚点和正文行。

但解析结果只是派生数据。原文件本身始终保持不变。

规范化可以处理换行、空白或确定性的显示问题,却不能借“自动纠错”的名义静默替换异体字、缺字或疑似错误。原始文本和规范文本必须能够同时查看。

这也是为什么我没有直接执行下面这种处理:

# 不应该把所有“看起来不统一”的字直接改掉再保存
normalized = auto_fix_everything(source_text)
save_as_original(normalized)

更安全的思路是:

不可变原文件
  ├── 解析得到来源片段和坐标
  ├── 建议性的规范文本
  └── 人工确认后的修订

为什么要先做来源分层

带注古籍往往把正文和注文放在同一份文件里。如果直接把整行拿去分段,会产生两个严重问题:

  1. 注文可能被当成经文的一部分;
  2. 注文可能进入翻译、拼音或 RAG 的模型输入。

ReadTao 因此在语义分段以前增加了一道“来源分层”门禁。

Backend 当前使用四种来源角色:

class SourceFragmentRole(StrEnum):
    MAIN_TEXT = "main_text"
    SOURCE_COMMENTARY = "source_commentary"
    PARATEXT = "paratext"
    UNRESOLVED = "unresolved"
角色含义后续去向
main_text典籍正文进入来源单元和语义分段
source_commentary原始来源自带的注、疏、音义独立锚定、修订和审校
paratext卷题、署名、跋文等非正文资料保留来源,不进入正文生成
unresolved规则无法可靠判断必须人工处理,不能提交

自动规则只给建议,还会保存规则版本、命中原因和置信度。编辑者可以在任意字符边界拆分片段,再重新指定角色。

来源注文默认锚定到前方最近的正文单元,编辑者可以调整前后位置和“注、疏、音义”等来源标签。它属于原典自身,不是 ReadTao 后加的编辑注,也不允许 AI 生成。

覆盖检查是导入链路的硬约束

只要允许在字符边界拆分,就必须回答一个问题:有没有字在处理过程中消失、重复或错位?

source_fragments 因此会保留:

  • 原始行号;
  • 行内起止字符位置;
  • 片段顺序;
  • 角色;
  • 规则和人工确认信息。

同一内容单元中的非元数据字符,必须按原顺序被片段恰好覆盖一次

接下来生成的来源文本单元和语义段落同样要检查:

  • 有没有缺失单元;
  • 有没有一个单元被重复使用;
  • 单元顺序是否连续;
  • 注文是否已经锚定;
  • 是否还存在待界定内容。

这组约束看起来有些严格,但它避免了一种很难发现的错误:页面上的文本读起来仍然通顺,某个被解析器漏掉的短句却已经永远离开了公开版本。

王弼注样例怎样使用固定参考

ReadTao 当前的完整演示使用 Kanseki Repository 中的 KR5c0073,也就是王弼《道德真經注》四卷来源文件。

这类文件里的经文、王弼注文和卷内其他材料需要分开。为了减少维护者盲切,Backend 还固定了一份可追溯的维基文库修订,用来做逐字符对齐和边界建议。

这里有两个不能越过的边界:

  1. 参考文本只能帮助判断正文、注文和标点边界;
  2. 参考文本中的字不能覆盖 Kanseki 底本中的原字。

换句话说,逐字符对齐成功以后,我可以利用参考标点把一条长正文细分成更合适的来源单元,却不能因为参考文本写了另一个字,就偷偷修改登记底本。

固定参考也不会在运行时临时联网抓取。修订号、文件、许可和校验和一起保存在仓库中,保证相同输入能够重复得到相同建议。

语义分段不是按 120 个字切一刀

来源分层完成后,只有 main_text 会进入语义分段。

当前规则版本叫 classical-zh-rules-v1。它是版本化、可解释的确定性 Python 规则,不是一个小模型,也没有训练权重和推理费用。

规则会观察:

  • 句号、分号等完整句边界;
  • “故”“是以”“夫”等篇章转折词;
  • 相邻来源单元的词汇衔接;
  • 段落软长度;
  • 是否出现不利于阅读的孤立短尾段。

每个候选段都会返回来源单元、边界理由和置信度,然后由 Studio 展示给编辑者。

Studio 会在候选段超过 120 个非空字符时提醒,但这只是展示和审读的软阈值:

  • 人工合并产生超长候选时立即提示;
  • 最终确认仍有超长段时,第一次点击只警告并暂停;
  • 编辑确认语义上确实需要保留时,第二次点击可以继续。

古籍里的语义完整性不会服从一个绝对字符数。这个提醒只是帮助我发现“虽然技术上能显示,但不利于对照阅读”的长段落。

稳定段落身份怎样产生

确认导入以后,Backend 会在一个事务里创建:

  • Division 内容单元;
  • Segment 稳定语义段落;
  • 第一份 SegmentRevision 原文修订;
  • 来源文件、来源片段和段落之间的映射。

Segment 保存 source_unit_ids,这才是分段完整性检查的权威映射;source_line_numbers 只是为了溯源和兼容读取保留的派生字段。

ReadTao Studio 中的来源单元、语义段落与内容审校

段落锚点也不是数组下标。它采用无意义短哈希加内容单元、段落序号,例如:

c80e7dff-001-01

如果只是修订文字,segment_id 和锚点保持不变。如果把一个段落拆成两个,原段落归档,新段落通过 lineage 记录来源关系。这样后续才有机会为旧链接保留重定向,而不是让所有历史引用悄悄失效。

首次导入可以重做,但有明确门禁

真实整理中,第一次分层或分段不合适很正常。完全禁止重做,会迫使编辑者重新上传文件;允许随时硬删除,又会破坏修订和发布历史。

Studio 因此提供“重新处理本卷”,但只允许在这个内容单元仍处于首次导入草稿时使用:

  • 没有生成任务;
  • 没有派生内容;
  • 没有人工修订;
  • 没有进入审校;
  • 没有被任何发布快照引用。

满足条件时,系统清除首次产生的来源分层和语义段落,保留作品、底本、来源登记、原始文件和导入会话,再回到来源分层预览。

一旦已经进入后续流程,就必须通过正常修订、归档或发布回滚处理,不能再假装历史从未发生。

这条导入链真正保护的是什么

走完以后,一段公开原文不再只是数据库里的一串文字。它可以回答:

  • 属于哪部作品和哪个底本;
  • 来自哪份原始文件;
  • 对应原文件的哪些字符区间;
  • 为什么在这里形成语义边界;
  • 当前文字是哪一份修订;
  • 进入了哪一个发布版本。

这才是我认为 Studio 最重要的价值:不是提高“上传速度”,而是让长期、低频的个人整理仍然有证据、有状态,也能够暂停后再继续。

下一篇继续处理分段之后的内容:AI 只能写初稿:ReadTao 的内容生成、人工审校与失败重试