Architecture

ZhiWu 整体架构

自托管 AI Agent 平台的分层结构:无状态 API 进程与租约化的 ARQ Worker 共享同一个 zhiwu 核心库,文件与命令外置到独立沙箱,数据按事务、向量、图、对象、队列分工到五个存储。本页内容与 docs/01-architecture.md、docs/00-overview.md、docs/07-data-model.md、docs/adr/0001 同步。

System Context

系统上下文

五层结构,与 docs/diagrams/architecture.mmd 一致。CSS 图没有连线,节点之间的"谁调谁"写在每层底部的走向说明里。前置条件:PostgreSQL schema 已由独立的 storage-migrator 迁移到位,运行进程启动时只校验版本、不改表(utils/lifespan.py)。

使用者层
Web 用户 / 管理员浏览器:工作台、知识库、Agent 配置HTTPS · 反向代理
CLI / 外部 Agent终端与脚本编排,API Key 鉴权zhiwukey_ · /api/external/kb/*

webUser → spa(经浏览器)· cliUser → api(带 zhiwukey_ 前缀密钥)。

前端层 · web/
单页应用 :5173Vue 3 + Vite 工作台proxy /api → :5050

spa → api:开发期由 Vite、生产期由反向代理把 /api 转发到 :5050(web/vite.config.js)。

后端进程层 · backend/
FastAPI API 进程 :5050REST/SSE、鉴权、限流、写请求并入队server/main.py · 26 routers
ARQ Worker 进程抢占租约、执行 Run 与知识库后台任务server/worker_main.py
zhiwu 核心库agents / knowledge / services / repositoriesbackend/zhiwu/src/zhiwu/

api · worker → core:两进程共享同一核心库;api 只 enqueue_job 入队,真正驱动 Agent 图的是 worker(services/run_worker.py)。

沙箱子系统层
Sandbox Provisioner :8002沙箱容器 CRUD · touch/quiesce · HTTP 反代docker/sandbox_provisioner/app.py
沙箱容器(每 runtime scope)agent-sandbox,隔离文件与命令执行HTTP :8080
MCP Servers外部工具服务与内置 chartstdio / sse / streamable_http

core → provisioner → sandboxBox:文件与命令经 CompositeBackend 交给 Provisioner,再由它 create/delete/proxy 到 :8080。

数据与外部服务层
PostgreSQL业务表 + LangGraph checkpoint36 张表
RedisARQ 队列 + Run 事件 Stream + 缓存redis://redis:6379
Milvus向量 + BM25 稀疏 + 图谱向量每 KB 一个 collection
Neo4j知识图谱实体 / 关系graph retrieval
MinIO文档 / 抽取图片 / 公开资源knowledgebases · kb-images · public
模型供应商Chat / Embed / Rerank / 图像SiliconFlow · DashScope · OpenAI 兼容 · Anthropic · Gemini
Langfuse 追踪模型与工具调用链路agent_runs.langfuse_trace_id

core → pg / redis / milvus / neo4j / minio 与出网调用 llm / langfuse;api →(入队) redis →(消费) worker。

i
内置 MCP 的落点。stdio 类型的 MCP 服务(内置 chart)在 Worker 进程内由 MultiServerMCPClient 拉起,而不在沙箱容器里(agents/mcp/service.py)。用户可配置的远程 MCP 只走 sse / streamable_http。
Process Model

进程模型

运行面由四个可执行进程加一个被共享的库组成。API 进程保持无状态,长任务全部下沉到 Worker;Provisioner 是独立 HTTP 服务;SPA 只调 /api。

进程 / 组件职责端口入口
API 进程 REST/SSE、鉴权与两层登录限流、权限校验;写 agent_run_requests + agent_runs(pending) 后仅 enqueue_job,立即返回 201 + run_id;无状态可水平扩。 :5050 backend/server/src/server/main.py
ARQ Worker 进程 消费 process_agent_run / process_task,用 owner token 做租约(lease)CAS 抢占并心跳续租,驱动 LangGraph 执行与知识库后台任务。并发与超时:ARQ_MAX_JOBS 默认 10、job_timeout 默认 3600s、max_tries=2。 无监听端口(消费 Redis 队列) backend/server/src/server/worker_main.py
zhiwu 核心库 agents / knowledge / models / services / repositories / storage / config / permissions;被 API 与 Worker 双进程共享,非独立进程。 — backend/zhiwu/src/zhiwu/
Sandbox Provisioner 沙箱容器生命周期:create/delete/touch/quiesce 与 HTTP 反代;backend 可切换 memory / docker / kubernetes;访问 token 要求 ≥ 32 字符。 :8002(供给的容器 :8080) docker/sandbox_provisioner/app.py
Web SPA 工作台 / 管理 / 可视化,统一调 /api/*;开发期 Vite 代理 /api → :5050。 :5173 web/src/main.js

CLI 客户端 zhiwu-cli/src/zhiwu_cli/main.py(命令 zhiwu)与 SPA 走同一套 /api,不是第五个常驻服务进程。

Data Flow

一次请求的数据流

从用户消息到终态落库,跨 API 进程、Redis、Worker 与沙箱。Request 是意图,Run 是业务执行,Attempt 是一次占有事实,三张表把"提交—抢占—收敛"拆开。来源:services/run_queue_service.py、services/run_worker.py。

提交 · Request

POST /api/agents/{slug}/threads/{thread_id}/runs → 鉴权中间件(Bearer JWT / API Key)→ 资源权限校验(share_config)→ 写 agent_run_requests(request_id 为幂等(idempotent)键)与 agent_runs(pending)。同线程已有活跃 Run 时按 queue_policy 排队 / 拒绝 / steer。

入队 · 立即返回

API 只做 enqueue_job(process_agent_run) 投到 Redis/ARQ,返回 201 + run_id,不在请求进程内跑重活(services/run_queue_service.py)。

抢占 · Run / Attempt

Worker 用 owner token 做租约 CAS 抢占执行权,创建 agent_run_attempts 并置 runs:running;心跳续租,租约过期的 Attempt 记 lease_expired,Run 可被其他 Worker 接管(services/run_worker.py)。

执行 · Graph

ChatbotAgent.astream_events v3 跑模型推理 ↔ 工具调用循环:文件 / execute → CompositeBackend → Provisioner → 沙箱 :8080;知识检索 → Milvus hybrid / Neo4j 图谱;外部能力 → MultiServerMCPClient;Skill 激活 → 读投影目录 SKILL.md 放开依赖工具。敏感工具(write_file/edit_file/execute)触发 ToolApproval 中断,前端审批后以 run_type=resume 从 checkpoint 续跑。

流式 · SSE

ChunkedEventWriter 攒批 XADD 写入 Redis Stream;前端 GET /runs/{run_id}/events(SSE)由 API 进程读 Stream 转发,支持 Last-Event-ID / after_seq 断点续传。

收敛 · 终态

终态以 DB 为准:正常结束 completed,异常 failed,取消以 PG 为准;checkpoint 持久化会话历史。Worker 启动与周期 reconcile 收敛过期租约、补发任务(services/run_worker.py)。

i
三条运行时约定。租约即执行权、取消以 PG 为准、事件尽力发布而终态以 DB 为准(services/run_worker.py)。部分唯一索引 uq_agent_runs_one_active_per_thread 保证同一 (uid, agent_slug, thread) 只有一个非终态 Run,多 Worker 并发消费天然安全。
Components

组件职责

核心库 backend/zhiwu/src/zhiwu/ 按包划分职责。依赖方向自上而下:server 进程壳(不属核心库)只装配路由/鉴权/lifespan,services 编排 agents 与 knowledge,二者向下用 models / storage / repositories,config 被各层读取。

包职责关键文件
agentsLangGraph Agent 图、12 层中间件栈、工具、Skill / MCP 集成、沙箱后端agents/base.py · agents/buildin/chatbot/graph.py
knowledgeRAG:工厂 / 解析 / 分块 / 检索 / 图谱 / 评估;实现含 milvus / dify / notionknowledge/factory.py · knowledge/runtime.py
modelschat / embed / rerank 模型适配与供应商管理models/(provider)
servicesRun 队列与执行、对话、知识库后台任务、定时派发、审计、Skill / MCP 服务services/run_worker.py · services/run_queue_service.py
repositoriesSQLAlchemy 仓储层,向上屏蔽表结构repositories/
storagePostgreSQL / Redis / MinIO / Neo4j 客户端与 schema 定义storage/postgres/ · storage/minio/client.py
configoptions / settings:系统配置项入库与 Redis 缓存(TTL 300s,sensitive 不缓存)config/options.py
permissionsRBAC 与资源 scope(share_config)permissions/resource_permission.py

扩展点:知识库类型经 KnowledgeBaseFactory 注册(knowledge/runtime.py)、Agent 后端图按 agents/buildin/ 目录 + backend_id 装配,Skill / MCP / 模型供应商均为数据驱动(PG 表),新增实例无需改代码。

Data Model

数据模型

PostgreSQL 共 36 张表(25 业务 + 11 知识),另有 LangGraph checkpoint 表用独立 autocommit 连接串。Redis 承担队列与事件,MinIO 存对象,Milvus 存向量,Neo4j 存图,文件系统存工作区与技能投影。来源:storage/postgres/models_business.py、models_knowledge.py、docs/07-data-model.md。

PostgreSQL 主要表分组

分组代表表说明
身份与组织users · departments · user_config · api_keys · cli_auth_sessionsargon2 密码、role(superadmin/admin/user)、uid;API Key 以 sha256 hash + HMAC 派生存储;user_config 存记忆与工具审批等 JSON 配置。
Agent 与能力配置agents · agent_envs · model_providers · mcp_servers · skills · config_optionsslug unique;模型 / 提示词 / 工具 / KB / MCP / Skills 绑定存于 agents 配置 JSON(无 junction 外键表)。
对话域projects · conversations · subagent_threads · messages · conversation_stats · tool_calls · message_feedbacksthread_id UUID unique;messages 兼作审计表(message_type = model_audit|tool_audit),无独立 audit 表。
调度与任务scheduled_agent_jobs · scheduled_agent_runs · tasks · operation_logscron 5 段表达式 + IANA 时区;tasks 为 KB 解析/索引/图谱等后台任务,由 task_registry 派发。
知识库(11 表)knowledge_bases · knowledge_files · knowledge_chunks · knowledge_graph_* · evaluation_*对外称 document、对内是 knowledge_files;图谱实体/三元组及其 mention 在 PG 有镜像表。

Agent Run 三表

表角色关键字段
agent_run_requests意图 / 幂等入口request_id unique、queue_policy(enqueue/reject/steer)、status(queued/dispatched/cancelled/rejected/failed)、input_payload、dispatched_run_id
agent_runs业务态 / 状态机status 7 态、run_type(chat/resume/subagent)、request_id unique、runtime_scope_id、租约字段(owner / expires)、langfuse_trace_id、token_usage JSON
agent_run_attempts占有事实(不可变)(run_id, attempt_no) unique、worker_id(owner token)、heartbeat / lease、outcome 6 值、error_type / message

关键索引与约束

  • uq_agent_runs_one_active_per_thread:部分唯一索引,(uid, agent_slug, conversation_thread_id) WHERE status 非终态 → 单线程单写者。
  • agent_runs.request_id unique → 幂等;ix_agent_runs_status_lease_expires → 租约回收扫描。
  • uq_agent_run_attempts_run_attempt_no → attempt 事实不重号,终止后不得改写。
  • skills.slug / agents.slug / mcp_servers.slug / conversations.thread_id / users.uid 均 unique。

Redis · MinIO · Milvus · Neo4j

存储用途 / 结构来源
RedisARQ 队列、Run 事件 Stream(XADD + MAXLEN 控制)、取消信号 key、config 缓存(TTL 300s,sensitive 项不缓存)、worker 健康 key。services/run_queue_service.py · config/options.py
MinIO三桶:knowledgebases(文档与解析产物)、kb-images(抽取图片)、public(可公开资源,如头像);对象键按 kb_id / 文档组织。storage/minio/client.py
Milvus每 KB 一个 collection;字段含 chunk 文本、稠密向量(维度默认 1024)、BM25 稀疏向量与元数据;支持 vector / BM25 / hybrid 加权融合检索。knowledge/implementations/milvus.py
Neo4j存知识图谱实体与关系;PG 侧以 knowledge_graph_entities / _triples / _entity_mentions / _triple_mentions 作镜像表。storage/neo4j/ · models_knowledge.py
i
迁移独占。运行进程不改 schema,只 require_current_schema() 校验;迁移由 python -m zhiwu.storage_migration 执行,且只接受空库或基线 v1(BUSINESS_SCHEMA_VERSION = 1),版本不等于基线直接 RuntimeError —— 升级等于重建库,无历史升级阶梯(storage_migration.py、manager.py)。
Deployment

部署拓扑

与 docs/diagrams/deployment.mmd 对应。关键事实:反向代理与完整平台编排不在本仓库——仓库只交付沙箱侧的镜像与 compose 清单,虚线框节点为部署侧提供。

反向代理
反向代理web 构建产物 + /api 转发部署侧提供 · 仓库未包含

浏览器 → nginx → api:TLS 终结与 nginx 配置均不属本仓。

应用进程
api 容器 :5050FastAPI:REST / SSEserver/main.py
worker 容器ARQ Worker · ARQ_MAX_JOBSserver/worker_main.py
storage-migrator一次性 Job:schema 迁移定义不在本仓库

migrator 只建库时跑一次(migrator → pg),api / worker 长期运行。

数据服务
PostgreSQL业务表 + checkpoint36 表
Redis队列 / 事件 / 缓存 / 健康 key:6379
MinIO对象存储knowledgebases · kb-images · public
Milvus向量 + BM25每 KB collection
Neo4j知识图谱graph

api → pg · redis · minio(并 SSE 读 redis);worker → pg · redis · minio · milvus · neo4j。

沙箱子系统
sandbox-provisioner :8002PROVISIONER_BACKEND = docker | kubernetes | memorydocker/sandbox_provisioner
Sandbox 容器(每 scope)agent-sandbox HTTP:8080 · 挂载 user-data / skills

worker →(创建/删除/touch/反代 :8002) prov → sbx;prov 用 docker backend 时经 docker.sock 起容器。

宿主卷与运行时
宿主卷 user-data/用户工作区持久根ZHIWU_USER_DATA_DIR
宿主卷 skill-projections/按 uid 生成的 Skill 投影ZHIWU_SKILL_PROJECTION_DIR
/var/run/docker.sockdocker backend 需要(高权限)宿主挂载

prov 与 sbx 对 user-data / skill-projections bind mount 同源;api 直读 user-data 提供工作区 / 工作台文件视图。

外部服务
模型供应商 APIChat / Embed / Rerank出网调用
MCP Server(sse / http)用户配置的远程工具出网调用
Langfuse追踪出网调用

worker → ext_llm · ext_mcp · ext_langfuse;只有执行侧直连这三者。

!
仓内不存在的编排。仓库根无 docker-compose.yml(git ls-files docker 已确认),docker/nginx/ 与 k8s/ 为空目录且未被 git 跟踪,全仓无 nginx / caddy / traefik 与 443 / TLS 听口定义。因此 api / worker / web / storage-migrator / 反向代理均无仓内定义,下图服务名是从代码引用推断的。
  • 唯二 Compose 文件:docker/sandbox_provisioner/docker-compose.yml(仅 1 个 service provisioner,zhiwu-sandbox-provisioner:local,宿主端口 8002)与 docker-compose.k8s.yml(同 service 换 kubernetes backend)。
  • 服务名推断依据:web/vite.config.js 默认 ^/api → http://api:5050、^/minio/public/ → http://minio:9000、REDIS_URL=redis://redis:6379/0。
  • storage-migrator 的代码在仓内(backend/zhiwu/src/zhiwu/storage_migration.py),缺的只是它的容器镜像与 Job 清单。
  • K8s 侧只有沙箱基础设施清单 docker/sandbox_provisioner/test/k8s-infra.yaml:Namespace zhiwu-know + 两个静态 PV/PVC(zhiwu-user-data、zhiwu-skills,storageClassName: ""),PVC 名须与 USER_DATA_PVC / SKILLS_PVC 对齐。
Decisions

架构决策

摘自 docs/adr/0001-architecture-overview.md。该 ADR 由代码逆向归纳,非原始决策记录;下列"为什么 / 代价"部分动机需访谈补充。

D1 · 双进程分离

决定:API(FastAPI)与执行(ARQ Worker)分开,共享 zhiwu 核心库。
为什么:Agent Run 是长任务(默认超时 3600s),不能阻塞请求进程;队列化后 API 无状态可水平扩,Worker 靠租约并发安全扩。
代价:事件回传跨进程,选 Redis Stream + SSE 轮询桥接而非进程内直推。
server/main.py · server/worker_main.py

D2 · Run 三表模型

决定:requests(意图)/ runs(业务态)/ attempts(执行事实)三表。
为什么:幂等提交(request_id unique)、单线程单写者(部分唯一索引)、执行历史不可变审计;租约 CAS + 心跳实现崩溃接管。
代价:三表 + 租约字段 + 唯一索引的组合复杂度。
models_business.py · services/run_worker.py

D3 · LangGraph + deepagents

决定:Agent 框架用 LangGraph + langchain create_agent + deepagents 中间件。
为什么:需要 checkpoint 持久恢复(审批中断 resume)、成熟中间件生态、astream_events v3 细粒度流事件。
代价:12 层中间件栈顺序成为强约束,定制需理解拦截语义。
agents/buildin/chatbot/graph.py

D4 · 文件/命令外置到 Provisioner

决定:沙箱以独立 HTTP 服务 Provisioner 供给。
为什么:Agent 生成的代码不可信;独立服务可切 backend(memory/docker/kubernetes)、集中管容器生命周期。
代价:docker backend 需挂 /var/run/docker.sock(高权限),K8s backend 为更强隔离路径。
docker/sandbox_provisioner/app.py

D5 · Skill = 文件 + 索引 + 投影

决定:文件系统内容 + PG 索引 + 按 uid 只读投影。
为什么:技能正文大且被沙箱读取,不宜入 DB;投影隔离越权访问,禁符号链接 + 原子 rename + fail-closed。
代价:同步时机与版本哈希复杂;投影失败时技能降级不可用。
agents/skills/service.py

D6 · MCP 收敛 stdio

决定:只信任内置 stdio,用户侧限定远程 transport。
为什么:stdio 等于任意命令执行;启动迁移强制禁用历史用户 stdio 配置。
代价:用户无法接入本地 stdio MCP 服务,攻击面收敛到 sse / streamable_http。
agents/mcp/service.py

Boundaries

边界与限制

运行时的硬限制来自 docs/00-overview.md §5,交付面与运维面的边界来自各文档的"已确认结论"。以下是刻意不做或受限之处。

  • 沙箱 provider 仅支持 SANDBOX_PROVIDER=provisioner(agents/backends/sandbox/provider.py)。
  • 用户可配置的 MCP transport 仅 sse / streamable_http,stdio 只保留给内置服务(agents/mcp/service.py)。
  • 远程 Skill 安装源仅白名单 HTTPS 域名,默认 github.com、modelscope.cn(config/options.py remote_skill_source_policy)。
  • Agent 文件工具显式不提供 delete(删除需审批与审计设计,暂不开放)(agents/backends/composite.py)。
  • Skill 投影与路径守卫 fail-closed:沙箱或投影不可用时相关能力降级为不可用,而非放开访问(agents/skills/service.py)。
!
运维面承担。依赖面广:PG / Redis / MinIO / Milvus / Neo4j / Provisioner 六个外部服务,运维成本高;事件通道受 Redis Stream 轮询桥接的时延上限约束;checkpoint、messages、事件 Stream 均未发现 TTL 清理任务,长期增长属运营决策(docs/adr/0001 后果 · docs/07-data-model.md)。
?
仓库无法确认(属部署侧或需访谈,故本页不作断言):生产反向代理与 TLS 终结方式、api/worker/web/storage-migrator 的镜像与编排位置、K8s 应用侧 Deployment/Service/Ingress、all-in-one-sandbox 镜像内预装运行时清单、以及 ADR 各决策的原始动机与当时对比过的备选项。