找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3041

积分

0

好友

411

主题
发表于 前天 06:54 | 查看: 11| 回复: 0

上一个周末,我从 400 行核心代码起步,逐步将一个工程演进成完整的、支持多租户的 AI Agent 平台。本文将完整记录这段从零开始的技术构建旅程。

为什么要从零开始构建这个平台?

市面上的 AI Agent 平台要么体系过于臃肿,动辄需要部署几十个微服务;要么过于封闭,无法自由地自定义工具和技能。我们真正想要的其实很简单:

  • 单二进制部署,做到零外部依赖,简化运维。
  • 多用户隔离,确保每个用户都有独立的沙箱化工作区,互不影响。
  • 真正的智能体,不是简单的聊天套壳,而是能够自主调用工具、执行代码、并且记忆上下文的 Agent。
  • 可插拔的生态,支持通过 MCP 协议接入数百个外部工具,并集成 SkillHub 技能市场。

最终,我们使用 Rust 实现了所有这些目标,并用 Tauri + React 构建了轻量级的桌面客户端。

OpenClaw 多租户 AI Agent 平台软件界面

架构设计全景图

整个系统的架构设计如下,清晰地划分了前后端与核心组件:

┌─────────────────────────────────────────────────────┐
│                 Tauri Desktop App                   │
│          React + Tailwind (shadcn rose)            │
│  ┌──────────┐  ┌──────────┐  ┌────────────────┐   │
│  │ ChatView │  │ ToolCard │  │ SkillHub Panel │   │
│  │ 流式渲染  │  │ 工具展示  │  │ 技能市场浏览    │   │
│  └────┬─────┘  └────┬─────┘  └───────┬────────┘   │
│       │     SSE / WebSocket          │             │
├───────┼──────────────────────────────┼─────────────┤
│       ▼          Gateway (Axum)      ▼             │
│  ┌─────────┐ ┌──────┐ ┌──────┐ ┌────────┐         │
│  │JWT Auth │ │ REST │ │  WS  │ │  SSE   │         │
│  └────┬────┘ └──┬───┘ └──┬───┘ └───┬────┘         │
│       └─────────┼────────┼─────────┘               │
│          ┌──────▼────────▼──────┐                  │
│          │   Session Manager    │ DashMap          │
│          └──────────┬───────────┘                  │
│                     ▼                               │
│          ┌─────────────────────┐                  │
│          │    Agent Runner     │                  │
│          │  ┌───────────────┐  │                  │
│          │  │  Agent Loop   │  │ 最多 20 轮自主决策│
│          │  │  LLM ⇄ Tools  │  │                  │
│          │  └───────────────┘  │                  │
│          │  ┌───────┐┌──────┐  │                  │
│          │  │Memory ││Skills│  │                  │
│          │  └───────┘└──────┘  │                  │
│          └──────────┬──────────┘                  │
│     ┌───────────────┼───────────────┐            │
│     ▼               ▼               ▼            │
│ ┌────────┐   ┌────────────┐   ┌──────────┐      │
│ │10 Tools│   │MCP Servers │   │ SkillHub │      │
│ │内置工具 │   │外部工具协议 │   │ 技能市场  │      │
│ └────────┘   └────────────┘   └──────────┘      │
│                     ▼                             │
│          ┌─────────────────────┐                  │
│          │  Sandbox Backend    │                  │
│          │  文件夹隔离 / 路径防穿越 │               │
│          └─────────────────────┘                  │
└─────────────────────────────────────────────────────┘

备注:这里的技能市场是直接接入的腾讯 SkillHub。

核心引擎:Agent Loop 的设计

许多所谓的“AI应用”本质上只是一个聊天转发器:用户输入,调用LLM API,返回文本。这远远称不上是一个真正的智能体。

真正的 Agent 核心在于 工具调用循环 (Tool Calling Loop),一个动态的决策-执行过程。例如:

用户: “帮我创建一个 hello.py 并运行它”
  ↓
LLM 决定: 调用 file_write 工具
  ↓
系统执行: 写入 hello.py
  ↓
LLM 看到结果,决定: 调用 exec 工具
  ↓
系统执行: python3 hello.py → “hello world”
  ↓
LLM 综合结果: “文件已创建并运行,输出是 hello world”

我们的 Rust 实现简洁而高效,核心逻辑如下:

for iteration in 0..MAX_TOOL_ITERATIONS {
    let response = self.llm
        .chat_with_tools(&messages, Some(&tools)).await?;

    if has_tool_calls(&response) {
        // 执行工具,并将结果追加到对话上下文
        for tc in tool_calls {
            let result = self.tool_registry.execute(&tc.name, params).await;
            messages.push(ChatMessage::tool(&tc.id, &result.content));
        }
        // 继续循环,让 LLM 基于工具执行结果进行下一步决策
    } else {
        // LLM 返回纯文本回复,循环结束
        return Ok(response.content);
    }
}

