AI 摘要
AI
正在生成摘要...

前阵子在openclaw的skill仓库里翻到一个叫rss-ai-reader的小工具,大致逻辑很简单:定时抓RSS订阅→丢给LLM生成中文摘要→推到飞书群里。开箱即用,看起来很美好,我直接部署。但用了几天,问题就冒出来了。

原版的问题

原版作者塞了91个RSS源在config.yaml里,都是技术博客,质量不低——SimonWillison、antirez、Gwern,每一个单独拎出来都值得订。但问题在于:抓取推送时,谁的文章多谁就占满推送队列。某天DaringFireball一口气发了5篇,我那天的推送列表小一半都是它。第二天Pluralistic又灌进来好几篇。我那几个真正想看的源——比如antirez偶尔冒一篇、DwarkeshPatel一周一篇——被稀释到看不见。你以为自己订了很多源,其实算法(或者说博客的生产频次)替你做了选择。高产源霸占了你的注意力,低产但高质量的源永远轮不到。
另一个问题是内容旱涝不均。有的时候一天更新了十几篇,有的时候三天推送不了一篇。原版一概而论,多的丢了不可惜,少的空转也不补充。
还有一个细节:原版虽然prompt里写了「把标题翻译成中文」「严格按以下格式输出」,但LLM经常不听话。有时候它输出英文标题,有时候标志符号漏了,有时候它直接说「我无法总结这篇文章」。没有任何校验,LLM做出什么就推什么。

我改了什么

我一口气改了一大堆,下面是所有改动的完整对比。

1.订阅模型:固定 + 随机的双轨制

原版的91个源全部硬编码在配置里。我拆成了两层:

  • 固定订阅(5个):OpenAIBlog、AnthropicNews、TechCrunch、ArsTechnica、GitHubBlog。这些是核心关注,每轮必刷。
  • 随机池(94个):单独一个all_feeds.txt文件,涵盖SimonWillison、DwarkeshPatel、constructionp hysics、experimentalhistory等等。每次运行时从中随机抽20个源来抓取推送。

这样一来,每次推送既有必读的基本盘,又有随机刷新出来的「意外之喜」。第一次运行时推送给我的The just-say-no engineer was a ZIRP phenomenon(“说不”的工程师是ZIRP的现象),给了我全新的视野——程序员被裁员的一个可能原因,这要是在原版我估计永远不会收到它。

2.配额分配:轮询制,不让一个源刷屏
每轮最多推12篇,分配逻辑是这样的:

  1. 固定订阅先上,每个feed保底1篇,然后按feed轮询填充,上限8篇
  2. 固定没用完的配额溢出给随机
  3. 随机同样按feed轮询,上限4篇+固定未使用完的配额

核心思路是每个源不论产量高低,都有公平的曝光机会。TechCrunch一天出10篇,在我这也就占1-2个位置;GitHubBlog一个月写3篇,每篇都不会漏。

3.旱涝调节:pending队列
原版的做法是:新文章>max_articles,多的就丢掉不要了;新文章为0,就空转一轮。我加了一个pending队列。多出来的文章不进数据库标记为已处理,而是存下来等下一轮。如果下一轮新内容不足,自动从pending队列里补上来。把多的匀给少的。这招特别管用——在很多博客不更新的时候,新文章常常不足12篇,pending队列能补到满配。

4.翻译与审查机制:三层把关
这是比较核心的改动。原版prompt里写了翻译要求,但写归写,没人检查。
我加了三个关卡:

第一关:内容审查 _is_content_valid
很多RSSfeed摘取的内容是空的、只有链接、或者只有「Please enable JavaScript to view this page」。这些内容不值得浪费我的token,直接标记为已处理不进入队列。

第二关:摘要格式校验 _is_summary_valid
LLM输出摘要后,检查:

  • 长度是否 ≥60字
  • 是否以🏷️开头
  • 五个标记(🏷️ 📌 💡 📋 ⭐)是否出现了至少3个
  • 有没有出现「我无法总结」「I cannot provide」等拒绝回复

第三关:Reflection Retry
如果哪条不合格,不是直接丢弃,而是把具体原因反馈给LLM,告诉它:「你上次的问题是摘要过短/格式不完整/漏了标记,请按原始格式重写。」模型会根据反馈自我修正,最多重试2轮。

另外,我用过DeepSeek的模型,它有个现象——输出前先来一段<think>...</think>思维链。我写了个正则剥离器,把这些思考块从最终输出里清掉,保证推到飞书的只有最终摘要,没有LLM的内心独白。

5. LLM:从SDK到HTTP直连
原版用AnthropicSDK和OpenAISDK,每个都依赖一整个包。我换成了HTTP直连,支持DeepSeek和MiniMax。配置里填一个base_url+api_key就行了,只要有API密钥就能用。

6.并发与速度
原版是串行处理:N篇文章就调N次LLM,一次等一次。我加了ThreadPoolExecutor,默认3个worker并发跑摘要。12篇文章从1多分钟压缩到20秒左右。

7.飞书推送:主动节流
原版飞书推送没做任何限速。自定义机器人约5msg/sec的限频,连续推快了就返回code 11232。我加了1.2秒的最小间隔,遇到11232自动等2s→3s→5s指数退避重试。

8.日志:print() → logging
原版全程print(),跑完了控制台一关,什么痕迹都没有。我换成了logging模块,每次运行自动生成日志文件。还挂了全局异常钩子,万一哪个地方崩了,栈信息能写进日志文件,不会悄无声息。

9.数据库扩展
原版数据库只有去重功能。我加了categorypushedcontent三个字段,新增了store_pendingget_pending_articlesmark_pushed三个方法,支撑pending队列。

10.其他

  • Telegram支持被我移除了——我没在用
  • Python版本降到了3.6.8兼容——服务器上的Python比较老

效果

改动落地后跑了一段时间,飞书群里的画风变了:

代码

原版作品:

本人作品:


评论