docs: add AI daily full-chain optimization audit

This commit is contained in:
Ubuntu
2026-06-10 18:51:28 +08:00
parent f2738cd223
commit 946949c2fd

View File

@@ -0,0 +1,497 @@
# 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. 让源失败、重复发布、配置不生效这类运维问题能被及时发现。