用 JavaScript 从零打造你的 AI 编程助手
所有代码及demo已放到仓库:github.com/underdog-s-...
我一直是 ai coding 的重度用户。看着屏幕上的光标自动飞舞、文件自动生成,我总觉得这背后一定有什么极其复杂的魔法。
作为一个开发者好奇它是怎么实现的。
结果让我大吃一惊:一旦你通过了迷雾,核心原理其实出奇地简单。 你不需要机器学习的博士学位,也不需要搞懂复杂的数学公式。你只需要懂一点 JavaScript,加上一个好的大模型 API。
在这篇深度教程中,我们将不依赖任何复杂的 Agent 框架(如 LangChain),仅使用 Node.js 和 通义千问(魔搭社区) ,亲手通过"逆向工程"构建一个迷你版的 Claude Code。
它将具备以下能力:
- 读懂你的意图:不仅仅是聊天,而是理解你要做什么项目。
- 长出"双手" :能够真的在你的硬盘上创建文件、读取代码。
- 自我纠错:如果代码跑不通,它会看着报错信息自己修 bug。
准备好了吗?我们开始。
第一部分:概念篇 ------ Agent 是如何思考的?
在写代码之前,我们需要先理解"编程 Agent"的大脑结构。
普通的 ChatGPT 就像一个被锁在只有屏幕的房间里的智者 。你问它怎么写贪吃蛇,它会在屏幕上打出代码,但它碰不到你的键盘,没法帮你保存文件。
我们要做的,就是给这个智者装上**"双手"和"眼睛"**。
这背后的核心逻辑,被称为 ReAct 模式 (Reason, Act, Observe) 。这就好比你修理东西时的过程:
- Reason (思考) :"用户想做个贪吃蛇。我现在两手空空,我应该先建个文件夹,再建个 HTML 文件。"
- Act (行动) :(动手拿起工具) 创建
index.html。 - Observe (观察) :(看一眼屏幕) "文件创建成功了。"
- Loop (循环) :"好,接下来我需要往里面写代码......"
我们的代码,就是去实现这个循环。
第二部分:准备工作
我们需要一个干净的 Node.js 环境。打开你的终端(Terminal):
perl
# 1. 创建文件夹
mkdir my-ai-coder
cd my-ai-coder
# 2. 初始化项目 (一路回车即可)
npm init -y
# 3. 安装依赖
# 我们使用 openai 官方库,因为它支持所有open兼容的apikey包括有免费额度的魔搭(modelscope)
npm install openai
创建一个名为 agent.js 的文件。这将是我们唯一的代码文件。
第三部分:一步步构建大脑
1. 定义"大脑"和"性格"
首先,我们要初始化大模型客户端。这里我们使用阿里云的通义千问 (Qwen-Max) ,因为它是目前中文编程能力最强的模型之一,而且 API 兼容性极好。
在 agent.js 顶部写入:
ini
const OpenAI = require('openai');
const fs = require('fs').promises; // 用于文件操作
const path = require('path');
const readline = require('readline');
// --- 配置区域 ---
// 这里定义 Agent 的"人设"。这非常重要,决定了它是否专业。
const SYSTEM_PROMPT = `你是一个世界级的全栈 JavaScript 编程专家。
你的目标是协助用户构建高质量、结构清晰的 Web 应用。
你的行为准则:
1. **实干家精神**:不要只给建议,要实际调用工具去创建和修改文件。
2. **多文件架构**:构建应用时,习惯将 HTML、CSS、JS 分离,除非用户要求单文件。
3. **结果导向**:每一步行动后,观察结果。如果出错,尝试通过读取文件分析原因并修复。
4. **完工汇报**:任务完成后,简要汇报你做了什么,并告诉用户如何运行。`;
2. 赋予它"双手" (定义工具)
大模型本身不知道什么是"文件"。我们需要定义一组工具 (Tools) ,就像给它递上一把瑞士军刀。
我们需要三个核心工具:
list_files: 看看当前目录下有啥。read_file: 看看文件里写了啥。write_file: 往文件里写东西(或者新建文件)。
matlab
// --- 工具定义 (给 AI 看的说明书) ---
const TOOLS_SCHEMA = [
{
type: "function",
function: {
name: "list_files",
description: "查看当前目录下的文件列表,用于了解项目结构。",
parameters: { type: "object", properties: { path: { type: "string", default: "." } } }
}
},
{
type: "function",
function: {
name: "read_file",
description: "读取某个文件的具体内容,用于检查代码或理解现有逻辑。",
parameters: {
type: "object",
properties: { path: { type: "string", description: "文件路径" } },
required: ["path"]
}
}
},
{
type: "function",
function: {
name: "write_file",
description: "创建新文件或覆盖现有文件。支持自动创建不存在的目录。",
parameters: {
type: "object",
properties: {
path: { type: "string", description: "文件路径 (例如: js/app.js)" },
content: { type: "string", description: "完整的代码内容" }
},
required: ["path", "content"]
}
}
}
];
3. 实现工具逻辑 (肌肉记忆)
光有说明书不行,我们得写出真正的 JavaScript 代码来执行这些操作。
javascript
// --- Agent 类:核心逻辑 ---
class CodingAgent {
constructor(apiKey) {
// 连接到魔搭社区 (DashScope)
this.client = new OpenAI({
apiKey: apiKey,
baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1"
});
this.model = "qwen-max"; // 使用最强模型
this.workDir = process.cwd(); // 锁定在当前目录工作
// 记忆系统:存储对话历史
this.history = [
{ role: "system", content: SYSTEM_PROMPT }
];
}
// 1. 实现"写文件"的动作
async _writeFile({ path: filePath, content }) {
try {
const fullPath = path.resolve(this.workDir, filePath);
// 自动创建文件夹,比如写入 'css/style.css' 时自动创建 'css' 目录
await fs.mkdir(path.dirname(fullPath), { recursive: true });
await fs.writeFile(fullPath, content, 'utf-8');
return `✅ 成功: 文件 ${filePath} 已写入。`;
} catch (e) {
return `❌ 错误: 写入文件失败 - ${e.message}`;
}
}
// 2. 实现"读文件"的动作
async _readFile({ path: filePath }) {
try {
const content = await fs.readFile(path.resolve(this.workDir, filePath), 'utf-8');
return content;
} catch (e) {
return `❌ 错误: 读取文件失败 - ${e.message}`;
}
}
// 3. 实现"看目录"的动作
async _listFiles({ path: dirPath = "." }) {
try {
const files = await fs.readdir(path.resolve(this.workDir, dirPath));
return JSON.stringify(files);
} catch (e) {
return `❌ 错误: 无法列出文件 - ${e.message}`;
}
}
// --- 工具调度中心 ---
// 当 AI 想要用工具时,这个函数负责把请求分发给上面具体的函数
async _executeTool(toolCall) {
const fnName = toolCall.function.name;
const args = JSON.parse(toolCall.function.arguments);
console.log(`⚙️ Agent 正在执行: ${fnName} -> ${args.path || ''}`);
let result;
if (fnName === 'list_files') result = await this._listFiles(args);
else if (fnName === 'read_file') result = await this._readFile(args);
else if (fnName === 'write_file') result = await this._writeFile(args);
else result = "错误: 未知的工具";
// 返回标准格式的结果
return {
tool_call_id: toolCall.id,
role: "tool",
name: fnName,
content: result
};
}
4. 注入灵魂:ReAct 循环
这是最关键的一步。我们需要建立一个循环:AI 思考 -> AI 决定用工具 -> 我们执行工具 -> 把结果告诉 AI -> AI 继续思考。
如果 AI 不需要用工具了,就说明任务完成了,循环结束。
javascript
// --- 思考与行动循环 ---
async chat(userContent) {
// 1. 把用户的指令加入记忆
this.history.push({ role: "user", content: userContent });
let turnCount = 0;
const MAX_TURNS = 15; // 防止 AI 陷入死循环,最多思考15轮
while (turnCount < MAX_TURNS) {
turnCount++;
process.stdout.write("🤖 Agent 思考中...");
// 2. 向 AI 提问
const response = await this.client.chat.completions.create({
model: this.model,
messages: this.history,
tools: TOOLS_SCHEMA, // 把工具箱递给它
tool_choice: "auto" // 让它自己决定用不用工具
});
const message = response.choices[0].message;
this.history.push(message); // 记住 AI 的回答
process.stdout.write("\r"); // 清除"思考中"提示
// 3. 核心判断:AI 是想说话,还是想干活?
if (message.tool_calls && message.tool_calls.length > 0) {
// -> AI 决定干活 (Action)
console.log(`\n⚡ Agent 决定执行 ${message.tool_calls.length} 个操作...`);
// 并行执行所有操作 (比如同时写 HTML 和 CSS)
const toolResults = await Promise.all(
message.tool_calls.map(toolCall => this._executeTool(toolCall))
);
// 把结果存入历史 (Observe)
this.history.push(...toolResults);
// 循环继续... AI 将看到这些结果,并决定下一步做什么
} else {
// -> AI 决定说话 (Finish)
console.log(`\n🟢 Agent 回复:\n${message.content}\n`);
return; // 任务结束,退出循环
}
}
}
}
5. 最后的拼图:交互界面
最后,我们需要一个简单的界面来启动它。
javascript
// --- 启动程序 ---
async function main() {
console.log("============================================");
console.log(" 🚀 JavaScript AI Coding Agent 启动中... ");
console.log("============================================");
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
const ask = (q) => new Promise(r => rl.question(q, r));
// 获取 API Key
const apiKey = process.env.DASHSCOPE_API_KEY || await ask("🔑 请输入你的 DashScope API Key: ");
const agent = new CodingAgent(apiKey);
while (true) {
const input = await ask("\n👨💻 请下达指令 (输入 exit 退出): ");
if (input === 'exit') break;
if (!input.trim()) continue;
try {
await agent.chat(input);
} catch (error) {
console.error("❌ 系统错误:", error.message);
}
}
rl.close();
}
if (require.main === module) main();
第四部分:见证奇迹 ------ 制作一个贪吃蛇游戏
现在,代码已经全部完成。让我们看看它是如何工作的。
1. 启动 Agent
在终端运行:
node agent.js
2. 下达复杂的指令
不要只说"写个游戏"。我们要给它一个类似产品经理的需求文档,测试它的架构能力:
"请帮我开发一个网页版贪吃蛇游戏。我需要清晰的项目结构,请分别创建
index.html(只有结构)、style.css(负责美观的样式)和game.js(负责逻辑)。写完后请告诉我。"
3. 观察它的思考过程
你会看到控制台疯狂输出日志,这正是 ReAct 循环 在运转:
-
思考 1:AI 意识到这需要三个文件。
-
行动 1 :它可能会同时发出 3 个
write_file的指令。⚙️ Agent 正在执行: write_file -> index.html⚙️ Agent 正在执行: write_file -> style.css⚙️ Agent 正在执行: write_file -> game.js
-
观察 1:文件系统告诉它:"3 个文件都创建成功了。"
-
思考 2:AI 检查任务是否完成。"嗯,文件都在了,内容也写进去了。"
-
回复:"游戏已创建完成,您可以直接打开 index.html 试玩。"
4. 验证成果
现在,去你的文件夹里看看。你会发现多了三个文件。双击 index.html,一个完整的贪吃蛇游戏应该就能运行了!
5. 让它自己修 Bug (自我进化)
假如(我是说假如)游戏里的蛇跑得太快了,或者是绿色的你想改成红色的。
不需要你自己改代码。直接告诉它:
"蛇的颜色太丑了,把蛇改成深红色的。另外,游戏速度太快了,帮我调慢一点。"
Agent 会:
- 调用
read_file去读style.css和game.js。 - 找到控制颜色的 CSS 类和控制速度的 JS 变量。
- 调用
write_file更新这两个文件。
这就是全自动编程的雏形。
总结
恭喜你!你刚刚用不到 150 行 JavaScript 代码,构建了一个拥有"大脑"和"双手"的 AI 工程师。
回顾一下我们学到的:
- Agent 不是魔法 :它只是一个
while循环,不断地把工具的执行结果喂给大模型。 - 工具定义很重要:你定义的工具越强大(比如给它运行命令的能力),Agent 就越强大。
- Prompt 是灵魂:System Prompt 决定了它是像个实习生一样乱写,还是像资深架构师一样规划文件结构。
这个小小的 agent.js 是通往未来的钥匙。你可以在此基础上添加更多工具:让它能搜索 Google、能运行 npm install、甚至能部署服务器。
现在,去创造你自己的超级员工吧!