构建 LLM + Tools 的简单 AI Agent Demo:从文件操作到项目生成

4 阅读4分钟

引言:AI Agent 的基础与发展趋势

AI Agent 作为当前热门技术,正从单纯的语言模型向智能执行系统演进。早期产品如千问、豆包、元宝等,通过 AI 推理完成复杂任务,标志着计算范式的转变。更先进的如 OpenClaw 项目,以一人公司模式运营多 Agent 系统,包括编程助手、PPT 生成、财务分析等。这些系统通过任务拆解、规划,调用特定 Agent 实现目标。另一个例子是 Seedance,利用视频数据进行分析,展示了 Agent 在实时处理上的潜力。

从 Prompt Engineering 到 Agent Engineering,这一演进强调全栈能力。直接调用大模型如 Gemini 1.5 Pro 虽便捷,但存在痛点:缺少记忆(Memory)导致忘记历史对话;无法执行外部操作(如访问网页);难以整合私有知识(RAG)。因此,AI Agent 的核心组成是 LLM + Memory + Tool + RAG。这让大模型能思考、规划、记忆,并自动执行任务。

本文聚焦一个 LLM + Tools 的小 demo,基于提供的笔记和代码,逐步构建一个简单 Agent。它不是完整的 Cursor(一个高级编程 Agent,能自动生成、编辑项目),而是基础版本:用 LangChain 集成工具,实现文件读写和命令执行。我们将用 JavaScript 示例代码演示如何让 Agent “手写”一个 React TodoList 项目。通过这个 demo,你能理解 Tool 的作用,并扩展到更复杂场景。

AI Agent 的本质与组件

AI Agent 本质上是扩展的大模型:添加 Tool 赋予执行能力,Memory 管理上下文,RAG 查询知识库。原本 LLM 能思考规划,但 Tool 让它能读写文件、执行 Bash 命令,实现自动化。例如,用 React 创建 TodoList:Agent 思考生成代码,用 Tool 保存文件、运行 npm 安装。这比纯 LLM 更实用。

LangChain 框架简化了 Agent 开发,提供 Memory、Tool、RAG 支持。需 Node.js 基础(如 NestJS)。我们选择 qwen-coder-turbo 作为 LLM,专精代码生成。Tool 包括 read_file、write_file 和 exec_command,这些是编程 Agent 的基石。

这个 demo 强调 LLM 与 Tool 的集成:LLM 决策调用 Tool,处理结果,形成循环,直到任务完成。它是一个小起点,向 Cursor 风格的 Agent 靠近。

Tool 的设计与集成原理

选择 LLM:qwen-coder-turbo,temperature=0 确保输出稳定。Tool 是函数,接受参数、执行操作、返回结果。LangChain 的 tool 模块用 Zod 校验参数。

示例中,read_file Tool 读取文件,用于代码分析。write_file 保存内容,exec_command 执行 shell 命令(如 npm init)。绑定 Tool 后,LLM 在对话中可调用。

消息系统:SystemMessage 定义流程,HumanMessage 用户输入,ToolMessage 反馈结果。循环处理 tool_calls:执行 Tool,加入消息,再调用 LLM。

这实现了 Agent 的“智能”:规划 → 调用 → 处理 → 迭代。

完整代码示例详解

首先,导入模块:

JavaScript

import 'dotenv/config';
import { ChatOpenAI } from '@langchain/openai';
import { tool } from '@langchain/core/tools';
import {
  HumanMessage,
  SystemMessage,
  ToolMessage,
} from '@langchain/core/messages';
import fs from 'node:fs/promises';
import { z } from 'zod';
import { exec } from 'child_process';
import util from 'util';

const execAsync = util.promisify(exec);

初始化模型:

JavaScript

const model = new ChatOpenAI({
  modelName: 'qwen-coder-turbo',
  apiKey: process.env.OPENAI_API_KEY,
  configuration: {
    baseURL: process.env.OPENAI_API_BASE_URL,
  },
  temperature: 0,
});

定义 Tool:

JavaScript

