本文深入解析了 Agentic Loop 的核心概念与实现方法,通过手写代码示例,详细阐述了模型、工具和循环代码三方协作的机制。文章重点介绍了 ReAct 思路在 Agent 主循环中的应用,并探讨了循环的两种停止方式及其影响。此外,还分析了 Agent 循环的三层结构,帮助读者全面理解大模型的工作原理,适合对 AI 和编程感兴趣的小白或程序员学习。
今年上半年,agentic loop这个词几乎刷屏了。
Anthropic 的 Claude Code 负责人 Boris Cherny 说过,手头这么多活里,loop 会是他未来十年最自豪的一个。他现在几乎不写 prompt,只写 loop。
OpenClaw 创建者、现在已经入职 OpenAI 的 Peter Steinberger,让上百个 Codex 实例几乎无人盯着,连转了约一个月。
这个热潮我们之前聊过:
那次聊的是「该不该」:什么任务值得让 agent 自己转,什么任务人得留在环里。
今天来回答另一个问题,「怎么造」:这台会自己转的循环,代码上到底长什么样。
从工具这条线看,料已经齐了:写工具、Tool Calling、MCP,我们前面都仔细展开过,感兴趣的朋友可以去回看系列文章。
写工具、Tool Calling 和 MCP 已经就位,Agent 主循环是让工具自己转起来的最后一步。
所以今天来看 Agent 主循环 本身:它怎么把工具结果带进下一轮、什么时候继续转、什么时候停。
Agent 的自主性是你代码里那台循环造出来的,模型没有这个魔法。
先划重点
模型每次只返回一轮响应。工具结果得由循环代码写回 messages、再调一次模型,任务才走得完。
Reason 想下一步,Act 发起工具调用,Observe 看工具带回什么。三个动作反复交替,这套思路就叫 ReAct。
循环有两种停法:模型不再返回 tool_calls 是自然停止,撞上轮数上限是开发者设的强制停止。强制停出来的是半成品。
generateText 接的是一整个任务,不是一次调用。手写的那台 while 整个搬进了它内部,stopWhen 不传默认只跑 1 步。
常见的 loop 可以分三层看:日程、验收、干活。验收这层,标准能写成脚本就交给机器,得靠判断就留着人。真正转动工具的,永远是最里层这台 while。
一次生成,走不到答案
工具查到了,答案却没来
我们接下来的 demo,都用 Vercel 的 AI SDK 配 DeepSeek。
AI SDK 我们前面一直在用,是老搭档了。
DeepSeek 便宜又够用,我平时跑 demo 就用它。
工具怎么定义、generateText怎么带工具调用,我们前面已经一起捋过了,这篇就接着它往下走。
天气工具用tool()定义:description告诉模型它能查气温,inputSchema约束参数格式。
我们要看的是循环怎么转,不是怎么调天气 API,所以工具不接真实接口,直接从一张假数据表取值就够了:上海 20°C,深圳 28°C。
const weatherData: Record<string, string> = { '上海': '上海: 20°C', '深圳': '深圳: 28°C', } const result = await generateText({ model: deepseek('deepseek-chat'), prompt: '上海和深圳谁更热?', tools: { weather: tool({ description: '查询指定城市当前气温', inputSchema: z.object({ city: z.string().describe('城市名'), }), execute: async ({ city }) => { const value = weatherData[city] ?? `${city}: 未知` console.log(`[天气工具结果] ${value}`) return value }, }), }, }) console.log(`[模型输出] ${result.text}`)实际运行的关键输出只有三行:
[天气工具结果] 上海: 20°C [天气工具结果] 深圳: 28°C [模型输出] 我来查询这两个城市的气温信息。前两行由天气工具的execute打印,第三行才是模型返回的文字。
AI SDK 确实调用了两次weather:一次传入上海,一次传入深圳。
两个城市的气温都查到了,模型却没回答哪个更热。
模型不会停在服务端等天气结果。 第一次模型调用返回两条工具调用请求后,这次调用就结束了。
AI SDK 接着在你的代码里执行两次execute,拿到 20°C 和 28°C。
注意这两个结果的去向:execute是 AI SDK 调用的,所以它的返回值先交给 AI SDK。模型此时还没收到气温。
模型单次调用返回两条工具调用后就结束,SDK 拿到气温整理成消息,但当前代码没有再调用一次模型,断点就在这里。
问题就在这一步:AI SDK 把结果整理成工具结果消息之后,当前代码没有拿这组新消息再调用一次模型。
第二次模型调用启动后,模型才会看到 20°C 和 28°C。
加一行 stopWhen 就能跑通,但循环还藏在 SDK 里面
现成解法在调用配置里只多一行stopWhen:
-import { generateText, tool } from 'ai' +import { generateText, stepCountIs, tool } from 'ai' const result = await generateText({ // 其余配置不变 + stopWhen: stepCountIs(3), })stepCountIs(3)允许这次任务最多走三步。两次天气工具调用都发生在第一步里。
第一步结束后还没到上限,generateText里面的循环便拿着两条工具结果,发起第二次模型调用。
模型这时才看到两份气温,回答「深圳更热」。
一行配置已经能把任务跑完,不过循环本身仍藏在generateText里面:
工具结果具体怎么变成消息?
下一次模型调用是谁启动的?
模型给出答案以后,循环又怎么知道该停?
这次我们先把这行收起来,自己造一台。
手写主循环,看清 Agent 为什么会自己往下走
第二次调用是怎么发生的,我们一起来手写一遍看看。
模型一次只给一轮响应,工具只把执行结果交给代码,真正决定「要不要再问一次模型」的是循环代码。
一轮循环里模型、工具、循环代码三方各管一段:模型判断下一步、工具只交结果、循环代码负责回灌与判停,自主性就在循环代码这一段。
模型还是 DeepSeek,天气工具也不变。
只是先不用 SDK 的generateText,改用自己写的callLLM(messages),直接拿模型的原始返回:它每次把完整消息和工具定义发过去,拿回一轮响应。
下面代码里的tool_calls、role这些字段,都是模型返回里本来的样子。
第一轮拿到工具结果,第二轮才回答
任务开始时,messages里只有一句用户问题:「上海和深圳谁更热?」
第一次调用模型,响应里有两条天气工具调用。
代码保存这条模型回复,执行两次weather,拿到上海 20°C、深圳 28°C,再把两条工具结果也放进messages。
此时模型已经结束了第一次响应,还没看到气温。
代码要把更新后的messages再发一次,模型才会在第二次调用里读到结果,回答「深圳更热」。
如果第二次响应里还有工具调用,代码就继续执行工具、补上结果、再调用模型。
这个重复过程,就是主循环。
这段过程写成 while,只剩十几行
while每转一圈,代码就带着最新的消息再调用一次模型。
const messages: any[] = [ { role: 'user', content: '上海和深圳谁更热?' }, ] let round = 0 while (true) { if (++round > 10) break const msg = (await callLLM(messages)).message messages.push(msg) if (!msg.tool_calls) break for (const tc of msg.tool_calls) { const { city } = JSON.parse(tc.function.arguments) const content = weather(city) messages.push({ role: 'tool', tool_call_id: tc.id, content }) } }第一轮执行到for,代码把模型发起的天气查询全部跑完,结果写进messages。
程序走到while末尾,又回到开头执行callLLM(messages),第二次模型调用就这样启动了。
messages可以理解成一份不断加长的对话记录,每条消息的role标注这句话是谁说的:user是用户,assistant是模型,tool是工具结果。
模型不会记住上一次请求,所以每一轮都要把这份完整记录重新发过去。
tool_call_id原样使用模型给出的tc.id,让 API 能把每个结果配回对应的那次查询。
运行手写版循环 demo,终端打出两轮:第 1 轮 API 返回两个 tool_calls、finish_reason 为 tool_calls,回灌两条 tool 结果;第 2 轮返回「深圳更热」、finish_reason 为 stop,自然停止。
两个 break:一个等模型说完,一个防它转个不停
第二轮里,模型看到两份气温,直接回答「深圳更热」,没有再发起工具调用。
代码遇到if (!msg.tool_calls) break,把这次响应当成最终答案退出循环。
此时messages最后一条,就是给用户的答案。
模型也可能一直要求调用工具。if (++round > 10) break是开发者加的硬上限,即使模型还想继续,代码跑满轮数也会停。
这时messages最后一条还是没消化完的工具结果,用户拿到的是半成品。
循环这边的动作就这么多:执行、回灌、再调用。
但每一轮要不要继续,循环代码看的是模型返回里有没有tool_calls。
它凭什么第一轮决定查天气,第二轮又决定不查了?
想一步、做一步、看一步,这套节奏叫 ReAct
前面手写的那台while,每转一圈,模型都在做同一件事:
看着眼前的信息,决定下一步。
这一圈里的三个动作,各有名字。
Reason:从现有信息判断下一步
天气例子的第一轮,模型手里只有用户的问题、还没有气温,于是决定先查天气。
这个「判断下一步做什么」的动作就是 Reason。
Act:发起调用,还不是结果
模型决定查天气,并不自己执行weather(),而是返回tool_calls,里面有工具名、参数和 id,代码读到才真正跑两次weather()。
这个发起动作的阶段叫 Act,它只是调用请求,还不是结果。
Observe:工具带回新信息,模型还看不见
weather()跑完,代码拿到 20°C 和 28°C,这些新信息叫 Observe。
此刻结果只在代码手里。要等它写进messages、while再转一圈,模型才在下一次调用里读到。
三个阶段就标在这十几行里
还是前面那段循环,把三个阶段标到对应的行上:
while (true) { const msg = (await callLLM(messages)).message // ← Reason:模型判断下一步 messages.push(msg) if (!msg.tool_calls) break // ← 没有新的 Act,任务结束 for (const tc of msg.tool_calls) { // ← Act:模型发起的工具调用 const { city } = JSON.parse(tc.function.arguments) const result = weather(city) // ← Observe:工具带回新信息 messages.push({ role: 'tool', tool_call_id: tc.id, content: result }) } }转到for末尾、回到while开头,就是拿着新的 Observe 再做一次 Reason。
Reason 判断下一步、Act 返回 tool_calls、代码执行工具得到 Observe,回灌进 messages 后再转一圈,三阶段闭环。
第一轮虽然同时返回两个tool_calls,也只是把两次查询排在同一轮。
模型发起调用时还没拿到气温,必须等 Observe 回灌后再转一圈,才能比较结果。
ReAct 是一种思路
模型拿着新的 Observe 再 Reason、决定下一个 Act,这套反复交替的节奏就叫 ReAct。
它是一种思路、一种范式;我一开始以为要 import 一个 ReAct 库才能用,但其实你手写的那台while就是它。
而手写的这套「想一步、做一步、看一步」,早在 2022 年就有人命名、验证过了。
前 OpenAI 研究员、现任腾讯首席 AI 科学家的姚顺雨,把它写进了论文《ReAct: Synergizing Reasoning and Acting in Language Models》(arXiv 2022 / ICLR 2023)。
论文里模型用纯文字写出Thought、Action、Observation;
到了 Tool Calling,Action 变成结构化的tool_calls、Observation 变成role: "tool"的消息,节奏还是同一套。
图片来源:ReAct 论文。模型在论文里用纯文字交替写出 Thought、Action、Observation;正文后面到了 Tool Calling,同一套节奏就换成了结构化的 tool_calls。
所以你在这台while里学到的,不会随某个 API 过时。
我们前面的天气例子在第二轮自然停了。
但模型不总这么省心,它可能到第十轮还在要求调工具,这时候就得代码出手让它停。
循环有两种停法,用户拿到的东西不一样
让循环停下来这件事,骨架里那行if (++round > 10) break已经写好了:跑满十轮,不管模型还想不想继续,一律掐断。
但两个break停出来的结果,对用户来说完全两样。
强制停止时,用户拿到的是半成品
天气任务第二轮,模型看完气温直接回答「深圳更热」,没有再发起工具调用,if (!msg.tool_calls) break退出。
这是 自然停止:停下来的依据是模型自己说完了。
撞上round > 10就是另一回事了。那一刻模型往往仍在返回tool_calls,它自己并不认为做完了,这是安全阀的 强制停止。
自然停止与强制停止的区别:末条消息一个是最终答案、一个是没消化的工具结果,用户拿到的分别是答案和半成品。
轮数上限是最常用的强制条件,尝试次数、token 预算也都算这一类。
手写的循环就这些了:发起、回灌、两个break判停。
这一整套交给generateText之后,还跟我们手写的每一行对得上吗?
手写是为了看懂,写完就可以交给 SDK
对得上,而且每一段都对得上。
分清三方的职责之后看得很明白:
模型只负责判断下一步、发起调用;
真正属于业务的,只有 weather 里那几行查数据的逻辑。
剩下的呢?
解析参数的JSON.parse、遍历执行的for、造 tool 消息回灌的push、两个break的判停,这一整圈跟业务没有关系。
换一个项目、换一批工具,写出来还是这些代码。
每个项目都一样的代码,没必要每个项目自己写一遍、自己维护一遍。
这类重复活 Agent 框架都替你写好了:LangChain、LangGraph 是这样,我们一直在用的 Vercel AI SDK 也是这样。
AI SDK 那台循环在哪?
我们前面加一行stopWhen任务就跑完了,因为generateText里面本来就有一台和手写版一样的循环。
把callLLM和while收起来,换回它,还是刚才那个比气温的任务:
const result = await generateText({ model: deepseek('deepseek-chat'), prompt: '上海和深圳谁更热?', tools: { weather: tool({ description: '查询指定城市当前气温', inputSchema: z.object({ city: z.string().describe('城市名') }), execute: async ({ city }) => weatherData[city] ?? `${city}: 未知`, }), }, stopWhen: stepCountIs(10), })跑一遍,两个 step 完成任务:第一步发起两次 weather 调用并回灌,第二步给出「深圳更热」。
运行 SDK 版 generateText demo,终端打出两个 step:step 1 发起两次 weather 调用并回灌、finishReason 为 tool-calls;step 2 给出「深圳更热」、finishReason 为 stop,末行显示「总共转了 2 个 step」。
和手写版逐项对得上,节奏完全一样——stopWhen上限这里特意写成 10,对齐手写版的round > 10。
SDK 说的 step,就是手写版的「轮」。
generateText 接的是一次任务
generateText 很容易被当成一次模型调用的封装:发个请求,拿回文本。
没有工具的时候,这么理解没问题,一次任务刚好等于一次生成。
但它承诺的其实是一次任务:调用进去,最终结果出来。
tools 里带上 execute 之后,干完一个任务可能需要好几轮往返,这个承诺不变,那台 while 只能装进它内部。
generateText 承诺的是「一次任务」:带 execute 时,它内部会转好几轮 while,直到任务完成。
对照关系也就清楚了:手写版那整段while循环对应一次 generateText 调用;手写版的 callLLM() 对应 SDK 内部每个 step 对模型发的那一次请求。
手写的每一段,SDK 用什么接管
手写版每一段(while 骨架、parse、遍历执行、造消息回灌、安全阀、自然停止)分别对应 SDK 接管的部分,你只交出 execute。
我交出去的只有 execute 这个业务函数,SDK 替我把 parse 校验、遍历调度、造消息回灌、循环判停这一整圈全接管了。
execute 进参数出结果,别的什么都不知道;围着它转的这一圈,全在它外面发生。
那 stopWhen 为什么默认只跑 1 步,而没有默认转到自然停止?
因为每转一轮都在花 token,花多少得由开发者自己拿主意。
在我看来,这其实是把责任归属交回给你:
让机器自己转,必须由开发者显式开启,写下stopWhen就等于同意它自主转、上限自己定。
把人拿出循环必须是主动决策;stopWhen的默认值,正好逼你把这个选择做在明处。
回头看,循环到底是谁的?
手写版里它就在你的文件里。
框架版里它搬进了 generateText,但启动它的是你的调用,转几步的上限写在你签的 stopWhen 里,干活的 execute 也是你交的。
循环可以不亲手写,但它的开关和上限,一直在你的代码里。
generateText 内部承载 Agent 主循环,stopWhen 和最大步数仍由你的代码控制。
开关和上限都在自己手里。
至于行业天天挂在嘴边的那个 loop,其实还得往外看两层。
行业说的 loop,其实是三层
开头提到的那两个人:不写 prompt 只写 loop 的 Boris,让上百个实例连跑一个月的 Peter。
他们说的 loop,跟我们整篇手写的这台,是同一个东西吗?
不完全是。
行业里说的「loop」,其实同时在指好几种东西。
我更愿意按「谁决定继续、谁决定停」把它理成三层来看。
摆清楚这三层,你就知道你学的是最里哪一层,也知道之前说的「人留在环里」,人待的是哪一层。
干活、验收、日程,一层套一层
最里层是干活循环,就是我们整篇在写的这台while:
模型 Reason、Act,代码把 Observe 回灌,一直转到工具调用停下来。
工具调用一停,这层就停。
今天整篇写的,就是这一层。
外面套着一层验收循环:干完一轮,有个裁判验收,不过关就打回重做。
裁判可以是人,也可以是机器。
之前聊 loop 该不该用时说过「人留在环里最好」,人待的就是验收层——
我平时让 Claude Code 出 plan、我审核、再放行,就是人在这里把关。
而开头 Boris 的「只写 loop」则正相反:
他想把验收层的人换成机器,让人不用在场。
机器怎么当这个裁判,/goal是最干净的例子。
最外面还可能再套一层 日程循环:到点就把里面这套重跑一遍,/loop、/schedule排的就是它。
只有周期性任务才有这层,修一个 bug 这种一次性任务没有。
这三层能一层套一层:日程循环到点,唤醒一次验收;验收打回,再触发一次干活循环重跑。
| 层 | 谁决定继续、谁决定停 | 例子 |
|---|---|---|
| 日程循环 最外,不一定有 | 时间表 / 事件 | /loop/schedule |
| 验收循环 中间 | 裁判:人,或读脚本结果的评估模型 | /goal |
| 干活循环 最里 | tool_calls有无 + 安全阀 | 我们手写的while |
Agent 循环分成嵌套的三层:日程循环、验收循环与干活循环,外层能力都围绕内核转。
/goal:机器裁判长什么样
到现在,任务做没做完都是模型自己说了算:
像比气温这种,它答出「深圳更热」就算完了,没有一条外部标准能核对。
可有些任务不一样,「做完没」能定出客观标准、用脚本直接量,不用问模型。
判据能不能写成一条脚本查得了的死规则,就是这两类任务的分界。
Claude Code 的/goal命令,就是让你把这条标准写出来、交给脚本去验收。
我在桌面建了个空目录,跑了这么一条命令:
/goal 在当前目录创建 haiku.txt,写一首关于「循环」的小诗。 验收标准(用 node 脚本检查):恰好 3 行、每行不超过 12 个字、 全文出现「循环」二字。最多尝试 3 次。写诗需要品味,但这三条验收不考品味:行数、字数、关键词,跑个 node 脚本就能测。
每轮结束,一个评估模型照着脚本跑出来的结果判过没过,不过就打回重写,最多三次。
重试能放心交给它自动做,是因为判过没过不用人来盯,验收标准是死的。
在空目录里跑 /goal 让模型写三行小诗,它写完主动补了个验证脚本跑验收,一次通过,底部显示 Goal achieved · 1 turn。
一张截图里,两层循环都在转
顺着截图左侧的色条看:
写诗是一轮 Act,看到写入成功是 Observe,「要用 node 脚本验证」是新一轮 Reason。
再往下,写脚本、跑脚本又是两轮 Act,最后模型判断不需要再调工具,自然停止。
这就是最里层那台干活循环在转:它没写完诗就交差,而是写完自己决定「要验证」、写脚本、跑脚本。
外面的验收循环也转了,只转一圈就过。
底部那行Goal achieved · 1 turn在数圈数,数圈数说明它本来准备多转几圈。
它一次就过,是因为验收标准写在 goal 里,模型写的时候就朝判据靠。
停止条件能写得多死,决定了一个任务能放心交给机器到什么程度。
所以不管外面套几层,真正调用工具、把结果带进下一轮的,始终是最里层那一台。
循环,终究是你的
Agent 的自主性,是你代码里那台循环造出来的。
回灌、判停、安全阀都在你的代码里;换成 AI SDK 也一样,stopWhen 你不写,它默认只跑一步就停。
所以要给它多大自主权,也由你定。
验收标准能完全写成脚本的任务,可以把验收循环交给机器裁判;验收还得靠判断和取舍的,人就留在这一层。
所以别被三层五层的说法唬住,剥到最里面,就是你文件里这台几十行的while。
如何学习大模型 AI ?
由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。
但是具体到个人,只能说是:
“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。
这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。
我在一线科技企业深耕十二载,见证过太多因技术卡位而跃迁的案例。那些率先拥抱 AI 的同事,早已在效率与薪资上形成代际优势,我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在大模型的学习中的很多困惑。我们整理出这套AI 大模型突围资料包:
- ✅ 从零到一的 AI 学习路径图
- ✅ 大模型调优实战手册(附医疗/金融等大厂真实案例)
- ✅ 百度/阿里专家闭门录播课
- ✅ 大模型当下最新行业报告
- ✅ 真实大厂面试真题
- ✅ 2026 最新岗位需求图谱
所有资料 ⚡️ ,朋友们如果有需要《AI大模型入门+进阶学习资源包》,下方扫码获取~
① 全套AI大模型应用开发视频教程
(包含提示工程、RAG、LangChain、Agent、模型微调与部署、DeepSeek等技术点)
② 大模型系统化学习路线
作为学习AI大模型技术的新手,方向至关重要。 正确的学习路线可以为你节省时间,少走弯路;方向不对,努力白费。这里我给大家准备了一份最科学最系统的学习成长路线图和学习规划,带你从零基础入门到精通!
③ 大模型学习书籍&文档
学习AI大模型离不开书籍文档,我精选了一系列大模型技术的书籍和学习文档(电子版),它们由领域内的顶尖专家撰写,内容全面、深入、详尽,为你学习大模型提供坚实的理论基础。
④ AI大模型最新行业报告
2025最新行业报告,针对不同行业的现状、趋势、问题、机会等进行系统地调研和评估,以了解哪些行业更适合引入大模型的技术和应用,以及在哪些方面可以发挥大模型的优势。
⑤ 大模型项目实战&配套源码
学以致用,在项目实战中检验和巩固你所学到的知识,同时为你找工作就业和职业发展打下坚实的基础。
⑥ 大模型大厂面试真题
面试不仅是技术的较量,更需要充分的准备。在你已经掌握了大模型技术之后,就需要开始准备面试,我精心整理了一份大模型面试题库,涵盖当前面试中可能遇到的各种技术问题,让你在面试中游刃有余。
以上资料如何领取?
为什么大家都在学大模型?
最近科技巨头英特尔宣布裁员2万人,传统岗位不断缩减,但AI相关技术岗疯狂扩招,有3-5年经验,大厂薪资就能给到50K*20薪!
不出1年,“有AI项目经验”将成为投递简历的门槛。
风口之下,与其像“温水煮青蛙”一样坐等被行业淘汰,不如先人一步,掌握AI大模型原理+应用技术+项目实操经验,“顺风”翻盘!
这些资料真的有用吗?
这份资料由我和鲁为民博士(北京清华大学学士和美国加州理工学院博士)共同整理,现任上海殷泊信息科技CEO,其创立的MoPaaS云平台获Forrester全球’强劲表现者’认证,服务航天科工、国家电网等1000+企业,以第一作者在IEEE Transactions发表论文50+篇,获NASA JPL火星探测系统强化学习专利等35项中美专利。本套AI大模型课程由清华大学-加州理工双料博士、吴文俊人工智能奖得主鲁为民教授领衔研发。
资料内容涵盖了从入门到进阶的各类视频教程和实战项目,无论你是小白还是有些技术基础的技术人员,这份资料都绝对能帮助你提升薪资待遇,转行大模型岗位。