13 KiB
Article Candidate / OpenClaw Input / Daily Digest 设计
1. 文档目的
本文档正式定义 OpenClaw 日报链路中的三层对象:
ArticleCandidateRecordOpenClawCandidateInputDailyDigest
目标不是只定义字段,而是明确每一层对象的职责边界,避免把“内部记录对象”和“发给 OpenClaw 的下游输入对象”混成同一个 payload。
2. 这次为什么要改
前一版设计里,article_candidate 同时承担了三种职责:
- 本地审计与追溯
- 规则过滤后的内部候选记录
- 发给 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
这里有两个关键转换:
summary + filter_result先生成ArticleCandidateRecord- 这是内部标准记录
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 直接消费的,而是给系统内部留档的。
如果这里过早裁掉字段,会丢失两种关键能力:
- 误判排查能力
- 例如文章被误判为
paywall时,必须能回看article与filter_result
- 例如文章被误判为
- 下游重放能力
- 后面如果调整了 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,会出现三个问题:
- token 浪费
- 每条都携带全文,批量聚合时成本会迅速膨胀
- 角色混乱
- OpenClaw 会被迫重新阅读原文,而不是消费上游已经压缩好的摘要结果
- 输入不稳定
- 正文长度差异很大,会让聚合阶段的上下文更难控制
因此,正文应保留在 ArticleCandidateRecord,而不是进入 OpenClawCandidateInput。
6.6 为什么过滤结果要压缩
完整的 filter_result 适合本地审计,不适合跨系统传输。
OpenClaw 通常只需要知道:
- 这条是
keep / review / drop中的哪一种 - 为什么会进入候选池
- 优先级大概是多少
它通常不需要知道:
- 具体命中了哪几条规则
- 每条规则携带了哪些 labels
- 完整的
matches证据链
所以建议把规则层压缩为:
selection_decisionselection_reasondigest_rank
6.7 为什么不要 source_refs
因为 source_refs 是本地实现细节,不是业务语义。
把它发给 OpenClaw 的问题有两个:
- 没价值
- OpenClaw 无法消费本地磁盘路径
- 高耦合
- 这会让 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. 当前阶段的实现建议
建议按这个顺序推进:
- 在文档层正式采用这三个对象
- 将当前代码里的
article_candidate重命名或重新定位为ArticleCandidateRecord - 新增
OpenClawCandidateInput模型 - 增加
ArticleCandidateRecord -> OpenClawCandidateInput的转换逻辑 - 让 OpenClaw 只消费精简输入对象
- 后续再补
DailyDigest的真实生成逻辑
11. 一句话结论
这次改动的本质,不是“删掉几个字段”,而是把“内部候选记录对象”和“发给 OpenClaw 的精简输入对象”彻底分层;只有这样,当前项目才能同时保留审计能力、控制 token 成本,并稳定服务 OpenClaw 的日报聚合。