ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

三个环境变量切换大模型:10 分钟跑起一套会自己判稿的 AIHOT 热点站

三个环境变量切换大模型:10 分钟跑起一套会自己判稿的 AIHOT 热点站

三个环境变量切换大模型:10 分钟跑起一套会自己判稿的 AIHOT 热点站

【免费下载链接】AIHOT一个自己找热点、自己写日报的网站框架。把信源和精选标准换成你的,它就是你的行业热点站。项目地址: https://gitcode.com/gh_mirrors/ai/AIHOT

过去一个月,一个叫 AIHOT 的开源项目连续出现在 GitHub 日榜上:GitHub 累计约 5400 Star、MIT 协议、TypeScript 全栈、一个曾经做到百万月活的 AI 热点资讯网站,把"引擎"完整开源了出来。社区里的讨论大多集中在同一个判断上——这不是又一个"RSS 阅读器 + 摘要"的玩具,而是一套会自己判稿的内容生产线:它自己抓信源、自己判断哪条值得看、自己写中文标题和摘要、把不同来源说的同一件事聚成事件算热度、每天早上自动出一份日报。

更难得的是,起跑门槛被压到了极致:你只需要一个 OpenAI 兼容的模型 API Key,和三个环境变量。本文就基于仓库真实源码,拆解"三变量换模型"的机制、三进程 + PostgreSQL 的最小启动姿势,以及第一份自动日报从采集到成刊的完整判稿流程。

为什么是它:一套会自己判稿的开源引擎

AIHOT 是自媒体博主"数字生命卡兹克"做的 AI 热点网站,仓库 README.md 里写得很直白:"这个仓库是它的引擎和框架:网站、后台、精选流程、聚簇和热度算法,所有提示词的原文和入选门槛,都在这里。"

它解决的问题很具体:做法律、做 HR、做金融的人想要自己的行业热点站,但没人能替他们定义"什么消息对这个行业才算热点"。于是作者把火种交出来——信源换成你的,精选标准换成你的 KnowHow,它就是你的行业热点站。

仓库带了 18 个公开的海外 AI 资讯源做示范(industry/sources.json,OpenAI、Google DeepMind、Hugging Face 等官方源),够你跑起来立刻看到效果。框架的核心能力包括:六种信源接入(RSS、网页列表、JSON 接口、X 账号、微信公众号、外部推送)、多阶段大模型判稿(预筛、双评、结构化、写作)、事件聚簇与热度算法、日报/周报/月报自动编排,以及预算熔断与模型回执机制。

三个环境变量,切换整个站的"大脑"

AIHOT 的模型调用层在 packages/backend/src/providers/llm.ts。框架内置一个名为default的模型规格,它完全由环境变量驱动:

default: { key: "default", service: "llm", baseUrlEnv: "LLM_BASE_URL", apiKeyEnv: "LLM_API_KEY", get model() { return process.env.LLM_MODEL ?? ""; }, get extra() { return extraFromEnv(process.env.LLM_EXTRA_JSON); }, get reasoningTokens() { return reasoningTokensFromEnv(process.env.LLM_REASONING_TOKENS); }, get jsonMode() { return process.env.LLM_JSON_MODE !== "false"; }, get vision() { return process.env.LLM_VISION === "true"; }, }

也就是说,预筛、评分、结构化、写作、归组、综述、周报月报总述……所有步骤的默认模型,都来自这三个变量:LLM_BASE_URL(接口地址)、LLM_API_KEY(密钥)、LLM_MODEL(模型名)。任何 OpenAI 兼容接口都行——调用就是POST {baseUrl}/chat/completions,带Bearer鉴权。.env.example里给了三种写法的对照:

# DeepSeek LLM_BASE_URL=https://api.deepseek.com/v1 LLM_MODEL=deepseek-flash LLM_EXTRA_JSON={"thinking":{"type":"disabled"}} # 千问 LLM_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1 LLM_MODEL=qwen3.8-flash LLM_EXTRA_JSON={"enable_thinking":false} # 智谱 LLM_BASE_URL=https://open.bigmodel.cn/api/paas/v4 LLM_MODEL=glm-5.3-flash LLM_EXTRA_JSON={"thinking":{"type":"enabled"},"reasoning_effort":"low"}

