Loop与Agent 问题驱动的架构设计分析

2012 字
10 分钟
Loop与Agent 问题驱动的架构设计分析

agent-loop.ts 与 agent.ts:问题驱动的架构设计分析#

从”要解决什么问题”出发,抽象本质问题,推导设计原则,展示技术实现


一、表面问题 → 本质问题抽象#

表面问题列表#

应用层在使用 Agent 时遇到的具体痛点:

┌────────────────────────────────────────────────────────────────┐
│ 表面问题一:循环协调难 │
├────────────────────────────────────────────────────────────────┤
│ 具体痛点: │
│ - LLM 调用 → 工具执行 → 结果处理,谁来协调? │
│ - 工具执行完,是继续调用 LLM 还是停止? │
│ - 多轮对话的状态如何维护? │
│ - 工具执行失败了怎么处理? │
│ │
│ 用户的困惑: │
│ ❌ 每次都要手动写循环逻辑 │
│ ❌ 不清楚何时该停止,何时该继续 │
│ ❌ 状态散落各处,难以维护 │
└────────────────────────────────────────────────────────────────┘

本质问题抽象#

表面痛点只是症状,真正需要解决的是 七个本质问题

表面问题本质问题映射关系
循环协调难编排性手动编排 → 自动编排
消息格式混乱转换性到处转换 → 边界转换
工具执行复杂编排性 + 扩展性手动编排 + 无钩子 → 自动编排 + 钩子
状态不可观测状态性状态散落 → 统一管理
消息注入困难队列性直接修改 → 队列注入
事件通知缺失事件性无通知 → 实时通知
运行控制复杂控制性手动控制 → 自动管理

二、解决问题的技术方案 → 抽象核心原则#

对应七个本质问题,设计遵循 七个核心原则

原则一:自动编排原则(解决编排性)#

核心思想

  • “把循环编排自动化,让应用层只发起任务”

技术方案

agentLoop() 函数
- runLoop(): 主循环逻辑
- 自动协调 LLM工具结果继续或停止
- 应用层只需要调用一次

关键技术

  • 循环自动化:while (hasMoreToolCalls)
  • 条件判断:stopReason === "end_turn" → 继续
  • 事件发射:emit({ type: "turn_start/end" })

原则二:边界转换原则(解决转换性)#

核心思想

  • “在边界处转换,在内部统一,避免到处转换”

技术方案

streamAssistantResponse() 函数
- AgentMessage[] (内部)
- convertToLlm() → Message[] (边界)
- 调用统一 API
- 结果转换回 AgentMessage[]

关键技术

  • 转换函数:convertToLlm(messages)
  • 时机控制:只在 LLM 调用前转换
  • 可扩展:应用层可自定义转换逻辑

原则三:状态集中原则(解决状态性)#

核心思想

  • “状态集中管理,实时反映,随时访问”

技术方案

AgentState 结构
- messages: 消息历史
- tools: 工具列表
- isStreaming: 是否在生成
- streamingMessage: 当前生成内容
- pendingToolCalls: 正在执行的工具
- errorMessage: 错误信息

关键技术

  • getter/setter 模式:数组副本,避免直接修改
  • 实时更新:processEvents() 同步更新状态
  • 统一访问:agent.state.messages

原则四:队列模式原则(解决队列性)#

核心思想

  • “消息通过队列注入,时机和数量可配置”

技术方案

PendingMessageQueue
- enqueue(): 添加消息
- drain(): 取出消息
- mode: "one-at-a-time" | "all"
两种队列
- steeringQueue: 中途注入
- followUpQueue: 后续注入

关键技术

  • 队列分离:steering 和 follow-up 不同时机
  • drain 策略:“one-at-a-time” 或 “all”
  • 时机控制:getSteeringMessages / getFollowUpMessages

原则五:事件发射原则(解决事件性)#

核心思想

  • “所有生命周期事件实时发射,订阅者随时监听”

技术方案

AgentEvent 类型
- agent_start / agent_end
- turn_start / turn_end
- message_start / message_update / message_end
- tool_execution_start / tool_execution_end
subscribe(listener): 订阅机制

关键技术

  • 层次化事件:agent → turn → message → tool
  • 实时发射:流式生成时立即发射
  • 订阅分发:processEvents() 分发给所有订阅者

原则六:运行控制原则(解决控制性)#

核心思想

  • “运行对象统一管理启动、中断、等待、并发控制”

技术方案

ActiveRun 对象
- promise: 当前运行的 Promise
- abortController: 中断控制器
- resolve: Promise resolve
控制方法
- prompt(): 启动
- abort(): 中断
- waitForIdle(): 等待完成
- activeRun 检查: 防止并发

