230行代码,零依赖,我用一个文件造了一个AI Agent

2 阅读5分钟

230行代码,零依赖,我用一个文件造了一个AI Agent

一个文件,230行代码,零npm依赖——这是我造一个AI Agent的全部成本。

它能自主思考、调用工具、读写文件、执行命令,完成你交给它的任务。它叫 Mini OpenClaw


为什么要造这个轮子

市面上的Agent框架,LangChain、AutoGPT、CrewAI……动辄几十个依赖,上千个文件,光node_modules就能吃掉半个硬盘。

我只想搞清楚一件事:Agent的本质到底是什么?

翻遍所有框架的源码,剥掉封装、抽象、设计模式,剩下的核心逻辑只有一个循环:

用户提问 → LLM思考 → 需要工具吗?
                       ├── 是 → 执行工具 → 结果喂回LLM → 继续思考…
                       └── 否 → 输出最终回答 ✅

这就是 ReAct(Reasoning + Acting)

Agent不是魔法,是一个while循环。

既然核心这么简单,为什么不能用一个文件实现它?

于是我动手了。


架构:极简到不能再简

整个项目只有一个文件 mini-openclaw.mjs,分成4个部分:

 mini-openclaw.mjs(单文件,4个部分)
┌──────────────────────────────────────┐
│  第一部分:工具定义                    │  ← 4个内置工具
│  第二部分:LLM调用                    │  ← CodeBuddy CLI驱动
│  第三部分:ReAct循环                  │  ← 思考→行动→观察
│  第四部分:交互式REPL                 │  ← 命令行界面
└──────────────────────────────────────┘
模块职责代码量
工具定义read_file · write_file · list_dir · run_command~40行
LLM调用通过CodeBuddy CLI非交互式调用大模型~50行
ReAct循环检测<tool_call>标签 → 执行工具 → 结果追加 → 继续~80行
REPLreadline交互界面,支持多轮对话~60行

没有package.json。没有node_modules。没有构建步骤。

node mini-openclaw.mjs,一行命令,直接跑。

最好的架构,是你能一眼看完的架构。


核心实现:4个关键设计决策

决策一:文本标签式工具调用

主流方案用OpenAI的function calling格式,需要特定的API结构。

Mini OpenClaw用了一种更简单的方式——文本标签

<tool_call>{"name":"read_file","args":{"path":"test.txt"}}</tool_call>

一个正则就能解析:

const m = text.match(/<tool_call>([\s\S]*?)<\/tool_call>/);

Before:依赖特定API格式,换模型就得改代码 After:纯文本协议,任何模型都能用

决策二:CLI而非HTTP

调用LLM不走HTTP API,直接spawn CodeBuddy CLI进程:

const proc = spawn("codebuddy", [
  "-p",                        // 非交互式
  "--output-format", "json",   // JSON输出
  "--max-turns", "1",          // 单轮
  "--tools", "",               // 禁用内置工具
  "--model", model,
  prompt,
]);

Before:需要HTTP客户端、处理鉴权、管理连接 After:一个spawn搞定,零网络代码

决策三:纯文本对话历史

不用复杂的消息对象数组,直接用字符串拼接:

User: 读取test.txt
Assistant: [调用了 read_file]
[工具结果]: hello world
Assistant: 文件内容是 hello world

简单、直观、debug友好。

决策四:重复调用检测

模型有时会陷入死循环,反复调用同一个工具。Mini OpenClaw用一行代码解决:

const callKey = JSON.stringify(call);
if (callKey === lastCallKey) {
  // 强制终止,让模型总结已有结果
}

Before:Agent卡死,token烧光 After:自动刹车,强制输出

好的防御机制,是用户永远感知不到的那种。


实战效果:3个场景

场景一:普通对话(0次工具调用)

You> 你好,介绍一下你自己

  ⚙ 迭代 1/15

🤖 Assistant:
我是一个智能助手,拥有读写文件、列出目录、执行命令等工具,
可以帮助你完成编程、调试、项目维护等开发相关工作。

LLM判断不需要工具,1次迭代,直接回答。

场景二:读取文件(1次工具调用)

You> 读取 .gitattributes 文件内容并解释它的作用

  ⚙ 迭代 1/15
  🔧 调用工具: read_file({"path":".gitattributes"})
  📋 结果: # Auto detect text files and perform LF normalization
            * text=auto

  ⚙ 迭代 2/15

🤖 Assistant:
.gitattributes 文件用于配置 Git 对文件的处理方式:
- `* text=auto`:对所有文件启用文本自动检测和行尾LF标准化

LLM思考 → 调用工具 → 拿到结果 → 总结回答。2次迭代。

场景三:执行命令(1次工具调用)

You> 用命令查看当前的 node 版本和操作系统信息

  ⚙ 迭代 1/15
  🔧 调用工具: run_command({"command":"node --version && uname -a"})
  📋 结果: v22.22.0
            Darwin Enos-MacBook-Pro.local 24.6.0 ... arm64

  ⚙ 迭代 2/15

🤖 Assistant:
当前环境信息:
- Node.js 版本:v22.22.0
- 操作系统:macOS Darwin (Apple Silicon arm64)

Before:手动敲命令,自己看输出 After:Agent自主决策执行什么命令,自动总结结果

Agent的价值不在于它能做什么,在于你不用告诉它怎么做。


ReAct循环:一张图看懂全部

┌─────────┐    prompt     ┌──────────────┐    spawn     ┌───────────────┐
│  用户    │ ────────────→ │  ReAct循环    │ ──────────→ │ CodeBuddy CLI │
│  输入    │              │  (react函数)   │ ←────────── │  (LLM回复)    │
└─────────┘              └──────┬───────┘    JSON       └───────────────┘
                                │
                    检测 <tool_call> 标签
                                │
                    ┌───────────┴───────────┐
                    │ 有                     │ 无
                    ▼                       ▼
              ┌──────────┐           ┌──────────┐
              │ 执行工具  │           │ 输出回答  │
              │ 追加结果  │           │ 循环结束  │
              │ 继续循环  │           └──────────┘
              └──────────┘

整个流程的代码实现,核心就是react函数里的一个for循环:

for (let i = 1; i <= maxIter; i++) {
  const prompt = buildSystemPrompt() + "\n\n" + history.join("\n\n");
  const reply = await callLLM(prompt, model, apiKey);
  const call = extractToolCall(reply);
  
  if (!call) {
    // 没有工具调用 → 最终回答
    return reply;
  }
  
  // 有工具调用 → 执行 → 结果追加到历史 → 继续循环
  const result = executeTool(call);
  history.push(`[工具结果]: ${result}`);
}

这就是一个AI Agent的全部核心逻辑。

没有中间件。没有插件系统。没有抽象层。

一个循环,一个正则,一个spawn。


快速上手:3步跑起来

# 1. 克隆
git clone https://github.com/wscats/enoclaw.git
cd enoclaw

# 2. 设置Key
export CODEBUDDY_API_KEY=ck_你的key

# 3. 运行
node mini-openclaw.mjs

想换模型?一个环境变量:

MODEL=hunyuan-2.0-thinking node mini-openclaw.mjs

支持的模型:deepseek-v3-2-volc · hunyuan-2.0-thinking · glm-5.0 · glm-4.7 · minimax-m2.5 · kimi-k2.5

前置条件只有两个:Node.js ≥ 18,CodeBuddy CLI(npm i -g @tencent-ai/codebuddy-code)。

好工具的标准:README都不用看完就能跑起来。


写在最后

230行代码能造一个Agent,这件事本身说明了什么?

AI Agent的门槛,从来不在代码量。

LangChain有10万行代码,Mini OpenClaw有230行。它们的核心循环,一模一样。

区别在于:一个让你用框架,一个让你理解框架

所有Agent框架,都是这230行的变体。


📎 GitHub:github.com/Wscats/mini…