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 不需要猜测工具意图
  • 批量工具时,所有工具都同意才停止(安全设计)

两种模式对比#

特性对比#

特性AbortControllerterminate
设计模式预先注册 + 状态共享返回值携带
注入时机创建时注册,运行时检查执行后返回,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-中断机制设计/
作者
AltumSisy
发布于
2026-06-02
许可协议
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 天前

文章目录