手写一个「迷你 Cursor」:从零构建 AI 编程助手(LangChain + Tool Calling 实战)

在人工智能技术飞速发展的今天,AI Agent(智能体) 已成为软件工程领域的新焦点。从自动化的代码审查到端到端的应用生成,大语言模型(LLM)正逐渐从单纯的“对话者”转变为能够执行复杂任务的“行动者”。对于前端和全栈开发者而言,Cursor、GitHub Copilot 等工具极大地提升了开发效率,但其背后的核心机制——即如何让模型具备读写文件系统、执行终端命令的能力——往往被视为黑盒。本文旨在揭开这一神秘面纱,通过 LangChain 框架与 Tool Calling(工具调用) 技术,手把手教你从零构建一个精简版的 AI 编程助手。

本教程将深入探讨如何赋予大模型操作本地环境的能力,使其能够独立完成从项目初始化、代码编写、依赖安装到服务启动的全流程任务。我们将重点解析 Agent 的核心架构,包括记忆管理、工具定义以及 ReAct(Reasoning + Acting)推理循环的实现。通过具体的代码示例和实战案例,读者将掌握如何利用 Zod 定义严格的参数模式,如何通过 System Prompt 规范模型行为,以及如何解决路径切换等常见工程难题。这不仅是一次技术实践,更是对现代 AI 应用开发范式的一次深度探索,帮助开发者理解并掌握构建自主智能体的关键技能。

AI Agent 核心概念解析:为何需要工具调用?

传统大语言模型的局限性

在日常使用 ChatGPT 或 DeepSeek 等通用大语言模型时,用户经常会遇到一个明显的瓶颈:模型虽然具备强大的代码生成能力,但它被隔离在沙盒环境中。具体表现为,模型无法直接访问用户的本地文件系统,不能创建、读取或修改实际存在的文件;它也无法执行系统命令,如 npm install 或 git commit,这意味着生成的代码需要人工复制粘贴并手动运行。此外,标准的大模型会话通常是无状态的,难以长期记住复杂的项目上下文或之前的交互细节,且缺乏对私有知识库(如公司内部文档)的直接访问能力。这些限制使得大模型在面对需要多步操作和环境影响的实际开发任务时,显得力不从心。

Agent 的定义与组成要素

为了解决上述问题,AI Agent(智能体) 的概念应运而生。Agent 并非单一的模型,而是一个由多个组件构成的系统架构。其核心公式可以概括为:AI Agent = LLM + Memory + Tool + RAG。其中,LLM 作为大脑负责推理和决策;Memory(记忆)模块用于存储对话历史、任务状态和用户偏好,确保上下文的连续性;Tool(工具)模块则是 Agent 的“手脚”,允许模型调用外部 API、操作文件系统或执行脚本;RAG(检索增强生成)则用于扩展模型的知识边界,使其能够基于私有数据进行回答。在构建编程助手的场景中,我们主要聚焦于 Tool 能力的实现,即赋予模型操作本地文件系统和执行系统命令的权限,从而打通虚拟智能与现实环境之间的壁垒。

LangChain 框架的优势选择

在众多开发框架中,LangChain 因其成熟的生态系统和标准化的接口成为构建 Agent 的首选。LangChain 提供了一套统一的抽象层,简化了不同模型提供商之间的切换成本,并内置了强大的 Tool Calling 机制。该机制允许开发者定义具有明确输入输出规范的工具,模型会根据当前任务需求,自动判断是否调用工具以及调用哪个工具,并生成符合规范的参数。本文选用 LangChain.js(Node.js 版本)配合 @langchain/openai 适配器,这不仅支持标准的 OpenAI 接口,还兼容阿里云通义千问(Qwen)、DeepSeek 等国内主流模型,为开发者提供了灵活的选择空间,同时也降低了部署和使用的门槛。

环境搭建与项目初始化配置

项目结构与依赖安装

构建 Mini Cursor 的第一步是建立标准化的 Node.js 开发环境。首先,创建一个名为 mini-cursor 的项目目录,并初始化 package.json 文件。接着,安装核心依赖库:@langchain/openai 用于与大模型进行通信,@langchain/core 提供基础的 Agent 和消息类型定义,zod 用于定义工具参数的 Schema 以确保类型安全,dotenv 用于管理敏感的环境变量,而 chalk 则用于美化控制台输出,提升调试体验。此外,还需安装 @types/node 以提供 Node.js 原生模块的类型定义,确保开发过程中的代码提示和错误检查功能正常运作。

