没有人推荐的 Stack
I’m happy to translate the article for you, but it looks like the text you’d like translated wasn’t included in your message. Could you please paste the content you want translated (excluding the source line you already provided)? Once I have the text, I’ll translate it into Simplified Chinese while preserving the formatting and code blocks.
后端:FastAPI
我来自 JavaScript 和 TypeScript——多年在前端使用 React,后端使用 Express 和 Fastify。当我决定这个项目使用 Python(因为 AI/ML 生态系统就在这里)时,我需要一种不那么陌生的框架。
FastAPI 立刻让我有了感觉。async/await 模型、基于装饰器的路由以及真正有用的类型提示,让它像是在 Python 中写 Fastify。熟悉感并不是全部原因,但如果说它不是因素,我就是在撒谎。
技术层面的理由同样成立。系统需要处理来自 n8n 的并发 webhook 回调、React 仪表盘的实时轮询,以及对 PostgreSQL 的持久 asyncpg 连接。所有这些都是异步 I/O,而 FastAPI 正是围绕这一模式构建的。Django 现在也支持 async,但仍然感觉像是事后添加的,而不是从设计之初就考虑的。
我还特意避免使用 ORM。系统中的每个查询都是通过 asyncpg 手写的 SQL。面对跨九个领域、超过 95 张表的数据库,我想要准确知道到底执行了什么——没有魔法、没有 N+1 惊喜、没有我没看过的迁移框架生成的 SQL。
为了不使用 Django 所付出的代价
- 没有免费管理后台——我从零开始构建了一个 React 仪表盘,花了好几周时间。
- 没有内置迁移系统——我通过原始 SQL 文件经 SSH 传入 Docker 来管理模式变更,这已经让我吃过不少亏(SSH → Docker →
psql的 shell 引号会把复杂语句弄乱)。 - 插件生态相对薄弱,当我需要 Django 已经拥有二十年的功能时,往往找不到合适的插件。
如果你在构建一个带有用户账户、管理后台和表单的 Web 应用,直接使用 Django。
FastAPI 适用于后端作为 API 层在各服务之间进行协调的场景,这正是我的需求。
数据库:PostgreSQL
这并不是一个困难的决定。我的数据高度关联:交易链接到银行账户,电子邮件分类引用消息,知识事实在多个来源之间得到强化,调度任务引用代理,而代理又引用模型。若在 MongoDB 中实现,则需要对所有数据进行去规范化,将文档嵌套在文档中,并手动处理一致性。
PostgreSQL 为我提供了超出单纯关系存储的功能,这些功能被证明至关重要。
LISTEN/NOTIFY
取代了通常需要消息队列的功能。当电子邮件被分类时,触发器会发送通知。大脑服务通过 asyncpg 在毫秒级捕获并作出响应。无需 Kafka、RabbitMQ——仅使用 Postgres 多年来内置的特性:
CREATE OR REPLACE FUNCTION notify_email_classified()
RETURNS TRIGGER AS $$
BEGIN
PERFORM pg_notify(
'email_classified',
json_build_object(
'id', NEW.id,
'category', NEW.category,
'urgency', NEW.urgency
)::text
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;在我的规模(大约每小时 50‑100 次事件)下,这已经足够。添加 Kafka 意味着再多一个容器、再多一套配置要维护,并且会多一个凌晨 3 点可能出问题的点。等真正需要时再加入。
CHECK 约束
这是整个项目中最好的决定之一。数据库强制 AI 只能输出哪些类别:
category VARCHAR(50) CHECK (category IN (
'billing', 'shipping', 'subscription',
'employment', 'legal', 'marketing',
'personal', 'automated' ...
));大型语言模型有时会忽视你的指令。一次提取器生成了一个不在允许列表中的类别,导致 INSERT 失败。这正是应该的:显著的错误比悄悄用无效类别污染数据要好得多。
窗口函数与区间查询
我使用它们来实现速率限制、冷却时间和断路器——这些通常会使用 Redis 来完成。这样可以少一个容器。
MongoDB 的优势所在: 真正的文档形数据,具有可变的模式(CMS 内容、异构用户档案、具有不同负载的事件日志)。我的数据并不属于这些情况。
工作流引擎:n8n
这是我最为矛盾的一项决策。
n8n 是一个自托管的可视化工作流编辑器。你可以把触发器、HTTP 请求、数据库查询以及代码节点串联起来。对于我的邮件流水线来说,能够以图形方式查看流程确实非常有价值。当出现故障时,我可以准确看到是哪一步出错以及它当时拥有的数据。
自托管的特性立刻排除了 Zapier 和 Make。我的工作流会处理邮件正文和财务数据,因而不能经过第三方。n8n 的代码节点允许我直接在工作流步骤中写入 JavaScript,这正是我构建 Ollama 调用所需的复杂 JSON 负载的方式。
痛点
- 生产事故 – 调度的工作流可能会重叠执行,因为 n8n 默认不阻止并发运行。我不得不在数据库层面构建一个守卫,检查前一次运行是否仍在进行中。
- 静默截断 – API 会在没有任何错误提示的情况下静默截断过长的 SQL 查询。
- 沙箱怪癖 – 代码节点在沙箱化的 V8 隔离环境中运行,
process.env不存在(需要使用$env)。 - 脆弱的表达式 – 在 HTTP Request 表达式中构建 JSON 很容易出错;复杂的负载应始终通过 Code 节点来处理。
尽管有这些缺点,可视化的特性以及嵌入自定义 JavaScript 的能力仍让 n8n 继续留在我的技术栈中——暂时如此。如果痛点超过收益,我会考虑换成更具编程性的方案(例如 Temporal 或自定义编排器)。
Source: …
首先
这些单独来看都不是致命问题。但总体而言,n8n 要求的防御式编程水平超出了我对工作流工具的预期。每个涉及 LLM 调用的工作流现在都要进行堆叠检查,每个 SQL 查询在部署后都要进行验证,我也学会了在 Code 节点中构建负载,而不是在表达式字段里。
如果你的工作流大多是代码,视觉化的收益很小,那就用调度器写 Python 脚本吧。n8n 的真正优势在于可视化编辑器。如果你不需要它,只是在无意义地增加复杂度。
本地 LLM 服务:Ollama
Ollama 之所以胜出,仅因为它的简洁性。安装后运行
ollama pull qwen3:14b即可在 localhost:11434 上得到模型服务 API。无需 CUDA 配置、无需 Python 环境管理、也不必处理 Docker GPU 直通的麻烦。
切换模型只需要在请求负载中改动一个字符串。API 在所有模型上保持一致(/api/chat、/api/generate、/api/embed),这让我的系统路由逻辑变得非常简单。
我放弃的东西:
vLLM 提供张量并行、连续批处理和量化控制,而这些都被 Ollama 的抽象层隐藏了。对于需要为大量并发用户提供服务的平台,vLLM 才是正确的选择。对于在 Mac mini 上一次运行一个模型的单用户系统,Ollama 的默认配置已经足够,且搭建时间只相差几个小时。
通信:Mattermost(暂时)
我需要对每一次重要操作进行人工在环(HITL)审批。系统会在聊天中发送带有上下文的消息,并附带 Approve/Reject 按钮。我点击后触发 webhook,工作流继续执行。
我选择 Mattermost 是因为它开源、可自托管,并且支持交互式消息附件。这就是全部评估依据——它可以在 Docker 中运行并带有按钮,并非出于战略考虑。
它能工作,但我计划迁移到 Rocket.Chat。我希望以后能用语音与助手交互,而 Mattermost 的音频通话功能有限。Rocket.Chat 还有更成熟的移动端应用,这一点很重要,因为 HITL 的核心是即使我不在桌前也能批准操作。
网络:Tailscale
三台机器需要相互通信。Tailscale 为每台机器分配一个稳定的 IP,且不受物理网络影响。无需端口转发、无需动态 DNS、也不必在路由器上打开端口。整个设置大约用了 10 分钟。
我本可以手动配置 WireGuard 来获得相同的加密和性能,但那样就得自己管理密钥轮换、端点配置和 NAT 穿透。对于三节点网络来说,Tailscale 的便利性值得付出。
有人常问: 为什么不使用 Cloudflare Tunnels?因为它们解决的是另一类问题。Cloudflare Tunnels 通过 Cloudflare 网络将服务暴露到互联网,而我的服务只需要在内部私密通信——这是一个网状 VPN,而不是反向代理。
搜索:Elasticsearch(后期添加)
我最初并没有使用 Elasticsearch,而是从 ChromaDB 起步,因为它更轻量、可以在 Docker 中运行、提供简洁的 Python API,且足以满足基本的向量检索需求。
当知识库规模扩大时问题出现了。我的事实、实体和模式数量达到了数千条,需要在同一次查询中既按语义又按精确关键词搜索。ChromaDB 只处理向量,PostgreSQL 只处理关键词。分别在两个系统中搜索再合并结果既脆弱又慢。
Elasticsearch 能在单个查询中同时完成两者(BM25 用于精确关键词匹配,k‑NN 用于向量相似度),这正是我迁移的原因。代价是机器上多占用约 4 GB 堆内存——在本已紧张的环境中是个挑战。对于较小的数据集或纯向量检索,ChromaDB 或 pgvector 是更轻量的选择。
我会在专门的文章中详细介绍迁移过程。
我会做的不同之处
部署。 目前我通过 SSH 登录到 Windows 主机并运行 PowerShell 命令进行部署。没有 CI/CD,也没有 GitHub Actions。之所以能工作,是因为我只有一个开发者,但如果还有其他人需要贡献,这将是最先出现问题的环节。
如果重新开始,我会从第一天就使用 Linux,并搭建一个基础的 GitHub Actions 流水线:推送到 main,构建容器,部署。不是 Kubernetes,也不是 Terraform——只是在自动化我现在手动运行的 90 秒脚本。
这是 “One Developer, 22 Containers” 的第 2 部分。接下来将讨论从 ChromaDB 迁移到 Elasticsearch,以及混合搜索如何改变我的 AI 系统检索信息的方式。
如果你也做过类似的选择——或是不同的决定——欢迎在评论区分享。我的 GitHub 主页在 GitHub。