> ## Content Index
> Fetch the complete content index at: https://www.hubwiz.com/blog/llms.txt
> Use this file to discover other available public pages before exploring further.

# AI代理的最小可用循环
- URL: https://www.hubwiz.com/blog/the-smallest-useful-loop-of-ai-agents/
- Published: 2026-05-19T00:52:52.000Z
- Updated: 2026-10-13T15:55:52.000Z
- Description: 在本系列中，我们将从最简单的骨架开始，逐步构建一个小型代理系统
- Author: admin
- Tags: AGENT, #Import 2026-10-14 14:41

我喜欢学习新事物，我最喜欢的学习方式是从具体和小的地方开始。我不想从大型框架或复杂的架构图开始。我想亲眼看到某个东西在工作，即使第一个版本非常有限。

这就是为什么我要在这个通讯中开始一个名为*从零开始构建AI代理*的新系列。

在接下来的几期中，我将逐步介绍如何从零开始构建一个小的代理系统。我们将从一个非常简单的REPL开始，然后逐步添加工具，引入简单的插件模式，用嵌入和RAG构建记忆系统，以及后来探索路由和规划。

最终的结果将是一个小型个人知识助手。你可能会发现它直接对你的工作流有用，但更大的目标是理解代理系统背后的架构。一旦活动部件清晰了，你可以将同样的想法应用到自己的项目中。

![使用TensorBoard Projector的嵌入可视化](https://substackcdn.com/image/fetch/$s_!uSIa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3751447-cbb3-4490-937d-73ec290b1bef_3568x2136.png)

当前的AI趋势中，有很多资源、教程和演示。但我仍然很难找到实用的指南，一步步展示底层到底发生了什么。我希望这个系列能帮助填补这个空白。

在本期中，我们将从骨架开始：AI代理的最小有用形态。

我还为本期录制了一个YouTube版本，在其中我逐步构建了第一个版本。如果你更喜欢观看代码的构建过程，可以在这里查看视频：[YouTube视频](https://www.youtube.com/watch?v=NzwsCB8Wmeg&ref=hubwiz.com)

如果你一直在听人们谈论AI代理，你可能想知道底层到底发生了什么。这个术语可能听起来很模糊，特别是当人们用它来描述从编码助手到工作流自动化工具的一切时。但在核心，这个想法出人意料地简单。

\*\*代理是一个带循环的程序。\*\*它读取输入，决定下一步做什么，采取行动，观察结果，然后重复。

那个循环就是基础。一旦你能清楚地看到它，许多代理系统就会变得更容易理解。

这里的目标不是构建一个生产就绪的框架。目标是让活动部件可见。我们将从一个微小的程序开始，将它连接到本地语言模型，然后给它一个真正的工具来获取实时天气数据。

最后，我们将拥有代理的基本骨架。

## 1、为什么从小处开始？

当人们谈论AI代理时，例子往往很快变得复杂。可能涉及多个工具、记忆、规划、文件访问、浏览器自动化、后台任务或代码执行。

这些是有用的功能，但它们也可能隐藏下面简单的想法。

所以，不要从一个完整的助手开始，让我们从最小的可能版本开始：一个持续询问输入的命令行程序。

```javascript
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

```

显然，这还没有用。但结构很重要。程序已经在做我们需要的基本事情了：

```
读取输入
决定做什么
采取行动
重复

```

这就是我们将不断改进的骨架。"决定"步骤目前很简单，"行动"步骤也很简单。但系统的形状已经存在了。

## 2、用模型调用替换回显

下一步是用语言模型调用替换回显行为。程序不再将用户输入直接发送回终端，而是将其发送给模型。

```javascript
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`);
  }
}

```

在这个例子中，我使用Ollama。如果你之前没用过Ollama，可以把它想象成一个本地模型服务器。它在你的机器上运行一个模型，给你的应用程序一个可以与之对话的API。

这意味着我的Node.js程序不需要包含模型本身。它只需要向Ollama发送请求。Ollama在本地运行模型并将响应发送回来。

循环本身没有改变。只有行动改变了。

之前，程序将输入回显到终端：

```
用户输入 -> 回显

```

现在，程序将输入发送给模型并打印模型响应：

```
用户输入 -> 发送给模型 -> 打印模型响应

```

此时，程序变成了一个简单的本地聊天应用。它可以接收输入，将其发送到本地模型，并打印响应。

为了让对话正常工作，我们还需要保留一个消息列表。这个消息列表就是对话的转录。每条消息通常有一个角色和一些内容：

```
system     -> 指令或规则
user       -> 人类说的
assistant  -> 模型回复的

```

转录很重要，因为模型需要上下文。如果我们只发送最新的用户输入，模型不会知道对话中之前发生了什么。通过保留消息历史，我们给模型足够的上下文来自然地继续。

到目前为止，我们已经构建了有用的东西，但它仍然有一个重要的限制。模型只能根据它已经知道的东西或我们在提示中提供的信息来响应。

如果我们问：

```
墨尔本现在的天气怎么样？

