Files
ephron-ren-prd/prd-ai-daily-cross-day-dedup.md

8.1 KiB
Raw Blame History

AI日报跨天去重修复 PRD

日期: 2026-06-08 状态: 草稿 相关仓库: ephron_ren/ai-daily-report


1. 问题陈述

AI日报 pipeline 的重复新闻问题,经排查由三个缺陷叠加导致:

1.1 无跨天去重(主因,影响面 ~50%

pipeline 是纯 daily function每次运行从零开始采集 → 当天内去重 → 改写 → 发布。没有任何机制检查某条新闻的 URL 或内容是否已在之前某天的日报中发过。

证据: 对比 6/7 和 6/8 的发布内容,约 50-60% 的条目重复。这些重复几乎全部来自 InfoQ RSS每天固定 15 条)和 MIT Tech Review RSS每天固定 10 条),它们的 feed 会持续包含多天前的文章。URL 完全相同(canonicalize_url 已剥离 tracking params但因为跨天没有数据可对比。

1.2 同天内 title 相似度阈值过于严格(次因,影响面 2-5 条/天)

dedupe.py 中的 _possible_duplicates() 使用 difflib.SequenceMatcher 阈值 0.65 判断标题是否相似。对于中英文混排的标题,这个阈值过高,导致同一天内不同来源报道同一新闻时无法被识别为候选重复。

例子6/8

  • Item 49「OpenAI定制芯片核心成员Clive Chan跳槽至Anthropic」来源 X
  • Item 50「OpenAI芯片核心叛逃Anthropic就在量产前夜」来源 量子位)
  • → 同一件事normalize 后阈值约 0.55-0.60,未被 Stage 2 标记为 possible duplicateStage 3 语义去重也看不到。

1.3 AI HOT 与 RSS 同源(影响有限)

AI HOT 聚合的内容与 pipeline 单独抓取的 RSS 有重叠。同 URL 的情况已被当天 URL 去重兜底(_2 后缀),但同一事件不同 URL 的文章无法覆盖。


2. 目标

  1. 消除跨天重复:同一 URL 的新闻在发布过后,后续日报不再收录
  2. 提升同天去重召回率:修复 title 相似度阈值,让 Stage 3 语义去重能覆盖更多跨来源重复
  3. 零误杀:去重机制只能移除真正重复的内容,不能因为标题或 URL 相似而误删独立新闻

3. 方案设计

3.1 跨天去重Stage 2.5 — 新增 stage

在 Stage 2硬去重和 Stage 3语义去重之间插入一个新的 Stage 2.5:跨天去重

机制: 维护一个历史 URL 集合文件(~/.hermes/scripts/ai_morning_out/published_urls.json),每次 publish 后追加当天所有已发布 items 的 canonical_url。每次运行 Stage 2.5 时加载该文件,过滤掉 canonical_url 已存在于历史集合中的 items。

文件格式(published_urls.json

{
  "version": 1,
  "urls": {
    "https://infoq.com/news/2026/06/google-litertlm-gemma4": {
      "first_seen": "2026-06-07",
      "last_published": "2026-06-07",
      "titles": ["Google LiteRT-LM 利用 Gemma 4 多令牌预测实现 2.2 倍推理加速"]
    },
    ...
  },
  "updated_at": "2026-06-08T10:02:00+08:00"
}

设计要点:

  • URL 作为 dedup keycanonical_url),和 Stage 2 的 URL 去重逻辑一致
  • 只记录已发布的 items即跑完 Stage 7 assemble 后,最终出现在 blog_markdown.md 中的 items
  • 写入时机Stage 8 publish 成功后追加到历史文件
  • 不对标题做跨天语义去重(防误杀),仅对 URL
  • 配置化:可通过 sources.jsonpipeline.json 控制「跨天去重窗口」,默认 7 天
  • 空 canonical_url 的 item 跳过跨天去重

为什么只对 URL 不对 title 做跨天? 同一事件在不同天可能有不同角度的报道(例如第一天简短报道发布,第二天深度分析),不同 URL 说明是不同文章,不应视为重复。

3.2 标题相似度阈值下调

改动:dedupe.py_possible_duplicates()

将 title 相似度阈值从 0.65 下调至 0.50,并针对中英文混排场景做优化:

# 旧
ratio >= 0.65

# 新
ratio >= 0.50

