介绍一个基于 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 命令、加载专业技能、甚至创建子智能体来并行处理任务。
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_DIR和AGENTS_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 - 错误码同步 :新增错误必须同步更新
AppErrorCode和ApiResponseCode - 模块自包含 :每个模块独立注册到
app.ts,模块间通过接口通信 - 提交前检查 :每次变更后执行
npm run typecheck确保类型安全
8. 收获与思考
通过这个练手项目,我对 AI Agent 的理解更深了:
-
安全是第一优先级:让 AI 执行 Shell 命令不是难事,难的是确保它不会"闯祸"。双层防护(权限过滤 + 沙箱隔离)是必要的。
-
Human-in-the-Loop 是实用关键:完全自主的 Agent 在现阶段还是不安全的。好的设计是在"自主"和"可控"之间找到平衡点------这就是四种模式的由来。
-
Token 管理是工程问题:AI 对话上下文有限,历史工具结果会快速消耗 token。压缩 + 持久化的双策略能有效延长 Agent 的"注意力"。
-
Skills 是扩展性的核心:通过 Skills 系统,领域知识可以和 Agent 逻辑解耦,这比把所有 prompt 塞进系统提示词要优雅得多。
如果你是 AI Agent 的初学者,想深入理解 Agent 的工作原理,不妨从这个项目开始,亲手跑起来、改一改、加个新工具,收获会很大。
欢迎 Star、Fork、提 Issue,一起交流 AI Agent 工程实践。