第 6 课:【实战】从零搭建 AI Agent

课程目标

通过本课程,你将:

  • 掌握从零开始构建 AI Agent 的完整流程
  • 学会技术栈选型和架构设计方法
  • 实现六大核心模块的完整代码
  • 集成 AI 模型和 Prompt Engineering
  • 完成测试、部署、运维全流程
  • 能够独立开发自己的 AI 编程助手

6.1 需求分析与技术选型

产品定位

我们要构建一个简化版的 Claude Code——一个基于 CLI 的 AI 编程助手,具备以下核心能力:

自然语言

开发者

命令行界面

会话管理

记忆系统

工具执行

AI 模型集成

本地数据库

文件操作

命令执行

网络请求

流式响应

Token 计数

沙盒环境

核心功能清单

功能模块 优先级 描述
CLI 交互 P0 REPL 界面、语法高亮、历史记录
会话管理 P0 多轮对话、上下文维护、中断恢复
文件操作 P0 读取/写入/搜索文件(沙盒中)
命令执行 P0 执行 shell 命令(沙盒中)
AI 集成 P0 Claude API、流式响应、错误处理
记忆系统 P1 短期记忆、简单持久化
权限控制 P1 用户确认、路径限制
网络请求 P2 HTTP 客户端(可选)
Git 集成 P2 Git 状态查看(可选)

技术栈选择

后端技术栈
运行时:
  - Node.js 18+ (LTS)
  - TypeScript 5.3+
  - ES Modules

构建工具:
  - 编译:tsc + esbuild
  - 打包:pkg 或 ncc
  - 测试:vitest
  - Lint: ESLint + Prettier

核心依赖:
  - CLI 框架:commander + ink (React for CLI)
  - AI SDK: @anthropic-ai/sdk
  - 数据库:better-sqlite3
  - HTTP: undici
  - 加密:node:crypto
  - 日志:pino
  - Schema 验证:zod

开发体验:
  - 热重载:tsx watch
  - 调试:ndb
  - 文档:typedoc
项目结构
my-ai-agent/
├── src/
│   ├── index.ts              # 入口文件
│   ├── cli/                  # CLI 层
│   │   ├── commands/         # 命令定义
│   │   ├── ui/               # UI 组件
│   │   └── repl.ts           # REPL 实现
│   │
│   ├── core/                 # 核心逻辑
│   │   ├── session/          # 会话管理
│   │   ├── memory/           # 记忆系统
│   │   ├── tools/            # 工具系统
│   │   └── ai/               # AI 集成
│   │
│   ├── sandbox/              # 沙盒环境
│   │   ├── filesystem.ts     # 虚拟文件系统
│   │   └── executor.ts       # 命令执行器
│   │
│   ├── utils/                # 工具函数
│   │   ├── logger.ts         # 日志
│   │   ├── crypto.ts         # 加密
│   │   └── async.ts          # 异步工具
│   │
│   └── types/                # 类型定义
│       └── index.ts
│
├── tests/                    # 测试文件
├── package.json
├── tsconfig.json
└── README.md

初始化项目

# 1. 创建项目目录
mkdir my-ai-agent
cd my-ai-agent

# 2. 初始化 npm 项目
npm init -y

# 3. 安装 TypeScript 和开发工具
npm install -D typescript @types/node tsx vitest eslint prettier

# 4. 安装生产依赖
npm install @anthropic-ai/sdk commander ink react better-sqlite3 \
            undici pino zod chalk highlight.js

# 5. 创建 TypeScript 配置
cat > tsconfig.json << 'EOF'
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "lib": ["ES2022"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "tests"]
}
EOF

# 6. 创建基础目录结构
mkdir -p src/{cli,core,sandbox,utils,types}
mkdir -p tests

6.2 核心模块实现

模块一:CLI 交互模块

命令行参数解析
// src/cli/commands/index.ts
import { Command } from 'commander';
import { repl } from './repl';
import { showVersion, showConfig } from './helpers';

export const program = new Command();

