ZH

 


什么是 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 消息里 filtertoolCall。一个都没有,turn 就结束。
  • 执行工具executeToolCalls() 按并行 / 串行策略跑,结果作为 toolResult 推回 context.messages
  • 决定去留shouldStopAfterTurn hook 可以提前喊停;否则只要还有工具调用,就继续下一轮。

关键直觉:pi 的「行动」从来不是一次性的。每一次工具结果都会回灌进下一次思考——这正是它能像真正的开发者一样「试错→观察→修正」的原因。

真实源码:runLoop()

这不是伪代码——下面是 packages/agent/src/agent-loop.tsrunLoop() 的精简版。点击高亮行或右侧注解,看每一段在做什么:

📄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