大家好,我是 JavaPub。

上一篇我们使用 Python,从 0 开始手写了一个 AI Agent。

这次换一个很多 Web 开发者更加熟悉的技术栈:

Node.js
+
gpt-5.6-sol
+
OpenAI Compatible API
+
Tool Calling

我们不使用 LangChain,不使用 LangGraph,也不引入一堆 Agent Framework。

直接手写一个最小可用的 AI Agent。

本文使用的 API 地址:

https://api.chongplus.plus

模型:

gpt-5.6-sol

最终,我们要实现这样的效果:

你:
帮我计算 12345 * 6789,然后把计算结果保存成笔记。

Agent:
我需要先计算。

[Agent] 调用工具:calculator
[Agent] 参数:{"expression":"12345 * 6789"}

[Tool] 返回:
83810205

Agent:
接下来需要保存计算结果。

[Agent] 调用工具:save_note
[Agent] 参数:
{"content":"12345 × 6789 = 83810205"}

[Tool] 返回:
笔记保存成功

Agent:
计算完成,12345 × 6789 = 83810205,并且已经保存到笔记。

注意:

这里并不是我们在 JavaScript 里面提前写好了:

calculator();
saveNote();

而是:

gpt-5.6-sol 自己判断应该调用什么工具、传什么参数,以及下一步应该继续干什么。

这就是今天最核心的内容。


一、什么才算 AI Agent?

我们先把概念说清楚。

普通的大模型应用一般是:

用户
 ↓
LLM
 ↓
回答

例如:

用户:
12345 × 6789 是多少?

模型直接返回一个答案。

而 Agent 的运行过程更接近:

用户提出目标
      ↓
gpt-5.6-sol
      ↓
判断下一步应该做什么
      ↓
调用工具
      ↓
获得工具返回值
      ↓
继续交给 gpt-5.6-sol
      ↓
再次判断
      ↓
调用其他工具
      ↓
最终完成任务

所以我们可以先记住一个公式:

AI Agent
=
LLM
+
Prompt
+
Tools
+
Memory
+
Agent Loop

其中:

LLM
负责思考和决策

Prompt
负责告诉 Agent 身份和规则

Tools
负责真正执行操作

Memory
负责保存上下文

Agent Loop
负责让 Agent 连续执行多个步骤

今天我们就把这五个部分全部写出来。


二、准备 Node.js 环境

推荐:

Node.js 18+

查看版本:

node -v

例如:

v22.18.0

创建项目:

mkdir node-agent
cd node-agent

初始化:

npm init -y

安装 OpenAI SDK:

npm install openai

项目结构非常简单:

node-agent/
├── package.json
└── agent.js

三、配置 ES Module

为了直接使用:

import OpenAI from "openai";

在:

package.json

增加:

{
  "type": "module"
}

完整一点可以是:

{
  "name": "node-agent",
  "version": "1.0.0",
  "type": "module",
  "main": "agent.js",
  "scripts": {
    "start": "node agent.js"
  },
  "dependencies": {
    "openai": "^5.0.0"
  }
}

之后就可以运行:

npm start

四、连接 gpt-5.6-sol

首先创建:

agent.js

写入:

import OpenAI from "openai";

const client = new OpenAI({
  apiKey: "sk-xxxxxxxxxxxxxxxx",
  baseURL: "https://api.chongplus.plus/v1",
});

const response = await client.chat.completions.create({
  model: "gpt-5.6-sol",

  messages: [
    {
      role: "user",
      content: "你好,请介绍一下你自己。",
    },
  ],
});

console.log(response.choices[0].message.content);

运行:

node agent.js

可能返回:

你好!我是一个 AI 助手,可以帮助你进行编程、分析、写作、信息整理以及完成各种任务。

到这里其实只是最普通的:

ChatBot

还不是 Agent。

因为模型目前只能:

输入文字
↓
生成文字

还无法真正执行操作。


五、System Prompt

接下来,我们先给 Agent 定义身份。

例如:

const SYSTEM_PROMPT = `
你是一个 AI Agent。

你的任务是帮助用户完成任务。

你可以根据实际情况调用提供给你的工具。

规则:

1. 如果能够直接回答,则直接回答。
2. 如果任务需要使用工具,则调用对应工具。
3. 不允许伪造工具执行结果。
4. 工具执行完成以后,根据返回结果继续判断下一步。
5. 一个任务允许连续调用多个工具。
6. 当任务真正完成以后,再向用户返回最终答案。
`;

请求:

const messages = [
  {
    role: "system",
    content: SYSTEM_PROMPT,
  },

  {
    role: "user",
    content: "你好",
  },
];

但是只有 Prompt 还不够。

虽然我们告诉模型:

你可以使用工具

但模型根本不知道:

有哪些工具?

所以接下来就要进入 AI Agent 最核心的一部分:

Tool Calling


六、什么是 Tool Calling?

Tool Calling,也经常被叫做:

Function Calling

你可以简单理解为:

我们把 JavaScript 函数的“使用说明书”告诉 gpt-5.6-sol,然后让模型自己决定什么时候需要调用。

例如我们有一个函数:

function calculator(expression) {
  // ...
}

它可以计算:

123 * 456

但是模型本身并不会直接运行这个 JavaScript 函数。

我们需要告诉模型:

这里有一个工具:

名称:
calculator

作用:
执行数学计算

参数:
expression

当用户说:

帮我算一下 123 * 456

gpt-5.6-sol 可能不会直接回答。

而是返回类似:

{
  "name": "calculator",
  "arguments": {
    "expression": "123 * 456"
  }
}

意思就是:

AI:

我认为这里应该调用 calculator。

参数是:

123 * 456

然后真正执行 JavaScript 函数的是:

我们的 Node.js 程序

所以:

LLM 决定做什么

Node.js 负责真正执行

整个流程是:

                 用户
                  │
                  ▼
          ┌──────────────┐
          │ gpt-5.6-sol  │
          └──────┬───────┘
                 │
            Tool Call
                 │
                 ▼
          ┌──────────────┐
          │   Node.js    │
          │    Tools     │
          └──────┬───────┘
                 │
            Tool Result
                 │
                 ▼
          ┌──────────────┐
          │ gpt-5.6-sol  │
          └──────┬───────┘
                 │
                 ▼
             最终回答

理解这个流程之后,Agent 基本就理解一半了。


七、第一个工具:Calculator

先实现一个简单计算器。

这里为了方便演示,我们写:

function calculator(expression) {
  try {
    const result = Function(
      `"use strict"; return (${expression})`
    )();

    return String(result);
  } catch (error) {
    return `计算失败:${error.message}`;
  }
}

测试:

console.log(
  calculator("12345 * 6789")
);

输出:

83810205

注意:

这种动态执行表达式的方式主要用于教程演示。

生产环境不要直接让不可信用户输入进入 Function() 或类似动态执行环境。

后面我们再讲安全问题。


八、告诉 gpt-5.6-sol 有这个工具

JavaScript 函数写好了,但模型现在还不知道。

因此需要定义:

const tools = [
  {
    type: "function",

    function: {
      name: "calculator",

      description:
        "执行数学计算。当用户需要进行数学运算时调用。",

      parameters: {
        type: "object",

        properties: {
          expression: {
            type: "string",

            description:
              "需要执行的数学表达式,例如:123 * 456",
          },
        },

        required: ["expression"],
      },
    },
  },
];

这一段实际上就是:

Tool Schema

你可以把它理解成:

写给大模型看的 API 文档。

它告诉模型:

工具叫什么?

这个工具是干什么的?

有哪些参数?

参数是什么类型?

哪些参数必须传?

九、让模型自己决定是否调用 Calculator

现在写:

import OpenAI from "openai";

const client = new OpenAI({
  apiKey: "sk-xxxxxxxxxxxxxxxx",
  baseURL: "https://api.chongplus.plus/v1",
});

const tools = [
  {
    type: "function",

    function: {
      name: "calculator",

      description: "执行数学计算。",

      parameters: {
        type: "object",

        properties: {
          expression: {
            type: "string",
            description: "数学表达式",
          },
        },

        required: ["expression"],
      },
    },
  },
];

const messages = [
  {
    role: "system",
    content:
      "你是一个 AI Agent,可以根据任务需要调用工具。",
  },

  {
    role: "user",
    content:
      "帮我计算 12345 * 6789",
  },
];

const response =
  await client.chat.completions.create({
    model: "gpt-5.6-sol",

    messages,

    tools,

    tool_choice: "auto",
  });

console.dir(
  response.choices[0].message,
  {
    depth: null,
  }
);

这里最重要的是:

tool_choice: "auto"

意思就是:

让模型自己判断:

到底需不需要调用工具。

运行之后,可能返回:

{
  role: 'assistant',
  content: null,
  tool_calls: [
    {
      id: 'call_q8F7G23',
      type: 'function',
      function: {
        name: 'calculator',
        arguments: '{"expression":"12345 * 6789"}'
      }
    }
  ]
}

重点看:

tool_calls

模型告诉我们的程序:

我要调用:

calculator

参数:

12345 * 6789

这时候:

content: null

也很正常。

因为模型现在不是准备直接回答用户。

而是在:

请求执行工具

十、解析 Tool Call

获得:

const message =
  response.choices[0].message;

然后:

const toolCall =
  message.tool_calls[0];

const functionName =
  toolCall.function.name;

const args = JSON.parse(
  toolCall.function.arguments
);

console.log(functionName);
console.log(args);

输出:

calculator

以及:

{
  expression: '12345 * 6789'
}

然后真正执行:

const result = calculator(
  args.expression
);

console.log(result);

返回:

83810205

现在 JavaScript 已经算出结果。

但是:

gpt-5.6-sol 并不知道结果是什么。

所以还要继续。


十一、把 Tool Result 返回给模型

首先把模型刚才的消息放进上下文:

messages.push(message);

然后加入:

messages.push({
  role: "tool",

  tool_call_id: toolCall.id,

  content: result,
});

现在对话实际上变成:

System:

你是一个 AI Agent。


User:

帮我计算 12345 * 6789


Assistant:

我要调用 calculator。


Tool:

83810205

接着再次调用 gpt-5.6-sol:

const finalResponse =
  await client.chat.completions.create({
    model: "gpt-5.6-sol",

    messages,

    tools,
  });

console.log(
  finalResponse.choices[0].message.content
);

可能输出:

12345 × 6789 = 83810205。

到这里,第一个真正意义上的:

Node.js AI Agent

已经出现了。


十二、完整 Calculator Agent

我们整理一下代码。

import OpenAI from "openai";