三个变量之外还有几个有用的"旋钮":用推理模型(先想再答)时设LLM_REASONING_TOKENS给推理留出输出额度,避免报output token limit reached (finish_reason=length);模型能看图就开LLM_VISION=true,精选内容的标题摘要会参考首图;接口不支持 JSON 模式时设LLM_JSON_MODE=false。

更进阶的玩法在 packages/backend/src/editorial/models.ts:modelFor()的选型优先级是后台切换 > 环境变量 > 代码默认,其中每一步都有专属环境变量——PREFILTER_MODEL(预筛)、SCORE_MODEL(评分)、UNDERSTAND_MODEL(内容理解)、SUMMARIZE_MODEL(标题摘要)、STRUCTURE_MODEL(结构抽取)、GROUP_MODEL/GROUP_REVIEW_MODEL(归组与复核)、DIGEST_MODEL(事件综述)、TRANSLATE_MODEL(全文翻译)。比如想让"归组复核"这一步换一家更稳的模型,直接在.env里加一行GROUP_REVIEW_MODEL=mimo-v2.6-flash即可。具名模型的预设(地址、密钥环境变量、额外参数)都写在 site/models.ts,框架把"每个步骤用哪个模型"完全留给了站点。

10 分钟起服务:web / api / worker 三进程 + PostgreSQL

整套系统的进程模型在 docs/architecture.md 里讲得很清楚,三个进程各司其职:

  • api(apps/api/):Fastify,提供网站自用接口、公开 API(/api/v1/)、RSS、MCP、后台接口、图片代理和分享图;
  • worker(apps/worker/):pg-boss 任务队列与定时任务——抓信源、调模型、归组、热度、日报、告警、清理,所有模型调用都发生在 worker 里;
  • web(apps/web/):React Router 服务端渲染的网页,只通过 HTTP 读 api,不碰数据库、不持有任何密钥。

业务代码全部集中在 packages/backend/,站点身份在site/,行业配置(信源、提示词、门槛)在industry/。

启动只需要三条命令:

git clone https://github.com/KKKKhazix/AIHOT.git myhot cd myhot node scripts/init-env.ts --llm-key <你的模型 API Key> docker compose up -d --build

init-env.ts(scripts/init-env.ts)会基于.env.example生成.env:填好随机生成的SESSION_SECRET、IMG_PROXY_SIGN_SECRET、POSTGRES_PASSWORD,生成一个管理员密码并打印一次(默认按 DeepSeek 配置,换模型服务商就照上面的例子改三个LLM_*变量)。docker compose up -d --build会拉起五个容器(docker-compose.yml):

  • db:PostgreSQL 17;
  • setup:先跑数据库迁移(scripts/migrate.ts)和种子数据(scripts/seed.ts,导入示范信源),然后退出;
  • api:Fastify,端口 3001;
  • worker:抓取、模型处理、定时任务;
  • web:SSR 网页,映射到宿主机 3000 端口。

打开 http://localhost:3000 就是站点,后台在/admin,用刚才打印的管理员密码登录。一两分钟后开始有内容,第一次导入的一百多条资料大约半小时处理完——每条都要走预筛、评分、结构化、写标题摘要,再归组。部署层面,docs/deploy.md 还覆盖了中国大陆服务器的 npm 镜像与出口代理(EGRESS_PROXY_URL)、域名与 HTTPS(docker compose --profile https up自动起 Caddy)等细节。

第一份自动日报是怎么"判"出来的

跑起来之后,最值得观察的就是 worker 后台那条完整的判稿流水线。以 README.md 的六步描述为骨架,对照源码走一遍:

第一步,预筛(宽召回)。industry/prompts/prefilter.md 只回答一个问题:这条材料是不是本行业的事。输出PASS / BLOCK / UNKNOWN三态——BLOCK的资料不出现在任何公开页面;拿不准的UNKNOWN保留待补材料,不直接丢弃。实现上,BLOCK且材料缺失时会降级为UNKNOWN(packages/backend/src/editorial/analyze.ts 的runPrefilter)。

第二步,双评(独立打两次分)。这是 AIHOT "会判稿"的核心。评分标准 industry/prompts/selection-score.md 把事件注意力价值压缩成一个 0–100 的整数attentionScore:内部按五轴(实质份量 sig、信息增量 nov、证据强度 cred、共振面 reson、可用性 act)独立打 0–10 分,再按事件类型权重(模型发布、产品上线、工具技巧、论文、行业事件等七类)加权求和。关键设计在 analyze.ts 的SCORE_CALLS = 2:同一份标准、同一篇材料,独立打两次分,两次之和 ≥ 2 × 门槛才入选,页面上显示向下取整的平均分。门槛按信源分级区分(industry/selection.ts):