关键技术

  • AbortController: 中断控制
  • Promise 管理: waitForIdle
  • activeRun 检查: 防止并发

原则七:钩子扩展原则(解决扩展性)#

核心思想

  • “扩展点清晰且不侵入核心,通过钩子机制实现”

技术方案

AgentLoopConfig 配置
- beforeToolCall: 工具执行前钩子
- afterToolCall: 工具执行后钩子
- convertToLlm: 自定义消息转换
- prepareNextTurn: 下轮对话准备
- shouldStopAfterTurn: 停止条件判断
- transformContext: 上下文转换

关键技术

  • 钩子时机:before/after 在工具执行前后调用
  • 返回值控制:beforeResult?.block 可阻止执行
  • 结果修改:afterResult 可修改工具结果

三、原则到问题的映射#

本质问题核心原则关键技术解决什么
编排性自动编排原则agentLoop() + runLoop()自动协调 LLM → 工具 → 结果
转换性边界转换原则convertToLlm() + 边界时机AgentMessage → Message,在边界处
状态性状态集中原则AgentState + getter/setter统一管理、实时反映
队列性队列模式原则PendingMessageQueue + steering/followUp时机和数量可配置
事件性事件发射原则AgentEvent + subscribe()实时通知、层次化
控制性运行控制原则ActiveRun + AbortController启动、中断、等待、并发控制
扩展性钩子扩展原则before/after 钩子 + 自定义转换清晰扩展点,不侵入核心

四、分层职责清晰#

agent-loop.ts: 编排层 → 解决编排性、转换性、扩展性
agent.ts: 状态管理层 → 解决状态性、队列性、事件性、控制性

agent-loop.ts 解决

  • 编排性:自动协调循环
  • 转换性:边界处转换消息
  • 扩展性:钩子机制

agent.ts 解决

  • 状态性:统一管理状态
  • 队列性:消息注入队列
  • 事件性:实时通知订阅者
  • 控制性:运行对象管理

五、总结:问题 → 原则 → 实现#

三条主线#

┌─────────────────────────────────────────────────────────────────┐
│ 表面问题 本质问题 核心原则 实现 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 循环协调难 ───► 编排性 ───► 自动编排原则 ───► agentLoop() │
│ │
│ 消息格式混乱 ───► 转换性 ───► 边界转换原则 ───► convertToLlm │
│ │
│ 状态不可观测 ───► 状态性 ───► 状态集中原则 ───► AgentState │
│ │
│ 消息注入困难 ───► 队列性 ───► 队列模式原则 ───► PendingQueue│
│ │
│ 事件通知缺失 ───► 事件性 ───► 事件发射原则 ───► AgentEvent │
│ │
│ 运行控制复杂 ───► 控制性 ───► 运行控制原则 ───► ActiveRun │
│ │
│ 工具执行复杂 ───► 扩展性 ───► 钩子扩展原则 ───► before/after│
│ │
└─────────────────────────────────────────────────────────────────┘

核心启示#

1. 问题驱动设计

表面痛点 → 本质问题 → 核心原则 → 技术实现
  • 不要只解决表面症状,找到本质问题
  • 一个本质问题对应一个核心原则
  • 一个核心原则指导多个技术实现

2. 分层职责清晰

agent-loop.ts: 编排层 → 解决编排性、转换性、扩展性
agent.ts: 状态管理层 → 解决状态性、队列性、事件性、控制性
  • 每一层只解决一类问题
  • 不越界,不混杂

3. 边界转换原则

AgentMessage[] ──► Message[] ──► 统一 API
│ │
Agent 内部 边界转换
  • 在边界处转换,在内部统一
  • 避免到处转换

4. 自动化 vs 可扩展

自动编排: agentLoop() 自动协调循环
可扩展: 钩子机制 (before/after) 不侵入核心
  • 核心逻辑自动化,减少手动编排
  • 扩展点清晰,通过钩子实现

六、一句话总结#

  • agent-loop.ts 解决”如何协调循环”的问题,通过 自动编排原则边界转换原则,让应用层只发起任务,不手动编排。

  • agent.ts 解决”如何管理状态和控制运行”的问题,通过 状态集中原则队列模式原则事件发射原则运行控制原则,让状态可观测、消息可注入、事件可订阅、运行可控制。

支持与分享

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

赞助
Loop与Agent 问题驱动的架构设计分析
https://firefly.cuteleaf.cn/posts/learn-pi/agent-loop/15-Loop与Agent架构设计分析/
作者
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 天前

文章目录