《NestJS智能体开发》(一):环境与项目搭建

742 阅读8分钟

智能体技术正以前所未有的速度改变着我们的生活和工作方式。从智能家居设备到智能办公系统,从智能客服到智能决策辅助工具,智能体的应用场景日益广泛。

Nest.js作为一种高效、可扩展的Node.js框架,凭借其强大的功能和灵活的架构,成为开发智能体应用的理想选择之一。

无论你是初入Nest.js领域的开发者,还是希望在智能体开发中进一步提升技能的资深工程师,本系列文章都将为你提供清晰、实用的指导。

环境搭建

安装 Volta

Volta是一个无忧的JavaScript工具管理器

curl https://get.volta.sh | bash

安装 Node

volta install node@v22.12.0

安装 pnpm

volta install pnpm@9.15.1

安装 pm2

volta install pm2@5.4.3

检查环境

$ volta list
⚡️ Currently active tools:

    Node: v22.12.0 (default)
    Tool binaries available:
        pm2, pm2-dev, pm2-docker, pm2-runtime (default)
        pnpm, pnpx (default)

See options for more detailed reports by running `volta list --help`.

创建Nest工程

创建一个文件夹 nest-agent 并使用 pnpm 初始化

mkdir nest-agent
cd nest-agent
pnpm init

nest-agent 创建源码目录和入口文件

mkdir src
cd src
touch main.ts
// src/main.ts
async function main() {}
main();

安装和配置 TypeScript

pnpm add typescript -D
pnpm add @types/node -D
touch tsconfig.json
touch tsconfig.build.json
// tsconfig.json
{
	"compilerOptions": {
		"module": "commonjs",
		"moduleResolution": "Node",
		"declaration": true,
		"removeComments": true,
		"emitDecoratorMetadata": true,
		"experimentalDecorators": true,
		"allowSyntheticDefaultImports": true,
		"sourceMap": true,
		"incremental": true,
		"strictNullChecks": false,
		"noImplicitAny": false,
		"strictBindCallApply": false,
		"forceConsistentCasingInFileNames": false,
		"noFallthroughCasesInSwitch": false,
		"jsx": "react-jsx",
		"outDir": "dist",
		"rootDir": "src",
		"target": "ES2021",
		"lib": ["DOM", "DOM.Iterable", "ES2021", "ES2022.Error"],
		"noErrorTruncation": true,
		"strict": true,
		"skipLibCheck": true,
		"baseUrl": ".",
		"paths": {
			"@/*": ["src/*"]
		}
	},
	"include": ["src/**/*"]
}
// tsconfig.build.json
{
	"extends": "./tsconfig.json",
	"exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
}

安装和配置 Nest

pnpm add @nestjs/schematics -D
touch nest-cli.json
// nest-cli.json
{
    "$schema": "https://json.schemastore.org/nest-cli",
    "collection": "@nestjs/schematics",
    "sourceRoot": "src", // 源码目录
    "entryFile": "main", // 入口文件
    "compilerOptions": {
        "deleteOutDir": true
    }
}

使用SWC优化编译

pnpm add @swc/cli -D
pnpm add @swc/core -D
// nest-cli.json
{
    "$schema": "https://json.schemastore.org/nest-cli",
    "collection": "@nestjs/schematics",
    "sourceRoot": "src",
    "entryFile": "main",
    "compilerOptions": {
        "builder": "swc", // 使用 SWC 编译
        "typeCheck": true, // 强制类型检查
        "deleteOutDir": true
    }
}

配置代码格式化

