1. 项目概述:从零构建AI编程助手Agent
在当今快速发展的AI领域,构建一个实用的AI编程助手已成为许多开发者的需求。本文将详细介绍如何从零开始构建一个简化版的Claude Code——一个基于命令行的AI编程助手。这个项目将具备自然语言交互、会话管理、文件操作、命令执行等核心功能,帮助开发者提高编码效率。
这个AI Agent的设计目标是成为一个轻量级但功能完备的编程助手,能够在本地开发环境中无缝集成。它不仅能理解自然语言指令,还能执行基本的开发任务,如文件操作、命令执行等,同时保持会话上下文和记忆能力。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 需求分析与技术选型
2.1 产品定位与核心功能
我们的AI编程助手定位为一个CLI工具,主要服务于开发者群体。它需要具备以下核心能力:
- 自然语言交互:理解开发者用自然语言描述的需求
- 会话管理:维护多轮对话上下文,支持中断恢复
- 文件操作:在沙盒环境中安全地读写文件
- 命令执行:执行基本的shell命令(有限制的)
- AI集成:与Claude API集成,实现智能响应
- 记忆系统:短期记忆和简单持久化能力
核心功能优先级如下:
| 功能模块 | 优先级 | 描述 |
|---|---|---|
| CLI交互 | P0 | REPL界面、语法高亮、历史记录 |
| 会话管理 | P0 | 多轮对话、上下文维护 |
| 文件操作 | P0 | 读取/写入/搜索文件(沙盒中) |
| 命令执行 | P0 | 执行shell命令(沙盒中) |
| AI集成 | P0 | Claude API、流式响应 |
| 记忆系统 | P1 | 短期记忆、简单持久化 |
| 权限控制 | P1 | 用户确认、路径限制 |
2.2 技术栈选择
经过评估,我们选择了以下技术栈:
运行时环境:
- Node.js 18+ (LTS版本)
- TypeScript 5.3+
- ES Modules规范
构建工具链:
- 编译:tsc + esbuild组合
- 打包:pkg或ncc
- 测试:vitest测试框架
- 代码规范: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
选择这些技术的主要考虑因素包括:
- Node.js生态丰富,适合CLI工具开发
- TypeScript提供更好的类型安全和开发体验
- 轻量级依赖,减少打包体积
- 良好的开发者体验工具链
3. 项目初始化与结构设计
3.1 项目初始化步骤
以下是创建项目的完整命令序列:
bash复制# 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
3.2 项目目录结构
设计良好的目录结构对项目可维护性至关重要。我们的项目结构如下:
code复制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
这种结构的主要优点:
- 清晰的关注点分离
- 易于扩展新功能
- 测试代码与实现代码分离
- 类型定义集中管理
4. 核心模块实现
4.1 CLI交互模块
4.1.1 命令行参数解析
CLI是用户与AI助手交互的主要界面。我们使用commander.js框架来处理命令行参数:
typescript复制// 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();
}
4.1.2 REPL实现
REPL(Read-Eval-Print Loop)是交互式编程环境的核心。我们的实现如下:
typescript复制// 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);
});
}
4.2 会话管理模块
会话管理是AI助手的核心功能之一,负责维护对话上下文和处理用户消息:
typescript复制// 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,
};
}
// ...其他方法省略...
}
4.3 记忆系统实现
记忆系统帮助AI记住重要信息,提升对话连贯性:
typescript复制// 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),
}));
}
// ...其他方法省略...
}
4.4 工具系统实现
工具系统允许AI执行实际的操作,如文件操作和命令执行:
typescript复制// 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;
}
}
5. AI模型集成
5.1 AI客户端实现
与Claude API的集成是实现智能响应的关键:
typescript复制// 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;
}
}
// ...其他方法省略...
}
5.2 系统提示词设计
精心设计的系统提示词对AI行为有重要影响:
typescript复制private getSystemPrompt(): string {
return `你是一个专业的 AI 编程助手。
你的职责:
1. 帮助用户编写、调试和理解代码
2. 回答技术问题
3. 执行安全的文件操作和命令
安全准则:
- 不执行危险命令(rm -rf / 等)
- 不访问敏感文件
- 所有操作都在沙盒环境中进行
保持友好、专业的语气。`;
}
6. 项目整合与运行
6.1 主入口文件
typescript复制// 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();
6.2 package.json配置
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"
}
}
6.3 运行项目
bash复制# 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项目"
7. 测试与质量保证
7.1 单元测试示例
typescript复制// 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');
});
// 更多测试用例...
});
8. 项目部署与优化建议
8.1 部署注意事项
- 环境变量管理:确保ANTHROPIC_API_KEY等敏感信息通过环境变量传递,不要硬编码在代码中
- 权限控制:在生产环境中,应限制工具执行的权限,避免安全风险
- 日志记录:配置详细的日志记录,便于问题排查
- 资源清理:确保会话结束时正确清理资源,如关闭数据库连接等
8.2 性能优化建议
- 缓存机制:为频繁访问的数据实现缓存,减少数据库查询
- 批处理操作:对多个小文件操作进行批处理,提高效率
- 异步处理:对耗时操作使用异步处理,避免阻塞主线程
- 内存管理:监控内存使用,避免内存泄漏
8.3 安全最佳实践
- 沙盒环境:所有文件操作和命令执行都应在严格的沙盒环境中进行
- 输入验证:对所有用户输入进行严格验证
- 路径限制:确保文件操作不超出项目目录范围
- 命令白名单:只允许执行预定义的安全命令
9. 项目扩展方向
- 插件系统:允许开发者通过插件扩展功能
- 多AI模型支持:除了Claude,还可以集成其他AI模型
- 可视化界面:开发基于Web的可视化界面
- 团队协作功能:支持多人协作和知识共享
- 学习模式:让AI能够从用户行为中学习并优化响应
10. 常见问题与解决方案
10.1 API调用失败
问题:调用Claude API时出现认证失败或超时错误
解决方案:
- 检查ANTHROPIC_API_KEY环境变量是否正确设置
- 验证网络连接是否正常
- 检查API配额是否用完
- 实现重试机制处理临时性失败
10.2 文件操作权限问题
问题:在某些系统上文件操作失败
解决方案:
- 确保程序有足够的文件系统权限
- 检查路径是否正确
- 处理跨平台路径分隔符差异
- 实现更严格的错误处理和恢复机制
10.3 会话上下文丢失
问题:长时间会话后上下文信息丢失或不完整
解决方案:
- 优化记忆系统的持久化策略
- 实现会话状态的定期自动保存
- 增加上下文长度限制的智能处理
- 提供手动保存和加载会话的功能
11. 开发经验分享
在实际开发过程中,我总结了以下几点经验:
-
模块化设计:将系统划分为清晰的模块,如CLI、会话管理、工具系统等,大大提高了代码的可维护性
-
类型安全:使用TypeScript和zod进行严格的类型检查,可以在开发早期发现许多潜在问题
-
渐进式开发:从最小可行产品开始,逐步添加功能,确保每个迭代都有可验证的成果
-
测试驱动:为关键功能编写测试用例,特别是工具执行和文件操作等高风险功能
-
错误处理:实现全面的错误处理和日志记录,对于调试和问题排查非常有帮助
-
用户体验:在CLI工具中,即时反馈和清晰的错误信息对用户体验至关重要
-
性能考量:对于频繁操作如文件读写,需要考虑性能影响并做相应优化
-
安全第一:所有外部输入和操作都应视为不可信的,必须进行严格的验证和限制
12. 项目总结
通过这个项目,我们实现了一个功能完备的AI编程助手,具备以下特点:
- 自然语言交互:开发者可以用自然语言描述需求
- 上下文感知:维护多轮对话上下文,理解复杂需求
- 实际执行能力:能够安全地执行文件操作和命令
- 记忆功能:记住重要信息,提升后续交互效率
- 可扩展架构:模块化设计便于功能扩展
这个项目展示了如何将现代AI能力与传统的开发工具相结合,创造出更高效的开发体验。它不仅是一个技术实现,更是一种新的开发范式探索。
