[控场AI]
· 10 分钟阅读· 5,497 字

本地优先Agent平台架构:让供应商永远无法插手

本地优先Agent平台架构:让供应商永远无法插手

TheYgent通过双平面架构、逻辑模型ID和持久化运行时,构建了一个数据边界结构性清晰、可自托管且可验证隐私的本地优先AI Agent平台。

TheYgent是一个本地优先的AI Agent平台,其架构核心是将系统分为推理平面与控制平面:推理平面运行模型并驻留在用户指定的位置,控制平面编排Agent且从不导入任何引擎,仅通过OpenAI兼容HTTP API访问推理能力。这一结构性边界确保了数据不会流经用户未运行的服务。平台使用逻辑模型ID将Agent与具体引擎解耦,迁移或混合使用本地与托管模型只需更改一次注册。Agent在底层是类型化文档,携带内容哈希以区分布局变化与行为变化。持久化运行时基于DBOS库,利用现有Postgres记录工作流状态,实现崩溃后从最后完成步骤恢复。TheYgent还公开所有网络连接清单,无分析或遥测,安装后完全离线运行,并采用大声失败与刻意契约的工程原则。

构建AI Agent平台时,大多数可视化工具都有一条共同的脚注:支持本地模型——请自备服务器。这条脚注背后隐藏着大量工作:选引擎、下载正确的量化版本、根据机器内存调整上下文大小、保持服务器运行、再把URL接入构建器。更关键的是,构建器本身往往默认将使用数据回传。

TheYgent从相反的方向出发。推理引擎是产品的一部分,产品永远不会成为你和模型之间那个你未曾选择的中间人。这篇文章深入其架构设计:各个组件是什么、边界在哪里、以及为什么这样划分。

本地优先Agent平台架构概览

双平面架构:一条清晰的接缝

TheYgent最重要的边界将系统分为两个平面。

推理平面运行模型。它承载所有重型组件——引擎进程、模型权重、模型注册表、API凭证——并且运行在你指定的任何位置:同一台笔记本、网络中的GPU服务器,或两者兼有。

控制平面编排Agent:图运行时、Agent注册表与版本、会话、触发器、MCP服务器、检索源、密钥和运行轨迹。

一条规则保持这种分离的纯粹性:控制平面永远不导入引擎。 其中没有任何MLX、vLLM或llama.cpp的绑定。它仅通过推理平面的OpenAI兼容HTTP API、经由一个共享客户端来访问推理能力。这带来两个结果。

第一,负载边界是结构性的,而非策略性的。当你直接与模型对话时,浏览器将提示词、音频和图像直接发送到推理平面。当Agent运行时,控制平面——同样由你托管——按逻辑ID将每次模型调用发送到推理平面。没有任何数据经过你不运行的服务,当数据跨越网络到达托管API时,那是因为你注册了该API。

第二,部署保持简单。推理是唯一需要独立服务的组件:它占用GPU、持有数GB权重、本身就是一个HTTP服务器。其余所有组件是一个FastAPI应用,内部模块化。同样的四种进程类型——界面、控制平面、推理平面和可选的持久化Worker——可以通过make up在裸机上运行,也可以在Docker Compose和Kubernetes上运行。只有接线方式不同。

控制平面与推理平面的类比:这一划分类似于Kubernetes中的控制平面(API Server、调度器、控制器)与工作节点(kubelet、容器运行时)的分离,也类似数据库系统中的查询规划器与存储引擎。控制平面负责编排、状态管理和策略决策,推理平面负责实际的重计算。TheYgent的关键约束——“控制平面永远不导入引擎”——等价于一种编译期强制的边界:如果控制平面无法链接到MLX、vLLM或llama.cpp的库,就不可能意外调用它们的内存或API。这在架构层面杜绝了“策略性边界”的常见漏洞——即口头约定不混用、但代码里仍可能调用。

OpenAI兼容HTTP API的含义:这指的是推理平面对外暴露的接口遵循OpenAI的/v1/chat/completions、/v1/embeddings等REST端点与JSON请求响应格式。任何兼容此协议的服务——无论是本地vLLM、远程OpenAI、Anthropic的兼容层,还是你自建的模型服务器——都可以作为推理后端接入。这带来的互操作性意味着控制平面的客户端代码只需实现一次,不因引擎变化而改动。

逻辑模型ID:Agent从不指定引擎

Agent、聊天或基准测试中的每个模型引用都是一个逻辑ID,例如triage-fast。引擎名称从不出现在数据平面上;如果图试图以mlx或llamacpp作为模型名来调用,运行会提前被拒绝并返回engine_name_not_allowed。