pnpm add @biomejs/biome -D
// biome.json
{
	"$schema": "https://biomejs.dev/schemas/1.9.3/schema.json",
	"vcs": {
		"enabled": false,
		"clientKind": "git",
		"useIgnoreFile": false
	},
	"files": {
		"ignoreUnknown": false,
		"ignore": ["node_modules/**", "dist/**", "build/**"]
	},
	"formatter": {
		"enabled": true,
		"indentStyle": "tab"
	},
	"organizeImports": {
		"enabled": true
	},
	"linter": {
		"enabled": true,
		"rules": {
			"recommended": true,
			"suspicious": {
				"noExplicitAny": "off",
				"noAsyncPromiseExecutor": "off",
				"noAssignInExpressions": "off"
			},
			"style": {
				"noUselessElse": "off",
				"useImportType": "off",
				"noParameterAssign": "off",
				"noInferrableTypes": "off",
				"noNonNullAssertion": "off",
				"useNodejsImportProtocol": "off"
			},
			"performance": {
				"noAccumulatingSpread": "off"
			},
			"complexity": {
				"noThisInStatic": "off",
				"noBannedTypes": "off",
				"noForEach": "off",
				"noStaticOnlyClass": "off"
			},
			"correctness": {
				"useExhaustiveDependencies": "off",
				"noConstructorReturn": "off"
			},
			"a11y": {
				"useKeyWithClickEvents": "off",
				"noSvgWithoutTitle": "off"
			}
		}
	},
	"javascript": {
		"formatter": {
			"quoteStyle": "double",
			"semicolons": "always"
		},
		"parser": {
			"unsafeParameterDecoratorsEnabled": true
		}
	}
}

配置启动脚本

// package.json
{
  "name": "nest-agent",
  "version": "1.0.0",
  "scripts": {
    "dev": "nest start --watch",
    "start": "node dist/main",
    "build": "nest build",
    "format": "pnpm biome format --write ."
  },
  ...
}

安装核心依赖

pnpm add @nestjs/core
pnpm add @nestjs/common
pnpm add @nestjs/platform-express
pnpm add rxjs
pnpm add reflect-metadata

创建根模块

image.png

// src/app.module.ts
import { Module } from "@nestjs/common";

@Module()
export class AppModule {}

启动Nest框架

// src/main.ts
import { NestFactory } from "@nestjs/core";
import { NestExpressApplication } from "@nestjs/platform-express";
import { AppModule } from "./app.module";

async function main() {
    const app = await NestFactory.create<NestExpressApplication>(AppModule);
    await app.listen(3000);
}

main()

源码结构

src/
├── config/                     # 配置文件
├── domain/                     # 领域层
│   ├── entities/               # 实体类
│   ├── value-objects/          # 值对象
│   ├── interfaces/             # 领域接口
│   ├── aggregates/             # 聚合根
│   ├── repositories/           # 仓库接口
│   └── domain-events/          # 领域事件
├── application/                # 应用层
│   ├── web/                    # Web 模块
│   │   ├── services/           # Web 应用服务
│   │   ├── dtos/               # Web 数据传输对象 (DTOs)
│   │   └── web.module.ts       # Web 模块定义
│   ├── admin/                  # Admin 模块
│   │   ├── services/           # Admin 应用服务
│   │   ├── dtos/               # Admin 数据传输对象 (DTOs)
│   │   └── admin.module.ts     # Admin 模块定义
├── infrastructure/             # 基础设施层
│   ├── databases/              # 数据库相关
│   ├── repositories/           # 仓库实现
│   ├── gateways/               # 外部服务网关
│   ├── controllers/            # 控制器
│   │   ├── web/                # Web 控制器
│   │   ├── admin/              # Admin 控制器
│   │   └── common/             # 公共控制器
│   ├── pipes/                  # 管道
│   ├── guards/                 # 守卫
│   ├── filters/                # 过滤器
│   ├── interceptors/           # 拦截器
│   └── middlewares/            # 中间件
├── shared/                     # 共享层
│   ├── utils/                  # 自定义工具
│   ├── constants/              # 自定义常量
│   ├── exceptions/             # 自定义异常
│   └── modules/                # 共享模块(如Kafka、Manticore等)
├── app.module.ts               # 根模块
└── main.ts                     # 入口文件

以下是对上述文件夹结构的详细介绍,包括每个部分的职责和作用:

1. 领域层(Domain Layer)

领域层是整个应用的核心,包含了业务逻辑和业务规则。它独立于任何外部系统,专注于定义业务实体、值对象、聚合根、仓库接口和领域事件等。

entities/ :实体类

  • 职责:定义业务实体,这些实体具有唯一标识符(ID),并且其生命周期需要被跟踪。
  • 示例UserOrder 等实体类。

value-objects/ :值对象

  • 职责:定义值对象,这些对象没有唯一标识符,其价值在于它们的属性值。
  • 示例MoneyAddress 等值对象。