thresholds: { T1: 60, T1_5: 65, T2: 76 }

官方一手信源(T1)门槛低一些,媒体和个人(T2)门槛高一些——同样一件事,官方原文更值得先看。

第三步,结构化。与评分并行,用 industry/prompts/structure.md 抽取分类、标签、主体公司,并判断这条是不是一条具体新闻;是的话抽出事实(谁、做了什么、对什么、什么时候)。源码里有一个很实在的反幻觉设计:normalizeStructure()会把模型抽出的引用(evidence/quote)逐一和原始材料比对,只保留确实出现在原文里的句子,防止模型编造出处。

第四步,写作。入选的和差一点入选的(平均分高于understandFloor: 50)按 industry/prompts/content-understanding.md 写中文标题、答案先行的摘要和推荐理由;其余按更便宜的summarize-*.md提示词写简短标题摘要,进"全部动态"。写作还带一重"身份护栏"(identity guard),防止模型把原文根本没提到的公司写进标题。

第五步,归组与热度。不同来源报道的同一件事,用标题摘要向量(没配向量服务时用文字重合度)找候选、再让模型判断关系,聚成一个事件;拿不准的合并,写入前再让模型复核一遍(GROUP_REVIEW_MODEL)。热度按事件不按文章算,规则全在 packages/backend/src/events/hot.ts:heat-v1-48h-halflife24h——48 小时窗口、24 小时半衰期衰减、每个独立来源只算一次。重复抓取不会多算,一家媒体发十篇也只算一次,所以排在前面的,是真正有很多人在说的事。

第六步,成刊。日报每天 08:00 出(时间在 site/site.ts 的EDITION_TIMES里改,排程每半小时检查一次),窗口是前一天 08:00 到当天 08:00。编排规则全在 packages/backend/src/reports/edition.ts:一件事一条,取报道最多的那条新闻里最权威的一篇(官方一手优先),同事件当天的其他进展列在下面;前 12 条进正文(第一条是头条,导语就是它自己的标题和摘要,第 2–4 条是今日看点),再往后最多 10 条一行快讯;报过的事只在有新进展时跟进。日报全程按规则计算,不调用模型——只有周报、月报的总述和栏目导读由模型写(industry/prompts/report-period*.md),而且模型写出了条目里没有的名字或数字,这段话就会被丢弃、换成模板生成。

整条流水线还配了两道成本安全阀:每个付费请求(模型、X、公众号、Jina)先记一张"回执",拿到结果先存再用——进程重启、任务重试时复用已付过钱的结果,不重复花钱(packages/backend/src/providers/receipts.ts);每个付费服务有每分钟、每小时、每天的上限,超过就熔断暂停。这也是社区讨论里反复提到的"预算熔断"。

跑起来之后,首页的每日精选、热点榜和日报页面长这样(截图来自用示范信源跑起来的本地站,站名是默认的 MyHOT):

下一步:把它变成你的行业热点站

自动判稿的"品味"完全沉淀在industry/目录里,代码基本不用动:industry/sources.json 换你的信源,industry/prompts/ 写你的行业 KnowHow,industry/selection.ts 调入选门槛,industry/taxonomy.ts 和 industry/topics.json 定义分类与主题;站名、出刊时间、文案则在 site/site.ts。提示词的版本就是它内容的哈希——改了提示词,之后的新资料按新版判断,已判过的不会重算。

如果你觉得默认门槛偏严或偏松,docs/selection.md 还提供了完整的闭环校准方法:拿一两百条自己标注过的资料(gold.jsonl),用 scripts/eval-selection.ts 跑一遍 SelectBench 评测,看错例、改提示词、调门槛,直到它选得合你的口味。

从三个环境变量到一份会自己判稿、自己出日报的行业热点站,中间隔的只是一条docker compose up。剩下的路——把信源换成你的,把标准换成你的——就交给你自己了。

【免费下载链接】AIHOT一个自己找热点、自己写日报的网站框架。把信源和精选标准换成你的,它就是你的行业热点站。项目地址: https://gitcode.com/gh_mirrors/ai/AIHOT

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进