原因: 下调后更多候选对进入 Stage 3 语义去重,由 LLM 判断是否真的重复。Stage 3 已有 max_deletion_ratio=0.5 的安全阀,不会误删过多。

同时考虑增加一种基于共享 token 比例的辅助判断(不必完全替换 edit distance

  • 计算两个 normalize 后标题的 token 交集大小/并集大小Jaccard 相似度)
  • 如果 Jaccard ≥ 0.4 且 edit distance ≥ 0.4,则标记为 possible duplicate

3.3 rss.py 增加 published_at 过滤

改动:sources/rss.pyfetch_rss()

对于 RSS 源,利用 <pubDate> 字段过滤掉发布日期超过 N 天的条目:

CROSS_DAY_FILTER_DAYS = {
    "InfoQ AI": 3,        # 只保留 3 天内发布的 InfoQ 文章
    "MIT科技评论AI": 5,
    # 量子位、橘鸦等不需要过滤,因为它们的 feed 本身已经当天
}

在 RSS fetcher 解析 <pubDate> 后,如果 published_at 距今超过配置的天数,直接丢弃。这能从源头上减少 InfoQ/MIT 的陈旧条目进入 pipeline。

注意: 这个过滤是跨天去重的补充优化,不是替代。即使 RSS 过滤后仍有少量陈旧条目Stage 2.5 的 URL 去重会兜底。


4. 实现计划

Phase 1Stage 2.5 跨天去重

步骤 文件 改动
1.1 ai_daily_report/models.py 新增 PublishedUrls 数据类version, urls, updated_at
1.2 ai_daily_report/dedupe.py 新增 cross_day_dedup_items(items, published_urls, max_age_days=7) 函数
1.3 ai_daily_report/pipeline.py 修改 run_stage0_to_stage2() 接受 published_urls 参数;新增 run_stage0_to_stage2_5()(含 stage 2.5
1.4 ai_daily_report/publish.py 修改 publish_markdown() 返回已发布的 items 列表;新增 update_published_urls() 写入历史文件
1.5 ai_daily_report/runner.py 加载 published_urls.json 传入 pipelinepublish 成功后更新
1.6 新文件 published_urls.json 初始为空文件
1.7 config/pipeline.json 新增 cross_day_dedup 配置段(enabled, max_age_days, history_path

Phase 2标题相似度优化

步骤 文件 改动
2.1 ai_daily_report/dedupe.py _possible_duplicates() 的阈值从 0.65 改为 0.50;增加 Jaccard 相似度辅助判断
2.2 测试 更新 _possible_duplicates 的单元测试阈值

Phase 3RSS 源过期过滤

步骤 文件 改动
3.1 ai_daily_report/sources/rss.py fetch_rss() 中按 published_at 过滤,丢弃超期条目
3.2 config/sources.json 为 InfoQ、MIT 等 RSS 源增加 max_item_age_days 字段

5. 风险与缓解

风险 缓解
跨天去重误杀不同 URL 的同事件新闻 只对 URL 做 key不碰 title。不同 URL 说明是不同的文章,保留。
title 阈值下调后 Stage 3 误删 max_deletion_ratio=0.5 安全阀保底LLM 判断语义重复而非关键词匹配
published_urls.json 文件损坏 JSON 解析失败时回退到无跨天去重log warning不影响当天发布
历史文件无限增长 默认保留 7 天窗口,超过自动清理;或限制文件大小上限
跨天去重后某天稿件太少 不改变。宁可少发也不发重复内容。

6. 验收标准

  1. 6/7 的重复文章(如 Google LiteRT-LM、Claude Code Dynamic Workflows、Dropbox Nova 等)在 6/9 的日报中不应出现
  2. 同一天内同一新闻的跨来源报道(如 量子位 + X + TechCrunch 报道同一事件)应在 Stage 3 语义去重中被识别并去重
  3. published_urls.json 在 publish 成功后正确更新,内容格式符合预期
  4. RSS 源中超过配置天数的文章不再出现在采集结果中
  5. 所有现有测试用例仍能通过
  6. 手动运行一次全流程dry-run 或 draft 模式)验证去重效果

7. 未纳入范围

  • 跨天语义去重:不比较不同天 items 的标题/摘要相似度,仅用 URL 去重。防止误判。
  • 增量采集不改变采集方式。RSS feed 全量拉取,在后面 stage 中过滤。
  • 历史数据回填:不追溯清理已有的已发布日报,只影响从部署日开始的运行。