← 返回文章选择

AI Reader 选型:一篇文章经过哪些服务

Miniflux 继续抓取与管理订阅,新增的评分和项目状态由工作台自己保存。

选技术栈时,我先拿一篇文章把流程过一遍:订阅源发布,Miniflux 抓到正文;评分程序读正文,写下分析;页面把文章和分析放到一起。沿这条路径分工,比先列一排框架名字更容易决定要写什么。

Miniflux 管文章,本地库只存它不管的

Miniflux 已经管着订阅、刷新、已读和星标,我准备继续用它。抓取出错回它那里查,评分失败到评分服务查。文章本身没必要为了新界面再复制成另一套真相。后来仓库的 README 也是这么写的:Miniflux 仍是抓取结果的事实来源,AI Reader 管的是会话、文章状态、评分批次、推荐和问答这些它不管的部分。

Miniflux 直供文章与阅读状态,评分经本地库交给页面

文字版示意
Miniflux ──文章与阅读状态──→ reader web
│ ↑
└→ Python 评分程序 → 本地 Postgres

这是四月的方案示意。仓库中能追溯的实现从五月开始,图里的分工是后来逐步落地的,不能把后面的代码都算到选型这一天。

本地库保存 Miniflux 没负责的部分。评分自然在这里,稍后读、最近阅读时间、归档时间和笔记也记在本地的 reader_entry_states;后来用户把文章选进项目,也用本地 entry_project_queue 保存。四月的图只画了评分结果的存放位置,项目线索是后来补上的另一类记录。五月的项目查询实现(scoring/repository.ts,版本 b733e6a)能看出区别。它查的是用户留作项目线索的文章,表里的 status 默认 queued,查询按 queued 取,和评分任务不是一回事。候选收藏仍对应 Miniflux API 的 starred。

这条线可以顺着走一遍。starred 是 Miniflux 的字段,收藏走它的 PUT /v1/entries/{entryID}/bookmark 端点,Miniflux 的 API 认证支持 HTTP Basic 和 X-Auth-Token 两种,仓库里的评分客户端用的是 X-Auth-Token 头。到了五月的版本,“选进项目”的接口会先看文章有没有被收藏,没收藏直接返回 409;收藏过的,才往 entry_project_queue 写一行。本地库不复制一份候选状态,只在我真的选了的时候留一条记录。

页面用 Next.js,模型调用先自己写

页面这边倾向 Next.js。服务端取数和页面在一个项目里,浏览器只和工作台交互,内部地址、访问凭据留在服务端。文章数据已经在同一个进程,复用查询逻辑就直接调用函数,不必为它单开一层接口。真把查询拆到另一个服务,这层接口就得自己写、自己维护,第一版没这个必要。

“凭据留在服务端”在 Next.js 里不用额外配置。官方的取数文档说明,Server Components 在服务端渲染,凭据和查询逻辑不会进客户端包,可以直接在里面查数据库。

模型调用先用 Python 写评分;文章问答围绕当前正文、摘要和选中文本组织上下文,逐段返回答案。考虑过 LangChain 和 Vercel AI SDK,第一版没接。调用路径还短,自己读得明白;等某段重复工作确实变多,再决定要不要换。

“路径还短”有多短,五月的代码能说明。评分那侧是一个几十行的同步 httpx 客户端,temperature 设 0.2,正文截到 6000 字符;模型调用失败时退回一个不调模型的 length-baseline,结果里写上 prompt 版本 rss-score-v3。两个框架没接的理由,放在一起看更清楚:

方案 官方定位 放在这里的问题
自己写 同步 httpx 客户端,带 baseline 兜底和 prompt 版本 要自己维护,但每一步都看得见
Vercel AI SDK 给 React、Next.js 等用的 TypeScript 工具包 评分在 Python 里,用不上
LangChain 卖的是 agent 和 harness 那一层 评分只是一次调用加解析 JSON,用不到 agent

到现在,Web 端 package.json 的依赖里也没有这两个库。当时说“再决定”,后来也一直没有回头去接。

部署:多一些配置,换来职责清楚

外部用户经过 Caddy 接的域名入口,Authelia 做访问控制;服务之间用内部地址和各自的凭据。部署用 Docker Compose,配置工作多了,但职责明确。Next.js、Python worker、Miniflux 和本地库都要沿这条线接好。

“多一些配置”具体多在三处。第一是两套库账号:五月 MVP 计划的 .env.example 里,Miniflux 和评分各占一个 Postgres 库,各有自己的密码和连接串,计划写的是“网关前置认证与旁路评分架构”,Caddy 是唯一的 80/443 入口。第二是内部地址:Docker Compose 的网络文档说明每个服务把名字注册到内部 DNS,服务之间用服务名和容器端口互访,不用暴露宿主端口。第三是认证挂在入口上:Caddy 的 forward_auth 把请求的副本交给认证网关,再把指定的响应头复制回原请求;Authelia 的代理集成文档也说它依赖反向代理设置的元数据头。代理本身不认识用户,头漏配一个,认证就形同虚设。

这个边界后来又收紧过。六月底的 v0.4 重构之后,Web 端不再直接读 Miniflux 或 Postgres,统一走 FastAPI 生成的客户端;在 Caddy 那里绕过 Authelia 的 /api/*,要在 FastAPI 里用 require_user、require_admin 自己拒绝。网关放行的路径得由应用自己把关,这是职责清楚的代价。

我最想保留的是一条能独立调试的路径:拿到一篇文章,读到哪一步失败,就去对应的服务看,不必每次把所有组件从头重启。评分带 baseline 兜底、结果里记着 prompt 版本,“评分失败”和“模型答得不好”就成了两件能分开查的事。

留下几句话 读者来信

读到这里,有什么想法?欢迎留下你的看法。