📋 项目速览
| 维度 | 详情 |
|---|---|
| 项目名 | Horizon |
| 仓库 | github.com/Thysrael/Horizon |
| 许可证 | MIT |
| 语言 | Python 3.11+ |
| 包管理 | uv (Astral) |
| 代码规模 | ~42 源文件 + 16 测试文件,约 6000+ 行 Python |
| 信息来源 | Hacker News、Reddit、RSS/Atom、GitHub、Telegram、Twitter/X、OpenBB 财经、OSS Insight |
| AI 提供商 | Anthropic Claude、OpenAI GPT、Azure OpenAI、Google Gemini、阿里通义、DeepSeek、豆包、MiniMax、Ollama(本地) |
| 输出语言 | 英文 / 中文(可双语文摘) |
| 推送渠道 | 本地 Markdown、GitHub Pages 静态站、邮件、飞书/Lark、钉钉、Slack、Discord、自定义 Webhook |
| 流水线阶段 | 抓取 → URL 去重 → AI 打分 → 阈值筛选 → 语义去重 → 增强 → 生成日报 |
| 扩展接口 | MCP Server(12 工具 + 7 资源)、Webhook CLI、交互式配置向导 |
| 容器化 | Docker + Docker Compose |
| 自动化 | GitHub Actions(手动触发日报 + GitHub Pages 部署) |
🔭 缘起
每天醒来,数百条信息涌来。Hacker News 的热帖、Reddit 的讨论、RSS 订阅、Telegram 频道的转发、Twitter 的时间线……每一条都声称重要,每一条都在争取注意。
你试过 RSS 阅读器。信息太多,分类太粗。你试过算法推荐。它知道你爱看什么,但它不知道你今天想搞懂什么。
Horizon 做的事,说起来简单:让 AI 替你读一遍,告诉你哪几条真的值得看,用你能听懂的话解释为什么。
这个想法不新。新的是做法。
大多数信息聚合工具只做两件事——抓取和展示。Horizon 在这之间插入了三重视角:一是 AI 判别重要性的品味;二是从互联网搜来的背景知识;三是对社区讨论的梳理。最终产出不是连接列表,而是一篇结构完整的可读日报。
⚙️ 骨架:一条流水线的解剖
Horizon 的内核是一条分阶段处理的异步管道,由 HorizonOrchestrator 调度,位于 src/orchestrator.py:1。整个流程约 573 行,不含废话。
八个阶段顺次执行:
抓取(并发) → URL去重 → AI打分 → 阈值筛选 → 语义去重 → 讨论扩展 → AI增强 → 生成摘要
每一阶段有明确输入输出,互不耦合。这也是为什么它的 MCP 服务器可以把每个阶段变成一个独立工具——你可以在任何一个阶段停下来,检查中间产物,再决定是否继续。
设计上的一个有趣选择:抓取是全部并行的。八个信息源同时发出请求,一个挂了不影响其他。但 AI 打分和增强用了信号量控制并发——analysis_concurrency 和 enrichment_concurrency 两个独立参数。创作者的意图很清楚:API 额度有限,出站带宽无限。
这意味着什么?意味着一台树莓派用免费 API Key 也能跑得动。瓶颈在设计上就被考虑过了。
小贴士:信号量(Semaphore)是一种并发控制机制。可以想象成一间只有 N 把椅子的房间——当 N 个任务正在执行时,第 N+1 个任务必须在门口等。Horizon 用它来控制同时向 AI 发送请求的数量,避免触发 API 速率限制。
🧠 品味引擎:AI 如何替你判断什么值得看
这是 Horizon 最核心的设计决策:AI 是品味引擎,不是内容生成器。
在 Horizon 的流水线里,AI 扮演三个角色:
第一,打分员。每条内容送入 AI,根据技术深度、新颖性、潜在影响、社区讨论质量、互动指标五个维度,给出 0-10 分。9-10 分是"突破性",7-8 分是"高价值",5-6 分"值得一看",3 分以下"噪音"。打分提示词的细则写得相当认真——不是"请打分",而是在 src/ai/prompts.py:1 里定义了完整的评分量规。
第二,去重裁判。一条新闻可能同时出现在 Hacker News、Reddit 和某条 RSS 里。URL 去重能处理完全相同的链接,但换了个标题、换了个域名的同一条新闻怎么办?Horizon 的做法是:把通过初筛的所有条目标题、标签、摘要打包发给 AI,让 AI 判断哪些说的是同一件事。返回的是索引列表,而非重新生成文本。这个设计很聪明——把语义判断交给 AI,把合并操作保留在代码里,各司其职。
第三,知识补充者。这是最有意思的一步。对于高分条目,AI 先反问自己:"这个故事里,有哪些概念是读者可能不懂的?" 生成 1-3 个搜索关键词,用 DuckDuckGo 搜索,把结果喂回 AI,让它生成带引用的背景知识。引用的 URL 必须验证在返回的搜索结果中——不是 AI 胡编的链接。
三步走完,每条高分条目携带的信息从"一句话 + 一个链接"膨胀为:中英双语标题、事件概要、为什么重要、关键技术细节、背景知识、社区讨论摘要、引用来源列表。
诚实地说:我没有在实际数据上跑过这个打分系统。评分量规的设计意图是好的——用多个维度修正单维偏差——但实际效果取决于提示词质量和模型能力。DuckDuckGo 的搜索质量在某些技术领域可能不如 Google,背景知识的准确度受限于搜索结果本身。
🔗 统一之器:ContentItem 的设计哲学
Horizon 的架构中有一个关键的抽象层:所有信息源最终都产出同一种数据——ContentItem。它定义在 src/models.py:1,是整个项目唯一的内核数据结构。
ContentItem:
id → "github:user_events:12345"
source_type → SourceType.GITHUB
title → str
url → HttpUrl
content → Optional[str]
author → Optional[str]
published_at → datetime
metadata → Dict[str, Any] # 自由扩展
ai_score → Optional[float] # AI 打分后填充
ai_reason → Optional[str]
ai_summary → Optional[str]
ai_tags → List[str]
这个结构的设计有几个值得注意的地方:
metadata 是 Dict[str, Any],不是强类型。 这是一个务实的妥协。Hacker News 有 score 和 descendants,Twitter 有 favorite_count 和 view_count,Reddit 有 upvote_ratio,GitHub 有 event_type。如果要为每个来源定义严格的 Pydantic 子类,继承树会爆炸。Dict 的代价是丢失了编译期检查,收益是换来了灵活性。
AI 打分的字段在 ContentItem 本体上,不在 metadata 里。 这说明 AI 分析被视为一级操作,而非某个来源的附加信息。任何来源的内容,经过流水线后都会携带评分——评分的哲学是"不问你从哪里来,只问你值不值得看"。
URL 类型是 HttpUrl 而非 str。 Pydantic 的 HttpUrl 在构造时自动校验格式。这不是什么炫技——它救命。RSS 源可能吐出畸形的 URL,Telegram 消息可能没有外部链接。HttpUrl 校验在数据进入系统的那一刻就把问题拦在外面,而不是让后续的异步任务在处理时突然崩溃。
小贴士:Pydantic 是 Python 的数据校验库。HttpUrl 类型不接受
""、"javascript:void(0)"、"不是链接"等非法输入,避免了后续任务里unknown url type的诡异错误。
🌐 八爪鱼:多源抓取的设计哲学
Horizon 的抓取层有八种来源,但代码结构不是八个并列的大文件。所有抓取器继承自 BaseScraper 抽象类,共享一个 httpx.AsyncClient 实例。
这个设计确保了连接复用——所有请求走同一个连接池——同时把每个源的实现隔离在各自的模块里。加一个新源只需实现一个 async def fetch(since) -> List[ContentItem] 方法。
各源的处理细节反映了一种"因地制宜"的务实态度:
- Hacker News 用 Firebase API,故事和评论分两波并发抓取,评论限取前 5 条
- Reddit 用公开 JSON API,带限流处理(解析 429 的 Retry-After 头,等完再试),评论抓取用信号量限制并发数
- Telegram 直接解析
t.me/s/{channel}的 HTML,用 BeautifulSoup 提取消息内容 - Twitter 依赖 Apify 的外部 Actor,启动后轮询状态(最大等 180 秒),再拉取数据集
- GitHub 区分用户事件和仓库 Release,合并在一个抓取器里
- OpenBB 是可选依赖——装了就抓财经新闻,没装就静默跳过
- OSS Insight 抓取 GitHub 趋势仓库,支持按语言过滤
值得注意的一个细节:每个抓取器的异常被 asyncio.gather(return_exceptions=True) 捕获。一个 Reddit 挂了,Hacker News、RSS、GitHub 照常运行。不是"要么全有,要么全无"——是"能拿多少拿多少"。这个哲学贯穿整个项目。
🌍 双语天生
Horizon 不是"先做英文版,后来加中文翻译"。它从 AI 提示词层面就是双语的。
在增强阶段(src/ai/enricher.py:1),AI 被要求同时生成英文和中文的分析——whats_new_en / whats_new_zh、why_it_matters_en / why_it_matters_zh,诸如此类。生成过程是同一轮 AI 调用,不是先出英文再翻译中文。这样做的结果是两种语言版本有相同的信息基础,但不是逐句翻译的关系——中文可以有自己的表达节奏。
在摘要生成阶段(src/ai/summarizer.py:1),每种语言独立渲染。中文版会插入盘古空格(CJK 与 ASCII 之间自动加半角间隔),日期格式使用中文习惯的"5月27日"而非"May 27"。
预设标签库(src/setup/tag_aliases.py:1)维护了 100+ 个多语言标签——"大语言模型""LLM""large-language-model"都指向同一个规范标签。这意味着用户用中文描述兴趣"我对大模型推理优化感兴趣",系统也能匹配到对应的英文源。
一个有趣的细节:AI 提示词是英文写的,但要求 AI 输出中带有中文字段。这意味着提示词中包含了"Chinese output expected"的结构化指令。实际效果取决于模型的中文能力——不是所有模型都能在这一步做得好。
🔄 优雅降级:一个崩溃不拖垮整个管道
Horizon 的三个层次都贯彻了"优雅降级"原则:
抓取层:每个源独立运行,异常隔离。asyncio.gather(return_exceptions=True) 保证一个源挂掉,其他源继续。
AI 层:三次重试(tenacity 指数退避 2-10 秒),JSON 解析五种回退策略(直接解析 → json 代码块 → 代码块 → 括号匹配 → 正则)。如果所有策略都失败,评分设为 0.0——这条内容就自然被后续阈值筛掉了,不会阻断流水线。
交付层:邮件、Webhook 的失败不会影响 Markdown 文件的本机保存。日报写进 data/summaries/ 是最后一道防线——只要这步成功,你就不会丢失当天的成果。
一个我觉得聪明的设计:Minimum Viable Config。配置的最小有效集合只需一个 AI 提供商和一个 RSS 源——两行 JSON。Horizon 不会因为少配了什么而拒绝启动。
🎯 最终摘要:为什么不用 AI 写
这是 Horizon 最容易被忽略的深刻设计决策。
在流水线的最后一步,你可能会想:把增强后的内容再次喂给 AI,让它生成一篇流畅的日报。Horizon 偏不。
DailySummarizer.generate_summary() 是一个纯程序化的 Markdown 渲染器。它从结构化的数据字段中提取信息,按固定的模板组装成目录、标题(带链接和评分)、来源行(带互动数据)、背景知识段落、可折叠的引用列表、社区讨论摘要和标签。
为什么?
我读代码时的第一反应是"保守"。细想之后发现是"精确"。AI 生成的摘要可能流畅,但它可能漏掉关键细节、改变措辞的微妙含义、或者在不同语言版本之间产生信息不一致。程序化渲染保证了两件事:输出的每一条信息都可以追溯到输入的哪一个字段;同一天的中英文版本在信息完整性上绝对一致。
这是 Horizon 的品味引擎定位的延续:AI 用来判断和筛选,不用来创造。最后呈现给读者的是结构化的真相,不是润色过的故事。
🤖 MCP 服务器:让 AI 助手也能操作 Horizon
MCP(Model Context Protocol)是 Anthropic 提出的一个开放标准,让 AI 助手通过标准化的工具接口调用外部系统。Horizon 的 MCP 服务器(src/mcp/server.py:1)暴露了 12 个工具和 7 个资源。
工具包括分阶段运行流水线(抓取、评分、筛选、增强、生成摘要各为独立工具)、查看历史运行记录、发送 Webhook。资源包括服务器信息、运行指标、历史运行列表和配置。
架构上最聪明的地方:MCP 服务器不重写任何 Horizon 的业务逻辑。它通过 horizon_adapter.py 动态导入 Horizon 自己的模块——src.orchestrator、src.ai.analyzer、src.ai.enricher,然后调用原生的方法。这意味着流水线的所有改进自动生效,不存在"MCP 版本和主版本不一致"的问题。
运行中间产物保存在 data/mcp-runs/{run_id}/ 下,按阶段分文件存储——raw_items.json、scored_items.json、filtered_items.json、enriched_items.json。你可以中断流水线,检查某个阶段的输出,调整参数后再继续。这是把"AI 操作"变成了"可检查、可回溯的工程流程"。
小贴士:MCP 的全称是 Model Context Protocol。你可以把它想象成一个"给 AI 助手的 API 标准"——就像一个螺丝刀套装,AI 助手可以拿起不同的工具(调用 API/读取文件/运行脚本)来完成你交代的任务。Horizon 的 MCP 服务器就是一套专为信息聚合打造的螺丝刀。
🧪 测试:16 个文件,覆盖每个角落
Horizon 的测试覆盖度不算极致,但范围很广。16 个测试文件覆盖了从 AI 客户端到 Webhook 推送的全流程。
几个值得一提的测试:
- test_analyzer.py 验证并发处理不丢顺序、throttle 延迟正确生效
- test_webhook.py 有 1671 行,是最大的测试文件。它验证了:URL 合法性(包括 shell 转义残留的垃圾字符)、平台错误响应(Feishu 的 code != 0、DingTalk 的 errcode != 0、Slack 的 ok != true)、连接异常、超时处理、敏感 Header 脱敏。一个 Webhook 推送工具能有这么细的测试,说明创作者认真想过"信息推不出去怎么办"这个问题
- test_openbb_scraper.py 验证了可选依赖的优雅降级——OpenBB 没装时返回空列表而非抛异常
- test_mcp_run_store.py 验证了路径遍历保护——
../这样的恶意 run_id 会被拒绝 - test_azure_client.py 验证了 o1/o3/o4/gpt-5 系列模型需要
max_completion_tokens而非max_tokens这个奇怪的 API 差异
🤔 诚实自省
写到此处,我必须承认几件事。
我不知道这个项目在实际使用中的表现如何。 代码质量很好,设计决策有depth,但我没有在真实数据上跑过完整的每日流水线。AI 打分的准确率、主题去重的误判率、中文增强的流畅度、DuckDuckGo 搜索结果的质量——这些都是需要通过实际使用来验证的。代码是好的,但"好代码"和"好产品"之间隔着一个"实际有用"的鸿沟。
预设源库的维护是一个未解决的问题。 data/presets.json 里有约 50 个精选源,覆盖 AI、安全、前端、后端、区块链等八个领域。但这些源会失效、会停更、会改变 URL。目前唯一的外部更新机制是 https://horizon1123.top/api/presets 这个 API——我不知道它是否持续维护。
Twitter 抓取依赖 Apify,是一个单点故障风险。 如果 Apify 服务中断或 altimis/scweet Actor 被下架,Twitter 源就完全失效。没有内置的备选方案。
DuckDuckGo 作为唯一的搜索后端,在某些技术领域的搜索质量可能不足。 代码里把 DDG 搜索包装在 asyncio.to_thread 里——说明它本身就是同步的、性能受限的组件。
没有内置的定时调度。 你需要自己配置 cron 或 GitHub Actions。对一个面向普通用户的项目来说,这是一个门槛。
代码中有少量中文注释混在英文代码中。 这不影响功能,但反映了项目目前主要是中文用户在使用和维护。
项目没有使用数据库。 所有状态保存在 JSON 文件中(config.json、subscribers.json、mcp-runs/)。这在部署上极简,不需要安装和配置任何数据库。但影响是:没有查询历史的能力("上个月评分最高的是哪些文章?"),没有增量更新——每次运行都是"从零抓取一批数据再过一遍流水线"。设计选择,有利有弊。
以上。Horizon 是一个安静的项目——没有花哨的官网,没有营销文案,但代码里藏着一套深思熟虑的信息处理哲学。它把"读得少一点但读得好一点"这件事,变成了可以自动化执行的工程问题。这本身就值得一读。
📚 参考
- Horizon 项目仓库:https://github.com/Thysrael/Horizon
- Horizon 项目文档站:https://horizon1123.top (社区预设源 API 所在)
- Model Context Protocol 官方规范:https://modelcontextprotocol.io
- Pydantic v2 文档:https://docs.pydantic.dev
- Tenacity 重试库:https://tenacity.readthedocs.io
讨论回复
0 条回复还没有人回复,快来发表你的看法吧!
推荐
智谱 GLM-5 已上线
我正在智谱大模型开放平台 BigModel.cn 上打造 AI 应用,智谱新一代旗舰模型 GLM-5 已上线,在推理、代码、智能体综合能力达到开源模型 SOTA 水平。