mkdir mini-cursor
cd mini-cursor
pnpm init
pnpm add @langchain/openai @langchain/core zod dotenv chalk
pnpm add -D @types/node

环境变量配置与模型选择

为了保障 API 密钥的安全性,避免硬编码在代码中,我们需要使用 .env 文件来管理配置信息。在该文件中,定义 OPENAI_API_KEY 用于身份验证,OPENAI_BASE_URL 指定模型服务的接入点,以及 MODEL_NAME 指定具体使用的模型版本。以阿里云通义千问为例,其兼容 OpenAI 的接口格式,因此可以直接复用 OpenAI 的客户端库。选择 qwen-coder-turbo 是因为其在代码生成和理解方面表现出色,且具有较高的性价比。当然,开发者也可以根据实际需求替换为 deepseek-chat 或 gpt-4o 等其他高性能模型,只需调整相应的 Base URL 和 Model Name 即可。

OPENAI_API_KEY=your-api-key-here
OPENAI_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
MODEL_NAME=qwen-coder-turbo

模块化文件结构设计

为了保持代码的可维护性和可扩展性,项目采用模块化的文件结构。all_tools.mjs 文件专门用于定义和导出所有可用的工具函数,包括文件读写、命令执行和目录列举等功能,实现了业务逻辑与工具定义的分离。main.mjs 文件则作为程序的入口点,负责初始化模型、绑定工具、定义系统提示词(System Prompt)以及执行核心的 Agent 循环逻辑。这种清晰的分层结构不仅便于后续添加新的工具类型,也使得调试和测试变得更加直观高效,符合现代软件工程的最佳实践原则。

mini-cursor/
├── .env                # 环境变量配置文件
├── all_tools.mjs       # 工具定义模块(读/写/执行/列目录)
├── main.mjs            # Agent 主循环与启动逻辑
└── package.json        # 项目依赖与脚本配置

核心工具链实现:赋予模型操作能力

读取文件工具的设计与实现

读取文件工具 是 Agent 获取上下文信息的基础。该工具利用 Node.js 的 fs/promises 模块异步读取指定路径的文件内容。在实现上,我们使用 tool 函数包裹异步执行逻辑,并通过 zod 定义输入参数 filePath,确保模型传入的是合法的字符串路径。执行成功后,工具返回文件的具体内容,若发生错误(如文件不存在),则返回友好的错误提示信息。这一步骤至关重要,因为它允许模型在修改代码前先行了解现有代码结构,避免盲目覆盖或产生逻辑冲突,体现了 Agent “先观察后行动” 的智能特性。

import { tool } from '@langchain/core/tools';
import fs from 'node:fs/promises';
import { z } from 'zod';

export const readFileTool = tool(
  async ({ filePath }) => {
    try {
      // 异步读取文件内容,指定编码为 utf-8
      const content = await fs.readFile(filePath, 'utf-8');
      console.log(`[工具调用] read_file("${filePath}") 成功读取 ${content.length} 字节`);
      // 返回格式化后的文件内容,便于模型解析
      return `文件内容:\n${content}`;
    } catch (error) {
      // 捕获异常并返回错误信息,防止程序崩溃
      return `错误:${error.message}`;
    }
  },
  {
    name: 'read_file',
    description: '读取指定路径的文件内容',
    schema: z.object({
      filePath: z.string().describe('文件路径'),
    }),
  }
);

写入文件工具与目录自动创建

写入文件工具 负责将模型生成的代码持久化到磁盘。考虑到项目中可能存在多级嵌套目录,该工具在写入文件前,会先调用 path.dirname 获取父目录路径,并使用 fs.mkdir 的 { recursive: true } 选项递归创建所有缺失的父目录。这一细节处理极大地提升了工具的鲁棒性,避免了因目录不存在导致的写入失败。同样,通过 zod 定义 filePath 和 content 两个必填参数,确保模型提供完整的写入信息。成功写入后,返回确认信息,使模型能够知晓操作结果,从而决定下一步动作。

import path from 'node:path';

