从零构建 AI Agent:完整指南

一篇完整的 AI Agent 构建教程,涵盖准备工作、核心代码实现、以及部署上线的全流程。

作者:bani
aiagenttutoriallangchainopenaideployment

从零构建 AI Agent:完整指南

本文将手把手教你如何构建一个功能完整的 AI Agent,从环境准备到最终部署上线,一步步带你掌握智能体开发的核心技术。

目录

  1. 什么是 AI Agent
  2. 准备工作
  3. 核心架构设计
  4. 代码实现
  5. 部署上线
  6. 总结与展望

什么是 AI Agent

AI Agent(智能体)是一种能够自主感知环境、做出决策并执行行动的 AI 系统。与传统的对话式 AI 不同,Agent 具备以下核心能力:

能力描述示例
感知理解用户输入和环境状态解析自然语言、读取文件
推理基于上下文做出决策选择合适的工具、规划任务步骤
行动执行具体操作调用 API、执行代码、操作文件
学习从反馈中改进记忆历史对话、优化策略

Agent vs Chatbot

传统 Chatbot: 用户输入 → LLM处理 → 文本输出

AI Agent:     用户输入 → LLM推理 → 工具调用 → 执行结果 → LLM总结 → 输出
                           ↑                            ↓
                           └────────── 反馈循环 ─────────┘

准备工作

1. 环境配置

系统要求

  • Node.js >= 18.0 或 Python >= 3.9
  • 稳定的网络连接(用于调用 LLM API)
  • 至少 4GB 可用内存

创建项目目录

# 创建项目
mkdir my-ai-agent
cd my-ai-agent
 
# 初始化 Node.js 项目
npm init -y
 
# 或使用 Python
python -m venv venv
source venv/bin/activate  # macOS/Linux
# venv\Scripts\activate   # Windows

2. 安装依赖

Node.js 版本(推荐)

npm install openai langchain @langchain/openai zod dotenv
npm install -D typescript @types/node ts-node

Python 版本

pip install openai langchain langchain-openai python-dotenv

3. 获取 API Key

目前主流的 LLM 提供商:

提供商获取地址特点
OpenAIplatform.openai.comGPT-4o,能力最强
Anthropicconsole.anthropic.comClaude,推理能力强
DeepSeekplatform.deepseek.com高性价比,中文友好
Googleaistudio.google.comGemini,多模态强

配置环境变量

创建 .env 文件:

# OpenAI
OPENAI_API_KEY=sk-your-openai-key
 
# 或 DeepSeek (兼容 OpenAI 接口)
OPENAI_API_KEY=sk-your-deepseek-key
OPENAI_BASE_URL=https://api.deepseek.com

核心架构设计

一个完整的 Agent 系统包含以下组件:

┌─────────────────────────────────────────────────────────────┐
│                        AI Agent 架构                         │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌──────────┐    ┌─────────────┐    ┌──────────────────┐   │
│  │  用户输入  │───▶│   控制器     │───▶│    LLM (大脑)    │   │
│  └──────────┘    │  Controller │    │  GPT-4 / Claude  │   │
│                  └──────┬──────┘    └────────┬─────────┘   │
│                         │                    │              │
│                         ▼                    ▼              │
│               ┌─────────────────┐   ┌────────────────┐     │
│               │   记忆系统       │   │   工具集合      │     │
│               │  Memory Store   │   │  Tool Registry │     │
│               └─────────────────┘   └────────────────┘     │
│                                              │              │
│                         ┌────────────────────┴───────┐     │
│                         ▼            ▼           ▼   ▼     │
│                    ┌────────┐  ┌────────┐  ┌────────┐      │
│                    │搜索工具 │  │计算工具 │  │文件工具 │      │
│                    └────────┘  └────────┘  └────────┘      │
│                                                              │
└─────────────────────────────────────────────────────────────┘

核心组件说明

  1. Controller(控制器):协调各组件,处理 Agent 的执行循环
  2. LLM(大语言模型):Agent 的"大脑",负责理解和决策
  3. Tools(工具集):Agent 可调用的能力扩展
  4. Memory(记忆):存储对话历史和上下文

代码实现

项目结构

my-ai-agent/
├── src/
│   ├── index.ts          # 入口文件
│   ├── agent.ts          # Agent 核心类
│   ├── tools/
│   │   ├── index.ts      # 工具注册
│   │   ├── search.ts     # 搜索工具
│   │   ├── calculator.ts # 计算工具
│   │   └── file.ts       # 文件工具
│   ├── memory/
│   │   └── store.ts      # 记忆存储
│   └── config.ts         # 配置文件
├── .env
├── package.json
└── tsconfig.json

Step 1: 配置文件

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "esModuleInterop": true,
    "outDir": "dist",
    "rootDir": "src"
  },
  "include": ["src/**/*"]
}

src/config.ts

import { config } from "dotenv";
config();
 
export const CONFIG = {
  openai: {
    apiKey: process.env.OPENAI_API_KEY!,
    baseURL: process.env.OPENAI_BASE_URL || "https://api.openai.com/v1",
    model: process.env.OPENAI_MODEL || "gpt-4o",
  },
  agent: {
    maxIterations: 10, // 最大执行循环次数
    verbose: true, // 是否输出详细日志
  },
} as const;

Step 2: 定义工具

src/tools/calculator.ts

import { z } from "zod";
 
// 工具定义接口
export interface Tool {
  name: string;
  description: string;
  parameters: z.ZodType<any>;
  execute: (params: any) => Promise<string>;
}
 
// 计算器工具
export const calculatorTool: Tool = {
  name: "calculator",
  description: "执行数学计算。支持加减乘除、幂运算、三角函数等。",
  parameters: z.object({
    expression: z.string().describe("要计算的数学表达式,例如: 2 + 3 * 4"),
  }),
  execute: async ({ expression }) => {
    try {
      // 安全的数学表达式计算
      const sanitized = expression.replace(/[^0-9+\-*/().%\s]/g, "");
      const result = Function(`"use strict"; return (${sanitized})`)();
      return `计算结果: ${expression} = ${result}`;
    } catch (error) {
      return `计算错误: ${(error as Error).message}`;
    }
  },
};

src/tools/search.ts

import { z } from "zod";
import { Tool } from "./calculator";
 
// 模拟搜索工具(实际项目中可接入真实搜索 API)
export const searchTool: Tool = {
  name: "web_search",
  description: "搜索网络获取实时信息。用于回答需要最新数据的问题。",
  parameters: z.object({
    query: z.string().describe("搜索关键词"),
  }),
  execute: async ({ query }) => {
    // 这里模拟搜索结果,实际可接入 Serper/Tavily 等搜索 API
    console.log(`🔍 正在搜索: ${query}`);
 
    // 模拟网络延迟
    await new Promise((r) => setTimeout(r, 500));
 
    return `搜索"${query}"的结果:
1. 相关文章: 《${query}详解》- 介绍了核心概念和应用场景
2. 官方文档: 提供了完整的使用指南
3. 社区讨论: 开发者分享的最佳实践`;
  },
};

src/tools/file.ts

import { z } from "zod";
import * as fs from "fs/promises";
import * as path from "path";
import { Tool } from "./calculator";
 
// 文件读取工具
export const readFileTool: Tool = {
  name: "read_file",
  description: "读取指定路径的文件内容",
  parameters: z.object({
    filePath: z.string().describe("文件的相对或绝对路径"),
  }),
  execute: async ({ filePath }) => {
    try {
      const content = await fs.readFile(filePath, "utf-8");
      return `文件内容:\n${content}`;
    } catch (error) {
      return `读取文件失败: ${(error as Error).message}`;
    }
  },
};
 
// 文件写入工具
export const writeFileTool: Tool = {
  name: "write_file",
  description: "将内容写入指定文件",
  parameters: z.object({
    filePath: z.string().describe("目标文件路径"),
    content: z.string().describe("要写入的内容"),
  }),
  execute: async ({ filePath, content }) => {
    try {
      await fs.mkdir(path.dirname(filePath), { recursive: true });
      await fs.writeFile(filePath, content, "utf-8");
      return `✅ 文件已成功写入: ${filePath}`;
    } catch (error) {
      return `写入文件失败: ${(error as Error).message}`;
    }
  },
};

src/tools/index.ts

import { Tool } from "./calculator";
import { calculatorTool } from "./calculator";
import { searchTool } from "./search";
import { readFileTool, writeFileTool } from "./file";
 
// 工具注册表
export const tools: Tool[] = [
  calculatorTool,
  searchTool,
  readFileTool,
  writeFileTool,
];
 
// 按名称获取工具
export function getToolByName(name: string): Tool | undefined {
  return tools.find((t) => t.name === name);
}
 
// 生成工具描述(用于 System Prompt)
export function generateToolDescriptions(): string {
  return tools.map((t) => `- ${t.name}: ${t.description}`).join("\n");
}

Step 3: 记忆系统

src/memory/store.ts

interface Message {
  role: "user" | "assistant" | "system" | "tool";
  content: string;
  toolCallId?: string;
  name?: string;
}
 
export class MemoryStore {
  private messages: Message[] = [];
  private maxMessages: number = 50;
 
  constructor(systemPrompt?: string) {
    if (systemPrompt) {
      this.messages.push({ role: "system", content: systemPrompt });
    }
  }
 
  /**
   * 添加消息到记忆
   */
  add(message: Message): void {
    this.messages.push(message);
    // 保持记忆在限制范围内(保留 system prompt)
    if (this.messages.length > this.maxMessages) {
      const systemMsg = this.messages.find((m) => m.role === "system");
      this.messages = systemMsg
        ? [systemMsg, ...this.messages.slice(-this.maxMessages + 1)]
        : this.messages.slice(-this.maxMessages);
    }
  }
 
  /**
   * 获取所有消息
   */
  getAll(): Message[] {
    return [...this.messages];
  }
 
  /**
   * 获取最近 N 条消息
   */
  getRecent(n: number): Message[] {
    return this.messages.slice(-n);
  }
 
  /**
   * 清空记忆(保留 system prompt)
   */
  clear(): void {
    const systemMsg = this.messages.find((m) => m.role === "system");
    this.messages = systemMsg ? [systemMsg] : [];
  }
 
  /**
   * 获取对话摘要(用于压缩长对话)
   */
  getSummary(): string {
    const userMsgs = this.messages.filter((m) => m.role === "user");
    return `对话历史包含 ${userMsgs.length} 条用户消息`;
  }
}

Step 4: Agent 核心实现

src/agent.ts

import OpenAI from "openai";
import { CONFIG } from "./config";
import { tools, getToolByName, generateToolDescriptions } from "./tools";
import { MemoryStore } from "./memory/store";
import { Tool } from "./tools/calculator";
 
// Agent 系统提示词
const SYSTEM_PROMPT = `你是一个智能助手,能够帮助用户完成各种任务。
 
你可以使用以下工具:
${generateToolDescriptions()}
 
使用工具时的注意事项:
1. 仔细分析用户需求,选择合适的工具
2. 如果需要多步操作,逐步执行并汇总结果
3. 如果无法完成任务,诚实说明原因
4. 计算类问题务必使用 calculator 工具,不要自己计算
 
始终用中文回复用户。`;
 
export class Agent {
  private client: OpenAI;
  private memory: MemoryStore;
  private maxIterations: number;
  private verbose: boolean;
 
  constructor() {
    this.client = new OpenAI({
      apiKey: CONFIG.openai.apiKey,
      baseURL: CONFIG.openai.baseURL,
    });
    this.memory = new MemoryStore(SYSTEM_PROMPT);
    this.maxIterations = CONFIG.agent.maxIterations;
    this.verbose = CONFIG.agent.verbose;
  }
 
  /**
   * 将自定义工具格式转换为 OpenAI 格式
   */
  private formatToolsForOpenAI(): OpenAI.ChatCompletionTool[] {
    return tools.map((tool) => ({
      type: "function" as const,
      function: {
        name: tool.name,
        description: tool.description,
        parameters: this.zodToJsonSchema(tool.parameters),
      },
    }));
  }
 
  /**
   * 简化的 Zod Schema 转 JSON Schema
   */
  private zodToJsonSchema(schema: any): object {
    // 简化处理,实际项目建议使用 zod-to-json-schema
    return {
      type: "object",
      properties: {
        expression: { type: "string" },
        query: { type: "string" },
        filePath: { type: "string" },
        content: { type: "string" },
      },
    };
  }
 
  /**
   * 执行工具调用
   */
  private async executeTool(
    name: string,
    args: Record<string, any>
  ): Promise<string> {
    const tool = getToolByName(name);
    if (!tool) {
      return `错误: 未找到工具 "${name}"`;
    }
 
    if (this.verbose) {
      console.log(`🔧 调用工具: ${name}`);
      console.log(`   参数: ${JSON.stringify(args)}`);
    }
 
    try {
      const result = await tool.execute(args);
      if (this.verbose) {
        console.log(`   结果: ${result.slice(0, 100)}...`);
      }
      return result;
    } catch (error) {
      return `工具执行错误: ${(error as Error).message}`;
    }
  }
 
