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架构设计分析/ 相关文章 智能推荐
1
Agent Loop 核心架构解析
pi 基于 agent-loop.ts 源码分析,理解双层循环架构、四大核心机制、五大问题抽象
2
AgentLoop 工具调用的设计问题与解决方案
pi 从开发者角度解析10个核心问题的具体解决方案,掌握并发控制、参数验证、中断取消、错误处理等关键技术
3
Agent Loop 核心机制详解
pi 深入理解双入口设计、双层循环架构、三种消息注入机制、工具执行模式
4
AgentLoop 工具调用的本质问题与核心原则
pi 从10个具体问题抽象出5个本质问题,推导出5个核心原则:声明式设计、传递式控制、结果式错误、Hook统一拦截、双层分离
5
AgentState 代理状态详解
pi 掌握代理的配置快照与运行时状态管理,理解系统提示、对话历史、流式输出的核心字段
随机文章 随机推荐