const client = new OpenAI({
  apiKey: "sk-xxxxxxxxxxxxxxxx",

  baseURL:
    "https://api.chongplus.plus/v1",
});


function calculator(expression) {
  try {
    const result = Function(
      `"use strict"; return (${expression})`
    )();

    return String(result);
  } catch (error) {
    return `计算失败:${error.message}`;
  }
}


const tools = [
  {
    type: "function",

    function: {
      name: "calculator",

      description:
        "执行数学计算。",

      parameters: {
        type: "object",

        properties: {
          expression: {
            type: "string",

            description:
              "需要执行的数学表达式",
          },
        },

        required: [
          "expression",
        ],
      },
    },
  },
];


const messages = [
  {
    role: "system",

    content:
      "你是一个 AI Agent,可以调用工具帮助用户完成任务。",
  },

  {
    role: "user",

    content:
      "帮我计算 12345 * 6789",
  },
];


const response =
  await client.chat.completions.create({
    model: "gpt-5.6-sol",

    messages,

    tools,

    tool_choice: "auto",
  });


const message =
  response.choices[0].message;


messages.push(message);


if (
  message.tool_calls &&
  message.tool_calls.length > 0
) {

  for (
    const toolCall
    of message.tool_calls
  ) {

    const name =
      toolCall.function.name;

    const args =
      JSON.parse(
        toolCall.function.arguments
      );

    let result;

    if (
      name === "calculator"
    ) {

      result =
        calculator(
          args.expression
        );
    }

    messages.push({
      role: "tool",

      tool_call_id:
        toolCall.id,

      content:
        String(result),
    });
  }


  const finalResponse =
    await client.chat.completions.create({
      model: "gpt-5.6-sol",

      messages,

      tools,
    });


  console.log(
    finalResponse
      .choices[0]
      .message
      .content
  );

} else {

  console.log(
    message.content
  );
}

运行:

node agent.js

返回:

12345 × 6789 = 83810205。

十三、再增加一个时间工具

Agent 真正有意思的地方在于:

一个 Agent 可以拥有很多工具。

我们再增加:

get_current_time

Node.js:

function getCurrentTime() {
  return new Date()
    .toLocaleString(
      "zh-CN",
      {
        timeZone:
          "Asia/Shanghai",
      }
    );
}

测试:

console.log(
  getCurrentTime()
);

例如输出:

2026/8/14 12:38:21

Tool Schema:

{
  type: "function",

  function: {
    name: "get_current_time",

    description:
      "获取当前北京时间。",

    parameters: {
      type: "object",

      properties: {},
    },
  },
}

现在:

const tools = [
  {
    type: "function",

    function: {
      name: "calculator",

      description:
        "执行数学计算。",

      parameters: {
        type: "object",

        properties: {
          expression: {
            type: "string",
          },
        },

        required: [
          "expression",
        ],
      },
    },
  },

  {
    type: "function",

    function: {
      name:
        "get_current_time",

      description:
        "获取当前北京时间。",

      parameters: {
        type: "object",

        properties: {},
      },
    },
  },
];

用户:

现在几点?

模型可能返回:

{
  "tool_calls": [
    {
      "type": "function",
      "function": {
        "name": "get_current_time",
        "arguments": "{}"
      }
    }
  ]
}

Node.js 执行:

2026/8/14 12:38:21

然后模型根据真正的 Tool Result 回答:

当前北京时间是 2026 年 8 月 14 日 12:38 左右。

十四、增加第三个工具:保存笔记

接下来增加:

save_note

这个工具已经开始让 Agent:

真正影响外部世界

首先:

import fs from "node:fs";

然后:

function saveNote(content) {
  fs.appendFileSync(
    "notes.txt",
    content + "\n",
    "utf8"
  );

  return "笔记保存成功";
}

例如执行:

console.log(
  saveNote(
    "今天开始学习 AI Agent"
  )
);

返回:

笔记保存成功

同时生成:

notes.txt

内容:

今天开始学习 AI Agent

Tool Schema:

{
  type: "function",

  function: {
    name: "save_note",

    description:
      "将指定内容保存到本地笔记。",

    parameters: {
      type: "object",

      properties: {
        content: {
          type: "string",

          description:
            "需要保存的笔记内容",
        },
      },

      required: [
        "content",
      ],
    },
  },
}

现在 Agent 拥有三个工具:

calculator

get_current_time

save_note

十五、真正的 Agent 不能只调用一次工具

现在问题来了。

用户如果说:

帮我计算 12345 * 6789,然后把结果保存到笔记。

这其实不是一步。

而是:

第一步

calculator

拿到:

83810205

然后:

第二步

save_note

最后才:

回答用户

因此整个过程应该是:

User
 ↓
gpt-5.6-sol
 ↓
calculator
 ↓
Tool Result
 ↓
gpt-5.6-sol
 ↓
save_note
 ↓
Tool Result
 ↓
gpt-5.6-sol
 ↓
Final Answer

如果我们只写:

if (message.tool_calls) {
}

执行一次就结束。

那么严格来说还不够。

一个真正的 Agent 需要:

Agent Loop


十六、Agent Loop 是整个 Agent 最核心的东西

最核心的代码其实就是:

while (true) {
}

大概逻辑:

while (true) {

  const response =
    await callLLM();

  if (
    response.tool_calls
  ) {

    await executeTools();

    continue;
  }

  return response.content;
}

什么意思?

就是:

让 AI 思考

如果想调用工具
    执行工具