几个关键的设计决策确保了系统的健壮性:

  • 设置最大迭代轮数,防止 Agent 陷入无限循环,此上限可由管理员配置。
  • 严格的超时控制:每个工具调用最长 60 秒,exec 命令执行最长 30 秒。
  • 超时后自动清理:强制终止相关子进程,并将错误返回给 LLM,由它来决定如何处理失败。

内置工具:少而精的核心能力

我们只内置了 10 个核心工具,因为许多特定工具可以通过后文提到的 MCP 协议动态注入,内置工具追求的是“够用”。

工具 能力
exec 执行任意 shell 命令(sh -c,支持管道、重定向)
file_read 读取工作区内的文件内容
file_write 创建或写入文件
file_edit 查找并替换,编辑文件内容
list_files 列出目录内容(支持递归)
apply_patch 应用 unified diff 格式的补丁
web_search 调用 DuckDuckGo 进行网页搜索
web_fetch 抓取网页内容,并自动将 HTML 转换为 Markdown
memory_search 在用户的记忆文件中进行搜索
memory_get 读取指定的记忆文件

所有工具都通过一个统一的 Tool trait 来定义,保证了接口的一致性:

#[async_trait]
pub trait Tool: Send + Sync {
    fn name(&self) -> &str;
    fn description(&self) -> &str;
    fn parameters_schema(&self) -> serde_json::Value; // JSON Schema
    async fn execute(&self, params: Value) -> ToolResult;
}

工具由 ToolRegistry 统一管理,它能自动将这些工具的接口描述转换为 OpenAI function calling 格式的 JSON Schema,并传递给大语言模型。

无限扩展:通过 MCP 协议接入外部工具生态

MCP (Model Context Protocol) 是由 Anthropic 推出的开放协议,它让 AI Agent 可以连接到任意的外部工具服务器,极大地扩展了能力边界。

我们实现了完整的 MCP 客户端。只需在配置文件中声明,即可接入海量工具:

# config/default.toml
[[mcp.servers]]
name = “playwright”
command = “npx”
args = [“-y”, “@playwright/mcp@latest”]

平台启动时会自动:

  1. 通过 stdio 启动 MCP Server 子进程。
  2. 完成 JSON-RPC 2.0 握手流程。
  3. 发现该服务器提供的所有工具。
  4. 将其注册到全局的 ToolRegistry 中。

之后,Agent 就可以像使用内置工具一样,无缝调用 Playwright 进行浏览器自动化操作:

用户: “打开 https://example.com 并截图”
Agent: [调用 playwright.navigate] → [调用 playwright.screenshot] → 完成

整个 MCP 传输层的 Rust 实现非常精简,核心结构体仅约 150 行代码:

pub struct StdioTransport {
    child: Child,
    writer: Arc<Mutex<BufWriter<ChildStdin>>>,
    pending: Arc<DashMap<u64, oneshot::Sender<Result<Value>>>>,
    next_id: AtomicU64,
}

大脑升级:技能 (Skill) 系统

如果说工具 (Tool) 是 Agent 执行任务的手脚,那么技能 (Skill) 就是指导它如何思考的大脑。

一个技能本质上是一个结构化的 SKILL.md 文件——一份详细的提示词(Prompt),教导 Agent 在特定场景下,应该按什么流程、使用哪些工具。

---
name: weather
description: Check current weather for any city
---

When the user asks about weather:
1. Use web_search to find current weather data
2. Use web_fetch to get details from a weather site
3. Present: temperature, conditions, humidity, wind speed

技能的三种获取方式

1. 内置技能 — 开箱即用
我们内置了 6 个核心技能,如 weather(天气查询)、coding(代码助手)、research(研究助手)等。

2. SkillHub 技能市场 — 一键安装
我们集成了腾讯 SkillHub 和 ClaHub 双数据源,提供了 100+ 个开箱即用的技能。后端并行请求两个数据源,合并去重后返回给前端;用户在前端的 SkillHub 面板中即可浏览、搜索并一键安装。

3. 对话创建 — 让 Agent 帮你写技能
内置的 skill-creator 技能允许用户通过自然语言对话,让 Agent 帮助生成自定义技能文件,并自动写入 workspace/skills/ 目录,即刻生效。

持久化记忆:实现跨会话的上下文

一个强大的 Agent 需要记忆。我们的记忆系统不局限于聊天记录,而是借鉴了 OpenClaw 风格的文件式记忆体系。

多租户平台项目目录结构示意图

每个用户的工作区目录结构如下:

workspaces/users/{user_id}/
├── workspace/
│   ├── MEMORY.md          ← 长期记忆文件,每次对话开始时自动加载到上下文中
│   └── memory/
│       └── 2026-04-03.md  ← 按日期生成的记忆日志
└── memory.db              ← SQLite 数据库,存储结构化的聊天历史

每次 Agent 启动时,系统会自动将 MEMORY.md 和最近两天的日志内容注入 System Prompt。同时,Agent 在运行过程中也可以通过 file_write 等工具,主动向记忆文件中写入重要信息。用户可以在前端侧边栏实时查看和管理自己的记忆内容。

流式响应:提升用户体验的关键

为了不让用户在等待 LLM 生成完整回复时感到焦虑,我们实现了完整的 Token 流式输出。实现原理清晰:

