Skip to content
Charles Shao
Go back

AI Agent 是什么:拆解模型、工具、记忆与调度循环

创建于:
v1.0.0
–views

本系列不依赖框架,从一个几十行的循环开始,逐步加上 schema 校验、记忆、计划、评测、护栏和续跑。为了让各篇讨论同一件事,全系列使用同一个业务目标:

计划「双11-美妆拉新」11 月 1 日起追投两天。先查 10 月 31 日的 ROI,达到 1.5 用 aggressive 档,否则用 conservative 档。

不做广告的读者需要先了解三个概念:

概念含义
ROI广告带来的成交额除以广告花费。ROI 为 1.5,表示每花 1 元带回 1.5 元成交
追投在原有预算之外,给某个广告组追加一段时间的预算。提交后立即从账户余额预付扣款
学习期广告组上线初期,投放系统仍在探索该投给哪些人。此时大幅追加预算会干扰学习,所以不开放激进档位

一个计划下有多个广告组。本例中只有一个广告组已经过了学习期,因此只有它能使用 aggressive 档。

本文先给出四个组件的定义,再用这个目标走一遍完整流程,最后讨论几个常见误解。

Table of contents

Open Table of contents

一、Agent 与普通聊天的区别

对同一个问题「双11-美妆拉新要不要追投」,两种系统的行为不同:

两者用的可以是同一个模型。区别来自外层:系统是否向模型提供了可调用的工具,以及是否有一段循环负责执行工具、把结果交还给模型、再决定是否继续。

需要说明的是,「能改写外部状态」不是 Agent 的必要条件。只查报表、不做任何写操作的系统,只要下一步由模型根据结果决定,同样是 Agent。写操作带来的是风险等级的变化,后文会单独讨论。

二、四个组件与基本术语

组件定义
模型大语言模型。每一轮根据当前上下文决定下一步:调用哪个工具、传什么参数,或者直接给出回答
工具由宿主程序执行的函数,可以是本地函数,也可以是远程服务。通过 schema 声明给模型,未声明的工具模型看不到
记忆在循环之外单独维护的状态,保存目标和已经确认的结论(如 ROI 是否达标、可用的广告组 ID)
循环驱动整个过程的代码:请求模型,执行模型要求的工具,把结果写回上下文,再次请求模型,直到满足退出条件

需要强调的是,模型本身不执行任何工具。它只在响应里声明「我要调用某个工具,参数是什么」,执行、校验和退出判断都由循环代码负责。

记忆单独列为一个组件,是因为 messages 虽然记录了全部历史,但它会随轮数增长,迟早需要裁剪。裁剪之后,早期轮次里的关键结论也会随之消失。把这些结论另存一份,在每轮请求前写入 system 消息,模型就不依赖完整历史。这是本系列采用的做法,后续「上下文与记忆」一篇会展开。

后续各篇会反复用到以下术语:

术语含义
messages每轮发送给模型的消息列表,按时间顺序记录 system、user、assistant、tool 四类消息
回灌把工具的返回值或错误信息作为一条 role=tool 的消息追加到 messages,供模型下一轮读取
终答模型不再请求工具时返回的文本内容
终止由代码强制结束循环,常见触发条件是达到轮数上限或累计 token 超出预算
出口循环结束的条件,包括正常给出终答和被代码终止两类
run一个目标从进入循环到最终结束的完整过程。中途因等待用户确认而暂停、确认后继续,仍属于同一个 run

左侧为普通问答:用户提问,模型返回步骤清单,请求结束,没有任何操作被执行。右侧为 Agent 的四个组件:模型决定下一步调用哪个工具,未声明的工具无法调用;记忆保存目标与结论,每轮写入 system 消息;循环负责执行工具、回灌结果并再次请求模型;没有 tool_calls 时给出终答,达到轮数上限或累计 token 超限时终止。

三、缺少任一组件会怎样

缺失组件结果
模型没有部件能根据上下文决定下一步,流程只能由代码写死
工具模型只能输出文字建议,例如「建议去投放后台追投」,无法查询数据,也无法执行操作
记忆短任务可以依赖 messages 完成;一旦上下文被裁剪,之前得到的 ROI 结论和 adgroup_id 就会丢失
循环只有一次请求和一次响应。模型即使请求了工具,也没有代码去执行,更不会有第二轮