把结果告诉 AI

再让 AI 思考

如果还要调用工具
    继续执行

直到 AI 不再调用工具

这就是:

Agent Loop

十七、实现 Tool Executor

随着工具越来越多,我们显然不能把所有判断都写在主逻辑里面。

所以创建:

async function executeTool(
  name,
  args
) {

  switch (name) {

    case "calculator":

      return calculator(
        args.expression
      );


    case "get_current_time":

      return getCurrentTime();


    case "save_note":

      return saveNote(
        args.content
      );


    default:

      return `未知工具:${name}`;
  }
}

以后新增工具,只需要继续:

case "search_web":

case "read_file":

case "send_email":

即可。


十八、完整 Agent Loop

现在进入整篇文章最重要的代码。

async function runAgent(
  userInput
) {

  const messages = [
    {
      role: "system",

      content: `
你是一个 AI Agent。

你可以调用工具帮助用户完成任务。

当前拥有:

1. calculator
数学计算。

2. get_current_time
获取当前北京时间。

3. save_note
保存笔记。

规则:

- 如果能够直接回答,则直接回答。
- 如果需要工具,必须调用工具。
- 不允许伪造工具执行结果。
- 一个任务允许调用多个工具。
- 每次工具执行结束以后,根据工具返回值继续完成任务。
- 当任务完全完成以后,再向用户返回最终答案。
`,
    },

    {
      role: "user",

      content:
        userInput,
    },
  ];


  while (true) {

    const response =
      await client
        .chat
        .completions
        .create({

          model:
            "gpt-5.6-sol",

          messages,

          tools,

          tool_choice:
            "auto",
        });


    const message =
      response
        .choices[0]
        .message;


    messages.push(
      message
    );


    if (
      !message.tool_calls ||
      message.tool_calls.length === 0
    ) {

      return message.content;
    }


    for (
      const toolCall
      of message.tool_calls
    ) {

      const name =
        toolCall
          .function
          .name;


      const args =
        JSON.parse(
          toolCall
            .function
            .arguments
        );


      console.log(
        `\n[Agent] 调用工具:${name}`
      );


      console.log(
        `[Agent] 参数:${JSON.stringify(args)}`
      );


      const result =
        await executeTool(
          name,
          args
        );


      console.log(
        `[Tool] 返回:${result}`
      );


      messages.push({
        role:
          "tool",

        tool_call_id:
          toolCall.id,

        content:
          String(result),
      });
    }
  }
}

这段代码其实已经是:

一个完整的最小 AI Agent Runtime

十九、第一次执行多步骤任务

调用:

const result =
  await runAgent(
    "帮我计算 12345 * 6789,然后把计算结果保存成笔记。"
  );

console.log(
  "\nAgent:"
);

console.log(result);

运行:

node agent.js

第一次:

[Agent] 调用工具:calculator

参数:

[Agent] 参数:
{"expression":"12345 * 6789"}

工具:

[Tool] 返回:
83810205

注意。

程序现在没有结束。

因为:

while (true)

会再次把:

Tool Result

交给 gpt-5.6-sol。

模型发现:

用户还要求保存结果。

所以继续:

[Agent] 调用工具:save_note

参数:

{
  "content": "12345 × 6789 = 83810205"
}

工具执行:

[Tool] 返回:
笔记保存成功

再次进入:

Agent Loop

这一次模型发现:

任务已经全部完成。

所以不再产生:

tool_calls

而是正常返回:

Agent:

计算完成。

12345 × 6789 = 83810205

计算结果已经保存到笔记。

同时项目目录出现:

notes.txt

里面:

12345 × 6789 = 83810205

这时候,一个完整的:

Node.js + gpt-5.6-sol AI Agent

已经跑起来了。


二十、完整可运行代码

下面直接给出完整版本。

文件:

agent.js

代码:

import OpenAI from "openai";
import fs from "node:fs";


/**
 * =========================
 * 基础配置
 * =========================
 */

const API_KEY =
  "sk-xxxxxxxxxxxxxxxx";

const BASE_URL =
  "https://api.chongplus.plus/v1";

const MODEL =
  "gpt-5.6-sol";


const client =
  new OpenAI({
    apiKey: API_KEY,

    baseURL:
      BASE_URL,
  });


/**
 * =========================
 * System Prompt
 * =========================
 */

const SYSTEM_PROMPT = `
你是一个 AI Agent。

你的目标是帮助用户完成任务,而不只是回答问题。

你拥有以下工具:

1. calculator
用于数学计算。

2. get_current_time
用于获取当前北京时间。

3. save_note
用于保存笔记。

工作规则:

1. 首先理解用户真正想完成的任务。
2. 如果能够直接回答,则直接回答。
3. 如果需要使用工具,则调用对应工具。
4. 不允许伪造工具执行结果。
5. 一个任务允许连续调用多个工具。
6. 每次工具执行完成以后,需要根据结果继续判断下一步。
7. 当任务真正全部完成以后,再向用户返回最终结果。
`;


/**
 * =========================
 * Tool 1
 * Calculator
 * =========================
 */

function calculator(
  expression
) {

  try {

    const result =
      Function(
        `"use strict"; return (${expression})`
      )();

    return String(
      result
    );

  } catch (error) {

    return (
      `计算失败:${error.message}`
    );
  }
}


/**
 * =========================
 * Tool 2
 * Current Time
 * =========================
 */