interfaces/ :领域接口

  • 职责:定义领域层需要的接口,这些接口通常由基础设施层实现。
  • 示例IUserRepository(用户仓库接口)。

aggregates/ :聚合根

  • 职责:定义聚合根,聚合根是领域模型中的一组相关实体和值对象的根。
  • 示例OrderAggregate(订单聚合根)。

repositories/ :仓库接口

  • 职责:定义数据访问的接口,这些接口由基础设施层实现。
  • 示例IUserRepository(用户仓库接口)。

domain-events/ :领域事件

  • 职责:定义领域事件,这些事件在业务逻辑中被触发,用于通知其他部分业务逻辑的完成。
  • 示例UserCreatedEvent(用户创建事件)。

2. 应用层(Application Layer)

应用层是领域层和基础设施层之间的桥梁,负责协调领域层的业务逻辑和基础设施层的资源访问。它封装了领域层的业务逻辑,并通过领域层的接口与基础设施层进行交互。

web/ :Web 模块

  • 职责:处理与 Web 相关的业务逻辑。

  • 内容

    • services/ :Web 应用服务,封装了 Web 相关的业务逻辑。
    • dtos/ :Web 数据传输对象(DTOs),用于在应用层和基础设施层之间传递数据。
    • web.module.ts:Web 模块的定义文件,用于组织和管理 Web 模块的依赖和服务。

admin/ :Admin 模块

  • 职责:处理与 Admin 相关的业务逻辑。

  • 内容

    • services/ :Admin 应用服务,封装了 Admin 相关的业务逻辑。
    • dtos/ :Admin 数据传输对象(DTOs),用于在应用层和基础设施层之间传递数据。
    • admin.module.ts:Admin 模块的定义文件,用于组织和管理 Admin 模块的依赖和服务。

3. 基础设施层(Infrastructure Layer)

基础设施层负责处理与外部系统的交互,如数据库操作、外部服务调用、文件存储等。它实现了领域层定义的接口,并为应用层提供具体的实现。

databases/ :数据库相关

  • 职责:包含数据库相关的实现,如数据库连接、迁移脚本等。

repositories/ :仓库实现

  • 职责:实现领域层定义的仓库接口,提供数据访问的具体实现。
  • 示例UserRepository(用户仓库实现)。

gateways/ :外部服务网关

  • 职责:实现外部服务的调用,如调用第三方 API、消息队列等。

controllers/ :控制器

  • 职责:处理 HTTP 请求和响应,是应用的入口点。

  • 内容

    • web/ :Web 控制器,处理 Web 相关的 HTTP 请求。
    • admin/ :Admin 控制器,处理 Admin 相关的 HTTP 请求。
    • common/ :公共控制器,处理通用的 HTTP 请求。

pipes/ :管道

  • 职责:实现数据验证和转换的管道。

guards/ :守卫

  • 职责:实现权限验证和访问控制的守卫。

filters/ :过滤器

  • 职责:实现异常处理和请求过滤的过滤器。

interceptors/ :拦截器

  • 职责:实现请求和响应的拦截器,用于日志记录、性能监控等。

middlewares/ :中间件

  • 职责:实现中间件,用于处理 HTTP 请求的生命周期。

4. 共享层(Shared Layer)

共享层包含项目中通用的工具函数、常量、异常处理等,以及一些可以被多个模块共享的基础设施模块。

utils/ :自定义工具

  • 职责:提供通用的工具函数。

constants/ :自定义常量

  • 职责:定义项目中使用的常量。

exceptions/ :自定义异常

  • 职责:定义项目中使用的自定义异常。

modules/ :共享模块

  • 职责:包含可以被多个模块共享的基础设施模块,如 Kafka、Manticore 等。

5. 根模块(App Module)

  • 职责:定义应用的根模块,组织和管理整个应用的依赖关系。
  • 文件app.module.ts

6. 入口文件(Main File)

  • 职责:应用的入口文件,用于启动应用。
  • 文件main.ts

PM2部署

创建pm2部署脚本

touch ecosystem.config.js
// ecosystem.config.js
module.exports = {
    apps: [
        {
            name: "agent",
            script: "./dist/main.js",
            exec_mode: "cluster_mode", // 集群模式
            instances: 4, // 4个进程
            env: {
                NODE_ENV: "production"
            }
        }
    ]
}

