本地优先知识产品的架构演进

本地优先知识产品的架构演进

# 本地优先知识产品的架构演进

结论

从本地单机 App 走向多端成熟产品,关键不是「要不要服务器」,而是把边界划成 云端加工厂 + 本地主仓库;并且从第一天就把 AI 执行层抽成可替换的接口, 产品形态改在前端,数据模型改在后端。

两条配套边界:apps 是「可独立运行或发布的产品单元」,不都是服务器进程; 文档里写的目标架构 ≠ 当前运行事实(判断现状要看有没有部署配置)。

一、Agent 先变成可执行入口,再动数据模型

原状态:侧边栏四个功能入口(订阅 / 笔记 / 剪藏 / 沉淀),AIPanel 已有对话, 但工具只能读(读笔记、读剪藏、搜索),不能创建和整理。

演进顺序:

阶段内容是否动数据模型
P0把 createNote / saveClip / addSubscription 暴露给 AI tools,让对话能创建笔记、存剪藏、加订阅、搜索后整理成新笔记否(改 AIPanel 与 tools 的接线)
P1侧边栏信息架构改成 Agent / Inbox / 知识库 / 订阅否(前端结构)
P2剪藏、RSS 新文章、临时笔记统一进 Inbox先不做底层合并
P3RSS 每篇总结是(给 feed_entries 加字段)

两个关键取舍:

  • Inbox 第一版做「虚拟 Inbox」:前端把 clips / feed entries / 未整理 notes 混合展示, 每个 item 标来源类型;点「整理到知识库」时生成或更新一条 note。 统一字段(inbox_status、output_note_id、ai_tags、processed_at)等跑通后再补。 理由是直接合成一张大表会牵动 Rust、迁移、搜索、列表、阅读器,成本远大于收益。
  • RSS 总结不要一刷新就全量跑:慢、贵、失败难处理。先做「打开文章时可生成」, 并给每个 AI 任务留状态字段(summary_status / summarized_at)。

判据:用户能体验到的能力先上线,迁移成本后置;能让前端解决的就不要先动表结构。

二、Agent 是侧边栏主入口,不是独立窗口

用户问「是否要单独一个 agent 窗口」,收敛结论是不要。

  • 独立 Agent 窗口很容易变成「又一个 ChatGPT 窗口」,和知识库本体割裂, 用户会困惑:我到底是在用知识库,还是在和一个机器人聊天。
  • 正确形态是贴着用户正在看的内容工作:
    • 侧边栏 Agent = 工作台 / 起点(主区域首页 + 大输入框 + 当前可执行动作 + 最近的 Inbox / 待处理)
    • 右侧 AI Panel = 当前内容的上下文助手(看文章时总结/整理/解释名词,写笔记时润色/扩写/找关联)
  • 整体结构:左侧选工作区,中间放知识内容与任务,右侧浮层是上下文 AI,Agent 作为第一入口而不是独立 app。

判据同族:新增一个独立入口,必须回答「去掉它用户会损失什么能力」(见 容器要有超出文件夹的意义才值得独立入口)。

三、AI 调用要抽成可替换的执行层

什么时候才真的需要独立 AI server —— 不是「AI 更耗性能」,而是这些需求出现时:

  1. 用户关闭 App 后仍要自动刷新 RSS、自动总结、发日报
  2. 不想让用户自己填 API key,由你统一提供 AI 能力
  3. 邮件推送、通知推送、定时日报
  4. 多设备同步
  5. 统一的成本控制、限流、缓存、失败重试
  6. 更复杂的 Agent 任务队列(每天处理大量订阅源)
  7. 账号体系、会员、用量统计

当前该怎么做:本地跑 AI,但按「未来可替换为 server」的方式写 ——

ai/summarizeFeedEntry()
ai/generateNote()
ai/classifyContent()

现在内部调本地 API,将来只把实现换成 call('/api/ai/summarize-feed-entry'), UI 和业务流程不用大改。要点:不要把 OpenAI 调用散落在 React 组件里, prompt 与 JSON schema 独立成模块,总结结果落 SQLite 并给每个 AI 任务留状态字段。

分层边界:

层职责
UI 组件展示、按钮、状态
业务服务层summarizeEntry / createNoteFromEntry / addSubscription
Tauri/RustSQLite、RSS fetch、本地文件、系统能力
AI Client模型调用、prompt、JSON schema、错误处理

四、云端加工厂 + 本地主仓库

用户提出的方案:本地把订阅源列表给云端 → 云端抓 RSS → 云端 AI 总结 → 结果回本地 → 本地保存。

这比「纯本地抓 RSS」更适合多端:

  • 移动端后台任务限制多,本地 App 不一定能稳定定时抓;云端不受设备限制
  • 多端体验一致,不会出现某台设备有新文章、另一台没有
  • AI pipeline 更自然:云端抓到就总结,客户端只拿「标题 + 正文/摘要 + 总结 + 标签 + 名词解释」
  • 日报天然适合云端生成

