什么是 Agent Loop

同样是调 GPT 或者 Claude,网页聊天框里它只能回你一段话;放进 Codex 或者 Claude Code 里,它能自己翻文件、改代码、跑测试,折腾二十分钟把活干完。

差别不在模型,在模型外面那圈代码------一段 while 循环。
本文收录于微信公众号:愤怒的代码 AngryCode

0x00、Agent 和聊天框,差的就是一个循环

先看一个说法:AI Agent 就是一段会重复执行的代码。

它干的事可以用一句话说完:把当前状况发给大模型,执行模型选中的工具,把结果塞回去,再发一遍,一直转到任务做完。

Think(想)→ Act(做)→ Observe(看)→ 重复,直到目标达成。

这段代码有个正式名字,叫 Agent Loop,也叫 agentic loop。它跑一圈叫一个 step,或者叫一个 turn。

删掉循环,同一个模型就退化成只会一问一答的聊天框;加上循环,它就成了能追着一个目标跑几十步的 Agent。模型没变,工具没变,变的只是外面这圈壳。


0x01、模型思考,循环执行

大模型输出的不是动作,是描述动作的一段文本 :想调用 search_flights,参数是出发地、目的地、日期。它没有手,没有网络,没有文件系统。真正去调接口、真正去读文件、真正把结果拿回来的,是外面这圈循环。

模型出主意,循环干活。所以超时、重试、并发、限流这些脏活,全归循环管,跟模型无关。

那为什么非要有循环,不一次问完拉倒?两个理由:

第一,一次调用干不完多步任务。

你让它"找明天深圳飞北京最便宜的 3 个直飞航班",它没法凭空回答。得查实时航班、读返回结果、筛掉中转、再按价格排一遍。每一步都依赖上一步的结果,而模型一次只能想一步。循环让它想一步、看一步、再想一步。

第二,模型是无状态的。

它不记得上一次调用发生过什么。每次请求,它都是从零开始读你递过去的那段文本,读完就忘。

那它怎么知道自己已经干到第几步了?

靠循环每轮把完整历史重发一遍:最初的目标、模型每次选中的工具、每个工具返回的结果,一条不落。这份不断变长的历史,就是 Agent 的短期记忆。

把模型想成一个每天换班的人:接班时,他对上一班干过什么毫无印象,必须把交接班记录从头读一遍,才知道活干到哪了。那本记录就是 messages,每轮从头读一遍的动作就是循环重发历史。记录越写越厚,读起来越慢------后面要讲的上下文溢出,根子就埋在这。


0x02、Think-Act-Observe

把任意一轮拆开,里面永远是这三个环节。

环节 谁在做 干了什么
Think(想) 模型 读目标、指令、工具清单、全部历史,决定"调哪个工具、传什么参",或者"我干完了,这是答案"
Act(做) 循环 真的执行那个工具:搜网页、查库、调接口、跑命令
Observe(看) 循环 把返回值记成一条观察记录(observation),追加进历史,下一轮整段再喂回去

三个环节走完,历史长了一截,模型比上一轮多知道一点,于是下一轮能做出更准的判断。几十轮下来,Agent 一点点逼近目标。

这套流程有个更出名的名字:ReAct(Reason and Act,推理与行动)。ReAct 是这套循环最流行的实现范式,但循环本身比任何范式都大------所有 Agent 骨架,拆到底都是这三个环节。

有个细节影响很大,却常被略过:observation 的写法,直接决定下一轮能不能做对决策。 同一个工具,把原始 JSON 整坨塞回去,和提炼成"航班 AI-123,6500 元,直飞",模型下一轮的表现不一样。现在不少 Agent 的瓶颈不在模型,在工具返回值的可读性。


0x03、查询航班过程

用户给的目标:"查明天深圳飞北京的直飞,给我最便宜的 3 个。"

  1. 循环把目标、指令、历史发给模型。
  2. 模型想一下:得先查航班。于是输出一个工具调用:search_flights(深圳, 北京, 明天)。
  3. 循环把这条调用执行掉。
  4. 工具返回一堆航班,循环把它记成 observation,追加进历史。
  5. 模型读这批结果,发现里面混着经停航班,于是决定:再调一次,按直飞过滤。
  6. 循环执行,记结果。
  7. 模型再读,发现过滤后只剩 2 个直飞,凑不够 3 个,于是换一个晚些的时段再查。
  8. 循环执行,记结果。
  9. 模型确认目标达成,输出最终答案:3 个航班号、时间、价格。

整个过程里,没有任何一步是提前写死的。循环不知道自己要转几圈,也不知道会用到哪几个工具------这是模型边跑边决定的。循环只负责把这三个环节转下去。写死流程的那叫工作流,不叫 Agent。


0x04、Agent Loop 代码实现

把上面这套东西落成代码,只要 25 行左右。call_llm 换成任何一家模型都行,call_tool 换成自己的工具集:

python 复制代码
async def agent_loop(user_message, tools, max_steps=10):
    messages = [{"role": "user", "content": user_message}]
    step = 0
    while True:
        # Guard:安全阀,撞上限就退出
        if step >= max_steps:
            return "Reached step limit without completing the task."
        step += 1

        # Think:模型思考,问模型下一步干什么
        response = await call_llm(messages, tools)

        # Done:模型说干完了,收工
        if response.is_done:
            return response.final_answer

        # Act + Observe:跑模型挑选的工具,把结果记录到历史中
        for tool_call in response.tool_calls:
            result = await call_tool(tool_call.name, tool_call.arguments)  # 执行工具
            # 把工具执行结果添加到历史记录中
            messages.append({
                "role": "tool",
                "name": tool_call.name,
                "content": str(result),
            })