四个组件中,循环是 Agent 与普通聊天的分界。普通聊天是一次请求、一次响应;Agent 是一个反复迭代的过程,必须明确定义出口:模型不再请求工具时正常结束,达到轮数上限或 token 预算时由代码终止。缺少后两个出口,模型一旦反复调用同一个工具,循环就不会停止。

四、一次追投任务的完整流程

以开头的目标为例,一次 run 的过程如下:

  1. 第一轮:查询报表。 模型请求调用报表工具,查询 10 月 31 日的数据。代码执行查询,把结果(ROI 1.8,目标 1.5)回灌到 messages,同时把「ROI 达标」写入记忆。
  2. 第二轮:搜索广告组。 模型请求搜索该计划下的广告组。代码返回三个广告组,并把它们的 ID 记入本次 run 的白名单。三个广告组中只有已过学习期的 AG-002 支持 aggressive 档。
  3. 第三轮:请求确认。 模型不再请求工具,而是给出方案:「即将为『商品卡成交-老客』(AG-002)下 aggressive 档追投,11-01 起两天,预付 800 元,确认吗?」追投涉及扣款,代码在收到用户确认前不会执行它,run 在此暂停。
  4. 第四轮:执行追投。 用户回复「确认」后,代码把这条消息追加到同一份 messages,继续循环。模型请求调用 boost_adgroup,代码先检查 AG-002 是否在白名单中,再提交追投,返回追投单号。模型给出终答,run 结束。

在这个流程里,没有工具,模型只能建议使用 aggressive 档;没有记忆,上下文裁剪后 AG-002 和 ROI 结论无从核对;没有循环,查完报表就结束了,不会进入后续步骤。

还有两处设计值得注意。第一,ID 校验发生在执行写操作的时候,而不是搜索的时候:搜索只负责记录哪些 ID 合法,追投时再检查传入的 ID 是否在其中,防止模型编造 ID。第二,确认之后继续使用原来的 messages,而不是开始一个新的 run,否则之前的搜索结果和白名单都会丢失。

五、几个常见误解

Agent 有自主意识。 没有。模型每一轮做的是同一件事:根据当前 messages 生成下一步输出,可能是工具调用,也可能是文本。所谓「自主」,是这种生成被放进循环、并且接上了工具之后表现出来的效果。

步骤越多越智能。 多步任务的成功率大致是各步成功率的乘积。假设各步相互独立、单步成功率为 95%,三步约为 86%,二十步只剩约 36%。实际工程中提高可靠性的办法通常是缩短链路、合并不必向用户展示的中间步骤,并在写操作前加入人工确认,而不是指望模型变得更强。

模型能调用任何它想调用的东西。 模型可能编造一个不存在的工具名,但宿主程序只执行已经注册的工具,找不到对应函数时返回错误。因此系统的风险边界由开放了哪些工具决定:查询报表是只读操作,出错的代价是一次无效查询;追投会直接扣款,必须叠加 ID 校验和人工确认。

六、判断一个系统是不是 Agent

可以从三个方面检查:

  1. 输入是什么。 系统接收的是一个目标(如「看 ROI 决定追投档位」),还是一串预先写好的步骤?
  2. 下一步由谁决定。 调用哪个工具、走哪条分支,是模型在运行时根据结果决定,还是代码里写死的 if-else?
  3. 能否与外部交互。 系统能否通过工具读取或修改外部数据,还是只能输出文本?

三项都偏向前者,才是本系列所说的 Agent。

需要说明,主线目标里「ROI 达到 1.5 用 aggressive 档」是一条明确的阈值规则,用代码 if-else 也能实现。保留它是为了让后续的轨迹检查有确定的正确答案。真实需求往往更模糊,例如「看看这个计划值不值得追投」,这时分支判断才真正需要交给模型。

小结

Agent 由模型、工具、记忆、循环四个组件构成。模型只负责决定下一步调用哪个工具或给出回答;工具的执行、参数校验、结果回灌和退出判断都由代码完成。区分 Agent 与普通聊天的关键是循环:它让模型可以根据上一步的结果决定下一步,也要求开发者明确定义循环在什么条件下结束。

下一篇将用几十行 Python 实现这个循环,逐轮查看 messages 的变化,并实现三个出口。


–views
Share this post on:

Previous Post
Agent 调度循环:工具调用、结果回灌与三个出口
Next Post
Go 日志设计笔记:从照搬 logback 到删掉一半设计