```

模型无法可靠地自己回答。它可能会猜测，给出一个通用的答案，或者说它无法访问实时信息。

这就是工具变得重要的地方。

## 3、为什么工具重要

语言模型不会自动获取实时数据。它不会自动调用API、读取你的日历、检查天气或更新数据库。

它能做的是决定需要使用一个工具。然后你的应用程序执行那个工具。

这个区别很重要。模型不直接运行工具。它产生一个结构化请求，实际上是说：

```
我需要用这些参数调用这个工具。

```

然后应用程序决定如何处理那个请求。

例如，如果用户询问当前天气，模型可能会请求一个带有位置的 `get_weather` 工具：

```
tool: get_weather
arguments: { location: "Melbourne" }

```

然后应用程序运行实际的工具，获取天气数据，并将结果发送回模型。只有在那之后，模型才会为用户产生最终答案。

这就是一个简单的LLM聊天程序开始变成代理的地方。不是因为模型突然变得神奇，而是因为程序现在有了一个围绕模型的循环，可以在外部世界采取行动。

## 4、添加第一个工具

让我们添加一个真正的工具：`get_weather`。

在我的例子中，我已经有一个小型的本地命令行天气工具。它接收一个位置并打印天气信息。从代理的角度来看，这个命令行工具变成了一个名为`get_weather`的能力。

但模型需要知道这个工具存在。所以当程序向Ollama发送请求时，它会在消息旁边包含一个工具定义。

概念上，程序在说：

```
你可以正常回答。

但如果你需要实时天气信息，
你可以调用get_weather并传入一个位置。

```

现在模型有了另一个选择。它不仅可以返回普通文本，还可以返回一个工具调用。

例如，用户可能会问：

```
墨尔本现在的天气怎么样？

```

模型可能会返回一个结构化请求：

```
调用get_weather，位置 = "Melbourne"

```

同样，模型不是在运行天气命令。Node.js程序在运行。在这个例子中，这个Node.js程序就是我们正在构建的代理运行时。

运行时接收工具调用，运行天气CLI，捕获结果，并将该结果作为工具消息添加回对话中。然后程序再次调用模型。

这一次，模型在转录中有天气数据，所以可以为用户生成一个有根据的答案。

流程看起来像这样：

```
用户提出问题
    ↓
模型决定是否需要工具
    ↓
应用程序执行请求的工具
    ↓
工具返回结果
    ↓
应用程序将结果发回模型
    ↓
模型产生最终答案

```

这就是为什么代理通常不只是一个单一的模型请求。它是一个循环。

模型决定下一步应该发生。运行时执行行动。结果被反馈给模型。然后模型再次决定。

## 5、代理循环

如果我们简化整个系统，代理循环看起来像这样：

```
当对话处于活跃状态时：
  读取用户输入
  将消息和工具发送给模型
  检查模型响应

  如果模型请求工具：
    执行该工具
    将工具结果添加到消息中
    再次调用模型

  否则：
    向用户显示最终响应

```

这就是核心思想。

LLM本身主要是文本输入和文本输出。代理是围绕LLM的循环。那个循环允许系统决定何时使用工具、执行行动、观察结果并继续。

一旦你理解了这个循环，许多代理系统就会变得不那么神秘。它们可能有更多工具。它们可能有记忆。它们可能规划多个步骤。它们可能与文件、浏览器、API或代码库交互。

但基本形状仍然相同：

```
决定
行动
观察
重复

```

## 6、要点

主要的要点是：LLM本身主要是文本输入和文本输出，而代理是围绕LLM的循环，可以决定何时使用工具并采取行动。

这个例子故意很小。它不是一个完整的助手或生产就绪的框架。它的目的是让活动部件可见。

一旦骨架清晰了，我们可以开始添加更实际的功能。

例如，不仅仅是获取天气数据，我们可以添加一个Google日历工具。然后代理不仅能回答问题，还能检查事件、找到空闲时间或创建日历条目。

这就是这个系列的方向。

我们将从这个小循环开始，然后逐步添加更多工具和设计决策，使代理系统逐步增长而不是变成一个黑盒。

如果你正在构建自己的代理系统，我认为这是最好的起点：不是从框架开始，而是从循环开始。

一旦你理解了循环，框架就变得更容易评估。你可以问出更好的问题：

```
模型调用在哪里？
工具在哪里定义的？
谁执行工具？
结果如何传回？
循环如何停止？

```

这些问题帮助你更清楚地看到系统。而这种清晰度比任何看起来很神奇的演示更有用。

在下一期中，我们将把这个骨架再推进一步，看看如何让工具更容易添加和管理。这是系统开始感觉不再像演示，而更像一个你可以扩展的小框架的地方。

如果你觉得这个主题对你有用，请留个评论让我知道你想看到什么样的代理工具被添加。天气是一个简单的起点，但日历、笔记、搜索和个人知识工具都打开了有趣的设计问题。

---

原文链接：[Building an AI Agent from Scratch: The Smallest Useful Loop](https://juntao.substack.com/p/building-an-ai-agent-from-scratch)

汇智网翻译整理，转载请标明出处

> **这是「Agent / MCP」专题的一部分。**完整地图见：[Agent / MCP / 自动化完全指南](https://www.hubwiz.com/blog/agent-stack/)