拆解 AIHOT 情报流水线采集、双评判稿、事件聚簇如何无人值守运转【免费下载链接】AIHOT一个自己找热点、自己写日报的网站框架。把信源和精选标准换成你的它就是你的行业热点站。项目地址: https://gitcode.com/gh_mirrors/ai/AIHOTAIHOT 是一个自己找热点、自己写日报的开源网站框架每天从一批信源收资料用大模型先筛一遍、再独立打两次分把不同来源说的同一件事聚成一个事件按有多少人在说排出热点每天早上自动出一份日报。上线以来累计约 5400 Star、入选 GitHub 日榜且这套引擎完全开源——所有提示词原文、入选门槛、热度算法都在仓库里信源和精选标准换成你的 KnowHow它就成了你的行业热点站。本文沿着一条资料从信源进来、到变成日报上的一个条目的完整路径逐层拆解它的采集、判稿、聚簇与出版机制看看无人值守这四个字背后到底有多少道保险。一条资料的一生先看全貌README 里有一张官方流程图概括了完整链路采集判重、抓原文→ 判断与写作预筛、评分、标题摘要、结构化→ 归组事件、热度、综述→ 公开读取层 → 日报/周报/月报。整个系统由三个进程组成apiFastify网站接口与公开 API、workerpg-boss 任务队列与定时任务抓信源、调模型、归组、出刊都在这里、webReact Router 服务端渲染只通过 HTTP 读 api不碰数据库。业务代码集中在packages/backend/一条不变的规则是页面不调模型——读者打开页面只读数据库里已有的结果模型只在 worker 的任务里调用。采集层信号源分级、双通道去重与自适应排期六种信源、三级分级各级门槛不同采集入口是packages/backend/src/sources/collect.ts。它支持六种信源RSS、网页列表、JSON 接口、X 账号、微信公众号以及外部脚本推送。每种信源有自己的读取器sources/目录下的rss.ts、web-list.ts、json-list.ts、x.ts、mp.ts。信源在后台被分成三级T1官方一手官网、官方博客、机构、T1_5官方账号与准官方、T2媒体与个人。分级不只是标签它直接决定入选门槛的差异见下文判稿部分也决定抓取频率——X 账号按参与模式区分编辑类账号每半小时读一次分片热点信号账号每小时一次collect.ts里的X_SHARD_MINUTES。双通道去重URL 指纹 内容指纹去重发生在两个层面。第一层是身份键identity key每条资料入库时算一个identityKey——X 帖用x:tweetId网页用规范化后的 URL都没有才退回src:sourceId:sha256(urltitle)content/materials.ts的identityKeyFor。同一把 key 只允许一条记录靠唯一索引仲裁并发首见。第二层是内容变更检测upsertMaterial会对标题、正文、摘要拼起来算contentHash只有内容真的变了才产生新 revision、才重新走分析。采集时还会专门处理URL 别名——比如同一篇文章的多个入口 URL在collect.ts里用 Map 按 identity key 去重避免别名既浪费详情页抓取、又挤占窗口内其他文章的配额。有意思的是清洗层还考虑了传输损失sameBarringLoss把 UFFFD解码丢失字符当作通配符比较因为有些 feed 每次加载都会随机乱掉几个字符导致同一篇文章两次加载的文本永远不一致——这种细节是真实线上系统才会踩到的坑。自适应排期按产出调整抓取频率collect.ts末尾的adaptIntervals每天跑一次统计每个信源近 7 天的日均产出per_day把抓取间隔调整到 15 分钟活跃源到 120 分钟安静源之间目标间隔约等于每 3 条产出抓一次。付费列表Jina 读取最快一小时一次避免跑穿日预算热点信号源可以放宽到 180 分钟。X 账号不走这套节奏按分片节奏走。排期还带健康反馈抓取失败连续 5 次标记failing退避时间按失败次数递增、上限六小时预算耗尽不算信源的失败——只等 15 分钟、不记失败次数recordFetch。RSS 用 ETag/Last-Modified 校验器命中 304 直接跳过整个解析流程。时间线规则旧文不刷屏每条资料入库时由统一的decideTimeline决定时间线发现时已发布超过 48 小时的资料、新信源第一次导入的存量一律按原文时间归档为 backfill——不进今天、不推送、不占热度。没有可信发布时间的资料先不公开等到从详情页读到日期再判定新旧。这一条从根上杜绝了回灌旧闻刷屏的问题。判稿预筛打底同一标准独立双评采集入库后资料进入editorial/analyze.ts的分析流程每个步骤的提示词都从industry/prompts/读取改标准不用改代码。第一步预筛wide recall预筛用PREFILTER_SYSTEMprefilterUser判断这条资料是否属于本行业只输出PASS / BLOCK / UNKNOWN。设计上追求高召回只有 BLOCK 才终止而在材料缺失时给的 BLOCK会被改判为 UNKNOWN——避免模型没看到全文就武断毙掉。UNKNOWN 会等新 revision 到来后再走后续步骤。第二步双评同一标准独立打两次分这是双评判稿的核心。SCORE_CALLS 2同一条资料用同一份评分标准、同一个模型、独立调用两次各给一个 0–100 的attentionScore两次之和 ≥ 2 × 门槛才算入选normalizeAnalysis里的selected判定。为什么打两次代码注释点破了意图两次调用是串行的第二次调用能复用 provider 的 prompt 缓存而两次独立打分实际上是对同一模型判断的两次采样用来摊薄单次调用的随机性。评分的输入刻意不带任何来源事实提示词禁止模型猜事实只给发布时间北京时间、原标题和完整正文——因为评分发生在任何中文文案生成之前避免写作影响打分。第三步门槛按信源分级门槛定义在industry/selection.ts这就是你的行业 KnowHow落点之一thresholds: { T1: 60, T1_5: 65, T2: 76 }, understandFloor: 50,官方一手信源门槛 60媒体与个人要 76——因为官方信源默认可信度高媒体转载的新消息必须足够重要才值得占用精选席位。没入选但平均分高于 50 的资料仍用精选级的内容理解来写标题、摘要、推荐理由其余用更便宜的标题摘要翻译。这个分级门槛提示词的组合可以用你自己标注的样本在 SelectBench 里闭环校准scripts/eval-selection.ts。第四步写作与结构化结构抽取分类、标签、主体、新闻事实与评分并行跑互不等待。写作分两档入选/近入选用understand内容理解输出中文标题、答案先行摘要、推荐理由、条目类型与作者角色其余用summarize。X 上已经是一段中文的短帖直接verbatim原样保留不调用模型。防幻觉在这里有两道硬校验normalizeStructure只保留原文里真实出现过的引用quote 必须能在原始材料中找到才能进事实证据finalizeCopy的 identityGuard 防止模型把原文没提到的公司写进标题。事件聚簇向量召回 → 模型判断 → 换家复核判完稿的资料进入归组阶段events/group.ts。聚簇不是简单按关键词聚类而是一套召回 判断 复核 人工不覆盖的四段式流程召回混合检索召回逻辑在events/recall.ts以最近 14 天属于活跃事实的报道为池子用标题摘要的向量做召回没配向量服务时退化为共享字符 bigram 的文本重合度。RECALL_MIN_COSINE 0.6取前 10 个候选事实。向量缓存在 worker 进程内Float32Array只有新增或变更的文本才重新 embedding归组串行执行单进程持有整个窗口。判断四路关系判定judgeBatch让模型对候选事实逐一判定关系输出对每个候选的明确裁决BatchSchema.refine强制每个候选必须恰好一个决策区分同一件事 / 后续进展 / 两件事。一篇报道挂在 fact同一真实事件上fact 挂在 story事件及其直接进展上进展只挂到开启它所在 story 的那个 fact——story 不会靠链式挂载无限长大。复核拿不准就换一家模型关键设计在confirmMerge当候选与事实的余弦相似度低于 0.85CONFIRM_BELOW_COSINE时合并不是直接写入而是让另一家复核模型GROUP_REVIEW_MODEL可以单独配置把两篇报道原文读一遍、独立判断只有它同意才写入。这相当于把高风险操作的仲裁权从主判模型手里剥离。人工优先级最高manualDecision保证人工改过的归属fact_articles.manual或保持独立grouping_overrides永远压过任何模型决策且归组修订不会覆盖人工选择。两条 story 因桥接报道被牢牢连在一起时consolidate.ts直接比较两个根事实两个模型都判为同一 story 才合并。热度按事件算不按文章算归组完成后热度计算在events/hot.ts这是一段没有模型参与的纯规则逻辑48 小时窗口内每个独立参与者只计一次重复采集不加热度一家媒体发十篇也只算一次热度按24 小时半衰期衰减power(0.5, elapsed/24h)事件入围至少2 个独立参与者且至少 1 个编辑类报道型信源榜单限 10 条热度值 ×10 展示保留一位小数。趋势标签新/上升/下降/飙升也是规则判定与 6 小时前比较只对比全程被观测到的参与者采集落后的信源被排除在对比之外避免假涨近 6 小时出现且参与方 ≥3、占比过半标surge。代码里甚至用信源时钟sourceClocks检测采集落后的源——一个源连续三个周期没抓成功就被标记为 behind其证据不参与对比。这也是为什么说热度算法零模型干预全部逻辑在hot.ts里结果可复现、可审计。生成与出版规则编排为主模型只写文案日报全规则编排不调模型日报逻辑在reports/edition.ts和reports/compose.ts。composeDaily的注释写得很直白The daily is computed by rule, without a model judging anything。每天 08:00EDITION_TIMES可改出刊覆盖过去 24 小时每个事件只出一条取最具代表性的报道pickRepresentative同一事件的后续进展列在它下面过去 7 天已报过的事件dailyMemory只在有新事实时跟进否则直接抑制——repeatsSuppressed就是被抑制的重复条数统计事件重要性 最好评分 独立参与者对数值 一手加分 − 跟进惩罚importance排布规则同样明确头条 1 条、高亮 3 条、正文 12 条MAIN_ENTRIES、简讯 10 条FLASH_ENTRIES同一来源最多 2 条PER_SOURCE避免一家媒体霸版评分漏掉但确属要闻的事件≥3 个独立参与者 有官方帖用fillIn补进来。注意日报有一个安静日保护窗口内没有任何被评分资料时直接抛错让 run 失败而不是出一份空日报——避免把上游故障误报成今天无事发生。周报月报模型只写总述和栏目导读周报、月报从日报里汇编composePeriod事件的选择、排序、栏目划分全部由规则决定模型只写总述周报 ≤240 字、月报 ≤340 字和 ≥3 个事件的栏目导读。写出来的文案还要过两道关fitted按句截断超长只保留放得下的完整句grounded文案里出现的每个大写/编号英文 token、每个 ≥3 位数字或带百分比的数字、每家公司名必须能在本期条目原文里找到否则这段文案直接弃用。总述不合格时期刊照常出版由读取层用条目原文生成总述——写作模型失败不阻塞出刊。无人值守的底盘回执、预算熔断与断点续跑最后是让整条流水线无人值守运转的工程底座docs/architecture.md有完整约定花钱的请求有回执每个付费请求模型、X、公众号、Jina先记一张回执拿到结果先存再用。进程重启、任务重试时复用已付钱的结果不重复花钱providers/receipts.ts预算熔断每个付费服务有每分钟/每小时/每天上限超过就暂停后台可配断点续跑出刊每半小时检查一次到期未出的期刊按序补写单次最多 8 份composeDueReports失败不阻塞后续安全阀COLLECT_ENABLED、MODEL_CALLS_ENABLED等开关只决定发不发出去只有显式true才打开测试环境默认关闭外部调用。小结这套流水线最值得借鉴的三个设计把整条链路拉通看AIHOT 的工程化取舍可以提炼为三点把主观判断和客观规则分层是否入选由双评分级门槛决定是否同一事件由模型判断换家复核但热度、日报编排、周报月报汇编、去重、时间线全部是纯规则——模型只出现在必须理解语义的环节且每个环节都能单独换模型、单独设预算。把花钱和可靠性分离回执机制保证进程重启不重复付费预算熔断保证成本失控前自动暂停而预算耗尽不被当作信源故障——失败归因清晰。把行业 KnowHow 收敛到两个目录industry/分类、信源、提示词、门槛和site/站名、模型、品牌承载全部可定制项业务代码不写死任何行业值。想改成法律、金融、HR 的行业热点站改的是提示词和门槛不是流水线。这套引擎已经以 MIT 协议开源你拿到的不是一个演示项目而是一份线上在跑的完整情报系统。剩下的路——把你行业的标准写进industry/prompts/selection-score.md——就是这套流水线真正开始为你运转的时刻。【免费下载链接】AIHOT一个自己找热点、自己写日报的网站框架。把信源和精选标准换成你的它就是你的行业热点站。项目地址: https://gitcode.com/gh_mirrors/ai/AIHOT创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考