【硬核实战】手撸一个本地AI Agent:从零构建 “OpenClaw” (Node.js + DeepSeek)

摘要:最近 AI Agent(智能体)的概念火遍全网。与其做一个单纯的“调包侠”,不如亲自动手写一个!本文将带你从零开始,使用 Node.js 构建一个运行在本地的、拥有“系统操作权限”的 AI 助手 —— 我们将其命名为 OpenClaw。它不仅能陪你聊天,还能帮你执行终端命令、读写文件。

关键词:AI Agent, Node.js, DeepSeek, OpenAI API, 本地部署, 自动化, Function Calling


1. 什么是 OpenClaw?为什么你需要一个本地 Agent?

传统的 ChatGPT 或 Claude 网页版虽然强大,但它们像被关在沙盒里的天才——它们无法直接访问你的本地文件,也无法帮你执行终端命令(比如“帮我把下载文件夹里所有的 PDF 移动到 Documents 目录”)。

OpenClaw 的核心理念是:给 AI 装上“爪子”(Tools)

在这篇教程中,我们将实现一个具备以下功能的 Agent:

  1. 大脑:接入 DeepSeek-V3 或 GPT-4(通过 OpenAI 兼容接口)。
  2. 感官:能够读取用户在终端的输入。
  3. 爪子:能够执行 Shell 命令、读取本地文件。
  4. 记忆:基于对话历史的上下文理解。

2. 环境准备 (Prerequisites)

可以添加qq交流群获取工具1078800977
在开始之前,请确保你的环境满足以下要求:

  • 操作系统:MacOS / Linux / Windows (WSL2 推荐)
  • Node.js:v18 或更高版本
  • API Key:你需要一个 OpenAI 格式的 API Key(推荐使用 DeepSeek,性价比极高;或者 OpenAI、Anthropic)。

初始化项目

打开终端,创建一个名为 openclaw 的文件夹并初始化:

mkdir openclaw
cd openclaw
npm init -y

修改 package.json,添加 "type": "module" 以支持 ES6 模块(这很重要!):

{
  "name": "openclaw",
  "version": "1.0.0",
  "type": "module",
  ...
}

安装必要的依赖:

# openai: 用于连接大模型标准接口
# dotenv: 用于管理环境变量,保护你的 Key
# chalk: 用于终端彩色输出(提升开发体验)
npm install openai dotenv chalk

3. 第一步:配置“大脑”与基础连接

在项目根目录下创建一个 .env 文件,填入你的 API Key。这里以 DeepSeek 为例:

# 如果使用 DeepSeek
OPENAI_API_KEY=sk-your-deepseek-key-here
OPENAI_BASE_URL=https://api.deepseek.com

# 如果使用 OpenAI
# OPENAI_API_KEY=sk-your-openai-key-here
# OPENAI_BASE_URL=https://api.openai.com/v1

4. 核心代码编写:Agent 的灵魂

我们将所有逻辑写在一个 index.js 文件中。为了方便大家理解,我们将代码分为三个部分:工具定义执行逻辑主循环

新建 index.js,将以下代码复制进去。

4.1 引入依赖与配置

import OpenAI from 'openai';
import dotenv from 'dotenv';
import chalk from 'chalk';
import readline from 'readline';
import { exec } from 'child_process';
import util from 'util';

// 加载环境变量
dotenv.config();

// 将 exec Promise 化,方便异步调用
const execPromise = util.promisify(exec);

// 配置 OpenAI 客户端
const client = new OpenAI({
    apiKey: process.env.OPENAI_API_KEY,
    baseURL: process.env.OPENAI_BASE_URL,
});

// 创建终端输入接口
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

console.log(chalk.blue.bold("🤖 OpenClaw v1.0 (Local Agent) is online."));
console.log(chalk.gray("Type 'exit' to quit.\n"));

4.2 定义“爪子” (Tools Schema)

这是 Agent 最关键的部分。我们需要告诉 AI:“你可以使用这个工具来操作电脑。”

// 定义工具描述 (Schema)
const tools = [
    {
        type: "function",
        function: {
            name: "execute_shell_command",
            description: "Execute a shell command on the local machine. Use this to list files, read content, check system status, or run scripts.",
            parameters: {
                type: "object",
                properties: {
                    command: {
                        type: "string",
                        description: "The shell command to execute (e.g., 'ls -la', 'cat package.json', 'whoami')."
                    }
                },
                required: ["command"]
            }
        }
    }
];

// 实际执行工具的函数
async function executeShellCommand(command) {
    console.log(chalk.cyan(`> 🔧 OpenClaw is executing: ${command}`));
    try {
        // 安全提示:生产环境中这里应该加一个白名单或确认步骤
        const { stdout, stderr } = await execPromise(command);
        if (stderr) {
             // 有些命令虽然成功但会输出到 stderr,这里视情况处理
            return `Output: ${stdout}\nStderr: ${stderr}`;
        }
        return stdout || "Command executed successfully with no output.";
    } catch (error) {
        return `Error executing command: ${error.message}`;
    }
}

4.3 实现 ReAct 循环 (Think -> Act -> Observe)

这是 OpenClaw 的核心逻辑。它会判断大模型是否返回了 tool_calls,如果是,则执行命令并将结果“喂”回给大模型。