program
  .name('my-ai-agent')
  .description('AI 编程助手 - 基于自然语言的代码助手')
  .version('1.0.0')
  
  // 交互式 REPL 模式
  .command('repl')
  .description('启动交互式 REPL')
  .option('-p, --project <path>', '项目根目录', process.cwd())
  .option('-c, --config <path>', '配置文件路径')
  .option('--no-memory', '禁用记忆功能')
  .action(repl)
  
  // 单次执行模式
  .command('exec [message...]')
  .description('执行单条指令')
  .option('-p, --project <path>', '项目根目录')
  .action(async (message, options) => {
    const fullMessage = message.join(' ');
    if (!fullMessage) {
      console.error('❌ 请提供要执行的指令');
      process.exit(1);
    }
    
    await executeOnce(fullMessage, options);
  })
  
  // 配置管理
  .command('config')
  .description('显示或修改配置')
  .argument('[key]', '配置键')
  .argument('[value]', '配置值')
  .action(showConfig);

// 启动程序
if (process.argv.length <= 2) {
  // 无参数时默认启动 REPL
  program.parse([...process.argv, 'repl']);
} else {
  program.parse();
}
REPL 实现(简化版)
// src/cli/commands/repl.ts
import * as readline from 'readline';
import chalk from 'chalk';
import { SessionManager } from '../../core/session/session-manager';
import { logger } from '../../utils/logger';

export async function repl(options: { project: string; memory: boolean }) {
  console.log(chalk.blue.bold('\n🤖 My AI Agent'));
  console.log(chalk.gray('版本 1.0.0 | 按 Ctrl+C 退出\n'));
  
  // 创建会话管理器
  const session = new SessionManager({
    projectId: options.project,
    enableMemory: options.memory,
  });
  
  // 初始化 readline
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
    prompt: chalk.green('❯ '),
  });
  
  // 显示欢迎信息
  console.log(chalk.yellow('💡 提示: 输入 /help 查看可用命令\n'));
  
  // 开始提示
  rl.prompt();
  
  // 处理输入
  rl.on('line', async (line) => {
    const input = line.trim();
    
    // 空行
    if (!input) {
      rl.prompt();
      return;
    }
    
    // 特殊命令
    if (input.startsWith('/')) {
      await handleSpecialCommand(input, session);
      rl.prompt();
      return;
    }
    
    try {
      // 发送消息给 AI
      console.log(chalk.cyan('\n📤 发送请求...'));
      
      const response = await session.sendMessage(input);
      
      // 显示响应
      console.log('\n' + formatResponse(response));
      
    } catch (error) {
      console.error(chalk.red('❌ 错误:'), error instanceof Error ? error.message : error);
      logger.error('REPL error:', error);
    }
    
    rl.prompt();
  });
  
  // 处理退出
  rl.on('close', async () => {
    console.log(chalk.yellow('\n👋 再见!'));
    await session.cleanup();
    process.exit(0);
  });
  
  // 处理 Ctrl+C
  process.on('SIGINT', async () => {
    console.log(chalk.yellow('\n\n收到中断信号,正在清理...'));
    await session.cleanup();
    process.exit(0);
  });
}

/**
 * 处理特殊命令
 */
async function handleSpecialCommand(
  input: string,
  session: SessionManager
): Promise<void> {
  const [command, ...args] = input.slice(1).split(' ');
  
  switch (command.toLowerCase()) {
    case 'help':
      console.log(`
${chalk.bold('可用命令:')}
  /help          显示帮助
  /clear         清空屏幕
  /history       显示历史记录
  /memory        显示记忆
  /config        显示配置
  /exit          退出程序
      `.trim());
      break;
      
    case 'clear':
      console.clear();
      break;
      
    case 'history':
      const history = session.getHistory();
      console.log(chalk.bold('\n历史记录:'));
      history.forEach((msg, i) => {
        console.log(`${i + 1}. ${msg.role}: ${msg.content.slice(0, 100)}...`);
      });
      break;
      
    case 'memory':
      const memories = await session.getMemories();
      console.log(chalk.bold('\n记忆:'));
      memories.forEach(m => {
        console.log(`${m.content}`);
      });
      break;
      
    case 'exit':
    case 'quit':
      rl.close();
      break;
      
    default:
      console.log(chalk.red(`未知命令:/${command}`));
  }
}

