从零构建AI智能体:基于LangGraph与DeepSeek实现自主项目创建

📅 2026/8/12 10:05:52
从零构建AI智能体:基于LangGraph与DeepSeek实现自主项目创建
1. 项目概述为什么我们要“手写”一个智能体在AI技术飞速发展的今天各种智能体Agent平台和框架层出不穷从Dify、LangChain到各种大厂推出的“一站式”解决方案似乎让构建一个AI应用变得前所未有的简单。点几下鼠标拖拽几个模块一个能对话、能查资料、能写代码的智能体就诞生了。这听起来很美好但作为一名有十多年经验的开发者我常常感到一丝不安当一切都封装在精美的UI和抽象的API背后我们是否真正理解了智能体是如何思考、决策和行动的当遇到一个平台无法满足的定制化需求或者出现一个难以调试的诡异Bug时那种“黑盒”般的无力感会瞬间袭来。这正是我决定动手“从零开始”写一个智能体的原因。这个项目的标题——“从零打造你的第一个智能体Agent手写一个能自主建项目的Mini Cursor 三”——已经清晰地表明了它的野心和路径。它不是一个简单的API调用教程而是一次深入Agent核心机制的“外科手术式”实践。我们将聚焦于一个非常具体且实用的场景让智能体能够理解用户的项目需求并自主地执行一系列创建项目的操作比如初始化目录、安装依赖、创建配置文件、甚至编写基础的样板代码。你可以把它想象成一个简化版、但完全由你掌控的“Cursor”或“CodeWhisperer”的自动化核心。为什么选择Node.js、LangChain和DeepSeek这个技术栈Node.js提供了强大的异步I/O能力非常适合处理AI Agent这种需要频繁进行网络请求调用模型API和文件系统操作的任务。LangChain作为一个成熟的框架它抽象了与大模型交互、工具调用、记忆管理等复杂模式为我们提供了坚实的脚手架避免了重复造轮子。而DeepSeek特别是其最新版本以其出色的代码理解能力、极长的上下文和极高的性价比成为了我们构建“编码智能体”的理想“大脑”。这个组合兼顾了效率、可控性和学习深度。通过这个系列这是第三部分我希望带给大家的不仅仅是几行能跑的代码更是一种“知其然更知其所以然”的底气。当你亲手实现了工具调用链、状态管理和决策循环后再回头看那些高级框架你会拥有完全不同的视角和解决问题的能力。2. 核心架构与设计思路拆解一个能“自主建项目”的智能体其核心在于将模糊的自然语言指令转化为一系列确定性的、可执行的操作。这背后是一个经典的ReActReasoning Acting模式。我们的设计需要清晰地回答几个问题智能体如何思考它拥有哪些“手脚”工具它如何记住对话历史和任务上下文各个部分如何协同工作2.1 智能体的“大脑”、“记忆”与“工具箱”我们的Mini Cursor Agent主要由三个核心部分组成它们共同构成了智能体的认知架构大脑LLM Core基于DeepSeek模型。它的核心职责是理解意图、规划步骤和做出决策。我们向它提供用户的指令、当前的对话历史记忆、以及可用的工具列表。它需要分析指令决定下一步是调用某个工具还是直接给出最终答案。记忆Memory这是智能体的“工作记忆”。我们采用ConversationSummaryBufferMemory。它的妙处在于不仅能保存完整的最近几次对话还能自动对更早的历史进行摘要防止过长的上下文挤占宝贵的Token同时保留关键信息。例如当用户说“在刚才创建的express-demo项目里再添加一个/api/users的路由”时智能体需要从记忆里回忆起“刚才创建了express-demo项目”这个事实。工具箱Tools这是智能体的“手脚”是我们赋予它与环境这里是文件系统、终端交互的能力。每个工具都是一个独立的函数有明确的名称、描述和参数。智能体通过阅读工具描述来学习何时使用它们。对于“建项目”这个场景我们至少需要createDirectory: 创建项目目录。createFile: 创建并写入文件内容。runCommand: 在指定目录下运行Shell命令如npm init -y,git init,npm install express。listFiles: 列出目录内容用于确认操作结果或进行下一步规划。2.2 工作流与状态管理LangGraph的用武之地如何将大脑、记忆和工具有机地串联起来形成一个可以循环运行、直到任务完成的工作流这就是LangGraph发挥关键作用的地方。LangGraph允许我们用“图”Graph的思维来定义智能体的执行流程。图中的节点Node代表一个处理步骤边Edge代表步骤之间的流转条件。对于我们的Agent可以构建一个经典的单Agent工作流图Agent节点这是核心决策点。它接收当前状态包含用户问题、对话历史、中间结果调用大模型大脑让模型决定下一步行动。模型的输出有两种可能AgentFinish认为任务已完成给出最终答案或AgentAction决定调用某个工具并给出调用参数。工具执行节点如果上一步是AgentAction流程就流转到这里。该节点根据Action指示调用对应的工具函数如runCommand并获取工具的执行结果如“package.json已创建成功”。结果处理与循环工具执行的结果会被添加回状态中。然后流程自动地、循环地回到Agent节点。此时智能体拥有了新的信息工具执行结果它可以重新评估任务状态决定是继续调用下一个工具还是宣告任务完成。这个循环会一直持续直到模型输出AgentFinish。这种设计完美地实现了ReAct模式中的“思考-行动-观察-再思考”的循环。LangGraph帮我们管理了这个循环的复杂性我们只需要定义好节点和流转逻辑。实操心得状态State的设计是关键。你需要仔细规划状态对象State Schema里应该包含哪些字段。至少要有input用户最新输入、chat_history记忆、intermediate_steps已执行的动作和结果、agent_outcome上一次Agent节点的输出。一个清晰的状态设计是工作流稳定运行的基础。3. 环境准备与核心依赖详解“工欲善其事必先利其器”。在开始编码前我们需要搭建一个稳定、可复现的开发环境。以下步骤和版本选择都经过实际项目验证能有效避免常见的环境冲突问题。3.1 Node.js与包管理器的选择与安装首先确保你的系统安装了合适的Node.js版本。对于AI应用推荐使用Node.js 18 LTS或20 LTS版本因为它们提供了良好的稳定性和对现代ES模块的支持。# 检查当前Node.js和npm版本 node --version npm --version如果你的版本不符合要求强烈建议使用**nvmNode Version Manager**来管理多个Node.js版本这在同时维护多个不同年代的项目时是救命稻草。# 安装nvm以macOS/Linux为例Windows请使用nvm-windows curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash # 重新加载shell配置或打开新终端 source ~/.bashrc # 或 ~/.zshrc # 安装并使用Node.js 20 nvm install 20 nvm use 20 # 验证 node --version # 应显示 v20.x.x关于包管理器npm是默认选择但yarn或pnpm在依赖解析速度和磁盘空间利用上更有优势。本项目使用npm但你可以自由替换。3.2 初始化项目与安装核心依赖创建一个新的项目目录并初始化package.json。mkdir mini-cursor-agent cd mini-cursor-agent npm init -y接下来安装我们所需的依赖。我们将它们分为“核心依赖”和“开发依赖”两类。核心依赖这些是智能体运行所必需的库。npm install langchain langchain/core langchain/openai langgraphlangchain: LangChain的主包提供了构建链Chain和智能体Agent所需的核心抽象。langchain/core: LangChain的核心基础包包含许多底层接口。langchain/openai: 尽管我们使用DeepSeek但其API与OpenAI兼容我们可以使用这个包提供的ChatOpenAI类来调用DeepSeek只需修改baseURL和apiKey即可。这是最便捷的方式。langgraph: 用于构建有状态、多步骤的工作流图是我们实现智能体循环的核心。开发依赖用于代码质量、类型提示和开发便利。npm install --save-dev typescript ts-node types/node dotenv npm install --save-dev eslint prettiertypescriptts-node: 我们将使用TypeScript来获得更好的类型安全和开发体验。types/node: 提供Node.js API的类型定义。dotenv: 用于从.env文件加载环境变量如API密钥避免硬编码。eslintprettier: 代码格式化和静态检查工具保证代码风格统一。初始化TypeScript配置npx tsc --init这会生成一个tsconfig.json文件。你可以根据需要进行调整一个适用于本项目的简化配置如下{ compilerOptions: { target: ES2020, module: commonjs, lib: [ES2020], outDir: ./dist, rootDir: ./src, strict: true, esModuleInterop: true, skipLibCheck: true, forceConsistentCasingInFileNames: true, resolveJsonModule: true }, include: [src/**/*], exclude: [node_modules] }3.3 获取并配置DeepSeek API密钥DeepSeek提供了开放的API服务。你需要前往其官方网站注册账号并在控制台中创建API Key。访问DeepSeek平台完成注册和登录。在控制台或个人中心找到“API密钥”或“应用管理”相关区域。创建一个新的API Key并妥善保存。它通常只显示一次。在项目根目录创建.env文件用于存储敏感信息DEEPSEEK_API_KEY你的_DeepSeek_API_Key_在这里重要安全提示务必在.gitignore文件中添加.env切勿将API密钥提交到版本控制系统。4. 工具箱Tools的实现赋予智能体“手脚”智能体本身不会魔法它所有对外部世界的操作都依赖于我们提供的工具。工具的实现质量直接决定了智能体的能力上限和可靠性。我们的工具需要安全、健壮并提供清晰的描述供大模型理解。4.1 文件系统操作工具我们将使用Node.js内置的fs/promises模块来实现异步的文件操作这比回调风格更易于在异步流程中集成。createDirectory工具// src/tools/fileSystemTools.ts import { z } from zod; import { DynamicStructuredTool } from langchain/core/tools; import * as fs from fs/promises; import * as path from path; export const createDirectoryTool new DynamicStructuredTool({ name: create_directory, description: 在指定路径创建一个新的目录。如果目录已存在则不会报错。, schema: z.object({ dirPath: z.string().describe(要创建的目录的完整路径或相对路径。), }), func: async ({ dirPath }) { try { // 解析路径确保处理相对路径 const absolutePath path.resolve(dirPath); await fs.mkdir(absolutePath, { recursive: true }); // recursive: true 允许创建多级目录 return 目录创建成功${absolutePath}; } catch (error: any) { return 创建目录时出错${error.message}; } }, });为什么用DynamicStructuredTool它结合了zod模式验证能确保大模型传入的参数格式正确并在调用前进行校验比普通的Tool接口更安全。recursive: true这个选项非常关键。当用户要求创建projects/my-app/src/components时如果中间目录不存在这个选项会一并创建它们避免了“目录不存在”的错误。createFile工具export const createFileTool new DynamicStructuredTool({ name: create_file, description: 在指定路径创建并写入一个文件。如果文件已存在默认会覆盖它。, schema: z.object({ filePath: z.string().describe(要创建的文件的完整路径。), content: z.string().describe(要写入文件的文本内容。), }), func: async ({ filePath, content }) { try { const absolutePath path.resolve(filePath); // 确保文件所在目录存在 const dir path.dirname(absolutePath); await fs.mkdir(dir, { recursive: true }); await fs.writeFile(absolutePath, content, utf-8); return 文件创建成功${absolutePath}; } catch (error: any) { return 创建文件时出错${error.message}; } }, });先创建目录在写入文件前先检查并创建其父目录这是一个健壮性设计能处理filePath包含未创建子目录的情况。listFiles工具export const listFilesTool new DynamicStructuredTool({ name: list_files, description: 列出指定目录下的所有文件和子目录。, schema: z.object({ dirPath: z.string().describe(要列出内容的目录路径。默认为当前目录。).optional().default(.), }), func: async ({ dirPath }) { try { const absolutePath path.resolve(dirPath); const items await fs.readdir(absolutePath, { withFileTypes: true }); const result items.map(item { const type item.isDirectory() ? [目录] : [文件]; return ${type} ${item.name}; }).join(\n); return 目录 ${absolutePath} 下的内容\n${result || (空目录)}; } catch (error: any) { return 列出文件时出错${error.message}; } }, });withFileTypes: true这个选项能让我们区分条目是文件还是目录提供更友好的输出信息。4.2 命令行执行工具这是最强大但也最危险的工具。我们必须谨慎处理避免执行恶意命令。runCommand工具// src/tools/commandTools.ts import { z } from zod; import { DynamicStructuredTool } from langchain/core/tools; import { exec } from child_process; import { promisify } from util; const execAsync promisify(exec); // 将回调风格的exec转换为Promise风格 export const runCommandTool new DynamicStructuredTool({ name: run_command, description: 在指定的工作目录下运行一个Shell命令。适用于安装依赖(npm install)、初始化项目(npm init)、运行脚本等。警告请谨慎使用此工具。, schema: z.object({ command: z.string().describe(要执行的Shell命令例如 npm init -y。), cwd: z.string().describe(命令执行的工作目录路径。).optional().default(.), }), func: async ({ command, cwd }) { // !!! 安全警告在实际生产环境中这里必须添加命令白名单或严格的输入过滤 !!! // 例如只允许以 npm、git、echo 等安全命令开头的操作。 // if (!isCommandAllowed(command)) { return 此命令不被允许执行。; } try { const absoluteCwd path.resolve(cwd); const { stdout, stderr } await execAsync(command, { cwd: absoluteCwd }); let result 命令执行完成。工作目录${absoluteCwd}\n; if (stdout) result 标准输出\n${stdout}\n; if (stderr) result 标准错误\n${stderr}\n; // 即使有stderr也返回成功因为很多工具如npm将信息输出到stderr return result.trim(); } catch (error: any) { // execAsync在命令返回非零退出码时会reject return 命令执行失败 (${error.code})${error.message}\n${error.stderr}; } }, });安全第一代码中的安全警告是重中之重。在开放给不可信用户使用的环境中绝对不能直接执行任意命令。必须实现一个命令白名单机制例如只允许/^npm (install|init|run|ci)/、/^git (init|add|commit)/等模式。本项目为演示简化了此部分但你务必牢记。cwd参数允许指定命令运行的工作目录这对于在特定项目文件夹内执行npm install等操作至关重要。处理stderr许多命令行工具如npm、git会将进度信息、警告输出到stderr但这不意味着命令失败。我们的工具将stdout和stderr都返回给智能体由它结合上下文判断是否成功。4.3 工具的描述与组合工具的描述description是智能体学会使用它的“说明书”。描述需要清晰、准确、无歧义并说明工具的用途、输入参数的意义以及可能的行为。例如run_command的描述中加入了“警告”这有助于模型在不确定时更谨慎。最后我们将所有工具组合成一个数组供后续的智能体使用// src/tools/index.ts import { createDirectoryTool, createFileTool, listFilesTool } from ./fileSystemTools; import { runCommandTool } from ./commandTools; export const allTools [createDirectoryTool, createFileTool, listFilesTool, runCommandTool];5. 构建智能体工作流集成大脑、记忆与工具有了强大的工具箱我们现在需要将它们与DeepSeek“大脑”和记忆系统连接起来形成一个可以自主运行的工作流。我们将使用LangGraph来定义这个有状态的、循环的智能体。5.1 初始化LLM与记忆系统首先配置我们的“大脑”——DeepSeek模型。// src/agent/core.ts import { ChatOpenAI } from langchain/openai; import { ChatPromptTemplate } from langchain/core/prompts; import { BufferMemory } from langchain/memory; import { ConversationSummaryBufferMemory } from langchain/memory; import dotenv from dotenv; dotenv.config(); // 初始化DeepSeek模型 const llm new ChatOpenAI({ modelName: deepseek-chat, // 根据DeepSeek最新模型名称调整如 deepseek-v3 openAIApiKey: process.env.DEEPSEEK_API_KEY, configuration: { baseURL: https://api.deepseek.com/v1, // DeepSeek API 端点 }, temperature: 0.1, // 较低的温度使输出更确定适合执行任务 maxTokens: 2000, });baseURL这是关键配置将ChatOpenAI的请求指向DeepSeek的API服务器。temperature设置为较低的0.1因为我们需要智能体做出稳定、可靠的操作决策而不是富有创意的回答。接下来初始化记忆系统。我们选择ConversationSummaryBufferMemory它在普通BufferMemory的基础上增加了摘要功能能更有效地利用上下文窗口。const memory new ConversationSummaryBufferMemory({ memoryKey: chat_history, // 存储在状态中的键名 llm: llm, // 用于生成摘要的模型可以用一个更便宜的模型这里为简化使用同一个 maxTokenLimit: 1000, // 记忆最新对话摘要的最大Token数 returnMessages: true, // 返回消息对象格式便于直接用于提示词模板 });5.2 创建智能体执行器Agent Executor在LangChain的语境中一个“智能体”由三部分组成LLM、工具列表和一个提示词模板。提示词模板用于指导LLM如何思考和使用工具。import { createReactAgent } from langchain/langgraph/prebuilt; import { allTools } from ../tools; // 1. 定义提示词模板 const prompt ChatPromptTemplate.fromMessages([ [system, 你是一个专业的软件开发助手擅长根据用户需求创建和管理项目。 你可以使用工具来操作文件系统和执行命令。 请严格按照以下规则行事 1. 仔细分析用户需求将其分解为具体的、可执行的操作步骤。 2. 一次只执行一个操作步骤并观察结果。 3. 如果用户的需求是创建一个新项目典型的步骤包括创建目录、初始化package.json、安装依赖、创建入口文件等。 4. 在运行命令如npm install前请确保已经在正确的项目目录下。 5. 每次行动后根据工具返回的结果决定下一步。如果结果成功继续下一步如果失败分析原因并尝试修复或告知用户。 6. 当所有步骤完成或用户需求已满足时给出清晰的最终总结。 当前对话历史 {chat_history} 请开始处理用户的最新请求。], [placeholder, {agent_scratchpad}], // 这个占位符会被LangChain自动替换为智能体的思考过程和工具调用记录 [human, {input}], ]); // 2. 使用LangChain的“预构建”方法快速创建一个基于ReAct模式的智能体 const agent createReactAgent({ llm, tools: allTools, prompt, });{agent_scratchpad}是一个特殊的占位符LangChain会在运行时将智能体之前的“思考”Reasoning和“行动”Action记录填充进去这对于实现多步推理至关重要。5.3 使用LangGraph定义工作流图现在我们将智能体、工具和记忆封装进一个LangGraph工作流。这提供了更精细的状态控制和循环逻辑。// src/agent/graph.ts import { StateGraph, END } from langchain/langgraph; import { BaseMessage } from langchain/core/messages; import { RunnableConfig } from langchain/core/runnables; // 1. 定义状态的结构 interface AgentState { input: string; // 用户最新输入 chat_history: BaseMessage[]; // 对话历史消息格式 intermediate_steps: Array{ action: any; observation: string }; // 已执行的动作和观察结果 agent_outcome?: any; // 上一次Agent节点的输出 } // 2. 定义工作流中的节点函数 // Agent节点调用模型进行决策 async function agentNode(state: AgentState) { // 准备输入给agent执行器的数据 const agentInput { input: state.input, chat_history: state.chat_history, intermediate_steps: state.intermediate_steps, }; // 调用我们之前创建的agent执行器 const outcome await agent.invoke(agentInput); // 返回更新后的状态 return { ...state, agent_outcome: outcome, }; } // 工具执行节点执行Agent选择的工具 async function toolNode(state: AgentState) { const lastOutcome state.agent_outcome; // 检查上一次agent的输出是否是工具调用AgentAction if (lastOutcome lastOutcome.returnValues lastOutcome.returnValues.output) { // 如果agent已经给出了最终答案AgentFinish直接传递状态 return state; } // 否则获取要执行的动作 const action lastOutcome; const actionTool action.tool; const actionInput action.toolInput; let observation; // 根据工具名找到对应的工具并执行 const tool allTools.find(t t.name actionTool); if (tool) { observation await tool.invoke(actionInput); } else { observation 错误未知工具 ${actionTool}; } // 将本次“行动-观察”对添加到步骤历史中 const newSteps [...state.intermediate_steps, { action, observation }]; return { ...state, intermediate_steps: newSteps, }; } // 3. 构建图并设置条件边 const workflow new StateGraphAgentState({ channels: { input: null, // 用户输入 chat_history: null, // 对话历史 intermediate_steps: { default: () [] }, // 步骤历史默认空数组 agent_outcome: null, // agent输出 } }) .addNode(agent, agentNode) .addNode(tool, toolNode) .addEdge(agent, tool) // 默认从agent流向tool // 定义条件边判断是继续循环还是结束 function shouldContinue(state: AgentState): tool | END { const lastOutcome state.agent_outcome; // 如果agent返回了最终结果AgentFinish则结束 if (lastOutcome lastOutcome.returnValues lastOutcome.returnValues.output) { return END; } // 否则继续执行工具 return tool; } workflow.addConditionalEdges( tool, // 从tool节点出发 shouldContinue, // 根据状态决定下一个节点 { tool: agent, // 如果继续回到agent节点进行下一轮思考 [END]: END, // 如果结束则终止 } ); // 设置入口点 workflow.setEntryPoint(agent); // 编译图得到可执行的工作流 const app workflow.compile();这个图定义了一个经典的循环agent - tool - (条件判断) - agent ...。智能体思考后决定行动行动后观察结果带着新结果再次思考直到任务完成。5.4 创建集成了记忆的调用入口最后我们创建一个方便调用的函数它负责处理记忆的保存和加载并初始化每次调用的状态。// src/agent/runner.ts import { HumanMessage, AIMessage } from langchain/core/messages; import { app } from ./graph; import { memory } from ./core; export async function runAgent(userInput: string): Promisestring { // 1. 从记忆加载历史 const savedHistory await memory.loadMemoryVariables({}); const chatHistory savedHistory.chat_history || []; // 2. 构建初始状态 const initialState: AgentState { input: userInput, chat_history: chatHistory, intermediate_steps: [], }; // 3. 执行工作流图 const finalState await app.invoke(initialState); // 4. 获取最终输出 let finalOutput 任务执行完成但未收到明确结果。; const lastOutcome finalState.agent_outcome; if (lastOutcome lastOutcome.returnValues lastOutcome.returnValues.output) { finalOutput lastOutcome.returnValues.output; } else if (finalState.intermediate_steps.length 0) { // 如果没有明确的AgentFinish但执行了步骤可以总结步骤 const lastStep finalState.intermediate_steps[finalState.intermediate_steps.length - 1]; finalOutput 任务执行流结束。最后一步结果${lastStep.observation}; } // 5. 将本次交互保存到记忆 // 将用户输入和AI输出转换为消息格式并保存 const newHumanMessage new HumanMessage(userInput); const newAIMessage new AIMessage(finalOutput); await memory.saveContext({ input: userInput }, { output: finalOutput }); // 或者使用 chatHistory 方式 // await memory.chatHistory.addMessage(newHumanMessage); // await memory.chatHistory.addMessage(newAIMessage); return finalOutput; }6. 实战测试让智能体创建一个Express.js项目理论部分已经完成现在是激动人心的实战环节。我们将编写一个测试脚本亲眼看看这个“手写”的Mini Cursor如何工作。创建一个测试文件src/test.tsimport { runAgent } from ./agent/runner; import * as readline from readline/promises; import { stdin as input, stdout as output } from process; async function main() { console.log( Mini Cursor Agent 测试 ); console.log(输入 exit 或 quit 退出。\n); const rl readline.createInterface({ input, output }); try { while (true) { const userInput await rl.question(\n你的指令: ); if (userInput.toLowerCase() exit || userInput.toLowerCase() quit) { console.log(再见); break; } if (!userInput.trim()) { continue; } console.log(\n--- Agent 开始工作 ---); const startTime Date.now(); try { const result await runAgent(userInput); const endTime Date.now(); console.log(\n--- Agent 回复 (耗时 ${endTime - startTime}ms) ---); console.log(result); } catch (error: any) { console.error(\n!!! Agent 运行出错 !!!); console.error(error.message); } console.log(----------------------); } } finally { rl.close(); } } main().catch(console.error);在package.json中添加一个启动脚本{ scripts: { start: ts-node src/test.ts, build: tsc } }现在运行npm start让我们给智能体下达第一个任务。测试用例1创建一个基础的Node.js项目你的指令: 帮我在当前目录下创建一个叫‘my-express-app’的Express.js项目并安装必要的依赖。观察控制台输出你会看到类似以下的日志流具体步骤可能因模型推理略有差异--- Agent 开始工作 --- 内部流程Agent思考 - 调用create_directory - 观察结果 - 思考 - 调用run_command执行cd my-express-app npm init -y - 观察结果 - 思考 - 调用run_command执行npm install express - ... --- Agent 回复 (耗时 12000ms) --- 已成功为您创建Express.js项目‘my-express-app’。 步骤摘要 1. 创建了项目目录 ‘my-express-app’。 2. 在该目录下初始化了package.json文件npm init -y。 3. 安装了express依赖包npm install express。 4. 创建了入口文件app.js并写入了一个简单的Express服务器示例代码。 5. 在package.json中添加了启动脚本 start: node app.js。 您现在可以进入my-express-app目录运行npm start来启动服务器。你可以进入my-express-app目录检查package.json、app.js文件以及node_modules是否都已就位。这就是你的智能体自主完成的工作测试用例2基于上下文的操作你的指令: 在刚才的项目里再创建一个‘routes’目录并在里面添加一个‘users.js’文件内容是一个返回用户列表的简单路由。智能体会从记忆里回忆起“刚才的项目”是my-express-app然后执行创建目录和文件的操作。7. 常见问题、调试技巧与优化方向在实际开发和测试中你肯定会遇到各种问题。这里记录了一些典型问题和解决思路。7.1 模型不调用工具或调用错误问题智能体一直“自言自语”不调用工具或者调用了错误的工具/参数。排查检查工具描述工具的描述是否清晰、无歧义模型完全依赖描述来理解工具功能。尝试将描述写得更像“说明书”明确输入输出。检查提示词Prompt系统提示词是否明确指令模型“使用工具”是否提供了清晰的步骤示例在提示词中加入“你必须使用提供的工具来完成任务”等强约束。启用详细日志在调用agent.invoke或app.invoke时可以监听LangChain的调试事件打印出模型的原始思考和决策过程。这需要设置环境变量LANGCHAIN_TRACING_V2true和LANGCHAIN_API_KEY或者使用callbacks参数。调整Temperature如果模型行为过于“天马行空”将temperature调低如0.1如果它过于保守不敢调用工具可以稍微调高如0.3但需谨慎。7.2 工具执行失败或结果未正确处理问题工具抛出了异常或者执行结果没有被智能体正确理解导致循环卡住或逻辑错误。排查工具函数的健壮性确保每个工具函数都有完善的try-catch并返回字符串格式的结果即使是错误信息。模型需要“观察”到结果才能继续。错误信息友好化工具返回的错误信息应该对人类和模型都友好。例如不仅仅是“Error: ENOENT”而是“创建文件失败目录‘/some/path’不存在”。检查工作目录CWD对于文件操作和命令执行当前工作目录至关重要。确保在调用工具时传递正确的cwd参数特别是在多步骤项目中。观察状态流在agentNode和toolNode函数中打印state查看intermediate_steps是否正确累积。这能帮你定位是哪个环节出了问题。7.3 性能与成本优化上下文过长长时间对话后记忆可能变得很长导致API调用Token数激增速度变慢成本增加。优化使用ConversationSummaryBufferMemory本身就是为了缓解此问题。可以调整maxTokenLimit或定期手动清理chat_history。对于超长任务可以考虑将任务状态持久化到数据库而不是全部放在内存上下文里。不必要的循环有时智能体会陷入“死循环”反复执行相似操作。优化在提示词中加强约束例如“如果一个操作连续失败两次请停止并报告错误”。也可以在shouldContinue函数中添加自定义逻辑比如限制最大循环次数例如10次达到后强制返回END。API调用慢DeepSeek API的响应速度受网络和模型负载影响。优化考虑对简单的、确定性的操作如“创建固定模板文件”进行短路处理。可以在调用智能体前先用一个简单的规则引擎判断如果是非常明确的任务直接执行不经过大模型推理。7.4 安全性强化再次强调命令执行白名单runCommandTool是最大的安全漏洞。在生产环境中必须实现命令和参数的白名单或严格的正则表达式过滤。绝对不允许执行rm -rf /、下载远程脚本、访问敏感文件等命令。文件路径限制限制工具可以访问的文件系统范围防止智能体读取或覆盖系统关键文件。可以通过在工具函数内解析路径并检查是否在允许的沙箱目录内。API密钥隔离确保.env文件不被泄露考虑使用环境变量或密钥管理服务。8. 项目总结与未来扩展思路走到这里你已经拥有了一个完全由自己代码构建的、具备基础项目创建能力的智能体。它虽然简陋但五脏俱全理解了ReAct架构实现了工具调用集成了记忆并用LangGraph管理了工作流。这个“轮子”造得值因为它彻底揭开了Agent神秘的面纱。回顾整个实现过程最关键的收获不在于用了某个库或某个API而在于理解了智能体**“思考-行动”的循环本质**以及状态State在这个循环中的流动。这是所有复杂Agent系统无论是AutoGPT、BabyAGI还是其他共有的核心模式。这个Mini Cursor Agent只是一个起点你可以从多个方向扩展它更丰富的工具集集成Git操作clone, commit, push、调用外部API获取天气、股票信息、发送邮件/通知、操作数据库等。多智能体协作使用LangGraph定义更复杂的图例如一个“架构师”智能体负责规划一个“开发”智能体负责写代码一个“测试”智能体负责运行检查它们通过共享状态协同工作。图形化界面为这个智能体后端开发一个Web前端或桌面应用让非开发者也能通过自然语言创建项目。集成更强大的模型除了DeepSeek可以尝试接入GPT-4o、Claude-3.5 Sonnet等或者使用本地部署的Ollama模型比较它们在代码任务上的表现。加入验证与回滚在工具执行后增加一个“验证”步骤。例如创建文件后读取内容确认安装依赖后检查node_modules是否存在。如果关键步骤失败提供回滚机制。手写智能体的过程是一个从“使用者”到“创造者”的思维转变。当你再看到那些炫酷的AI应用时你看到的将不再是魔法而是一个个精心设计的工具、状态机和提示词。这种深度的理解是任何现成平台都无法给予的。希望这个项目能成为你深入AI Agent世界的一块坚实跳板。