本系列不依赖框架,从一个几十行的循环开始,逐步加上 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-美妆拉新要不要追投」,两种系统的行为不同:
- 普通聊天:模型生成一段建议,例如「先看昨天的 ROI,达标再追投」。请求到此结束,报表没有被查询,追投也没有发生,后续操作由人完成。
- Agent:模型先请求查询报表,代码执行查询并把结果交还给模型;模型再请求搜索广告组,代码再次执行;条件满足后,模型给出追投方案,经用户确认后由代码提交追投。
两者用的可以是同一个模型。区别来自外层:系统是否向模型提供了可调用的工具,以及是否有一段循环负责执行工具、把结果交还给模型、再决定是否继续。
需要说明的是,「能改写外部状态」不是 Agent 的必要条件。只查报表、不做任何写操作的系统,只要下一步由模型根据结果决定,同样是 Agent。写操作带来的是风险等级的变化,后文会单独讨论。
二、四个组件与基本术语
| 组件 | 定义 |
|---|---|
| 模型 | 大语言模型。每一轮根据当前上下文决定下一步:调用哪个工具、传什么参数,或者直接给出回答 |
| 工具 | 由宿主程序执行的函数,可以是本地函数,也可以是远程服务。通过 schema 声明给模型,未声明的工具模型看不到 |
| 记忆 | 在循环之外单独维护的状态,保存目标和已经确认的结论(如 ROI 是否达标、可用的广告组 ID) |
| 循环 | 驱动整个过程的代码:请求模型,执行模型要求的工具,把结果写回上下文,再次请求模型,直到满足退出条件 |
需要强调的是,模型本身不执行任何工具。它只在响应里声明「我要调用某个工具,参数是什么」,执行、校验和退出判断都由循环代码负责。
记忆单独列为一个组件,是因为 messages 虽然记录了全部历史,但它会随轮数增长,迟早需要裁剪。裁剪之后,早期轮次里的关键结论也会随之消失。把这些结论另存一份,在每轮请求前写入 system 消息,模型就不依赖完整历史。这是本系列采用的做法,后续「上下文与记忆」一篇会展开。
后续各篇会反复用到以下术语:
| 术语 | 含义 |
|---|---|
messages | 每轮发送给模型的消息列表,按时间顺序记录 system、user、assistant、tool 四类消息 |
| 回灌 | 把工具的返回值或错误信息作为一条 role=tool 的消息追加到 messages,供模型下一轮读取 |
| 终答 | 模型不再请求工具时返回的文本内容 |
| 终止 | 由代码强制结束循环,常见触发条件是达到轮数上限或累计 token 超出预算 |
| 出口 | 循环结束的条件,包括正常给出终答和被代码终止两类 |
run | 一个目标从进入循环到最终结束的完整过程。中途因等待用户确认而暂停、确认后继续,仍属于同一个 run |
三、缺少任一组件会怎样
| 缺失组件 | 结果 |
|---|---|
| 模型 | 没有部件能根据上下文决定下一步,流程只能由代码写死 |
| 工具 | 模型只能输出文字建议,例如「建议去投放后台追投」,无法查询数据,也无法执行操作 |
| 记忆 | 短任务可以依赖 messages 完成;一旦上下文被裁剪,之前得到的 ROI 结论和 adgroup_id 就会丢失 |
| 循环 | 只有一次请求和一次响应。模型即使请求了工具,也没有代码去执行,更不会有第二轮 |
四个组件中,循环是 Agent 与普通聊天的分界。普通聊天是一次请求、一次响应;Agent 是一个反复迭代的过程,必须明确定义出口:模型不再请求工具时正常结束,达到轮数上限或 token 预算时由代码终止。缺少后两个出口,模型一旦反复调用同一个工具,循环就不会停止。
四、一次追投任务的完整流程
以开头的目标为例,一次 run 的过程如下:
- 第一轮:查询报表。 模型请求调用报表工具,查询 10 月 31 日的数据。代码执行查询,把结果(ROI 1.8,目标 1.5)回灌到
messages,同时把「ROI 达标」写入记忆。 - 第二轮:搜索广告组。 模型请求搜索该计划下的广告组。代码返回三个广告组,并把它们的 ID 记入本次 run 的白名单。三个广告组中只有已过学习期的
AG-002支持 aggressive 档。 - 第三轮:请求确认。 模型不再请求工具,而是给出方案:「即将为『商品卡成交-老客』(AG-002)下 aggressive 档追投,11-01 起两天,预付 800 元,确认吗?」追投涉及扣款,代码在收到用户确认前不会执行它,run 在此暂停。
- 第四轮:执行追投。 用户回复「确认」后,代码把这条消息追加到同一份
messages,继续循环。模型请求调用boost_adgroup,代码先检查AG-002是否在白名单中,再提交追投,返回追投单号。模型给出终答,run 结束。
在这个流程里,没有工具,模型只能建议使用 aggressive 档;没有记忆,上下文裁剪后 AG-002 和 ROI 结论无从核对;没有循环,查完报表就结束了,不会进入后续步骤。
还有两处设计值得注意。第一,ID 校验发生在执行写操作的时候,而不是搜索的时候:搜索只负责记录哪些 ID 合法,追投时再检查传入的 ID 是否在其中,防止模型编造 ID。第二,确认之后继续使用原来的 messages,而不是开始一个新的 run,否则之前的搜索结果和白名单都会丢失。
五、几个常见误解
Agent 有自主意识。 没有。模型每一轮做的是同一件事:根据当前 messages 生成下一步输出,可能是工具调用,也可能是文本。所谓「自主」,是这种生成被放进循环、并且接上了工具之后表现出来的效果。
步骤越多越智能。 多步任务的成功率大致是各步成功率的乘积。假设各步相互独立、单步成功率为 95%,三步约为 86%,二十步只剩约 36%。实际工程中提高可靠性的办法通常是缩短链路、合并不必向用户展示的中间步骤,并在写操作前加入人工确认,而不是指望模型变得更强。
模型能调用任何它想调用的东西。 模型可能编造一个不存在的工具名,但宿主程序只执行已经注册的工具,找不到对应函数时返回错误。因此系统的风险边界由开放了哪些工具决定:查询报表是只读操作,出错的代价是一次无效查询;追投会直接扣款,必须叠加 ID 校验和人工确认。
六、判断一个系统是不是 Agent
可以从三个方面检查:
- 输入是什么。 系统接收的是一个目标(如「看 ROI 决定追投档位」),还是一串预先写好的步骤?
- 下一步由谁决定。 调用哪个工具、走哪条分支,是模型在运行时根据结果决定,还是代码里写死的
if-else? - 能否与外部交互。 系统能否通过工具读取或修改外部数据,还是只能输出文本?
三项都偏向前者,才是本系列所说的 Agent。
需要说明,主线目标里「ROI 达到 1.5 用 aggressive 档」是一条明确的阈值规则,用代码 if-else 也能实现。保留它是为了让后续的轨迹检查有确定的正确答案。真实需求往往更模糊,例如「看看这个计划值不值得追投」,这时分支判断才真正需要交给模型。
小结
Agent 由模型、工具、记忆、循环四个组件构成。模型只负责决定下一步调用哪个工具或给出回答;工具的执行、参数校验、结果回灌和退出判断都由代码完成。区分 Agent 与普通聊天的关键是循环:它让模型可以根据上一步的结果决定下一步,也要求开发者明确定义循环在什么条件下结束。
下一篇将用几十行 Python 实现这个循环,逐轮查看 messages 的变化,并实现三个出口。