function getCurrentTime() {

  return new Date()
    .toLocaleString(
      "zh-CN",

      {
        timeZone:
          "Asia/Shanghai",

        hour12:
          false,
      }
    );
}


/**
 * =========================
 * Tool 3
 * Save Note
 * =========================
 */

function saveNote(
  content
) {

  fs.appendFileSync(

    "notes.txt",

    content + "\n",

    "utf8"

  );

  return (
    "笔记保存成功"
  );
}


/**
 * =========================
 * Tool Schema
 * =========================
 */

const tools = [

  {
    type: "function",

    function: {

      name:
        "calculator",

      description:
        "执行数学计算。当用户需要数学运算时使用。",

      parameters: {

        type:
          "object",

        properties: {

          expression: {

            type:
              "string",

            description:
              "需要执行的数学表达式,例如 123 * 456",
          },
        },

        required: [
          "expression",
        ],
      },
    },
  },


  {
    type: "function",

    function: {

      name:
        "get_current_time",

      description:
        "获取当前北京时间。",

      parameters: {

        type:
          "object",

        properties: {},
      },
    },
  },


  {
    type: "function",

    function: {

      name:
        "save_note",

      description:
        "将指定内容保存到本地笔记文件。",

      parameters: {

        type:
          "object",

        properties: {

          content: {

            type:
              "string",

            description:
              "需要保存的笔记内容",
          },
        },

        required: [
          "content",
        ],
      },
    },
  },
];


/**
 * =========================
 * Tool Executor
 * =========================
 */

async function executeTool(
  name,
  args
) {

  switch (name) {

    case "calculator":

      return calculator(
        args.expression
      );


    case "get_current_time":

      return getCurrentTime();


    case "save_note":

      return saveNote(
        args.content
      );


    default:

      return (
        `未知工具:${name}`
      );
  }
}


/**
 * =========================
 * Conversation Memory
 * =========================
 */

const messages = [

  {
    role:
      "system",

    content:
      SYSTEM_PROMPT,
  },

];


/**
 * =========================
 * Agent
 * =========================
 */

async function runAgent(
  userInput
) {

  messages.push({

    role:
      "user",

    content:
      userInput,

  });


  while (true) {

    const response =
      await client
        .chat
        .completions
        .create({

          model:
            MODEL,

          messages,

          tools,

          tool_choice:
            "auto",

        });


    const message =
      response
        .choices[0]
        .message;


    messages.push(
      message
    );


    /**
     * 没有 Tool Call
     *
     * 说明 Agent
     * 认为任务已经完成
     */

    if (
      !message.tool_calls ||
      message.tool_calls.length === 0
    ) {

      return (
        message.content
      );
    }


    /**
     * 执行所有 Tool Call
     */

    for (
      const toolCall
      of message.tool_calls
    ) {

      const name =
        toolCall
          .function
          .name;


      let args = {};

      try {

        args =
          JSON.parse(
            toolCall
              .function
              .arguments
          );

      } catch {

        args = {};
      }


      console.log(
        `\n[Agent] 调用工具:${name}`
      );


      console.log(
        `[Agent] 参数:${JSON.stringify(args)}`
      );


      let result;

      try {

        result =
          await executeTool(
            name,
            args
          );

      } catch (error) {

        result =
          `工具执行失败:${error.message}`;
      }


      console.log(
        `[Tool] 返回:${result}`
      );


      messages.push({

        role:
          "tool",

        tool_call_id:
          toolCall.id,

        content:
          String(result),

      });
    }
  }
}


/**
 * =========================
 * CLI
 * =========================
 */

console.log(
  "=============================="
);

console.log(
  "gpt-5.6-sol AI Agent"
);

console.log(
  "输入 exit / quit 退出"
);

console.log(
  "=============================="
);


process.stdin.setEncoding(
  "utf8"
);


process.stdout.write(
  "\n你:"
);


process.stdin.on(
  "data",

  async (data) => {

    const input =
      data.trim();


    if (
      !input
    ) {

      process.stdout.write(
        "\n你:"
      );

      return;
    }


    if (
      input === "exit" ||
      input === "quit"
    ) {

      console.log(
        "\nAgent 已退出。"
      );

      process.exit(0);
    }


    try {

      const result =
        await runAgent(
          input
        );


      console.log(
        `\nAgent:${result}`
      );

    } catch (error) {

      console.error(
        "\nAgent 执行失败:",
        error.message
      );
    }


    process.stdout.write(
      "\n你:"
    );
  }
);

二十一、启动 Agent

执行:

node agent.js

或者:

npm start

输出:

==============================
gpt-5.6-sol AI Agent
输入 exit / quit 退出
==============================

你:

现在输入:

你:帮我计算 99876 * 6677

Agent:

[Agent] 调用工具:calculator

[Agent] 参数:
{"expression":"99876 * 6677"}

[Tool] 返回:
666472052

最终:

Agent:

99876 × 6677 = 666472052。

二十二、测试多工具任务

再输入:

计算 123456 * 789,然后把结果保存下来。

可能运行:

[Agent] 调用工具:calculator

[Agent] 参数:
{"expression":"123456 * 789"}

[Tool] 返回:
97406784

接着:

[Agent] 调用工具:save_note

[Agent] 参数:
{"content":"123456 × 789 = 97406784"}

[Tool] 返回:
笔记保存成功

最终:

Agent:

计算完成:

123456 × 789 = 97406784

