agent_loop的两层循环
核心实现位于:
packages/agent/src/agent-loop.ts:162-320- 入口
runAgentLoop():agent-loop.ts:101-125 Agent对 loop 的配置:packages/agent/src/agent.ts:467-504
结构如下:
while(true){lethasMoreToolCalls=true;while(hasMoreToolCalls||pendingMessages.length>0){// 发送请求、处理 assistant response、执行工具、处理 steering}// 检查 follow-up 或显式 continuation}可以先用一句话概括:
- 内层循环:当前 agent run 中,只要还有工具调用或 steering 消息,就继续推进下一个 turn。
- 外层循环:内层循环认为 agent 可以停止后,再检查是否有 follow-up,或者是否需要无新消息地继续一轮。
1. 为什么需要两层循环
一次用户 prompt 不一定只产生一次模型请求:
用户:请修复这个 bug ↓ 模型:调用 read ↓ 工具:返回文件内容 ↓ 模型:调用 edit ↓ 工具:返回编辑成功 ↓ 模型:调用测试工具 ↓ 工具:返回测试结果 ↓ 模型:给出最终回答这里发生了多个模型请求和多个工具执行,但它们都属于同一次 agent run。
如果用户在 agent 运行期间输入了新指令:
用户:不要改这个文件,改另一个文件这条消息可能作为 steering message 插入当前 run。
如果 agent 原本已经准备结束,但队列里又有新任务:
用户:顺便更新一下 README这条消息可能作为 follow-up,在 agent 原本要结束后触发下一轮。
因此需要区分:
当前 turn 内继续推进 vs. 当前 run 本来要结束,但又有新的后续操作2. 外层循环
外层循环位于:
packages/agent/src/agent-loop.ts:177-178- 结束逻辑位于
agent-loop.ts:300-319
代码逻辑可以抽象为:
while(true){run inner loop;constfollowUpMessages=awaitgetFollowUpMessages();if(followUpMessages.length>0){pendingMessages=followUpMessages;continue;}if(explicitContinuation){explicitContinuation=false;continue;}break;}外层循环的职责不是直接执行工具,而是决定:
当前内层循环结束后,整个 agent run 是否真的结束。
2.1 外层循环的第一次进入
进入runLoop()时:
letcurrentContext=initialContext;letconfig=initialConfig;letlastCompletedTurn:PrepareNextTurnContext|undefined;letexplicitContinuation=false;letpendingMessages=awaitconfig.getSteeringMessages?.()||[];位置:
agent-loop.ts:170-175
随后立即进入外层循环:
while(true){lethasMoreToolCalls=true;while(hasMoreToolCalls||pendingMessages.length>0){...}...}hasMoreToolCalls初始为true,这是为了确保第一次一定发送模型请求,而不是直接跳到外层循环末尾。
第一次请求通常来自:
初始 user message ↓ 内层循环 ↓ 模型请求2.2 外层循环检查 follow-up
内层循环退出后,说明:
没有待处理工具调用 并且没有待处理 steering message但这还不代表 agent 一定结束。
外层循环会调用:
constfollowUpMessages=(awaitconfig.getFollowUpMessages?.())||[];位置:
agent-loop.ts:300-301
如果存在 follow-up:
if(followUpMessages.length>0){explicitContinuation=false;pendingMessages=followUpMessages;continue;}位置:
agent-loop.ts:302-306
这里的continue是针对外层循环的。下一次外层循环开始后: