docs: add AI daily semantic dedupe recall PRD
This commit is contained in:
355
prd-ai-daily-semantic-dedupe-recall-fix.md
Normal file
355
prd-ai-daily-semantic-dedupe-recall-fix.md
Normal file
@@ -0,0 +1,355 @@
|
|||||||
|
# PRD:AI 日报语义重复召回修复
|
||||||
|
|
||||||
|
## 1. 背景
|
||||||
|
|
||||||
|
2026-06-10 发布的 AI 日报出现多条语义上重复或高度重叠的新闻。用户反馈后,对发布页面与本地运行产物进行排查:
|
||||||
|
|
||||||
|
- 发布页面:`https://blog.ephron.ren/posts/ai-2026-06-10`
|
||||||
|
- 本地 Markdown:`~/.hermes/scripts/ai_morning_out/2026-06-10/blog_markdown.md`
|
||||||
|
- 本地运行报告:`~/.hermes/scripts/ai_morning_out/2026-06-10/run_report.json`
|
||||||
|
|
||||||
|
## 2. 当前问题
|
||||||
|
|
||||||
|
### 2.1 直接现象
|
||||||
|
|
||||||
|
6/10 日报中至少存在以下重复/高度重叠样本:
|
||||||
|
|
||||||
|
| 编号 | 标题 | 问题类型 |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | Anthropic 发布 Claude Fable 5 与 Claude Mythos 5 | 与 7、8 属于同一模型发布事件链,信息重叠 |
|
||||||
|
| 7 | Claude Mythos 与 Claude Fable 即将发布消息流出 | 与 1、8 重叠,且“即将发布”在正式发布后应被合并或降级 |
|
||||||
|
| 8 | Claude Mythos 5 发布,主打更强代码能力 | 与 1 属于同一发布事件的补充报道 |
|
||||||
|
| 18 | OpenRouter 推出 Advisor 工具 | 与 37 同属 OpenRouter 产品/集成动态,需判断是否合并为一条 OpenRouter 动态 |
|
||||||
|
| 37 | OpenRouter 发布 Cursor 集成指南 | 与 18 弱相关,至少应进入语义去重/合并审查候选 |
|
||||||
|
|
||||||
|
> 注:18/37 是否最终合并需要 LLM 或规则判断,但它们不应完全绕过去重审查。
|
||||||
|
|
||||||
|
### 2.2 运行报告暴露的问题
|
||||||
|
|
||||||
|
`run_report.json` 中关键字段:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"stage2": {
|
||||||
|
"input_count": 50,
|
||||||
|
"output_count": 50,
|
||||||
|
"removed_count": 0,
|
||||||
|
"groups": [],
|
||||||
|
"possible_duplicates": []
|
||||||
|
},
|
||||||
|
"stage3": {
|
||||||
|
"input_count": 41,
|
||||||
|
"candidate_group_count": 0,
|
||||||
|
"removed_count": 0,
|
||||||
|
"duplicate_groups": [],
|
||||||
|
"uncertain": [],
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
含义:
|
||||||
|
|
||||||
|
1. Stage 2 没有召回任何可能重复候选;
|
||||||
|
2. Stage 3 语义去重完全没有候选可审;
|
||||||
|
3. 因此当天所有语义重复都不是 LLM 判断错误,而是候选召回阶段失败。
|
||||||
|
|
||||||
|
## 3. 根因分析
|
||||||
|
|
||||||
|
### 3.1 Stage 3 被 Stage 2 候选完全限制
|
||||||
|
|
||||||
|
当前流水线:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# ai_daily_report/pipeline.py
|
||||||
|
candidates = [
|
||||||
|
candidate
|
||||||
|
for candidate in stage2_5_result["reports"]["stage2"].get("possible_duplicates", [])
|
||||||
|
if set(candidate.get("item_ids", [])).issubset(remaining_ids)
|
||||||
|
]
|
||||||
|
semantic_items, stage3_report = semantic_dedup_items(items, candidates, ...)
|
||||||
|
```
|
||||||
|
|
||||||
|
Stage 3 只处理 Stage 2 的 `possible_duplicates`。如果 Stage 2 召回为 0,Stage 3 不会主动发现任何语义重复。
|
||||||
|
|
||||||
|
### 3.2 Stage 2 召回方式过窄
|
||||||
|
|
||||||
|
当前 Stage 2 候选召回只基于标题字符串相似度:
|
||||||
|
|
||||||
|
```python
|
||||||
|
TITLE_SIMILARITY_THRESHOLD = 0.50
|
||||||
|
TOKEN_JACCARD_THRESHOLD = 0.40
|
||||||
|
TOKEN_EDIT_DISTANCE_THRESHOLD = 0.40
|
||||||
|
|
||||||
|
if ratio >= 0.50 or (ratio >= 0.40 and jaccard >= 0.40):
|
||||||
|
possible.append(...)
|
||||||
|
```
|
||||||
|
|
||||||
|
问题:
|
||||||
|
|
||||||
|
- 对中文标题改写、英文品牌词、爆料/正式发布/媒体解读这种同事件不同表述不敏感;
|
||||||
|
- 只看标题,不看摘要、URL 域名、实体、模型名、公司名;
|
||||||
|
- 阈值实际代码为 `TOKEN_JACCARD_THRESHOLD = 0.40`,高于技能文档里曾记录的 0.25,召回更保守;
|
||||||
|
- 只在改写前做召回,Stage 4 改写后标题更统一、更容易看出重复,但没有二次复检。
|
||||||
|
|
||||||
|
### 3.3 缺少“同事件合并”概念
|
||||||
|
|
||||||
|
当前去重只删除“同一新闻”,没有明确处理:
|
||||||
|
|
||||||
|
- 爆料 + 正式发布;
|
||||||
|
- 官方发布 + 媒体补充;
|
||||||
|
- 同一模型/产品的多个角度;
|
||||||
|
- 同一公司当天多个弱相关小动态。
|
||||||
|
|
||||||
|
因此,系统倾向于保留所有不同 URL,即使读者看到的是同一事件的拆碎版本。
|
||||||
|
|
||||||
|
## 4. 产品目标
|
||||||
|
|
||||||
|
### 4.1 目标
|
||||||
|
|
||||||
|
将 AI 日报的去重从“标题相似去重”升级为“候选召回 + 语义审查 + 同事件合并”的两层机制:
|
||||||
|
|
||||||
|
1. 提高语义重复候选召回率;
|
||||||
|
2. 让 Stage 3 审查更多合理候选,而不是 0 候选直接跳过;
|
||||||
|
3. 对同一事件的爆料、发布、媒体解读进行合并或主次处理;
|
||||||
|
4. 保持用户偏好:不做精选,不按重要性删新闻,只删除/合并重复信息。
|
||||||
|
|
||||||
|
### 4.2 非目标
|
||||||
|
|
||||||
|
- 不引入主观新闻精选;
|
||||||
|
- 不按重要性筛掉独立新闻;
|
||||||
|
- 不为了减少条数而牺牲覆盖率;
|
||||||
|
- 不做跨天语义去重,跨天仍以 URL 历史去重为主,避免误删后续进展。
|
||||||
|
|
||||||
|
## 5. 需求设计
|
||||||
|
|
||||||
|
### 5.1 新增 Stage 2.8:语义候选召回
|
||||||
|
|
||||||
|
在 Stage 2.5 之后、Stage 3 之前新增候选召回层:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Stage 0 采集
|
||||||
|
Stage 1 归一化
|
||||||
|
Stage 2 硬去重 + 标题候选
|
||||||
|
Stage 2.5 跨天 URL 去重
|
||||||
|
Stage 2.8 语义候选召回(新增)
|
||||||
|
Stage 3 LLM 语义去重/合并
|
||||||
|
Stage 4 改写
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Stage 2.8 输入:Stage 2.5 后剩余 items。
|
||||||
|
Stage 2.8 输出:候选 pair/group,合并进 Stage 3 的 candidates。
|
||||||
|
|
||||||
|
### 5.2 候选召回特征
|
||||||
|
|
||||||
|
Stage 2.8 应至少使用以下非 LLM 特征:
|
||||||
|
|
||||||
|
| 特征 | 用途 |
|
||||||
|
|---|---|
|
||||||
|
| 标题字符相似度 | 保留现有能力 |
|
||||||
|
| 标题 token Jaccard | 保留现有能力,但阈值可调 |
|
||||||
|
| 摘要 token Jaccard | 识别标题不同但摘要实体重叠的新闻 |
|
||||||
|
| 公司/产品/模型实体重叠 | 识别 Claude Fable/Mythos、OpenRouter、Cursor 等同主题事件 |
|
||||||
|
| URL 域名与来源类型 | 区分官方源、媒体源、社交源 |
|
||||||
|
| 时间/状态词 | 识别“即将发布 / 发布 / 亮相 / 报道称”等事件阶段 |
|
||||||
|
|
||||||
|
### 5.3 实体抽取规则(第一版)
|
||||||
|
|
||||||
|
先用规则实现,不新增 LLM 调用:
|
||||||
|
|
||||||
|
- 英文连续词/型号:`Claude Fable 5`、`Claude Mythos 5`、`OpenRouter Advisor`、`Gemma 4 12B`;
|
||||||
|
- 常见公司/平台词典:OpenAI、Anthropic、Google DeepMind、Cohere、OpenRouter、Cursor、Claude、Gemini、Gemma、MiMo;
|
||||||
|
- 中文产品/公司可从标题和摘要中按词典匹配;
|
||||||
|
- 同一 pair 满足以下任一条件进入候选:
|
||||||
|
- 共享 2 个以上强实体;
|
||||||
|
- 共享 1 个强实体,且摘要 Jaccard 超过阈值;
|
||||||
|
- 同公司 + 同产品家族 + 标题/摘要出现发布阶段词。
|
||||||
|
|
||||||
|
### 5.4 候选数量控制
|
||||||
|
|
||||||
|
为避免 Stage 3 prompt 过大:
|
||||||
|
|
||||||
|
- 每条新闻最多关联 5 个候选;
|
||||||
|
- 全局候选 pair 默认上限 80;
|
||||||
|
- 候选按分数排序:实体重叠 > 摘要相似 > 标题相似;
|
||||||
|
- 分数低于阈值的 pair 不进入 Stage 3;
|
||||||
|
- run_report 记录被截断数量。
|
||||||
|
|
||||||
|
### 5.5 Stage 3 输出语义增强
|
||||||
|
|
||||||
|
Stage 3 prompt 需要区分三类结果:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"duplicate_groups": [],
|
||||||
|
"merge_groups": [],
|
||||||
|
"not_duplicates": [],
|
||||||
|
"uncertain": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
含义:
|
||||||
|
|
||||||
|
- `duplicate_groups`:同一新闻,删除重复项;
|
||||||
|
- `merge_groups`:同一事件不同角度,保留一条主项,并把其他来源作为补充来源/补充要点;
|
||||||
|
- `not_duplicates`:独立新闻;
|
||||||
|
- `uncertain`:不确定,默认保留。
|
||||||
|
|
||||||
|
### 5.6 同事件合并策略
|
||||||
|
|
||||||
|
当 Stage 3 返回 `merge_groups`:
|
||||||
|
|
||||||
|
- 选择官方源或信息更完整的项作为主项;
|
||||||
|
- 被合并项不单独出现在日报条目中;
|
||||||
|
- 主项 summary 增加一两句补充信息;
|
||||||
|
- 主项链接保留主链接,并在 `duplicate_sources` 或新增 `merged_sources` 中记录补充来源;
|
||||||
|
- 不改变“只去重不精选”的原则:这是合并重复信息,不是筛选新闻。
|
||||||
|
|
||||||
|
## 6. 6/10 样本验收用例
|
||||||
|
|
||||||
|
实现后,使用 2026-06-10 的 41 条最终样本回放:
|
||||||
|
|
||||||
|
### 6.1 必须进入候选审查
|
||||||
|
|
||||||
|
| 样本 | 期望 |
|
||||||
|
|---|---|
|
||||||
|
| 1 Anthropic 发布 Claude Fable 5 与 Claude Mythos 5 + 7 Claude Mythos 与 Claude Fable 即将发布消息流出 | 必须进入 Stage 3 候选 |
|
||||||
|
| 1 Anthropic 发布 Claude Fable 5 与 Claude Mythos 5 + 8 Claude Mythos 5 发布,主打更强代码能力 | 必须进入 Stage 3 候选 |
|
||||||
|
| 7 Claude Mythos 与 Claude Fable 即将发布消息流出 + 8 Claude Mythos 5 发布,主打更强代码能力 | 必须进入 Stage 3 候选 |
|
||||||
|
| 18 OpenRouter 推出 Advisor 工具 + 37 OpenRouter 发布 Cursor 集成指南 | 应进入 Stage 3 候选,由 LLM 判断是否合并或保留 |
|
||||||
|
|
||||||
|
### 6.2 必须合并或删除
|
||||||
|
|
||||||
|
| 样本 | 期望 |
|
||||||
|
|---|---|
|
||||||
|
| 7 + 1 | 7 作为爆料消息,在 1 官方发布存在时不应独立成条 |
|
||||||
|
| 8 + 1 | 8 如果只是 1 的媒体补充,应合并进 1;如含独立重要信息,也应作为 1 的补充来源而非独立条目 |
|
||||||
|
|
||||||
|
### 6.3 必须保留
|
||||||
|
|
||||||
|
以下不能因为共享公司/平台词而误删:
|
||||||
|
|
||||||
|
| 样本 | 原因 |
|
||||||
|
|---|---|
|
||||||
|
| 4 Gemini 3.5 Live Translate + 5 Gemma 4 12B | 同公司但不同模型、不同能力,必须保留 |
|
||||||
|
| 20 Claude Managed Agents + 35 AgentsView 可为 Claude Fable 5 设置自定义价格 | 都含 Claude,但一个是平台功能,一个是第三方工具,必须保留 |
|
||||||
|
| 21 Cursor Evals + 27 Cursor 欧洲总部 | 同公司但一个是产品功能,一个是组织扩张,必须保留 |
|
||||||
|
|
||||||
|
## 7. 报告与可观测性
|
||||||
|
|
||||||
|
`run_report.json` 需新增:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"stage2_8": {
|
||||||
|
"input_count": 41,
|
||||||
|
"candidate_pair_count": 12,
|
||||||
|
"candidate_group_count": 5,
|
||||||
|
"truncated_count": 0,
|
||||||
|
"reasons": {
|
||||||
|
"entity_overlap": 6,
|
||||||
|
"summary_similarity": 3,
|
||||||
|
"title_similarity": 3
|
||||||
|
},
|
||||||
|
"candidates": [
|
||||||
|
{
|
||||||
|
"item_ids": ["...", "..."],
|
||||||
|
"reason": "entity_overlap",
|
||||||
|
"score": 0.78,
|
||||||
|
"shared_entities": ["Claude Mythos 5", "Claude Fable 5"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Stage 3 报告需新增:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"merge_group_count": 1,
|
||||||
|
"merged_count": 2,
|
||||||
|
"not_duplicate_count": 8,
|
||||||
|
"uncertain_count": 0
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. 配置项
|
||||||
|
|
||||||
|
在 `config/pipeline.json` 增加:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"semantic_candidate_recall": {
|
||||||
|
"enabled": true,
|
||||||
|
"max_pairs": 80,
|
||||||
|
"max_pairs_per_item": 5,
|
||||||
|
"title_similarity_threshold": 0.45,
|
||||||
|
"title_jaccard_threshold": 0.25,
|
||||||
|
"summary_jaccard_threshold": 0.18,
|
||||||
|
"strong_entity_overlap_threshold": 2,
|
||||||
|
"weak_entity_with_summary_threshold": 0.12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. 实施建议
|
||||||
|
|
||||||
|
### 9.1 模块拆分
|
||||||
|
|
||||||
|
新增模块:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ai_daily_report/candidate_recall.py
|
||||||
|
```
|
||||||
|
|
||||||
|
建议函数:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def build_semantic_candidates(items: list[NewsItem], config: CandidateRecallConfig) -> tuple[list[dict], dict]:
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 与现有 Stage 2 兼容
|
||||||
|
|
||||||
|
- 保留 `dedupe.py` 中硬去重逻辑;
|
||||||
|
- `stage2.possible_duplicates` 继续存在;
|
||||||
|
- Stage 2.8 合并 Stage 2 的标题候选与新增语义候选,去重后传入 Stage 3;
|
||||||
|
- 不在 Stage 2.8 直接删除任何新闻。
|
||||||
|
|
||||||
|
### 9.3 测试
|
||||||
|
|
||||||
|
新增测试:
|
||||||
|
|
||||||
|
```text
|
||||||
|
tests/test_stage2_8_candidate_recall.py
|
||||||
|
tests/test_stage3_merge_groups.py
|
||||||
|
tests/test_pipeline_semantic_duplicate_regression.py
|
||||||
|
```
|
||||||
|
|
||||||
|
必须覆盖:
|
||||||
|
|
||||||
|
- Claude Fable/Mythos 三条样本;
|
||||||
|
- Google DeepMind 同公司不同模型不得误删;
|
||||||
|
- Cursor 产品功能与公司扩张不得误删;
|
||||||
|
- 候选数上限与 report 字段;
|
||||||
|
- Stage 3 返回 `merge_groups` 后 Markdown 只出现主条目。
|
||||||
|
|
||||||
|
## 10. 验收标准
|
||||||
|
|
||||||
|
1. 6/10 回放中,Stage 3 `candidate_group_count` 不再为 0;
|
||||||
|
2. Claude Fable/Mythos 三条不会全部独立出现在最终日报;
|
||||||
|
3. Google DeepMind Gemini/Gemma 两条仍独立保留;
|
||||||
|
4. `run_report.json` 能解释每个候选的召回原因;
|
||||||
|
5. 新增测试全部通过;
|
||||||
|
6. 不增加默认 LLM 调用次数,仍使用现有 Stage 3 LLM 调用完成语义判断;
|
||||||
|
7. 当 Stage 2.8 失败时,流水线应降级为原逻辑并在 report 中记录错误,不阻断发布。
|
||||||
|
|
||||||
|
## 11. 风险与防护
|
||||||
|
|
||||||
|
| 风险 | 防护 |
|
||||||
|
|---|---|
|
||||||
|
| 实体重叠导致误合并 | Stage 2.8 只召回不删除,最终由 Stage 3 判断 |
|
||||||
|
| 候选过多导致 prompt 过长 | 全局和单 item 上限、按分数截断 |
|
||||||
|
| LLM 过度合并 | 只接受 high confidence;uncertain 默认保留 |
|
||||||
|
| 官方发布与媒体深度解读被误合并 | prompt 明确:有独立新增事实/分析角度则保留 |
|
||||||
|
| 报告不可审计 | report 记录 shared_entities、score、reason |
|
||||||
|
|
||||||
|
## 12. 优先级
|
||||||
|
|
||||||
|
P0。该问题直接影响 AI 日报质量,且当前 Stage 3 在候选为 0 时完全失效,属于去重链路架构缺陷。
|
||||||
Reference in New Issue
Block a user