一次 Agent 请求经过 12 个容器,真正跑模型的只有一个
SOIT Team

对 quickstart 最常见的反应是:一个演示要起十二个容器,太重了。这个反应很公道,而诚实的回答不是替这个数字辩护,是沿着一次 agent run 逐个服务走一遍,说清每个容器实际负责什么、删掉它之后消失的是哪一条保证。上一篇回答的是「能砍到几个」,这一篇回答「剩下的为什么在那儿」。
先纠正数字,因为争论双方经常在用两个不同的数。quickstart 那条命令写了十二个服务名,而 compose 文件里定义了十四个。实际启动的是十三个:minio-init 没写在命令里,但 api 的依赖要求它成功执行完毕,所以它会被拉起来,建好桶、关掉匿名访问,然后退出。第十四个是 scheduler,没有任何服务依赖它,因此 quickstart 拓扑里它根本不会启动。「compose 里定义了几个」和「你实际跑起来几个」是两个数,先把它们分开,讨论「重不重」才有意义。
其中三个是一次性任务,在你的第一个请求到达之前就已经退出了:migrate 建表,bootstrap 建管理员与租户,minio-init 准备对象桶。它们消耗的是启动时间,不是常驻开销。另外两个就是被演示的东西本身:api 和 web。剩下的全部,是台账以及围绕台账的那几条边界。
Postgres 是台账,而且不是可有可无的那种。一次 run 被记在五张表里,覆盖 run 本身、它的步骤、它的工具调用、它的产物和它的成本。这是平台对每次执行所做的两个承诺——可观测、可重放——的物理基础。MinIO 是这件事的另一半,存放 run 产生的大对象,库里只留一个 storage key 和一个 SHA-256。这两个也是整个栈里仅有的两个 readiness 硬门槛:任何一个挂掉,平台会直接报告自己未就绪,而不是继续接受它记不下来的请求。
剩下的容器,每一个都只买一条边界。Vault 用 KV v2 存凭据,让密钥既不落进程也不落 dotenv 文件。Redis 缓存权限判定(5 分钟 TTL)、支撑限流器、承载跨实例事件总线。Milvus 提供向量检索,而 etcd 值得单独点名,因为它是一个高频误解的来源:它是 Milvus 自己的元数据存储,不是平台的依赖,跟着 Milvus 一起进退。另有一处不对称值得注意:Milvus 挂掉会让知识检索报错,但不会拉低 readiness。这个设计是有意的,它恰好告诉你运行时认为哪些能力关乎自己的承诺、哪些不。
有一个容器要等到响应已经返回之后才变得有意思。run 里产生的事件,是和业务写入同一个事务一起落库的;真正把它们投递出去的是 outbox dispatcher,带 checkpoint 幂等。把它拿掉,请求过程中什么都不会出错,因为请求本来就已经成功了——事件只是永远停在 pending,所有下游什么都收不到。另外,outbox dispatcher 和 knowledge ingest worker 这两个后台进程,与 api 是同一份代码、换了个入口,把它们当成两套需要单独运维的系统之前,值得先知道这件事。
如果不按名字、而按「它保证了什么」重新分组,常驻部分其实很小:两个是产品本身,两个是台账与产物的物理载体,两个半是向量检索这一个能力,一个是密钥隔离,一个是跨副本广播与缓存,两个是同一份代码拆出来的后台进程。整个拓扑里真正在跑模型的,是其中一个进程。剩下的重量买的是同一样东西:跑完之后这次执行还留得下证据,而且过程中崩了不会留下半个状态。
这个代价值不值得,取决于你要拿它干什么。如果只是想知道一个 agent 能不能把任务跑通,这个栈对你就是过重的,上一篇讲了怎么砍到四个常驻容器。如果这个 agent 要放进一个事后需要有人对账的流程里,那么上面这些容器就是你迟早要自己写一遍的东西。有一条需要挑明:本篇是读代码和读 compose 得到的静态结论,不是一次新的端到端实跑;尤其 scheduler 那一条,依据是拓扑与配置默认值,而不是真的建一个定时任务等它不触发。仓库在 github.com/soit-ai/soit,完整拓扑在 docker/docker-compose.yml,如果你觉得哪一格的取舍判断错了,欢迎直接开 issue。