Files
reader/docs/source-schema-design.md
T
2026-03-24 17:01:35 +08:00

11 KiB

Source Schema 设计草案

1. 文档目的

本文档用于定义阅读流系统中的来源与内容对象模型,目标是把“来源分类”的讨论收敛成一套可执行的数据结构,供后续的抓取、摘要、过滤、入库和推送流程统一使用。

这份文档关注的是数据抽象,而不是具体实现语言、数据库模型或 API 细节。


2. 设计目标

这套 schema 需要解决以下问题:

  • 不同渠道的内容如何进入同一条流水线
  • RSS、微信文章、普通网页、GitHub 更新如何被统一表示
  • 摘要器应该接收什么输入对象
  • 过滤规则应该依赖哪些标准字段
  • 入库与推送如何复用统一元信息

核心目标是建立稳定的中间层,避免后续各模块直接依赖渠道差异。


3. 设计原则

3.1 分层而不是混合

建议将系统中的对象分成三层:

  • source
  • item
  • document

每一层职责不同:

  • source:描述一个来源渠道本身
  • item:描述一次抓取到的候选内容
  • document:描述经过正文抽取和摘要后的标准内容对象

3.2 先标准化,再智能化

在进入摘要、过滤、入库前,应该先把不同渠道的内容统一映射成相同的对象结构,而不是在每个处理阶段临时兼容各种来源格式。

3.3 渠道类型与内容形态分离

建议明确区分:

  • 来源渠道是什么
  • 内容本身是什么形态

例如:

  • 微信文章和 RSS 博文可能都属于 article
  • GitHub Release 和 changelog 可能来自不同渠道,但内容形态更接近事件流

4. 对象分层

4.1 source

source 是来源配置对象,用来描述某个渠道本身是什么、如何接入、是否启用。

特点:

  • 偏静态
  • 由用户或系统配置维护
  • 不代表具体某一篇内容

4.2 item

item 是采集层产物,用来表示“这次抓回来的一个候选条目”。

特点:

  • 来自 RSS、API、HTML 抓取或转换 feed
  • 可能只有标题、链接和摘要
  • 不保证已经拿到完整正文

4.3 document

document 是标准内容对象,用来表示“已经过正文抽取和摘要处理,可以进入过滤、入库和推送阶段”。

特点:

  • 已经完成正文抽取
  • 已经有结构化摘要
  • 是后续规则引擎的主输入

5. 枚举设计

5.1 source_type

表示来源类型。

可选值:

  • feed
  • converted_feed
  • event_feed
  • page_watch
  • custom_source

说明:

  • feed:原生 RSS / Atom
  • converted_feed:由 RSSHub、wewe-rss、RSS-Bridge 等转换而来
  • event_feed:更像事件流,例如 GitHub Releases、Commits、Discussions
  • page_watch:轮询普通网页的更新情况
  • custom_source:为特殊站点定制的抓取源

5.2 ingest_mode

表示接入方式。

可选值:

  • feed_direct
  • feed_converted
  • api_backed
  • html_scraped
  • hybrid

说明:

  • feed_direct:直接消费 RSS / Atom
  • feed_converted:先转换成 feed,再统一消费
  • api_backed:通过 API 获取数据
  • html_scraped:直接抓 HTML 页面并解析
  • hybrid:先消费 feed 元数据,再按需抓正文

5.3 content_kind

表示内容形态。

可选值:

  • article
  • thread
  • release
  • changelog
  • video
  • mixed

说明:

  • article:标准文章
  • thread:串联式内容,例如帖子串、社交媒体长线程
  • release:版本发布说明
  • changelog:更新日志
  • video:视频内容
  • mixed:无法明确归入单一形态

5.4 fetch_state

表示候选内容的正文抓取状态。

可选值:

  • pending
  • fetched
  • failed
  • skipped

5.5 pipeline_state

表示内容在整条流水线中的处理状态。

可选值:

  • ingested
  • summarized
  • filtered
  • stored
  • pushed
  • dropped