结果已经保存到笔记。

看到这里就应该能理解:

我们没有提前规定:

calculator
↓
save_note

而是 gpt-5.6-sol 根据:

用户目标
+
当前上下文
+
工具列表
+
工具执行结果

自己决定下一步。


二十三、我们已经给 Agent 加了 Memory

注意完整代码里:

const messages = [];

是在:

runAgent()

外面的。

也就是说:

messages

不会随着一次请求结束而销毁。

所以:

你:
我叫 JavaPub。

Agent:
好的。

你:
我叫什么?

Agent:
你叫 JavaPub。

模型能够根据前面的:

Conversation History

继续回答。

这就是最简单的:

Short-Term Memory

也就是:

短期记忆

二十四、为什么 messages 就是 Memory?

因为一次完整请求实际上是:

messages = [
  {
    role: "system",
    content: "..."
  },

  {
    role: "user",
    content: "我叫 JavaPub"
  },

  {
    role: "assistant",
    content: "好的"
  },

  {
    role: "user",
    content: "我叫什么?"
  }
];

gpt-5.6-sol 可以看到:

完整历史上下文

所以知道:

JavaPub

但是这种方式有一个问题。

如果用户连续聊:

100 次
1000 次
10000 次

messages 会越来越大。

因此真正的 Agent 系统一般还需要:

Context Window 管理

历史消息截断

Conversation Summary

Redis

MySQL

PostgreSQL

Vector Database

RAG

Long-Term Memory

也就是:

长期记忆

二十五、Tool 可以干什么?

今天只有:

calculator

get_current_time

save_note

看起来比较简单。

但是只要理解了 Tool Calling,你就可以无限扩展。

比如:

search_web

让 AI 搜索互联网。


比如:

read_file

读取文件。


比如:

write_file

创建或者修改文件。


比如:

run_shell

执行:

npm install

git status

git pull

docker ps

比如:

query_database

让 Agent 查询:

MySQL

PostgreSQL

MongoDB

Redis

比如:

github_create_issue

让 Agent 自动:

创建 Issue

比如:

send_email

发送邮件。


比如:

generate_image

调用图片模型。


比如:

generate_video

调用视频生成模型。


甚至:

deploy_application

让 Agent:

拉取 GitHub
↓
npm install
↓
build
↓
Docker 构建
↓
发布
↓
健康检查

这个时候,它就已经开始向:

Coding Agent

DevOps Agent

发展了。


二十六、Agent 到底是谁在控制程序?

这是理解 Agent 最重要的地方。

传统程序:

const result =
  calculator();

saveNote(result);

执行顺序由:

程序员

决定。

而 Agent:

User
 ↓
LLM
 ↓
选择 Tool
 ↓
执行
 ↓
观察结果
 ↓
LLM
 ↓
决定下一步

也就是说:

模型开始参与程序流程控制。

这是 Agent 和传统自动化一个很大的区别。


二十七、Agent 和 Workflow 的区别

例如:

const data =
  await search();

const article =
  await writeArticle(data);

const image =
  await generateImage(article);

await publish(
  article,
  image
);

这是:

Workflow

因为流程是固定的:

搜索
 ↓
写文章
 ↓
生成图片
 ↓
发布

但是 Agent 是:

用户:

帮我写一篇 DeepSeek 最新进展的文章并准备配图。

然后 AI 自己决定:

我要不要搜索?

搜索几个关键词?

资料够不够?

需不需要继续搜索?

什么时候开始写文章?

需不需要生成图片?

生成几张?

什么时候结束?

所以可以简单理解:

Workflow:

代码决定执行流程。

而:

Agent:

模型参与决定执行流程。

不过真正生产环境最常见的并不是:

Agent VS Workflow

而是:

Agent
+
Workflow

确定性的流程交给代码。

模糊决策交给模型。

这样通常更加稳定。


二十八、Agent Loop 到底解决了什么?

来看最核心的一段:

while (true) {

  const response =
    await llm();

  if (
    response.tool_calls
  ) {

    await executeTool();

    continue;
  }

  return response.content;
}

它实现的实际上是:

Think
 ↓
Act
 ↓
Observe
 ↓
Think
 ↓
Act
 ↓
Observe
 ↓
Answer

也就是很多 Agent 文章里经常提到的:

Thought

Action

Observation

只不过现代 Tool Calling 已经把很多过程标准化了。

你不一定需要让模型真的输出:

Thought:
我现在应该……

真正重要的是:

模型能够获得工具结果

然后:

继续决策

二十九、为什么很多 Agent Framework 看起来特别复杂?

当你去看:

LangChain

LangGraph

AutoGen

CrewAI

OpenAI Agents SDK

经常会遇到:

Agent

Node

Graph

State

Checkpoint

Tool

Memory

Chain

Workflow

Tracing

第一次学非常容易懵。

但是当你手写完今天这个 Agent 以后,再回头看它们就容易很多。

因为框架实际上主要是在帮助我们解决:

Agent Loop

Tool Registry

State 管理

Memory

异常重试

任务恢复

多 Agent

Human in the Loop

Tracing

日志

持久化

权限

并发

底层基本思想并没有变。

所以如果第一次学习 AI Agent,我建议:

先不要急着学框架,先自己手写一个最小 Agent。

几十到几百行 Node.js 已经足够。


三十、生产环境一定不能这样裸奔

教程为了方便理解,我们使用了:

Function(
  `"use strict"; return (${expression})`
)();

这只是演示。

不要直接把不可信用户输入拿去动态执行。

更不要直接给 Agent:

root SSH

rm -rf 权限

数据库 DROP 权限

支付权限

转账权限

因为 Agent 仍然可能:

理解错误

生成错误参数

选错工具

出现 Prompt Injection

被恶意输入诱导

真正上线至少需要:

Tool 白名单

参数校验

权限控制

超时

重试

并发控制

调用次数限制

审计日志

Sandbox

敏感操作确认

三十一、敏感操作加入 Human in the Loop

例如 Agent 想执行:

delete_database

不要马上执行。

可以变成:

Agent:

准备执行:

DROP DATABASE production

这是一个高风险操作。

是否确认?

用户:

确认

程序才真正执行。

这就是:

Human in the Loop

也就是:

人在回路

AI 负责:

分析和建议

但高风险操作:

必须由人确认。

这是生产级 Agent 非常重要的设计。


三十二、再进一步:把 Tool 做成统一注册表

现在我们是:

switch (name) {

  case "calculator":

  case "save_note":

}

工具少的时候没问题。

但是如果以后有:

50 个 Tool

就会很难维护。

我们可以改成:

const toolHandlers = {

  calculator:
    async ({
      expression
    }) => {

      return calculator(
        expression
      );
    },


  get_current_time:
    async () => {

      return getCurrentTime();
    },


  save_note:
    async ({
      content
    }) => {

      return saveNote(
        content
      );
    },

};

执行:

async function executeTool(
  name,
  args
) {

  const handler =
    toolHandlers[name];

  if (!handler) {

    return (
      `未知工具:${name}`
    );
  }

  return await handler(
    args
  );
}

这样以后新增:

search_web

read_file

write_file

send_email

只要注册进去即可。

这就开始有一点:

Tool Registry

的味道了。


三十三、给 Agent 增加最大循环次数

还有一个生产环境问题。

现在:

while (true)

理论上可以无限执行。

万一模型一直:

调用 Tool
↓
调用 Tool
↓
调用 Tool

就有可能导致:

无限循环

因此增加:

const MAX_STEPS =
  20;

改成:

for (
  let step = 0;
  step < MAX_STEPS;
  step++
) {

  // Agent Loop

}

最后:

throw new Error(
  "Agent 超过最大执行步骤"
);

这样最多:

20 Steps

避免失控。


三十四、更加完整的 Agent Loop

生产一点的写法:

async function runAgent(
  userInput
) {

  messages.push({

    role:
      "user",

    content:
      userInput,

  });


  const MAX_STEPS =
    20;


  for (
    let step = 1;
    step <= MAX_STEPS;
    step++
  ) {

    console.log(
      `\n========== Step ${step} ==========`
    );


    const response =
      await client
        .chat
        .completions
        .create({

          model:
            MODEL,

          messages,

          tools,

          tool_choice:
            "auto",

        });


    const message =
      response
        .choices[0]
        .message;


    messages.push(
      message
    );


    if (
      !message.tool_calls ||
      message.tool_calls.length === 0
    ) {

      return (
        message.content
      );
    }


    for (
      const toolCall
      of message.tool_calls
    ) {

      const name =
        toolCall
          .function
          .name;


      const args =
        JSON.parse(
          toolCall
            .function
            .arguments || "{}"
        );


      console.log(
        `[Agent] Tool:${name}`
      );


      console.log(
        `[Agent] Args:${JSON.stringify(args)}`
      );


      let result;


      try {

        result =
          await executeTool(
            name,
            args
          );

      } catch (error) {

        result =
          `Tool Error:${error.message}`;
      }


      console.log(
        `[Tool] Result:${result}`
      );


      messages.push({

        role:
          "tool",

        tool_call_id:
          toolCall.id,

        content:
          String(result),

      });
    }
  }


  throw new Error(
    "Agent 达到最大执行步骤,任务终止。"
  );
}

例如:

========== Step 1 ==========

[Agent] Tool:calculator

[Agent] Args:
{"expression":"12345 * 6789"}

[Tool] Result:
83810205


========== Step 2 ==========

[Agent] Tool:save_note

[Agent] Args:
{"content":"12345 × 6789 = 83810205"}

[Tool] Result:
笔记保存成功


========== Step 3 ==========

Agent:

计算完成并已保存笔记。

现在整个 Agent 的运行过程已经非常清楚了。


三十五、一个真正 Agent 的五大部分

到这里重新看:

Agent
=
LLM
+
Prompt
+
Tools
+
Memory
+
Loop

基本就完全理解了。

1. LLM

这里就是:

gpt-5.6-sol

负责:

理解目标

分析问题

选择工具

生成参数

判断下一步

组织最终答案

可以把它理解成:

Agent 的大脑

2. Prompt

我们写的:

SYSTEM_PROMPT

负责规定:

身份

目标

规则

工具使用方式

行为边界

类似:

Agent 操作系统里的系统规则

3. Tools

今天:

calculator

get_current_time

save_note

相当于:

Agent 的手和脚

没有工具:

Agent 只能说。

有工具:

Agent 可以做。

这是两者非常重要的区别。


4. Memory

今天:

messages

就是最简单的 Memory。

让 Agent 知道:

前面聊过什么

执行过什么 Tool

工具返回过什么

用户之前要求了什么

5. Agent Loop