将ID绑定到实际提供服务的引擎,只在推理平面的管理API上进行一次:

curl -X PUT http://localhost:8081/admin/models/triage-fast \
  -H 'Content-Type: application/json' \
  -d '{
    "binding": "llamacpp",
    "source": "local-path",
    "model": "/path/to/model-Q4_K_M.gguf",
    "modality": "chat"
  }'

Binding只有四个值:mlx、vllm、llamacpp或openai-compatible。前三个是TheYgent管理生命周期的引擎;世界上其他一切——托管API、你已有的模型服务器——都是openai-compatible加一个URL。另外两个维度与之并列且不合并:source表示权重来源(hf、local-path、url),modality表示服务器功能(chat、vision、embeddings、speech-to-text、text-to-speech、image generation)。

这种设计的好处是:将模型从Mac上的MLX迁移到Linux上的llama.cpp——或从本地模型迁移到托管API——只需更改一次注册。不需要编辑任何Agent,也不需要重新发布。由于引擎选择是按节点进行的,一个图可以同时运行本地模型和托管模型:小型本地模型对所有请求进行分诊,路由器只将困难请求发送到API。

引擎管理器,而非模型服务器

推理平面不要求你启动服务器。它自己启动。

  • 惰性启动。 引擎在首次调用其逻辑ID时在空闲本地端口上生成,请求等待其健康检查通过。这就是为什么对模型的首次调用总是较慢。
  • 驻留上限。 同时只保持少量引擎加载——默认两个。需要新引擎时,最近最少使用、优先级最低的引擎被驱逐。有请求在途的引擎永远不会被驱逐;如果无法释放任何引擎,调用会以no_capacity失败,而不是反复颠簸。
  • 空闲回收。 空闲十五分钟的引擎被拆除;将模型标记为keep warm可豁免。
  • 放弃的调用会被取消。 如果客户端在非流式响应到达前断开连接,对引擎的请求会立即关闭——这对图像生成尤为重要,因为一次渲染可能需要数分钟。
  • 每个引擎和模态一个启动器。 Chat、vision、embeddings、transcription、speech和images各自映射到正确的服务器程序,没有对应服务器的组合会被明确拒绝并返回清晰错误,而不是被错误地服务。

模型通过应用内的Hugging Face目录获取,该目录在你下载前就显示每种量化版本是否适合机器内存。安装的权重注册为本地文件,因此加载它们永远不会访问网络。

LRU驱逐与驻留上限的权衡:同时保持少量引擎加载(默认两个),是在内存占用与冷启动延迟之间取的折衷。GPU显存和系统内存是有限资源,每个加载的引擎都会占用数GB权重。LRU(最近最少使用)策略驱逐最久未用的引擎,但通过“有请求在途的引擎永不驱逐”和优先级标记避免打断正在进行的推理。无法释放时的no_capacity错误,是一种显式的背压信号——拒绝新请求而不是让所有请求都变慢。

惰性启动的成本:首次调用某逻辑ID时才会生成引擎进程,请求需等待健康检查通过。这解释了为什么首次调用总是较慢——包含了进程启动、权重从磁盘加载到内存/显存的时间,可能从数秒到数十秒。若需避免此延迟,可将模型标记为keep warm,使其免于空闲回收。

Agent即文档:类型化中间表示

在画布上拖拽节点、绘制连线。但在底层,Agent就是一个类型化文档——一个在Pydantic中定义一次的中间表示,编辑器使用的TypeScript类型从中生成,并有CI检查在两者偏离时失败。

文档携带一个内容哈希,在服务端基于规范化JSON计算:填充默认值、排序键、剥离布局块。在画布上移动节点永远不会改变Agent的身份;改变其行为则总是会改变。发布生成一个不可变、内容寻址的版本,你可以通过API调用、放在触发器后面,或回滚到。

密钥从不存放在文档中。连接携带对加密存储的引用,因此轮换API密钥时引用保持不变——Agent的哈希也不变。

持久化:在关键场景中可靠运行

Agent运行在两种运行时上。交互式运行时是进程内解释器,实时流式输出token;如果进程在运行中途死亡,运行会被诚实地标记为失败。当有人在观看时,这没问题。但无人值守的工作——凌晨3点的webhook、每周计划、等待数天的审批——需要更多:运行能在崩溃后存活,从最后完成的步骤恢复而不重做工作。