6. source 对象设计

6.1 角色定义

source 用于描述一个来源本身,而不是某条具体内容。

它应该回答这些问题:

  • 这个来源是什么
  • 它属于哪一类来源
  • 用什么方式接入
  • 是否启用
  • 默认语言和默认内容形态是什么

6.2 建议字段

{
  "source_id": "wechat-aiweekly",
  "name": "AI Weekly 微信源",
  "source_type": "converted_feed",
  "ingest_mode": "feed_converted",
  "content_kind": "article",
  "base_url": "https://example.com",
  "feed_url": "https://example.com/feed.xml",
  "language": "zh",
  "enabled": true,
  "tags": ["ai", "newsletter"],
  "priority": 80
}

6.3 字段说明

  • source_id

    • 系统内部唯一标识
  • name

    • 人类可读名称
  • source_type

    • 来源类型
  • ingest_mode

    • 接入方式
  • content_kind

    • 默认内容形态
  • base_url

    • 来源站点主域名
  • feed_url

    • feed 地址;没有则可为空
  • language

    • 默认语言
  • enabled

    • 是否启用该来源
  • tags

    • 来源级标签,供过滤和统计使用
  • priority

    • 用于抓取调度、结果排序或推送优先级控制

6.4 最小可用版本

{
  "source_id": "my-blog",
  "source_type": "feed",
  "ingest_mode": "feed_direct",
  "feed_url": "https://example.com/rss.xml",
  "enabled": true
}

7. item 对象设计

7.1 角色定义

item 是采集层的标准对象,用来统一表示从不同渠道抓回来的候选内容。

它应该回答这些问题:

  • 这条候选内容来自哪个 source
  • 它的标题、链接和发布时间是什么
  • RSS 是否已经提供摘要或正文
  • 当前是否已抓到完整正文

7.2 建议字段

{
  "item_id": "sha256:xxx",
  "source_id": "wechat-aiweekly",
  "external_id": "feed-entry-id-or-url",
  "title": "文章标题",
  "url": "https://example.com/post/123",
  "author": "作者",
  "published_at": "2026-03-23T08:00:00Z",
  "discovered_at": "2026-03-23T10:00:00Z",
  "content_kind": "article",
  "language": "zh",
  "raw_summary": "RSS 提供的摘要",
  "raw_content": "RSS 提供的正文或片段",
  "metadata": {
    "feed_title": "源标题",
    "categories": ["AI", "Tools"]
  },
  "fetch_state": "pending"
}

7.3 字段说明

  • item_id

    • 内部唯一标识
    • 建议由 source_id + canonical_url + published_at 做 hash 生成
  • source_id

    • 指向来源配置对象
  • external_id

    • 来源平台本身的 id,例如 RSS entry id、原始 URL 或平台内容 id
  • title

    • 候选内容标题
  • url

    • 原始内容链接
  • author

    • 作者信息;如果无法获取可为空
  • published_at

    • 原始发布时间
  • discovered_at

    • 被系统发现的时间
  • content_kind

    • 当前条目的内容形态
  • language

    • 当前条目的语言
  • raw_summary

    • 来源提供的摘要内容
  • raw_content

    • 来源直接提供的正文、正文片段或富文本转纯文本结果
  • metadata

    • 保留来源特有元信息,例如 feed 分类、栏目名、标签
  • fetch_state

    • 正文抓取状态

7.4 最小可用版本

{
  "item_id": "sha256:xxx",
  "source_id": "my-blog",
  "title": "文章标题",
  "url": "https://example.com/post/1",
  "published_at": "2026-03-23T08:00:00Z",
  "raw_summary": "摘要",
  "raw_content": "",
  "fetch_state": "pending"
}

8. document 对象设计

8.1 角色定义

document 是经过正文抽取和摘要处理后的标准内容对象。

它应该回答这些问题:

  • 最终正文是什么
  • 摘要和高亮点是什么
  • 这个内容值不值得进入下一步过滤或入库
  • 当前它处于流水线哪个阶段

