Files
ephron-ren-prd/prd-ai-daily-full-chain-optimization-audit.md

498 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AI 日报定时任务全链路优化审计
## 0. 文档信息
- 审计对象Hermes cron job `76297415d88d`,名称 `AI日报单任务09:30`
- 实际调度:`0 10 * * *`,每天北京时间 10:00
- 审计范围:定时触发、采集、生成、去重、异常、日志、发布、告警、稳定性、近期内容质量
- 主要证据:本地运行产物 `~/.hermes/scripts/ai_morning_out/2026-06-04``2026-06-10`cron 输出 `~/.hermes/cron/output/76297415d88d/`,代码仓库 `~/.hermes/scripts/ai-daily-report`
---
## 1. 总结
当前 AI 日报链路已经完成了从多源采集、LLM 改写分类、导览生成到博客发布的自动化闭环,但近期运行暴露出三个 P0 级问题:
1. **语义去重召回失效**Stage 3 语义去重完全依赖 Stage 2 的标题相似候选。6/10 的 `stage2.possible_duplicates=[]`,导致 `stage3.candidate_group_count=0`,实际没有进行语义审查。
2. **源失败仍静默发布**6/9、6/10 橘鸦源连续失败,状态为 `error`、数量为 0但任务仍发布成功回执没有突出“关键补充源缺失”。
3. **发布前质量门禁不足**:近期日报中多天出现同事件重复,如 6/4 OpenClaw/Suno、6/5 Magenta、6/6 Open Code Review、6/8 OpenAI 芯片、6/9 高德 ABot、6/10 Claude Fable/Mythos。
整体判断:链路不是不可用,而是处于“能发布,但缺少质量防线和可观测性”的状态。优先改造方向应是:保存中间产物、增强候选召回、增加发布前质量门禁、补齐重试/告警/幂等。
## 2. 当前链路现状
### 2.1 定时触发
- Cron job ID`76297415d88d`
- 名称:`AI日报单任务09:30`
- 实际 schedule`0 10 * * *`
- 模式:`no_agent: true`
- 脚本:`ai_daily_report_cron.py`
- 投递:`origin`
- 工具集:`terminal`
- 最近状态6/10 10:02:46 运行成功,下一次为 6/11 10:00
问题:任务名仍写 09:30但实际为 10:00这会误导运维判断尤其橘鸦源约 09:34 更新后,调度时间曾多次调整。
### 2.2 Wrapper 执行流程
Wrapper 文件:`~/.hermes/scripts/ai_daily_report_cron.py`
流程:
1. 进入仓库 `~/.hermes/scripts/ai-daily-report`
2. 执行 `git pull --ff-only`
3. 设置 `PYTHONPATH`
4. 调用 `run_daily_report(mode='publish', source_mode='live', llm_mode='live')`
5. 打印采集数量、去重数量、发布 URL
问题:`git pull` 失败只打印 stderr不阻断最终可能继续用旧代码发布。
### 2.3 数据源配置
配置文件:`config/sources.json`
当前源:
| 源 | 类型 | 角色 | timeout | retries | 状态 |
|---|---|---|---:|---:|---|
| AI HOT | `aihot` | primary | 25s | 2 | 正常 |
| InfoQ AI | `rss` | supplement | 25s | 1 | 正常3 天年龄过滤 |
| MIT 科技评论 AI | `rss` | supplement | 25s | 1 | 正常5 天年龄过滤 |
| 量子位 | `rss` | supplement | 25s | 1 | 正常 |
| 橘鸦AI早报 | `juya_rss` | supplement | 45s | 2 | 6/9 起 404/错误 |
注意:`retries` 字段存在于配置,但当前采集代码没有真正按配置重试。
### 2.4 生成发布管线
核心文件:`ai_daily_report/pipeline.py`
当前阶段:
| Stage | 模块 | 说明 |
|---|---|---|
| 0 | `collect.py` | 并发采集 |
| 1 | `normalize.py` | 清洗、canonical URL、质量 flags |
| 2 | `dedupe.py` | URL/标题硬去重 + 标题相似候选 |
| 2.5 | `dedupe.py` | 跨天 URL 去重 |
| 3 | `semantic_dedupe.py` | LLM 语义去重,仅审 Stage 2 候选 |
| 4 | `rewrite.py` | LLM 改写 + 分类 |
| 5 | `classify.py` | 分类校验/兜底 |
| 6 | `guide.py` | LLM 导览 |
| 7 | `assemble.py` | Markdown 组装和基础校验 |
| 8 | `publish.py` | 创建、发布、更新 URL 历史 |
### 2.5 近期运行数据
本地完整 `run_report.json` 覆盖 2026-06-04 至 2026-06-10。
| 日期 | 原始条数 | 最终条数 | 橘鸦 | Stage2 候选 | Stage3 删除 | 发布 |
|---|---:|---:|---:|---:|---:|---|
| 6/4 | 90 | 89 | 23 | 1 | 0 | okslug 为 `ai-2026-06-04-2` |
| 6/5 | 88 | 82 | 24 | 0 | 0 | ok |
| 6/6 | 86 | 82 | 21 | 1 | 1 | ok |
| 6/7 | 53 | 52 | 5 | 0 | 0 | ok |
| 6/8 | 53 | 52 | 5 | 0 | 0 | ok |
| 6/9 | 51 | 51 | 0/error | 0 | 0 | ok |
| 6/10 | 50 | 41 | 0/error | 0 | 0 | ok |
结论Stage 3 多数日期没有候选,语义去重实际经常没有工作。
## 3. 发现的问题
### 3.1 P0语义去重候选召回失效
证据:
- 6/5、6/7、6/8、6/9、6/10 的 `stage3.candidate_group_count=0`
- 6/10 明显存在 Claude Fable/Mythos 同事件多条,但 `stage2.possible_duplicates=[]`
- `semantic_dedupe.py` 在 candidates 为空时直接返回,不会主动审查所有 items
涉及代码:
- `ai_daily_report/pipeline.py`Stage 3 candidates 只来自 `stage2.possible_duplicates`
- `ai_daily_report/dedupe.py`:候选召回只基于标题相似度
影响:重复新闻会绕过去重直接发布;用户看到的是同一事件拆成多条,日报质量下降。
### 3.2 P0发布前质量门禁过弱
当前 `validate.py` 只检查:空 items、Markdown 太短、无章节、JSON 碎片、无 URL、非法 section。
缺失检查:
- 同实体高频出现
- 同标题高相似度
- 同一源失败导致覆盖缺口
- Stage 3 候选为 0 的异常状态
- 最终 Markdown 中重复标题/同事件重复
近期扫描样本:
| 日期 | 可疑重复 |
|---|---|
| 6/4 | OpenClaw 两条、Suno 融资两条 |
| 6/5 | Google Magenta RealTime 2 两条 |
| 6/6 | Open Code Review 两条 |
| 6/8 | OpenAI 芯片核心成员跳槽 Anthropic 两条 |
| 6/9 | 高德 ABot-Earth0.5 两条 |
| 6/10 | Claude Fable/Mythos 多条 |
### 3.3 P0源失败仍发布告警不充分
证据:
- 6/9、6/10 橘鸦源状态为 `error`,条数为 0
- Stage 8 仍 `status=ok`
- cron 回执只展示采集数量,不突出源异常原因
影响:用户不知道日报缺失一个重要补充源,也无法判断当天内容覆盖是否可靠。
### 3.4 P1配置项未全部生效
`config/pipeline.json` 中存在:
```json
"rewrite_batch_size": 10
```
`pipeline.py` 调用 `rewrite_items()` 时未传入该配置,实际使用 `rewrite.py` 默认 `batch_size=30`
影响:配置与真实行为不一致;后续调参容易误判。
### 3.5 P1HTTP fetch 无统一重试/backoff
`clients.py::fetch_text()` 只做单次 `urllib.request.urlopen`。虽然 `sources.json``retries` 字段,但 `collect.py` 没有按配置重试。
影响:临时网络波动会直接变成源失败;错误类型也未细分为 404、timeout、parse_error、empty。
### 3.6 P1发布流程缺少幂等保护
`publish.py` 直接 `create_post``publish_post`。如果同日重复运行,平台可能生成 `ai-YYYY-MM-DD-2` 这类 slug。
证据6/4 发布 URL 为 `https://blog.ephron.ren/posts/ai-2026-06-04-2`
影响:同一天可能出现多个日报版本;历史 URL 与预期 slug 不一致。
### 3.7 P1中间产物不足回放困难
当前输出只有:
- `blog_markdown.md`
- `run_report.json`
缺少:
- `stage1_items.json`
- `stage2_items.json`
- `stage2_5_items.json`
- `stage3_items.json`
- `stage4_items.json`
- LLM prompt/response 摘要或 hash
影响:一旦用户反馈重复/幻觉/分类错误很难还原某条新闻在每个阶段的标题、摘要、URL、候选关系。
### 3.8 P2cron wrapper 与包内配置割裂
Wrapper 固定传:
- `mode='publish'`
- `source_mode='live'`
- `llm_mode='live'`
- `base_url='https://blog.ephron.ren'`
同时 `pipeline.json` 又有 `default_mode='dry-run'`。配置来源不统一。
影响:调试和生产行为容易混淆。
### 3.9 P2Skill 文档与代码阈值曾出现不一致
技能文档曾记录 title Jaccard 阈值为 0.25,但代码实际为 0.40。该问题说明“运维知识库”和代码配置缺少自动校验。
影响:排障时容易根据过期文档做错误判断。
## 4. 问题影响
| 问题 | 直接影响 | 长期影响 |
|---|---|---|
| 语义去重候选为 0 | 重复新闻发布 | 用户对日报质量信任下降 |
| 源失败静默发布 | 内容覆盖缺口不可见 | 误以为日报完整 |
| 发布前门禁弱 | 明显质量问题进入公开博客 | 后续需要人工返工或删除重发 |
| 配置不生效 | 运维调参无效 | 形成错误排障经验 |
| 无重试/backoff | 临时波动放大成源失败 | 稳定性依赖运气 |
| 发布不幂等 | 同日多 slug | 历史链接、引用和 SEO 混乱 |
| 中间产物缺失 | 排查成本高 | 无法建立可靠回归测试集 |
---
## 5. 优先级排序
| 优先级 | 事项 | 原因 |
|---|---|---|
| P0 | 新增 Stage 2.8 语义候选召回 | 当前 Stage 3 经常没有候选,语义去重实际失效 |
| P0 | 发布前质量门禁 | 阻止明显重复、源失败、空候选直接发布 |
| P0 | 保存中间 item 快照 | 支撑回放、调试、回归测试 |
| P1 | 统一重试/backoff 与错误分类 | 提升源稳定性,减少静默失败 |
| P1 | 发布幂等保护 | 避免同日多版本和 slug 漂移 |
| P1 | 让 pipeline 配置真实生效 | 消除配置与行为不一致 |
| P2 | 统一 wrapper 与包内 CLI 配置 | 降低维护成本 |
| P2 | 技能文档自动校验 | 防止知识库漂移 |
## 6. 具体优化建议与改造方案
### 6.1 新增 Stage 2.8:语义候选召回
新增文件:`ai_daily_report/candidate_recall.py`
目标:不新增 LLM 调用,先用规则扩大 Stage 3 的候选池。
建议召回特征:
- 标题字符相似度
- 标题 token Jaccard
- 摘要 token Jaccard
- 公司/产品/模型实体重叠
- URL 域名与来源类型
- 事件阶段词:发布、即将发布、爆料、上线、开源、融资、收购
输出示例:
```json
{
"item_ids": ["item_a", "item_b"],
"reason": "entity_overlap",
"score": 0.78,
"shared_entities": ["Claude Mythos", "Claude Fable"]
}
```
接入位置:`pipeline.py` 中 Stage 2.5 之后、Stage 3 之前。
### 6.2 增强 Stage 3支持同事件合并
当前 Stage 3 只有 `duplicate_groups`。建议增加:
```json
{
"duplicate_groups": [],
"merge_groups": [],
"not_duplicates": [],
"uncertain": []
}
```
策略:
- `duplicate_groups`:同一新闻,删除重复项
- `merge_groups`:同一事件不同角度,保留主条目,把其他来源作为补充来源
- `uncertain`:默认保留,避免误删
### 6.3 新增发布前质量门禁
建议新增模块:`ai_daily_report/quality_gate.py`
检查项:
- Stage 3 候选为 0 且 item 数大于 30warning
- 任一强实体出现 3 次以上warning 或 block
- 最终标题相似度高于 0.55warning
- 任一 enabled source 状态为 errorwarning
- required source 失败block
- publish 前 `blocking_errors` 非空:阻断发布
### 6.4 保存中间产物
`runner.py` 中输出:
```text
stage0_sources.json
stage1_items.json
stage2_items.json
stage2_5_items.json
stage2_8_candidates.json
stage3_items.json
stage4_items.json
quality_gate.json
```
每条 item 至少保留id、title_raw、title、summary_raw、summary、url、canonical_url、source_group、source_label、section、quality_flags、entities。
### 6.5 实现统一重试/backoff
改造位置:
- `clients.py::fetch_text`
- `collect.py::_collect_one`
建议:
-`config.retries` 重试
- 对 429/500/502/503/504/timeout 使用指数退避
- 对 404 直接失败,不重试
- report 记录 `attempts``error_type``http_status`
### 6.6 发布幂等保护
改造位置:`publish.py`
建议:
- 发布前查询目标 slug 是否已存在
- 如果存在且内容 hash 相同:返回 `status=already_published`
- 如果存在但内容不同:默认阻断,除非显式 `allow_republish=true`
- 避免自动生成 `-2` slug 后误以为正常
### 6.7 让 pipeline 配置生效
改造位置:`runner.py``pipeline.py`
建议把以下配置传入实际函数:
- `rewrite_batch_size`
- `semantic_dedup_max_deletion_ratio`
- `quality_gate` 配置
- `semantic_candidate_recall` 配置
### 6.8 改进 cron 回执与失败告警
Wrapper 回执应包含:
- 每个源状态、条数、错误类型
- Stage 2/2.5/2.8/3 删除与候选数量
- 质量门禁结果
- 发布结果与实际 slug
- 如果源失败或质量 warning必须在回执中显式显示
对于 no_agent cron脚本 stdout 就是告警内容,应避免只输出“发布成功”。
## 7. 可执行改造计划
### Phase 1可观测性与安全网
1.`runner.py` 保存各阶段 item 快照。
2.`validate.py` 或新增 `quality_gate.py` 增加重复/源失败 warning。
3. 修改 wrapper 回执,展示源失败和质量门禁结果。
4. 修正 cron job 名称为与实际时间一致。
交付标准:不改变内容生成结果,但能解释每条新闻的处理路径。
### Phase 2去重召回升级
1. 新增 `candidate_recall.py`
2.`pipeline.py` 增加 Stage 2.8。
3. Stage 3 prompt 增加 `merge_groups`
4. 建立 6/10 回归 fixtures。
交付标准Claude Fable/Mythos 三条不再全部独立出现Gemini/Gemma 仍保留。
### Phase 3稳定性与发布治理
1. 实现 fetch retry/backoff。
2. 发布前做 slug 幂等检查。
3.`pipeline.json` 配置真实生效。
4. 对 required source 失败设置阻断或 draft 降级策略。
交付标准:临时源波动不再轻易导致内容缺失;同日重复运行不会生成 `-2` 版本。
### Phase 4长期维护
1. 建立 `tests/fixtures/recent_daily_reports/`
2. 每次调整去重逻辑跑历史回放。
3. 将技能文档中的阈值、阶段说明改为从代码/配置生成或定期校验。
4. 增加每周自动审计报告统计源失败率、重复候选、最终条数、fallback ratio。
---
## 8. 涉及文件和代码位置
| 文件 | 作用 | 建议动作 |
|---|---|---|
| `~/.hermes/scripts/ai_daily_report_cron.py` | cron wrapper | 阻断 git pull 失败、增强回执、展示 warning |
| `config/sources.json` | 源配置 | 明确 required/min_items/retries 语义 |
| `config/pipeline.json` | 管线配置 | 增加 quality_gate、candidate_recall、publish_idempotency |
| `ai_daily_report/clients.py` | HTTP/LLM/Blog client | 增加 retry/backoff、HTTP 错误分类 |
| `ai_daily_report/collect.py` | Stage 0 | 使用 config.retries记录 attempts/error_type |
| `ai_daily_report/dedupe.py` | Stage 2/2.5 | 保留硬去重,减少标题候选职责 |
| `ai_daily_report/candidate_recall.py` | 新增 Stage 2.8 | 实体/摘要/标题综合召回 |
| `ai_daily_report/semantic_dedupe.py` | Stage 3 | 支持 merge_groups 和候选审计 |
| `ai_daily_report/rewrite.py` | Stage 4 | 配置 batch_size 生效,避免伪独立标题 |
| `ai_daily_report/validate.py` | Stage 7 校验 | 扩展质量门禁或迁移到 quality_gate |
| `ai_daily_report/publish.py` | Stage 8 | 增加 slug 幂等检查 |
| `ai_daily_report/runner.py` | orchestration | 保存中间产物,传递配置 |
| `tests/` | 测试 | 增加历史回放与重复回归测试 |
---
## 9. 验收标准
### 9.1 功能验收
- 6/10 回放中Stage 3 `candidate_group_count` 不再为 0。
- Claude Fable/Mythos 三条不会全部独立发布。
- 6/4 OpenClaw/Suno、6/5 Magenta、6/6 Open Code Review、6/8 OpenAI 芯片、6/9 高德 ABot 样本能进入候选或被合并。
- Gemini/Gemma、Cursor Evals/Cursor 欧洲总部这类同公司不同事件不被误删。
### 9.2 稳定性验收
- 源 timeout/5xx 会按配置重试。
- 404 不重复重试,并在 report 中记录为 `http_404`
- enabled source 失败会在 cron 回执中显著显示。
- required source 失败会阻断发布或降级为 draft。
### 9.3 发布验收
- 同日重复运行不会默认生成 `ai-YYYY-MM-DD-2`
- publish result 明确区分 `ok``blocked``failed``already_published`
- run_report 中包含实际 slug 和公开 URL。
### 9.4 可维护性验收
- 每次运行保存完整阶段快照。
- 新增测试覆盖 6/10 语义重复事故。
- `pipeline.json` 中配置项都有测试证明实际生效。
- 文档与代码阈值不再长期漂移。
---
## 10. 建议配置草案
```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
},
"quality_gate": {
"block_on_required_source_failure": true,
"warn_on_enabled_source_failure": true,
"warn_when_stage3_candidates_zero_min_items": 30,
"warn_on_final_title_similarity": 0.55,
"warn_on_entity_frequency": 3
},
"publish_idempotency": {
"enabled": true,
"allow_republish": false
}
}
```
---
## 11. 结论
当前 AI 日报定时任务的主链路已经能稳定产出,但质量治理还停留在“发布是否成功”的层面,没有充分检查“内容是否应该发布”。
最优先的改造不是继续调 LLM prompt而是补齐工程链路
1. 让每个阶段可回放;
2. 让语义去重有足够候选;
3. 让明显质量问题不能直接发布;
4. 让源失败、重复发布、配置不生效这类运维问题能被及时发现。