持久化运行时提供了这一点。它构建在DBOS之上——一个嵌入式Python库,将工作流状态记录到控制平面已有的Postgres中——无需部署新的有状态服务。几条规则保证其正确性:

  • 一个注册的工作流。 恢复时按名称查找工作流,因此不是为每个Agent生成一个,而是恰好一个,它加载固定的、不可变的Agent文档并遍历它。
  • 诚实的保证。 已完成的步骤是精确一次:恢复时从日志重放而不重新执行,因此POST不会被重发。崩溃时正在执行的步骤是至少一次,所以HTTP工具支持幂等键。
  • 编排不做I/O。 路由器、转换器、规则护栏和循环控制在工作流体内运行,从日志结果中重新推导出相同结果——这种确定性是恢复工作的基础。所有I/O都在日志步骤中进行。
  • Token是副作用。 流在到达时发送给订阅者;只有组装好的答案被记录,因此重放的步骤不会重新流式输出。

人工审批、子图、循环和扇出映射是仅持久化的节点类型。人工审批节点可以等待数天,重启后仍能继续。

DBOS的工作流状态记录机制:DBOS是一个嵌入式Python库,它将工作流的每个步骤执行结果(成功返回值或异常)持久化到关系型数据库中。当工作流在崩溃后重新执行时,DBOS会按步骤顺序查询日志:若某步骤已有记录,则直接返回日志中的结果而不重新执行该步骤的代码。这实现了“已完成步骤精确一次”——POST请求不会因重放而重复发送。代价是工作流体内的代码必须具有确定性:给定相同的日志输入,必须推导出相同的控制流,因此路由、循环、条件判断等不能依赖随机数、当前时间或外部I/O;所有I/O必须封装为日志步骤。

可观测性:无需额外基础设施

每次运行都会在你自己的Postgres中生成span和每个节点的输入输出,界面将其绘制为可缩放的瀑布图:哪个节点运行了、多长时间、在哪个模型上、用了多少token。捕获什么内容——以及保留多久——按Agent设置,配有保留和脱敏规则。导出到OpenTelemetry collector是第二个可选的sink,永远不是必需的。

可验证的隐私承诺

"你的数据保持私密"说起来容易,验证起来难。因此TheYgent不是承诺,而是发布了它所做的每一个连接的完整列表以及何时进行:浏览或安装模型时的Hugging Face、打开hub浏览器时的MCP注册表,以及你自己配置的任何内容——托管API、远程MCP服务器、你爬取的网站。

列表包含了一个本可以省略的请求:启动时,推理平面的模型客户端库从GitHub获取公共模型价格列表。它不携带你的任何数据,离线时回退到捆绑副本——但既然是一个请求,就列在清单上。没有使用分析、崩溃报告、更新检查或许可证服务器,安装后TheYgent完全离线运行。

承诺是精确的:你拥有每一跳。 你可以随时注册托管API。TheYgent只是拒绝成为你未曾选择的中间人。

可验证隐私清单的实际意义:通常的“隐私政策”是法律文本,用户无法独立验证,且可能随版本更新而变更。TheYgent发布的网络连接清单则是一种技术声明:列出代码中所有可能发起外连的目标及触发条件。这使具备技术能力的用户可以审计或通过防火墙规则验证。特别地,启动时获取模型价格的GitHub请求虽然不含用户数据,但因其存在而被列出,体现了“清单完整而非只列敏感项”的原则。离线回退到捆绑副本的设计,确保了即使该请求被拦截或网络不通,核心功能不受影响。

大声失败,刻意契约

代码库中有两个习惯贯穿始终。失败是大声的:未知的输入引用、无效的图、引擎名称出现在逻辑ID的位置、无法填充的端口——每个都快速失败并返回命名错误码,从不静默回退。契约是刻意的:数据平面形状、Agent文档信封和四值binding枚举保持冻结,直到有意识地做出更改、命名并测试——即使是增量更改。

如何开始

TheYgent在Sustainable Use License下提供源代码,支持在macOS和Linux上自托管——裸机、Docker Compose或Kubernetes。

  • 代码: github.com/al2m4n/TheYgent
  • 文档: docs.theygent.ai,包括完整的架构文档供贡献者参考
  • 观看运行: 视频演示,从安装到发布Agent
  • 从可用Agent开始: 随附的十二个Agent模板
#本地优先Agent平台#TheYgent架构#AI Agent自托管#推理引擎管理#逻辑模型ID#DBOS持久化#隐私保护AI
分享:

相关推荐