17 KiB
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 级问题:
- 语义去重召回失效:Stage 3 语义去重完全依赖 Stage 2 的标题相似候选。6/10 的
stage2.possible_duplicates=[],导致stage3.candidate_group_count=0,实际没有进行语义审查。 - 源失败仍静默发布:6/9、6/10 橘鸦源连续失败,状态为
error、数量为 0,但任务仍发布成功,回执没有突出“关键补充源缺失”。 - 发布前质量门禁不足:近期日报中多天出现同事件重复,如 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
流程:
- 进入仓库
~/.hermes/scripts/ai-daily-report - 执行
git pull --ff-only - 设置
PYTHONPATH - 调用
run_daily_report(mode='publish', source_mode='live', llm_mode='live') - 打印采集数量、去重数量、发布 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_duplicatesai_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 中存在:
"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.mdrun_report.json
缺少:
stage1_items.jsonstage2_items.jsonstage2_5_items.jsonstage3_items.jsonstage4_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 域名与来源类型
- 事件阶段词:发布、即将发布、爆料、上线、开源、融资、收购
输出示例:
{
"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。建议增加:
{
"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 中输出:
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_textcollect.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 - 避免自动生成
-2slug 后误以为正常
6.7 让 pipeline 配置生效
改造位置:runner.py、pipeline.py
建议把以下配置传入实际函数:
rewrite_batch_sizesemantic_dedup_max_deletion_ratioquality_gate配置semantic_candidate_recall配置
6.8 改进 cron 回执与失败告警
Wrapper 回执应包含:
- 每个源状态、条数、错误类型
- Stage 2/2.5/2.8/3 删除与候选数量
- 质量门禁结果
- 发布结果与实际 slug
- 如果源失败或质量 warning,必须在回执中显式显示
对于 no_agent cron:脚本 stdout 就是告警内容,应避免只输出“发布成功”。
7. 可执行改造计划
Phase 1:可观测性与安全网
- 在
runner.py保存各阶段 item 快照。 - 在
validate.py或新增quality_gate.py增加重复/源失败 warning。 - 修改 wrapper 回执,展示源失败和质量门禁结果。
- 修正 cron job 名称为与实际时间一致。
交付标准:不改变内容生成结果,但能解释每条新闻的处理路径。
Phase 2:去重召回升级
- 新增
candidate_recall.py。 - 在
pipeline.py增加 Stage 2.8。 - Stage 3 prompt 增加
merge_groups。 - 建立 6/10 回归 fixtures。
交付标准:Claude Fable/Mythos 三条不再全部独立出现;Gemini/Gemma 仍保留。
Phase 3:稳定性与发布治理
- 实现 fetch retry/backoff。
- 发布前做 slug 幂等检查。
- 让
pipeline.json配置真实生效。 - 对 required source 失败设置阻断或 draft 降级策略。
交付标准:临时源波动不再轻易导致内容缺失;同日重复运行不会生成 -2 版本。
Phase 4:长期维护
- 建立
tests/fixtures/recent_daily_reports/。 - 每次调整去重逻辑跑历史回放。
- 将技能文档中的阈值、阶段说明改为从代码/配置生成或定期校验。
- 增加每周自动审计报告,统计源失败率、重复候选、最终条数、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. 建议配置草案
{
"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,而是补齐工程链路:
- 让每个阶段可回放;
- 让语义去重有足够候选;
- 让明显质量问题不能直接发布;
- 让源失败、重复发布、配置不生效这类运维问题能被及时发现。