前言

最近Claude Code、Cursor等AI编程助手火遍全网,它们能自动理解需求、编写代码、运行命令,甚至帮你调试bug。你有没有好奇过,这些工具背后的核心原理是什么?

今天,我们就来手写一个简化版的Claude Code Agent,通过实践深入理解AI Agent的工作机制。相信我,这比你想象的要简单得多!

整体思路

我们的目标是创建一个能自动执行编程任务的Agent,比如让它“创建一个React + Vite的TodoList应用”。

整体架构分为三层:

  1. LLM(大语言模型):负责理解和规划任务

  2. Tools(工具集):让LLM能够操作文件系统、执行命令

  3. Agent循环:协调LLM和Tools,完成复杂任务

用户需求 → LLM思考 → 调用工具 → 获取结果 → LLM再思考 → ... → 完成任务

技术选型

  • TypeScript:类型安全,开发体验好

  • LangChain:LLM应用开发框架,统一各家模型接口

  • OpenAI:使用GPT-4作为推理引擎

  • Node.js:运行环境

核心概念解析

1. Message体系

在LangChain中,对话由不同类型的消息组成:

// SystemMessage: 设定AI的角色和能力边界
new SystemMessage("你是一个编程助手,可以读写文件、执行命令...")

// HumanMessage: 用户输入
new HumanMessage("创建一个React + Vite的TodoList")

// AIMessage: AI的思考和回答
new AIMessage("我来帮你创建项目,首先使用Vite初始化...")

// ToolMessage: 工具执行结果
new ToolMessage({ content: "项目创建成功", toolCallId: "xxx" })

2. Tool机制

工具是LLM能力的延伸,让AI能够“动手干活”:

const writeFileTool = {
  name: "write_file",
  description: "写入文件到磁盘",
  schema: z.object({
    path: z.string(),
    content: z.string()
  }),
  async execute({ path, content }) {
    await fs.writeFile(path, content);
    return "文件写入成功";
  }
}

3. ReAct工作流

ReAct = Reason + Act,即“推理 + 行动”:

  1. Reason(推理):LLM分析当前状态,决定下一步行动

  2. Act(行动):执行选定的工具

  3. Observe(观察):获取执行结果

  4. 循环上述步骤直到任务完成

代码实现

第一步:环境配置

import { ChatOpenAI } from "@langchain/openai";
import { SystemMessage, HumanMessage, AIMessage, ToolMessage } from "@langchain/core/messages";
import { tool } from "@langchain/core/tools";
import { z } from "zod";
import fs from "fs/promises";
import { exec } from "child_process";
import util from "util";

const execPromise = util.promisify(exec);

// 初始化LLM
const model = new ChatOpenAI({
  modelName: "gpt-4",
  temperature: 0,
  apiKey: process.env.OPENAI_API_KEY
});

第二步:定义工具集

// 1. 写入文件工具
const writeFileTool = tool(
  async ({ path, content }) => {
    await fs.writeFile(path, content, "utf-8");
    return `✅ 文件 ${path} 写入成功`;
  },
  {
    name: "write_file",
    description: "将内容写入指定路径的文件",
    schema: z.object({
      path: z.string().describe("文件路径"),
      content: z.string().describe("要写入的内容")
    })
  }
);

// 2. 读取文件工具
const readFileTool = tool(
  async ({ path }) => {
    const content = await fs.readFile(path, "utf-8");
    return content;
  },
  {
    name: "read_file",
    description: "读取指定路径的文件内容",
    schema: z.object({
      path: z.string().describe("文件路径")
    })
  }
);

// 3. 执行命令工具
const execCommandTool = tool(
  async ({ command }) => {
    try {
      const { stdout, stderr } = await execPromise(command);
      return stdout || stderr || "命令执行完成";
    } catch (error) {
      return `❌ 命令执行失败: ${error.message}`;
    }
  },
  {
    name: "execute_command",
    description: "在终端执行shell命令",
    schema: z.object({
      command: z.string().describe("要执行的shell命令")
    })
  }
);

// 工具列表
const tools = [writeFileTool, readFileTool, execCommandTool];
const toolsByName = Object.fromEntries(
  tools.map(t => [t.name, t])
);

第三步:实现Agent主循环

