Files
reader/docs/openclaw/article-candidate-daily-digest-schema.md

13 KiB

Article Candidate / OpenClaw Input / Daily Digest 设计

1. 文档目的

本文档正式定义 OpenClaw 日报链路中的三层对象:

  • ArticleCandidateRecord
  • OpenClawCandidateInput
  • DailyDigest

目标不是只定义字段,而是明确每一层对象的职责边界,避免把“内部记录对象”和“发给 OpenClaw 的下游输入对象”混成同一个 payload。


2. 这次为什么要改

前一版设计里,article_candidate 同时承担了三种职责:

  1. 本地审计与追溯
  2. 规则过滤后的内部候选记录
  3. 发给 OpenClaw 的下游输入

这会直接带来三个问题:

  • 字段过重
    • article.plain_text 这类正文内容会显著增加 token 消耗
  • 字段过细
    • filter_decision.matches、matched_rules、labels 这类规则证据链更适合本地审计,不适合发给下游聚合器
  • 字段过耦合
    • source_refs 是本地文件系统路径,对 OpenClaw 没有意义,反而会让上下游绑定内部实现

批量跑完 FreshRSS -> extraction -> LLM summary -> filter -> article_candidate 后,这个问题已经非常清楚:

  • 本地对象需要尽量保留信息,便于追溯误判
  • OpenClaw 输入需要尽量精简,便于聚合日报和控制 token

因此,正确改法不是继续给一个 article_candidate 做加减法,而是把对象拆层。


3. 设计结论

建议正式拆成三层:

3.1 ArticleCandidateRecord

定位:

  • 内部候选记录对象
  • 面向本地留档、审计、调试、复跑

它回答的问题是:

  • 这条候选内容从哪里来
  • 提取、摘要、过滤各阶段到底产出了什么
  • 为什么规则引擎会给出当前决策
  • 后续如果要重放或排查,该去哪里追溯

3.2 OpenClawCandidateInput

定位:

  • 发给 OpenClaw 的精简输入对象
  • 面向日报聚合与编排

它回答的问题是:

  • 这条内容是什么
  • 为什么值得放进候选池
  • 应该如何排序和栏目化

它不负责保存本地调试信息,也不负责承载完整正文。

3.3 DailyDigest

定位:

  • OpenClaw 聚合后的日级主产物
  • 面向日报汇报、知识沉淀、人工 review

它回答的问题是:

  • 今天最值得关注的内容是什么
  • 这些内容能提炼出什么结论
  • 哪些内容值得进入长期知识库

4. 推荐链路

推荐把链路明确为:

item -> article -> summary -> filter_result -> ArticleCandidateRecord -> OpenClawCandidateInput -> DailyDigest

这里有两个关键转换:

  1. summary + filter_result 先生成 ArticleCandidateRecord
    • 这是内部标准记录
  2. ArticleCandidateRecord 再投影成 OpenClawCandidateInput
    • 这是跨系统传输对象

这样做的本质是:

  • 内部对象追求可追溯
  • 下游对象追求低耦合、低 token、高可消费性

5. ArticleCandidateRecord 设计

5.1 角色定义

ArticleCandidateRecord 是当前项目内部的正式候选记录对象。

它不是:

  • 发给 OpenClaw 的最终 payload
  • 发给用户的日报消息
  • 最终知识库对象

它是下游所有再加工动作之前的“内部事实底稿”。

5.2 设计原则

  • 保留上游对象,便于调试和重放
  • 保留规则证据链,便于解释误判
  • 保留本地引用,便于审计
  • 允许后续重新生成不同版本的下游 payload

5.3 建议字段

{
  "candidate_id": "cand:sha256:xxx",
  "item": {"...": "Item"},
  "article": {"...": "ExtractedArticle"},
  "summary": {"...": "LlmSummaryResult"},
  "filter_result": {"...": "FilterDecisionResult"},
  "digest_section_hint": "insights",
  "digest_rank": 60,
  "review_state": "pending",
  "rendered_markdown": null,
  "metadata": {
    "generated_at": "2026-03-25T10:00:00Z",
    "pipeline_version": "v2",
    "producer": "summary_mcp",
    "run_id": "candidate-2026-03-25-001"
  },
  "source_refs": {
    "item_path": "outputs/freshrss/items/batch/item-01.item.json",
    "extracted_path": "outputs/freshrss/extracted/batch/item-01.extracted.json",
    "summary_path": "outputs/freshrss/summary/batch/item-01/result.loop.json",
    "filter_path": "outputs/freshrss/filter/batch/item-01.filter.json"
  }
}

5.4 字段说明

  • candidate_id

    • 候选记录唯一标识
    • 建议基于 item_id 或 extract_id 派生
  • item

    • 保留来源、标题、发布时间、作者等上游元数据
  • article

    • 保留正文提取结果与质量标记
    • 供本地调试与重放使用
  • summary

    • 保留结构化摘要结果
    • 是后续 OpenClaw 输入映射的主要来源
  • filter_result

    • 保留规则引擎的完整裁决与命中细节
    • 主要用于解释和排查
  • digest_section_hint

    • 给 OpenClaw 的栏目建议
  • digest_rank

    • 给 OpenClaw 的排序信号
  • review_state

    • 记录人工复核状态
  • rendered_markdown

    • 可选的人类可读卡片
    • 用于调试或 fallback 展示
  • metadata

    • 记录运行时元信息
  • source_refs

    • 仅用于本地追溯
    • 不应进入跨系统 payload

5.5 为什么内部对象要保留正文和完整过滤结果

因为这层对象不是给 OpenClaw 直接消费的,而是给系统内部留档的。

如果这里过早裁掉字段,会丢失两种关键能力:

  1. 误判排查能力
    • 例如文章被误判为 paywall 时,必须能回看 article 与 filter_result
  2. 下游重放能力
    • 后面如果调整了 OpenClaw payload 或摘要策略,可以从这层重新投影,而不必重新抓取全文

6. OpenClawCandidateInput 设计

6.1 角色定义

OpenClawCandidateInput 是当前项目发给 OpenClaw 的正式输入对象。

它应当是:

  • 扁平化的
  • 精简的
  • 稳定的
  • 不依赖本地文件路径的

6.2 设计原则

  • 不携带正文全文
  • 不携带规则引擎的完整证据链
  • 不携带本地 source_refs
  • 只保留 OpenClaw 聚合日报真正需要的字段

6.3 建议字段

{
  "candidate_id": "cand:sha256:xxx",
  "title": "套壳中国大模型撑起500亿美元估值?扒一扒 Cursor 的套壳疑云",
  "url": "http://www.ruanyifeng.com/blog/2026/03/kimi-cursor.html",
  "published_at": "2026-03-21T10:19:11Z",
  "author": "阮一峰",
  "source_name": "阮一峰博客",
  "summary": "2 到 3 句话摘要",
  "highlights": ["...", "...", "..."],
  "keywords": ["Cursor", "Kimi K2.5"],
  "topics": ["人工智能", "商业伦理"],
  "category": "观点评论",
  "worth_keeping": true,
  "worth_reason": "有持续性的行业洞察价值",
  "selection_decision": "review",
  "selection_reason": "worth_keeping=true but no stronger keep rule matched",
  "digest_section_hint": "insights",
  "digest_rank": 60
}

6.4 字段说明

  • candidate_id

    • 与内部记录保持同一主键,便于回溯
  • title / url / published_at / author / source_name

    • OpenClaw 做日报聚合所需的最小来源信息
  • summary / highlights / keywords / topics / category

    • OpenClaw 聚合时真正需要的语义材料
  • worth_keeping / worth_reason

    • 摘要层提供的长期价值判断信号
  • selection_decision / selection_reason

    • 规则层的压缩结果
    • 用于告诉 OpenClaw:这条为什么进入候选池,以及应该怎样对待
  • digest_section_hint / digest_rank

    • OpenClaw 做栏目化和排序时最直接的信号

6.5 为什么不用正文全文

因为 OpenClaw 当前承担的是“日报聚合”角色,不是“再次全文阅读器”。

如果把 article.plain_text 一并发给 OpenClaw,会出现三个问题:

  1. token 浪费
    • 每条都携带全文,批量聚合时成本会迅速膨胀
  2. 角色混乱
    • OpenClaw 会被迫重新阅读原文,而不是消费上游已经压缩好的摘要结果
  3. 输入不稳定
    • 正文长度差异很大,会让聚合阶段的上下文更难控制

因此,正文应保留在 ArticleCandidateRecord,而不是进入 OpenClawCandidateInput。

6.6 为什么过滤结果要压缩

完整的 filter_result 适合本地审计,不适合跨系统传输。

OpenClaw 通常只需要知道:

  • 这条是 keep / review / drop 中的哪一种
  • 为什么会进入候选池
  • 优先级大概是多少

它通常不需要知道:

  • 具体命中了哪几条规则
  • 每条规则携带了哪些 labels
  • 完整的 matches 证据链

所以建议把规则层压缩为:

  • selection_decision
  • selection_reason
  • digest_rank

6.7 为什么不要 source_refs

因为 source_refs 是本地实现细节,不是业务语义。

把它发给 OpenClaw 的问题有两个:

  1. 没价值
    • OpenClaw 无法消费本地磁盘路径
  2. 高耦合
    • 这会让 OpenClaw 输入隐式依赖当前项目的目录结构与输出命名方式

因此,source_refs 应只保留在 ArticleCandidateRecord。


7. 两个对象之间的映射关系

