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

课程目标

通过本课程,你将:

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

6.1 需求分析与技术选型

产品定位

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

自然语言

开发者

命令行界面

会话管理

记忆系统

工具执行

AI 模型集成

本地数据库

文件操作

命令执行

网络请求

流式响应

Token 计数

沙盒环境

核心功能清单

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

技术栈选择

后端技术栈
运行时:
  - 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

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