代价是隐私边界改变:云端至少会接触用户订阅了哪些源、文章标题/链接、正文或摘要、总结结果。 产品定义要跟着改成:

本地保存知识库主数据,云端负责订阅抓取和 AI 加工,可短期缓存处理结果。

要配的产品承诺:云端只存订阅任务与近期处理结果、本地才是长期知识库、用户可清除云端缓存、 可选「本地抓取 / 云端抓取」。云端的角色是待同步结果池,不是永久知识库。

结果怎么回本地:

方式特点
本地主动拉取(App 打开时问「有没有新处理好的」→ 下载 → 写入 → 回执)第一版选它:最简单稳定;代价是不打开 App 本地就没有最新数据
云端推送(push / websocket)体验更好但复杂,Mac/Win/iOS/Android 推送体系各不相同

五、多端共享的是服务,不是 UI

目标若是 Mac / Windows / iOS / Android / iPad 的成熟产品,架构必须改: 数据不能只存在某一台电脑,RSS 也不能依赖用户打开某个设备才刷新。

客户端(只负责展示、编辑、缓存)
  → 统一服务器
  → 账号 / 数据同步 / RSS 抓取 / AI 总结 / 日报 / 知识库数据
  • 4G 内存服务器够第一版(只要不在服务器跑本地大模型);真正的成本是 AI API 费用、并发与数据库设计
  • 客户端选型:Mac/Windows 继续 Tauri,Web 保留一套,移动端可评估 Tauri mobile,但更稳的路线通常是 RN/Expo
  • 原 Tauri 代码不废:本地 SQLite 从「唯一数据库」降级为「缓存」,RSS 抓取迁服务器,前端 AI 调用改成请求自己的服务器

判据:不要追求「一份 UI 跑所有端」,要追求「一套后端数据和 AI 能力,多端共用同一套服务」。

六、云端版先做骨架,Agent 面板先留位

从本地 Tauri App 迈向云端产品时,第一步不是「把 Agent 接上」,而是先把云端骨架搭出来:

  • 新开独立云端项目(如 mewmo-cloud),不要塞进本地 Tauri App —— 它是新云端产品,不是「本地知识库 2.0」
  • 技术选型偏云端能力:Next.js + TypeScript + Tailwind,后续 Postgres、cron/队列
  • 首页只表达三件事:这是什么产品、它能做什么、怎么开始;主按钮进静态 demo dashboard
  • 右侧 Agent 面板先留空占位(「将在这里读取当前页面上下文并对话」),不接模型、不接数据库、不接登录

理由:用户此时还不熟 Web Agent 的交互与技术架构,先验证「云端 mewmo 应该长什么样」的页面结构是否成立。Agent 后续分三层接:前端聊天 UI → 网站后端 API(读云端数据再调模型)→ Agent 工具层(search_notes / read_note / write_note / list_clips)。编排层选择见 Agent编排层薄自建优先于重型框架。

完整蓝图(Web / Mac / iOS / iPad / 扩展 / Agent / Admin / 同步 / 支付)要保留全量,但按阶段落地:见 完整蓝图不等于同阶段交付、数据权威模型要先于同步与迁移确定。

七、侧边栏是「当前内容的 AI 面板」,拆成总结与 agent 两个 tab

用户提出的结构(替代「侧边栏就是 chat」):

Article Intelligence
├─ Summary tab:后台 worker 产出的、已落库的 AI 结果(摘要 / 要点 / 标签 / 相关内容)
└─ Agent tab:围绕当前文章继续交互的会话

关键判断:

  • 总结不是聊天记录,是文章的结构化 AI 元数据:打开任何一篇文章都应立刻读到已经算好的结果。
  • 两个 tab 不等于两个 agent:总结是后台 worker 产物,agent 是前台交互能力;共享同一套 AI 能力,但生命周期不同。
  • 链路:后台:文章入库 → 自动总结 / 打标签 → 存 DB;前台:打开文章 → Summary 直读 DB → Agent 基于 Summary 继续行动。
  • 首版范围收敛:总结 tab 只对 clip / feed_entry 生效,不含 notes。

判据:「已算好的结果」和「继续交互」是两种不同生命周期的东西,要在 UI 上分开,而不是塞进同一条聊天流。

八、手机看电脑 Agent:bridge-owned app-server 是唯一真源

目标从「手机发起自己的 Codex turn」收敛到「手机实时同步电脑端 Codex」。判断:官方 remote-control 会被「第三方 key 不能用」卡住,所以另起独立 app-server 是对的;但独立 app-server 要能实时同步,前提是它成为唯一真源:

电脑端输入/界面 ─┐
                ├─→ 同一个 owned app-server ─→ 事件广播给所有客户端
手机端输入/界面 ─┘

路线:bridge 启动长期存活的 codex app-server(daemon 级);所有线程以 app-server 的 threadId 为主键;电脑端入口也改走 bridge(本地 Web UI / menubar companion / CLI wrapper);bridge 统一发 turn/start 并监听 notification 广播给所有客户端。

