一个Agent最小需要什么?

0 阅读7分钟

不可逆的 Agent · 第 1 篇

本系列回答一个问题:当 Agent 的动作有真实世界后果时,该怎么设计。

我一直以为,Agent 是个复杂的东西。

打开任何一个 Agent 框架的文档,你会看到一整面墙的概念:规划器、执行器、记忆模块、工具注册表、回调链、状态图。看起来,要搭一个 Agent,得先学会这一整套词汇。

直到我把市面上能找到的 LLM 应用挨个拆开看了一遍——聊天机器人、代码助手、自动化工作流——我发现一件事:把所有花哨的东西剥掉之后,剩下的核心小得惊人。而且所有 Agent 剩下的都是同一个东西

这篇文章就是把这个剥离过程重新走一遍。我们从最简单的形态开始,每次只加一个零件,看看到哪一步,它突然就"活"了。

· · ·

第零步:一个裸的 LLM

从最简单的开始:一个 LLM,你问它答。

"帮我把客厅灯调暗一点。"

它会回答得头头是道——"好的,您可以打开智能家居 App,找到客厅灯,将亮度滑块调至 30%……"

然后什么都没有发生。灯还是那么亮。

这不是 Agent,这是一个会说话的说明书。它缺的不是智力——它的"推理"其实已经够用了——它缺的是和世界的连接。它不知道你家的灯现在什么状态,也没有任何办法去碰那盏灯。

AI 领域管这个问题叫 grounding(接地):模型内部的知识是从训练数据里统计出来的,是滞后的、封闭的,它和眼前这个真实、实时的世界之间,隔着一层玻璃。

要打破玻璃,需要下一个零件。

· · ·

第一步:给它一双眼睛——工具(读)

第一个零件:允许 LLM 调用一个函数,get_light_status("客厅")

现在它能"看"了。你说"把客厅灯调暗一点",它先查一下:当前亮度 80%。于是它的回答从泛泛的说明书,变成了有根据的判断——"当前 80%,调到 40% 比较合适。"

这就是感知。注意,感知不是 LLM 自己长出来的能力,而是外挂的:LLM 负责决定"我需要看什么",工具负责真的去看。

但它还是只能说,不能做。

· · ·

第二步:给它一双手——工具(写)

第二个零件:再给一个函数,set_light_brightness("客厅", 40)

现在,同样一句"把客厅灯调暗一点",它查状态、算目标值、调用函数——灯,真的暗下来了。

这就是行动

停一下。注意刚才发生了一件安静但重大的事:从这一步起,这个系统的输出不再只是文字了。它开始改变文字之外的世界。

读工具和写工具,代码上看起来几乎一样,都是一次函数调用。但它们在性质上完全不同:**读错了,大不了再读一次;写错了,世界已经变了。**现在这个差别看起来无所谓——灯调错了再调回来就是。请记住这个伏笔,这个"读与写的不对称",是整个系列后半部分的主角。

· · ·

第三步:让它能连续做事——循环

现在换一个稍微复杂的任务:"如果客厅太亮就调暗,顺便看看卧室。"

单次调用不够了。它需要:查客厅 → 判断 → 调暗 → 查卧室 → 判断 → (可能)再调。这是好几步,每一步做什么取决于上一步看到了什么。

第三个零件:一个循环。

while 任务未完成:
    LLM 看着目前的情况,决定下一步动作
    执行动作(读或写)
    把结果告诉 LLM

这个循环就是 ReAct 模式的骨架(Yao et al., 2022)——Reasoning + Acting,想一步、做一步、看结果、再想下一步。它解决的根本问题是:真实任务是不确定的,你无法在动手之前就规划好一切。必须边做边看。

到这里,感知、推理、行动,三件套齐了,还被一个循环串成了闭环。是不是就完成了?

我最初也以为是。但真跑起来会发现一个诡异的 bug。

· · ·

第四步:那个容易被忽略的零件——状态

跑上面那个任务,你可能会看到 Agent 查完客厅、调完亮度之后,突然又去查一遍客厅——它忘了自己刚做过什么。

因为 LLM 是无状态的。每一次调用,它都是一张白纸,只知道你这一次喂给它的内容。循环的第一圈和第二圈之间,如果没有人把"上一圈发生了什么"带过去,它就是失忆的。

所以第四个零件,朴素得几乎不像个零件:把每一轮的动作和结果,追加到对话历史里,下一轮全部带上。

history = []
while not done:
    action = llm.think(goal, tools, history)   # 带着全部历史思考
    result = execute(action)
    history.append((action, result))            # 累积状态

这个不断变长的 history,就是 Agent 的最小记忆。它简陋——就是一个列表——但它完成了一次质变:有了它,Agent 才从"一次次独立的反应"变成"一个连续的行动过程"。它能看到自己的过去了。

顺带埋下第二个伏笔:当任务变长、这个列表膨胀到塞不进上下文窗口时怎么办?那就是记忆模块真正登场的时候——但不是今天。

· · ·

还差一个问题:它怎么知道该停?

循环有了,但循环什么时候结束?

这个问题比看起来难。目前的答案通常是三层兜底:LLM 自己宣布"任务完成"(最常用,但它有时会在没做完时就宣布做完);最大迭代次数(防止无限循环烧钱);外部条件校验(最可靠,但不是所有任务都有可校验的条件)。

注意这里面藏着一个裂缝:"LLM 以为做完了"和"任务真的做完了"之间,隔着一段距离。在软件世界里,这段距离通常无伤大雅。但如果 Agent 控制的是真实设备呢?"我以为灯关了"和"灯真的关了"——这个裂缝会变成什么?

先把问题放在这,它是后面某一篇的全部内容。

· · ·

所以,一个 Agent 最小需要什么

把剥离的结果摆在一起:

一个 LLM(推理)+ 一组读写工具(感知与行动)
+ 一个循环(持续)+ 一份累积的历史(状态)

四个零件,几十行代码,没有任何黑魔法。写这篇之前我一直没能用一句话说清"Agent 是什么",写到这里它自己浮出来了:

Agent 不是一个更聪明的模型,
而是一个让模型的判断能够作用于世界、
并承受世界反馈的结构。

聪明在 LLM 里,但"活"在结构里。

· · ·

我们故意没做的事

这个最小 Agent 能工作,但它的每个零件都藏着一个会在规模变大时爆炸的假设:

  • 它走一步看一步——复杂任务需要规划(第 2 篇)
  • 它的记忆是一个塞进上下文的列表——长任务需要真正的记忆系统(第 3 篇)
  • 它的工具想调就调——真实系统需要权限与接口设计(第 4 篇)
  • 它是一个人在战斗——大任务需要多 Agent 协作(第 5 篇)

而贯穿这一切的,是今天埋下的那两个伏笔:读与写的不对称、"以为做完"与"真的做完"的裂缝。在软件世界里,它们只是瑕疵;当 Agent 的手伸进物理世界——灯光、阀门、电机——它们会变成设计的全部核心。

因为物理世界,没有 Ctrl+Z。

· · ·

留一个问题给你:你现在用的、或者正在构建的 AI 应用里,哪些动作是可逆的,哪些不是?你分得清吗?

本文是《不可逆的 Agent》系列第 1 篇。系列回答一个问题:当 Agent 的动作有真实世界后果时,该怎么设计。

下一篇《Agent 怎么"想"——从 ReAct 到 plan-and-execute》首发公众号「我是Bob大叔」,关注不错过。