const readFileTool = tool(
  async ({ path }) => {
    const content = await fs.readFile(path, 'utf-8');
    console.log(`[工具调用] read_file("${path}") 成功读取 ${content.length} 个字符`);
    return content;
  },
  {
    name: 'read_file',
    description: `用此工具来读取文件内容,当用户需要读取文件、查看代码时、分析文件内容时,调用此工具,输入文件路径(可以是相对路径或者绝对路径)`,
    schema: z.object({ path: z.string().describe('要读取的文件路径') }),
  }
);

const writeFileTool = tool(
  async ({ path, content }) => {
    await fs.writeFile(path, content, 'utf-8');
    console.log(`[工具调用] write_file("${path}") 成功写入`);
    return `文件 ${path} 已成功写入`;
  },
  {
    name: 'write_file',
    description: `用此工具写入文件内容,当需要创建或修改文件时调用,输入路径和内容`,
    schema: z.object({
      path: z.string().describe('要写入的文件路径'),
      content: z.string().describe('要写入的文件内容'),
    }),
  }
);

const execCommandTool = tool(
  async ({ command }) => {
    const { stdout, stderr } = await execAsync(command);
    console.log(`[工具调用] exec_command("${command}") 执行完成`);
    return `stdout: ${stdout}\nstderr: ${stderr}`;
  },
  {
    name: 'exec_command',
    description: `用此工具执行 shell 命令,如安装依赖或运行项目,当需要运行 npm、git 等命令时调用`,
    schema: z.object({ command: z.string().describe('要执行的 shell 命令') }),
  }
);

const tools = [readFileTool, writeFileTool, execCommandTool];

绑定 Tool:

JavaScript

const modelWithTools = model.bindTools(tools);

消息数组与用户查询示例:

JavaScript

const messages = [
  new SystemMessage(`
    你是一个编程 Agent,像 Cursor 一样,能生成代码、读写文件、执行命令。

    工作流程:
    1. 理解用户任务(如创建 React TodoList)。
    2. 规划步骤:生成代码 → 写文件 → 执行命令(如 npm init)。
    3. 使用工具执行:read_file 查看现有文件,write_file 保存代码,exec_command 运行命令。
    4. 如果需要,迭代优化。

    可用工具:
    - read_file: 读取文件内容
    - write_file: 写入文件内容
    - exec_command: 执行 shell 命令
  `),
  new HumanMessage('请帮我创建一个简单的 React TodoList 项目,生成代码并保存到 ./todo-app 目录下,然后安装依赖。'),
];

主循环:

JavaScript

let response = await modelWithTools.invoke(messages);
messages.push(response);

while (response.tool_calls && response.tool_calls.length > 0) {
  console.log(`\n[检测到] ${response.tool_calls.length} 个工具`);
  const toolResults = await Promise.all(
    response.tool_calls.map(async (toolCall) => {
      const tool = tools.find((t) => t.name === toolCall.name);
      if (!tool) {
        return `错误:找不到工具 ${toolCall.name}`;
      }
      console.log(`[执行工具] ${toolCall.name}(${JSON.stringify(toolCall.args)})`);
      try {
        const result = await tool.invoke(toolCall.args);
        return result;
      } catch (err) {
        return `错误:${err.message}`;
      }
    })
  );
  response.tool_calls.forEach((toolCall, index) => {
    messages.push(
      new ToolMessage({
        content: toolResults[index],
        tool_call_id: toolCall.id,
      })
    );
  });
  response = await modelWithTools.invoke(messages);
}

console.log(response.content);

运行过程:Agent 规划创建目录(exec_command 'mkdir todo-app')、生成代码(LLM 输出)、写文件(write_file 保存 App.js 等)、安装依赖(exec_command 'cd todo-app && npm init -y && npm install react react-dom')。最终输出项目创建成功。

扩展到更高级 Agent

当前是小 demo,添加 Memory(LangChain BufferMemory)实现多轮对话。集成 RAG 查询代码库。向 Cursor 靠近:支持 VS Code 插件、代码迭代优化。

安全考虑:限制 Tool 路径/命令,避免风险。性能:Promise.all 并行执行。

结语:

通过这个小 demo,我们看到 AI Agent 的潜力:LLM + Tool 让大模型从思考者变为执行者。基于 LangChain,你可以扩展到 Memory 和 RAG,构建全栈 Agent。