硬边界:若电脑端仍用官方 Desktop 且不连这个 owned app-server,就看不到它的实时流。 这一步(电脑端入口改走 bridge)是整套方案里最关键、也最容易漏的一环。

另一个反模式是「懒加载」:点进会话才 read_session_history、只在「手机发起的 turn」才推 stream_chunk,会让电脑端独立生成时手机既不更新窗口也不触发通知——这与「实时镜像」目标直接冲突。

九、apps 是「可独立运行或发布的产品单元」,不是「服务器进程」

判据:app 能独立启动 / 部署 / 持续运行;package 是被别人调用的共享能力,本身不能启动。

AI 能力放在 packages/ai 而不是 apps/ai,是有意分层:

apps/web ──────┐
               ├── packages/ai ──→ OpenAI / Anthropic / 自定义模型
apps/worker ───┘

packages/ai 只负责选 provider、拼请求、加载 prompt、清洗输入、解析返回, 暴露 summarizeContent() / generateAgentReply();它没有端口、没有页面、没有常驻进程。 即时对话由 Web 调用(用户要立刻拿到回复),异步摘要由 Worker 调用(可排队可重试)。

只有出现这些需求时才值得拆出独立 apps/ai-service:AI 请求量大需要独立扩容、自部署模型/GPU、 Web 与 Apple 与扩展之外还有多个系统要统一调用、需要集中限流/审计/计费/模型路由、AI 服务改用 Python。 现在拆只会多一次网络请求、一套服务鉴权、一份部署费用和一个故障点。

同理,apps 里也有跑在用户设备上的:apps/apple、apps/extension 不是服务器进程。

十、代管平台与自有服务器的边界

Vercel(请求驱动)、Railway(常驻进程)、Neon(托管 Postgres)、Upstash(托管 Redis) 本质都是替你提供服务器,只是把机器管理隐藏起来。

判据:需要 24 小时常驻的进程不能用 serverless 平台替代。 Web 的 Node runtime 里那个 scheduler 只能算兜底 —— 它不是队列消费者、 不能消费剪藏抓取与 AI 摘要队列,Vercel 的实例也不是可靠的 24 小时进程; 生产环境缺常驻 Worker 时,任务会进 Redis 但没人消费,表现为「已保存但一直抓取中」。

自有服务器可以完全替代 Railway:同一台机器上分别跑 worker 与 agent 两个容器, 不需要公开域名,只要能主动访问 Neon / Upstash / Vercel / AI API。 代价是自己承担进程守护、崩溃重启、安全更新与防火墙、日志监控与磁盘、部署与回滚、密钥管理 —— Railway 卖的不是特殊技术,就是这些运维工作。

部署上的一条硬经验:构建镜像与运行容器要分离。 monorepo 构建(pnpm install + 生成 Prisma Client)瞬时吃 CPU/内存/磁盘, 在 4GB 无 swap、还跑着别的项目的服务器上本机构建,是整条部署链路里风险最高的一步。 因此:GitHub Actions 构建镜像 → 推 GHCR → 服务器只拉取运行; 好处是构建失败不影响线上、环境一致、按 commit 回滚、服务器不留构建缓存。

十一、AI 能力要分两类:产品 + AI 与 Agent

两类需求的架构归属不同:

  • 产品 + AI:确定性、短时、单步能力(文章总结、标题生成、自动打标)→ 仍由 Web API / Worker 调用 packages/ai,本质是产品能力。
  • AI Agent:目标导向、可调工具、多步执行、可暂停确认、失败恢复 → 独立部署的 agent-server,前端只负责发起任务、展示过程和处理确认。

packages/ai 不必删,但降级为「模型适配 + 通用 AI 原语」;Agent 的编排、状态与工具不能再放进 packages。运行状态真相源留在自己的 PostgreSQL(runs / steps / approvals / events),可观测平台只做 trace 与成本分析(见 观测平台不能当运行状态真相源)。

架构决定里最影响后续的一个问题是:首版 Agent 是否支持跨分钟甚至跨小时暂停后恢复。若已经独立建 agent-server,最好一开始就把持久化运行状态设计正确。

十二、Cron 是同一个 app 的第二个入口,不是新 app

RSS 定时更新没新增 apps/cron,而是在 apps/worker 里加第二个启动入口(cron:feeds,跑完即退出),部署上复用同一个镜像:常驻容器只消费 AI 队列,feed-cron 服务每分钟启动一次。

判据:两者共享数据库连接、抓取逻辑、AI package、环境变量、Dockerfile 与部署流程 —— 新建 app 只会重复 package、环境配置、Dockerfile 和部署流程。 但要在命名上区分清楚:「每分钟启动一次、做完就退出」与「24 小时常驻监听队列」是两种运行形态,即使放在同一个 app 里也不该混为一个概念。

相关