
从零构建 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 是一个命令行程序,不停地问你要输入:
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。
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 时,除了消息,还要带上工具定义:
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。现在模型多了一个选项:除了返回普通文本,它可以返回一个工具调用请求。
流程是这样的:
用户提问
↓
模型判断是否需要工具
↓
应用执行请求的工具
↓
工具返回结果
↓
应用把结果发回给模型
↓
模型生成最终回答
模型决定下一步发生什么,运行时执行动作,结果喂回给模型,模型再决定——这就是 Agent 的本质:一个循环,而不是单次模型请求。
Agent 循环的完整形状
把整个系统简化,Agent 循环长这样:
while 对话进行中:
读取用户输入
把消息和工具定义发给模型
检查模型响应
如果模型请求了工具:
执行工具
把工具结果加入消息列表
再次调用模型
否则:
向用户展示最终回复
LLM 本身大多是"文本进、文本出";Agent 是 LLM 外面的那个循环,让系统能决定何时用工具、执行动作、观察结果、继续推进。理解了这个循环,再去看各种 Agent 框架就会问出更好的问题:模型调用在哪?工具定义在哪?谁来执行工具?结果怎么传回去?循环怎么终止?
下一步可以做什么
这个例子刻意做得最小,不是完整的助手,更不是生产级框架——目的是让活动部件可见。骨架清晰之后,就能往上加真实能力了:比如把查天气换成 Google Calendar 工具,Agent 就能查事件、找空闲时间、建日程条目。
这也是作者这个系列的方向:从最小的循环出发,逐步加工具和设计决策,让 Agent 系统一步步长出来,而不是一开始就是个黑盒。系列下一篇会讲如何让工具的添加和管理更简单——从那里开始,系统就不再像 demo,而更像一个可以扩展的小框架。
想自己动手的话,装好 Node.js 和 Ollama,把上面的代码拼起来,跑通"读-想-做"循环,再换一个你真正用得上的工具(文件读取、搜索、笔记都行)——一个属于你自己的 Agent 骨架就搭起来了。
© 2026 四月
原文链接:https://www.aprilzz.com/tutorials/minimal-ai-agent-loop
相关文章
MCP Server 从零搭建:用 TypeScript 为 AI Agent 构建自定义工具生态
手把手教你搭建 MCP Server——从项目初始化、工具注册到部署运行,让 AI Agent 通过 MCP 协议调用任意外部工具
从零构建 AI Agent:工具篇
手把手教你为 AI Agent 添加工具调用能力——从 bash 执行到文件操作再到网页抓取,完整的 Python 代码示例
在 Mac 上搭建本地编程 Agent:llama.cpp + Gemma 4 + MTP 投机解码完整指南
断网也能用的编程 Agent:用 llama.cpp 在 Mac 上跑 Gemma 4 26B,配合 MTP 投机解码把生成速度从 58 提到 72 token/s,再接上支持图片输入的 Pi 终端 Agent。