  /**
   * Agent 主循环
   */
  async run(userMessage: string): Promise<string> {
    // 添加用户消息到记忆
    this.memory.add({ role: "user", content: userMessage });
 
    let iterations = 0;
 
    while (iterations < this.maxIterations) {
      iterations++;
 
      if (this.verbose) {
        console.log(`\n--- 迭代 ${iterations} ---`);
      }
 
      // 调用 LLM
      const response = await this.client.chat.completions.create({
        model: CONFIG.openai.model,
        messages: this.memory.getAll() as any,
        tools: this.formatToolsForOpenAI(),
        tool_choice: "auto",
      });
 
      const message = response.choices[0].message;
 
      // 如果没有工具调用,返回最终回复
      if (!message.tool_calls || message.tool_calls.length === 0) {
        const content = message.content || "抱歉,我无法处理这个请求。";
        this.memory.add({ role: "assistant", content });
        return content;
      }
 
      // 记录 assistant 消息(包含工具调用)
      this.memory.add({
        role: "assistant",
        content: message.content || "",
      });
 
      // 执行所有工具调用
      for (const toolCall of message.tool_calls) {
        const args = JSON.parse(toolCall.function.arguments);
        const result = await this.executeTool(toolCall.function.name, args);
 
        // 记录工具执行结果
        this.memory.add({
          role: "tool",
          content: result,
          toolCallId: toolCall.id,
          name: toolCall.function.name,
        });
      }
    }
 
    return "达到最大迭代次数,任务未完成。";
  }
 
  /**
   * 重置对话
   */
  reset(): void {
    this.memory.clear();
    console.log("🔄 对话已重置");
  }
}

Step 5: 入口文件

src/index.ts

import * as readline from "readline";
import { Agent } from "./agent";
 
async function main() {
  console.log("🤖 AI Agent 启动成功!");
  console.log("输入你的问题,输入 'quit' 退出,输入 'reset' 重置对话\n");
 
  const agent = new Agent();
 
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });
 
  const prompt = () => {
    rl.question("You: ", async (input) => {
      const trimmed = input.trim();
 
      if (trimmed.toLowerCase() === "quit") {
        console.log("👋 再见!");
        rl.close();
        return;
      }
 
      if (trimmed.toLowerCase() === "reset") {
        agent.reset();
        prompt();
        return;
      }
 
      if (!trimmed) {
        prompt();
        return;
      }
 
      try {
        const response = await agent.run(trimmed);
        console.log(`\nAgent: ${response}\n`);
      } catch (error) {
        console.error(`错误: ${(error as Error).message}`);
      }
 
      prompt();
    });
  };
 
  prompt();
}
 
main();

Step 6: 运行测试

# 编译并运行
npx ts-node src/index.ts
 
# 或添加到 package.json scripts
npm run dev

测试对话示例

🤖 AI Agent 启动成功!
输入你的问题,输入 'quit' 退出,输入 'reset' 重置对话

You: 帮我计算 (15 + 27) * 3 - 18 / 2

--- 迭代 1 ---
🔧 调用工具: calculator
   参数: {"expression":"(15 + 27) * 3 - 18 / 2"}
   结果: 计算结果: (15 + 27) * 3 - 18 / 2 = 117...

--- 迭代 2 ---

Agent: 计算结果是 **117**

计算过程:
1. 15 + 27 = 42
2. 42 × 3 = 126
3. 18 ÷ 2 = 9
4. 126 - 9 = 117

You: 搜索一下 "LangChain 入门教程"

--- 迭代 1 ---
🔍 正在搜索: LangChain 入门教程
🔧 调用工具: web_search
   参数: {"query":"LangChain 入门教程"}
   结果: 搜索"LangChain 入门教程"的结果:...

Agent: 我找到了以下关于 LangChain 入门教程的资源:

1. **《LangChain 入门教程详解》** - 详细介绍了核心概念和应用场景
2. **LangChain 官方文档** - 提供了完整的使用指南
3. **社区讨论帖** - 开发者分享的最佳实践

建议你从官方文档开始学习!

部署上线

方案一:Docker 容器化部署

Dockerfile

FROM node:20-alpine
 
WORKDIR /app
 
# 复制依赖文件
COPY package*.json ./
 
# 安装依赖
RUN npm ci --only=production
 
# 复制源码
COPY . .
 
# 编译 TypeScript
RUN npm run build
 
# 设置环境变量
ENV NODE_ENV=production
 
# 启动应用
CMD ["node", "dist/index.js"]

docker-compose.yml

version: "3.8"
 
services:
  ai-agent:
    build: .
    container_name: my-ai-agent
    restart: unless-stopped
    environment:
      - OPENAI_API_KEY=${OPENAI_API_KEY}
      - OPENAI_BASE_URL=${OPENAI_BASE_URL}
    volumes:
      - ./data:/app/data # 持久化数据
    ports:
      - "3000:3000" # 如果添加了 HTTP 服务

部署命令

