我让 AI 帮我写了一个 Code Agent!
从零到一,深入理解 AI 编程助手的核心原理
前言
最近,我让 AI 帮我从零开始写了一个类似 Claude Code 的 AI 编程助手。在这个过程中,顺便了解了 Code Agent 产品的核心架构和实现原理。本文将分享这次实践的完整过程和关键收获。
项目概述
我们构建的是一个名为 Mini Claude Code 的 CLI 工具,它具备以下核心能力:
- 🤖 ReAct Agent 循环 - 思考-行动-观察的自动化执行
- 🔧 工具系统 - 文件操作、Shell 执行、代码搜索等
- 🔌 MCP 协议支持 - 可扩展的外部工具集成
- 💬 多模型支持 - 支持 Ollama、Groq、DeepSeek 等免费模型
核心架构
1. ReAct 模式:Agent 的灵魂
ReAct (Reasoning + Acting) 是 Code Agent 的核心工作模式。它让 AI 能够像人类一样思考和行动:
用户输入任务
↓
┌─────────────────────────────────┐
│ 思考 (Thought) │
│ "我需要先读取文件了解代码结构" │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ 行动 (Action) │
│ 调用 read_file 工具 │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ 观察 (Observation) │
│ 获取文件内容,分析结果 │
└─────────────────────────────────┘
↓
继续思考 → 下一步行动 → ... → 任务完成
关键实现:
async runAgentLoop(input: string, maxIterations: number = 10): Promise<void> {
let iteration = 0;
let isComplete = false;
const originalTask = input;
while (!isComplete && iteration < maxIterations) {
// 1. AI 思考并决定行动
const response = await this.aiClient.streamChat(
isFirstRound ? originalTask : '',
onChunk
);
// 2. 解析工具调用
if (response.toolCalls.length > 0) {
const toolCall = response.toolCalls[0];
// 3. 执行工具
const result = await this.executeToolCall(toolCall.tool, toolCall.params);
// 4. 将结果反馈给 AI(包含原始任务提醒)
this.aiClient.addToolResult(toolCall.tool, result.output, result.success, originalTask);
}
// 5. 检查任务是否完成
if (response.isComplete) {
isComplete = true;
}
}
}
核心要点:
- 每轮只执行一个工具调用,等待结果后再决定下一步
- 工具结果反馈时必须包含原始任务,避免 AI "忘记"目标
- 设置最大迭代次数,防止无限循环
2. 工具系统:Agent 的双手
工具是 Agent 与外部世界交互的接口。我们实现了统一的工具抽象:
export interface Tool {
name: string;
description: string;
parameters: Record<string, unknown>;
execute: (params: Record<string, unknown>, workspacePath: string) => Promise<ToolResult>;
}
内置工具:
| 工具名 | 功能 |
|---|---|
read_file | 读取文件内容 |
create_file | 创建新文件 |
edit_file | 编辑文件(搜索替换) |
shell | 执行 Shell 命令 |
search_code | 搜索代码 |
list_files | 列出目录文件 |
list_tools | 列出所有可用工具 |
工具调用格式:
AI 通过特定格式输出工具调用,CLI 解析并执行:
{
"tool": "read_file",
"params": {
"filePath": "/path/to/file.ts"
}
}
3. System Prompt:Agent 的大脑
System Prompt 定义了 AI 的行为模式和能力边界:
getSystemPrompt(): string {
const toolsDescription = this.generateToolsDescription();
return `你是 Mini Claude Code,一个智能编程助手,采用 ReAct 模式工作。
## 工作模式
1. **思考 (Thought)**: 分析当前情况,决定下一步行动
2. **行动 (Action)**: 调用工具执行操作
3. **观察 (Observation)**: 查看工具执行结果
4. **循环**: 根据观察结果继续思考,直到任务完成
## 可用工具
${toolsDescription}
## 输出格式
**思考**: [推理过程]
**行动**: \`\`\`tool { "tool": "xxx", "params": {...} } \`\`\`
**回复**: [给用户的回复]
## 重要规则
1. 每次只执行一个工具调用
2. 任务完成时标注 "[任务完成]"
...`;
}
关键设计:
- 动态生成工具描述,支持运行时添加新工具
- 明确的输出格式约定,便于解析
- 清晰的规则约束,引导 AI 正确行为
4. MCP 协议:Agent 的扩展能力
MCP (Model Context Protocol) 是 Anthropic 提出的标准化协议,让 AI 应用可以连接外部工具和数据源。
架构设计:
┌─────────────────┐ ┌─────────────────┐
│ Mini Claude │ │ MCP Server │
│ Code │────▶│ (filesystem) │
│ │ └─────────────────┘
│ MCPManager │ ┌─────────────────┐
│ MCPClient │────▶│ MCP Server │
│ │ │ (git) │
└─────────────────┘ └─────────────────┘
使用官方 SDK:
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
// 创建客户端
this.client = new Client({ name: 'mini-claude-code', version: '0.1.0' });
// 连接服务器
this.transport = new StdioClientTransport({ command, args, env });
await this.client.connect(this.transport);
// 获取工具列表
const toolsResult = await this.client.listTools();
// 调用工具
const result = await this.client.callTool({ name: toolName, arguments: args });
MCP 工具与内置工具统一消费:
// MCP 工具注册到 ALL_TOOLS
registerTool({
name: `${serverName}::${mcpTool.name}`, // 格式:服务器名::工具名
description: mcpTool.description,
execute: async (params) => {
return await client.callTool(mcpTool.name, params);
}
});
// 统一通过 getTool() 查找和执行
const tool = getTool(toolName); // 不区分内置还是 MCP
await tool.execute(params, workspacePath);
5. 对话历史管理
对话历史是 Agent 的"记忆",需要精心管理:
private conversationHistory: Message[] = [];
// 确保 System Prompt 始终是最新的
private ensureSystemPrompt(): void {
const newSystemPrompt = this.getSystemPrompt();
if (this.conversationHistory.length === 0) {
this.conversationHistory.push({ role: 'system', content: newSystemPrompt });
} else {
this.conversationHistory[0].content = newSystemPrompt; // 更新工具列表
}
}
// 工具结果反馈(包含原始任务提醒)
addToolResult(toolName: string, result: string, success: boolean, originalTask?: string): void {
let content = `**观察** - 工具 ${toolName} 执行结果:\n${result}`;
if (originalTask) {
content += `\n\n**提醒**: 你正在执行的原始任务是: "${originalTask}"`;
}
this.conversationHistory.push({ role: 'user', content });
}
// 历史裁剪,防止 token 超限
private trimHistory(): void {
if (this.conversationHistory.length > maxLength) {
const systemMessage = this.conversationHistory[0];
this.conversationHistory = [systemMessage, ...this.conversationHistory.slice(-maxLength)];
}
}
技术栈
| 组件 | 技术选型 |
|---|---|
| 运行时 | Node.js + TypeScript |
| CLI 框架 | Commander + Inquirer |
| AI 调用 | OpenAI SDK (兼容多种 API) |
| MCP 支持 | @modelcontextprotocol/sdk |
| 终端美化 | Chalk + Ora |
项目结构
mini-claude-code/
├── src/
│ ├── index.ts # 入口
│ ├── cli.ts # CLI 交互 + ReAct 循环
│ ├── ai-client.ts # AI 模型调用
│ ├── config.ts # 配置管理
│ ├── tools/ # 工具实现
│ │ ├── index.ts # 工具注册中心
│ │ ├── read-file.ts
│ │ ├── create-file.ts
│ │ ├── edit-file.ts
│ │ ├── shell.ts
│ │ ├── search-code.ts
│ │ ├── list-files.ts
│ │ └── list-tools.ts
│ └── mcp/ # MCP 支持
│ ├── index.ts
│ ├── types.ts
│ ├── client.ts # MCP 客户端
│ └── manager.ts # MCP 服务器管理
└── package.json
关键收获
1. ReAct 循环是核心
Code Agent 的本质是一个 ReAct 循环:思考 → 行动 → 观察 → 继续思考。这个循环让 AI 能够:
- 分解复杂任务为多个步骤
- 根据执行结果动态调整策略
- 处理错误并尝试修复
2. 工具是能力的边界
AI 的能力取决于它能使用的工具。好的工具设计应该:
- 功能单一、职责明确
- 参数清晰、易于理解
- 返回结构化的结果
3. Prompt 工程至关重要
System Prompt 决定了 AI 的行为模式:
- 明确的输出格式约定
- 清晰的规则约束
- 动态的工具列表
4. 上下文管理是难点
Agent 需要在多轮对话中保持"记忆":
- 原始任务不能丢失
- 工具结果需要正确反馈
- 历史需要适时裁剪
5. MCP 是扩展的未来
MCP 协议让 Agent 能力可以无限扩展:
- 标准化的工具接口
- 即插即用的服务器
- 统一的消费方式
总结
通过这次实践,我深刻理解了 Code Agent 产品的核心原理:
- ReAct 模式 - Agent 的工作方式
- 工具系统 - Agent 的能力边界
- System Prompt - Agent 的行为定义
- MCP 协议 - Agent 的扩展机制
- 对话管理 - Agent 的记忆系统
这个项目虽然简单,但麻雀虽小五脏俱全。它展示了一个 Code Agent 的完整架构,也为进一步学习和扩展打下了基础。