手写一个 AI Agent 全栈项目:从沙箱执行到子智能体的完整实现

介绍一个基于 Express.js + Vercel AI SDK 的 AI Agent 练手项目,涵盖沙箱安全执行、Human-in-the-Loop 审批、动态 Skills 加载、子智能体等核心功能。


1. 背景

去年开始 AI Agent 概念大火,各种框架层出不穷。但与其直接用现成的 Agent 框架,不如亲手从零搭建一个------既能深入理解 AI Agent 的工作原理,又能把控安全、扩展性等工程细节。

这个项目是我个人的练手项目,基于 Turborepo Monorepo ,包含一个 Express.js API 后端和一个 Vue 3 前端。核心目标是构建一个安全、可扩展的 AI Agent 平台,让它能自主执行文件操作、运行 Shell 命令、加载专业技能、甚至创建子智能体来并行处理任务。

源码地址:express-turbo-monorepo


2. 技术栈选型

类别 选型 说明
构建编排 Turborepo Monorepo 管理,增量构建缓存
后端框架 Express 5 最新版 Express,支持异步错误处理
运行时 Node.js 23 + TypeScript 全栈类型安全
AI SDK Vercel AI SDK v6 ToolLoopAgent、流式响应
校验 Zod 4 Schema 校验 + OpenAPI 自动生成
沙箱 @anthropic-ai/sandbox-runtime bubblewrap 内核隔离
日志 Pino 结构化 JSON 日志,按天轮转
前端 Vue 3 + Nuxt UI 4 单页聊天客户端
部署 Docker 多阶段构建 Alpine 镜像,生产优化

