用 JavaScript 从零打造你的 AI 编程助手(仅100来行代码)

用 JavaScript 从零打造你的 AI 编程助手

所有代码及demo已放到仓库:github.com/underdog-s-...

我一直是 ai coding 的重度用户。看着屏幕上的光标自动飞舞、文件自动生成,我总觉得这背后一定有什么极其复杂的魔法。

作为一个开发者好奇它是怎么实现的。

结果让我大吃一惊:一旦你通过了迷雾,核心原理其实出奇地简单。 你不需要机器学习的博士学位,也不需要搞懂复杂的数学公式。你只需要懂一点 JavaScript,加上一个好的大模型 API。

在这篇深度教程中,我们将不依赖任何复杂的 Agent 框架(如 LangChain),仅使用 Node.js通义千问(魔搭社区) ,亲手通过"逆向工程"构建一个迷你版的 Claude Code。

它将具备以下能力:

  1. 读懂你的意图:不仅仅是聊天,而是理解你要做什么项目。
  2. 长出"双手" :能够真的在你的硬盘上创建文件、读取代码。
  3. 自我纠错:如果代码跑不通,它会看着报错信息自己修 bug。

准备好了吗?我们开始。


第一部分:概念篇 ------ Agent 是如何思考的?

在写代码之前,我们需要先理解"编程 Agent"的大脑结构。

普通的 ChatGPT 就像一个被锁在只有屏幕的房间里的智者 。你问它怎么写贪吃蛇,它会在屏幕上打出代码,但它碰不到你的键盘,没法帮你保存文件。

我们要做的,就是给这个智者装上**"双手""眼睛"**。

这背后的核心逻辑,被称为 ReAct 模式 (Reason, Act, Observe) 。这就好比你修理东西时的过程:

  1. Reason (思考) :"用户想做个贪吃蛇。我现在两手空空,我应该先建个文件夹,再建个 HTML 文件。"
  2. Act (行动) :(动手拿起工具) 创建 index.html
  3. Observe (观察) :(看一眼屏幕) "文件创建成功了。"
  4. 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. 思考 1:AI 意识到这需要三个文件。

  2. 行动 1 :它可能会同时发出 3 个 write_file 的指令。

    • ⚙️ Agent 正在执行: write_file -> index.html
    • ⚙️ Agent 正在执行: write_file -> style.css
    • ⚙️ Agent 正在执行: write_file -> game.js
  3. 观察 1:文件系统告诉它:"3 个文件都创建成功了。"

  4. 思考 2:AI 检查任务是否完成。"嗯,文件都在了,内容也写进去了。"

  5. 回复:"游戏已创建完成,您可以直接打开 index.html 试玩。"

4. 验证成果

现在,去你的文件夹里看看。你会发现多了三个文件。双击 index.html,一个完整的贪吃蛇游戏应该就能运行了!

5. 让它自己修 Bug (自我进化)

假如(我是说假如)游戏里的蛇跑得太快了,或者是绿色的你想改成红色的。

不需要你自己改代码。直接告诉它:

"蛇的颜色太丑了,把蛇改成深红色的。另外,游戏速度太快了,帮我调慢一点。"

Agent 会:

  1. 调用 read_file 去读 style.cssgame.js
  2. 找到控制颜色的 CSS 类和控制速度的 JS 变量。
  3. 调用 write_file 更新这两个文件。

这就是全自动编程的雏形。


总结

恭喜你!你刚刚用不到 150 行 JavaScript 代码,构建了一个拥有"大脑"和"双手"的 AI 工程师。

回顾一下我们学到的:

  • Agent 不是魔法 :它只是一个 while 循环,不断地把工具的执行结果喂给大模型。
  • 工具定义很重要:你定义的工具越强大(比如给它运行命令的能力),Agent 就越强大。
  • Prompt 是灵魂:System Prompt 决定了它是像个实习生一样乱写,还是像资深架构师一样规划文件结构。

这个小小的 agent.js 是通往未来的钥匙。你可以在此基础上添加更多工具:让它能搜索 Google、能运行 npm install、甚至能部署服务器。

现在,去创造你自己的超级员工吧!

相关推荐
~央千澈~34 分钟前
【06】原创音乐人完整的原创音乐全流程制作-歌词创作-用心创作-完善创作-卓伊凡
网络·人工智能·au·cubase
FreeBuf_35 分钟前
恶意npm包利用隐藏提示和脚本规避AI安全工具检测
人工智能·安全·npm
超龄超能程序猿36 分钟前
LabelImage+YOLOv8 图片单一目标检测模型训练
人工智能·yolo·目标检测
周杰伦_Jay36 分钟前
【免费云平台部署指南】按场景选型+全维度对比(附直达地址)
网络·人工智能·架构·开源·云计算
还不秃顶的计科生37 分钟前
谈一谈多模态
人工智能
lally.37 分钟前
CIFAR-10图像识别(从0开始学机器学习系列)
人工智能·机器学习
乾元37 分钟前
多厂商配置对齐器:AI 如何在 Cisco / Huawei / Juniper 间做语义映射
运维·开发语言·网络·人工智能·网络协议·华为·智能路由器
高洁0140 分钟前
卷积神经网络(CNN)
人工智能·python·深度学习·神经网络·transformer
墨染星辰云水间41 分钟前
机器学习(二)
人工智能·机器学习