30行代码实现AI编程智能体:从ReAct原理到实战应用

📅 2026/8/12 15:46:12
30行代码实现AI编程智能体:从ReAct原理到实战应用
1. 项目概述从“用”到“造”的认知跃迁最近和几个搞开发的朋友聊天发现一个挺有意思的现象大家用 Cursor 这类 AI 编程工具用得飞起天天“CmdK”让 AI 写代码、改 Bug但一聊到它到底是怎么工作的尤其是背后那个能理解需求、规划步骤、执行代码的“智能体”Agent核心很多人就有点含糊了。这感觉就像天天开自动挡的车却从没打开过引擎盖看看里面的变速箱是怎么换挡的。所以我决定动手“造”一个看看。不是要复刻一个完整的 Cursor那工程量太大了。我的目标是用最精简的代码——目标 30 行左右——把驱动 AI 程序员Agent最核心的那个“思考-行动”循环给实现出来。这个过程本质上是在拆解一个复杂系统的“第一性原理”当我们让 AI 去完成一个编程任务时它的大脑里究竟发生了什么代码是如何被生成、验证和迭代的这个微型项目非常适合想深入理解 AI Agent 原理的开发者无论你是前端、后端还是全栈。你不需要精通机器学习但需要对 Node.js 和调用 API 有基本了解。通过这三十行代码你将能亲手触摸到 AI 自动编程的“引擎”理解其工作流、决策逻辑和边界所在。这远比单纯使用工具更有价值它能让你在未来设计、评估乃至调试 AI 编程应用时拥有降维打击的认知优势。2. 核心原理拆解Agent 的“思考-行动”循环要造一个 AI 程序员我们首先得把它抽象成一个可计算的模型。现代 AI 编程 Agent无论是 Cursor 的核心还是 GitHub Copilot Workspace 的幕后其思想大多源于 ReActReasoning Acting框架以及更早的“规划-执行”智能体范式。我们可以将其核心循环提炼为以下四步任务理解与规划接收用户的自然语言指令如“写一个函数计算斐波那契数列”将其分解为一系列可执行的原子步骤或子目标。工具调用与执行根据规划选择并调用合适的“工具”。在编程上下文中最核心的工具就是“代码解释器”Code Interpreter和“文件系统操作”读、写、列出文件。观察与推理获取工具执行的结果如代码运行输出、文件内容、错误信息结合当前上下文分析任务完成度并决定下一步行动。循环与终止重复“规划-执行-观察”的循环直到任务被判定为完成或达到最大迭代次数。这个循环的“智能”来源于驱动它的大型语言模型LLM。LLM 在这里扮演着“大脑”的角色负责理解、规划、决策和生成代码。而我们构建的 Agent 系统则是为这个“大脑”配备“四肢”工具和一套“行动准则”循环逻辑。一个常见的误解是AI 程序员只是一次性生成一大段代码。实际上高效的 Agent 更像一个严谨的工程师它会边写边测通过运行代码来验证其正确性根据错误信息进行调试这种“执行-反馈”的闭环才是其强大适应性的关键。我们的 30 行核心代码就是要搭建起这个闭环的最小可行系统。3. 环境准备与工具选型为了实现这个微型 Agent我们需要选择最直接、最轻量的技术栈。核心是两个方面一个强大的“大脑”LLM和一套连接大脑与工具的“神经系统”。3.1 LLM 服务接入选择性价比高的“大脑”对于这个实验项目我们不需要本地部署百亿参数的大模型。使用云 API 是最快、最经济的方式。目前市面上有多家提供者我们的选择标准是API 稳定、响应速度快、且足够便宜甚至免费。OpenAI GPT-4/3.5-Turbo生态最成熟文档齐全但需要海外支付方式且产生费用。Anthropic Claude在长上下文和逻辑推理上表现优异同样需要付费。国内大模型 API如 DeepSeek、智谱 GLM、月之暗面 Kimi访问速度快符合本地网络环境很多提供免费的额度非常适合实验。例如DeepSeek 的 Chat API 免费且支持 128K 上下文完全能满足我们的需求。在本项目中我将以DeepSeek 的 API为例进行演示因为它对开发者友好免费额度充足。你需要去其官方平台注册账号并获取一个 API Key。3.2 开发环境与依赖安装我们的运行环境是 Node.js版本 18 即可利用其非阻塞 I/O 特性方便地进行网络请求和子进程操作。首先创建一个新的项目目录并初始化mkdir mini-ai-programmer cd mini-ai-programmer npm init -y然后安装核心依赖npm install axiosaxios一个优秀的 HTTP 客户端库用于调用 DeepSeek 的 API。相比原生的fetch它在 Node.js 环境中兼容性更好错误处理也更方便。我们不需要复杂的 Agent 框架如 LangChain、LlamaIndex因为我们就是要从零理解原理。文件系统操作fs模块和运行代码child_process模块这些“工具”Node.js 本身就提供了。注意选择 API 时务必仔细阅读其官方文档的速率限制和使用条款。免费额度虽好但频繁测试也可能触发限制。建议在代码中加入简单的延时避免短时间大量请求。4. 30行核心代码逐行解析下面就是整个项目的核心agent.js文件内容。我将它分为几个逻辑块并逐行解释其作用。// agent.js const axios require(axios); const fs require(fs).promises; const { exec } require(child_process); const { promisify } require(util); const execAsync promisify(exec); // 将 exec 转为 Promise 风格便于使用 async/await const API_KEY 你的-DeepSeek-API-Key; // 替换为你的实际 Key const API_URL https://api.deepseek.com/v1/chat/completions; class MiniAIAgent { constructor() { this.conversationHistory []; // 保存与LLM的对话历史 this.maxIterations 5; // 防止无限循环 } // 核心方法与LLM对话 async chatWithLLM(messages) { try { const response await axios.post(API_URL, { model: deepseek-chat, // 指定模型 messages: messages, temperature: 0.1, // 低温度让输出更确定、更专注于代码 }, { headers: { Authorization: Bearer ${API_KEY}, Content-Type: application/json } }); return response.data.choices[0].message.content; } catch (error) { console.error(调用LLM API失败:, error.response?.data || error.message); return null; } } // 核心循环执行“思考-行动”循环 async run(task) { console.log(开始任务: ${task}); let iteration 0; // 初始化系统提示定义Agent的角色和能力 this.conversationHistory [ { role: system, content: 你是一个AI程序员助手。请严格按以下步骤工作 1. 理解用户任务。 2. 规划步骤。如果需要写代码请生成一个完整的、可独立运行的Node.js代码块。 3. 代码必须用 \\\javascript ... \\\ 格式包裹。 4. 一次只做一步明确的行动。 }, { role: user, content: task } ]; while (iteration this.maxIterations) { iteration; console.log(\n--- 第 ${iteration} 轮思考 ---); // 1. 思考/规划询问LLM下一步该做什么 const llmResponse await this.chatWithLLM(this.conversationHistory); if (!llmResponse) break; console.log(AI 回复:, llmResponse); this.conversationHistory.push({ role: assistant, content: llmResponse }); // 2. 解析与行动检查回复中是否包含可执行的代码 const codeMatch llmResponse.match(/javascript\n([\s\S]*?)/); if (codeMatch) { const codeToRun codeMatch[1].trim(); console.log(检测到代码开始执行...); const result await this.executeCode(codeToRun); console.log(执行结果:, result); // 3. 观察将执行结果反馈给LLM作为下一轮思考的输入 this.conversationHistory.push({ role: user, content: 我执行了你的代码。结果如下\n${result}\n请根据结果分析任务是否完成或进行下一步。 }); } else { // 如果没有检测到代码可能是任务完成或需要用户进一步输入 console.log(未检测到可执行代码。任务可能已完成或需要澄清。); const userFeedback await this.getUserFeedback(); if (userFeedback done) break; this.conversationHistory.push({ role: user, content: userFeedback }); } } console.log(\n--- 任务循环结束 ---); } // 工具函数执行生成的JavaScript代码 async executeCode(code) { // 将代码写入临时文件 const tempFile temp_${Date.now()}.js; await fs.writeFile(tempFile, code); try { // 使用 node 命令执行临时文件 const { stdout, stderr } await execAsync(node ${tempFile}); // 清理临时文件 await fs.unlink(tempFile); return 标准输出:\n${stdout}\n${stderr ? 标准错误:\n${stderr} : }; } catch (error) { await fs.unlink(tempFile).catch(() {}); // 尝试清理忽略错误 return 执行出错:\n${error.stderr || error.message}; } } // 工具函数获取简单用户反馈模拟 async getUserFeedback() { // 这里简化处理真实场景可以连接更复杂的交互 return new Promise((resolve) { // 模拟AI判断任务完成或我们手动终止 // 在实际增强版中这里可以接入读取终端输入 if (this.conversationHistory.length 4) { // 简单模拟条件 resolve(done); } else { resolve(请继续。); } }); } } // 启动Agent const agent new MiniAIAgent(); agent.run(写一个Node.js函数计算前10个斐波那契数并打印出来。);关键代码块解析初始化与配置第1-10行引入必要的模块。promisify(exec)是关键一步它把 Node.js 传统的回调式exec函数变成了返回 Promise 的execAsync这样我们就可以在async/await函数中优雅地执行系统命令比如运行 AI 生成的代码。chatWithLLM方法第15-30行这是 Agent 的“感官输入”通道。它构造了一个符合 DeepSeek API 格式的请求。注意temperature: 0.1这个参数它控制输出的随机性。在代码生成任务中我们通常希望输出稳定、确定所以设置一个较低的值。错误处理是必须的要避免一次 API 调用失败就导致整个 Agent 崩溃。run方法 - 系统提示词第38-45行这是 Agent 的“人格”和“工作手册”。系统提示词System Prompt的质量直接决定了 LLM 的行为模式。我们明确要求它按步骤工作、生成完整代码、使用指定格式、一次只做一步。清晰的指令能极大减少 LLM 的“胡言乱语”。run方法 - 主循环第47-84行这是“思考-行动”循环的实体。思考将包含所有历史消息的conversationHistory发送给 LLM获取它的回复。行动用正则表达式javascript\n([\s\S]*?)从回复中提取代码块。这个正则匹配被三个反引号包裹的 JavaScript 代码。观察调用executeCode工具执行提取的代码并将标准输出stdout和标准错误stderr全部捕获作为观察结果。这一点至关重要AI 通过错误信息stderr来学习调试就像程序员看报错一样。反馈将执行结果作为新一轮的用户输入推回对话历史。这样LLM 在下一轮就能基于代码的实际运行结果进行推理和规划。executeCode方法第87-102行这是 Agent 的“手”。它把代码字符串写入一个临时文件然后用node命令运行它。使用临时文件而非eval是为了安全性和更好的错误隔离。eval在全局作用域执行容易引发意外且错误堆栈不清晰。一定要做好文件清理unlink避免产生大量垃圾文件。启动第115-116行实例化 Agent 并给它第一个任务。我们用一个经典的编程面试题“斐波那契数列”来测试它。实操心得系统提示词是 Agent 的“灵魂”。在实验时不妨多调整提示词。例如加上“如果你认为任务已完成请明确说出‘任务完成’”可以让 Agent 更好地终止循环。另外正则表达式提取代码虽然简单但很脆弱。更健壮的方法是要求 LLM 以严格的 JSON 格式输出包含action和code字段这将是下一步优化的方向。5. 运行测试与结果分析现在让我们运行这个 Agent看看它如何工作。在终端执行node agent.js你会看到类似以下的输出流具体内容因 AI 回答而异开始任务: 写一个Node.js函数计算前10个斐波那契数并打印出来。 --- 第 1 轮思考 --- AI 回复: 我将为您编写一个计算前10个斐波那契数的Node.js函数。斐波那契数列的前两项是0和1有时是1和1这里按01开始后续每一项是前两项之和。 javascript function fibonacci(n) { if (n 0) return []; if (n 1) return [0]; const fib [0, 1]; for (let i 2; i n; i) { fib.push(fib[i - 1] fib[i - 2]); } return fib; } const result fibonacci(10); console.log(前10个斐波那契数:, result);检测到代码开始执行... 执行结果: 标准输出: 前10个斐波那契数: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]--- 第 2 轮思考 --- AI 回复: 代码已成功执行。输出显示前10个斐波那契数为 [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]。任务已完成。 未检测到可执行代码。任务可能已完成或需要澄清。--- 任务循环结束 ---**过程分析** 1. **第一轮**Agent 接收到任务LLM 理解了需求并直接生成了完整的、可运行的 Node.js 代码。代码被成功提取并执行输出了正确的结果 [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]。这个结果被反馈给 LLM。 2. **第二轮**LLM 收到了上一轮的输出即执行结果并进行分析。它判断“代码已成功执行...任务已完成”因此在回复中没有生成新的代码块。我们的 Agent 检测不到代码并根据简单的模拟逻辑这里我们模拟它判断任务完成结束了循环。 整个过程中Agent 自动完成了“理解需求 - 生成代码 - 执行验证 - 分析结果 - 结束任务”的完整闭环。这就是一个最简化的 AI 程序员的工作流程。 你可以尝试更复杂的任务比如“**读取当前目录下的 package.json 文件并解析出它的 name 和 version 字段然后打印**”。观察 Agent 如何规划它可能会先生成读取文件的代码运行后发现文件不存在或格式错误然后根据错误信息再生成新的代码进行调试。这正是 Cursor 等工具在背后为你做的事情。 ## 6. 从原型到产品深入探索与优化方向 我们这个 30 行的原型揭示了核心原理但距离一个健壮的、可用的 AI 编程助手还有巨大差距。Cursor 等成熟产品的背后是一系列复杂得多的工程实现。基于这个原型我们可以从以下几个方向进行深化理解 **6.1 工具扩展给 Agent 装上更多“瑞士军刀”** 目前我们的 Agent 只有“运行 JS 代码”这一把锤子。一个真正的编程助手需要丰富的工具集 * **文件操作工具**不只是读还要能写、创建、删除、列出文件。这需要封装 fs 模块的更多功能。 * **Shell 命令工具**运行 git 命令、npm install、ls 等让 Agent 能进行版本控制和包管理。 * **网络请求工具**让 Agent 能获取 API 数据、下载依赖等。 * **代码静态分析工具**集成 ESLint、Prettier 等在运行前检查代码质量。 每个工具都可以被定义为一个函数LLM 通过一个“工具描述列表”来知道它能调用什么。当 LLM 决定使用某个工具时它会生成一个结构化的调用请求如 {“action”: “write_file”, “path”: “./test.js”, “content”: “...”}由 Agent 来解析并执行对应的函数。 **6.2 规划与反思从单步执行到复杂任务分解** 对于“创建一个 Express 服务器并连接 MongoDB”这样的复杂任务LLM 需要先进行任务分解Task Decomposition。这可以通过在系统提示词中要求其先输出规划步骤或者使用专门的“规划器”LLM 来实现。更高级的 Agent 还具有“反思”Reflection能力即对自己之前的行动和结果进行评估判断是否偏离目标从而调整后续策略。 **6.3 状态管理与记忆** 我们的原型用 conversationHistory 简单记录了对话这是短期记忆。对于长会话需要更精细的记忆管理 * **短期记忆**当前的对话上下文。 * **长期记忆**向量数据库。可以将项目的重要信息如架构文档、核心 API 说明转换成向量存储起来当 Agent 需要相关知识时进行语义搜索并注入上下文。 * **工作记忆**当前任务相关的文件内容、已执行的操作等。 **6.4 错误处理与韧性** 工业级 Agent 必须有强大的错误处理能力 * **API 失败重试**LLM 调用可能因网络或限流失败需要指数退避重试机制。 * **代码安全沙箱**绝不能在生产环境直接 exec 不可信的 AI 生成代码必须使用 Docker 容器或安全的沙箱环境如 vm2 模块进行隔离执行。 * **死循环检测**除了最大迭代次数还要检测对话是否陷入无意义的循环如反复生成同样的错误代码。 **6.5 与 Cursor 的对比思考** Cursor 将上述所有复杂模块进行了高度集成和封装提供了丝滑的用户体验 * **深度编辑器集成**它不是一个独立的进程而是深度嵌入 VS Code能直接操作编辑器文本、理解项目结构、引用定义。 * **丰富的上下文**它能自动将你打开的文件、相关的错误信息、终端输出作为上下文喂给 LLM无需手动指定。 * **多模态交互**除了聊天还可以通过快捷键CmdK直接编辑选中代码通过“”符号引用特定文件。 * **工程化优化**在背后它可能使用了比简单循环更复杂的 Agent 架构如分层代理、专门用于代码生成的微调模型等。 理解了我们这个简单原型再去看 Cursor你就会明白它每一个便捷功能背后对应的技术模块是什么从而能更高效地利用它甚至在它“犯傻”时能大致猜到问题可能出在哪个环节是上下文不够还是工具调用错了。 ## 7. 常见问题与实战调试技巧 在实现和实验这个微型 Agent 的过程中你肯定会遇到各种问题。下面是一些典型问题及其解决思路 **7.1 LLM 不按格式输出代码** * **现象**AI 回复是纯文本解释没有用 javascript ... 包裹代码。 * **原因**系统提示词不够强硬或者 LLM 的“温度”temperature参数太高导致输出随机性大。 * **解决** 1. 强化系统提示词例如“**你必须**将生成的代码用 javascript ... 格式包裹。这是指令不是建议。” 2. 将 temperature 参数降至 0.1 甚至 0。 3. 在代码解析环节增加容错性比如尝试匹配 js ... 或 ... 。 **7.2 代码执行陷入无限循环或错误循环** * **现象**AI 反复生成有同样错误的代码或者生成一个死循环代码导致 exec 超时。 * **原因**LLM 没有从错误反馈中有效学习或者任务本身有歧义。 * **解决** 1. **改进反馈**在将错误信息反馈给 LLM 时加入更明确的指引。例如“上面的代码执行时出现了 ReferenceError: xxx is not defined 错误。请先分析错误原因修正代码后再重新生成。” 2. **设置超时**在 execAsync 外包裹一个 Promise.race设置执行超时如 10 秒超时后强制终止进程并返回“执行超时”的错误信息。 3. **人工干预**在循环中加入手动检查点或者在 getUserFeedback 函数中实现真正的终端输入让人来判断是否继续。 **7.3 API 调用速度慢或超时** * **现象**等待 LLM 回复时间过长影响交互体验。 * **原因**网络延迟或模型本身响应慢。 * **解决** 1. **使用流式响应**如果 API 支持使用 Server-Sent Events (SSE) 流式获取回复可以让用户看到生成过程感知上更快。 2. **模型降级**对于不需要顶级推理能力的步骤如简单的代码格式化可以换用更小、更快的模型如 DeepSeek 的 lightweight 模型。 3. **客户端优化**在等待时显示加载动画提升用户体验。 **7.4 生成的代码有安全风险** * **现象**AI 生成了诸如 rm -rf / 或访问敏感文件的代码。 * **原因**LLM 在训练数据中见过此类代码且系统提示词未做安全限制。 * **解决** 1. **沙箱隔离**这是**必须**的。在服务器端永远在 Docker 容器或安全的子进程中运行 AI 生成的代码。在 Node.js 中可以研究 worker_threads 配合严格限制或使用 vm2 这类沙箱库。 2. **提示词约束**在系统提示词开头加入强硬的安全指令例如“你生成的代码绝不能尝试删除文件、访问网络、或执行任何可能危害系统的操作。你只能操作当前工作目录下的文件。” 3. **输出过滤**在执行前对生成的代码进行简单的静态分析检查是否有危险的关键字或模式。 **7.5 如何处理复杂的、多文件的项目任务** 这是当前微型 Agent 的短板。一个可行的进阶思路是 1. **增强上下文**在对话历史中不仅包含对话还包含当前相关文件的内容。可以在每轮思考前自动将工作目录的文件列表或指定文件的内容作为“系统”或“用户”消息的一部分发送给 LLM。 2. **实现文件树工具**给 Agent 增加 list_files 和 read_file 工具。当 LLM 需要了解项目结构时它可以主动调用这些工具。 3. **分步规划**对于“创建项目”这类任务要求 LLM 先输出一个步骤清单如 1. 创建 package.json 2. 安装依赖 3. 创建 index.js然后 Agent 引导 LLM 逐步完成每一步。 调试这样的 Agent 系统核心是 **“打开黑盒”**。你需要详细记录每一轮的输入给 LLM 的完整消息和输出LLM 的回复、工具执行结果。当出现问题时首先检查这个日志看是 LLM 的理解出了问题还是工具执行出错或者是循环逻辑有缺陷。把这个日志想象成 AI 程序员的“思维链”它是你进行调试的最重要依据。