Agent Loop 核心架构解析

1454 字
7 分钟
Agent Loop 核心架构解析

Agent Loop 核心架构解析#

基于 pi/packages/agent/src/agent-loop.ts 源码分析


一、核心功能架构#

┌─────────────────────────────────────────────────────────────┐
│ AGENT LOOP 架构图 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 入口层 │
│ ├─ agentLoop() → 新会话启动(添加prompts) │
│ └─ agentLoopContinue() → 继续会话(不添加新消息) │
│ │
│ ↓ │
│ 调度层: runAgentLoop / runAgentLoopContinue │
│ │
│ ↓ │
│ 核心层: runLoop() 【双层循环架构】 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 外层循环: 处理 follow-up 消息(用户在等待时输入) │ │
│ │ ──────────────────────────────────────────────── │ │
│ │ 内层循环: 处理 tool calls + steering messages │ │
│ │ │ │
│ │ ┌─ streamAssistantResponse() ← LLM交互边界 │ │
│ │ │ (AgentMessage[] → Message[] 转换) │ │
│ │ │ │ │
│ │ └─ executeToolCalls() │ │
│ │ ├─ executeToolCallsSequential() │ │
│ │ └─ executeToolCallsParallel() │ │
│ │ ├─ prepareToolCall() 准备阶段 │ │
│ │ ├─ executePreparedToolCall() 执行阶段 │ │
│ │ └─ finalizeExecutedToolCall() 收尾阶段 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 事件流: EventStream<AgentEvent> │
│ ├─ agent_start/agent_end │
│ ├─ turn_start/turn_end │
│ ├─ message_start/message_update/message_end │
│ └─ tool_execution_start/update/end │
│ │
└─────────────────────────────────────────────────────────────┘

四大核心机制#

机制职责关键代码位置
双层循环外层处理会话延续,内层处理单轮交互runLoop() L155-269
流式转换实时更新消息状态,支持打字机效果streamAssistantResponse() L275-368
边界转换AgentMessage ↔ Message 只在LLM调用处转换L293-295
工具执行策略支持并行/串行两种执行模式executeToolCalls() L373-388

二、问题抽象与解决原则#

问题 1:消息格式的双重身份#

维度内容
现象Agent内部使用AgentMessage,但LLM需要Message[]
抽象领域模型与外部协议的边界转换问题
解决streamAssistantResponse中统一转换,保持内部一致性

问题 2:用户输入的时机不确定性#

维度内容
现象用户可能在Agent处理工具时输入新消息(steering)
抽象异步输入与同步处理流的竞态条件
解决getSteeringMessages()钩子,在工具执行后检查新消息

问题 3:工具执行的并发控制#

维度内容
现象有些工具必须顺序执行(如文件读写),有些可以并行
抽象执行策略的可配置性需求
解决工具级别executionMode + 全局toolExecution配置

问题 4:流式响应的状态管理#

维度内容
现象流式过程中需要更新部分消息内容
抽象不可变状态与增量更新的平衡
解决partialMessage引用更新 + message_update事件

问题 5:工具生命周期的可观测性#

维度内容
现象需要追踪工具执行的全过程用于调试和UI
抽象执行过程的可观测性需求
解决完整的事件系统(start/update/end)

三、核心设计原则#

原则说明代码体现
单一转换边界内部统一格式,只在边界处转换L1注释: “Transforms to Message[] only at the LLM call boundary”
事件驱动架构所有状态变化通过事件通知AgentEventSink回调 + EventStream
可配置策略执行模式、钩子函数均可注入AgentLoopConfig配置对象
防御式编程每个关键节点检查abort信号多处signal?.aborted检查
三阶段工具执行准备→执行→收尾,支持拦截prepare/execute/finalize
引用更新模式流式过程中直接更新引用context.messages[context.messages.length - 1] = partialMessage

四、面试/分享核心要点#

一句话定位#

这是一个支持流式响应、双向循环、策略化工具执行的Agent运行时核心。

三句话讲架构#

  1. 双层循环:外层处理会话生命周期(支持用户插队输入),内层处理单轮对话(LLM↔Tool交互)
  2. 边界转换:内部使用AgentMessage,只在调用LLM时转换为Message,保持领域模型一致性
  3. 策略执行:工具支持串行/并行两种模式,通过三阶段生命周期(prepare/execute/finalize)实现可观测和可拦截

技术亮点(Q&A)#

Q1: 如何处理流式响应的状态管理?#

A: 使用partialMessage引用,通过message_update事件实时同步,最终用done事件替换为finalMessage,保证数据一致性。

// 关键代码片段
for await (const event of response) {
switch (event.type) {
case "text_delta":
case "toolcall_delta":
partialMessage = event.partial;
context.messages[context.messages.length - 1] = partialMessage;
await emit({ type: "message_update", message: { ...partialMessage } });
break;
// ...
}
}

Q2: 如何支持用户”插队”输入?#

A: 通过getSteeringMessages()钩子,在每轮工具执行后检查新消息,将新消息加入pendingMessages,在内层循环头部处理。

// 外层循环中的处理逻辑
let pendingMessages: AgentMessage[] = (await config.getSteeringMessages?.()) || [];
while (hasMoreToolCalls || pendingMessages.length > 0) {
if (pendingMessages.length > 0) {
// 处理用户插队消息
for (const message of pendingMessages) {
currentContext.messages.push(message);
}
pendingMessages = [];
}
// ...继续正常流程
}

Q3: 工具执行如何做到既灵活又可控?#

A: 三层机制:

  1. 全局配置 sequential/parallel
  2. 工具级别 executionMode
  3. before/after钩子 支持拦截和修改结果
// 配置接口
interface AgentLoopConfig {
toolExecution?: "sequential" | "parallel";
beforeToolCall?: (context: ToolCallContext, signal?: AbortSignal) => Promise<{ block?: boolean; reason?: string } | void>;
afterToolCall?: (context: ToolCallContext, signal?: AbortSignal) => Promise<Partial<ToolResult> | void>;
}

设计价值#

价值说明
可测试性事件流可完全mock,不依赖真实LLM
可观测性完整事件生命周期,支持实时UI更新
可扩展性配置驱动,钩子函数支持自定义行为

五、记忆口诀#

双层循环管会话,边界转换是关键
流式更新用引用,工具执行分三段
事件驱动全链路,配置钩子够灵活

六、关键函数速查#

函数行号职责
agentLoopL31-54新会话入口
agentLoopContinueL64-93继续会话入口
runAgentLoopL95-118新会话调度
runAgentLoopContinueL120-143继续会话调度
runLoopL155-269核心主循环
streamAssistantResponseL275-368流式LLM响应
executeToolCallsL373-388工具执行分发
executeToolCallsSequentialL395-449串行执行
executeToolCallsParallelL451-516并行执行
prepareToolCallL562-626工具准备阶段
executePreparedToolCallL628-663工具执行阶段
finalizeExecutedToolCallL665-708工具收尾阶段
shouldTerminateToolBatchL544-546终止条件判断

源码路径: pi/packages/agent/src/agent-loop.ts

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

赞助
Agent Loop 核心架构解析
https://firefly.cuteleaf.cn/posts/learn-pi/agent-loop/11-AgentLoop核心架构/
作者
AltumSisy
发布于
2026-06-13
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
AltumSisy
Hello, I'm AltumSisy.
公告
欢迎来到我的博客!这是一则示例公告。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
32
分类
3
标签
25
总字数
68,347
运行时长
0
最后活动
0 天前

文章目录