/**
 * 格式化 AI 响应
 */
function formatResponse(response: string): string {
  // 简单的代码块高亮
  return response.replace(/```(\w+)?\n([\s\S]*?)```/g, (match, lang, code) => {
    return `\n${chalk.bgBlue.black(' ' + (lang || 'code') + ' ')}\n${code}`;
  });
}

模块二:会话管理模块

// src/core/session/session-manager.ts
import { EventEmitter } from 'events';
import { Message, SessionState, SessionConfig } from '../../types';
import { MemoryManager } from '../memory/memory-manager';
import { ToolExecutor } from '../tools/tool-executor';
import { AIClient } from '../ai/ai-client';

export class SessionManager extends EventEmitter {
  private state: SessionState;
  private messages: Message[] = [];
  private memoryManager: MemoryManager;
  private toolExecutor: ToolExecutor;
  private aiClient: AIClient;
  
  constructor(config: SessionConfig) {
    super();
    
    this.state = {
      id: this.generateSessionId(),
      projectId: config.projectId,
      status: 'idle',
      createdAt: new Date(),
      updatedAt: new Date(),
    };
    
    this.memoryManager = new MemoryManager(config.projectId);
    this.toolExecutor = new ToolExecutor(config.projectId);
    this.aiClient = new AIClient();
  }
  
  /**
   * 发送消息并获取响应
   */
  async sendMessage(content: string): Promise<string> {
    try {
      // 1. 添加用户消息
      const userMessage: Message = {
        id: this.generateMessageId(),
        role: 'user',
        content,
        timestamp: new Date(),
      };
      
      this.messages.push(userMessage);
      this.updateTimestamp();
      
      // 2. 构建上下文
      const context = await this.buildContext();
      
      // 3. 发送到 AI
      const response = await this.aiClient.sendMessage(context);
      
      // 4. 处理响应
      const assistantMessage: Message = {
        id: this.generateMessageId(),
        role: 'assistant',
        content: response.content,
        timestamp: new Date(),
      };
      
      this.messages.push(assistantMessage);
      
      // 5. 如果有工具调用,执行它们
      if (response.toolCalls && response.toolCalls.length > 0) {
        const toolResults = await this.executeToolCalls(response.toolCalls);
        
        // 添加工具结果
        this.messages.push({
          role: 'tool',
          toolCalls: toolResults,
          timestamp: new Date(),
        });
        
        // 递归:让 AI 继续处理
        return this.sendMessage('工具执行完成,请继续');
      }
      
      // 6. 提取重要信息到记忆
      await this.extractMemories(content, response.content);
      
      return response.content;
      
    } catch (error) {
      this.emit('error', error);
      throw error;
    }
  }
  
  /**
   * 构建发送给 AI 的上下文
   */
  private async buildContext(): Promise<any> {
    // 获取最近的对话历史(最近 20 条)
    const recentMessages = this.messages.slice(-20);
    
    // 获取相关记忆
    const lastUserMessage = this.messages
      .filter(m => m.role === 'user')
      .pop();
    
    const relevantMemories = lastUserMessage
      ? await this.memoryManager.searchMemories(lastUserMessage.content)
      : [];
    
    // 获取项目信息
    const projectInfo = await this.getProjectInfo();
    
    return {
      systemPrompt: this.getSystemPrompt(),
      messages: recentMessages,
      memories: relevantMemories,
      project: projectInfo,
    };
  }
  
  /**
   * 执行工具调用
   */
  private async executeToolCalls(toolCalls: any[]): Promise<any[]> {
    const results = [];
    
    for (const toolCall of toolCalls) {
      this.emit('tool_start', toolCall);
      
      try {
        // 请求用户确认(如果是危险操作)
        if (this.requiresConfirmation(toolCall)) {
          const confirmed = await this.requestUserConfirmation(toolCall);
          if (!confirmed) {
            results.push({
              toolCallId: toolCall.id,
              success: false,
              error: '用户拒绝执行',
            });
            continue;
          }
        }
        
        // 执行工具
        const result = await this.toolExecutor.execute(toolCall);
        
        results.push({
          toolCallId: toolCall.id,
          success: true,
          result,
        });
        
        this.emit('tool_complete', toolCall, result);
        
      } catch (error) {
        results.push({
          toolCallId: toolCall.id,
          success: false,
          error: error instanceof Error ? error.message : String(error),
        });
        
        this.emit('tool_error', toolCall, error);
      }
    }
    
    return results;
  }
  
