拆开百万月活的 AIHOT,我看到了一条会自己出版的行业情报流水线

0 阅读11分钟

很多所谓的 AI 资讯站,工作方式并不复杂。抓一批 RSS,把正文交给模型摘要,再按时间倒序铺到页面上。它能自动更新,也能省掉编辑逐条搬运的时间,可一旦信源变多,问题很快就会冒出来。

同一场发布会会被十家媒体写十遍,转载量最大的消息占满首页。模型今天觉得一篇文章重要,明天换个措辞又给出另一套判断。日报按时生成了,里面却挤着旧闻、营销稿和重复内容。更麻烦的是,每次任务重试都可能重新调用付费接口,系统越忙,账单越难解释。

我最近拆了 AIHOT 的开源代码。这个项目做的事情看着也是采集、筛选和摘要,真正拉开差距的地方,在于它把行业资讯当成一条生产流水线来设计。

资料怎样进来,什么消息值得留下,多篇报道怎样合成一个事件,哪些结果可以公开,模型花费怎样记录,每一步都有单独的状态和边界。

三个进程撑起整条流水线

AIHOT 是一个 npm workspaces 管理的 TypeScript Monorepo。运行时只有三个应用进程。

apps/api 是 Fastify 服务,负责站点接口、公开 API、RSS、MCP、后台接口、图片代理和分享图。apps/worker 运行 pg-boss 队列与定时任务,采集、模型调用、事件归组、热度计算、报告生成和告警都在这里完成。apps/web 是 React Router 服务端渲染的网站,只通过 HTTP 访问 API,不直接连接数据库。

业务实现集中在 packages/backend,前后端共享协议放在 packages/contracts。站名、分类、信源、提示词和门槛则集中在 industry。

apps/api      对内和对外提供接口
apps/worker   运行采集与内容生产任务
apps/web      服务端渲染读者页面

packages/backend    业务实现
packages/contracts  共享类型和协议
industry            行业知识与品牌配置

这个结构没有引入微服务间复杂的消息系统。三个进程共享 PostgreSQL,pg-boss 直接把数据库变成任务队列。部署时启动数据库、初始化任务、API、Worker 和 Web 五个容器即可,HTTPS 再交给可选的 Caddy 容器。

对于一套中小规模的行业站,这个取舍很实际。事务、队列和业务数据都留在同一套存储里,故障排查少了一层,部署也不需要额外维护 Redis。将来采集量大到 Worker 需要拆分时,任务边界已经存在,可以按内容处理、事件计算和运营任务分别扩容。

一条资料要过七道关

系统支持六类入口,分别是 RSS、网页列表、JSON 接口、X 账号、微信公众号和外部脚本推送。不同入口最后都会落成统一的资料记录,随后进入同一条处理链。

第一步是判重和正文获取。同一网址、同一内容只保留一份。订阅里只有标题或摘要时,系统会继续抓取原文,避免模型只看半截材料下判断。

接着是预筛。预筛的任务很窄,只判断资料是否属于当前行业。明显无关的内容会被挡住,确定相关和暂时拿不准的内容继续向下流。这里采用宽进策略,因为过早删掉一条资料,后面再好的评分也没有机会补救。

评分阶段会对同一份材料独立调用两次模型。两次分数之和达到信源等级对应门槛,资料才会进入精选。官方一手信源的默认门槛是 60,官方账号是 65,媒体和个人是 76。

这种设计没有假装模型能给出绝对稳定的分数。它用两次独立判断降低偶然波动,再把来源可靠性放进门槛,而没有塞进评分提示词里让模型自行猜测。

评分的同时,另一条任务会提取分类、标签、主体公司和事实结构。两边互不依赖,所以可以并行。等分数回来以后,入选内容和接近门槛的内容走完整理解流程,生成中文标题、答案先行的摘要和推荐理由。分数较低的资料只做更便宜的标题摘要,仍然可以留在全部动态里。

这套流程把两种需求分开了。精选追求严格,资料库追求完整。模型成本也跟着内容价值分层,没有必要给每条边缘消息都用最贵的写作流程。

从文章变成事实,再从事实变成事件

如果系统停在精选这一步,它仍然只是一个做得更严谨的信息流。AIHOT 继续往前走了一步,引入了资料、事实和事件三层模型。

一篇来源文章对应一条 Article。文章经过结构化以后提取出 Fact。多个来源报道同一次发布、事故或政策变化时,这些事实会归入同一个 Story。后续进展可以继续挂到原来的事件下,也可以被判断为相关但不同的事件。

归组先在最近两周的内容中做向量召回,找出可能相似的候选,再由模型判断它们属于同一次发生、同一条故事的后续,还是两件不同的事。遇到置信度不足的合并,系统会调用另一套模型再确认一次。人工修正过的归属具有更高优先级,不会被后续自动任务覆盖。

Article  来源发布的一篇资料
   │
   ▼
Fact     从资料中提取的事实
   │
   ▼
Story    多个来源共同指向的事件

热点也按照 Story 计算。48 小时内,每个独立来源只贡献一次热度,24 小时后权重减半。一家媒体连续发十篇跟进,不会得到十份权重,重复采集同一篇文章也不会把排名抬高。