# 构建镜像
docker build -t my-ai-agent .
 
# 运行容器
docker run -d \
  --name ai-agent \
  --env-file .env \
  my-ai-agent
 
# 使用 docker-compose
docker-compose up -d

方案二:添加 HTTP API 服务

为了让 Agent 能通过 API 调用,添加一个 HTTP 服务层。

安装额外依赖

npm install express cors
npm install -D @types/express @types/cors

src/server.ts

import express from "express";
import cors from "cors";
import { Agent } from "./agent";
 
const app = express();
const port = process.env.PORT || 3000;
 
app.use(cors());
app.use(express.json());
 
// Agent 实例池(生产环境建议使用 Redis 存储会话)
const agents = new Map<string, Agent>();
 
function getAgent(sessionId: string): Agent {
  if (!agents.has(sessionId)) {
    agents.set(sessionId, new Agent());
  }
  return agents.get(sessionId)!;
}
 
// 健康检查
app.get("/health", (req, res) => {
  res.json({ status: "ok", timestamp: new Date().toISOString() });
});
 
// 对话接口
app.post("/chat", async (req, res) => {
  const { message, sessionId = "default" } = req.body;
 
  if (!message) {
    return res.status(400).json({ error: "message is required" });
  }
 
  try {
    const agent = getAgent(sessionId);
    const response = await agent.run(message);
    res.json({ response, sessionId });
  } catch (error) {
    res.status(500).json({ error: (error as Error).message });
  }
});
 
// 重置会话
app.post("/reset", (req, res) => {
  const { sessionId = "default" } = req.body;
  const agent = getAgent(sessionId);
  agent.reset();
  res.json({ message: "Session reset", sessionId });
});
 
app.listen(port, () => {
  console.log(`🚀 Agent API 服务已启动: http://localhost:${port}`);
});

更新 package.json

{
  "scripts": {
    "dev": "ts-node src/index.ts",
    "server": "ts-node src/server.ts",
    "build": "tsc",
    "start": "node dist/server.js"
  }
}

方案三:部署到云平台

Vercel(Serverless)

创建 api/chat.ts

import { Agent } from "../src/agent";
 
export const config = {
  maxDuration: 60, // 最大执行时间 60 秒
};
 
export default async function handler(req: any, res: any) {
  if (req.method !== "POST") {
    return res.status(405).json({ error: "Method not allowed" });
  }
 
  const { message } = req.body;
  const agent = new Agent();
 
  try {
    const response = await agent.run(message);
    res.json({ response });
  } catch (error) {
    res.status(500).json({ error: (error as Error).message });
  }
}

Railway/Render 部署

  1. 连接 GitHub 仓库
  2. 设置环境变量(OPENAI_API_KEY 等)
  3. 设置启动命令:npm run start
  4. 自动部署

方案四:生产环境最佳实践

添加日志和监控

// src/utils/logger.ts
import pino from "pino";
 
export const logger = pino({
  level: process.env.LOG_LEVEL || "info",
  transport: {
    target: "pino-pretty",
    options: { colorize: true },
  },
});

添加速率限制

import rateLimit from "express-rate-limit";
 
const limiter = rateLimit({
  windowMs: 60 * 1000, // 1 分钟
  max: 20, // 每分钟最多 20 个请求
  message: { error: "请求过于频繁,请稍后再试" },
});
 
app.use("/chat", limiter);

添加输入验证

import { z } from "zod";
 
const chatSchema = z.object({
  message: z.string().min(1).max(10000),
  sessionId: z.string().optional(),
});
 
app.post("/chat", async (req, res) => {
  const result = chatSchema.safeParse(req.body);
  if (!result.success) {
    return res.status(400).json({ error: result.error.issues });
  }
  // ... 处理逻辑
});

总结与展望

我们学到了什么

  1. Agent 架构:理解了感知-推理-行动的循环
  2. 工具系统:如何定义和注册可扩展的工具
  3. 记忆管理:实现对话历史的存储和管理
  4. 部署方案:从本地开发到生产级部署

进阶方向

方向描述推荐框架/工具
多 Agent 协作多个 Agent 分工合作LangGraph, AutoGen
RAG 增强接入知识库LlamaIndex, Chroma
流式输出实时展示思考过程SSE, WebSocket
可观测性追踪和调试LangSmith, Helicone
安全防护prompt 注入防御Guardrails, NeMo

💡 Tips: 构建 Agent 时,从简单开始,逐步添加功能。先让核心循环跑通,再优化细节。

如果这篇文章对你有帮助,欢迎点赞和分享!有问题可以在评论区留言讨论。