151 lines
7.4 KiB
Markdown
151 lines
7.4 KiB
Markdown
# 架构设计
|
||
|
||
中文 · [English](architecture.md)
|
||
|
||
## 设计目标
|
||
|
||
K1412 Agent 是一个多用户网页编码 Agent,其 Agent 循环可以独立于账户系统和用户界面进行修改。架构将高成本的实验面控制在较小范围内:研究者应当能够改变上下文、记忆、调度、工具、委派或完成策略,而不必分叉鉴权、聊天记录、Docker 生命周期或整个前端。
|
||
|
||
系统有意只保留一条 Agent 路径。早期曾计划在自定义 Work 循环之外并存一条轻量 Chat 循环,但这会重复产品行为、混淆模型选择,并增加前后端必须保持兼容的代码量,因此已经移除。
|
||
|
||
## 系统上下文
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
U["浏览器用户"] --> P["DNS、TLS 与 Nginx Proxy Manager"]
|
||
P --> W["Open WebUI<br/>鉴权、RBAC、历史记录、界面"]
|
||
W --> R["Agent Runtime<br/>循环、上下文、记忆、模型路由"]
|
||
R --> M["模型提供方<br/>K1412 API 或 DeepSeek"]
|
||
R --> G["Workspace Gateway<br/>身份与执行策略"]
|
||
G -->|Tailscale + SSH Docker| X["每用户工作区容器<br/>home-node-itx"]
|
||
W --> DB["PostgreSQL<br/>用户与对话"]
|
||
R --> DB
|
||
W --> Q["Redis<br/>协调"]
|
||
```
|
||
|
||
只有 Web 对公网开放。Runtime、Gateway、PostgreSQL 和 Redis 位于私有 Compose 网络中。工作区容器运行在另一台物理 Docker 主机上,不接收应用密钥,也不暴露主机端口。
|
||
|
||
## 职责边界
|
||
|
||
| 层 | 负责 | 不应负责 |
|
||
| --- | --- | --- |
|
||
| Open WebUI | 注册、登录、会话、管理员审批、RBAC、对话持久化、渲染 | Agent 调度、模型提供方密钥、Docker 访问 |
|
||
| Agent Runtime | 模型目录、上下文策略、Agent 循环、工具、调度、委派、记忆、计划、运行事件、证据门禁 | 浏览器会话、Docker socket |
|
||
| Workspace Gateway | 签名身份校验、工作区路径策略、每用户锁、Docker 生命周期、文件交付 | 模型调用、公开鉴权界面 |
|
||
| 工作区容器 | 用户代码、依赖、生成文件、命令执行 | 其他用户的数据、服务凭据、Docker socket |
|
||
| PostgreSQL | Open WebUI 持久化状态,以及 Runtime 事件、计划和记忆 | 代码执行 |
|
||
| 共享基础设施 | TLS、镜像仓库、源码托管、私有路由、模型服务 | Agent 行为 |
|
||
|
||
这种分层允许 Web UI 按自己的节奏升级,同时让 Agent 循环可以频繁迭代。
|
||
|
||
## 请求生命周期
|
||
|
||
### Agent 请求
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant B as 浏览器
|
||
participant W as Open WebUI
|
||
participant R as Runtime
|
||
participant M as 模型提供方
|
||
participant G as Gateway
|
||
participant X as 用户工作区
|
||
|
||
B->>W: 已鉴权的对话请求
|
||
W->>R: OpenAI 兼容请求 + 服务密钥 + 签名用户身份
|
||
R->>R: 校验身份、构建上下文、记录 run.created
|
||
loop 直到有证据支持完成,或预算耗尽
|
||
R->>M: 最近上下文 + 工具 + 提供方策略
|
||
M-->>R: 助手内容和/或工具调用
|
||
R->>R: 规范化、校验、限流并调度调用
|
||
R->>G: 工具请求 + 服务密钥 + 新签名身份
|
||
G->>G: 校验身份并绑定用户工作区
|
||
G->>X: 在资源和路径约束下执行
|
||
X-->>G: 退出码、输出或文件结果
|
||
G-->>R: 结构化工具结果
|
||
R->>R: 持久化事件并评估完成证据
|
||
end
|
||
R-->>W: SSE 回答和已完成的工具详情块
|
||
W-->>B: 渲染后的 Agent 响应
|
||
```
|
||
|
||
Open WebUI 发送的身份在 Runtime 接受请求时校验一次。之后,Runtime 会为每一次 Gateway 调用重新签发已经校验过的身份。这样可以避免一次耗时较长的 Agent 运行,在模型或工具工作期间继续复用已经过期的身份令牌。
|
||
|
||
### 工作区文件请求
|
||
|
||
```text
|
||
带 Open WebUI 会话的浏览器
|
||
-> /api/v1/k1412/workspace/*
|
||
-> Open WebUI 校验当前登录用户
|
||
-> Web 签发短期用户身份
|
||
-> Gateway 校验服务密钥和签名身份
|
||
-> Gateway 只选择该用户对应的哈希容器与数据卷
|
||
-> 返回浏览、下载或流式归档响应
|
||
```
|
||
|
||
浏览器永远不会获得 Gateway 服务密钥,也不会直接连接 Docker。
|
||
|
||
## Runtime 内部结构
|
||
|
||
从 Open WebUI 的视角看,Runtime 是一个 OpenAI 兼容的模型提供方,但普通聊天补全实际由 K1412 循环实现:
|
||
|
||
- `ModelProvider` 将内部模型规格转换为提供方请求,并统一流式补全响应。
|
||
- `ContextPolicy` 移除已渲染的工具详情标记,前置系统契约和持久记忆,并在每个模型的字符预算内保留最新的可见消息。
|
||
- `ToolRegistry` 声明工具 schema,将工作区工具路由到 Gateway,将状态工具路由到 `RuntimeStore`。
|
||
- `AgentLoop` 负责迭代、工具规范化、校验、调度、恢复、委派和基于证据的完成判定。
|
||
- `RuntimeStore` 持久化运行事件、每对话计划和用户记忆。
|
||
|
||
更多细节参见 [Agent 循环实现](agent-loop.zh-CN.md)。
|
||
|
||
## 工作区架构
|
||
|
||
`local-docker` 与 `ssh-docker` 实现同一套 `ExecutionProvider` 契约。生产环境使用 `ssh-docker`:Gateway 通过只读挂载的专用 SSH 配置,连接 `home-node-itx` 上的 Docker。
|
||
|
||
每个不可变的 Open WebUI 用户 ID 都映射到一个由 SHA-256 派生的工作区 ID,并且恰好对应一组:
|
||
|
||
- 名为 `k1412-ws-<workspace-id>` 的容器;
|
||
- 名为 `k1412-ws-data-<workspace-id>` 的数据卷;
|
||
- 名为 `k1412-ws-net-<workspace-id>` 的桥接网络。
|
||
|
||
容器以 UID/GID 1000 运行,根文件系统只读,移除全部 Linux capabilities,启用 `no-new-privileges`,设置 PID/CPU/内存限制,不挂载 Docker socket,也不发布端口。`/workspace` 是唯一持久可写的文件系统。`/tmp` 可写,但以 `noexec` 方式挂载。
|
||
|
||
Python 环境位于 `/workspace/.venv`。用户级 home 与软件包缓存位于 `/workspace/.agent` 下。镜像会把虚拟环境和用户二进制目录放在 `PATH` 前部。命令在启用 `pipefail` 的 Bash 中运行,因此失败的安装不会被成功的 `tail` 或类似管道末端命令掩盖。
|
||
|
||
当 `WORKSPACE_IMAGE` 改为新的不可变标签后,Gateway 会在下一次访问时替换过期容器,但重新挂载已有命名数据卷。因此,镜像升级可以改变执行环境而不删除用户文件。
|
||
|
||
## 网络与部署
|
||
|
||
生产环境以一个 Unraid Compose Manager 项目运行:
|
||
|
||
- `public`:Web 入口;
|
||
- `internal`:私有服务间通信;
|
||
- `egress`:Runtime 访问模型提供方,以及 Gateway 的 SSH 访问。
|
||
|
||
只有 Web 发布 NAS 端口 `12004`。VPS 上的 Nginx Proxy Manager 为 `agent.k1412.top` 终止 TLS,并通过 Tailscale 转发。镜像以 `linux/amd64` 构建,使用不可变的“时间戳-提交”标签推送到 `docker.k1412.top/wuyang/*`,部署时只更新相关服务。
|
||
|
||
文档门户打包在 Web 镜像中,通过 `/doc/` 提供,不会增加新的公开服务、端口、数据库或代理规则。
|
||
|
||
## 稳定面与实验面
|
||
|
||
稳定的平台契约:
|
||
|
||
- 已鉴权的 Web 到 Runtime 请求;
|
||
- Runtime/Web 到 Gateway 的签名身份;
|
||
- `ExecutionProvider` 工作区语义;
|
||
- 每用户数据卷隔离;
|
||
- 运行事件持久化;
|
||
- 公开模型 ID;
|
||
- 工作区文件 API。
|
||
|
||
预期持续实验的部分:
|
||
|
||
- 提示词与上下文策略;
|
||
- 记忆选择;
|
||
- 工具目录与意图路由;
|
||
- 调度器与并行;
|
||
- 委派;
|
||
- 恢复与证据规则;
|
||
- 模型预算与路由。
|
||
|
||
所有对实验结果有影响的行为,都应在 `run.created` 中带版本号,使实现变化前后的结果仍然可比较。
|