什么是 Agent Loop?
传统聊天机器人的工作模式很简单:你说一句话,它回一句话,一问一答。但 pi 不同——它是一个 Agent(智能体)。这意味着它不只是回答问题,而是主动执行任务:读文件、改代码、跑命令、看结果,再决定下一步。
让它「动起来」的核心机制,就是 Agent Loop。每当你给 pi 一个任务,它就进入一个持续的循环:
┌───────────────────────────────────────────┐ │ Agent Loop │ │ │ │ ┌─────────┐ │ │ │ Think │ stream(model, context) │ │ └────┬────┘ 大脑吐 token,边想边说 │ │ ▼ │ │ ┌─────────┐ │ │ │ Act │ 执行 toolCall │ │ └────┬────┘ read / edit / bash … │ │ ▼ │ │ ┌─────────┐ │ │ │ Observe │ toolResult 回灌 context │ │ └────┬────┘ │ │ ▼ │ │ ┌─────────┐ 还有 toolCall ──▶ 再来一轮 │ │ │ Repeat? │ │ │ └────┬────┘ 没有了 ──▶ turn 结束 │ │ └──────────────▶ 回到 Think │ └───────────────────────────────────────────┘
这个循环会一直跑,直到某一轮 assistant 消息里不再包含工具调用——这时 pi 认为任务做完了,停下来等你说话。
一轮「心跳」里发生了什么
每一轮 turn,runLoop() 都会做这几件事:
- 注入待发消息:你在等待时插队输入的 steering message 会先被塞进 context。
- 流式生成:
streamAssistantResponse()调用模型,token 实时流出,UI 同步渲染思考与文字。 - 检查工具调用:从 assistant 消息里
filter出toolCall。一个都没有,turn 就结束。 - 执行工具:
executeToolCalls()按并行 / 串行策略跑,结果作为toolResult推回context.messages。 - 决定去留:
shouldStopAfterTurnhook 可以提前喊停;否则只要还有工具调用,就继续下一轮。
关键直觉:pi 的「行动」从来不是一次性的。每一次工具结果都会回灌进下一次思考——这正是它能像真正的开发者一样「试错→观察→修正」的原因。
真实源码:runLoop()
这不是伪代码——下面是 packages/agent/src/agent-loop.ts 里 runLoop() 的精简版。点击高亮行或右侧注解,看每一段在做什么:
packages/agent/src/agent-loop.tstypescript
6 个注解
1async function runLoop(initialContext, newMessages, config, signal, emit, streamFn) {
2 let currentContext = initialContext;
3 let pendingMessages = (await config.getSteeringMessages?.()) || [];
4
5 // 外层循环:agent 本想停下,但又来了 follow-up 消息时继续
6 while (true) {
7 let hasMoreToolCalls = true;
8
9 // 内层循环:处理工具调用与插队消息
10 while (hasMoreToolCalls || pendingMessages.length > 0) {
11 await emit({ type: "turn_start" });
12
13 // 把插队的 steering 消息注入到下一次回复之前
14 for (const message of pendingMessages) {
15 currentContext.messages.push(message);
16 }
17 pendingMessages = [];
18
19 // 流式生成 assistant 回复
20 const message = await streamAssistantResponse(
21 currentContext, config, signal, emit, streamFn,
22 );
23
24 // 从回复里筛出工具调用
25 const toolCalls = message.content.filter((c) => c.type === "toolCall");
26
27 const toolResults = [];
28 hasMoreToolCalls = false;
29 if (toolCalls.length > 0) {
30 const batch = await executeToolCalls(currentContext, message, config, signal, emit);
31 toolResults.push(...batch.messages);
32 hasMoreToolCalls = !batch.terminate;
33
34 // 工具结果回灌 context —— 下一轮的「观察」输入
35 for (const result of toolResults) {
36 currentContext.messages.push(result);
37 }
38 }
39
40 await emit({ type: "turn_end", message, toolResults });
41
42 // hook:本轮之后是否应该停?
43 if (await config.shouldStopAfterTurn?.({ message, toolResults, ... })) {
44 await emit({ type: "agent_end", messages: newMessages });
45 return;
46 }
47 pendingMessages = (await config.getSteeringMessages?.()) || [];
48 }
49
50 // agent 本想停。检查是否有排队的后续消息
51 const followUp = (await config.getFollowUpMessages?.()) || [];
52 if (followUp.length > 0) { pendingMessages = followUp; continue; }
53 break;
54 }
55 await emit({ type: "agent_end", messages: newMessages });
56}
一个真实的例子
假设你说:「修复这个失败的测试」。pi 的 Agent Loop 可能这样跑:
第1轮 Think: 先看哪个测试挂了
Act: bash("npm test 2>&1 | tail -50")
Observe: validateEmail > 应拒绝无效邮箱 期望 false 收到 true
第2轮 Think: validateEmail 有 bug,定位源码
Act: grep("validateEmail", "src/")
Observe: src/utils/validator.ts:23
第3轮 Think: 读实现
Act: read("src/utils/validator.ts")
Observe: 正则漏了对 ".." 的校验
第4轮 Think: 改正则
Act: edit("src/utils/validator.ts", ...)
Observe: 文件已更新
第5轮 Think: 验证修复
Act: bash("npm test")
Observe: All tests passed ✓ → 无 toolCall,loop 结束五轮循环,每一轮都基于上一轮的观察做新决策。如果第 5 轮还失败,pi 会继续循环、换方案——这就是「自动错误恢复」。
与简单问答的区别
- 传统聊天机器人:输入 → 输出 → 结束。只能生成文本,不能执行操作。
- Agent Loop:输入 → 思考 → 执行工具 → 观察 → 再思考 → … → 返回结果。可以主动探索、实验、迭代。
正是这个循环,让 pi 从「语言模型」进化成「智能体」——面对开放式任务,自己找到路径。下一章我们会看到,在这个底层引擎之上,还有一个业务门面:AgentSession。