教程·阅读约 2 分钟·
从零构建 AI Agent:先写一个最小的"读-想-做"循环(Node.js 实战)

从零构建 AI Agent:先写一个最小的"读-想-做"循环(Node.js 实战)

不依赖任何框架,用 Node.js + Ollama 从零搭出 AI Agent 的最小骨架:一个 REPL 循环、一次模型调用、一个工具。理解了这个循环,所有 Agent 框架都不再神秘。

原文来源:The Pragmatic Developer(Juntao Qiu) — 用最少的代码搭出 AI Agent 的最小可用循环:REPL 骨架 → 接入本地模型 → 加第一个工具,让 Agent 的每个部件都看得见摸得着。

"AI Agent"这个词现在被用得很泛,从编码助手到工作流自动化都被叫 Agent。但剥开所有包装,核心概念其实简单得惊人:Agent 就是一个带循环的程序——读取输入、决定下一步、执行动作、观察结果、然后重复

问题在于,大多数教程一上来就铺开框架和架构图,把简单的东西藏在复杂度后面。这篇文章换个思路:从最小的形状开始,用 Node.js 搭一个骨架,接上本地模型(Ollama),再加一个真实工具(查天气),让 Agent 的每个部件都可见。看完你会发现,很多 Agent 系统变得不再神秘——它们可能工具更多、有记忆、会规划,但基本形状都一样。

第一步:先写一个循环

最小的 Agent 是一个命令行程序,不停地问你要输入:

code
async function main() {
  const rl = createInterface({ input, output });
 
  while (true) {
    // 1) 读取
    let line = await rl.question("You> ");
 
    // 2) 判断(命令 vs 普通输入)
    if (shouldExit(line)) break;
 
    // 3) 行动——把输入原样打回去
    output.write(`Assistant> ${line}\n\n`);
  }
 
  rl.close();
}

你输入一行,程序读进去,做点什么,然后等下一行。这已经是一个循环了。这个阶段没有任何智能——你输入 hello,它回 hello。但结构已经有了:读输入 → 决定做什么 → 采取行动 → 重复。骨架就位,后面每一步都只是替换"决定"和"行动"这两个环节。

—— 广告 ——

第二步:把"回声"换成模型调用

下一步,把原样回声换成语言模型调用。用 Ollama 当本地模型服务器:它在你机器上跑模型,给你的应用提供一个 API。你的 Node.js 程序不需要包含模型本身,只需发请求给 Ollama。

code
async function main() {
  const rl = createInterface({ input, output });
 
  /** @type {{ role: string, content: string }[]} */
  const messages = [];
 
  while (true) {
    let line = await rl.question("You> ");
 
    messages.push({ role: "user", content: trimmed });
 
    const data = await ollamaChat(messages);
    const assistant = data?.message;
    const reply =
      assistant?.content?.trim?.() ??
      "(no text from model — check model / Ollama logs)";
 
    // 把回复存进对话记录,下一轮模型才有上下文
    messages.push({ role: "assistant", content: reply });
 
    output.write(`\nAssistant> ${reply}\n\n`);
  }
}

循环本身没变,变的只是"行动":之前是 用户输入 → 回声,现在是 用户输入 → 发给模型 → 打印回复。这时程序已经是一个简单的本地聊天应用了。

注意这里必须维护一个消息列表(对话记录)。每条消息带 role 和 content:system 是规则、user 是人的输入、assistant 是模型的回复。只发最新一条用户输入的话,模型不知道之前聊过什么——记录历史就是为了给模型上下文。

第三步:工具为什么重要

模型本身不会自动获取实时数据。它不会调 API、不会读日历、不会查天气。它能做的是决定需要工具,然后由你的应用执行工具。这个区别很关键:模型不直接运行工具,它产出一个结构化请求,大意是"我要用这个参数调用这个工具"。

比如用户问"墨尔本现在天气怎么样",模型可能会请求 get_weather 工具、参数 location="Melbourne"。然后由应用真正运行工具、拿到天气数据、把结果发回给模型。模型拿到结果后,才生成最终回答。

这就是简单的 LLM 聊天程序开始变成 Agent 的那个点:不是因为模型突然有了魔法,而是程序在模型外面套了一个能对外部世界采取行动的循环

第四步:加上第一个工具

加一个真实的 get_weather 工具。实现上可以是一个本地命令行工具:传入地点,打印天气信息。从 Agent 的角度看,它成了一个叫 get_weather 的能力。

但模型得知道这个工具存在。所以发请求给 Ollama 时,除了消息,还要带上工具定义:

code
const tools = [
  {
    type: "function",
    function: {
      name: "get_weather",
      description:
        "Fetch current weather for a place using OpenWeather. Prefer 'City,CC' (ISO country) when ambiguous.",
      parameters: {
        type: "object",
        required: ["location"],
        properties: {
          location: {
            type: "string",
            description:
              'Query for the city, e.g. "Tokyo,JP", "New York,US", or "Melbourne,AU".',
          },
        },
      },
    },
  },
];

程序相当于告诉模型:你可以正常回答;但如果需要实时天气,允许你带一个 location 参数调用 get_weather。现在模型多了一个选项:除了返回普通文本,它可以返回一个工具调用请求。

流程是这样的:

code
用户提问
    ↓
模型判断是否需要工具
    ↓
应用执行请求的工具
    ↓
工具返回结果
    ↓
应用把结果发回给模型
    ↓
模型生成最终回答

模型决定下一步发生什么,运行时执行动作,结果喂回给模型,模型再决定——这就是 Agent 的本质:一个循环,而不是单次模型请求。

Agent 循环的完整形状

把整个系统简化,Agent 循环长这样:

code
while 对话进行中:
  读取用户输入
  把消息和工具定义发给模型
  检查模型响应

  如果模型请求了工具:
    执行工具
    把工具结果加入消息列表
    再次调用模型

  否则:
    向用户展示最终回复

LLM 本身大多是"文本进、文本出";Agent 是 LLM 外面的那个循环,让系统能决定何时用工具、执行动作、观察结果、继续推进。理解了这个循环,再去看各种 Agent 框架就会问出更好的问题:模型调用在哪?工具定义在哪?谁来执行工具?结果怎么传回去?循环怎么终止?

下一步可以做什么

这个例子刻意做得最小,不是完整的助手,更不是生产级框架——目的是让活动部件可见。骨架清晰之后,就能往上加真实能力了:比如把查天气换成 Google Calendar 工具,Agent 就能查事件、找空闲时间、建日程条目。

这也是作者这个系列的方向:从最小的循环出发,逐步加工具和设计决策,让 Agent 系统一步步长出来,而不是一开始就是个黑盒。系列下一篇会讲如何让工具的添加和管理更简单——从那里开始,系统就不再像 demo,而更像一个可以扩展的小框架。

想自己动手的话,装好 Node.js 和 Ollama,把上面的代码拼起来,跑通"读-想-做"循环,再换一个你真正用得上的工具(文件读取、搜索、笔记都行)——一个属于你自己的 Agent 骨架就搭起来了。

分享到
微博Twitter

© 2026 四月

原文链接:https://www.aprilzz.com/tutorials/minimal-ai-agent-loop