  /**
   * 提取记忆
   */
  private async extractMemories(
    userInput: string,
    aiResponse: string
  ): Promise<void> {
    // 简单的启发式规则
    const patterns = [
      /项目使用\s*(.+?)(?:框架 | 语言 | 技术)/,
      /我 prefer\s*(.+?)(?:来 | 进行)/,
      /默认配置是\s*(.+?)(?:。|$)/,
    ];
    
    for (const pattern of patterns) {
      const match = userInput.match(pattern);
      if (match) {
        await this.memoryManager.addMemory({
          type: 'long_term',
          content: match[0],
          tags: ['preference'],
          importance: 0.7,
        });
      }
    }
  }
  
  /**
   * 获取历史记录
   */
  getHistory(): Message[] {
    return [...this.messages];
  }
  
  /**
   * 获取记忆
   */
  async getMemories(): Promise<any[]> {
    return this.memoryManager.getAllMemories();
  }
  
  /**
   * 清理资源
   */
  async cleanup(): Promise<void> {
    await this.memoryManager.save();
    this.toolExecutor.cleanup();
  }
  
  private updateTimestamp(): void {
    this.state.updatedAt = new Date();
  }
  
  private generateSessionId(): string {
    return `session_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
  }
  
  private generateMessageId(): string {
    return `msg_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
  }
  
  private requiresConfirmation(toolCall: any): boolean {
    // 检查是否是危险操作
    const dangerousTools = ['terminal', 'file_delete', 'network_request'];
    return dangerousTools.includes(toolCall.name);
  }
  
  private async requestUserConfirmation(toolCall: any): Promise<boolean> {
    const { createInterface } = await import('readline');
    const chalk = (await import('chalk')).default;
    
    const rl = createInterface({
      input: process.stdin,
      output: process.stdout,
    });
    
    return new Promise(resolve => {
      console.log(chalk.yellow('\n⚠️  需要确认'));
      console.log(`操作:${toolCall.name}`);
      console.log(`参数:${JSON.stringify(toolCall.args, null, 2)}`);
      
      rl.question(chalk.green('\n是否继续?(y/N) '), answer => {
        rl.close();
        resolve(answer.toLowerCase() === 'y');
      });
    });
  }
  
  private async getProjectInfo(): Promise<any> {
    const fs = await import('fs/promises');
    const path = await import('path');
    
    try {
      const packageJsonPath = path.join(this.state.projectId, 'package.json');
      const content = await fs.readFile(packageJsonPath, 'utf-8');
      return JSON.parse(content);
    } catch {
      return null;
    }
  }
  
  private getSystemPrompt(): string {
    return `你是一个专业的 AI 编程助手。
    
你的职责:
1. 帮助用户编写、调试和理解代码
2. 回答技术问题
3. 执行安全的文件操作和命令

安全准则:
- 不执行危险命令(rm -rf / 等)
- 不访问敏感文件
- 所有操作都在沙盒环境中进行

保持友好、专业的语气。`;
  }
}

模块三:记忆系统(简化版)

// src/core/memory/memory-manager.ts
import Database from 'better-sqlite3';
import { join } from 'path';
import { Memory, MemoryFilters } from '../../types';

export class MemoryManager {
  private db: Database;
  
  constructor(projectPath: string) {
    const dbPath = join(projectPath, '.ai-agent-memory.db');
    this.db = new Database(dbPath);
    this.initializeSchema();
  }
  
  /**
   * 初始化数据库
   */
  private initializeSchema(): void {
    this.db.exec(`
      CREATE TABLE IF NOT EXISTS memories (
        id TEXT PRIMARY KEY,
        type TEXT NOT NULL,
        content TEXT NOT NULL,
        tags TEXT,
        importance REAL DEFAULT 0.5,
        access_count INTEGER DEFAULT 0,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
      );
      
      CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type);
      CREATE INDEX IF NOT EXISTS idx_memories_importance ON memories(importance DESC);
    `);
  }
  