async function runAgent(userInput: string) {
  // 消息历史
  const messages = [
    new SystemMessage(`你是一个智能编程助手,能够通过工具完成各种编程任务。
    
    可用工具:
    - write_file: 写入文件
    - read_file: 读取文件  
    - execute_command: 执行shell命令
    
    请按以下步骤思考:
    1. 理解用户的完整需求
    2. 规划实现步骤
    3. 逐步执行,每次只调用一个工具
    4. 遇到错误要分析原因并尝试修复`),
    
    new HumanMessage(userInput)
  ];

  // 绑定工具到模型
  const modelWithTools = model.bindTools(tools);
  
  let maxIterations = 20;
  
  while (maxIterations-- > 0) {
    console.log(`\n🔄 第 ${20 - maxIterations} 轮思考...`);
    
    // 调用LLM
    const response = await modelWithTools.invoke(messages);
    messages.push(response);
    
    // 检查是否有工具调用
    const toolCalls = response.additional_kwargs?.tool_calls || [];
    
    if (toolCalls.length === 0) {
      // 没有工具调用,任务完成
      console.log("✅ 任务完成!");
      console.log(response.content);
      return response.content;
    }
    
    // 执行工具调用
    for (const toolCall of toolCalls) {
      const toolName = toolCall.function.name;
      const toolArgs = JSON.parse(toolCall.function.arguments);
      const toolId = toolCall.id;
      
      console.log(`🔧 调用工具: ${toolName}`);
      console.log(`📝 参数:`, toolArgs);
      
      try {
        // 执行工具
        const tool = toolsByName[toolName];
        if (!tool) {
          throw new Error(`未知工具: ${toolName}`);
        }
        
        const result = await tool.invoke(toolArgs);
        
        // 添加工具执行结果到消息历史
        messages.push(new ToolMessage({
          content: result,
          toolCallId: toolId
        }));
        
        console.log(`✅ 工具执行成功`);
        console.log(`📄 结果:`, result.slice(0, 200) + "...");
        
      } catch (error) {
        // 错误处理
        messages.push(new ToolMessage({
          content: `❌ 工具执行失败: ${error.message}`,
          toolCallId: toolId
        }));
        
        console.log(`❌ 工具执行失败:`, error.message);
      }
    }
  }
  
  return "任务执行超时,请检查是否陷入死循环";
}

第四步:启动Agent

// 入口函数
async function main() {
  const userRequest = process.argv[2] || "创建一个React + Vite的TodoList应用";
  
  console.log("🚀 启动AI编程助手...");
  console.log(`📋 任务: ${userRequest}\n`);
  
  try {
    const result = await runAgent(userRequest);
    console.log("\n🎉 最终结果:");
    console.log(result);
  } catch (error) {
    console.error("💥 发生错误:", error);
  }
}

main();

运行演示

假设我们让Agent创建TodoList应用,它会这样工作:

🚀 启动AI编程助手...
📋 任务: 创建一个React + Vite的TodoList应用

🔄 第 1 轮思考...
🔧 调用工具: execute_command
📝 参数: { command: "npm create vite@latest todo-app -- --template react" }
✅ 工具执行成功

🔄 第 2 轮思考...
🔧 调用工具: execute_command
📝 参数: { command: "cd todo-app && npm install" }
✅ 工具执行成功

🔄 第 3 轮思考...
🔧 调用工具: write_file
📝 参数: { path: "todo-app/src/App.jsx", content: "..." }
✅ 工具执行成功

... (继续编写代码、运行项目)

✅ 任务完成!
🎉 你的TodoList应用已创建完成,运行 npm run dev 即可启动!

核心要点总结

1. LLM的局限性

大语言模型本身是无状态的,不能直接操作外部世界。它只能:

  • 理解文本

  • 生成文本

  • 规划步骤

通过Tool机制,我们赋予LLM“手脚”,让它能真正干活。

2. 消息历史的重要性

Agent的“记忆”就是messages数组。每次交互都追加新消息:

  • LLM的思考和工具调用 → AIMessage

  • 工具执行结果 → ToolMessage

这保证了Agent能“记住”之前做过的所有事情。

3. 错误处理机制

Agent必须能处理失败:

  • 命令执行失败 → 分析错误 → 尝试修复

  • 文件写入冲突 → 调整策略 → 重新执行

4. 工具设计原则

  • 单一职责:每个工具只做一件事

  • 清晰描述:让LLM理解工具的用途和参数

  • 错误返回:返回详细的错误信息,帮助LLM调试

进阶优化方向

  1. 并行工具调用:使用Promise.all同时执行多个独立操作

  2. Token优化:压缩工具返回结果,避免上下文过长

  3. 安全控制:限制危险命令,添加用户确认环节

  4. 多模态支持:集成图像生成、代码可视化等能力

  5. 记忆持久化:保存对话历史,支持断点续传

结语

通过这篇文章,我们亲手实现了一个简化版的AI编程助手,揭开了Claude Code、Cursor等工具的神秘面纱。

核心原理并不复杂:

  • LLM负责思考和规划

  • Tools负责执行具体操作

  • Agent循环协调两者完成复杂任务

这只是一个开始。有了这个基础框架,你可以添加更多工具(如Git操作、API调用、数据库查询等),构建出更强大的AI助手。

技术改变世界,而AI正在改变技术本身。 希望这篇文章能帮助你更好地理解AI Agent的原理,甚至开发出自己的AI工具!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