今天:

while (true)

或者:

for (...)

负责:

让 AI 不断:

思考
↓
行动
↓
观察
↓
继续思考

直到:

任务完成

三十六、从 Mini Agent 到真正产品

今天我们写的 Agent:

大概几百行以内

但是继续发展之后,就可以变成:

AI Coding Agent

例如给它:

read_file

write_file

list_files

run_shell

git_diff

git_commit

它就可以:

读取项目
↓
分析 Bug
↓
修改代码
↓
运行测试
↓
发现错误
↓
继续修改
↓
再次测试

这已经很接近:

Coding Agent

的核心运行模式了。


还可以做:

运维 Agent

给它:

ssh_command

docker_ps

docker_logs

restart_container

read_nginx_config

用户:

为什么我的 API 返回 502?

Agent:

查看 Docker
↓
查看 Nginx
↓
查看 upstream
↓
读取日志
↓
定位异常
↓
建议修复

甚至在有权限的情况下:

执行修复

还可以做:

内容 Agent

提供:

search_web

write_article

generate_image

publish_wordpress

用户:

写一篇今天 AI 行业最重要的新闻并发布。

Agent:

搜索新闻
↓
筛选信息
↓
交叉验证
↓
写文章
↓
生成配图
↓
发布

三十七、MCP 其实也可以看成 Tool 的升级

现在 AI Agent 领域经常看到:

MCP

很多人第一次看觉得又是一个非常新的东西。

但是如果已经理解今天的:

Tools

就很好理解。

今天我们的 Tool 是:

const tools = [...]

全部硬编码在 Node.js 里面。

而 MCP 想解决的问题之一就是:

让 Tool 的提供方式更加标准化。

以后 Agent 可以连接:

GitHub MCP

Database MCP

Browser MCP

Filesystem MCP

而不是每一个 Agent 都重新写一遍 Tool 接口。

所以理解:

Tool Calling

之后,再学习 MCP 会容易很多。


三十八、最后再看一次完整工作流程

假设用户说:

帮我计算 98765 × 4321,
把结果保存起来,
然后告诉我现在几点。

整个 Agent 可能执行:

User
 │
 ▼
gpt-5.6-sol
 │
 │ Tool Call
 ▼
calculator
 │
 ▼
426741565
 │
 ▼
gpt-5.6-sol
 │
 │ Tool Call
 ▼
save_note
 │
 ▼
保存成功
 │
 ▼
gpt-5.6-sol
 │
 │ Tool Call
 ▼
get_current_time
 │
 ▼
2026/8/14 12:38:21
 │
 ▼
gpt-5.6-sol
 │
 ▼
Final Answer

整个过程中:

Node.js

负责:

运行工具
维护上下文
控制 Agent Loop

而:

gpt-5.6-sol

负责:

理解目标
选择 Tool
生成 Tool 参数
判断下一步
最终回答

这就是一个最基础,但是完整的:

AI Agent Runtime

三十九、真正理解 AI Agent

如果只记住今天一件事情,我建议记住:

while (true) {

  const response =
    await llm(messages);

  if (
    response.tool_calls
  ) {

    const result =
      await executeTool();

    messages.push(
      result
    );

    continue;
  }

  return (
    response.content
  );
}

不要小看这几十行逻辑。

它实际上就是:

模型
↓
行动
↓
环境
↓
反馈
↓
模型
↓
行动

这也是今天很多 AI Agent 的核心思想。

框架可能不断换:

LangChain

LangGraph

AutoGen

CrewAI

Agents SDK

模型也可能不断更新。

但是:

Model

Prompt

Tool

Memory

Loop

这几个东西短时间内不会消失。


四十、总结

今天我们完全没有使用任何复杂 Agent Framework。

只使用:

Node.js

OpenAI SDK

gpt-5.6-sol

OpenAI Compatible API

Tool Calling

就实现了一个真正可以运行的:

AI Agent

API:

https://api.chongplus.plus

模型:

gpt-5.6-sol

安装:

npm install openai

整个 Agent 最核心的公式:

AI Agent
=
LLM
+
Prompt
+
Tools
+
Memory
+
Agent Loop

普通 AI 是:

你问它一个问题。

它给你一个回答。

而 Agent 更重要的变化是:

你给它一个目标。

它自己判断:

下一步做什么?
需要什么工具?
工具结果是什么?
接下来还需要干什么?
什么时候任务才算真正完成?

这就是为什么我一直觉得:

学习 Agent 最好的方式,不是第一天就安装 LangChain,而是亲手写一次 Agent Loop。

当你第一次看到终端里出现:

[Agent] 调用工具:calculator

紧接着:

[Tool] 返回:83810205

然后模型不是停止,而是继续:

[Agent] 调用工具:save_note

你就会真正理解:

AI Agent 到底是什么。

而当我们继续给它增加:

浏览器

搜索

文件系统

Shell

数据库

GitHub

图片生成

视频生成

邮件

服务器

这个几十到几百行代码的 Mini Agent,就可以逐渐成长为真正的:

Coding Agent

Browser Agent

DevOps Agent

Research Agent

Content Agent

Enterprise Agent

真正困难的,也会从:

怎么让 AI 调一次 Tool?

慢慢变成:

怎么让 Agent
长期、
稳定、
安全、
低成本地
完成复杂任务?

到了这里,才是真正的 Agent 工程。

一起成为勇猛精进的人类。

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