从零构建一个 AI Coding Agent:不会写代码?让 AI 帮你写

一个前端开发者花三个晚上,用 500 行代码教会 AI 自主编程的故事。


你有没有想过,Cursor 是怎么工作的?你给它一句话,它就能自动创建项目、写代码、装依赖、启动服务------整个过程你只需看着。

好奇心驱使下,我决定把这个过程"拆开看看"。不调用任何现成的 Agent 框架,只用最基础的 LangChain API,从零搭建一个最小可用的 AI 编程助手。

最终效果:在终端敲一行命令,Agent 就自动完成了创建 React 项目、编写组件、安装依赖、启动开发服务器的全流程。

css 复制代码
$ node src/main.js "用 pnpm 创建 todo 应用并启动"

🚀 Agent 启动

━━━ 第 1/15 轮 ━━━
🔧 模型请求调用 execute_command:
  ⚡ pnpm create vite react-todo-app --template react-ts (3.2s) ✅

━━━ 第 2/15 轮 ━━━
🔧 模型请求调用 write_file:
  ✔ react-todo-app/src/App.tsx → 3847 字符 ✅

...(几轮后)

🤖 Agent 最终回复:任务完成,服务器已启动

这篇文章记录了我从零构建这个项目的完整过程,以及踩过的每一个坑。


一、核心原理:ReAct 模式

Agent 的核心不是"让 AI 写代码",而是让 AI 在思考和行动之间循环

1.1 不是一次性生成

普通的 ChatGPT 对话是一次性的:你问,它答。

AI Coding Agent 不同。任务通常需要多步操作------创建项目 → 写文件 → 装依赖 → 启动服务 → 检查报错 → 修 bug → ...。每步的结果都可能影响下一步的决策。

这就需要一种循环机制:ReAct(Reasoning + Acting)

markdown 复制代码
用户任务
    ↓
模型思考 → 决定:调用 execute_command
    ↓
命令执行 → 返回结果:命令成功
    ↓
模型再思考 → 决定:调用 write_file
    ↓
文件写入 → 返回结果:写入成功
    ↓
模型继续思考 → 任务完成,返回总结文本

1.2 用代码实现这个循环

js 复制代码
// 这是整个 Agent 的核心------只用了 30 行代码
for (let i = 0; i < maxIterations; i++) {
    // 1. 把到目前为止的对话历史发给模型
    const response = await model.invoke(messages);

    // 2. 模型返回纯文本?任务完成
    if (!response.tool_calls || response.tool_calls.length === 0) {
        return response.content;
    }

    // 3. 模型想调用工具?逐个执行
    for (const toolCall of response.tool_calls) {
        const tool = tools.find(t => t.name === toolCall.name);
        const result = await tool.invoke(toolCall.args);

        // 4. 把执行结果作为新消息加入历史
        messages.push(new ToolMessage({
            content: result,
            tool_call_id: toolCall.id,
        }));
    }
    // 回到第 1 步,继续循环
}

这就是 ReAct 的全部。没有魔法,就是一个 while 循环。

1.3 终止条件

循环什么时候停?

  • 模型返回纯文本,没有 tool_calls → 任务完成,输出结果
  • 达到最大迭代次数(如 15 轮)→ 强制结束,防止死循环

二、给 AI 装上手脚:工具系统

光有"大脑"不够,还得让它能干活。我实现了四个工具:

工具 作用 关键实现
read_file 读取文件内容 fs.readFile(path, 'utf8')
write_file 写入文件(自动建目录) fs.mkdir(dir, {recursive}) + fs.writeFile()
list_directory 列出目录内容 fs.readdir(path)
exec_command 执行终端命令 spawn(cmd, args, {shell, stdio: 'inherit'})

每个工具都使用 LangChain 的 tool() 函数定义,Zod v4 做参数校验:

js 复制代码
import { tool } from '@langchain/core/tools'
import { z } from 'zod'

export const writeFileTool = tool(
    async ({ filePath, content }) => {
        const dir = path.dirname(filePath);
        await fs.mkdir(dir, { recursive: true });
        await fs.writeFile(filePath, content, 'utf8');
        return `成功写入 ${filePath}`;
    },
    {
        name: 'write_file',
        description: '向指定路径写入文件,自动创建父目录',
        schema: z.object({
            filePath: z.string().describe('文件路径'),
            content: z.string().describe('文件内容'),
        })
    }
)

LangChain 会把 Zod schema 转成 JSON Schema 发给模型,模型就知道这个工具需要什么参数了。


三、最难搞的部分:让 Agent 执行命令

write_fileread_file 都好说,Node.js 的 fs 模块足够。但 exec_command 是真正的坑。

3.1 为什么必须开子进程

Node.js 是一个 JS 运行环境,它本身不懂怎么执行 pnpm install

当 Agent 决定"我要装依赖",我的代码必须临时开一个子进程来跑这条命令:

js 复制代码
import { spawn } from 'node:child_process'

const childProcess = spawn('pnpm', ['install'], {
    cwd: '/project/react-todo-app',   // 在哪执行
    shell: 'bash.exe',                 // 用哪个 shell 解析命令
    stdio: 'inherit',                  // 输出直接在终端显示
});

spawn 本质上就是在代码里替你在终端敲了一行命令。

3.2 shell 到底是什么

敲到终端里的 pnpm install 只是一串字符。CPU 看不懂。

Shell(bash / cmd / PowerShell)是一个翻译官,负责把这串字符翻译成操作系统能理解的操作:找到 pnpm.exe → 启动它 → 把 install 传给它。