export const writeFileTool = tool(
  async ({ filePath, content }) => {
    try {
      // 获取文件所在的目录路径
      const dir = path.dirname(filePath);
      // 递归创建目录,确保父级目录存在
      await fs.mkdir(dir, { recursive: true });
      // 写入文件内容
      await fs.writeFile(filePath, content, 'utf-8');
      console.log(`[工具调用] write_file("${filePath}") 成功写入 ${content.length} 字节`);
      return `文件写入成功: ${filePath}`;
    } catch (error) {
      return `写入文件失败:${error.message}`;
    }
  },
  {
    name: 'write_file',
    description: '向指定路径写入文件内容,自动创建目录',
    schema: z.object({
      filePath: z.string().describe('文件路径'),
      content: z.string().describe('要写入的文件内容'),
    }),
  }
);

执行命令工具与工作目录管理

执行命令工具 是 Agent 最具威力的功能之一,它允许模型运行诸如 pnpm install 或 npm run dev 等系统命令。为了实现这一功能,我们使用 Node.js 的 child_process.spawn 方法,并启用 shell: true 以支持管道符和逻辑运算符。特别需要注意的是 workingDirectory 参数的引入,它允许模型指定命令执行的上下文目录,从而避免了在命令字符串中拼接 cd 命令带来的路径混乱风险。通过监听 close 事件,我们可以准确判断命令执行状态,并将标准输出或错误信息反馈给模型,形成闭环控制。

import { spawn } from 'node:child_process';

export const executeCommandTool = tool(
  async ({ command, workingDirectory }) => {
    // 确定工作目录,默认为当前进程目录
    const cwd = workingDirectory || process.cwd();
    console.log(`[工具调用] execute_command("${command}") 在目录 ${cwd} 执行`);

    return new Promise((resolve, reject) => {
      // spawn 子进程执行命令
      const child = spawn(command, [], {
        cwd,
        shell: true,        // 启用 shell 以支持复杂命令语法
        stdio: 'inherit',   // 继承父进程的标准输入输出,实时显示日志
      });

      let errorMsg = '';
      // 监听错误事件
      child.on('error', (error) => { errorMsg = error.message; });
      // 监听关闭事件,判断执行结果
      child.on('close', (code) => {
        if (code === 0) {
          // 构造包含工作目录信息的成功消息
          const cwdInfo = workingDirectory
            ? `\n\n重要提示:命令在目录"${workingDirectory}"中执行成功。如果需要在这个项目目录中继续执行命令,请使用 workingDirectory "${workingDirectory}" 参数,不要使用 cd 命令。`
            : '';
          resolve(`命令执行成功: ${command} ${cwdInfo}`);
        } else {
          reject(new Error(errorMsg || `命令退出码 ${code}`));
        }
      });
    });
  },
  {
    name: 'execute_command',
    description: '执行系统命令,支持指定工作目录,实时显示输出',
    schema: z.object({
      command: z.string().describe('要执行的命令'),
      workingDirectory: z.string().optional().describe('指定工作目录,默认当前工作目录'),
    }),
  }
);

目录列举工具与统一导出

目录列举工具 用于帮助模型了解项目结构。通过 fs.readdir 读取指定目录下的文件和文件夹列表,并将其格式化为易读的字符串返回。这对于模型在创建新文件前确认目录状态,或在调试时查找特定文件至关重要。最后,将所有定义好的工具通过 ES Module 的 export 语句统一导出,以便在主程序中一次性导入和绑定。这种模块化的设计不仅代码整洁,也便于后续扩展更多类型的工具,如网络请求、数据库操作等,从而构建更加强大的全能型 Agent。

export const listDirectoryTool = tool(
  async ({ directoryPath }) => {
    try {
      // 读取目录下的所有条目
      const files = await fs.readdir(directoryPath);
      console.log(`[工具调用] list_directory("${directoryPath}") 成功列出 ${files.length} 个文件`);
      // 将文件列表格式化为字符串
      return `目录内容:\n${files.map(f => `- ${f}`).join('\n')}`;
    } catch (error) {
      return `列出目录失败:${error.message}`;
    }
  },
  {
    name: 'list_directory',
    description: '列出指定目录下的所有文件和文件夹',
    schema: z.object({
      directoryPath: z.string().describe('目录路径'),
    }),
  }
);

// 统一导出所有工具
export {
  readFileTool,
  writeFileTool,
  executeCommandTool,
  listDirectoryTool,
};

## 第六章:工程化陷阱与最佳实践