8.2 建议字段

{
  "document_id": "sha256:yyy",
  "item_id": "sha256:xxx",
  "source_id": "wechat-aiweekly",
  "url": "https://example.com/post/123",
  "title": "文章标题",
  "author": "作者",
  "published_at": "2026-03-23T08:00:00Z",
  "language": "zh",
  "content_kind": "article",
  "plain_text": "抽取后的正文",
  "summary": "3-5句摘要",
  "highlights": ["要点1", "要点2", "要点3"],
  "keywords": ["rss", "mcp"],
  "topics": ["workflow", "knowledge-base"],
  "scores": {
    "relevance": 0.91,
    "novelty": 0.72,
    "readability": 0.84
  },
  "quality_flags": {
    "is_paywalled": false,
    "is_truncated": false,
    "is_low_content": false
  },
  "pipeline_state": "summarized"
}

8.3 字段说明

  • document_id

    • document 的唯一标识
  • item_id

    • 回溯到原始候选条目
  • source_id

    • 回溯到来源配置
  • url

    • 原始链接
  • title

    • 标题
  • author

    • 作者信息
  • published_at

    • 发布时间
  • language

    • 语言
  • content_kind

    • 内容形态
  • plain_text

    • 抽取后的正文纯文本
  • summary

    • 结构化摘要中的主摘要
  • highlights

    • 关键要点
  • keywords

    • 关键词
  • topics

    • 主题标签
  • scores

    • 打分结果,例如相关性、新颖度、可读性
  • quality_flags

    • 内容质量标志,例如是否付费墙、是否截断、是否低信息密度
  • pipeline_state

    • 当前流水线状态

8.4 最小可用版本

{
  "document_id": "sha256:yyy",
  "item_id": "sha256:xxx",
  "title": "文章标题",
  "url": "https://example.com/post/1",
  "plain_text": "正文",
  "summary": "3-5句摘要",
  "highlights": ["点1", "点2"],
  "scores": {
    "relevance": 0.9
  },
  "pipeline_state": "summarized"
}

9. 三层对象之间的关系

建议关系如下:

  • 一个 source 可以产生多个 item
  • 一个 item 在多数情况下对应一个 document
  • document 是 item 经过正文抽取和摘要处理后的结果

简化理解:

  • source 解决“从哪里来”
  • item 解决“抓到了什么”
  • document 解决“它值不值得留下”

10. 为什么必须分层

如果不区分 source、item 和 document,后面会出现这些问题:

  • 来源配置和运行时数据混在一起
  • RSS 字段、网页抓取字段和摘要字段混在一起
  • 过滤规则既要判断来源,又要判断内容质量,还要判断摘要分数
  • 不同来源的差异会不断渗透到下游模块

分层后的收益是:

  • 采集层可以独立演进
  • 摘要器输入更稳定
  • 过滤器只依赖统一对象,不依赖来源细节
  • 入库和推送更容易复用

11. 当前阶段的最小实现建议

为了降低复杂度,第一阶段建议:

  • 只支持 feed 和 converted_feed
  • 只实现 source、item、document 这三层基础对象
  • item 只要求能容纳 RSS 拉回来的标题、链接、摘要、正文片段
  • document 只要求能容纳正文、摘要、高亮点和基础打分

这样做的好处是:

  • 结构足够稳定
  • 成本足够低
  • 可以直接支撑下一步 summary-core 设计

12. 后续可继续讨论的问题

  1. 是否需要给 source 增加抓取周期、超时、重试策略字段
  2. item_id 和 document_id 的生成策略是否要统一
  3. metadata 是否需要按来源类型拆出专用字段
  4. document 的 scores 和 quality_flags 是否要定义更严格的 schema
  5. 这套 schema 最终是落成 JSON 文件、数据库表,还是 Pydantic 模型

13. 当前阶段的一句话结论

当前最合适的做法是先固定 source -> item -> document 这三层对象模型,再围绕它设计 summary-core、过滤器和入库流程。