建议映射规则如下:

  • OpenClawCandidateInput.candidate_id
    • 来自 ArticleCandidateRecord.candidate_id
  • title / url / published_at / author
    • 优先来自 item
  • source_name
    • 优先来自 item.source_title,没有时可退化为域名或作者来源
  • summary / highlights / keywords / topics / category
    • 来自 summary
  • worth_keeping / worth_reason
    • 来自 summary.worth_keeping 与 summary.reason
  • selection_decision
    • 来自 filter_result.decision
  • selection_reason
    • 可取 filter_result.reasons[0] 或压缩后的组合说明
  • digest_section_hint
    • 来自 ArticleCandidateRecord.digest_section_hint
  • digest_rank
    • 来自 ArticleCandidateRecord.digest_rank

简化理解:

  • ArticleCandidateRecord 保留证据
  • OpenClawCandidateInput 保留结论

8. DailyDigest 设计

8.1 角色定义

DailyDigest 是 OpenClaw 聚合多个 OpenClawCandidateInput 后形成的日级主产物。

它不应该再携带单篇全文,也不应该回退到内部调试结构。

8.2 建议字段

{
  "digest_id": "digest:2026-03-25",
  "date": "2026-03-25",
  "title": "2026-03-25 阅读日报",
  "summary": "今天的输入主要围绕 AI 工具、工程实践和软件产品策略展开。",
  "sections": [
    {
      "section_id": "insights",
      "title": "观察与判断",
      "summary": "今天更值得关注的是 AI 对软件行业护城河和商业包装的影响。",
      "items": [
        {
          "candidate_id": "cand:sha256:xxx",
          "title": "文章标题",
          "summary": "单篇摘要压缩版",
          "why_it_matters": "为什么今天值得被放进这个栏目",
          "action": "值得继续跟进"
        }
      ]
    }
  ],
  "top_items": ["cand:sha256:xxx"],
  "key_takeaways": [
    "摘要层和知识沉淀层应分开。",
    "规则证据链应保留在本地,而不是直接下发给聚合器。"
  ],
  "watchlist": [
    "继续收敛 paywall 误判。"
  ],
  "candidate_ids": ["cand:sha256:xxx"],
  "source_refs": [
    {
      "candidate_id": "cand:sha256:xxx",
      "url": "https://example.com/post/1"
    }
  ],
  "editor_notes": "今天的输入以观点和周刊类内容为主。",
  "stats": {
    "candidate_total": 12,
    "kept_total": 4,
    "review_total": 6,
    "dropped_total": 2
  },
  "metadata": {
    "generated_at": "2026-03-25T21:00:00Z",
    "producer": "openclaw",
    "pipeline_version": "v2"
  }
}

8.3 sections.items[] 子结构建议

{
  "candidate_id": "cand:sha256:xxx",
  "title": "文章标题",
  "summary": "单篇摘要压缩版",
  "why_it_matters": "为什么这条内容今天值得看",
  "action": "值得试用 / 值得跟进 / 值得收藏 / 待验证"
}

这里最关键的不是原始摘要复述,而是 why_it_matters。

因为日报的价值不在于再列一遍新闻,而在于表达“今天为什么值得看”。


9. 为什么这样设计更合理

9.1 控制 token 成本

把正文全文挡在 OpenClaw 之前,能显著降低聚合阶段的 token 开销。

9.2 保持对象职责单一

  • ArticleCandidateRecord 负责本地审计
  • OpenClawCandidateInput 负责下游消费
  • DailyDigest 负责日级成品

每层只做一件事,后续更容易演进。

9.3 降低上下游耦合

OpenClaw 不需要知道本地 outputs/ 目录结构,也不应该依赖规则引擎的内部细节。

9.4 保留调试与重放能力

内部对象依然保留全文、质量标记、完整过滤证据链,因此不会因为“下游精简”而损失工程可维护性。

9.5 为后续人审与知识库沉淀留出口

当日报和知识库真正接起来时:

  • OpenClaw 基于精简输入做日报聚合
  • 人工基于日报成品做最终判断
  • 本地记录对象作为审计底稿长期保留

这个边界是清晰且可持续的。


10. 当前阶段的实现建议

建议按这个顺序推进:

  1. 在文档层正式采用这三个对象
  2. 将当前代码里的 article_candidate 重命名或重新定位为 ArticleCandidateRecord
  3. 新增 OpenClawCandidateInput 模型
  4. 增加 ArticleCandidateRecord -> OpenClawCandidateInput 的转换逻辑
  5. 让 OpenClaw 只消费精简输入对象
  6. 后续再补 DailyDigest 的真实生成逻辑

11. 一句话结论

这次改动的本质,不是“删掉几个字段”,而是把“内部候选记录对象”和“发给 OpenClaw 的精简输入对象”彻底分层;只有这样,当前项目才能同时保留审计能力、控制 token 成本,并稳定服务 OpenClaw 的日报聚合。