// 初始化对话历史,设定 System Prompt
const messages = [
    { 
        role: "system", 
        content: `You are OpenClaw, a helpful AI assistant running on the user's local machine.
        You have permission to access the file system and run commands via the 'execute_shell_command' tool.
        
        Rules:
        1. When asked to do something on the computer, USE THE TOOL. Do not guess.
        2. Be concise in your responses.
        3. If the output is long, summarize it unless asked for details.
        ` 
    }
];

// 处理单次对话流
async function processChat(userBuffer) {
    // 1. 发送请求给 LLM
    const completion = await client.chat.completions.create({
        messages: messages,
        model: "deepseek-chat", // ⚠️ 注意:根据你的服务商修改模型名称,如 "gpt-4o"
        tools: tools,
        tool_choice: "auto", 
    });

    const responseMessage = completion.choices[0].message;

    // 2. 检查 AI 是否想调用工具
    if (responseMessage.tool_calls) {
        // 把 AI 的“思考/调用请求”加入历史,保持上下文完整
        messages.push(responseMessage);

        // 遍历所有工具调用请求(有时 AI 会一次性调用多个工具)
        for (const toolCall of responseMessage.tool_calls) {
            if (toolCall.function.name === 'execute_shell_command') {
                const args = JSON.parse(toolCall.function.arguments);
                
                // --- 真正执行命令的地方 ---
                const output = await executeShellCommand(args.command);
                // -----------------------
                
                // 3. 将工具的执行结果(Observation)返回给 AI
                messages.push({
                    tool_call_id: toolCall.id,
                    role: "tool",
                    name: "execute_shell_command",
                    content: output, // 这里是命令行的真实输出
                });
            }
        }

        // 4. 让 AI 拿到工具结果后,再次生成最终回复
        const secondResponse = await client.chat.completions.create({
            messages: messages,
            model: "deepseek-chat", 
        });

        return secondResponse.choices[0].message.content;

    } else {
        // 如果没有调用工具,直接返回文本回复
        messages.push(responseMessage);
        return responseMessage.content;
    }
}

4.4 启动交互循环

function ask() {
    rl.question(chalk.green('You: '), async (input) => {
        if (input.toLowerCase() === 'exit') {
            console.log(chalk.gray("Goodbye!"));
            rl.close();
            return;
        }

        // 记录用户输入
        messages.push({ role: "user", content: input });

        try {
            process.stdout.write(chalk.gray("OpenClaw is thinking...\r"));
            
            const finalResponse = await processChat(input);
            
            // 清除 thinking 提示并输出回答
            process.stdout.clearLine();
            process.stdout.cursorTo(0);
            console.log(chalk.yellow('OpenClaw: ') + finalResponse + '\n');
        } catch (error) {
            console.error(chalk.red("\nError:"), error.message);
        }

        ask(); // 递归调用,保持持续对话
    });
}

// 启动!
ask();

5. 实战演示 (Demo Time)

现在,让我们启动 OpenClaw 并测试它的能力!

在终端运行:

node index.js

测试案例 1:文件操作

你输入: 在当前目录下创建一个叫 demo.txt 的文件,里面写入 "Hello CSDN"
OpenClaw:
(后台日志: > OpenClaw is executing: echo “Hello CSDN” > demo.txt)

文件 demo.txt 已成功创建并写入内容。

测试案例 2:系统查询

你输入: 我想知道我的电脑运行了多久了(uptime)
OpenClaw:
(后台日志: > OpenClaw is executing: uptime)

你的系统运行时间如下:up 3 days, 4:21…

测试案例 3:自我认知与逻辑

你输入: 查看 demo.txt 的内容,并把内容重复三遍输出给我
OpenClaw:
(后台日志: > OpenClaw is executing: cat demo.txt)

好的,内容重复三遍如下:
Hello CSDN
Hello CSDN
Hello CSDN


6. 进阶扩展思路

恭喜!你刚刚用不到 100 行代码,构建了一个拥有系统级操作能力的 AI Agent。这只是一个开始,你可以从以下几个方向继续扩展 OpenClaw:

  1. 增加安全锁:为了防止 AI 发疯执行 rm -rf /,你可以增加一个 confirm 步骤,当检测到危险命令时,先让用户输入 y 确认。
  2. 网络能力:添加一个 fetch_url 工具,让 OpenClaw 可以读取网页内容,实现“联网搜索”。
  3. 持久化记忆:目前重启后 OpenClaw 会失忆。你可以引入简单的 JSON 文件读写,或者向量数据库来保存长期记忆。
  4. 系统级集成:将其打包成 CLI 工具,让它能在任何目录下直接通过 openclaw "帮我整理当前文件夹" 来调用。

7. 总结

AI Agent 的时代正在到来,Local-First(本地优先) 将是保障隐私和效率的重要方向。通过本文的实战,你应该已经理解了 Agent 的核心原理:Prompt Engineering + Function Calling

如果你觉得这篇文章对你有帮助,欢迎点赞收藏!你的支持是我更新的最大动力。

参考资料

  • OpenAI API Documentation
  • DeepSeek API Docs
  • Node.js Child Process Documentation

本文由ywy 原创发布于 CSDN,转载请注明出处。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