在构建基于 LLM 的自动化代理时,理论上的可行性往往掩盖了工程落地中的细节魔鬼。以下针对开发过程中高频出现的痛点进行深度剖析,并提供经过验证的解决方案,以确保系统的稳定性与鲁棒性。

### 6.1 解决命令执行中的工作目录歧义

大语言模型倾向于生成符合人类直觉的 Shell 命令,例如 `cd target_dir && npm install`。然而,在 Node.js 中使用 `child_process.spawn` 并启用 `shell: true` 时,这种链式命令往往因为子进程的环境隔离或路径解析问题而失效,导致“找不到目录”的错误。**核心解决方案**是解耦“路径切换”与“命令执行”,在工具定义中显式引入 `workingDirectory` 参数。通过在 Prompt 中明确约束模型:“当指定 `workingDirectory` 时,严禁在 `command` 中包含 `cd` 指令”,可以显著降低执行错误率。这种设计不仅简化了底层执行逻辑,还让状态管理更加清晰,便于后续调试和日志追踪。

### 6.2 应对模型幻觉与参数校验缺失

即使是最先进的模型,也可能产生“幻觉”,例如引用一个尚未创建的文件路径,或遗漏工具调用所需的必填参数。如果工具内部缺乏防御性编程,这类错误会导致整个 Agent 流程崩溃。**最佳实践**是在每个 Tool 的内部实现严格的输入校验和异常捕获机制。当检测到文件不存在或参数格式错误时,不要直接抛出异常终止程序,而是将具体的错误信息(如“文件 /src/main.ts 不存在,请先创建该文件”)作为字符串返回给模型。利用 LLM 的自我修正能力,它会根据反馈重新规划步骤,从而形成“尝试-报错-修正”的闭环,大幅提升任务最终成功的概率。

### 6.3 防范无限循环与资源耗尽

在 ReAct(Reasoning + Acting)模式下,如果模型陷入逻辑死胡同,可能会反复调用同一个失败的工具,导致 API 费用激增甚至服务不可用。**必须实施硬性限制**,即在 Agent 的主循环中设置 `maxIterations`(最大迭代次数),通常建议设置为 15-30 次。一旦达到阈值,强制终止循环并返回当前状态摘要。此外,还可以引入“单调性检查”,即检测最近几次调用的工具和参数是否完全重复,若发现停滞迹象,提前介入中断。这种熔断机制是生产级 AI 应用不可或缺的安全网,确保系统在任何极端情况下都能优雅退出。

### 6.4 模型选型与成本效益平衡

不同的基础模型在代码生成、逻辑推理和工具调用能力上存在显著差异,选型需结合具体场景权衡。**Qwen-Coder-Turbo** 等专用代码模型在语法准确性和补全速度上表现优异,且成本低廉,适合国内开发者处理常规编码任务;**DeepSeek-Chat** 提供了极具竞争力的免费额度和强大的通用逻辑推理能力,适合初创项目快速验证;而 **GPT-4o** 则在复杂指令遵循和多步推理上依然占据统治地位,适合处理架构设计或极度复杂的调试场景。建议在开发初期使用高性能模型进行 Prompt 调试,稳定后切换至高性价比模型以降低长期运营成本。

### 6.5 优化工具返回值的信息密度

LLM 无法直接感知终端的标准输出(stdout/stderr),它完全依赖工具函数的返回值来理解执行结果。如果 `execute_command` 仅返回“执行成功”,模型将无法判断安装是否完整或编译是否通过。**关键策略**是让工具返回富含语义的总结信息。例如,在执行包安装后,返回“成功安装 234 个依赖包,耗时 12 秒”;在执行测试脚本后,返回“运行 15 个测试用例,全部通过”。这种结构化的反馈不仅减少了 Token 消耗,还帮助模型更准确地判断下一步行动,避免因信息模糊导致的重复试探。

## 第七章:架构演进 —— 从单一助手到自主 Agent

当前的 Mini Cursor 仅具备基础的文件操作能力,但通过模块化扩展,它可以演变为一个全能的软件开发代理。以下是几个关键的进阶方向,旨在提升系统的智能化水平和适用范围。

### 7.1 扩展工具集以增强感知与执行能力