选择这些技术栈的理由:

  • Express 5: 轻量灵活,适合理解 Agent 的请求-响应生命周期
  • Vercel AI SDK : 提供高阶抽象 ToolLoopAgent,让 Agent 自动循环调用工具,无需手动管理多轮逻辑
  • Turborepo : 天然支持 apps/* 多包结构,API 和前端共享配置
  • bubblewrap 沙箱: 使用 Linux 内核命名空间实现真正的文件系统和网络隔离

3. 项目结构

bash 复制代码
express-turbo-monorepo/
├── apps/
│   └── api/                       # Express.js API 服务
│       ├── src/
│       │   ├── index.ts          # 应用入口点
│       │   ├── app.ts            # Express 应用工厂
│       │   ├── errors/           # 错误处理
│       │   ├── lib/              # 工具库(日志、AI 提供商)
│       │   ├── middleware/       # 中间件
│       │   ├── modules/          # 功能模块
│       │   │   ├── chat/         # AI 对话模块
│       │   │   └── skills/       # Skills 管理模块
│       │   ├── sandbox/          # 沙箱运行时
│       │   ├── skills/           # Skills 发现与加载
│       │   ├── tools/            # AI 工具定义
│       │   ├── schema/           # Zod 校验规则
│       │   └── util/             # 工具函数
│       └── package.json
│   └── dev-web/                   # Vue 3 前端
├── turbo.json                     # Turborepo 配置
└── package.json

每个模块采用一致的文件结构:

xml 复制代码
modules/<name>/
  <name>.module.ts       # 路由注册
  <name>.routes.ts       # 路由定义
  <name>.controller.ts   # 请求处理
  <name>.service.ts      # 业务逻辑

4. 核心功能详解

4.1 AI 对话与工具调用

基于 Vercel AI SDK 的 ToolLoopAgent,Agent 会自动循环:思考 → 调用工具 → 获取结果 → 再思考,直到完成用户任务。

typescript 复制代码
const { text, steps } = streamText({
  model: provider,
  system: agentSystemPrompt(availableTools, skills),
  tools: agentTools,
  maxSteps: 30, // 最大工具调用步数
  onStepFinish: ({ toolResults }) => {
    // 每个 step 完成后压缩历史结果,节省 token
    return compressToolResults(toolResults, config);
  },
});

核心设计点:

  • 30 步上限:防止 Agent 无限循环
  • Tool Result 压缩:历史工具结果超过 120 字符时,替换为压缩提示文本,保留最近 3 条完整
  • 大结果持久化:输出超过 30000 字符时自动写入磁盘,AI 收到预览 + 文件路径引用

4.2 内置工具一览

项目为 AI 注册了丰富的工具集:

工具 功能 安全等级
readFile 按字符范围读取文件 低风险
editFile 搜索替换文件内容 中风险
createFile 创建新文件 中风险
deletePath 删除文件或目录 高风险
runCommand 执行 Shell 命令 高风险
readDir 列出目录内容 低风险
loadSkill 加载专业技能 低风险
difyWorkflow 调用 Dify API 中风险
plan 分步任务规划 低风险
subagent 创建子智能体 中风险

4.3 沙箱安全执行

这是项目最重要的设计之一。AI 执行 Shell 命令时,不能直接暴露宿主系统。

沙箱方案采用双层防护:

第一层:应用层权限过滤

typescript 复制代码
const permissionRules: PermissionRule[] = [
  // deny - 直接拒绝的危险操作
  { tool: 'runCommand', behavior: 'deny', content: 'rm -rf', path: null },
  { tool: 'runCommand', behavior: 'deny', content: 'sudo', path: null },
  { tool: 'runCommand', behavior: 'deny', content: 'curl.*\\|.*sh', path: null },
  // allow - 安全的只读操作
  { tool: 'runCommand', behavior: 'allow', content: 'ls', path: null },
  { tool: 'runCommand', behavior: 'allow', content: 'cat', path: null },
  // ask - 需要人工审批
  { tool: 'editFile', behavior: 'ask', content: null, path: null },
];

第二层:系统级沙箱隔离

使用 @anthropic-ai/sandbox-runtime(基于 bubblewrap)进行内核级隔离:

  • 文件系统限制 :只能访问 SANDBOX_DIRAGENTS_DIR,禁止访问 ~/.ssh 等敏感路径
  • 写保护 :禁止覆盖 .env*.pem*.key 等关键文件
  • 网络限制:支持域名白名单/黑名单
  • 深度防护mandatoryDenySearchDepth: 10 防止符号链接逃逸

4.4 Human-in-the-Loop 审批系统

这是让 AI Agent 安全可用的关键。项目设计了四种聊天模式,控制工具执行的严格程度:

模式 读操作 写操作 适用场景
default 需审批 需审批 默认模式,最安全
plan 需审批 拒绝 计划阶段,只读探索
auto 允许 拒绝 自动执行,安全可控
yolo 允许 允许 完全信任,无限制

用户可以通过请求体中的 metadata.mode 切换模式:

json 复制代码
{
  "prompt": "帮我创建一个 React 组件",
  "metadata": { "mode": "yolo" }
}

在前端,当工具需要审批时,消息气泡会显示工具输入参数和批准/拒绝按钮,用户确认后对话自动继续。

4.5 Skills 动态加载系统

Skills 是注入 AI 领域知识的一种方式。每个 Skill 就是一个包含 SKILL.md 的目录,放在 AGENTS_DIR 下:

objectivec 复制代码
agents/
  python-developer/
    SKILL.md          # 含 YAML frontmatter 的 Markdown
  react-optimizer/
    SKILL.md

AI 通过 loadSkill 工具按需加载。系统支持:

  • 自动发现目录下的所有 Skill
  • ZIP 上传/下载/删除 Skill(通过 Skills Module API)
  • 运行时动态注入系统提示词

4.6 子智能体(Subagent)

对于复杂任务,主 Agent 可以创建子 Agent 并行处理:

bash 复制代码
用户: "帮我分析这个项目的代码结构并生成文档"
  → 主 Agent 创建计划
    → 子 Agent A: 探索 src/tools/ 目录
    → 子 Agent B: 探索 src/modules/ 目录
    → 子 Agent C: 阅读 package.json 和配置文件
  → 汇总结果,生成文档

子 Agent 拥有独立的指令和工具集,执行完毕后返回结构化总结给主 Agent。

4.7 OpenAPI / Swagger 文档

从 Zod Schema 自动生成 OpenAPI 3.1 规范文档,访问 /docs 即可查看交互式 API 文档:

typescript 复制代码
new OpenApiRegistryZodExtensions([
  registry.register('ChatRequest', chatRequestSchema),
  registry.register('ChatResponse', chatResponseSchema),
]);

5. 架构设计要点

5.1 工厂模式

Express 应用通过工厂函数创建,方便测试和生命周期管理:

typescript 复制代码
// app.ts
export function createApp() {
  const app = express();
  app.use(helmet());
  app.use(cors());
  app.use(httpLogger);
  app.use(express.json());

  app.get('/health', (_req, res) => {
    res.json(jsonSuccess({ status: 'ok' }));
  });

  app.use('/api', helloModule);
  app.use('/api', chatModule);
  app.use('/api', skillsModule);

  app.use(notFoundHandler);
  app.use(errorHandler);
  return app;
}

5.2 统一的响应与错误码

所有 API 响应遵循统一格式:

typescript 复制代码
interface ApiJsonResponse<T> {
  responseCode: string;  // "000000" 成功,"400001" 校验错误等
  responseMsg: string;   // 描述信息
  data: T;               // 响应数据
}

5.3 环境变量即配置

所有配置通过 .env 加载,启动时用 Zod 校验,缺失环境变量直接阻止启动:

typescript 复制代码
const envSchema = z.object({
  PORT: z.coerce.number().default(3000),
  OPENAI_API_KEY: z.string().min(1),
  OPENAI_BASE_URL: z.string().url(),
  SANDBOX_ENABLED: z.coerce.boolean().default(true),
  // ...更多配置
});

6. Docker 部署

项目提供多阶段 Docker 构建,生产镜像只有 150MB 左右:

dockerfile 复制代码
# 阶段 1: 编译
FROM node:23-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# 阶段 2: 运行
FROM node:23-alpine
RUN apk add --no-cache bash ripgrep bubblewrap socat
COPY --from=builder /app/dist ./dist
CMD ["node", "dist/index.js"]

关键点:因为沙箱依赖 bubblewrap 操作内核命名空间,运行容器时需要添加权限:

bash 复制代码
docker run --cap-add=SYS_ADMIN --cap-add=NET_ADMIN -p 3000:3000 your-image

7. 开发流程与规范

项目制定了清晰的开发规范,适合团队协作:

  • 类型优先 :所有类型先在 schema 中用 Zod 定义,再导出 TypeScript 类型供业务代码使用
  • 工具函数复用 :重复逻辑提取到 src/util/,禁止 Copy-Paste
  • 错误码同步 :新增错误必须同步更新 AppErrorCodeApiResponseCode
  • 模块自包含 :每个模块独立注册到 app.ts,模块间通过接口通信
  • 提交前检查 :每次变更后执行 npm run typecheck 确保类型安全

8. 收获与思考

通过这个练手项目,我对 AI Agent 的理解更深了:

  1. 安全是第一优先级:让 AI 执行 Shell 命令不是难事,难的是确保它不会"闯祸"。双层防护(权限过滤 + 沙箱隔离)是必要的。

  2. Human-in-the-Loop 是实用关键:完全自主的 Agent 在现阶段还是不安全的。好的设计是在"自主"和"可控"之间找到平衡点------这就是四种模式的由来。

  3. Token 管理是工程问题:AI 对话上下文有限,历史工具结果会快速消耗 token。压缩 + 持久化的双策略能有效延长 Agent 的"注意力"。

  4. Skills 是扩展性的核心:通过 Skills 系统,领域知识可以和 Agent 逻辑解耦,这比把所有 prompt 塞进系统提示词要优雅得多。

如果你是 AI Agent 的初学者,想深入理解 Agent 的工作原理,不妨从这个项目开始,亲手跑起来、改一改、加个新工具,收获会很大。


项目地址:express-turbo-monorepo

欢迎 Star、Fork、提 Issue,一起交流 AI Agent 工程实践。

相关推荐
lqqjuly1 小时前
前沿算法深度解析(二)
人工智能·算法·机器学习
Bode_20021 小时前
基于大数据分析的全生命周期质量追溯质量评估体系落地方案
大数据·人工智能
wang09071 小时前
自己动手写一个spring之IOC_2
java·后端·spring
分布式存储与RustFS2 小时前
RustFS S3 Table 开源后,我重新梳理了一下 Iceberg 数据湖的选型思路
人工智能·开源·minio·dpu·rustfs·ai存储·s3 table
ltl2 小时前
推理退化:为什么大模型会输出乱码、死循环和无意义文本
后端
ltl2 小时前
架构视图与文档:C4 模型从入门到实战
后端
excel2 小时前
HLS TS 文件损坏的元凶:Git 提交与拉取
前端
DevOpenClub2 小时前
用 Agent 搭建网页内容采集与结构化处理流水线
人工智能