  /**
   * 添加记忆
   */
  async addMemory(memory: Omit<Memory, 'id' | 'createdAt' | 'updatedAt'>): Promise<string> {
    const id = `mem_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
    
    const stmt = this.db.prepare(`
      INSERT INTO memories (id, type, content, tags, importance, access_count)
      VALUES (?, ?, ?, ?, ?, 0)
    `);
    
    stmt.run(
      id,
      memory.type,
      memory.content,
      JSON.stringify(memory.tags || []),
      memory.importance || 0.5
    );
    
    return id;
  }
  
  /**
   * 搜索记忆
   */
  async searchMemories(query: string, limit: number = 10): Promise<Memory[]> {
    // 简单的全文搜索(实际应该用向量搜索)
    const stmt = this.db.prepare(`
      SELECT * FROM memories
      WHERE content LIKE ?
      ORDER BY importance DESC, access_count DESC
      LIMIT ?
    `);
    
    const rows = stmt.all(`%${query}%`, limit) as any[];
    
    return rows.map(row => ({
      id: row.id,
      type: row.type,
      content: row.content,
      tags: JSON.parse(row.tags || '[]'),
      importance: row.importance,
      accessCount: row.access_count,
      createdAt: new Date(row.created_at),
      updatedAt: new Date(row.updated_at),
    }));
  }
  
  /**
   * 获取所有记忆
   */
  async getAllMemories(): Promise<Memory[]> {
    const rows = this.db.prepare('SELECT * FROM memories ORDER BY created_at DESC').all() as any[];
    
    return rows.map(row => ({
      id: row.id,
      type: row.type,
      content: row.content,
      tags: JSON.parse(row.tags || '[]'),
      importance: row.importance,
      accessCount: row.access_count,
      createdAt: new Date(row.created_at),
      updatedAt: new Date(row.updated_at),
    }));
  }
  
  /**
   * 保存(SQLite 自动保存,此方法用于兼容性)
   */
  async save(): Promise<void> {
    // SQLite 自动提交,无需额外操作
  }
}

模块四:工具系统

工具接口定义
// src/core/tools/tool-interface.ts
import { z } from 'zod';

export interface Tool {
  name: string;
  description: string;
  parameters: z.ZodType<any>;
  requiresConfirmation: boolean;
  execute(params: any, context: ExecutionContext): Promise<ToolResult>;
}

export interface ExecutionContext {
  workingDirectory: string;
  environment: Record<string, string>;
}

export interface ToolResult {
  success: boolean;
  output: string;
  error?: string;
}
文件系统工具
// src/core/tools/builtin/file-system.ts
import { Tool, ExecutionContext, ToolResult } from '../tool-interface';
import { z } from 'zod';
import * as fs from 'fs/promises';
import * as path from 'path';

export class FileSystemTool implements Tool {
  readonly name = 'file_system';
  readonly description = '读取、写入和管理文件';
  readonly requiresConfirmation = false;
  
  readonly parameters = z.object({
    action: z.enum(['read', 'write', 'list', 'delete']),
    path: z.string(),
    content: z.string().optional(),
  });
  
  async execute(
    params: z.infer<typeof this.parameters>,
    context: ExecutionContext
  ): Promise<ToolResult> {
    try {
      // 验证路径安全
      const safePath = await this.validatePath(params.path, context.workingDirectory);
      
      switch (params.action) {
        case 'read': {
          const content = await fs.readFile(safePath, 'utf-8');
          return {
            success: true,
            output: content,
          };
        }
        
        case 'write': {
          if (!params.content) {
            return { success: false, output: '', error: '写入内容不能为空' };
          }
          
          // 确保父目录存在
          await fs.mkdir(path.dirname(safePath), { recursive: true });
          await fs.writeFile(safePath, params.content, 'utf-8');
          
          return {
            success: true,
            output: `✓ 成功写入文件:${safePath}`,
          };
        }
        
        case 'list': {
          const entries = await fs.readdir(safePath, { withFileTypes: true });
          const output = entries.map(entry => {
            const type = entry.isDirectory() ? '📁' : '📄';
            return `${type} ${entry.name}`;
          }).join('\n');
          
          return { success: true, output };
        }
        
        case 'delete': {
          await fs.unlink(safePath);
          return {
            success: true,
            output: `✓ 成功删除文件:${safePath}`,
          };
        }
        
        default:
          return { success: false, output: '', error: '未知的操作' };
      }
      
    } catch (error) {
      return {
        success: false,
        output: '',
        error: error instanceof Error ? error.message : String(error),
      };
    }
  }
  
  /**
   * 验证路径安全
   */
  private async validatePath(userPath: string, workingDir: string): Promise<string> {
    const resolved = path.resolve(workingDir, userPath);
    
    // 确保路径在工作目录内
    if (!resolved.startsWith(workingDir)) {
      throw new Error(`路径超出工作目录范围:${resolved}`);
    }
    
    return resolved;
  }
}
终端执行工具
// src/core/tools/builtin/terminal.ts
import { Tool, ExecutionContext, ToolResult } from '../tool-interface';
import { z } from 'zod';
import { spawn } from 'child_process';

export class TerminalTool implements Tool {
  readonly name = 'terminal';
  readonly description = '在终端执行 shell 命令';
  readonly requiresConfirmation = true;
  
  readonly parameters = z.object({
    command: z.string(),
    cwd: z.string().optional(),
    timeout: z.number().default(30000),
  });
  
  async execute(
    params: z.infer<typeof this.parameters>,
    context: ExecutionContext
  ): Promise<ToolResult> {
    try {
      // 命令白名单检查
      const baseCommand = params.command.split(/\s+/)[0];
      if (!this.isAllowedCommand(baseCommand)) {
        return {
          success: false,
          output: '',
          error: `命令 "${baseCommand}" 不在白名单中`,
        };
      }
      
      return new Promise((resolve) => {
        const child = spawn('/bin/sh', ['-c', params.command], {
          cwd: params.cwd || context.workingDirectory,
          env: { ...process.env, ...context.environment },
          stdio: ['ignore', 'pipe', 'pipe'],
          timeout: params.timeout,
        });
        
        let stdout = '';
        let stderr = '';
        
        child.stdout.on('data', data => stdout += data);
        child.stderr.on('data', data => stderr += data);
        
        child.on('close', code => {
          resolve({
            success: code === 0,
            output: stdout || stderr,
            error: code !== 0 ? `退出码:${code}` : undefined,
          });
        });
        
        child.on('error', error => {
          resolve({
            success: false,
            output: '',
            error: error.message,
          });
        });
      });
      
    } catch (error) {
      return {
        success: false,
        output: '',
        error: error instanceof Error ? error.message : String(error),
      };
    }
  }
  
  private isAllowedCommand(command: string): boolean {
    const allowedCommands = [
      'ls', 'dir', 'cat', 'head', 'tail',
      'grep', 'find', 'pwd', 'mkdir', 'touch',
      'git', 'npm', 'node', 'python',
    ];
    
    return allowedCommands.includes(command);
  }
}
工具注册中心
// src/core/tools/tool-registry.ts
import { Tool } from './tool-interface';
import { FileSystemTool } from './builtin/file-system';
import { TerminalTool } from './builtin/terminal';

export class ToolRegistry {
  private tools: Map<string, Tool> = new Map();
  
  constructor() {
    this.registerBuiltins();
  }
  
  private registerBuiltins(): void {
    this.register(new FileSystemTool());
    this.register(new TerminalTool());
    // 可以添加更多工具...
  }
  
  register(tool: Tool): void {
    if (this.tools.has(tool.name)) {
      throw new Error(`工具 "${tool.name}" 已注册`);
    }
    this.tools.set(tool.name, tool);
  }
  
  getTool(name: string): Tool | undefined {
    return this.tools.get(name);
  }
  
  getAvailableTools(): Array<{ name: string; description: string }> {
    return Array.from(this.tools.values()).map(tool => ({
      name: tool.name,
      description: tool.description,
    }));
  }
}

模块五:AI 模型集成

// src/core/ai/ai-client.ts
import Anthropic from '@anthropic-ai/sdk';
import { Message } from '../../types';

export class AIClient {
  private client: Anthropic;
  
  constructor() {
    const apiKey = process.env.ANTHROPIC_API_KEY;
    
    if (!apiKey) {
      throw new Error('请设置 ANTHROPIC_API_KEY 环境变量');
    }
    
    this.client = new Anthropic({ apiKey });
  }
  
  /**
   * 发送消息并获取响应
   */
  async sendMessage(context: any): Promise<{
    content: string;
    toolCalls?: any[];
  }> {
    try {
      // 格式化消息
      const messages = this.formatMessages(context.messages);
      
      // 构建系统提示词
      const systemPrompt = this.buildSystemPrompt(context);
      
      // 创建流式请求
      const stream = await this.client.messages.create({
        model: 'claude-sonnet-4-20260130',
        max_tokens: 4096,
        system: systemPrompt,
        messages,
        stream: true,
      });
      
      // 累积响应
      let fullContent = '';
      const toolCalls = [];
      
      for await (const chunk of stream) {
        if (chunk.type === 'content_block_delta') {
          fullContent += chunk.delta?.text || '';
          process.stdout.write(chunk.delta?.text || '');
        } else if (chunk.type === 'content_block_start') {
          if (chunk.content_block?.type === 'tool_use') {
            toolCalls.push(chunk.content_block);
          }
        }
      }
      
      console.log(); // 换行
      
      return {
        content: fullContent,
        toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
      };
      
    } catch (error) {
      if (error instanceof Anthropic.APIError) {
        throw new Error(`API 错误:${error.status} - ${error.message}`);
      }
      throw error;
    }
  }
  
  /**
   * 格式化消息
   */
  private formatMessages(messages: Message[]): any[] {
    return messages.map(msg => ({
      role: msg.role,
      content: msg.content,
    }));
  }
  
  /**
   * 构建系统提示词
   */
  private buildSystemPrompt(context: any): string {
    const parts = [
      '你是一个专业的 AI 编程助手。',
      '',
      '你的能力:',
      '- 编写、调试和理解代码',
      '- 回答技术问题',
      '- 执行文件操作和命令',
      '',
      '安全准则:',
      '- 不执行危险命令',
      '- 不访问敏感文件',
      '- 保持友好专业的语气',
    ];
    
    // 添加项目上下文
    if (context.project) {
      parts.push('', '当前项目信息:', JSON.stringify(context.project, null, 2));
    }
    
    // 添加记忆
    if (context.memories && context.memories.length > 0) {
      parts.push('', '相关记忆:');
      context.memories.forEach((m: any) => {
        parts.push(`- ${m.content}`);
      });
    }
    
    return parts.join('\n');
  }
}

6.3 整合与运行

主入口文件

// src/index.ts
#!/usr/bin/env node

import { program } from './cli/commands';

async function main() {
  try {
    await program.parseAsync();
  } catch (error) {
    console.error('启动失败:', error instanceof Error ? error.message : error);
    process.exit(1);
  }
}

main();

package.json 配置

{
  "name": "my-ai-agent",
  "version": "1.0.0",
  "description": "AI 编程助手",
  "type": "module",
  "bin": {
    "my-ai-agent": "./dist/index.js"
  },
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "build": "tsc && cp package.json dist/",
    "start": "node dist/index.js",
    "test": "vitest"
  },
  "dependencies": {
    "@anthropic-ai/sdk": "^0.18.0",
    "better-sqlite3": "^9.0.0",
    "chalk": "^5.3.0",
    "commander": "^11.1.0",
    "ink": "^4.4.1",
    "pino": "^8.17.0",
    "react": "^18.2.0",
    "undici": "^6.0.0",
    "zod": "^3.22.4"
  },
  "devDependencies": {
    "@types/better-sqlite3": "^7.6.8",
    "@types/node": "^20.10.0",
    "@types/react": "^18.2.43",
    "eslint": "^8.55.0",
    "prettier": "^3.1.0",
    "tsx": "^4.6.2",
    "typescript": "^5.3.2",
    "vitest": "^1.0.4"
  }
}

运行项目

# 1. 设置 API Key
export ANTHROPIC_API_KEY="sk-..."

# 2. 开发模式运行
npm run dev

# 3. 构建
npm run build

# 4. 全局安装
npm link

# 5. 使用
my-ai-agent repl
my-ai-agent exec "帮我创建一个 TypeScript 项目"

6.4 测试与质量保证

单元测试示例

// tests/unit/file-system.test.ts
import { describe, it, expect, beforeEach } from 'vitest';
import { FileSystemTool } from '../../src/core/tools/builtin/file-system';
import { mkdtemp, rm } from 'fs/promises';
import { join } from 'path';
import { tmpdir } from 'os';

describe('FileSystemTool', () => {
  let tool: FileSystemTool;
  let tempDir: string;
  
  beforeEach(async () => {
    tool = new FileSystemTool();
    tempDir = await mkdtemp(join(tmpdir(), 'test-'));
  });
  
  afterEach(async () => {
    await rm(tempDir, { recursive: true, force: true });
  });
  
  it('应该能读取文件', async () => {
    // 准备测试文件
    const testFile = join(tempDir, 'test.txt');
    await import('fs/promises').then(fs => 
      fs.writeFile(testFile, 'hello world')
    );
    
    // 执行测试
    const result = await tool.execute(
      { action: 'read', path: 'test.txt' },
      { workingDirectory: tempDir, environment: {} }
    );
    
    expect(result.success).toBe(true);
    expect(result.output).toBe('hello world');
  });
  
  it('应该能写入文件', async () => {
    const result = await tool.execute(
      { action: 'write', path: 'output.txt', content: 'test content' },
      { workingDirectory: tempDir, environment: {} }
    );
    
    expect(result.success).toBe(true);
    
    // 验证文件内容
    const fs = await import('fs/promises');
    const content = await fs.readFile(join(tempDir, 'output.txt'), 'utf-8');
    expect(content).toBe('test content');
  });
  
  it('应该阻止路径遍历攻击', async () => {
    const result = await tool.execute(
      { action: 'read', path: '../../../etc/passwd' },
      { workingDirectory: tempDir, environment: {} }
    );
    
    expect(result.success).toBe(false);
    expect(result.error).toContain('路径超出工作目录范围');
  });
});

课后作业

基础题

  1. 完成基础功能:实现所有 P0 优先级的功能
  2. 添加错误处理:完善所有异常情况的处理
  3. 编写文档:为你的项目写一个完整的 README

进阶题

  1. 实现记忆系统:添加向量搜索和记忆压缩
  2. 增强权限控制:实现四层权限体系
  3. 添加 GUI 界面:使用 Ink 创建漂亮的 TUI

挑战题

  1. 实现沙盒环境:集成 Docker 容器执行危险操作
  2. 多模型支持:同时支持 Claude、GPT-4、开源模型
  3. 插件系统:允许用户自定义工具和扩展功能

毕业设计

要求

实现一个完整的 AI 编程助手,包含以下功能:

  1. 核心功能(必选)

    • REPL 交互界面
    • 文件读写操作
    • 命令执行(带沙盒)
    • AI 模型集成
  2. 高级功能(至少选 2 个)

    • 记忆系统
    • 权限控制
    • Git 集成
    • 网络请求
  3. 创新功能(自选)

    • 独特的 UI 设计
    • 特殊的工具扩展
    • 性能优化
    • 其他创新点

提交格式

your-project/
├── src/              # 源代码
├── tests/            # 测试
├── README.md         # 项目说明
├── package.json
└── demo.gif          # 演示 GIF

总结

恭喜你完成了整个课程!现在你已经:

✅ 了解了 Claude Code 源码泄露事件的完整经过
✅ 掌握了大型项目的架构设计方法
✅ 学习了记忆系统、权限系统、沙盒的实现细节
✅ 从零构建了自己的 AI Agent

下一步建议

  1. 持续改进:不断优化你的项目
  2. 开源分享:将代码开源到 GitHub
  3. 社区交流:参与相关技术社区
  4. 深入学习:研究更高级的主题(如自主 Agent、多智能体协作等)

版权声明:本课程内容仅用于教育和技术研究目的,请勿将所学知识用于非法活动。所有案例分析均基于公开信息,旨在提升安全意识和技术水平。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