AI 生成的命令通常包含管道(|)、链式执行(&&),这些不是可执行文件的名字,而是 Shell 语法。所以必须有一个 Shell 进程来解析。

3.3 Windows 上的坑

我是 Windows 用户。Agent 跑的也是我的 Windows 电脑。

问题是:AI 生成的命令全是 Linux 语法(lscatecho -e),但 Windows 默认 Shell(cmd.exe)不兼容这些。

解决方案:显式指定 Git Bash 作为首选的 shell。

js 复制代码
shell: process.platform === 'win32'
    ? 'D:\\Git\\Git\\bin\\bash.exe'   // Windows:用 Git Bash
    : true,                            // Mac/Linux:默认 shell 就是 bash

Git Bash 是安装 Git for Windows 时附带的,本质上是把 Linux 的 bash 搬到了 Windows。

3.4 工具失败不崩溃

这是我踩的最大的坑。

最初版本中,命令执行失败就 reject()。结果:Agent 跑到一半崩溃,整个 Node 进程退出。

后来我改了策略:

js 复制代码
// ❌ 之前
if (code !== 0) {
    reject(`命令失败,退出码: ${code}`);  // Agent 直接死掉
}

// ✅ 现在
if (code !== 0) {
    resolve(`命令失败,退出码: ${code}`);  // 把错误作为文本返回给模型
}

工具失败时,返回描述性的错误文本(而不是抛异常)。模型看到错误后可以自己决定怎么做------这就是 ReAct 的核心价值:它不是死板的脚本,它能根据结果调整策略


四、项目架构:从意大利面条到模块化

第一版开发时,为了快速验证想法,所有代码都塞在 main.js 里。模型配置、Agent 循环、可视化日志、任务定义,全在一个 200 行的文件里。

验证通过后,我把它拆成了清晰的模块:

bash 复制代码
src/
├── main.js                  # 入口(30 行):解析参数,组装启动
├── config.js                # 配置中心(模型、超时、迭代次数)
├── agent/
│   └── ReactAgent.js        # 核心类:可复用的 ReAct 循环
├── prompts/
│   └── systemPrompt.js      # System prompt 模板
├── tools/                   # 四个工具
│   ├── index.js             # 工具注册中心(统一导出)
│   ├── readFileTool.js
│   ├── writeFileTool.js
│   ├── listDirTool.js
│   └── execCommandTool.js
└── utils/
    └── logger.js            # 统一 chalk 彩色日志

核心设计决策:

  • ReactAgent 类与 CLI 解耦:可以用在任何项目里,不绑定命令行
  • 工具是纯函数:只返回结果字符串,不做可视化(日志交给 Agent 层统一处理)
  • 所有配置支持环境变量覆盖:MODEL_NAME、BASE_URL、TIMEOUT、MAX_ITERATIONS 等

五、我学到了什么

关于 Agent 设计

  • ReAct 循环本身极其简单------就是一个 for 循环 + 消息管理。框架的价值在于工程化,不是算法
  • 工具的错误处理策略比工具本身更重要。不崩溃是底线
  • AI 写 bash 很强,写 PowerShell 很弱。技术选型要顺着 AI 来,而不是跟着系统默认走

关于 Node.js

  • spawn 是 Agent 项目里最复杂的部分。理解 shell、子进程、环境变量、工作目录的关系花了我最多时间
  • path.join() 代替手写斜杠不是挑剔,是跨平台必须
  • process.cwd() 是整个项目的"锚点"。所有路径都从它开始计算

关于 Windows 开发

  • AI 工具的生态是以 Linux/Mac 为中心的。在 Windows 上开发,得学会"假装在 Linux"------WSL 或 Git Bash
  • Git Bash 不是妥协,是最佳选择。它让 bash 语法在 Windows 上完美运行

六、下一步

这个项目是概念验证。未来可以扩展的方向:

  • 让 Agent 支持"自我修正"(看到终端报错后自动修 bug)
  • 添加上下文管理(长对话的 token 压缩)
  • 支持更多模型(Claude、GPT 等)
  • 增加任务模板,减少反复描述

仓库地址min-cursor

如果你也在学习 Agent 开发,或者对底层原理感兴趣,欢迎交流。


本文是 Min-Cursor 项目系列的第一篇。下一篇将深入探讨 Agent 中的子进程管理和 Shell 选择。

相关推荐
小Z加点糖1 小时前
开源 FlowScript:把 Skill 变成可执行、可检查、可回放的工作流
agent
学渣超1 小时前
微服务日志智能诊断系统(九) 让 LLM 成为日志分析专家
java·后端·agent
想要成为糕糕手1 小时前
从 0 到 1 打造 Coding Agent——mini cursor(终篇):装上命令行手脚,造一个会"干活"的 AI 程序员
langchain·agent·ai编程
leeyi1 小时前
让 Agent 说中文:Prompt 模板怎么管理(第47篇-E33)
aigc·agent·ai编程
不好听6131 小时前
让 Agent 开口说话:用 Chalk 实现可视化终端
agent
不好听6131 小时前
AI Agent 的四肢:Node.js 内置模块实战指南
agent
学渣超1 小时前
微服务日志智能诊断系统(七) 日志 RAG 引擎——蒸馏、索引与精准检索
java·后端·agent
掉鱼的猫1 小时前
代码审查 Agent Harness 实战:AI 自动 Code Review
java·llm·agent
学渣超1 小时前
微服务日志智能诊断系统(六) Fast Path vs Slow Path——双路径日志诊断策略
java·后端·agent