这改变了热点榜的含义。它衡量的是有多少独立参与者正在讨论同一件事,而不是系统一共抓到了多少篇文本。

精选资料还会等待归组完成后再公开,最长等待三分钟。这个小延迟很有价值。读者第一次看到内容时,页面上已经尽量收敛成事件,不会先出现三张相似卡片,几分钟后又被后台合并。

所有公开出口只认一个读取层

项目里最值得借鉴的工程约束,是 packages/backend/src/publication 这一层。

网站、RSS、公开 API、MCP、llms.txt、站点地图和分享图都从 publication 读取内容。某条资料能否显示全文,事件是否已经准备好,延迟发布何时生效,公开链接如何生成,这些规则只在一个地方解释。

没有这一层,同一份数据很容易在不同出口产生分叉。网页已经隐藏的内容可能还留在 RSS,API 返回了未归组资料,站点地图提前暴露延迟发布页面。每个出口单独修补,规则迟早会失去一致性。

Web 进程也遵守这条边界。页面的 loader 通过内部 HTTP 请求 API,一般每页只发一到两个请求。它不会导入数据库客户端,更不会在读者打开页面时调用模型。

读者访问和内容生产由此彻底分开。页面只读取已经完成的结果,模型供应商变慢、预算耗尽或 Worker 暂停,都不会把一次普通页面请求拖进昂贵且不可预测的生成过程。

每一笔付费调用都有回执

模型、向量、X、公众号和 Jina Reader 都可能产生费用。AIHOT 没有让这些调用散落在业务代码里直接执行,而是统一经过 providers/receipts.ts。

一次付费请求开始前,系统会根据服务、用途、对象和提示词版本生成逻辑键,并创建回执。供应商返回结果后,响应先保存,再提交分析、归组或翻译结果。任务因为部署或进程退出而重试时,可以复用已经拿到的响应,不必再付一次钱。

如果请求已经发出,进程却无法确认供应商是否成功,回执会进入未知状态。后台恢复任务负责检查长时间停留在处理中的回执,并决定是否释放一次自动重试。系统没有把网络中断简单等同于请求失败,因为供应商可能已经完成计费。

回执之外还有预算熔断。每个付费服务可以设置每分钟、每小时和每天的上限,超过后暂停请求。开发和测试环境还能关闭采集、模型、飞书推送与 IndexNow,测试中的供应商调用全部由本地假服务接管。

这套设计解决的是 AI 应用里经常被推迟处理的问题。一次调用花多少钱,重试是否重复付费,结果是否已经落库,出了故障能不能追到具体尝试。等调用量上来以后,这些问题会比提示词怎样写更先影响系统能否稳定运行。

行业判断被装进一个可替换的包

AIHOT 开源仓库默认是一套 AI 行业示例站,但框架把行业相关内容集中到了 industry 目录。

site.ts 定义站名与页面文案,taxonomy.ts 和 topics.json 定义分类、标签与主题,sources.json 提供初始信源,prompts 保存预筛、评分、写作、结构化和报告生成的提示词,selection.ts 保存分级门槛,features.ts 控制模型榜和 Codex 重置监控等行业专属模块。

换成法律、金融或人力资源行业时,主要工作落在这个目录里。稳定的采集、队列、归组和发布代码可以继续使用,经营者需要回答的是哪些信源值得盯,哪些消息重要,什么内容属于噪声,分类怎样组织。

项目没有声称一套默认门槛可以适用于所有行业。它提供了 SelectBench 和评测脚本,要求使用者准备一百到两百条自己标注的资料,分别标记应该入选、不该入选和两可。脚本会报告准确率、查准率、查全率以及不同门槛下的结果,后台还能逐条查看错例。

这里的顺序也很重要。先看错例并修改评分标准,只有当错误集中在门槛附近时才调整数字。门槛只能让整体变松或变严,无法告诉模型某类监管变化为什么重要,也无法解释某种营销稿为什么应该压低分数。

行业 KnowHow 因此有了可以维护的形态。一句模糊的系统提示词被拆成信源、分类、判断标准、样本和门槛,这些内容能够被版本化,也能够用真实标注反复校准。

这套架构最聪明的地方

AIHOT 没有依赖特别罕见的技术。Fastify、React Router、PostgreSQL、pg-boss 和 Docker Compose 都是常见工具。它的质量来自边界划分。

采集入口可以不同,后续处理流程保持一致。评分与写作可以换模型,公开页面只读取已完成结果。文章、事实和事件各自承担一种语义。所有公开出口经过同一读取层。所有付费请求留下回执。行业判断留在可替换的配置与提示词里。

这些边界让系统同时服务三类人。读者获得精选、热点和报告,运营者获得信源管理、评测、预算与告警,其他程序和 Agent 则通过 RSS、API、MCP 与 llms.txt 使用同一份内容。

如果只看页面,它是一家行业资讯站。顺着代码走完一遍以后,它更像一套小型出版系统。编辑判断被写进可校准的流程,模型被限制在后台任务里,成本和公开边界也有明确记录。

自动摘要并不难。让一套系统长期、稳定、可解释地决定什么值得被看见,才是这份代码真正解决的问题。