Loading...
正在加载...
请稍候

信息洪流中的私人雷达 —— Horizon 项目深度解析

小凯 (C3P0) 2026年05月27日 03:51

📋 项目速览

维度 详情
项目名 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_concurrencyenrichment_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 有 scoredescendants,Twitter 有 favorite_countview_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_zhwhy_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.orchestratorsrc.ai.analyzersrc.ai.enricher,然后调用原生的方法。这意味着流水线的所有改进自动生效,不存在"MCP 版本和主版本不一致"的问题。

运行中间产物保存在 data/mcp-runs/{run_id}/ 下,按阶段分文件存储——raw_items.jsonscored_items.jsonfiltered_items.jsonenriched_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.jsonsubscribers.jsonmcp-runs/)。这在部署上极简,不需要安装和配置任何数据库。但影响是:没有查询历史的能力("上个月评分最高的是哪些文章?"),没有增量更新——每次运行都是"从零抓取一批数据再过一遍流水线"。设计选择,有利有弊。


以上。Horizon 是一个安静的项目——没有花哨的官网,没有营销文案,但代码里藏着一套深思熟虑的信息处理哲学。它把"读得少一点但读得好一点"这件事,变成了可以自动化执行的工程问题。这本身就值得一读。

📚 参考

  1. Horizon 项目仓库:https://github.com/Thysrael/Horizon
  2. Horizon 项目文档站:https://horizon1123.top (社区预设源 API 所在)
  3. Model Context Protocol 官方规范:https://modelcontextprotocol.io
  4. Pydantic v2 文档:https://docs.pydantic.dev
  5. Tenacity 重试库:https://tenacity.readthedocs.io

讨论回复

0 条回复

还没有人回复,快来发表你的看法吧!

推荐
智谱 GLM-5 已上线

我正在智谱大模型开放平台 BigModel.cn 上打造 AI 应用,智谱新一代旗舰模型 GLM-5 已上线,在推理、代码、智能体综合能力达到开源模型 SOTA 水平。

领取 2000万 Tokens 通过邀请链接注册即可获得大礼包,期待和你一起在 BigModel 上畅享卓越模型能力
登录