单一的读写执行工具不足以应对复杂的开发场景,需要引入更多维度的能力。**添加 `search_web` 工具**可以让 Agent 实时获取最新的技术文档或 StackOverflow 解决方案,解决知识库滞后问题;**集成 `run_python` 沙箱**允许模型执行数据分析或脚本生成任务,扩展应用场景至后端逻辑;**实现 `fetch_url`** 使其能够下载远程资源或读取在线 API 文档;**加入 `git_commit` 工具**则能实现代码版本的自动管理,形成完整的开发闭环。这些工具的加入将使 Agent 从简单的代码编辑器升级为具备互联网连接能力和版本控制意识的智能体。

### 7.2 引入记忆机制以维持上下文连贯性

在无状态的对话中,Agent 容易遗忘用户之前的偏好或项目背景。通过集成 LangChain 的 **`BufferMemory`**,可以保留最近 N 轮对话历史,适用于短周期的交互任务;而对于长周期项目,建议使用 **`ConversationSummaryMemory`**,它会在每轮对话后生成摘要,既保留了关键信息又控制了 Context Window 的大小。这种记忆机制使得 Agent 能够记住“用户喜欢使用 Tailwind CSS”或“该项目采用 TypeScript 严格模式”,从而在后续的代码生成中自动适配风格,提供更具个性化和一致性的服务体验。

### 7.3 融合 RAG 技术注入私有领域知识

通用大模型缺乏对企业内部规范、私有 API 或特定业务逻辑的了解。通过构建 **RAG(检索增强生成)** 管道,可以将公司内部的技术文档、代码规范指南向量化并存入 Chroma 或 Pinecone 等向量数据库。当用户提问时,系统先检索相关片段,再将其作为上下文注入 Prompt。这使得 Agent 能够回答诸如“如何调用我们内部的支付接口?”之类的问题,并生成符合公司规范的代码。RAG 的引入极大地拓展了 AI 助手的应用边界,使其成为企业级知识管理和研发效能提升的核心组件。

### 7.4 探索多 Agent 协作架构

随着任务复杂度的增加,单一大模型难以兼顾全局规划与细节执行。借鉴 **Manus** 或 **AutoGen** 的理念,可以构建多 Agent 协作系统。例如,设立一个 **Manager Agent** 负责拆解任务和分配工作,一个 **Coder Agent** 专注代码编写,一个 **Reviewer Agent** 负责代码审查和单元测试,以及一个 **Deployer Agent** 处理部署流程。各 Agent 之间通过消息队列或共享状态进行通信,相互校验成果。这种分工协作模式不仅提高了复杂任务的完成率,还通过角色隔离降低了单个模型的认知负载,是未来 AI 软件工程的重要演进方向。

## 第八章:完整代码实现与环境配置

为了复现上述功能,以下是经过精简和标准化的核心代码实现。请确保你的开发环境已安装 Node.js (v18+) 和 pnpm。

### 8.1 项目初始化与依赖安装

首先创建项目目录并初始化,安装必要的 LangChain 库和辅助工具。

