AbortController 与 terminate 设计原理
1612 字
8 分钟
AbortController 与 terminate 设计原理
AbortController 与 terminate 设计原理
用户点击”停止”按钮时,发生了什么?
如果 LLM 正在响应 → AbortController.abort() 立即中断 HTTP 流 如果工具正在执行 → 工具返回 terminate: true 不继续下一轮
两种场景,两种机制,两种设计模式。
AbortController:预先注册模式
AbortController 不是”中途注入”,而是”预先注册”。
设计思想
想象一个快递系统:
你下单时告诉快递员:"如果我不想要了,打这个电话取消"││ 电话号码 = AbortSignal(预先注册的"取消通道")││ 快递员在送货过程中不断检查:"电话有没有被打?"││ 如果被打 → 停止送货 → 通知你"已取消"三个阶段
创建阶段(预先注册)
const abortCtrl = new AbortController();agent.run(input, { signal: abortCtrl.signal });Agent 内部:
- 持有 signal 引用(不是一次性传递,而是持续持有)
- 每次调用 LLM、每次读取数据,都带上这个 signal
运行阶段(持续检查)
// HTTP 请求层fetch(url, { signal });
// 底层内部while (读取数据) { if (signal.aborted) { // 每次循环都检查 throw AbortError; } // 继续读取...}取消阶段(触发信号)
// 用户点击"停止"abortCtrl.abort();
// 发生什么signal.aborted = true // 状态改变所有检查 signal.aborted 的地方 → "看到"变化 → 触发错误Agent 捕获错误 → 发出 error 事件 → Agent 结束关键:状态共享,而非消息传递
传统消息传递:用户发送”停止”消息 → Agent 收到消息 → 处理
AbortController 设计:用户改变 signal.aborted = true → Agent 持有的 signal 引用看到变化 → 自动触发错误
区别:
- 消息传递:需要 Agent 有接收逻辑
- AbortController:Agent 只需持有引用,底层自动检查
terminate:返回值携带模式
terminate 不是”注入”,而是”返回值自带标记”。
设计思想
想象餐厅用餐:
你吃完饭后,服务员问:"还需要点别的吗?"││ 如果你只是吃完一道菜:│ → 服务员:"好的,下一道菜"(继续)││ 如果你吃完了整个套餐:│ → 你说:"不用了,我吃完了"(terminate)│ → 服务员不继续推荐,直接结账││ "我吃完了"就是 terminate: true│ 它是你的"回答",不是服务员的"指令"三个阶段
工具执行阶段
async execute(id, args, signal, onUpdate) { // 执行工具逻辑...
return { content: [...], terminate: true // 工具自己决定 };}Agent 收集阶段
const results = [ { content: [...], terminate: true }, // 工具A { content: [...], terminate: false }, // 工具B];
// Agent 问:所有工具都说"我吃完了"吗?const allTerminate = results.every(r => r.terminate === true);
// 不全是 → 继续调用 LLM// 全是 → 结束本轮决策阶段
if (allTerminate) { // 所有工具都说"不需要继续了" // Agent 结束,不调用 LLM} else { // 还有工具需要继续 // Agent 用工具结果调用 LLM → 继续生成}关键:返回值携带决策信息
传统设计:Agent → 工具执行 → Agent 决定是否继续
terminate 设计:Agent → 工具执行 → 工具返回结果 + terminate 建议 → Agent 检查建议 → 决定是否继续
优势:
- 工具可以表达”我已完成最终输出”
- Agent 不需要猜测工具意图
- 批量工具时,所有工具都同意才停止(安全设计)
两种模式对比
特性对比
| 特性 | AbortController | terminate |
|---|---|---|
| 设计模式 | 预先注册 + 状态共享 | 返回值携带 |
| 注入时机 | 创建时注册,运行时检查 | 执行后返回,Agent收集检查 |
| 触发方式 | 外部改变状态 | 内部返回值携带 |
| 传播方式 | 引用共享(多处同时看到) | 值传递(逐层返回) |
| 生效时机 | 立即(下次检查时) | 执行完成后 |
| 作用对象 | HTTP请求、数据读取 | Agent轮次逻辑 |
| 事件表现 | error (reason: aborted) | 正常 tool_execution_end |
图示对比
AbortController(预先注册):
创建时 ──────────────────────────────────────→ 运行时 │ │ │ 注册 signal │ 检查 signal.aborted │ ↓ │ ↓ │ Agent 持有引用 │ 每个操作都检查 │ HTTP 持有引用 │ if (aborted) throw Error │ │ ↓ ↓用户调用 abort() Agent 收到错误 │ │ │ signal.aborted = true │ 发出 error 事件 │ │ Agent 结束 ↓ ↓terminate(返回值携带):
执行时 ──────────────────────────────────────→ 完成时 │ │ │ tool.execute() │ tool 返回结果 │ │ { terminate: true } │ │ ↓ ↓Agent 等待 Agent 收集所有结果 │ │ │ │ 检查 every(terminate) │ │ ↓ │ │ 决定是否继续 ↓ ↓Web Chat 停止功能实现
用户点击”停止”按钮时,同时触发两个机制:
两层停止机制
// 优雅停止(推荐)function handleGracefulCancel() { cancelRequested = true; // 标记取消意图}
// 强制停止(紧急情况)function handleForceCancel() { abortCtrl.abort(); // 立即中断 HTTP}AgentRunner 实现
class AgentRunner { private abortCtrl: AbortController | null = null; private cancelRequested = false; // 优雅取消标志
// 优雅取消:工具继续执行,完成后 Agent 终止 gracefulCancel() { this.cancelRequested = true; }
// 强制取消:立即中断 forceCancel() { this.abortCtrl?.abort(); }
async run(input: string) { this.abortCtrl = new AbortController(); this.cancelRequested = false;
const agent = new Agent({ model: { ... }, tools: [...],
// 关键:afterToolCall 检查取消状态 afterToolCall: async (context, signal) => { if (this.cancelRequested) { console.log("[优雅取消] 工具已完成,Agent 将终止"); return { content: context.result.content, // 保留工具结果 terminate: true, // 强制终止 }; } return undefined; // 不修改 }, });
const stream = agent.run(input, { signal: this.abortCtrl.signal }); // ... }}流程对比
优雅关闭:
gracefulCancel() → cancelRequested=true ↓工具正常执行完成 ↓afterToolCall 检查 → 修改 terminate: true ↓tool_execution_end (terminate: true) ↓every(terminate) → Agent 终止强制关闭:
forceCancel() → abort() ↓工具检查 signal.aborted → 立即中断 ↓message_update: error (reason: aborted) ↓Agent 直接结束适用场景
| Agent 状态 | 优雅停止 | 强制停止 |
|---|---|---|
| LLM 正在响应 | ✗(无法优雅) | ✓ 立即中断 |
| 工具正在执行 | ✓ 完成工具后终止 | ✓ 立即中断 |
| 空闲等待 | ✓ 无需处理 | ✓ 无需处理 |
总结
AbortController = 预先注册的”取消通道”
- 创建时注册,运行时检查状态
- 用户改变状态 → 所有检查点同时看到 → 自动触发错误
terminate = 返回值携带的”完成建议”
- 工具执行后返回 terminate: true
- Agent 收集所有返回值 → 检查 every(terminate) → 决定是否继续
核心区别:
- AbortController 是”外部中断”(用户主动触发)
- terminate 是”内部建议”(工具表达意图)
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!
AbortController 与 terminate 设计原理
https://firefly.cuteleaf.cn/posts/learn-pi/event&message/06-中断机制设计/ 相关文章 智能推荐
1
Message 与 Tool 的时序关系
pi 理解工具调用的两阶段设计:LLM生成参数与Agent执行工具的时序边界与协作方式
2
AgentEvent 系统分层设计理念
pi 理解 pi-agent-core 和 pi-ai 的分层设计,掌握 AgentEvent 与 AssistantMessageEvent 的职责边界
3
message_update 事件深入
pi 理解 message_update 如何连接高层 AgentEvent 和底层 AssistantMessageEvent,掌握两个关联字段的使用
4
AgentEvent 核心使用场景
pi 掌握流式输出、状态获取、工具处理、错误处理这四大核心场景,覆盖90%的开发需求
5
AgentEvent 类型系统详解
pi 深入理解 AgentEvent 的7种业务事件和 AssistantMessageEvent 的13种技术事件
随机文章 随机推荐