# 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 | ok,slug 为 `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 P1:HTTP 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 P2:cron wrapper 与包内配置割裂 Wrapper 固定传: - `mode='publish'` - `source_mode='live'` - `llm_mode='live'` - `base_url='https://blog.ephron.ren'` 同时 `pipeline.json` 又有 `default_mode='dry-run'`。配置来源不统一。 影响:调试和生产行为容易混淆。 ### 3.9 P2:Skill 文档与代码阈值曾出现不一致 技能文档曾记录 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 数大于 30:warning - 任一强实体出现 3 次以上:warning 或 block - 最终标题相似度高于 0.55:warning - 任一 enabled source 状态为 error:warning - 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. 让源失败、重复发布、配置不生效这类运维问题能被及时发现。