Claude Code 源码泄露之六:应用最佳实践-从零搭建 AI Agent
本文介绍了如何从零搭建一个AI编程助手Agent。课程内容包括需求分析、技术选型和核心模块实现三个部分。首先明确了产品定位为一个简化版Claude Code CLI工具,具备会话管理、文件操作、命令执行等核心功能。技术栈选择Node.js+TypeScript环境,使用commander框架构建CLI,集成Anthropic的AI SDK和SQLite数据库。文章详细展示了项目初始化步骤、目录结构
·
第 6 课:【实战】从零搭建 AI Agent
课程目标
通过本课程,你将:
- 掌握从零开始构建 AI Agent 的完整流程
- 学会技术栈选型和架构设计方法
- 实现六大核心模块的完整代码
- 集成 AI 模型和 Prompt Engineering
- 完成测试、部署、运维全流程
- 能够独立开发自己的 AI 编程助手
6.1 需求分析与技术选型
产品定位
我们要构建一个简化版的 Claude Code——一个基于 CLI 的 AI 编程助手,具备以下核心能力:
核心功能清单:
| 功能模块 | 优先级 | 描述 |
|---|---|---|
| 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('路径超出工作目录范围');
});
});
课后作业
基础题
- 完成基础功能:实现所有 P0 优先级的功能
- 添加错误处理:完善所有异常情况的处理
- 编写文档:为你的项目写一个完整的 README
进阶题
- 实现记忆系统:添加向量搜索和记忆压缩
- 增强权限控制:实现四层权限体系
- 添加 GUI 界面:使用 Ink 创建漂亮的 TUI
挑战题
- 实现沙盒环境:集成 Docker 容器执行危险操作
- 多模型支持:同时支持 Claude、GPT-4、开源模型
- 插件系统:允许用户自定义工具和扩展功能
毕业设计
要求
实现一个完整的 AI 编程助手,包含以下功能:
-
核心功能(必选)
- REPL 交互界面
- 文件读写操作
- 命令执行(带沙盒)
- AI 模型集成
-
高级功能(至少选 2 个)
- 记忆系统
- 权限控制
- Git 集成
- 网络请求
-
创新功能(自选)
- 独特的 UI 设计
- 特殊的工具扩展
- 性能优化
- 其他创新点
提交格式
your-project/
├── src/ # 源代码
├── tests/ # 测试
├── README.md # 项目说明
├── package.json
└── demo.gif # 演示 GIF
总结
恭喜你完成了整个课程!现在你已经:
✅ 了解了 Claude Code 源码泄露事件的完整经过
✅ 掌握了大型项目的架构设计方法
✅ 学习了记忆系统、权限系统、沙盒的实现细节
✅ 从零构建了自己的 AI Agent
下一步建议:
- 持续改进:不断优化你的项目
- 开源分享:将代码开源到 GitHub
- 社区交流:参与相关技术社区
- 深入学习:研究更高级的主题(如自主 Agent、多智能体协作等)
版权声明:本课程内容仅用于教育和技术研究目的,请勿将所学知识用于非法活动。所有案例分析均基于公开信息,旨在提升安全意识和技术水平。
更多推荐



所有评论(0)