逐段看:

  • while True:循环本体,Agent 的运行时。
  • max_steps:硬性上限,防止它转到天荒地老。这是整套代码里最重要的一道保险。
  • call_llm(messages, tools) :把全量历史和工具清单一起递过去。await 的意义在于这段时间不占着进程------同一个进程可以同时服务别的会话。
  • if response.is_done:模型主动喊停,直接返回答案,跳出循环。
  • for tool_call in response.tool_calls :执行工具,把结果 append 进 messages,下一轮跟着历史一起回去。

最后这点是整段代码的重心:messages 是这个函数里唯一活着的东西。它从用户那句话开始,每轮长一截。长期记忆、并行调用、重试、日志,都是在它外面加的装饰,内核就这 25 行。


0x05、并行工具调用

上面演示的是一轮一个工具,真实场景往往不是这样。

现代模型能在一轮里吐出多个工具调用。要查三个城市的天气,它不必查一个等一次,可以一次发三个。循环收到后并行执行,三份结果一起记回历史。

这就是代码里写 for tool_call in response.tool_calls 的原因------它是个列表,可能只有一条,也可能有五条。

循环的形状一点没变:模型决策、工具执行、观察回流。变的只是单轮能携带的动作数。把互相独立的工具并行跑,是给 Agent 提速最直接的手段,而且不需要改动循环结构。

同一套循环也被用在动作根本不是接口调用的地方------比如点屏幕、敲键盘的 computer-use Agent。工具换成了"点这里""输这段",三个环节不变。


0x06、循环结束

一个停不下来的循环就是灾难。停止条件只有两类,两类都得有。

自然停止:模型说完了。 某轮它读完历史,判断目标达成,直接给最终答案而不是再点工具。循环看到这个信号就退出。这是我们想要的那种结束。

安全停止:步数上限。 如果模型永远不说"我干完了",它就会一直点工具。所以循环自己数着轮次,撞到 max_steps 就强制退出。

区别很关键:自然停止的判断来自模型,安全停止的判断来自你的代码。 只靠前者,等于把刹车交给一个可能判断失误的司机。

生产环境通常还会再叠几层:时间预算(跑太久就停)、成本预算(token 或钱花超了就停)、人工中断(让人随时能喊停)。

如果 Agent 三天两头撞上步数上限,问题一般不在循环,在指令和工具------它没能靠自己找到终点。上限是安全网,不该当成正常出口。


0x07、四种翻车方式

循环本身很简单,但它的失败方式很固定。认清这四种,比背下循环结构有用得多。

失败模式 现象 修法
无限循环 一直点工具,永远不给最终答案,时间和钱一起烧 硬性步数上限,这道保险不能省
卡在同一个动作 同样的工具、同样的参数反复调,结果一样,毫无进展 检测重复调用,回灌一句"这条你已经试过了,换个思路";多数时候改指令也能解决
上下文溢出 历史只增不减,几十轮后超出 context window(模型一次能读的最大文本量) 摘要早期轮次,或丢掉过期的 observation,让最有用的信息留在窗口里
过早停止 任务没做完就交答案,比无限循环更隐蔽 把"什么算完成"写清楚,越具体越好

这四条里,第三条最容易被低估。它不报错,只是从某天开始,Agent 突然忘了十分钟前自己干过什么------因为那些轮次已经被摘要掉了。

生产级 Agent 的工程量,基本都花在这四种失败上。 顺风顺水跑几轮就完事的路径,是所有情况里最简单的一条。


0x08、总结

同一个模型,在聊天框里只能一问一答,在编辑器里能自己干二十分钟的活。模型没变,工具没变,变的只是外面那圈壳。

那圈壳是 25 行代码:一个 while、一次模型调用、一个停止判断、一段把工具结果塞回历史的代码。

模型提供智能,循环提供记忆和方向。智能可以靠换模型升级,记忆和方向只能靠写对这段循环。这是理解 Agent 的入场券:Agent 不是更聪明的模型,是一个能反复使用同一个模型的装置。


本文收录于微信公众号:愤怒的代码 AngryCode

相关推荐
正飞信息技术1 小时前
llms.txt部署指南:给AI爬虫一张清晰的网站地图
人工智能·爬虫
Zhao-Jichao1 小时前
AI Prompting for Everyone 课程笔记 - P08 上下文 Context
人工智能·笔记
Token_w1 小时前
手机远控跑Claude Code:三个AI任务同时运行,ToDesk/UU远程/向日葵终端功能对比
人工智能·智能手机·todesk
鬼手点金1 小时前
Skills:领域能力的可组合单元
java·人工智能·笔记·自动化·1024程序员节
jianjinwssy1 小时前
从零设计一个 Agent 安全沙箱:Docker 隔离、三级风控与 Kafka 审计
人工智能·docker·kafka
fellow991 小时前
DSH Desktop - Android Edition
android·agent·deepseek·harness·dsh
hpoenixf1 小时前
金融 Agent 全景:连接研究、业务与责任的系统架构
agent
五点六六六1 小时前
Memory 系统如何从 0 到 1 构建(企业级入门版
agent·领域驱动设计·aiops
hpoenixf1 小时前
一份报告背后的九个架构判断
agent