编译Nest项目

pnpm build

pm2启动项目

pm2 start

远程部署

// ecosystem.config.js
module.exports = {
	apps: [
		{
			name: "agent",			
			script: "./dist/main.js",
			exec_mode: "cluster_mode",
			instances: 4, // 根据你的服务器CPU核心数设置启动的进程数
			env: {
				NODE_ENV: "production",
			},
		},
	],
	deploy: {
		agent: {
			user: "root",
			host: [“你的服务器IP”],
			ref: "origin/main",
			repo: "你的仓库地址",
			path: "/root/code/nest-agent",
                   // 确保你的服务上已经安装了相关环境(如: `git`、`node`、 `pnpm`、 `pm2`)
			"post-deploy": "/root/.volta/bin/pnpm install && /root/.volta/bin/pnpm build", 
		},
	},
};

配置服务器

ssh-copy-id root@你的服务器IP

配置pm2服务器环境

pm2 deploy agent setup

执行远程部署

pm2 deploy agent && pm2 deploy agent exec \"/root/.volta/bin/pm2 start --only agent\"

Docker部署

创建Dockerfile

# 使用 Node.js 22 Alpine 镜像作为构建阶段的基础镜像
FROM node:22-alpine AS builder  
# 全局安装 pnpm 作为包管理器
RUN npm install -g pnpm  
# 设置工作目录为 /app
WORKDIR /app  
# 将 package.json 和 pnpm-lock.yaml 复制到工作目录
COPY package.json pnpm-lock.yaml ./ 
# 安装项目依赖,确保依赖版本固定 
RUN pnpm install --frozen-lockfile  
# 将项目的所有文件复制到工作目录
COPY . .  
# 运行构建命令
RUN pnpm build  

# 使用 Node.js 22 Alpine 镜像作为运行阶段的基础镜像
FROM node:22-alpine  
# 全局安装 pnpm
RUN npm install -g pnpm  
# 设置工作目录为 /app
WORKDIR /app  
# 从构建阶段复制构建输出
COPY --from=builder /app/dist ./dist  
# 复制 package.json 和 pnpm-lock.yaml
COPY --from=builder /app/package.json /app/pnpm-lock.yaml ./  
# 安装生产环境依赖,确保只安装生产环境所需的包
RUN pnpm install --frozen-lockfile --prod  
# 设置环境变量 NODE_ENV 为 production
ENV NODE_ENV=production  
# 暴露容器的 3000 端口
EXPOSE 3000  
# 启动应用的命令
CMD ["pnpm", "start"]

构建镜像

docker build -t nest-agent .

启动容器

docker run -d -p 3000:3000 -e NODE_ENV=development --name my-agent nest-agent

查看日志

docker logs my-agent

Kubernetes部署

安装Tilt

curl -fsSL https://raw.githubusercontent.com/tilt-dev/tilt/master/scripts/install.sh | bash

有关Tilt的更多介绍可查看服务编排即代码

k8s文件夹下创建deployment-dev.yaml编排文件

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nest-agent
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nest-agent
  template:
    metadata:
      labels:
        app: nest-agent
    spec:
      containers:
      - name: nest-agent
        image: my-agent:dev
        imagePullPolicy: Always
        ports:
        - containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
  name: nest-agent-service
spec:
  selector:
    app: nest-agent
  ports:
    - protocol: TCP
      port: 80
      targetPort: 3000
  type: LoadBalancer

创建Tiltfile

# -*- mode: Python -*-
env = os.getenv('ENV', 'dev')

print("ENV:{}".format(env))

# 根据环境变量调整 Docker 镜像名称和标签
image_name = "my-agent:{}".format(env)

# 构建 Docker 镜像
docker_build(image_name, '.')

# 根据环境变量加载不同的 Kubernetes 部署文件
k8s_yaml("./k8s/deployment-{}.yaml".format(env))

# 定义 Kubernetes 资源
k8s_resource('nest-agent')

启动服务

tilt up --context orbstack # 由于笔者使用 orbstack 在本机启动 k8s 所以这里传递 --context orbstack

访问http://localhost:10350查看部署情况

image.png

每次代码更新后Tilt会自动构建镜像并重新部署服务