// LLM 客户端:解析 SSE 流,逐个 Token(delta)发送
pub async fn chat_stream(
    &self, messages, tools, tx: &Sender<StreamChunk>
) -> Result<ChatMessage> {
    // stream: true → 获取 reqwest bytes_stream → 逐行解析 → 发送 Delta
}

// Agent Runner 端:将收到的 Delta 转发到前端事件通道
while let Some(chunk) = stream_rx.recv().await {
    match chunk {
        StreamChunk::Delta(text) => {
            tx.send(AgentEvent::Text { content: text }).await;
        }
        ...
    }
}

安全基石:多租户隔离的实现

多租户隔离是企业级平台的核心需求。我们的整体架构清晰地体现了隔离层次。

openclaw/
├── Cargo.toml                  # workspace 根
├── crates/
│   ├── openclaw-gateway/       # Axum 网关、WebSocket、REST
│   ├── openclaw-agent/         # Agent 运行核心(Runner, Skill, Memory)
│   ├── openclaw-sandbox/       # 沙箱抽象层(核心隔离模块)
│   └── openclaw-config/        # 配置管理
├── config/                     # 配置文件
└── workspaces/                 # 运行时用户工作区(按用户ID隔离)
    └── {user_id}/
        ├── workspace/          # 用户个人工作文件
        ├── agents/             # 用户个人Agent配置
        └── memory.db           # 用户个人聊天数据库

在安全层面,我们采取了分层策略:基础层使用文件夹隔离,并辅以严格的路径安全检查防止目录穿越;高级层则计划支持 macOS 的 sandbox/containerLinux 的 namespace/gVisor/Firecracker 等更彻底的沙箱技术。作为一个服务端优先的平台,我们目前主要专注于 Linux/macOS 环境。

这样一来,每个用户都拥有完全独立、互不干扰的:

  • 文件工作区(经过沙箱路径校验)。
  • SQLite 数据库(存储私密聊天历史)。
  • 长期记忆文件(MEMORY.md)。
  • 已安装的技能集合。
  • MCP 工具实例。

用户通过注册/登录获得 JWT Token,在任何设备上使用同一账号登录,都将映射到同一个隔离的虚拟工作空间。

核心的路径安全检查函数如下:

// 路径安全校验,防止用户通过相对路径访问系统或其他用户文件
fn safe_path(&self, user_id: &str, relative: &Path) -> Result<PathBuf> {
    let root = normalize_path(&self.user_root(user_id));
    let normalized = normalize_path(&root.join(“workspace”).join(relative));
    if !normalized.starts_with(&root) {
        return Err(anyhow!(“Path escape detected”));
    }
    Ok(normalized)
}

跨平台客户端:基于 Tauri 的桌面应用

我们默认提供了一个使用 Tauri 开发的桌面客户端。得益于清晰的架构设计,这个核心的 Rust Claw Gateway 也可以轻松对接飞书、微信等生态,因为它本质上是一个标准的后端服务。

技术栈Tauri 2.0 + Vite + React + Tailwind CSS

核心前端组件

  • LoginScreen — 处理用户注册/登录,Token 存储于 localStorage。
  • ChatView — 消息列表,支持自动滚动与 Markdown 渲染。
  • ToolCallCard — 折叠式工具调用展示组件(包含状态指示灯、调用参数和执行结果)。
  • MemorySidebar — 实时展示和管-理用户的记忆文件与技能。
  • SkillPanel — 双标签页面板(“我的技能” 和 “SkillHub 市场”)。
  • useChat hook — 封装了 SSE 流式通信的状态管理逻辑。

前端处理流式响应的核心逻辑片段:

// SSE 流式 hook 核心逻辑
for await (const event of chatStream(token, text)) {
  switch (event.type) {
    case ‘text’:   msg.content += event.content; break;
    case ‘tool_start’: msg.toolCalls.push({…}); break;
    case ‘tool_result’: /* 更新最后一个运行中的工具 */; break;
    case ‘done’:   msg.isStreaming = false; break;
  }
}

总结与展望

这个项目实践证明了:使用 Rust 构建 AI Agent 平台,不仅是可行的,更是优雅且高效的。

单二进制部署、极低的内存占用、强大的类型安全、卓越的异步并发性能——这些都是 Rust 的天然优势。而 Tauri 框架让我们能够利用熟悉的 React 等 Web 技术快速构建出高性能的跨平台桌面客户端,完全避免了 Electron 那样沉重的内存开销。

从 400 行核心代码到功能完备的多租户平台,这是一次充满挑战也极具成就感的 开源实战。我们希望这次的技术分享能为你带来启发。完整代码已开源,欢迎在 云栈社区 与更多开发者交流探讨。

项目地址:https://github.com/coder-brzhang/rust-mutil-user-claw




上一篇:MySQL与向量数据库在AI时代如何选型:并存协作还是相互取代?
下一篇:嵌入式Linux显示开发入门:Framebuffer、Qt与OpenGL ES三大应用路径实战
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-4-7 15:54 , Processed in 0.655960 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表