AI代理的最小可用循环
我喜欢学习新事物,我最喜欢的学习方式是从具体和小的地方开始。我不想从大型框架或复杂的架构图开始。我想亲眼看到某个东西在工作,即使第一个版本非常有限。
这就是为什么我要在这个通讯中开始一个名为从零开始构建AI代理的新系列。
在接下来的几期中,我将逐步介绍如何从零开始构建一个小的代理系统。我们将从一个非常简单的REPL开始,然后逐步添加工具,引入简单的插件模式,用嵌入和RAG构建记忆系统,以及后来探索路由和规划。
最终的结果将是一个小型个人知识助手。你可能会发现它直接对你的工作流有用,但更大的目标是理解代理系统背后的架构。一旦活动部件清晰了,你可以将同样的想法应用到自己的项目中。
当前的AI趋势中,有很多资源、教程和演示。但我仍然很难找到实用的指南,一步步展示底层到底发生了什么。我希望这个系列能帮助填补这个空白。
在本期中,我们将从骨架开始:AI代理的最小有用形态。
我还为本期录制了一个YouTube版本,在其中我逐步构建了第一个版本。如果你更喜欢观看代码的构建过程,可以在这里查看视频:YouTube视频
如果你一直在听人们谈论AI代理,你可能想知道底层到底发生了什么。这个术语可能听起来很模糊,特别是当人们用它来描述从编码助手到工作流自动化工具的一切时。但在核心,这个想法出人意料地简单。
**代理是一个带循环的程序。**它读取输入,决定下一步做什么,采取行动,观察结果,然后重复。
那个循环就是基础。一旦你能清楚地看到它,许多代理系统就会变得更容易理解。
这里的目标不是构建一个生产就绪的框架。目标是让活动部件可见。我们将从一个微小的程序开始,将它连接到本地语言模型,然后给它一个真正的工具来获取实时天气数据。
最后,我们将拥有代理的基本骨架。
1、为什么从小处开始?
当人们谈论AI代理时,例子往往很快变得复杂。可能涉及多个工具、记忆、规划、文件访问、浏览器自动化、后台任务或代码执行。
这些是有用的功能,但它们也可能隐藏下面简单的想法。
所以,不要从一个完整的助手开始,让我们从最小的可能版本开始:一个持续询问输入的命令行程序。
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、用模型调用替换回显
下一步是用语言模型调用替换回显行为。程序不再将用户输入直接发送回终端,而是将其发送给模型。
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
汇智网翻译整理,转载请标明出处