```bash
mkdir mini-cursor-agent && cd mini-cursor-agent
pnpm init -y
pnpm add @langchain/openai @langchain/core dotenv chalk zod

8.2 环境变量配置

在项目根目录创建 .env 文件,配置模型接入凭证。支持兼容 OpenAI 格式的任意服务商端点。

OPENAI_API_KEY=your_api_key_here
OPENAI_BASE_URL=https://api.openai.com/v1 
MODEL_NAME=gpt-4o-mini

8.3 核心工具定义 (all_tools.mjs)

该模块封装了文件系统和命令执行的基础能力,注重错误处理和路径安全。

// all_tools.mjs
import { tool } from '@langchain/core/tools';
import fs from 'node:fs/promises';
import path from 'node:path';
import { spawn } from 'node:child_process';
import { z } from 'zod';

// 读取文件工具:增加内容长度限制防止 Token 爆炸
const readFileTool = tool(
    async ({ filePath }) => {
        try {
            const content = await fs.readFile(filePath, 'utf-8');
            // 简单截断保护,实际生产环境建议分块读取
            const truncatedContent = content.length > 5000 ? content.substring(0, 5000) + "\n... (内容过长已截断)" : content;
            return 文件内容:\n${truncatedContent};
        } catch (error) {
            return 错误:读取文件失败 - ${error.message};
        }
    },
    {
        name: 'read_file',
        description: '读取指定路径的文件内容',
        schema: z.object({
            filePath: z.string().describe('文件的绝对或相对路径')
        })
    }
);

// 写入文件工具:自动创建父级目录
const writeFileTool = tool(
    async ({ filePath, content }) => {
        try {
            const dir = path.dirname(filePath);
            await fs.mkdir(dir, { recursive: true });
            await fs.writeFile(filePath, content, 'utf-8');
            return 文件写入成功: ${filePath};
        } catch (error) {
            return 写入文件失败:${error.message};
        }
    },
    {
        name: 'write_file',
        description: '向指定路径写入文件内容,若目录不存在则自动创建',
        schema: z.object({
            filePath: z.string().describe('目标文件路径'),
            content: z.string().describe('要写入的具体内容')
        })
    }
);

// 执行命令工具:分离工作目录与命令,避免 cd 陷阱
const executeCommandTool = tool(
    async ({ command, workingDirectory }) => {
        const cwd = workingDirectory || process.cwd();
        return new Promise((resolve) => {
            // 解析命令第一个词作为 executable,其余作为 args
            const [cmd, ...args] = command.split(' ');
            const child = spawn(cmd, args, {
                cwd,
                stdio: ['pipe', 'pipe', 'pipe'], // 捕获输出以便返回给模型
                shell: true
            });

            let stdout = '';
            let stderr = '';

            child.stdout.on('data', (data) => {
                stdout += data.toString();
            });

            child.stderr.on('data', (data) => {
                stderr += data.toString();
            });

            child.on('close', (code) => {
                if (code === 0) {
                    // 成功时返回标准输出,限制长度
                    const output = stdout.length > 1000 ? stdout.substring(0, 1000) + '...' : stdout;
                    resolve(命令执行成功。输出:\n${output});
                } else {
                    // 失败时返回错误信息,帮助模型修正
                    const errorOutput = stderr || stdout;
                    resolve(命令执行失败 (Exit Code: ${code})。错误信息:\n${errorOutput});
                }
            });

            child.on('error', (error) => {
                 resolve(命令启动失败: ${error.message});
            });
        });
    },
    {
        name: 'execute_command',
        description: '在指定目录下执行系统命令。注意:不要使用 cd 命令,请使用 workingDirectory 参数指定目录。',
        schema: z.object({
            command: z.string().describe('要执行的 Shell 命令,不含 cd'),
            workingDirectory: z.string().optional().describe('命令执行的工作目录路径')
        })
    }
);

// 列出目录工具
const listDirectoryTool = tool(
    async ({ directoryPath }) => {
        try {
            const files = await fs.readdir(directoryPath);
            return 目录内容 (${files.length} 项):\n${files.map(f => - ${f}).join('\n')};
        } catch (error) {
            return 列出目录失败:${error.message};
        }
    },
    {
        name: 'list_directory',
        description: '列出指定目录下的文件和子文件夹名称',
        schema: z.object({
            directoryPath: z.string().describe('目标目录路径')
        })
    }
);

export {
    readFileTool,
    writeFileTool,
    executeCommandTool,
    listDirectoryTool
};

8.4 主程序入口 (main.mjs)

实现 ReAct 循环逻辑,整合模型、工具和记忆流。

// main.mjs
import 'dotenv/config';
import { ChatOpenAI } from '@langchain/openai';
import { HumanMessage, SystemMessage, ToolMessage } from '@langchain/core/messages';
import { readFileTool, writeFileTool, executeCommandTool, listDirectoryTool } from './all_tools.mjs';
import chalk from 'chalk';

// 初始化模型
const model = new ChatOpenAI({
    modelName: process.env.MODEL_NAME || 'gpt-4o-mini',
    apiKey: process.env.OPENAI_API_KEY,
    temperature: 0, // 低温度以保证工具调用的确定性
    configuration: {
        baseURL: process.env.OPENAI_BASE_URL
    }
});

// 绑定工具
const tools = [readFileTool, writeFileTool, executeCommandTool, listDirectoryTool];
const modelWithTools = model.bindTools(tools);

/**
 * 运行 Agent 主循环
 * @param {string} query - 用户初始指令
 * @param {number} maxIterations - 最大思考/执行步数
 */
async function runAgent(query, maxIterations = 20) {
    const messages = [
        new SystemMessage(
        你是一个高级 AI 编程助手。你的目标是帮助用户完成软件开发任务。

        可用工具:
        1. read_file: 读取文件内容
        2. write_file: 创建或覆盖文件
        3. execute_command: 执行终端命令 (务必使用 workingDirectory 参数,禁止在 command 中使用 cd)
        4. list_directory: 查看目录结构

        行为准则:
        - 每次只执行一个工具调用,等待结果后再进行下一步。
        - 遇到错误时,分析错误原因并尝试修正。
        - 任务完成后,简要总结所做的工作。
        - 保持回复简洁专业。
        ),
        new HumanMessage(query),
    ];

    console.log(chalk.blue('🚀 Agent 启动,正在处理任务...'));

    for (let i = 0; i < maxIterations; i++) {
        console.log(chalk.yellow(\n--- 第 ${i + 1} 轮思考 ---));

        // 1. 模型推理
        const response = await modelWithTools.invoke(messages);
        messages.push(response);

        // 2. 检查是否有工具调用
        if (!response.tool_calls || response.tool_calls.length === 0) {
            console.log(chalk.green('\n✅ 任务完成或无需更多工具调用。'));
            console.log(chalk.white(response.content));
            return response.content;
        }

        // 3. 执行工具并收集结果
        for (const toolCall of response.tool_calls) {
            console.log(chalk.cyan(🛠️ 调用工具: ${toolCall.name}));
            console.log(chalk.gray(   参数: ${JSON.stringify(toolCall.args)}));

            const selectedTool = tools.find(t => t.name === toolCall.name);
            if (selectedTool) {
                try {
                    const result = await selectedTool.invoke(toolCall.args);
                    console.log(chalk.dim(   结果预览: ${result.substring(0, 100)}...));

                    // 将工具结果加入消息历史
                    messages.push(new ToolMessage({
                        content: result,
                        tool_call_id: toolCall.id
                    }));
                } catch (error) {
                    messages.push(new ToolMessage({
                        content: 工具执行异常: ${error.message},
                        tool_call_id: toolCall.id
                    }));
                }
            }
        }
    }

    console.log(chalk.red('\n⚠️ 达到最大迭代次数,强制终止。'));
    return "任务未能在限定步数内完成,请检查指令或增加迭代次数。";
}

// 示例任务:创建一个简单的 React 计数器
const taskPrompt = 
创建一个名为 counter-app 的简单 React 项目:
1. 使用 vite 创建项目 (假设环境已有 node/pnpm)。
2. 修改 src/App.jsx 为一个计数器应用,包含增加、减少按钮和显示数字。
3. 添加基本的 CSS 样式使居中显示。
4. 列出最终目录结构确认文件存在。
注意:如果 pnpm create 失败,可以尝试手动创建文件结构。
;

// 启动
runAgent(taskPrompt).catch(console.error);

第九章:总结与技术展望

通过本实战指南,我们从零构建了一个具备基本文件操作和命令执行能力的 AI 编程助手原型。这不仅是一次代码练习,更是对 AI Agent 核心架构(LLM + Tools + Memory + Planning)的深度解构。

我们掌握了以下关键技能:

  1. 工具定义的标准化:利用 Zod 进行参数校验,确保模型输入的规范性。
  2. ReAct 模式的实现:通过“观察-思考-行动”循环,赋予模型自主解决复杂问题的能力。
  3. 工程化陷阱的规避:解决了工作目录混淆、无限循环和资源限制等生产环境常见问题。
  4. 提示词工程的实践:通过 System Prompt 明确约束模型行为,显著提升工具调用的准确率。

当前的 Mini Cursor 虽然功能有限,但它展示了 AI 辅助编程的巨大潜力。未来的发展方向将聚焦于多模态理解(识别截图报错)、长期记忆优化(向量数据库集成)以及多智能体协作(专门化的测试、部署 Agent)。随着模型成本的降低和推理能力的提升,每个人都将拥有定制化的超级编程助手,彻底改变软件开发的范式。

第十章:后续学习资源与建议

如果你希望进一步深入 AI Agent 开发领域,建议关注以下方向:

  • 官方文档深耕:仔细阅读 LangChain JS Documentation,特别是关于 Graphs 和 Memory 的高级用法。
  • 框架对比研究:尝试使用 LlamaIndex 处理复杂的 RAG 场景,或探索 AutoGen 的多智能体模式。
  • 社区互动:关注 Hugging Face 和 GitHub 上的开源 Agent 项目,参与 Issue 讨论,了解前沿的最佳实践。

技术变革的脚步从未停歇,现在正是动手构建属于你的 AI 助手的最佳时机。期待在未来的开源社区中看到你的创新作品!