气隙 AI:用 Ollama 完全离线运行 LLM
零出站 AI 的实用架构:以 Ollama 作为本地 LLM、LanceDB 存储向量、对每个 prompt 做 DLP 脱敏,全部运行在一个离线容器中。
为什么气隙是一种部署形态,而不是功能开关
大多数「自托管」AI 产品仍在泄露数据。这里一次模型 API 调用,那里一次 embedding 请求,一个遥测 ping,一次回传总部的许可证检查--每一项单独看都说得过去,但合在一起就意味着你的内部文档、prompt 和回答会穿越一条你无法控制的网络边界。对于国防承包商、医院、银行以及任何有数据驻留要求的组织,这是直接出局的条件。要求不是「大致离线」,而是一个拔掉网线也能运作的 AI 系统。
Simmetric Chat 正是围绕这一要求构建的。气隙运行不是需要解锁的模式,也不是要购买的插件:社区平台的默认配置本就是离线配置--本地 LLM、本地 embedding、基于文件的本地向量存储,以及一条永远不需要互联网的文档管线。本文逐层拆解零出站连接如何成立,以及关闭边界后你放弃什么--很少。
本地技术栈:每一层都有离线默认值
在典型的 SaaS 架构中,AI 聊天有四个外部依赖:语言模型、embedding 模型、向量存储和文档管线。Simmetric Chat 的默认值把这四项全部替换为本地组件:
- LLM 运行在 Ollama 中。 平台的默认 provider 是本地网络上的 Ollama,默认模型(
gemma4:latest)在安装阶段就已拉取,因此聊天在运行时下载任何模型之前就能工作。Ollama 在你自己的网络上提供 OpenAI 兼容的 HTTP 接口,provider 配置用一个 URL 指向它--而不是云端点。 - Embedding 在本地运行。 默认 embedding provider 是本地 transformers 模型(384 维,在 collector 进程内运行),而不是托管 embedding API。本地 embedding 在结构上就与气隙兼容,而 embedding 不匹配防护意味着之后在本地与远程 provider 之间切换是安全的--检索会降级为关键词搜索,直到重建索引完成,而不是返回来自混合空间的分数。
- 向量存放在 LanceDB 中。 默认向量存储基于文件,保存在部署自己的存储目录中,每个 workspace 一个 collection。没有向量数据库服务器,没有网络跳转。对于规模需要网络索引的团队,Qdrant、pgvector 和 Chroma 仍然可用,但没有任何东西强制使用它们。
- 文档解析在你的网络内完成。 Collector--负责解析、切分并嵌入上传文件的服务--是你部署的一部分。PDF、Word 和 Excel 文件、演示文稿以及 YouTube 转录都在边界内处理,还包括通过服务端视觉模型 OCR 管线处理的扫描文档,该管线使用本地视觉模型。
这份列表中的任何一项都不是特例,也不是事后补上的。每个边界上的策略模式让本地选项只是默认 provider,通过配置选择,与其他任何 provider 无异。
DLP:在任何内容离开之前脱敏--甚至在它移动之前
关闭出站连接并不能关闭所有外泄路径,因为内容也会在你的网络内部移动:prompt 进入平台,回答流向用户,两者都会经过人们会从中复制的聊天界面。Simmetric Chat 的数据防泄漏过滤器双向扫描--入站 prompt 和出站回答,包括逐 token 的流式刷新--并在内容被存储或展示之前对匹配项脱敏。
扫描器识别聊天场景中真正重要的 PII 类别:电子邮件地址、信用卡号、API 密钥和私钥,以及--在 v0.22 中新增--结构化欧洲标识符:意大利 Partita IVA、codice fiscale 和 IBAN,作为内置 pattern 幂等地植入,并以标签锚定,因此普通数字永远不会触发它们。Pattern 配置由管理员管理并基于数据库:启用或禁用任何 pattern,添加最多五十个自定义 pattern,支持实时编译校验和测试预览,并查看按来源过滤的匹配历史面板。
设计意图是让 DLP 成为最后一道防线,而不是第一道。第一道防线是架构:有了本地模型、本地 embedding 和本地向量,敏感内容从一开始就无处可去--不存在第三方端点。DLP 随后覆盖仍然存在的路径--离开平台流向你自己用户的内容,或善意的员工粘贴进 prompt 的内容。它也覆盖 widget 表面,因此你网站上面向访客的对话由同一条过滤器链脱敏,审计轨迹区分聊天流量与 widget 流量。
单容器部署:一切都在一个主机上
关闭边界是一回事;把软件送进边界是另一回事。气隙环境通常无法舒适地运行五服务的 compose 栈--它们运行一个主机、一个镜像,活动部件越少,需要审查和传输的工件就越少。
Simmetric Chat 正是为这种场景提供了单容器 all-in-one 镜像。一个容器,由进程管理器监管,运行一切:PostgreSQL 16(pgvector 扩展在镜像构建时从源码编译,因此离线安装)、API 服务器、文档 collector 和 widget 服务。容器暴露三个端口--API 用 3000,collector 用 3210,widget 用 3211--并对自己的 /api/health 端点做健康检查,该端点报告数据库、collector 和磁盘状态。
把镜像送进去与送进任何其他工件是同样的纪律:传输镜像 tarball(或你审查过的 registry 镜像),加载并运行。镜像中的环境默认值是脚手架占位符--生产环境通过环境配置覆盖 JWT_SECRET、COLLECTOR_SECRET 和加密密钥,如果缺少生产密钥,服务器在启动时会大声失败,因此配置到一半的气隙部署绝不会静默启动。启动之后,软件更新是新镜像版本,而不是包安装--生命周期中任何时刻都不需要互联网访问。
# 传输进边界,然后:
docker load -i simmetric-chat-all-in-one.tar
docker run -d \
-p 3000:3000 -p 3210:3210 -p 3211:3211 \
-v simmetric-storage:/app/storage \
-e JWT_SECRET=$(openssl rand -hex 32) \
-e COLLECTOR_SECRET=$(openssl rand -hex 32) \
simmetric-chat:all-in-one模型权重走同样的路径。Ollama 模型在联网机器上拉取,保存为 blob 后导入边界内--平台随后像对待任何其他本地模型一样对待它们。技术栈中没有任何东西会回传总部去验证模型、许可证或自身:遥测默认禁用,没有外部错误上报依赖,许可证校验是针对内嵌公钥的本地 RS256 签名检查。
离线状态下仍能做什么--以及真正缺什么
气隙部署应当对自己的边界保持诚实。零出站连接下仍能工作的功能:
- 通过 Ollama 与本地 LLM 聊天,以 SSE 逐 token 流式输出
- 文档上传与摄取--解析、切分、本地 embedding、LanceDB 向量
- 带引用的混合检索、元数据过滤和合成管线
- RBAC、workspace、项目、审计轨迹和完整的 admin 界面
- Webhook(投递到你自己的接收端)、Web Push(VAPID 来自你自己的服务器)和 API 密钥
- 可嵌入 widget,运行在你自己的 widget 服务上
真正缺少的:云 LLM provider、托管 embedding、外部网络搜索,以及任何需要出站调用的东西。优雅降级的设计意味着这些集成每一项都只是配置补充,而不是代码改动--但离线默认值就是不带它们发布。无论边界是开是关,平台启动、响应请求和处理文档的方式完全相同。
结果是:一个 AI workspace,其安全姿态不依赖于对供应商数据处理的信任,因为数据路径中根本不存在供应商。所有触碰你文档的东西--模型、embedding、向量索引、解析器--都运行在你拥有的硬件上、你控制的网络中、一个你可以审计的容器镜像里。
